/ adafruit_apds9960 / apds9960.py
apds9960.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2017 Michael McWethy for Adafruit Inc 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # The above copyright notice and this permission notice shall be included in 13 # all copies or substantial portions of the Software. 14 # 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 # THE SOFTWARE. 22 """ 23 `APDS9960` 24 ==================================================== 25 26 Driver class for the APDS9960 board. Supports gesture, proximity, and color 27 detection. 28 29 * Author(s): Michael McWethy 30 """ 31 import time 32 import digitalio 33 from adafruit_register.i2c_bits import RWBits 34 from adafruit_register.i2c_bit import RWBit 35 from adafruit_bus_device.i2c_device import I2CDevice 36 from micropython import const 37 38 39 # ADDRESS_DEF = const(0x39) 40 # INTEGRATION_TIME_DEF = const(0x01) 41 # GAIN_DEF = const(0x01) 42 43 #pylint: disable-msg=bad-whitespace 44 #APDS9960_RAM = const(0x00) 45 APDS9960_ENABLE = const(0x80) 46 APDS9960_ATIME = const(0x81) 47 #APDS9960_WTIME = const(0x83) 48 #APDS9960_AILTIL = const(0x84) 49 # APDS9960_AILTH = const(0x85) 50 # APDS9960_AIHTL = const(0x86) 51 # APDS9960_AIHTH = const(0x87) 52 APDS9960_PILT = const(0x89) 53 APDS9960_PIHT = const(0x8B) 54 APDS9960_PERS = const(0x8C) 55 # APDS9960_CONFIG1 = const(0x8D) 56 # APDS9960_PPULSE = const(0x8E) 57 APDS9960_CONTROL = const(0x8F) 58 # APDS9960_CONFIG2 = const(0x90) 59 APDS9960_ID = const(0x92) 60 APDS9960_STATUS = const(0x93) 61 APDS9960_CDATAL = const(0x94) 62 # APDS9960_CDATAH = const(0x95) 63 # APDS9960_RDATAL = const(0x96) 64 # APDS9960_RDATAH = const(0x97) 65 # APDS9960_GDATAL = const(0x98) 66 # APDS9960_GDATAH = const(0x99) 67 # APDS9960_BDATAL = const(0x9A) 68 # APDS9960_BDATAH = const(0x9B) 69 APDS9960_PDATA = const(0x9C) 70 # APDS9960_POFFSET_UR = const(0x9D) 71 # APDS9960_POFFSET_DL = const(0x9E) 72 # APDS9960_CONFIG3 = const(0x9F) 73 APDS9960_GPENTH = const(0xA0) 74 # APDS9960_GEXTH = const(0xA1) 75 APDS9960_GCONF1 = const(0xA2) 76 APDS9960_GCONF2 = const(0xA3) 77 # APDS9960_GOFFSET_U = const(0xA4) 78 # APDS9960_GOFFSET_D = const(0xA5) 79 # APDS9960_GOFFSET_L = const(0xA7) 80 # APDS9960_GOFFSET_R = const(0xA9) 81 APDS9960_GPULSE = const(0xA6) 82 APDS9960_GCONF3 = const(0xAA) 83 APDS9960_GCONF4 = const(0xAB) 84 APDS9960_GFLVL = const(0xAE) 85 APDS9960_GSTATUS = const(0xAF) 86 # APDS9960_IFORCE = const(0xE4) 87 # APDS9960_PICLEAR = const(0xE5) 88 # APDS9960_CICLEAR = const(0xE6) 89 APDS9960_AICLEAR = const(0xE7) 90 APDS9960_GFIFO_U = const(0xFC) 91 # APDS9960_GFIFO_D = const(0xFD) 92 # APDS9960_GFIFO_L = const(0xFE) 93 # APDS9960_GFIFO_R = const(0xFF) 94 #pylint: enable-msg=bad-whitespace 95 96 97 #pylint: disable-msg=too-many-instance-attributes 98 class APDS9960: 99 """ 100 APDS9900 provide basic driver services for the ASDS9960 breakout board 101 """ 102 103 _gesture_enable = RWBit(APDS9960_ENABLE, 6) 104 _gesture_valid = RWBit(APDS9960_GSTATUS, 0) 105 _gesture_mode = RWBit(APDS9960_GCONF4, 0) 106 _proximity_persistance = RWBits(4, APDS9960_PERS, 4) 107 108 def __init__(self, 109 i2c, *, 110 interrupt_pin=None, 111 address=0x39, 112 integration_time=0x01, 113 gain=0x01): 114 115 self.buf129 = None 116 self.buf2 = bytearray(2) 117 118 self.i2c_device = I2CDevice(i2c, address) 119 self._interrupt_pin = interrupt_pin 120 if interrupt_pin: 121 self._interrupt_pin.switch_to_input(pull=digitalio.Pull.UP) 122 123 if self._read8(APDS9960_ID) != 0xAB: 124 raise RuntimeError() 125 126 self.enable_gesture = False 127 self.enable_proximity = False 128 self.enable_color = False 129 self.enable_proximity_interrupt = False 130 self.clear_interrupt() 131 132 self.enable = False 133 time.sleep(0.010) 134 self.enable = True 135 time.sleep(0.010) 136 137 self.color_gain = gain 138 self.integration_time = integration_time 139 self.gesture_dimensions = 0x00 # all 140 self.gesture_fifo_threshold = 0x01 # fifo 4 141 self.gesture_gain = 0x02 # gain 4 142 self.gesture_proximity_threshold = 50 143 self._reset_counts() 144 145 # gesture pulse length=0x2 pulse count=0x3 146 self._write8(APDS9960_GPULSE, (0x2 << 6) | 0x3) 147 148 ## BOARD 149 def _reset_counts(self): 150 """Gesture detection internal counts""" 151 self._saw_down_start = 0 152 self._saw_up_start = 0 153 self._saw_left_start = 0 154 self._saw_right_start = 0 155 156 157 enable = RWBit(APDS9960_ENABLE, 0) 158 """Board enable. True to enable, False to disable""" 159 enable_color = RWBit(APDS9960_ENABLE, 1) 160 """Color detection enable flag. 161 True when color detection is enabled, else False""" 162 enable_proximity = RWBit(APDS9960_ENABLE, 2) 163 """Enable of proximity mode""" 164 gesture_fifo_threshold = RWBits(2, APDS9960_GCONF1, 6) 165 """Gesture fifo threshold value: range 0-3""" 166 gesture_gain = RWBits(2, APDS9960_GCONF2, 5) 167 """Gesture gain value: range 0-3""" 168 color_gain = RWBits(2, APDS9960_CONTROL, 0) 169 """Color gain value""" 170 enable_proximity_interrupt = RWBit(APDS9960_ENABLE, 5) 171 """Proximity interrupt enable flag. True if enabled, 172 False to disable""" 173 174 ## GESTURE DETECTION 175 @property 176 def enable_gesture(self): 177 """Gesture detection enable flag. True to enable, False to disable. 178 Note that when disabled, gesture mode is turned off""" 179 return self._gesture_enable 180 181 @enable_gesture.setter 182 def enable_gesture(self, enable_flag): 183 if not enable_flag: 184 self._gesture_mode = False 185 self._gesture_enable = enable_flag 186 187 def gesture(self): #pylint: disable-msg=too-many-branches 188 """Returns gesture code if detected. =0 if no gesture detected 189 =1 if an UP, =2 if a DOWN, =3 if an LEFT, =4 if a RIGHT 190 """ 191 # buffer to read of contents of device FIFO buffer 192 if not self.buf129: 193 self.buf129 = bytearray(129) 194 195 buffer = self.buf129 196 buffer[0] = APDS9960_GFIFO_U 197 if not self._gesture_valid: 198 return 0 199 200 time_mark = 0 201 gesture_received = 0 202 while True: 203 204 up_down_diff = 0 205 left_right_diff = 0 206 gesture_received = 0 207 time.sleep(0.030) # 30 ms 208 209 n_recs = self._read8(APDS9960_GFLVL) 210 if n_recs: 211 212 with self.i2c_device as i2c: 213 i2c.write(buffer, end=1, stop=False) 214 i2c.readinto(buffer, start=1, end=min(129, 1 + n_recs * 4)) 215 upp, down, left, right = buffer[1:5] 216 217 if abs(upp - down) > 13: 218 up_down_diff = upp - down 219 220 if abs(left - right) > 13: 221 left_right_diff = left - right 222 223 if up_down_diff != 0: 224 if up_down_diff < 0: 225 # either leading edge of down movement 226 # or trailing edge of up movement 227 if self._saw_up_start: 228 gesture_received = 0x01 # up 229 else: 230 self._saw_down_start += 1 231 elif up_down_diff > 0: 232 # either leading edge of up movement 233 # or trailing edge of down movement 234 if self._saw_down_start: 235 gesture_received = 0x02 # down 236 else: 237 self._saw_up_start += 1 238 239 if left_right_diff != 0: 240 if left_right_diff < 0: 241 # either leading edge of right movement 242 # trailing edge of left movement 243 if self._saw_left_start: 244 gesture_received = 0x03 # left 245 else: 246 self._saw_right_start += 1 247 elif left_right_diff > 0: 248 # either leading edge of left movement 249 # trailing edge of right movement 250 if self._saw_right_start: 251 gesture_received = 0x04 #right 252 else: 253 self._saw_left_start += 1 254 255 # saw a leading or trailing edge; start timer 256 if up_down_diff or left_right_diff: 257 time_mark = time.monotonic() 258 259 # finished when a gesture is detected or ran out of time (300ms) 260 if gesture_received or time.monotonic() - time_mark > 0.300: 261 self._reset_counts() 262 break 263 264 return gesture_received 265 266 @property 267 def gesture_dimensions(self): 268 """Gesture dimension value: range 0-3""" 269 return self._read8(APDS9960_GCONF3) 270 271 @gesture_dimensions.setter 272 def gesture_dimensions(self, dims): 273 self._write8(APDS9960_GCONF3, dims & 0x03) 274 275 @property 276 def color_data_ready(self): 277 """Color data ready flag. zero if not ready, 1 is ready""" 278 return self._read8(APDS9960_STATUS) & 0x01 279 280 @property 281 def color_data(self): 282 """Tuple containing r, g, b, c values""" 283 return self._color_data16(APDS9960_CDATAL + 2), \ 284 self._color_data16(APDS9960_CDATAL + 4), \ 285 self._color_data16(APDS9960_CDATAL + 6), \ 286 self._color_data16(APDS9960_CDATAL) 287 288 ### PROXIMITY 289 @property 290 def proximity_interrupt_threshold(self): 291 """Tuple containing low and high threshold 292 followed by the proximity interrupt persistance. 293 When setting the proximity interrupt threshold values using a tuple of 294 zero to three values: low threshold, high threshold, persistance. 295 persistance defaults to 4 if not provided""" 296 return self._read8(APDS9960_PILT), \ 297 self._read8(APDS9960_PIHT), \ 298 self._proximity_persistance 299 300 @proximity_interrupt_threshold.setter 301 def proximity_interrupt_threshold(self, setting_tuple): 302 if setting_tuple: 303 self._write8(APDS9960_PILT, setting_tuple[0]) 304 if len(setting_tuple) > 1: 305 self._write8(APDS9960_PIHT, setting_tuple[1]) 306 persist = 4 # default 4 307 if len(setting_tuple) > 2: 308 persist = min(setting_tuple[2], 7) 309 self._proximity_persistance = persist 310 311 312 @property 313 def gesture_proximity_threshold(self): 314 """Proximity threshold value: range 0-255""" 315 return self._read8(APDS9960_GPENTH) 316 317 @gesture_proximity_threshold.setter 318 def gesture_proximity_threshold(self, thresh): 319 self._write8(APDS9960_GPENTH, thresh & 0xff) 320 321 def proximity(self): 322 """proximity value: range 0-255""" 323 return self._read8(APDS9960_PDATA) 324 325 def clear_interrupt(self): 326 """Clear all interrupts""" 327 self._writecmdonly(APDS9960_AICLEAR) 328 329 @property 330 def integration_time(self): 331 """Proximity integration time: range 0-255""" 332 return self._read8(APDS9960_ATIME) 333 334 @integration_time.setter 335 def integration_time(self, int_time): 336 self._write8(APDS9960_ATIME, int_time & 0xff) 337 338 # method for reading and writing to I2C 339 def _write8(self, command, abyte): 340 """Write a command and 1 byte of data to the I2C device""" 341 buf = self.buf2 342 buf[0] = command 343 buf[1] = abyte 344 with self.i2c_device as i2c: 345 i2c.write(buf) 346 347 def _writecmdonly(self, command): 348 """Writes a command and 0 bytes of data to the I2C device""" 349 buf = self.buf2 350 buf[0] = command 351 with self.i2c_device as i2c: 352 i2c.write(buf, end=1) 353 354 def _read8(self, command): 355 """Sends a command and reads 1 byte of data from the I2C device""" 356 buf = self.buf2 357 buf[0] = command 358 with self.i2c_device as i2c: 359 i2c.write(buf, end=1) 360 i2c.readinto(buf, end=1) 361 return buf[0] 362 363 def _color_data16(self, command): 364 """Sends a command and reads 2 bytes of data from the I2C device 365 The returned data is low byte first followed by high byte""" 366 buf = self.buf2 367 buf[0] = command 368 with self.i2c_device as i2c: 369 i2c.write(buf, end=1, stop=False) 370 i2c.readinto(buf) 371 return buf[1] << 8 | buf[0]