From 424e72cd30ae3279f82480ca599626f79fa7995e Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Sun, 14 Nov 2021 18:38:13 -0600 Subject: [PATCH 01/14] Reducing memory footprint by using _naming for purely internal constants --- adafruit_apds9960/apds9960.py | 104 +++++++++++++++++----------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index edea299..216e0b0 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -56,23 +56,23 @@ # GAIN_DEF = const(0x01) # APDS9960_RAM = const(0x00) -APDS9960_ENABLE = const(0x80) -APDS9960_ATIME = const(0x81) +_APDS9960_ENABLE = const(0x80) +_APDS9960_ATIME = const(0x81) # APDS9960_WTIME = const(0x83) # APDS9960_AILTIL = const(0x84) # APDS9960_AILTH = const(0x85) # APDS9960_AIHTL = const(0x86) # APDS9960_AIHTH = const(0x87) -APDS9960_PILT = const(0x89) -APDS9960_PIHT = const(0x8B) -APDS9960_PERS = const(0x8C) +_APDS9960_PILT = const(0x89) +_APDS9960_PIHT = const(0x8B) +_APDS9960_PERS = const(0x8C) # APDS9960_CONFIG1 = const(0x8D) # APDS9960_PPULSE = const(0x8E) -APDS9960_CONTROL = const(0x8F) +_APDS9960_CONTROL = const(0x8F) # APDS9960_CONFIG2 = const(0x90) -APDS9960_ID = const(0x92) -APDS9960_STATUS = const(0x93) -APDS9960_CDATAL = const(0x94) +_APDS9960_ID = const(0x92) +_APDS9960_STATUS = const(0x93) +_APDS9960_CDATAL = const(0x94) # APDS9960_CDATAH = const(0x95) # APDS9960_RDATAL = const(0x96) # APDS9960_RDATAH = const(0x97) @@ -80,28 +80,28 @@ # APDS9960_GDATAH = const(0x99) # APDS9960_BDATAL = const(0x9A) # APDS9960_BDATAH = const(0x9B) -APDS9960_PDATA = const(0x9C) +_APDS9960_PDATA = const(0x9C) # APDS9960_POFFSET_UR = const(0x9D) # APDS9960_POFFSET_DL = const(0x9E) # APDS9960_CONFIG3 = const(0x9F) -APDS9960_GPENTH = const(0xA0) +_APDS9960_GPENTH = const(0xA0) # APDS9960_GEXTH = const(0xA1) -APDS9960_GCONF1 = const(0xA2) -APDS9960_GCONF2 = const(0xA3) +_APDS9960_GCONF1 = const(0xA2) +_APDS9960_GCONF2 = const(0xA3) # APDS9960_GOFFSET_U = const(0xA4) # APDS9960_GOFFSET_D = const(0xA5) # APDS9960_GOFFSET_L = const(0xA7) # APDS9960_GOFFSET_R = const(0xA9) -APDS9960_GPULSE = const(0xA6) -APDS9960_GCONF3 = const(0xAA) -APDS9960_GCONF4 = const(0xAB) -APDS9960_GFLVL = const(0xAE) -APDS9960_GSTATUS = const(0xAF) +_APDS9960_GPULSE = const(0xA6) +_APDS9960_GCONF3 = const(0xAA) +_APDS9960_GCONF4 = const(0xAB) +_APDS9960_GFLVL = const(0xAE) +_APDS9960_GSTATUS = const(0xAF) # APDS9960_IFORCE = const(0xE4) # APDS9960_PICLEAR = const(0xE5) # APDS9960_CICLEAR = const(0xE6) -APDS9960_AICLEAR = const(0xE7) -APDS9960_GFIFO_U = const(0xFC) +_APDS9960_AICLEAR = const(0xE7) +_APDS9960_GFIFO_U = const(0xFC) # APDS9960_GFIFO_D = const(0xFD) # APDS9960_GFIFO_L = const(0xFE) # APDS9960_GFIFO_R = const(0xFF) @@ -144,10 +144,10 @@ class APDS9960: """ - _gesture_enable = RWBit(APDS9960_ENABLE, 6) - _gesture_valid = RWBit(APDS9960_GSTATUS, 0) - _gesture_mode = RWBit(APDS9960_GCONF4, 0) - _proximity_persistance = RWBits(4, APDS9960_PERS, 4) + _gesture_enable = RWBit(_APDS9960_ENABLE, 6) + _gesture_valid = RWBit(_APDS9960_GSTATUS, 0) + _gesture_mode = RWBit(_APDS9960_GCONF4, 0) + _proximity_persistance = RWBits(4, _APDS9960_PERS, 4) def __init__( self, @@ -164,7 +164,7 @@ def __init__( self.i2c_device = I2CDevice(i2c, address) - if self._read8(APDS9960_ID) != 0xAB: + if self._read8(_APDS9960_ID) != 0xAB: raise RuntimeError() self.enable_gesture = False @@ -188,7 +188,7 @@ def __init__( self._reset_counts() # gesture pulse length=0x2 pulse count=0x3 - self._write8(APDS9960_GPULSE, (0x2 << 6) | 0x3) + self._write8(_APDS9960_GPULSE, (0x2 << 6) | 0x3) ## BOARD def _reset_counts(self) -> None: @@ -198,20 +198,20 @@ def _reset_counts(self) -> None: self._saw_left_start = 0 self._saw_right_start = 0 - enable = RWBit(APDS9960_ENABLE, 0) + enable = RWBit(_APDS9960_ENABLE, 0) """Board enable. True to enable, False to disable""" - enable_color = RWBit(APDS9960_ENABLE, 1) + enable_color = RWBit(_APDS9960_ENABLE, 1) """Color detection enable flag. True when color detection is enabled, else False""" - enable_proximity = RWBit(APDS9960_ENABLE, 2) + enable_proximity = RWBit(_APDS9960_ENABLE, 2) """Enable of proximity mode""" - gesture_fifo_threshold = RWBits(2, APDS9960_GCONF1, 6) + gesture_fifo_threshold = RWBits(2, _APDS9960_GCONF1, 6) """Gesture fifo threshold value: range 0-3""" - gesture_gain = RWBits(2, APDS9960_GCONF2, 5) + gesture_gain = RWBits(2, _APDS9960_GCONF2, 5) """Gesture gain value: range 0-3""" - color_gain = RWBits(2, APDS9960_CONTROL, 0) + color_gain = RWBits(2, _APDS9960_CONTROL, 0) """Color gain value""" - enable_proximity_interrupt = RWBit(APDS9960_ENABLE, 5) + enable_proximity_interrupt = RWBit(_APDS9960_ENABLE, 5) """Proximity interrupt enable flag. True if enabled, False to disable""" @@ -256,7 +256,7 @@ def gesture(self) -> int: # pylint: disable-msg=too-many-branches self.buf129 = bytearray(129) buffer = self.buf129 - buffer[0] = APDS9960_GFIFO_U + buffer[0] = _APDS9960_GFIFO_U if not self._gesture_valid: return 0 @@ -269,7 +269,7 @@ def gesture(self) -> int: # pylint: disable-msg=too-many-branches gesture_received = 0 time.sleep(0.030) # 30 ms - n_recs = self._read8(APDS9960_GFLVL) + n_recs = self._read8(_APDS9960_GFLVL) if n_recs: with self.i2c_device as i2c: @@ -336,25 +336,25 @@ def gesture(self) -> int: # pylint: disable-msg=too-many-branches @property def gesture_dimensions(self) -> int: """Gesture dimension value: range 0-3""" - return self._read8(APDS9960_GCONF3) + return self._read8(_APDS9960_GCONF3) @gesture_dimensions.setter def gesture_dimensions(self, dims: int) -> None: - self._write8(APDS9960_GCONF3, dims & 0x03) + self._write8(_APDS9960_GCONF3, dims & 0x03) @property def color_data_ready(self) -> int: """Color data ready flag. zero if not ready, 1 is ready""" - return self._read8(APDS9960_STATUS) & 0x01 + return self._read8(_APDS9960_STATUS) & 0x01 @property def color_data(self) -> Tuple[int, int, int, int]: """Tuple containing r, g, b, c values""" return ( - self._color_data16(APDS9960_CDATAL + 2), - self._color_data16(APDS9960_CDATAL + 4), - self._color_data16(APDS9960_CDATAL + 6), - self._color_data16(APDS9960_CDATAL), + self._color_data16(_APDS9960_CDATAL + 2), + self._color_data16(_APDS9960_CDATAL + 4), + self._color_data16(_APDS9960_CDATAL + 6), + self._color_data16(_APDS9960_CDATAL), ) ### PROXIMITY @@ -366,17 +366,17 @@ def proximity_interrupt_threshold(self) -> Tuple[int, int, int]: zero to three values: low threshold, high threshold, persistance. persistance defaults to 4 if not provided""" return ( - self._read8(APDS9960_PILT), - self._read8(APDS9960_PIHT), + self._read8(_APDS9960_PILT), + self._read8(_APDS9960_PIHT), self._proximity_persistance, ) @proximity_interrupt_threshold.setter def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: if setting_tuple: - self._write8(APDS9960_PILT, setting_tuple[0]) + self._write8(_APDS9960_PILT, setting_tuple[0]) if len(setting_tuple) > 1: - self._write8(APDS9960_PIHT, setting_tuple[1]) + self._write8(_APDS9960_PIHT, setting_tuple[1]) persist = 4 # default 4 if len(setting_tuple) > 2: persist = min(setting_tuple[2], 7) @@ -385,29 +385,29 @@ def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: @property def gesture_proximity_threshold(self) -> int: """Proximity threshold value: range 0-255""" - return self._read8(APDS9960_GPENTH) + return self._read8(_APDS9960_GPENTH) @gesture_proximity_threshold.setter def gesture_proximity_threshold(self, thresh: int) -> None: - self._write8(APDS9960_GPENTH, thresh & 0xFF) + self._write8(_APDS9960_GPENTH, thresh & 0xFF) @property def proximity(self) -> int: """Proximity value: range 0-255""" - return self._read8(APDS9960_PDATA) + return self._read8(_APDS9960_PDATA) def clear_interrupt(self) -> None: """Clear all interrupts""" - self._writecmdonly(APDS9960_AICLEAR) + self._writecmdonly(_APDS9960_AICLEAR) @property def integration_time(self) -> int: """Proximity integration time: range 0-255""" - return self._read8(APDS9960_ATIME) + return self._read8(_APDS9960_ATIME) @integration_time.setter def integration_time(self, int_time: int) -> None: - self._write8(APDS9960_ATIME, int_time & 0xFF) + self._write8(_APDS9960_ATIME, int_time & 0xFF) # method for reading and writing to I2C def _write8(self, command: int, abyte: int) -> None: From 30eeadd62628d9747e34c4623bfa29a36bd8b7da Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Sun, 28 Nov 2021 16:06:15 -0600 Subject: [PATCH 02/14] optimization testing: Replacd adafruit_register lib with internals and constants --- adafruit_apds9960/apds9960.py | 192 ++++++++++++++++++++++++++++++---- 1 file changed, 170 insertions(+), 22 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index 216e0b0..103580b 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -36,8 +36,6 @@ * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice """ import time -from adafruit_register.i2c_bits import RWBits -from adafruit_register.i2c_bit import RWBit from adafruit_bus_device.i2c_device import I2CDevice from micropython import const @@ -106,6 +104,27 @@ # APDS9960_GFIFO_L = const(0xFE) # APDS9960_GFIFO_R = const(0xFF) +_BIT_MASK_ENABLE_EN = const(0x01) +_BIT_MASK_ENABLE_PROX = const(0x04) +_BIT_MASK_ENABLE_PROX_INT = const(0x10) +_BIT_MASK_ENABLE_GESTURE = const(0x20) +_BIT_MASK_ENABLE_COLOR = const(0x02) + +_BIT_MASK_GSTATUS_GVALID = const(0x01) + +_BIT_MASK_GCONF4_GMODE = const(0x01) + +_BIT_POSITON_PERS_PPERS = const(4) +_BIT_MASK_PERS_PPERS = const(0xF0) + +_BIT_POSITON_GCONF1_GFIFOTH = const(6) +_BIT_MASK_GCONF1_GFIFOTH = const(0xC0) + +_BIT_POSITON_GCONF2_GGAIN = const(5) +_BIT_MASK_GCONF2_GGAIN = const(0x60) + +_BIT_POSITON_CONTROL_AGAIN = const(0) +_BIT_MASK_CONTROL_AGAIN = const(0x03) # pylint: disable-msg=too-many-instance-attributes class APDS9960: @@ -144,10 +163,42 @@ class APDS9960: """ - _gesture_enable = RWBit(_APDS9960_ENABLE, 6) - _gesture_valid = RWBit(_APDS9960_GSTATUS, 0) - _gesture_mode = RWBit(_APDS9960_GCONF4, 0) - _proximity_persistance = RWBits(4, _APDS9960_PERS, 4) + # _gesture_enable = RWBit(_APDS9960_ENABLE, 6) + + @property + def _gesture_enable(self) -> bool: + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE) + + @_gesture_enable.setter + def _gesture_enable(self, value: bool) -> None: + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE, value) + + # _gesture_mode = RWBit(_APDS9960_GCONF4, 0) + + @property + def _gesture_mode(self) -> bool: + return self._get_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GMODE) + + @_gesture_mode.setter + def _gesture_mode(self, value: bool) -> None: + self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GMODE, value) + + # _gesture_valid = RWBit(_APDS9960_GSTATUS, 0) + + @property + def _gesture_valid(self) -> bool: + return self._get_bit(_APDS9960_GSTATUS, _BIT_MASK_GSTATUS_GVALID) + + # _proximity_persistance = RWBits(4, _APDS9960_PERS, 4) + + @property + def _proximity_persistance(self) -> int: + self._get_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS) + + @_proximity_persistance.setter + def _proximity_persistance(self, value: int) -> None: + self._set_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS, value) + def __init__( self, @@ -198,22 +249,84 @@ def _reset_counts(self) -> None: self._saw_left_start = 0 self._saw_right_start = 0 - enable = RWBit(_APDS9960_ENABLE, 0) - """Board enable. True to enable, False to disable""" - enable_color = RWBit(_APDS9960_ENABLE, 1) - """Color detection enable flag. - True when color detection is enabled, else False""" - enable_proximity = RWBit(_APDS9960_ENABLE, 2) - """Enable of proximity mode""" - gesture_fifo_threshold = RWBits(2, _APDS9960_GCONF1, 6) - """Gesture fifo threshold value: range 0-3""" - gesture_gain = RWBits(2, _APDS9960_GCONF2, 5) - """Gesture gain value: range 0-3""" - color_gain = RWBits(2, _APDS9960_CONTROL, 0) - """Color gain value""" - enable_proximity_interrupt = RWBit(_APDS9960_ENABLE, 5) - """Proximity interrupt enable flag. True if enabled, - False to disable""" + # enable = RWBit(_APDS9960_ENABLE, 0) + + @property + def enable(self) -> bool: + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN) + + @enable.setter + def enable(self, value: bool) -> None: + """Board enable. True to enable, False to disable""" + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN, value) + + # enable_color = RWBit(_APDS9960_ENABLE, 1) + + @property + def enable_color(self) -> bool: + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR) + + @enable_color.setter + def enable_color(self, value: bool) -> None: + """Color detection enable flag. + True when color detection is enabled, else False""" + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR, value) + + # enable_proximity = RWBit(_APDS9960_ENABLE, 2) + + @property + def enable_proximity(self) -> bool: + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX) + + @enable_proximity.setter + def enable_proximity(self, value: bool) -> None: + """Enable of proximity mode""" + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX, value) + + # gesture_fifo_threshold = RWBits(2, _APDS9960_GCONF1, 6) + + @property + def gesture_fifo_threshold(self) -> int: + self._get_bits(_APDS9960_GCONF1, _BIT_POSITON_GCONF1_GFIFOTH, _BIT_MASK_GCONF1_GFIFOTH) + + @gesture_fifo_threshold.setter + def gesture_fifo_threshold(self, value: int) -> None: + """Gesture fifo threshold value: range 0-3""" + self._set_bits(_APDS9960_GCONF1, _BIT_POSITON_GCONF1_GFIFOTH, _BIT_MASK_GCONF1_GFIFOTH, value) + + # gesture_gain = RWBits(2, _APDS9960_GCONF2, 5) + + @property + def gesture_gain(self) -> int: + self._get_bits(_APDS9960_GCONF2, _BIT_POSITON_GCONF2_GGAIN, _BIT_MASK_GCONF2_GGAIN) + + @gesture_gain.setter + def gesture_gain(self, value: int) -> None: + """Gesture gain value: range 0-3""" + self._set_bits(_APDS9960_GCONF2, _BIT_POSITON_GCONF2_GGAIN, _BIT_MASK_GCONF2_GGAIN, value) + + # color_gain = RWBits(2, _APDS9960_CONTROL, 0) + + @property + def color_gain(self) -> int: + self._get_bits(_APDS9960_CONTROL, _BIT_POSITON_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN) + + @gesture_gain.setter + def color_gain(self, value: int) -> None: + """Color gain value""" + self._set_bits(_APDS9960_CONTROL, _BIT_POSITON_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN, value) + + # enable_proximity_interrupt = RWBit(_APDS9960_ENABLE, 5) + + @property + def enable_proximity_interrupt(self) -> bool: + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX_INT) + + @enable_proximity_interrupt.setter + def enable_proximity_interrupt(self, value: bool) -> None: + """Proximity interrupt enable flag. True if enabled, + False to disable""" + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX_INT, value) ## GESTURE ROTATION @property @@ -433,6 +546,41 @@ def _read8(self, command: int) -> int: i2c.write_then_readinto(buf, buf, out_end=1, in_end=1) return buf[0] + def _get_bit(self, register: int, bitmask: int) -> int: + buf = self.buf2 + buf[0] = register + with self.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + return (buf[1] & bitmask) + + def _set_bit(self, register: int, bitmask: int, value: bool) -> None: + buf = self.buf2 + buf[0] = register + with self.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + if value: + buf[1] |= bitmask + else: + buf[1] &= bitmask + with self.i2c_device as i2c: + i2c.write(buf, end=1) + + def _get_bits(self, register: int, bit_position: int, bit_mask: int) -> int: + buf = self.buf2 + buf[0] = register + with self.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + return (buf[1] & bit_mask) >> bit_position + + def _set_bits(self, register: int, bit_position: int, bit_mask: int, value: int) -> None: + buf = self.buf2 + buf[0] = register + with self.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + buf[1] = (buf[1] & ~bit_mask) | (value << bit_position) + with self.i2c_device as i2c: + i2c.write(buf, end=1) + def _color_data16(self, command: int) -> int: """Sends a command and reads 2 bytes of data from the I2C device The returned data is low byte first followed by high byte""" From 9e335a00c8ae3f8ad74990ccff6798c54e003784 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Sun, 28 Nov 2021 16:44:40 -0600 Subject: [PATCH 03/14] optimization testing: Removing non-basic functions --- adafruit_apds9960/apds9960.py | 126 ++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index 103580b..d84333d 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -55,7 +55,7 @@ # APDS9960_RAM = const(0x00) _APDS9960_ENABLE = const(0x80) -_APDS9960_ATIME = const(0x81) +# _APDS9960_ATIME = const(0x81) # APDS9960_WTIME = const(0x83) # APDS9960_AILTIL = const(0x84) # APDS9960_AILTH = const(0x85) @@ -66,7 +66,7 @@ _APDS9960_PERS = const(0x8C) # APDS9960_CONFIG1 = const(0x8D) # APDS9960_PPULSE = const(0x8E) -_APDS9960_CONTROL = const(0x8F) +# _APDS9960_CONTROL = const(0x8F) # APDS9960_CONFIG2 = const(0x90) _APDS9960_ID = const(0x92) _APDS9960_STATUS = const(0x93) @@ -82,7 +82,7 @@ # APDS9960_POFFSET_UR = const(0x9D) # APDS9960_POFFSET_DL = const(0x9E) # APDS9960_CONFIG3 = const(0x9F) -_APDS9960_GPENTH = const(0xA0) +# _APDS9960_GPENTH = const(0xA0) # APDS9960_GEXTH = const(0xA1) _APDS9960_GCONF1 = const(0xA2) _APDS9960_GCONF2 = const(0xA3) @@ -91,7 +91,7 @@ # APDS9960_GOFFSET_L = const(0xA7) # APDS9960_GOFFSET_R = const(0xA9) _APDS9960_GPULSE = const(0xA6) -_APDS9960_GCONF3 = const(0xAA) +# _APDS9960_GCONF3 = const(0xAA) _APDS9960_GCONF4 = const(0xAB) _APDS9960_GFLVL = const(0xAE) _APDS9960_GSTATUS = const(0xAF) @@ -123,8 +123,8 @@ _BIT_POSITON_GCONF2_GGAIN = const(5) _BIT_MASK_GCONF2_GGAIN = const(0x60) -_BIT_POSITON_CONTROL_AGAIN = const(0) -_BIT_MASK_CONTROL_AGAIN = const(0x03) +# _BIT_POSITON_CONTROL_AGAIN = const(0) +# _BIT_MASK_CONTROL_AGAIN = const(0x03) # pylint: disable-msg=too-many-instance-attributes class APDS9960: @@ -166,11 +166,11 @@ class APDS9960: # _gesture_enable = RWBit(_APDS9960_ENABLE, 6) @property - def _gesture_enable(self) -> bool: + def enable_gesture(self) -> bool: return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE) - @_gesture_enable.setter - def _gesture_enable(self, value: bool) -> None: + @enable_gesture.setter + def enable_gesture(self, value: bool) -> None: self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE, value) # _gesture_mode = RWBit(_APDS9960_GCONF4, 0) @@ -205,8 +205,8 @@ def __init__( i2c: I2C, *, address: int = 0x39, - integration_time: int = 0x01, - gain: int = 0x01, + # integration_time: int = 0x01, + # gain: int = 0x01, rotation: int = 0 ): @@ -230,12 +230,12 @@ def __init__( self.enable = True time.sleep(0.010) - self.color_gain = gain - self.integration_time = integration_time - self.gesture_dimensions = 0x00 # all + # self.color_gain = gain + # self.integration_time = integration_time + # self.gesture_dimensions = 0x00 # all self.gesture_fifo_threshold = 0x01 # fifo 4 self.gesture_gain = 0x02 # gain 4 - self.gesture_proximity_threshold = 50 + # self.gesture_proximity_threshold = 50 self._reset_counts() # gesture pulse length=0x2 pulse count=0x3 @@ -307,14 +307,14 @@ def gesture_gain(self, value: int) -> None: # color_gain = RWBits(2, _APDS9960_CONTROL, 0) - @property - def color_gain(self) -> int: - self._get_bits(_APDS9960_CONTROL, _BIT_POSITON_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN) + # @property + # def color_gain(self) -> int: + # self._get_bits(_APDS9960_CONTROL, _BIT_POSITON_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN) - @gesture_gain.setter - def color_gain(self, value: int) -> None: - """Color gain value""" - self._set_bits(_APDS9960_CONTROL, _BIT_POSITON_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN, value) + # @gesture_gain.setter + # def color_gain(self, value: int) -> None: + # """Color gain value""" + # self._set_bits(_APDS9960_CONTROL, _BIT_POSITON_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN, value) # enable_proximity_interrupt = RWBit(_APDS9960_ENABLE, 5) @@ -342,23 +342,23 @@ def rotation(self, new_rotation: int) -> None: raise ValueError("Rotation value must be one of: 0, 90, 180, 270") ## GESTURE DETECTION - @property - def enable_gesture(self) -> bool: - """Gesture detection enable flag. True to enable, False to disable. - Note that when disabled, gesture mode is turned off""" - return self._gesture_enable - - @enable_gesture.setter - def enable_gesture(self, enable_flag: bool) -> None: - if not enable_flag: - self._gesture_mode = False - self._gesture_enable = enable_flag - - def rotated_gesture(self, original_gesture: int) -> int: - """Applies rotation offset to the given gesture direction and returns the result""" - directions = [1, 4, 2, 3] - new_index = (directions.index(original_gesture) + self._rotation // 90) % 4 - return directions[new_index] + # @property + # def enable_gesture(self) -> bool: + # """Gesture detection enable flag. True to enable, False to disable. + # Note that when disabled, gesture mode is turned off""" + # return self._gesture_enable + + # @enable_gesture.setter + # def enable_gesture(self, enable_flag: bool) -> None: + # if not enable_flag: + # self._gesture_mode = False + # self._gesture_enable = enable_flag + + # def rotated_gesture(self, original_gesture: int) -> int: + # """Applies rotation offset to the given gesture direction and returns the result""" + # directions = [1, 4, 2, 3] + # new_index = (directions.index(original_gesture) + self._rotation // 90) % 4 + # return directions[new_index] def gesture(self) -> int: # pylint: disable-msg=too-many-branches """Returns gesture code if detected. =0 if no gesture detected @@ -443,17 +443,21 @@ def gesture(self) -> int: # pylint: disable-msg=too-many-branches break if gesture_received != 0: if self._rotation != 0: - return self.rotated_gesture(gesture_received) + + directions = [1, 4, 2, 3] + new_index = (directions.index(gesture_received) + self._rotation // 90) % 4 + return directions[new_index] + return gesture_received - @property - def gesture_dimensions(self) -> int: - """Gesture dimension value: range 0-3""" - return self._read8(_APDS9960_GCONF3) + # @property + # def gesture_dimensions(self) -> int: + # """Gesture dimension value: range 0-3""" + # return self._read8(_APDS9960_GCONF3) - @gesture_dimensions.setter - def gesture_dimensions(self, dims: int) -> None: - self._write8(_APDS9960_GCONF3, dims & 0x03) + # @gesture_dimensions.setter + # def gesture_dimensions(self, dims: int) -> None: + # self._write8(_APDS9960_GCONF3, dims & 0x03) @property def color_data_ready(self) -> int: @@ -495,14 +499,14 @@ def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: persist = min(setting_tuple[2], 7) self._proximity_persistance = persist - @property - def gesture_proximity_threshold(self) -> int: - """Proximity threshold value: range 0-255""" - return self._read8(_APDS9960_GPENTH) + # @property + # def gesture_proximity_threshold(self) -> int: + # """Proximity threshold value: range 0-255""" + # return self._read8(_APDS9960_GPENTH) - @gesture_proximity_threshold.setter - def gesture_proximity_threshold(self, thresh: int) -> None: - self._write8(_APDS9960_GPENTH, thresh & 0xFF) + # @gesture_proximity_threshold.setter + # def gesture_proximity_threshold(self, thresh: int) -> None: + # self._write8(_APDS9960_GPENTH, thresh & 0xFF) @property def proximity(self) -> int: @@ -513,14 +517,14 @@ def clear_interrupt(self) -> None: """Clear all interrupts""" self._writecmdonly(_APDS9960_AICLEAR) - @property - def integration_time(self) -> int: - """Proximity integration time: range 0-255""" - return self._read8(_APDS9960_ATIME) + # @property + # def integration_time(self) -> int: + # """Proximity integration time: range 0-255""" + # return self._read8(_APDS9960_ATIME) - @integration_time.setter - def integration_time(self, int_time: int) -> None: - self._write8(_APDS9960_ATIME, int_time & 0xFF) + # @integration_time.setter + # def integration_time(self, int_time: int) -> None: + # self._write8(_APDS9960_ATIME, int_time & 0xFF) # method for reading and writing to I2C def _write8(self, command: int, abyte: int) -> None: From 94a9d49a948b45030408df507d52dde482f81bd3 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Sun, 28 Nov 2021 19:23:39 -0600 Subject: [PATCH 04/14] optimization testing: Setting complex defaults with minimal code --- adafruit_apds9960/apds9960.py | 224 +++++++++++----------------------- 1 file changed, 68 insertions(+), 156 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index d84333d..7b9a819 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -49,55 +49,55 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_APDS9960.git" -# ADDRESS_DEF = const(0x39) -# INTEGRATION_TIME_DEF = const(0x01) -# GAIN_DEF = const(0x01) +# Only one address is possible for the APDS9960, no alternates are available +_ADDRESS = const(0x39) +_DEVICE_ID = const(0xAB) # APDS9960_RAM = const(0x00) _APDS9960_ENABLE = const(0x80) -# _APDS9960_ATIME = const(0x81) -# APDS9960_WTIME = const(0x83) -# APDS9960_AILTIL = const(0x84) -# APDS9960_AILTH = const(0x85) -# APDS9960_AIHTL = const(0x86) -# APDS9960_AIHTH = const(0x87) +_APDS9960_ATIME = const(0x81) +# _APDS9960_WTIME = const(0x83) +# _APDS9960_AILTIL = const(0x84) +# _APDS9960_AILTH = const(0x85) +# _APDS9960_AIHTL = const(0x86) +# _APDS9960_AIHTH = const(0x87) _APDS9960_PILT = const(0x89) _APDS9960_PIHT = const(0x8B) _APDS9960_PERS = const(0x8C) -# APDS9960_CONFIG1 = const(0x8D) -# APDS9960_PPULSE = const(0x8E) -# _APDS9960_CONTROL = const(0x8F) -# APDS9960_CONFIG2 = const(0x90) +# _APDS9960_CONFIG1 = const(0x8D) +# _APDS9960_PPULSE = const(0x8E) +_APDS9960_CONTROL = const(0x8F) +# _APDS9960_CONFIG2 = const(0x90) _APDS9960_ID = const(0x92) _APDS9960_STATUS = const(0x93) _APDS9960_CDATAL = const(0x94) -# APDS9960_CDATAH = const(0x95) -# APDS9960_RDATAL = const(0x96) -# APDS9960_RDATAH = const(0x97) -# APDS9960_GDATAL = const(0x98) -# APDS9960_GDATAH = const(0x99) -# APDS9960_BDATAL = const(0x9A) -# APDS9960_BDATAH = const(0x9B) +# _APDS9960_CDATAH = const(0x95) +# _APDS9960_RDATAL = const(0x96) +# _APDS9960_RDATAH = const(0x97) +# _APDS9960_GDATAL = const(0x98) +# _APDS9960_GDATAH = const(0x99) +# _APDS9960_BDATAL = const(0x9A) +# _APDS9960_BDATAH = const(0x9B) _APDS9960_PDATA = const(0x9C) -# APDS9960_POFFSET_UR = const(0x9D) -# APDS9960_POFFSET_DL = const(0x9E) -# APDS9960_CONFIG3 = const(0x9F) -# _APDS9960_GPENTH = const(0xA0) -# APDS9960_GEXTH = const(0xA1) +# _APDS9960_POFFSET_UR = const(0x9D) +# _APDS9960_POFFSET_DL = const(0x9E) +# _APDS9960_CONFIG3 = const(0x9F) +_APDS9960_GPENTH = const(0xA0) +_APDS9960_GEXTH = const(0xA1) _APDS9960_GCONF1 = const(0xA2) _APDS9960_GCONF2 = const(0xA3) -# APDS9960_GOFFSET_U = const(0xA4) -# APDS9960_GOFFSET_D = const(0xA5) -# APDS9960_GOFFSET_L = const(0xA7) -# APDS9960_GOFFSET_R = const(0xA9) +# _APDS9960_GOFFSET_U = const(0xA4) +# _APDS9960_GOFFSET_D = const(0xA5) +# _APDS9960_GOFFSET_L = const(0xA7) +# _APDS9960_GOFFSET_R = const(0xA9) _APDS9960_GPULSE = const(0xA6) # _APDS9960_GCONF3 = const(0xAA) _APDS9960_GCONF4 = const(0xAB) _APDS9960_GFLVL = const(0xAE) _APDS9960_GSTATUS = const(0xAF) -# APDS9960_IFORCE = const(0xE4) -# APDS9960_PICLEAR = const(0xE5) -# APDS9960_CICLEAR = const(0xE6) +# _APDS9960_IFORCE = const(0xE4) +# _APDS9960_PICLEAR = const(0xE5) +# _APDS9960_CICLEAR = const(0xE6) _APDS9960_AICLEAR = const(0xE7) _APDS9960_GFIFO_U = const(0xFC) # APDS9960_GFIFO_D = const(0xFD) @@ -105,10 +105,10 @@ # APDS9960_GFIFO_R = const(0xFF) _BIT_MASK_ENABLE_EN = const(0x01) -_BIT_MASK_ENABLE_PROX = const(0x04) -_BIT_MASK_ENABLE_PROX_INT = const(0x10) -_BIT_MASK_ENABLE_GESTURE = const(0x20) _BIT_MASK_ENABLE_COLOR = const(0x02) +_BIT_MASK_ENABLE_PROX = const(0x04) +_BIT_MASK_ENABLE_PROX_INT = const(0x20) +_BIT_MASK_ENABLE_GESTURE = const(0x40) _BIT_MASK_GSTATUS_GVALID = const(0x01) @@ -117,11 +117,11 @@ _BIT_POSITON_PERS_PPERS = const(4) _BIT_MASK_PERS_PPERS = const(0xF0) -_BIT_POSITON_GCONF1_GFIFOTH = const(6) -_BIT_MASK_GCONF1_GFIFOTH = const(0xC0) +# _BIT_POSITON_GCONF1_GFIFOTH = const(6) +# _BIT_MASK_GCONF1_GFIFOTH = const(0xC0) -_BIT_POSITON_GCONF2_GGAIN = const(5) -_BIT_MASK_GCONF2_GGAIN = const(0x60) +# _BIT_POSITON_GCONF2_GGAIN = const(5) +# _BIT_MASK_GCONF2_GGAIN = const(0x60) # _BIT_POSITON_CONTROL_AGAIN = const(0) # _BIT_MASK_CONTROL_AGAIN = const(0x03) @@ -204,43 +204,41 @@ def __init__( self, i2c: I2C, *, - address: int = 0x39, - # integration_time: int = 0x01, - # gain: int = 0x01, - rotation: int = 0 + rotation: int = 0, + reset: bool = True, + set_defaults: bool = True ): + + self.rotation = rotation self.buf129 = None self.buf2 = bytearray(2) - self.i2c_device = I2CDevice(i2c, address) + self.i2c_device = I2CDevice(i2c, _ADDRESS) - if self._read8(_APDS9960_ID) != 0xAB: + if self._read8(_APDS9960_ID) != _DEVICE_ID: raise RuntimeError() - self.enable_gesture = False - self.enable_proximity = False - self.enable_color = False - self._rotation = rotation - self.enable_proximity_interrupt = False - self.clear_interrupt() - - self.enable = False - time.sleep(0.010) - self.enable = True - time.sleep(0.010) - - # self.color_gain = gain - # self.integration_time = integration_time - # self.gesture_dimensions = 0x00 # all - self.gesture_fifo_threshold = 0x01 # fifo 4 - self.gesture_gain = 0x02 # gain 4 - # self.gesture_proximity_threshold = 50 + if reset: + self._write8(_APDS9960_ENABLE, 0) # Disable sensor and all functions/interrupts + self.clear_interrupt() + + self.enable = True + time.sleep(0.010) # Wait for PON delay to clear before proceeding + + if set_defaults: + self._write8(_APDS9960_PIHT, 5) # Trigger PINT at >= 5 counts + self._write8(_APDS9960_PERS, 80) # PPERS: 5 cycles, APERS: 0 cycles + self._write8(_APDS9960_ATIME, 182) # ATIME: 182 (200ms color integration time) + self._write8(_APDS9960_CONTROL, 1) # AGAIN: 1 (4x color gain) + self._write8(_APDS9960_GPENTH, 5) # Trigger gesture engine at >= 5 counts of proximity + self._write8(_APDS9960_GEXTH, 30) # Exit gesture loop if all counts drop below 30 + self._write8(_APDS9960_GCONF1, 2) # GEXPERS: 4 cycles, GEXMSK: 0, GFIFOTH: 0 datasets + self._write8(_APDS9960_GCONF2, 33) # GWTIME: 1 (2.8ms), GLDRIVE: 100mA, GGAIN: 1 (2x) + self._write8(_APDS9960_GPULSE, 133) # GPULSE: 5 pulses, GPLEN: 16 us + self._reset_counts() - # gesture pulse length=0x2 pulse count=0x3 - self._write8(_APDS9960_GPULSE, (0x2 << 6) | 0x3) - ## BOARD def _reset_counts(self) -> None: """Gesture detection internal counts""" @@ -249,8 +247,6 @@ def _reset_counts(self) -> None: self._saw_left_start = 0 self._saw_right_start = 0 - # enable = RWBit(_APDS9960_ENABLE, 0) - @property def enable(self) -> bool: return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN) @@ -260,8 +256,6 @@ def enable(self, value: bool) -> None: """Board enable. True to enable, False to disable""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN, value) - # enable_color = RWBit(_APDS9960_ENABLE, 1) - @property def enable_color(self) -> bool: return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR) @@ -272,8 +266,6 @@ def enable_color(self, value: bool) -> None: True when color detection is enabled, else False""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR, value) - # enable_proximity = RWBit(_APDS9960_ENABLE, 2) - @property def enable_proximity(self) -> bool: return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX) @@ -283,41 +275,6 @@ def enable_proximity(self, value: bool) -> None: """Enable of proximity mode""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX, value) - # gesture_fifo_threshold = RWBits(2, _APDS9960_GCONF1, 6) - - @property - def gesture_fifo_threshold(self) -> int: - self._get_bits(_APDS9960_GCONF1, _BIT_POSITON_GCONF1_GFIFOTH, _BIT_MASK_GCONF1_GFIFOTH) - - @gesture_fifo_threshold.setter - def gesture_fifo_threshold(self, value: int) -> None: - """Gesture fifo threshold value: range 0-3""" - self._set_bits(_APDS9960_GCONF1, _BIT_POSITON_GCONF1_GFIFOTH, _BIT_MASK_GCONF1_GFIFOTH, value) - - # gesture_gain = RWBits(2, _APDS9960_GCONF2, 5) - - @property - def gesture_gain(self) -> int: - self._get_bits(_APDS9960_GCONF2, _BIT_POSITON_GCONF2_GGAIN, _BIT_MASK_GCONF2_GGAIN) - - @gesture_gain.setter - def gesture_gain(self, value: int) -> None: - """Gesture gain value: range 0-3""" - self._set_bits(_APDS9960_GCONF2, _BIT_POSITON_GCONF2_GGAIN, _BIT_MASK_GCONF2_GGAIN, value) - - # color_gain = RWBits(2, _APDS9960_CONTROL, 0) - - # @property - # def color_gain(self) -> int: - # self._get_bits(_APDS9960_CONTROL, _BIT_POSITON_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN) - - # @gesture_gain.setter - # def color_gain(self, value: int) -> None: - # """Color gain value""" - # self._set_bits(_APDS9960_CONTROL, _BIT_POSITON_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN, value) - - # enable_proximity_interrupt = RWBit(_APDS9960_ENABLE, 5) - @property def enable_proximity_interrupt(self) -> bool: return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX_INT) @@ -342,24 +299,6 @@ def rotation(self, new_rotation: int) -> None: raise ValueError("Rotation value must be one of: 0, 90, 180, 270") ## GESTURE DETECTION - # @property - # def enable_gesture(self) -> bool: - # """Gesture detection enable flag. True to enable, False to disable. - # Note that when disabled, gesture mode is turned off""" - # return self._gesture_enable - - # @enable_gesture.setter - # def enable_gesture(self, enable_flag: bool) -> None: - # if not enable_flag: - # self._gesture_mode = False - # self._gesture_enable = enable_flag - - # def rotated_gesture(self, original_gesture: int) -> int: - # """Applies rotation offset to the given gesture direction and returns the result""" - # directions = [1, 4, 2, 3] - # new_index = (directions.index(original_gesture) + self._rotation // 90) % 4 - # return directions[new_index] - def gesture(self) -> int: # pylint: disable-msg=too-many-branches """Returns gesture code if detected. =0 if no gesture detected =1 if an UP, =2 if a DOWN, =3 if an LEFT, =4 if a RIGHT @@ -450,15 +389,6 @@ def gesture(self) -> int: # pylint: disable-msg=too-many-branches return gesture_received - # @property - # def gesture_dimensions(self) -> int: - # """Gesture dimension value: range 0-3""" - # return self._read8(_APDS9960_GCONF3) - - # @gesture_dimensions.setter - # def gesture_dimensions(self, dims: int) -> None: - # self._write8(_APDS9960_GCONF3, dims & 0x03) - @property def color_data_ready(self) -> int: """Color data ready flag. zero if not ready, 1 is ready""" @@ -499,15 +429,6 @@ def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: persist = min(setting_tuple[2], 7) self._proximity_persistance = persist - # @property - # def gesture_proximity_threshold(self) -> int: - # """Proximity threshold value: range 0-255""" - # return self._read8(_APDS9960_GPENTH) - - # @gesture_proximity_threshold.setter - # def gesture_proximity_threshold(self, thresh: int) -> None: - # self._write8(_APDS9960_GPENTH, thresh & 0xFF) - @property def proximity(self) -> int: """Proximity value: range 0-255""" @@ -517,15 +438,6 @@ def clear_interrupt(self) -> None: """Clear all interrupts""" self._writecmdonly(_APDS9960_AICLEAR) - # @property - # def integration_time(self) -> int: - # """Proximity integration time: range 0-255""" - # return self._read8(_APDS9960_ATIME) - - # @integration_time.setter - # def integration_time(self, int_time: int) -> None: - # self._write8(_APDS9960_ATIME, int_time & 0xFF) - # method for reading and writing to I2C def _write8(self, command: int, abyte: int) -> None: """Write a command and 1 byte of data to the I2C device""" @@ -555,7 +467,7 @@ def _get_bit(self, register: int, bitmask: int) -> int: buf[0] = register with self.i2c_device as i2c: i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) - return (buf[1] & bitmask) + return bool(buf[1] & bitmask) def _set_bit(self, register: int, bitmask: int, value: bool) -> None: buf = self.buf2 @@ -565,9 +477,9 @@ def _set_bit(self, register: int, bitmask: int, value: bool) -> None: if value: buf[1] |= bitmask else: - buf[1] &= bitmask + buf[1] &= ~bitmask with self.i2c_device as i2c: - i2c.write(buf, end=1) + i2c.write(buf, end=2) def _get_bits(self, register: int, bit_position: int, bit_mask: int) -> int: buf = self.buf2 @@ -583,7 +495,7 @@ def _set_bits(self, register: int, bit_position: int, bit_mask: int, value: int) i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) buf[1] = (buf[1] & ~bit_mask) | (value << bit_position) with self.i2c_device as i2c: - i2c.write(buf, end=1) + i2c.write(buf, end=2) def _color_data16(self, command: int) -> int: """Sends a command and reads 2 bytes of data from the I2C device From 999945eaddff6cc7e220ceb26a5ef244288be0f7 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Sun, 28 Nov 2021 19:29:56 -0600 Subject: [PATCH 05/14] optimization testing: Setting complex defaults with minimal code (rearranging) --- adafruit_apds9960/apds9960.py | 77 ++++++++++++++++------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index 7b9a819..128a503 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -163,43 +163,6 @@ class APDS9960: """ - # _gesture_enable = RWBit(_APDS9960_ENABLE, 6) - - @property - def enable_gesture(self) -> bool: - return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE) - - @enable_gesture.setter - def enable_gesture(self, value: bool) -> None: - self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE, value) - - # _gesture_mode = RWBit(_APDS9960_GCONF4, 0) - - @property - def _gesture_mode(self) -> bool: - return self._get_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GMODE) - - @_gesture_mode.setter - def _gesture_mode(self, value: bool) -> None: - self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GMODE, value) - - # _gesture_valid = RWBit(_APDS9960_GSTATUS, 0) - - @property - def _gesture_valid(self) -> bool: - return self._get_bit(_APDS9960_GSTATUS, _BIT_MASK_GSTATUS_GVALID) - - # _proximity_persistance = RWBits(4, _APDS9960_PERS, 4) - - @property - def _proximity_persistance(self) -> int: - self._get_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS) - - @_proximity_persistance.setter - def _proximity_persistance(self, value: int) -> None: - self._set_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS, value) - - def __init__( self, i2c: I2C, @@ -240,12 +203,33 @@ def __init__( self._reset_counts() ## BOARD - def _reset_counts(self) -> None: - """Gesture detection internal counts""" - self._saw_down_start = 0 - self._saw_up_start = 0 - self._saw_left_start = 0 - self._saw_right_start = 0 + @property + def enable_gesture(self) -> bool: + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE) + + @enable_gesture.setter + def enable_gesture(self, value: bool) -> None: + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE, value) + + @property + def _gesture_mode(self) -> bool: + return self._get_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GMODE) + + @_gesture_mode.setter + def _gesture_mode(self, value: bool) -> None: + self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GMODE, value) + + @property + def _gesture_valid(self) -> bool: + return self._get_bit(_APDS9960_GSTATUS, _BIT_MASK_GSTATUS_GVALID) + + @property + def _proximity_persistance(self) -> int: + self._get_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS) + + @_proximity_persistance.setter + def _proximity_persistance(self, value: int) -> None: + self._set_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS, value) @property def enable(self) -> bool: @@ -299,6 +283,13 @@ def rotation(self, new_rotation: int) -> None: raise ValueError("Rotation value must be one of: 0, 90, 180, 270") ## GESTURE DETECTION + def _reset_counts(self) -> None: + """Gesture detection internal counts""" + self._saw_down_start = 0 + self._saw_up_start = 0 + self._saw_left_start = 0 + self._saw_right_start = 0 + def gesture(self) -> int: # pylint: disable-msg=too-many-branches """Returns gesture code if detected. =0 if no gesture detected =1 if an UP, =2 if a DOWN, =3 if an LEFT, =4 if a RIGHT From a0df006ee79cbd164a147d4c17c5961aad6d9b09 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Sun, 28 Nov 2021 19:49:26 -0600 Subject: [PATCH 06/14] optimization testing: Setting complex defaults with minimal code (adding reset, cleanup) --- adafruit_apds9960/apds9960.py | 60 +++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index 128a503..62b300c 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -132,10 +132,9 @@ class APDS9960: APDS9900 provide basic driver services for the ASDS9960 breakout board :param ~busio.I2C i2c: The I2C bus the ASDS9960 is connected to - :param int address: The I2C device address. Defaults to :const:`0x39` - :param int integration_time: integration time. Defaults to :const:`0x01` - :param int gain: Device gain. Defaults to :const:`0x01` :param int rotation: rotation of the device. Defaults to :const:`0` + :param bool reset: If true, reset device on init. Defaults to :const:`True` + :param bool set_defaults: If true, set sensible defaults on init. Defaults to :const:`True` **Quickstart: Importing and using the APDS9960** @@ -155,10 +154,11 @@ class APDS9960: i2c = board.I2C() # uses board.SCL and board.SDA apds = APDS9960(i2c) - Now you have access to the :attr:`sensor.proximity` attribute + Now you have access to the :attr:`apds.proximity_enable` :attr:`apds.proximity` attributes .. code-block:: python + apds.proximity_enable = True proximity = apds.proximity """ @@ -184,21 +184,34 @@ def __init__( if reset: self._write8(_APDS9960_ENABLE, 0) # Disable sensor and all functions/interrupts + + # Reset basic config registers to power-on defaults + self._write8(_APDS9960_ATIME, 255) + self._write8(_APDS9960_PIHT, 0) + self._write8(_APDS9960_PERS, 0) + self._write8(_APDS9960_CONTROL, 1) + self._write8(_APDS9960_GPENTH, 0) + self._write8(_APDS9960_GEXTH, 0) + self._write8(_APDS9960_GCONF1, 0) + self._write8(_APDS9960_GCONF2, 0) + self._write8(_APDS9960_GPULSE, 0) + + # Clear all interrupts self.clear_interrupt() + # Enable sensor and wait 10ms for the power on delay to finish self.enable = True - time.sleep(0.010) # Wait for PON delay to clear before proceeding + time.sleep(0.010) if set_defaults: - self._write8(_APDS9960_PIHT, 5) # Trigger PINT at >= 5 counts - self._write8(_APDS9960_PERS, 80) # PPERS: 5 cycles, APERS: 0 cycles - self._write8(_APDS9960_ATIME, 182) # ATIME: 182 (200ms color integration time) - self._write8(_APDS9960_CONTROL, 1) # AGAIN: 1 (4x color gain) - self._write8(_APDS9960_GPENTH, 5) # Trigger gesture engine at >= 5 counts of proximity - self._write8(_APDS9960_GEXTH, 30) # Exit gesture loop if all counts drop below 30 - self._write8(_APDS9960_GCONF1, 2) # GEXPERS: 4 cycles, GEXMSK: 0, GFIFOTH: 0 datasets - self._write8(_APDS9960_GCONF2, 33) # GWTIME: 1 (2.8ms), GLDRIVE: 100mA, GGAIN: 1 (2x) - self._write8(_APDS9960_GPULSE, 133) # GPULSE: 5 pulses, GPLEN: 16 us + self.proximity_interrupt_threshold = (0, 5, 4) # Trigger PINT at >= 5, PPERS: 4 cycles + self._write8(_APDS9960_GPENTH, 0x05) # Enter gesture engine at >= 5 counts + self._write8(_APDS9960_GEXTH, 0x1E) # Exit gesture engine if all counts drop below 30 + self._write8(_APDS9960_GCONF1, 0x82) # GEXPERS: 1 (4 cycles), GFIFOTH: 2 (8 datasets) + self._write8(_APDS9960_GCONF2, 0x21) # GWTIME: 1 (2.8ms), GLDRIVE: 100mA, GGAIN: 1 (2x) + self._write8(_APDS9960_GPULSE, 0x85) # GPULSE: 5 (6 pulses), GPLEN: 2 (16 us) + self._write8(_APDS9960_ATIME, 0xB6) # ATIME: 182 (200ms color integration time) + self._write8(_APDS9960_CONTROL, 0x01) # AGAIN: 1 (4x color gain), PGAIN: 0 (1x) self._reset_counts() @@ -224,11 +237,11 @@ def _gesture_valid(self) -> bool: return self._get_bit(_APDS9960_GSTATUS, _BIT_MASK_GSTATUS_GVALID) @property - def _proximity_persistance(self) -> int: + def _proximity_persistence(self) -> int: self._get_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS) - @_proximity_persistance.setter - def _proximity_persistance(self, value: int) -> None: + @_proximity_persistence.setter + def _proximity_persistence(self, value: int) -> None: self._set_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS, value) @property @@ -380,6 +393,7 @@ def gesture(self) -> int: # pylint: disable-msg=too-many-branches return gesture_received + ## COLOR @property def color_data_ready(self) -> int: """Color data ready flag. zero if not ready, 1 is ready""" @@ -395,18 +409,18 @@ def color_data(self) -> Tuple[int, int, int, int]: self._color_data16(_APDS9960_CDATAL), ) - ### PROXIMITY + ## PROXIMITY @property def proximity_interrupt_threshold(self) -> Tuple[int, int, int]: """Tuple containing low and high threshold - followed by the proximity interrupt persistance. + followed by the proximity interrupt persistence. When setting the proximity interrupt threshold values using a tuple of - zero to three values: low threshold, high threshold, persistance. - persistance defaults to 4 if not provided""" + zero to three values: low threshold, high threshold, persistence. + persistence defaults to 4 if not provided""" return ( self._read8(_APDS9960_PILT), self._read8(_APDS9960_PIHT), - self._proximity_persistance, + self._proximity_persistence, ) @proximity_interrupt_threshold.setter @@ -418,7 +432,7 @@ def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: persist = 4 # default 4 if len(setting_tuple) > 2: persist = min(setting_tuple[2], 7) - self._proximity_persistance = persist + self._proximity_persistence = persist @property def proximity(self) -> int: From 9f65fc2e690a0aeacd6da696355e52025f2fc0d5 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Sun, 28 Nov 2021 22:39:13 -0600 Subject: [PATCH 07/14] Gesture refactor, property/doc cleanup --- adafruit_apds9960/apds9960.py | 463 +++++++++++++++++++--------------- 1 file changed, 258 insertions(+), 205 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index 62b300c..c8cbbb6 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -50,7 +50,7 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_APDS9960.git" # Only one address is possible for the APDS9960, no alternates are available -_ADDRESS = const(0x39) +_APDS9960_I2C_ADDRESS = const(0x39) _DEVICE_ID = const(0xAB) # APDS9960_RAM = const(0x00) @@ -109,30 +109,20 @@ _BIT_MASK_ENABLE_PROX = const(0x04) _BIT_MASK_ENABLE_PROX_INT = const(0x20) _BIT_MASK_ENABLE_GESTURE = const(0x40) +_BIT_MASK_STATUS_GINT = const(0x04) +_BIT_MASK_GSTATUS_GFOV = const(0x02) +_BIT_MASK_GCONF4_GFIFO_CLR = const(0x04) -_BIT_MASK_GSTATUS_GVALID = const(0x01) - -_BIT_MASK_GCONF4_GMODE = const(0x01) - -_BIT_POSITON_PERS_PPERS = const(4) +_BIT_POS_PERS_PPERS = const(4) _BIT_MASK_PERS_PPERS = const(0xF0) -# _BIT_POSITON_GCONF1_GFIFOTH = const(6) -# _BIT_MASK_GCONF1_GFIFOTH = const(0xC0) - -# _BIT_POSITON_GCONF2_GGAIN = const(5) -# _BIT_MASK_GCONF2_GGAIN = const(0x60) - -# _BIT_POSITON_CONTROL_AGAIN = const(0) -# _BIT_MASK_CONTROL_AGAIN = const(0x03) - # pylint: disable-msg=too-many-instance-attributes class APDS9960: """ - APDS9900 provide basic driver services for the ASDS9960 breakout board + Provide basic driver services for the APDS9960 breakout board - :param ~busio.I2C i2c: The I2C bus the ASDS9960 is connected to - :param int rotation: rotation of the device. Defaults to :const:`0` + :param ~busio.I2C i2c: The I2C bus the APDS9960 is connected to + :param int rotation: Rotation of the device. Defaults to :const:`0` :param bool reset: If true, reset device on init. Defaults to :const:`True` :param bool set_defaults: If true, set sensible defaults on init. Defaults to :const:`True` @@ -171,118 +161,133 @@ def __init__( reset: bool = True, set_defaults: bool = True ): - + self.rotation = rotation - self.buf129 = None - self.buf2 = bytearray(2) + self.buf129 = None # Gesture FIFO buffer + self.buf4 = None # Gesture data processing buffer + self.buf2 = bytearray(2) # I2C communication buffer - self.i2c_device = I2CDevice(i2c, _ADDRESS) + self.i2c_device = I2CDevice(i2c, _APDS9960_I2C_ADDRESS) if self._read8(_APDS9960_ID) != _DEVICE_ID: raise RuntimeError() if reset: - self._write8(_APDS9960_ENABLE, 0) # Disable sensor and all functions/interrupts + # Disable sensor and all functions/interrupts + self._write8(_APDS9960_ENABLE, 0) # Reset basic config registers to power-on defaults - self._write8(_APDS9960_ATIME, 255) - self._write8(_APDS9960_PIHT, 0) - self._write8(_APDS9960_PERS, 0) - self._write8(_APDS9960_CONTROL, 1) + self.proximity_interrupt_threshold = (0, 0, 0) self._write8(_APDS9960_GPENTH, 0) self._write8(_APDS9960_GEXTH, 0) self._write8(_APDS9960_GCONF1, 0) self._write8(_APDS9960_GCONF2, 0) + self._write8(_APDS9960_GCONF4, 0) self._write8(_APDS9960_GPULSE, 0) + self._write8(_APDS9960_ATIME, 255) + self._write8(_APDS9960_CONTROL, 1) - # Clear all interrupts + # Clear all non-gesture interrupts self.clear_interrupt() + # Clear gesture FIFOs and interrupt + self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GFIFO_CLR, True) + # Enable sensor and wait 10ms for the power on delay to finish self.enable = True time.sleep(0.010) if set_defaults: - self.proximity_interrupt_threshold = (0, 5, 4) # Trigger PINT at >= 5, PPERS: 4 cycles - self._write8(_APDS9960_GPENTH, 0x05) # Enter gesture engine at >= 5 counts - self._write8(_APDS9960_GEXTH, 0x1E) # Exit gesture engine if all counts drop below 30 - self._write8(_APDS9960_GCONF1, 0x82) # GEXPERS: 1 (4 cycles), GFIFOTH: 2 (8 datasets) - self._write8(_APDS9960_GCONF2, 0x21) # GWTIME: 1 (2.8ms), GLDRIVE: 100mA, GGAIN: 1 (2x) - self._write8(_APDS9960_GPULSE, 0x85) # GPULSE: 5 (6 pulses), GPLEN: 2 (16 us) - self._write8(_APDS9960_ATIME, 0xB6) # ATIME: 182 (200ms color integration time) - self._write8(_APDS9960_CONTROL, 0x01) # AGAIN: 1 (4x color gain), PGAIN: 0 (1x) - - self._reset_counts() + # Trigger PINT at >= 5, PPERS: 4 cycles + self.proximity_interrupt_threshold = (0, 5, 4) + # Enter gesture engine at >= 5 counts + self._write8(_APDS9960_GPENTH, 0x05) + # Exit gesture engine if all counts drop below 30 + self._write8(_APDS9960_GEXTH, 0x1E) + # GEXPERS: 2 (4 cycles), GEXMSK: 0 (default) GFIFOTH: 2 (8 datasets) + self._write8(_APDS9960_GCONF1, 0x82) + # GGAIN: 2 (4x), GLDRIVE: 100 mA (default), GGAIN: 1 (2x) + self._write8(_APDS9960_GCONF2, 0x41) + # GPULSE: 5 (6 pulses), GPLEN: 2 (16 us) + self._write8(_APDS9960_GPULSE, 0x85) + # ATIME: 182 (200ms color integration time) + self._write8(_APDS9960_ATIME, 0xB6) + # AGAIN: 1 (4x color gain), PGAIN: 0 (1x) + self._write8(_APDS9960_CONTROL, 0x01) ## BOARD - @property - def enable_gesture(self) -> bool: - return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE) - - @enable_gesture.setter - def enable_gesture(self, value: bool) -> None: - self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE, value) - - @property - def _gesture_mode(self) -> bool: - return self._get_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GMODE) - - @_gesture_mode.setter - def _gesture_mode(self, value: bool) -> None: - self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GMODE, value) - - @property - def _gesture_valid(self) -> bool: - return self._get_bit(_APDS9960_GSTATUS, _BIT_MASK_GSTATUS_GVALID) - - @property - def _proximity_persistence(self) -> int: - self._get_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS) - - @_proximity_persistence.setter - def _proximity_persistence(self, value: int) -> None: - self._set_bits(_APDS9960_PERS, _BIT_POSITON_PERS_PPERS, _BIT_MASK_PERS_PPERS, value) - @property def enable(self) -> bool: + """If true, the sensor is enabled + If set to false, the sensor will enter a low-power sleep state""" return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN) @enable.setter def enable(self, value: bool) -> None: - """Board enable. True to enable, False to disable""" + """If true, the sensor is enabled + If set to false, the sensor will enter a low-power sleep state""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN, value) - @property - def enable_color(self) -> bool: - return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR) - - @enable_color.setter - def enable_color(self, value: bool) -> None: - """Color detection enable flag. - True when color detection is enabled, else False""" - self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR, value) - + ## Proximity Properties @property def enable_proximity(self) -> bool: + """If true, the sensor's proximity engine is enabled""" return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX) @enable_proximity.setter def enable_proximity(self, value: bool) -> None: - """Enable of proximity mode""" + """If true, the sensor's proximity engine is enabled""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX, value) @property def enable_proximity_interrupt(self) -> bool: + """If true, internal proximity interrupts assert interrupt pin""" return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX_INT) @enable_proximity_interrupt.setter def enable_proximity_interrupt(self, value: bool) -> None: - """Proximity interrupt enable flag. True if enabled, - False to disable""" + """If true, internal proximity interrupts assert interrupt pin""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX_INT, value) - ## GESTURE ROTATION + @property + def proximity_interrupt_threshold(self) -> Tuple[int, int, int]: + """Tuple representing proximity engine low/high threshold (0-255) and persistence (0-15)""" + return ( + self._read8(_APDS9960_PILT), + self._read8(_APDS9960_PIHT), + self._get_bits(_APDS9960_PERS, _BIT_POS_PERS_PPERS, _BIT_MASK_PERS_PPERS), + ) + + @proximity_interrupt_threshold.setter + def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: + """Tuple representing proximity engine low/high threshold (0-255) and persistence (0-15)""" + if setting_tuple: + self._write8(_APDS9960_PILT, setting_tuple[0]) + if len(setting_tuple) > 1: + self._write8(_APDS9960_PIHT, setting_tuple[1]) + persist = 4 # default 4 + if len(setting_tuple) > 2: + persist = min(setting_tuple[2], 15) + self._set_bits( + _APDS9960_PERS, _BIT_POS_PERS_PPERS, _BIT_MASK_PERS_PPERS, persist + ) + + def clear_interrupt(self) -> None: + """Clear all non-gesture interrupts""" + self._writecmdonly(_APDS9960_AICLEAR) + + ## Gesture Properties + @property + def enable_gesture(self) -> bool: + """If true, the sensor's gesture engine is enabled""" + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE) + + @enable_gesture.setter + def enable_gesture(self, value: bool) -> None: + """If true, the sensor's gesture engine is enabled""" + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE, value) + @property def rotation(self) -> int: """Gesture rotation offset. Acceptable values are 0, 90, 180, 270.""" @@ -290,43 +295,75 @@ def rotation(self) -> int: @rotation.setter def rotation(self, new_rotation: int) -> None: + """Gesture rotation offset. Acceptable values are 0, 90, 180, 270.""" if new_rotation in [0, 90, 180, 270]: self._rotation = new_rotation else: raise ValueError("Rotation value must be one of: 0, 90, 180, 270") - ## GESTURE DETECTION - def _reset_counts(self) -> None: - """Gesture detection internal counts""" - self._saw_down_start = 0 - self._saw_up_start = 0 - self._saw_left_start = 0 - self._saw_right_start = 0 - - def gesture(self) -> int: # pylint: disable-msg=too-many-branches - """Returns gesture code if detected. =0 if no gesture detected - =1 if an UP, =2 if a DOWN, =3 if an LEFT, =4 if a RIGHT - """ - # buffer to read of contents of device FIFO buffer - if not self.buf129: - self.buf129 = bytearray(129) - - buffer = self.buf129 - buffer[0] = _APDS9960_GFIFO_U - if not self._gesture_valid: - return 0 + ## Color/Light Properties + @property + def enable_color(self) -> bool: + """If true, the sensor's color/light engine is enabled""" + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR) - time_mark = 0.0 - gesture_received = 0 - while True: + @enable_color.setter + def enable_color(self, value: bool) -> None: + """If true, the sensor's color/light engine is enabled""" + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR, value) - up_down_diff = 0 - left_right_diff = 0 - gesture_received = 0 - time.sleep(0.030) # 30 ms + ## PROXIMITY + @property + def proximity(self) -> int: + """Proximity value: 0-255 + lower values are farther, higher values are closer""" + return self._read8(_APDS9960_PDATA) - n_recs = self._read8(_APDS9960_GFLVL) - if n_recs: + ## GESTURE DETECTION + # pylint: disable-msg=too-many-branches,too-many-locals,too-many-statements + def gesture(self) -> int: + """Returns gesture code if detected. + 0 if no gesture detected + 1 if up, + 2 if down, + 3 if left, + 4 if right + """ + # If FIFOs have overflowed we're already way too late, so clear those FIFOs and wait + if self._get_bit(_APDS9960_GSTATUS, _BIT_MASK_GSTATUS_GFOV): + self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GFIFO_CLR, True) + wait_cycles = 0 + # Don't wait forever though, just enough to see if a gesture is happening + while ( + not self._get_bit(_APDS9960_STATUS, _BIT_MASK_STATUS_GINT) + and wait_cycles <= 30 + ): + time.sleep(0.003) + wait_cycles += 1 + + # Only start retrieval if there are datasets to retrieve + frame = [] + datasets_available = self._read8(_APDS9960_GFLVL) + if ( + self._get_bit(_APDS9960_STATUS, _BIT_MASK_STATUS_GINT) + and datasets_available > 0 + ): + if not self.buf129: + self.buf129 = bytearray(129) + + buffer = self.buf129 + buffer[0] = _APDS9960_GFIFO_U + + if not self.buf4: + self.buf4 = bytearray(4) + + buffer_dataset = self.buf4 + + # Retrieve new data until our FIFOs are truly empty + while True: + dataset_count = self._read8(_APDS9960_GFLVL) + if dataset_count == 0: + break with self.i2c_device as i2c: i2c.write_then_readinto( @@ -334,64 +371,110 @@ def gesture(self) -> int: # pylint: disable-msg=too-many-branches buffer, out_end=1, in_start=1, - in_end=min(129, 1 + n_recs * 4), + in_end=min(129, 1 + (dataset_count * 4)), ) - upp, down, left, right = buffer[1:5] - - if abs(upp - down) > 13: - up_down_diff = upp - down - - if abs(left - right) > 13: - left_right_diff = left - right - if up_down_diff != 0: - if up_down_diff < 0: - # either leading edge of down movement - # or trailing edge of up movement - if self._saw_up_start: - gesture_received = 0x01 # up - else: - self._saw_down_start += 1 - elif up_down_diff > 0: - # either leading edge of up movement - # or trailing edge of down movement - if self._saw_down_start: - gesture_received = 0x02 # down - else: - self._saw_up_start += 1 - - if left_right_diff != 0: - if left_right_diff < 0: - # either leading edge of right movement - # trailing edge of left movement - if self._saw_left_start: - gesture_received = 0x03 # left - else: - self._saw_right_start += 1 - elif left_right_diff > 0: - # either leading edge of left movement - # trailing edge of right movement - if self._saw_right_start: - gesture_received = 0x04 # right + # Unpack data stream into more usable U/D/L/R datasets for analysis + idx = 0 + for i in range(dataset_count): + rec = i + 1 + idx = 1 + ((rec - 1) * 4) + + buffer_dataset[0] = buffer[idx] + buffer_dataset[1] = buffer[idx + 1] + buffer_dataset[2] = buffer[idx + 2] + buffer_dataset[3] = buffer[idx + 3] + + # Drop fully-saturated and fully-zero to conserve memory + # Filter to remove useless (saturated, empty, low-count) datasets + if ( + (not all(val == 255 for val in buffer_dataset)) + and (not all(val == 0 for val in buffer_dataset)) + and (all(val >= 30 for val in buffer_dataset)) + ): + if len(frame) < 2: + frame.append(tuple(buffer_dataset)) else: - self._saw_left_start += 1 + frame[1] = tuple(buffer_dataset) - # saw a leading or trailing edge; start timer - if up_down_diff or left_right_diff: - time_mark = time.monotonic() + # Wait a very short time to see if new FIFO data has arrived before we drop out + time.sleep(0.03) - # finished when a gesture is detected or ran out of time (300ms) - if gesture_received or time.monotonic() - time_mark > 0.300: - self._reset_counts() - break - if gesture_received != 0: + # If we only got one useful frame, that's not enough to make a solid guess + if len(frame) < 2: + return 0 + + # We should have a dataframe with two tuples now, a "first" and "last" entry. + # Time to process the dataframe! + + # Determine our up/down and left/right ratios along with our first/last deltas + f_r_ud = ((frame[0][0] - frame[0][1]) * 100) // (frame[0][0] + frame[0][1]) + f_r_lr = ((frame[0][2] - frame[0][3]) * 100) // (frame[0][2] + frame[0][3]) + + l_r_ud = ((frame[1][0] - frame[1][1]) * 100) // (frame[1][0] + frame[1][1]) + l_r_lr = ((frame[1][2] - frame[1][3]) * 100) // (frame[1][2] + frame[1][3]) + + delta_ud = l_r_ud - f_r_ud + delta_lr = l_r_lr - f_r_lr + + # Make our first guess at what gesture we saw, if any + state_ud = 0 + state_lr = 0 + + if delta_ud >= 30: + state_ud = 1 + elif delta_ud <= -30: + state_ud = -1 + + if delta_lr >= 30: + state_lr = 1 + elif delta_lr <= -30: + state_lr = -1 + + # Make our final decision based on our first guess and, if required, the delta data + gesture_found = 0 + + # Easy cases + if state_ud == -1 and state_lr == 0: + gesture_found = 1 + elif state_ud == 1 and state_lr == 0: + gesture_found = 2 + elif state_ud == 0 and state_lr == -1: + gesture_found = 3 + elif state_ud == 0 and state_lr == 1: + gesture_found = 4 + + # Not so easy cases + if gesture_found == 0: + if state_ud == -1 and state_lr == 1: + if abs(delta_ud) > abs(delta_lr): + gesture_found = 1 + else: + gesture_found = 4 + elif state_ud == 1 and state_lr == -1: + if abs(delta_ud) > abs(delta_lr): + gesture_found = 2 + else: + gesture_found = 3 + elif state_ud == -1 and state_lr == -1: + if abs(delta_ud) > abs(delta_lr): + gesture_found = 1 + else: + gesture_found = 3 + elif state_ud == 1 and state_lr == 1: + if abs(delta_ud) > abs(delta_lr): + gesture_found = 2 + else: + gesture_found = 3 + + if gesture_found != 0: if self._rotation != 0: - - directions = [1, 4, 2, 3] - new_index = (directions.index(gesture_received) + self._rotation // 90) % 4 - return directions[new_index] + # If we need to rotate our gesture, lets do that before returning + dir_lookup = [1, 4, 2, 3] + idx = (dir_lookup.index(gesture_found) + self._rotation // 90) % 4 + return dir_lookup[idx] - return gesture_received + return gesture_found ## COLOR @property @@ -409,40 +492,6 @@ def color_data(self) -> Tuple[int, int, int, int]: self._color_data16(_APDS9960_CDATAL), ) - ## PROXIMITY - @property - def proximity_interrupt_threshold(self) -> Tuple[int, int, int]: - """Tuple containing low and high threshold - followed by the proximity interrupt persistence. - When setting the proximity interrupt threshold values using a tuple of - zero to three values: low threshold, high threshold, persistence. - persistence defaults to 4 if not provided""" - return ( - self._read8(_APDS9960_PILT), - self._read8(_APDS9960_PIHT), - self._proximity_persistence, - ) - - @proximity_interrupt_threshold.setter - def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: - if setting_tuple: - self._write8(_APDS9960_PILT, setting_tuple[0]) - if len(setting_tuple) > 1: - self._write8(_APDS9960_PIHT, setting_tuple[1]) - persist = 4 # default 4 - if len(setting_tuple) > 2: - persist = min(setting_tuple[2], 7) - self._proximity_persistence = persist - - @property - def proximity(self) -> int: - """Proximity value: range 0-255""" - return self._read8(_APDS9960_PDATA) - - def clear_interrupt(self) -> None: - """Clear all interrupts""" - self._writecmdonly(_APDS9960_AICLEAR) - # method for reading and writing to I2C def _write8(self, command: int, abyte: int) -> None: """Write a command and 1 byte of data to the I2C device""" @@ -467,41 +516,45 @@ def _read8(self, command: int) -> int: i2c.write_then_readinto(buf, buf, out_end=1, in_end=1) return buf[0] - def _get_bit(self, register: int, bitmask: int) -> int: + def _get_bit(self, register: int, mask: int) -> bool: + """Gets a single bit value from the I2C device's register""" buf = self.buf2 buf[0] = register with self.i2c_device as i2c: i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) - return bool(buf[1] & bitmask) + return bool(buf[1] & mask) - def _set_bit(self, register: int, bitmask: int, value: bool) -> None: + def _set_bit(self, register: int, mask: int, value: bool) -> None: + """Sets a single bit value in the I2C device's register""" buf = self.buf2 buf[0] = register with self.i2c_device as i2c: i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) if value: - buf[1] |= bitmask + buf[1] |= mask else: - buf[1] &= ~bitmask + buf[1] &= ~mask with self.i2c_device as i2c: i2c.write(buf, end=2) - def _get_bits(self, register: int, bit_position: int, bit_mask: int) -> int: + def _get_bits(self, register: int, pos: int, mask: int) -> int: + """Sets a multi-bit value in the I2C device's register""" buf = self.buf2 buf[0] = register with self.i2c_device as i2c: i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) - return (buf[1] & bit_mask) >> bit_position + return (buf[1] & mask) >> pos - def _set_bits(self, register: int, bit_position: int, bit_mask: int, value: int) -> None: + def _set_bits(self, register: int, pos: int, mask: int, value: int) -> None: + """Sets a multi-bit value in the I2C device's register""" buf = self.buf2 buf[0] = register with self.i2c_device as i2c: i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) - buf[1] = (buf[1] & ~bit_mask) | (value << bit_position) + buf[1] = (buf[1] & ~mask) | (value << pos) with self.i2c_device as i2c: i2c.write(buf, end=2) - + def _color_data16(self, command: int) -> int: """Sends a command and reads 2 bytes of data from the I2C device The returned data is low byte first followed by high byte""" From faa7969c4fd63ce818b51409e4834f95230a3ce7 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Sun, 28 Nov 2021 22:58:38 -0600 Subject: [PATCH 08/14] Gesture refactor, property/doc cleanup (update) --- adafruit_apds9960/apds9960.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index c8cbbb6..5f8f497 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -9,7 +9,7 @@ Driver class for the APDS9960 board. Supports gesture, proximity, and color detection. -* Author(s): Michael McWethy +* Author(s): Michael McWethy, Erik Hess Implementation Notes -------------------- @@ -116,7 +116,7 @@ _BIT_POS_PERS_PPERS = const(4) _BIT_MASK_PERS_PPERS = const(0xF0) -# pylint: disable-msg=too-many-instance-attributes + class APDS9960: """ Provide basic driver services for the APDS9960 breakout board @@ -174,8 +174,10 @@ def __init__( raise RuntimeError() if reset: - # Disable sensor and all functions/interrupts - self._write8(_APDS9960_ENABLE, 0) + # Disable prox, gesture, and color engines + self.enable_proximity = False + self.enable_gesture = False + self.enable_color = False # Reset basic config registers to power-on defaults self.proximity_interrupt_threshold = (0, 0, 0) @@ -194,14 +196,18 @@ def __init__( # Clear gesture FIFOs and interrupt self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GFIFO_CLR, True) - # Enable sensor and wait 10ms for the power on delay to finish + # Disable sensor and all functions/interrupts + self._write8(_APDS9960_ENABLE, 0) + time.sleep(0.025) # Sleeping could take at ~2-25 ms if engines were looping + + # Re-enable sensor and wait 10ms for the power on delay to finish self.enable = True time.sleep(0.010) if set_defaults: - # Trigger PINT at >= 5, PPERS: 4 cycles + # Trigger proximity interrupt at >= 5, PPERS: 4 cycles self.proximity_interrupt_threshold = (0, 5, 4) - # Enter gesture engine at >= 5 counts + # Enter gesture engine at >= 5 proximity counts self._write8(_APDS9960_GPENTH, 0x05) # Exit gesture engine if all counts drop below 30 self._write8(_APDS9960_GEXTH, 0x1E) @@ -324,10 +330,7 @@ def proximity(self) -> int: def gesture(self) -> int: """Returns gesture code if detected. 0 if no gesture detected - 1 if up, - 2 if down, - 3 if left, - 4 if right + 1 = up, 2 = down, 3 = left, 4 = right """ # If FIFOs have overflowed we're already way too late, so clear those FIFOs and wait if self._get_bit(_APDS9960_GSTATUS, _BIT_MASK_GSTATUS_GFOV): @@ -479,7 +482,7 @@ def gesture(self) -> int: ## COLOR @property def color_data_ready(self) -> int: - """Color data ready flag. zero if not ready, 1 is ready""" + """Color data ready flag. Zero if not ready, 1 if ready""" return self._read8(_APDS9960_STATUS) & 0x01 @property From 0b1887349c1a6d8659a4f5a5b45d750640fdce82 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Wed, 1 Dec 2021 19:04:42 -0600 Subject: [PATCH 09/14] Color engine tweaks and doc updates --- adafruit_apds9960/apds9960.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index 5f8f497..b8d2015 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -109,6 +109,7 @@ _BIT_MASK_ENABLE_PROX = const(0x04) _BIT_MASK_ENABLE_PROX_INT = const(0x20) _BIT_MASK_ENABLE_GESTURE = const(0x40) +_BIT_MASK_STATUS_AVALID = const(0x01) _BIT_MASK_STATUS_GINT = const(0x04) _BIT_MASK_GSTATUS_GFOV = const(0x02) _BIT_MASK_GCONF4_GFIFO_CLR = const(0x04) @@ -217,9 +218,9 @@ def __init__( self._write8(_APDS9960_GCONF2, 0x41) # GPULSE: 5 (6 pulses), GPLEN: 2 (16 us) self._write8(_APDS9960_GPULSE, 0x85) - # ATIME: 182 (200ms color integration time) - self._write8(_APDS9960_ATIME, 0xB6) - # AGAIN: 1 (4x color gain), PGAIN: 0 (1x) + # ATIME: 255 (712ms color integration time, max count of 65535) + self._write8(_APDS9960_ATIME, 0x00) + # AGAIN: 1 (4x color gain), PGAIN: 0 (1x) (default), LDRIVE: 0 (100 mA) (default) self._write8(_APDS9960_CONTROL, 0x01) ## BOARD @@ -318,6 +319,15 @@ def enable_color(self, value: bool) -> None: """If true, the sensor's color/light engine is enabled""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR, value) + @property + def color_data_ready(self) -> int: + """Color data ready flag. + + Returns ``0`` if no new data is ready, ``1`` if new data is ready. + + This flag is reset when `color_data` is read.""" + return self._get_bit(_APDS9960_STATUS, _BIT_MASK_STATUS_AVALID) + ## PROXIMITY @property def proximity(self) -> int: @@ -480,14 +490,17 @@ def gesture(self) -> int: return gesture_found ## COLOR - @property - def color_data_ready(self) -> int: - """Color data ready flag. Zero if not ready, 1 if ready""" - return self._read8(_APDS9960_STATUS) & 0x01 - @property def color_data(self) -> Tuple[int, int, int, int]: - """Tuple containing r, g, b, c values""" + """Tuple containing red, green, blue, and clear light intensity values detected by the + sensor during the latest color/light engine run. + + Each value is a 16-bit integer with a possible value of ``0`` to ``65535``. + + .. hint:: Testing with and tuning `color_gain` and `color_integration_time` values will + likely be required to get useful color results. Optimum values for these will depend + largely on the implementation details such as sensor positioning, illumination intensity + and color temperature, reflectivity of the object(s) being measured, etc.""" return ( self._color_data16(_APDS9960_CDATAL + 2), self._color_data16(_APDS9960_CDATAL + 4), From c02b1df8730b88ac06e39ee75c42ca7d6bccb267 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Thu, 2 Dec 2021 10:56:38 -0600 Subject: [PATCH 10/14] Adding color engine tuning properties --- adafruit_apds9960/apds9960.py | 58 ++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index b8d2015..dced402 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -117,7 +117,10 @@ _BIT_POS_PERS_PPERS = const(4) _BIT_MASK_PERS_PPERS = const(0xF0) +_BIT_POS_CONTROL_AGAIN = const(0) +_BIT_MASK_CONTROL_AGAIN = const(3) +# pylint: disable-msg=too-many-instance-attributes class APDS9960: """ Provide basic driver services for the APDS9960 breakout board @@ -218,10 +221,10 @@ def __init__( self._write8(_APDS9960_GCONF2, 0x41) # GPULSE: 5 (6 pulses), GPLEN: 2 (16 us) self._write8(_APDS9960_GPULSE, 0x85) - # ATIME: 255 (712ms color integration time, max count of 65535) - self._write8(_APDS9960_ATIME, 0x00) - # AGAIN: 1 (4x color gain), PGAIN: 0 (1x) (default), LDRIVE: 0 (100 mA) (default) - self._write8(_APDS9960_CONTROL, 0x01) + # ATIME: 256 (712ms color integration time, max count of 65535) + self.color_integration_time = 256 + # AGAIN: 1 (4x color gain) + self.color_gain = 1 ## BOARD @property @@ -328,6 +331,53 @@ def color_data_ready(self) -> int: This flag is reset when `color_data` is read.""" return self._get_bit(_APDS9960_STATUS, _BIT_MASK_STATUS_AVALID) + @property + def color_gain(self) -> int: + """Color/light sensor gain value. + + This sets the gain multiplier for the ADC during color/light engine operations. + + .. csv-table:: + :header: "``color_gain``", "Gain Multiplier", "Note" + + 0, "1x", "Power-on Default" + 1, "4x", "Driver Default" + 2, "16x", "" + 3, "64x", "" + """ + return self._get_bits( + _APDS9960_CONTROL, _BIT_POS_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN + ) + + @color_gain.setter + def color_gain(self, value: int) -> None: + self._set_bits( + _APDS9960_CONTROL, _BIT_POS_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN, value + ) + + @property + def color_integration_time(self) -> int: + """Color/light sensor gain. + + Represents the integration time in number of 2.78 ms cycles for the ADC during color/light + engine operations. This also effectively sets the maxmium value returned for each channel + by `color_data`. + + .. csv-table:: + :header: "``color_integration_time``", "Time", "Max Count", "Note" + + 1, "2.78 ms", 1025, "Power-on Default" + 10, "27.8 ms", 10241, "" + 37, "103 ms", 37889, "" + 72, "200 ms", 65535, "Driver Default" + 256, "712 ms", 65535, "" + """ + return 256 - self._read8(_APDS9960_ATIME) + + @color_integration_time.setter + def color_integration_time(self, value: int) -> None: + self._write8(_APDS9960_ATIME, 256 - value) + ## PROXIMITY @property def proximity(self) -> int: From 4c97c604b565b61333d3ff37d4f815bc8d7087e7 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Thu, 2 Dec 2021 13:39:39 -0600 Subject: [PATCH 11/14] Docstring overhaul --- adafruit_apds9960/apds9960.py | 280 +++++++++++++++++++++++++++++----- 1 file changed, 241 insertions(+), 39 deletions(-) diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index dced402..7670bd9 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -19,13 +19,13 @@ * Adafruit `APDS9960 Proximity, Light, RGB, and Gesture Sensor `_ (Product ID: 3595) -* Adafruit `Adafruit CLUE +* Adafruit `CLUE `_ (Product ID: 4500) -* Adafruit `Adafruit Feather nRF52840 Sense +* Adafruit `Feather nRF52840 Sense `_ (Product ID: 4516) -* Adafruit `Adafruit Proximity Trinkey +* Adafruit `Proximity Trinkey `_ (Product ID: 5022) **Software and Dependencies:** @@ -148,13 +148,16 @@ class APDS9960: i2c = board.I2C() # uses board.SCL and board.SDA apds = APDS9960(i2c) - Now you have access to the :attr:`apds.proximity_enable` :attr:`apds.proximity` attributes + Now you have access to the :attr:`apds.proximity_enable` and :attr:`apds.proximity` + attributes .. code-block:: python apds.proximity_enable = True proximity = apds.proximity + .. note:: There is no ``address`` argument because the APDS-9960 only has one address and + doesn't offer any option to configure alternative addresses. """ def __init__( @@ -168,8 +171,8 @@ def __init__( self.rotation = rotation - self.buf129 = None # Gesture FIFO buffer - self.buf4 = None # Gesture data processing buffer + self.buf129 = None # Gesture FIFO buffer, only instantiated if needed + self.buf4 = None # Gesture data processing buffer, only instantiated if needed self.buf2 = bytearray(2) # I2C communication buffer self.i2c_device = I2CDevice(i2c, _APDS9960_I2C_ADDRESS) @@ -229,40 +232,125 @@ def __init__( ## BOARD @property def enable(self) -> bool: - """If true, the sensor is enabled - If set to false, the sensor will enter a low-power sleep state""" + """If ``True``, the sensor is enabled. + + If set to ``False``, the sensor will enter a low-power sleep state + + When enabled, the sensor's state machine will run through the following steps in sequence, + repeating from the top after all states are run through. + + #. **Idle State** + + - Will only remain in this state if all three sense engines are disabled. + + #. **Proximity Engine** *(if enabled)* + + - Will only run if `enable_proximity` is ``True``. + - Will run once, storing fresh data in the sensor's proximity data registers. If + proximity data is is lower than or exceeds the configured proximity thresholds an + internal persistence is incremented on each run as well. + + #. **Gesture Engine** *(if enabled)* + + - Will only run if `enable_gesture` is ``True`` and if entry threshold of `proximity` + is greater or equal to the gesture proximity entry threshold of 5 counts. + - Will continuously loop, storing new results in the sensor's gesture FIFO buffers, + until one of four conditions occur. + + - Exit threshold is met. *(all gesture measurements <= 30 counts)* + - The gesture engine or sensor are disabled. *(`enable_gesture` or `enable` + properties are set to ``False``)* + - The sensor is re-initalized by the driver + - The sensor is power cycled + + #. **Wait Timer** *(set to 0 by default)* + + - This driver does not set or make available the ``WAIT`` or ``WLONG`` registers that + control this function and, on intialization, leaves the timer at its power-on default + state of ``0``, effectively disabling this timer. + + #. **Color/Light Engine** *(if enabled)* + + - Will run start if `enable_color` is ``True``. + - Will run once, storing fresh data in the sensor's color data registers on each run. + + .. note:: Waking the sensor from its sleep state takes at least 7 ms. Disabling the sensor + and entering a sleep state can take as little as 2.78 ms, more typically about 25 ms, or + potentially quite a bit longer depending on what engines were enabled and what state was + active at the time the disable command was received. + + .. hint:: When in a sleep state the sensor's power usage drops to as little as 1-10 uA, + compared as much as 790 uA of power usage when enabled with proximity and/or gesture + engines running. While in a sleep state, the sensor will still listen for and respond + to I2C communication which can lead to minor increases in power usage. + """ return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN) @enable.setter def enable(self, value: bool) -> None: - """If true, the sensor is enabled - If set to false, the sensor will enter a low-power sleep state""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN, value) ## Proximity Properties @property def enable_proximity(self) -> bool: - """If true, the sensor's proximity engine is enabled""" + """If ``True``, the sensor's proximity engine is enabled.""" return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX) @enable_proximity.setter def enable_proximity(self, value: bool) -> None: - """If true, the sensor's proximity engine is enabled""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX, value) @property def enable_proximity_interrupt(self) -> bool: - """If true, internal proximity interrupts assert interrupt pin""" + """If ``True``, the internal proximity interrupt asserts the sensor's interrupt pin. + + Internal proximity interrupt triggering is configured via `proximity_interrupt_threshold`. + + .. tip:: Using this interrupt will require attaching the sensor's ``INT`` pin to an + available digital I/O with an internal or external pull-up resistor. + + For boards with built-in sensors the pin is likely already mapped within ``board``. + + .. csv-table:: + :header: "Board", "Pin Mapping" + + "CLUE", "``board.PROXIMITY_LIGHT_INTERRUPT``" + "Feather nRF52840 Sense", "``board.PROXIMITY_LIGHT_INTERRUPT``" + "Proximity Trinkey", "``board.INTERRUPT``" + """ return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX_INT) @enable_proximity_interrupt.setter def enable_proximity_interrupt(self, value: bool) -> None: - """If true, internal proximity interrupts assert interrupt pin""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX_INT, value) @property def proximity_interrupt_threshold(self) -> Tuple[int, int, int]: - """Tuple representing proximity engine low/high threshold (0-255) and persistence (0-15)""" + """Tuple representing proximity engine low/high threshold and persistence, which determine + when the sensor's proximity interrupt is asserted. + + 1. Low Threshold (``PILT``) + 2. High Threshold (``PIHT``) *(optional)* + 3. Proximity Persistence (``PERS``) *(optional)* + + The first two items are the "low threshold" and "high threshold" values. These can be set + to any number between ``0`` and ``255``. If the proximity value is lower than the low + threshold or higher than the high threshold for enough cycles, an interrupt will be + asserted. + + The third item is the "persistence" value. This can be set to any value between ``0`` to + ``15``. This represents the number of 2.78 ms out-of-threshold cycles to wait for before + asserting the interrupt. This is basically a filter to prevent premature/false interrupts. + + .. hint:: For example, setting a low threshold of ``0`` and a high threshold of ``5`` will + cause the interrupt to be asserted very early when an object **enters the sensor's line + of sight**. Coversely, a low threshold of ``5`` and a high threshold of ``255`` will + trigger an interrupt only if an object **is removed from the sensor's line of sight**. + + .. hint:: Tuning the persistence value can be useful in some use cases but for most + situations the driver's default value of 4 should provide for stable results without + much delay in interrupt triggering. + """ return ( self._read8(_APDS9960_PILT), self._read8(_APDS9960_PIHT), @@ -271,41 +359,64 @@ def proximity_interrupt_threshold(self) -> Tuple[int, int, int]: @proximity_interrupt_threshold.setter def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: - """Tuple representing proximity engine low/high threshold (0-255) and persistence (0-15)""" - if setting_tuple: + if setting_tuple and 0 <= setting_tuple[0] <= 255: self._write8(_APDS9960_PILT, setting_tuple[0]) - if len(setting_tuple) > 1: + if len(setting_tuple) > 1 and 0 <= setting_tuple[0] <= 255: self._write8(_APDS9960_PIHT, setting_tuple[1]) persist = 4 # default 4 - if len(setting_tuple) > 2: + if len(setting_tuple) > 2 and 0 <= setting_tuple[0] <= 15: persist = min(setting_tuple[2], 15) self._set_bits( _APDS9960_PERS, _BIT_POS_PERS_PPERS, _BIT_MASK_PERS_PPERS, persist ) def clear_interrupt(self) -> None: - """Clear all non-gesture interrupts""" + """Clears all non-gesture interrupts. + + This includes all of the following internal interrupts: + + * **Proximity Interrupt** (``PINT``) + * **Proximity Saturation Interrupt** (``STATUS``) + * **Color/Light Interrupt** (``STATUS``) + * **Color/Light Clear Saturation Interrupt** (``STATUS``) + """ self._writecmdonly(_APDS9960_AICLEAR) ## Gesture Properties @property def enable_gesture(self) -> bool: - """If true, the sensor's gesture engine is enabled""" + """If ``True``, the sensor's gesture engine is enabled. + + .. note:: The gesture engine will only operate if `enable_proximity` is also set to ``True`` + """ return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE) @enable_gesture.setter def enable_gesture(self, value: bool) -> None: - """If true, the sensor's gesture engine is enabled""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE, value) @property def rotation(self) -> int: - """Gesture rotation offset. Acceptable values are 0, 90, 180, 270.""" + """Clock-wise offset to apply to gesture results. + + Acceptable values are ``0``, ``90``, ``180``, ``270``. + + .. tip:: The sensor's "top" end is the one with the larger of the two circular windows. + + Some rotation examples for various boards with the APS-9960 built in: + + .. csv-table:: + :header: "Board", "Rotation" + + "CLUE", 270 + "Feather nRF52840 Sense, with USB port to the left", 270 + "Proximity Trinkey, plugged into right-side laptop USB port", 270 + "Proximity Trinkey, plugged into left-side laptop USB port", 90 + """ return self._rotation @rotation.setter def rotation(self, new_rotation: int) -> None: - """Gesture rotation offset. Acceptable values are 0, 90, 180, 270.""" if new_rotation in [0, 90, 180, 270]: self._rotation = new_rotation else: @@ -314,12 +425,11 @@ def rotation(self, new_rotation: int) -> None: ## Color/Light Properties @property def enable_color(self) -> bool: - """If true, the sensor's color/light engine is enabled""" + """If ``True``, the sensor's color/light engine is enabled""" return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR) @enable_color.setter def enable_color(self, value: bool) -> None: - """If true, the sensor's color/light engine is enabled""" self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR, value) @property @@ -344,7 +454,20 @@ def color_gain(self) -> int: 1, "4x", "Driver Default" 2, "16x", "" 3, "64x", "" - """ + + .. tip:: To get useful, predictable `color_data` results it is important to tune this, + along with `color_integration_time`, to accommodate different lighting conditions, sensor + placements, material transparencies, expected object reflectivity, and environmental + conditions. + + For instance, measuring color of objects close to the sensor with bright, nearby + illumination (such as the white LEDs on the `Adafruit CLUE + `_) may work well with a `color_gain` of ``0`` + and a `color_integration_time` of ``72`` or lower. + + However, measuring the intensity and color temperature of ambient light through + difusion glass or plastic is likely to require experimenting with a wide range of + integration time and gain settings before useful data can be obtained.""" return self._get_bits( _APDS9960_CONTROL, _BIT_POS_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN ) @@ -369,8 +492,8 @@ def color_integration_time(self) -> int: 1, "2.78 ms", 1025, "Power-on Default" 10, "27.8 ms", 10241, "" 37, "103 ms", 37889, "" - 72, "200 ms", 65535, "Driver Default" - 256, "712 ms", 65535, "" + 72, "200 ms", 65535, "" + 256, "712 ms", 65535, "Driver Default" """ return 256 - self._read8(_APDS9960_ATIME) @@ -381,17 +504,84 @@ def color_integration_time(self, value: int) -> None: ## PROXIMITY @property def proximity(self) -> int: - """Proximity value: 0-255 - lower values are farther, higher values are closer""" + """Proximity sensor data. + + The proximity engine returns a number between ``0`` and ``255`` which represents the + intensity of the reflected IR light detected from the sensor's internal LEDs, which pulse + continously during proximity engine operation. + + A value of ``0`` indicates no reflected IR light was received. This typically indicates + that no object(s) were in the sensor's line of sight and within detectable range of its IR + LED pulses. + + A value of ``255`` indicates that the maximum detectable amount of reflected IR light was + received. This typically indicates that an object was detected very close to the sensor. + + .. caution:: Will always return ``0`` if `enable_proximity` is not set ``True``. + + .. note:: The sensor itself offers a very wide variety of configuration options for tuning + the proximity engine, such as the LEDs (pulse count/length, drive power) and the + photosensors (gain, offsets, masking). However, this driver does not make those readily + available in order to keep file size and memory footprint to a minimum, which is + critical for its use on more constrained platforms. + """ return self._read8(_APDS9960_PDATA) ## GESTURE DETECTION # pylint: disable-msg=too-many-branches,too-many-locals,too-many-statements def gesture(self) -> int: - """Returns gesture code if detected. - 0 if no gesture detected - 1 = up, 2 = down, 3 = left, 4 = right - """ + """Gesture sensor data. + + This checks the sensor for new gesture engine results and, if they are present, retrieves + and processes the results to determine what, if any, gesture can be deduced from the sensor + data. + + Returns a gesture code indicating the direction of the gesture. Before returning the code, + the `rotation` value is used to "rotate" the result as intended. + + .. csv-table:: + :header: "Code", "Direction" + + 0, "No gesture detected" + 1, "Up" + 2, "Down" + 3, "Left" + 4, "Right" + + .. caution:: Will always return ``0`` if `enable_proximity` and `enable_gesture` are not set + to ``True``. + + The data returned by the sensor is a continuous stream of four proximtiy measurements + constrained to up/down/left/right dimensions by using four directionally-aligned sensors. + The sensor itself doesn't include any logic to determine the gesture, leaving that work to + the implementer. + + This driver implements an algorithm that reliably detects simple gestures in most scenarios + while remaining small and efficient enough to work within the resource constraints of as + many CircuitPython boards/platforms as possible. + + .. tip:: Detecting gestures with this driver's algorithm requires actively, continously + polling for a gesture, with as little time as possible between `gesture()` calls. Even + with continous polling, however, its possible that gestures may go undetected by + `gesture()` calls if they occurred between or on the edges of `gesture()` method + execution. + + .. warning:: If gesture data becomes available from the sensor, this driver will + continuously pull in that new data and analyze it until the sensor's gesture engine + exits and the sensor's FIFO buffers are clear. This allows for much more reliable + gesture detection by comparing the "first" to the "last" detected state at the cost of + blocking until the FIFOs are all clear. This will only happen if all four gesture values + drop below ``30``. + + As a result, if an object is close to the sensor when `gesture()` is called, the method + will not return until it moves away. + + .. note:: The sensor itself offers a very wide variety of configuration options for tuning + the gesture engine, such as the LEDs (pulse count/length, drive power), the photosensors + (gain, offsets, masking), the gesture engine's entry/exit thresholds, wait time, and + more. However, this driver does not make those readily available in order to keep file + size and memory footprint to a minimum, which is critical for its use on more + constrained platforms.""" # If FIFOs have overflowed we're already way too late, so clear those FIFOs and wait if self._get_bit(_APDS9960_GSTATUS, _BIT_MASK_GSTATUS_GFOV): self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GFIFO_CLR, True) @@ -547,10 +737,22 @@ def color_data(self) -> Tuple[int, int, int, int]: Each value is a 16-bit integer with a possible value of ``0`` to ``65535``. - .. hint:: Testing with and tuning `color_gain` and `color_integration_time` values will - likely be required to get useful color results. Optimum values for these will depend - largely on the implementation details such as sensor positioning, illumination intensity - and color temperature, reflectivity of the object(s) being measured, etc.""" + .. caution:: Will always return ``(0, 0, 0, 0)`` if `enable_color` is not set to ``True``. + + .. tip:: To get useful, predictable `color_data` results it is important to tune + `color_gain` and `color_integration_time`, to accommodate different lighting conditions, + sensor placements, plastic/glass transparencies, expected object reflectivity, and + environmental conditions. + + For instance, measuring color of objects close to the sensor with bright, nearby + illumination (such as the white LEDs on the `Adafruit Clue + `_) may work well with a `color_gain` of ``0`` + and a `color_integration_time` of ``72``. + + However, measuring the intensity and color temperature of ambient light through + difusion glass or plastic is likely to require experimenting with a wide range of + `color_gain` and `color_integration_time` settings before useful data can be obtained. + """ return ( self._color_data16(_APDS9960_CDATAL + 2), self._color_data16(_APDS9960_CDATAL + 4), From b04fac47692a7139451926a9ba3ef056530db873 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Thu, 2 Dec 2021 13:42:58 -0600 Subject: [PATCH 12/14] README update --- README.rst | 173 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 125 insertions(+), 48 deletions(-) diff --git a/README.rst b/README.rst index d90fad3..f366967 100644 --- a/README.rst +++ b/README.rst @@ -14,10 +14,17 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_APDS9960/actions/ :alt: Build Status -The APDS9960 is a specialized chip that detects hand gestures, proximity -and ambient light color over I2C. Its available on -`Adafruit as a breakout `_. +The APDS-9960 is a specialized chip that detects hand gestures, proximity +and ambient light color over I2C. Its available from +`Adafruit as a breakout `_ and as a built-in sensor on +several Adafruit development boards. +* `Adafruit CLUE `_ +* `Adafruit Feather nRF52840 Sense `_ +* `Adafruit Proximity Trinkey `_ + +This driver provides easy access to proximity, gesture and color data from the APDS-9960 sensor +with a minimal footprint to allow it to work on all CircuitPython platforms. Installation and Dependencies ============================= @@ -26,13 +33,16 @@ This driver depends on: * `Adafruit CircuitPython `_ Please ensure all dependencies are available on the CircuitPython filesystem. + This is easily achieved by downloading `the Adafruit library and driver bundle `_. Installing from PyPI -------------------- -On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from PyPI `_. To install for current user: +On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from PyPI `_. + +To install for current user: .. code-block:: shell @@ -64,98 +74,165 @@ Usage Example i2c = board.I2C() int_pin = digitalio.DigitalInOut(board.D5) + int_pin.switch_to_input(pull=digitalio.Pull.UP) apds = APDS9960(i2c) - apds.enable_proximity = True - apds.proximity_interrupt_threshold = (0, 175) apds.enable_proximity_interrupt = True + apds.proximity_interrupt_threshold = (0, 175) + apds.enable_proximity = True while True: + if not int_pin.value: print(apds.proximity) apds.clear_interrupt() Hardware Set-up --------------- -Connect Vin to 3.3 V or 5 V power source, GND to ground, SCL and SDA to the appropriate pins. +If you're using a board with a built-in APDS-9960, no hardware setup will be required. + +If you're using a breakout board via the pin header, connect ``Vin`` to a 3.3 V or 5 V power source, +connect ``GND`` to ground, then connect ``SCL`` and ``SDA`` to the appropriate pins. + +Optionally, if you'd like to use the sensor's interrupt pin connect ``INT`` to any available +digital I/O pin. Basics ------ -Of course, you must import i2c bus device, board pins, and the library: +To get started, import ``board`` and, and this library: .. code:: python3 - import board - from adafruit_apds9960.apds9960 import APDS9960 - import digitalio + import board + from adafruit_apds9960.apds9960 import APDS9960 -To set-up the device to gather data, initialize the I2CDevice using SCL -and SDA pins. Then initialize the library. Optionally provide an interrupt -pin for proximity detection. +To set up the sensor to gather data, initialize the I2C bus via ``board.I2C()`` +then initialize the APDS-9960 library. .. code:: python3 - int_pin = digitalio.DigitalInOut(board.A1) - i2c = board.I2C() - apds = APDS9960(i2c) + i2c = board.I2C() + apds = APDS9960(i2c) + +Proximity +--------- + +To get a proximity result, enable the proximity engine then read the `proximity` value. + +This will return a value between 0 and 255, with higher values indicating that something is close +to the sensor. + +.. code:: python3 + + apds.enable_proximity = True + + while True: + print(apds.proximity) Gestures -------- -To get a gesture, see if a gesture is available first, then get the gesture Code +First, enable both the proximity and gesture engines. The gesture engine relies on the proximity +engine to determine when to start itself up and, as a result, proximity readings won't be reliable +while the gesture engine is enabled. + +To get a gesture, use the `gesture()` function to see if a gesture has been detected. If a value +greater than 0 is returned, a gesture has been detected. .. code:: python3 - gesture = apds.gesture() - if gesture == 1: - print("up") - if gesture == 2: - print("down") - if gesture == 3: - print("left") - if gesture == 4: - print("right") + # Uncomment and set the rotation if depending on how your sensor is mounted. + # apds.rotation = 270 # 270 for CLUE -Color Measurement ------------------ + apds.enable_proximity = True + apds.enable_gesture = True -To get a color measure, enable color measures, wait for color data, -then get the color data. + while True: + gesture = apds.gesture() + if gesture == 1: + print("up") + if gesture == 2: + print("down") + if gesture == 3: + print("left") + if gesture == 4: + print("right") + +Color/Light Measurement +----------------------- + +To get a color measurement, first enable the color/light engine, wait for color data to arrive, +then read the `color_data` values. .. code:: python3 - apds.enable_color = True + apds.enable_color = True + + while True: + while not apds.color_data_ready: + time.sleep(0.005) + + r, g, b, c = apds.color_data + print("r: {}, g: {}, b: {}, c: {}".format(r, g, b, c)) - while not apds.color_data_ready: - time.sleep(0.005) +Interrupt Pin +------------- - r, g, b, c = apds.color_data - print("r: {}, g: {}, b: {}, c: {}".format(r, g, b, c)) +This sensor has an interrupt pin can be asserted (pulled low) if proximity is detected outside of a +specified window of values. + +For boards with a built-in APDS-9960 this interupt pin will already be defined. For example, on the +Clue and Feather nRF52840 Sense boards this pin is mapped to ``board.PROXIMITY_LIGHT_INTERRUPT`` +and on the Proximity Trinkey it is mapped to ``board.INTERRUPT``. + +.. code:: python3 + + int_pin = digitalio.DigitalInOut(board.D5) + int_pin.switch_to_input(pull=digitalio.Pull.UP) Proximity Detection ---------------------- +------------------- + +With the interrupt pin set up we can define a threshold and enable the assertion of the sensor's +interrupt pin by the proximity engine before enabling the proximity engine itself. -To check for a object in proximity, see if a gesture is available first, then get the gesture Code +In this configuration, the sensor's interrupt pin will be asserted when an object is close to the +sensor. After checking on the interrupt it can be cleared using `clear_interrupt()` .. code:: python3 - apds.enable_proximity = True + apds.enable_proximity = True + + # set the interrupt threshold to fire when proximity reading goes above 175 + apds.proximity_interrupt_threshold = (0, 175) - # set the interrupt threshold to fire when proximity reading goes above 175 - apds.proximity_interrupt_threshold = (0, 175) + # assert interrupt pin on internal proximity interrupt + apds.enable_proximity_interrupt = True - # enable the proximity interrupt - apds.enable_proximity_interrupt = True + # enable the sensor's proximity engine + apds.enable_proximity = True - while True: - if not interrupt_pin.value: - print(apds.proximity) + while True: + if not interrupt_pin.value: + print(apds.proximity) + + # clear the interrupt + apds.clear_interrupt() + +Initiaization Options +---------------------- - # clear the interrupt - apds.clear_interrupt() +By default, when the driver is initialized, the APDS-9960 sensor's internal settings are reset and +sensible defaults are applyed to several low-level settings that should work well for most use cases. + +If either the "reset" or "set defauls" behaviors (or both) aren't desired, they can be individually +disabled via init kwargs. + +.. code:: python3 + apds = APDS9960(i2c, reset=False, set_defaults=False) Documentation ============= From 46636f1d1521f3c1558c3aed7b667f31dadc9825 Mon Sep 17 00:00:00 2001 From: Erik Hess Date: Thu, 2 Dec 2021 13:45:51 -0600 Subject: [PATCH 13/14] examples update --- docs/examples.rst | 26 +++++++++++------------ examples/apds9960_color_simpletest.py | 2 -- examples/apds9960_proximity_simpletest.py | 8 ++++++- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index e3285ce..3151df1 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -3,35 +3,35 @@ Simple test Ensure your device works with this simple test. -.. literalinclude:: ../examples/apds9960_color_simpletest.py - :caption: examples/apds9960_color_simpletest.py +.. literalinclude:: ../examples/apds9960_simpletest.py + :caption: examples/apds9960_simpletest.py :linenos: -Gesture Example ---------------- +Proximity Example +----------------- -Show how to use the device with simple gestures +Example illustrating proximity detection -.. literalinclude:: ../examples/apds9960_gesture_simpletest.py - :caption: examples/apds9960_gesture_simpletest.py +.. literalinclude:: ../examples/apds9960_proximity_simpletest.py + :caption: examples/apds9960_proximity_simpletest.py :linenos: -Proximity Example ------------------ +Gesture Example +--------------- -Example showing proximity feature +Example illustrating gesture detection -.. literalinclude:: ../examples/apds9960_proximity_simpletest.py - :caption: examples/apds9960_proximity_simpletest.py +.. literalinclude:: ../examples/apds9960_gesture_simpletest.py + :caption: examples/apds9960_gesture_simpletest.py :linenos: Color Example --------------- -Example showing how to get RGB values +Example illustrating color detection .. literalinclude:: ../examples/apds9960_color_simpletest.py :caption: examples/apds9960_color_simpletest.py diff --git a/examples/apds9960_color_simpletest.py b/examples/apds9960_color_simpletest.py index 1ecad2d..cdb4f20 100644 --- a/examples/apds9960_color_simpletest.py +++ b/examples/apds9960_color_simpletest.py @@ -12,8 +12,6 @@ while True: - # create some variables to store the color data in - # wait for color data to be ready while not apds.color_data_ready: time.sleep(0.005) diff --git a/examples/apds9960_proximity_simpletest.py b/examples/apds9960_proximity_simpletest.py index cc70f59..dac893f 100644 --- a/examples/apds9960_proximity_simpletest.py +++ b/examples/apds9960_proximity_simpletest.py @@ -7,12 +7,18 @@ i2c = board.I2C() int_pin = digitalio.DigitalInOut(board.D5) +int_pin.switch_to_input(pull=digitalio.Pull.UP) apds = APDS9960(i2c) -apds.enable_proximity = True +# set the interrupt threshold to fire when proximity reading goes above 175 apds.proximity_interrupt_threshold = (0, 175) + +# assert the interrupt pin when the proximity interrupt is triggered apds.enable_proximity_interrupt = True +# enable the sensor's proximity engine +apds.enable_proximity = True + while True: # print the proximity reading when the interrupt pin goes low if not int_pin.value: From 72949af6d455a3f4af239b52c505643e4271c1ed Mon Sep 17 00:00:00 2001 From: foamyguy Date: Sat, 8 Jan 2022 11:53:45 -0600 Subject: [PATCH 14/14] typos --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index f366967..6991996 100644 --- a/README.rst +++ b/README.rst @@ -225,9 +225,9 @@ Initiaization Options ---------------------- By default, when the driver is initialized, the APDS-9960 sensor's internal settings are reset and -sensible defaults are applyed to several low-level settings that should work well for most use cases. +sensible defaults are applied to several low-level settings that should work well for most use cases. -If either the "reset" or "set defauls" behaviors (or both) aren't desired, they can be individually +If either the "reset" or "set defaults" behaviors (or both) aren't desired, they can be individually disabled via init kwargs. .. code:: python3