From 1a5629a50f58503caaf280b1e7aee2ad7c193e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Sat, 14 Mar 2026 22:19:46 +0100 Subject: [PATCH 1/3] apds9960: Auto-enable sensor when reading without active mode. --- lib/apds9960/apds9960/device.py | 20 ++++++++++++++++ tests/scenarios/apds9960.yaml | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/lib/apds9960/apds9960/device.py b/lib/apds9960/apds9960/device.py index e83efe82..e0dc5618 100644 --- a/lib/apds9960/apds9960/device.py +++ b/lib/apds9960/apds9960/device.py @@ -230,8 +230,24 @@ def isLightAvailable(self): return val == APDS9960_BIT_AVALID + def _ensure_light_enabled(self): + enable = self.getMode() + if not (enable & (1 << APDS9960_MODE_AMBIENT_LIGHT)) or not (enable & 1): + self.enableLightSensor(interrupts=False) + for _ in range(50): + if self.isLightAvailable(): + return + sleep_ms(10) + + def _ensure_proximity_enabled(self): + enable = self.getMode() + if not (enable & (1 << APDS9960_MODE_PROXIMITY)) or not (enable & 1): + self.enableProximitySensor(interrupts=False) + sleep_ms(50) + # reads the ambient (clear) light level as a 16-bit value def readAmbientLight(self): + self._ensure_light_enabled() # read value from clear channel, low byte register low = self._read_reg(APDS9960_REG_CDATAL) @@ -242,6 +258,7 @@ def readAmbientLight(self): # reads the red light level as a 16-bit value def readRedLight(self): + self._ensure_light_enabled() # read value from red channel, low byte register low = self._read_reg(APDS9960_REG_RDATAL) @@ -252,6 +269,7 @@ def readRedLight(self): # reads the green light level as a 16-bit value def readGreenLight(self): + self._ensure_light_enabled() # read value from green channel, low byte register low = self._read_reg(APDS9960_REG_GDATAL) @@ -262,6 +280,7 @@ def readGreenLight(self): # reads the blue light level as a 16-bit value def readBlueLight(self): + self._ensure_light_enabled() # read value from blue channel, low byte register low = self._read_reg(APDS9960_REG_BDATAL) @@ -276,6 +295,7 @@ def readBlueLight(self): # reads the proximity level as an 8-bit value def readProximity(self): + self._ensure_proximity_enabled() return self._read_reg(APDS9960_REG_PDATA) # ******************************************************************************* diff --git a/tests/scenarios/apds9960.yaml b/tests/scenarios/apds9960.yaml index b19db006..e30195cc 100644 --- a/tests/scenarios/apds9960.yaml +++ b/tests/scenarios/apds9960.yaml @@ -79,6 +79,48 @@ tests: expect: 128 mode: [mock] + # ----- Auto-enable ----- + + - name: "Ambient light readable without explicit enable" + action: script + script: | + dev.disableLightSensor() + val = dev.readAmbientLight() + result = isinstance(val, int) + expect_true: true + mode: [mock] + + - name: "Proximity readable without explicit enable" + action: script + script: | + dev.disableProximitySensor() + val = dev.readProximity() + result = isinstance(val, int) + expect_true: true + mode: [mock] + + - name: "Red light readable after power off" + action: script + script: | + dev.disablePower() + val = dev.readRedLight() + result = isinstance(val, int) + expect_true: true + mode: [mock] + + - name: "No re-enable when already active" + action: script + script: | + dev.enableLightSensor(False) + i2c.clear_write_log() + dev.readAmbientLight() + log = i2c.get_write_log() + result = len(log) == 0 + expect_true: true + mode: [mock] + + # ----- Hardware ----- + - name: "Ambient light in plausible range" action: call method: readAmbientLight From 4a30d305edc39ec9de9449de10c64c05a289a7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Sat, 14 Mar 2026 22:38:32 +0100 Subject: [PATCH 2/3] apds9960: Address Copilot review on auto-enable PR. --- lib/apds9960/apds9960/device.py | 13 +++++++++--- tests/scenarios/apds9960.yaml | 36 ++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/apds9960/apds9960/device.py b/lib/apds9960/apds9960/device.py index e0dc5618..0227bf0f 100644 --- a/lib/apds9960/apds9960/device.py +++ b/lib/apds9960/apds9960/device.py @@ -230,9 +230,13 @@ def isLightAvailable(self): return val == APDS9960_BIT_AVALID + def isProximityAvailable(self): + val = self._read_reg(APDS9960_REG_STATUS) + return (val & APDS9960_BIT_PVALID) == APDS9960_BIT_PVALID + def _ensure_light_enabled(self): enable = self.getMode() - if not (enable & (1 << APDS9960_MODE_AMBIENT_LIGHT)) or not (enable & 1): + if not (enable & (1 << APDS9960_MODE_AMBIENT_LIGHT)) or not (enable & APDS9960_BIT_PON): self.enableLightSensor(interrupts=False) for _ in range(50): if self.isLightAvailable(): @@ -241,9 +245,12 @@ def _ensure_light_enabled(self): def _ensure_proximity_enabled(self): enable = self.getMode() - if not (enable & (1 << APDS9960_MODE_PROXIMITY)) or not (enable & 1): + if not (enable & (1 << APDS9960_MODE_PROXIMITY)) or not (enable & APDS9960_BIT_PON): self.enableProximitySensor(interrupts=False) - sleep_ms(50) + for _ in range(50): + if self.isProximityAvailable(): + return + sleep_ms(10) # reads the ambient (clear) light level as a 16-bit value def readAmbientLight(self): diff --git a/tests/scenarios/apds9960.yaml b/tests/scenarios/apds9960.yaml index e30195cc..b22bc617 100644 --- a/tests/scenarios/apds9960.yaml +++ b/tests/scenarios/apds9960.yaml @@ -81,30 +81,48 @@ tests: # ----- Auto-enable ----- - - name: "Ambient light readable without explicit enable" + - name: "Auto-enable writes ENABLE register for light" action: script script: | dev.disableLightSensor() - val = dev.readAmbientLight() - result = isinstance(val, int) + i2c.clear_write_log() + dev.readAmbientLight() + log = i2c.get_write_log() + wrote_enable = any(reg == 0x80 for reg, data in log) + enable_val = dev.getMode() + has_pon = bool(enable_val & 0x01) + has_aen = bool(enable_val & 0x02) + result = wrote_enable and has_pon and has_aen expect_true: true mode: [mock] - - name: "Proximity readable without explicit enable" + - name: "Auto-enable writes ENABLE register for proximity" action: script script: | dev.disableProximitySensor() - val = dev.readProximity() - result = isinstance(val, int) + i2c.clear_write_log() + dev.readProximity() + log = i2c.get_write_log() + wrote_enable = any(reg == 0x80 for reg, data in log) + enable_val = dev.getMode() + has_pon = bool(enable_val & 0x01) + has_pen = bool(enable_val & 0x04) + result = wrote_enable and has_pon and has_pen expect_true: true mode: [mock] - - name: "Red light readable after power off" + - name: "Auto-enable restores power for light read" action: script script: | dev.disablePower() - val = dev.readRedLight() - result = isinstance(val, int) + i2c.clear_write_log() + dev.readRedLight() + log = i2c.get_write_log() + wrote_enable = any(reg == 0x80 for reg, data in log) + enable_val = dev.getMode() + has_pon = bool(enable_val & 0x01) + has_aen = bool(enable_val & 0x02) + result = wrote_enable and has_pon and has_aen expect_true: true mode: [mock] From b3576c8d1036350a31583f40fc1ee8feab51b6ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Sat, 14 Mar 2026 22:44:44 +0100 Subject: [PATCH 3/3] apds9960: Add timeout raise in auto-enable helpers for consistency. --- lib/apds9960/apds9960/device.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/apds9960/apds9960/device.py b/lib/apds9960/apds9960/device.py index 0227bf0f..6ace6bd8 100644 --- a/lib/apds9960/apds9960/device.py +++ b/lib/apds9960/apds9960/device.py @@ -242,6 +242,7 @@ def _ensure_light_enabled(self): if self.isLightAvailable(): return sleep_ms(10) + raise OSError("APDS9960 light data ready timeout") def _ensure_proximity_enabled(self): enable = self.getMode() @@ -251,6 +252,7 @@ def _ensure_proximity_enabled(self): if self.isProximityAvailable(): return sleep_ms(10) + raise OSError("APDS9960 proximity data ready timeout") # reads the ambient (clear) light level as a 16-bit value def readAmbientLight(self):