/ 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]