Simple test

Ensure your device works with this simple test.

examples/apds9999_simpletest.py
 1# SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries
 2#
 3# SPDX-License-Identifier: MIT
 4import time
 5
 6import board
 7
 8from adafruit_apds9999 import APDS9999
 9
10"""
11Demonstrate the setup and basic RGB/IR and proximity
12 sensing functionality of the APDS9999
13"""
14
15apds_sensor = APDS9999(board.I2C())
16
17apds_sensor.light_sensor_enabled = True
18apds_sensor.proximity_sensor_enabled = True
19apds_sensor.rgb_mode = True
20
21while True:
22    time.sleep(1)
23    r, g, b, ir = apds_sensor.rgb_ir
24
25    print(
26        f"r: {r}, g: {g}, b: {b}, ir: {ir} "
27        f"lux: {apds_sensor.calculate_lux(g)} proximity: {apds_sensor.proximity}"
28    )

Manual interrupt test

See how the proximity and light interrupt features work

examples/apds9999_manual_interrupt_test.py
 1# SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries
 2#
 3# SPDX-License-Identifier: MIT
 4"""
 5APDS9999 Threshold Interrupt Demo
 6
 7Demonstrates proximity and light sensor threshold interrupts.
 8
 9Hardware: Connect the sensor INT pin to board.D8.
10
11- Wave a hand near the sensor to trigger a proximity interrupt.
12- Cover/uncover or shine a light on the sensor to trigger a light interrupt.
13"""
14
15import time
16
17import board
18import busio
19from digitalio import DigitalInOut, Direction, Pull
20
21from adafruit_apds9999 import APDS9999, LightInterruptChannel
22
23# The APDS-9999 INT pin is active-low (open-drain), so use a pull-up.
24interrupt_pin = DigitalInOut(board.D8)
25interrupt_pin.direction = Direction.INPUT
26interrupt_pin.pull = Pull.UP
27
28# --- Sensor setup ---
29i2c = busio.I2C(board.SCL, board.SDA)
30sensor = APDS9999(i2c)
31
32# Enable both sensors and select RGB (vs ALS) mode
33sensor.proximity_sensor_enabled = True
34sensor.light_sensor_enabled = True
35sensor.rgb_mode = True
36
37# --- Proximity Threshold Setup ---
38sensor.proximity_threshold_low = 50  # Interrupt when prox < 50  (object far)
39sensor.proximity_threshold_high = 200  # Interrupt when prox > 200 (object close)
40sensor.proximity_persistence = 2  # Require 3 consecutive out-of-range readings
41sensor.proximity_interrupt_enabled = True
42
43print("Proximity thresholds: low=50, high=200, persistence=3")
44
45# --- Light Threshold Setup ---
46sensor.light_threshold_low = 1000  # Interrupt when light < 1000
47sensor.light_threshold_high = 50000  # Interrupt when light > 50000
48sensor.light_interrupt_channel = LightInterruptChannel.GREEN  # Compare green channel
49sensor.light_persistence = 2  # Require 3 consecutive out-of-range readings
50sensor.light_interrupt_enabled = True
51
52print("Light thresholds: low=1000, high=50000 (green channel)")
53print()
54print("Waiting for interrupts...")
55print("- Wave hand near sensor for proximity interrupt")
56print("- Cover/uncover sensor for light interrupt")
57print()
58
59last_print = time.monotonic()
60
61while True:
62    # The INT pin is active-low: it goes False when an interrupt has fired.
63    if not interrupt_pin.value:
64        # Read main_status once — this clears all interrupt flags on the device.
65        # Returns: (prox_data_ready, prox_interrupt, prox_logic,
66        #           light_data_ready, light_interrupt, power_on_reset)
67        status = sensor.main_status
68
69        if status[1]:  # proximity_interrupt
70            prox = sensor.proximity
71            print(f">>> PROXIMITY INTERRUPT! Value: {prox}")
72
73        if status[4]:  # light_interrupt
74            r, g, b, ir = sensor.rgb_ir
75            print(f">>> LIGHT INTERRUPT! Green: {g}")
76
77    # Periodic sensor readings every 500 ms
78    now = time.monotonic()
79    if now - last_print >= 0.5:
80        last_print = now
81        prox = sensor.proximity
82        r, g, b, ir = sensor.rgb_ir
83        print(f"Prox: {prox}\tGreen: {g}")

Automatic interrupt test

Automated test that uses NeoPixels to verify light threshold interrupt behavior

examples/apds9999_automatic_interrupt_threshold_test.py
  1# SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries
  2#
  3# SPDX-License-Identifier: MIT
  4"""
  5APDS-9999 Light Threshold Interrupt Automated Test
  6===================================================
  7
  8Uses on-board NeoPixels to ramp brightness from off to full white, verifying
  9that the APDS-9999 light threshold interrupt pin behaves correctly across
 10three regions:
 11
 12  1. BELOW low threshold  – INT pin should be asserted (active-low → False)
 13  2. BETWEEN thresholds   – INT pin should be de-asserted (True)
 14  3. ABOVE high threshold – INT pin should be asserted (active-low → False)
 15
 16Hardware
 17--------
 18* APDS-9999 INT pin → board.D8  (active-low, open-drain; pull-up enabled)
 19* APDS-9999 wired to board I2C (SDA/SCL)
 20* On-board NeoPixels at board.NEOPIXEL (5 pixels assumed)
 21
 22The sensor should be placed face-down on (or very close to) the NeoPixel
 23strip so the NeoPixel is the dominant light source.
 24"""
 25
 26import time
 27
 28import board
 29import busio
 30import neopixel
 31from digitalio import DigitalInOut, Direction, Pull
 32
 33from adafruit_apds9999 import (
 34    APDS9999,
 35    LightGain,
 36    LightInterruptChannel,
 37    LightMeasurementRate,
 38    LightResolution,
 39)
 40
 41# ---------------------------------------------------------------------------
 42# Configuration
 43# ---------------------------------------------------------------------------
 44
 45# Number of NeoPixels on the board / strip.
 46NUM_PIXELS = 5
 47
 48# Settle time (seconds) after changing NeoPixel brightness before reading.
 49SETTLE_TIME = 0.6
 50
 51# Number of sensor samples to average per reading.
 52NUM_SAMPLES = 5
 53
 54# Light interrupt thresholds (green channel counts).
 55# These are chosen to sit comfortably within the brightness ramp so that we
 56# can clearly demonstrate all three regions.
 57LIGHT_THRESHOLD_LOW = 30_000  # Below this → interrupt fires (too dark)
 58LIGHT_THRESHOLD_HIGH = 100_000  # Above this → interrupt fires (too bright)
 59
 60# Persistence: require this many consecutive out-of-range readings before
 61# asserting the interrupt.  Keep at 1 so every out-of-range reading fires.
 62LIGHT_PERSISTENCE = 1
 63
 64# Brightness steps used for the ramp.  Each entry is a 0.0–1.0 value.
 65# We want points clearly below, inside, and above the thresholds.
 66BRIGHTNESS_STEPS = [0.0, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.85, 1.0]
 67
 68# ---------------------------------------------------------------------------
 69# Helpers
 70# ---------------------------------------------------------------------------
 71
 72
 73def average_green(sensor, n=NUM_SAMPLES, delay=0.15):
 74    """Return the average green-channel reading over *n* samples."""
 75    total = 0
 76    for _ in range(n):
 77        _, g, _, _ = sensor.rgb_ir
 78        total += g
 79        time.sleep(delay)
 80    return total // n
 81
 82
 83def read_interrupt(pin):
 84    """Return True if the INT pin is currently asserted (active-low → pin False)."""
 85    return not pin.value
 86
 87
 88def print_result(label, passed, detail=""):
 89    status = "PASS" if passed else "FAIL"
 90    msg = f"  [{status}] {label}"
 91    if detail:
 92        msg += f" – {detail}"
 93    print(msg)
 94    return passed
 95
 96
 97def clear_interrupt(sensor):
 98    """Read main_status to clear any pending interrupt flags on the device."""
 99    _ = sensor.main_status
100
101
102# ---------------------------------------------------------------------------
103# Hardware setup
104# ---------------------------------------------------------------------------
105
106print("=" * 56)
107print("APDS9999 Light Threshold Interrupt Test")
108print("=" * 56)
109
110# Interrupt pin: active-low, open-drain → use internal pull-up.
111interrupt_pin = DigitalInOut(board.D8)
112interrupt_pin.direction = Direction.INPUT
113interrupt_pin.pull = Pull.UP
114
115# NeoPixels – start off.
116pixels = neopixel.NeoPixel(board.NEOPIXEL, NUM_PIXELS, brightness=0.0, auto_write=True)
117pixels.fill((255, 255, 255))  # white; brightness controls intensity
118
119# I2C + sensor.
120i2c = board.I2C()
121sensor = APDS9999(i2c)
122
123# Light sensor only – no proximity.
124sensor.proximity_sensor_enabled = False
125sensor.light_sensor_enabled = True
126sensor.rgb_mode = True
127
128# Use 18-bit resolution
129sensor.light_resolution = LightResolution.RES_18BIT
130sensor.light_measurement_rate = LightMeasurementRate.RATE_200MS
131sensor.light_gain = LightGain.GAIN_9X
132
133# Configure light interrupt thresholds.
134sensor.light_threshold_low = LIGHT_THRESHOLD_LOW
135sensor.light_threshold_high = LIGHT_THRESHOLD_HIGH
136sensor.light_interrupt_channel = LightInterruptChannel.GREEN
137sensor.light_persistence = LIGHT_PERSISTENCE
138sensor.light_interrupt_enabled = True
139
140# Clear any stale interrupt state before starting.
141clear_interrupt(sensor)
142time.sleep(0.5)
143
144print(f"\nLight interrupt channel : GREEN")
145print(f"Low  threshold          : {LIGHT_THRESHOLD_LOW:,} counts")
146print(f"High threshold          : {LIGHT_THRESHOLD_HIGH:,} counts")
147print(f"Persistence             : {LIGHT_PERSISTENCE} reading(s)")
148print()
149
150# ---------------------------------------------------------------------------
151# Brightness ramp + interrupt verification
152# ---------------------------------------------------------------------------
153
154all_passed = True
155
156# Collect (brightness, green_counts, int_asserted) for every step.
157ramp_data = []
158
159print("Running brightness ramp...")
160print(f"  {'Brightness':>10}  {'Green':>8}  {'INT Pin':>8}  {'Region':>18}  {'Check':>6}")
161print("  " + "-" * 58)
162
163for brightness in BRIGHTNESS_STEPS:
164    pixels.brightness = brightness
165    time.sleep(SETTLE_TIME)
166
167    # Clear any previous interrupt so we get a fresh reading for this level.
168    clear_interrupt(sensor)
169    time.sleep(SETTLE_TIME)
170
171    green = average_green(sensor)
172    int_asserted = read_interrupt(interrupt_pin)
173
174    if green < LIGHT_THRESHOLD_LOW:
175        region = "BELOW LOW"
176    elif green > LIGHT_THRESHOLD_HIGH:
177        region = "ABOVE HIGH"
178    else:
179        region = "IN RANGE"
180
181    ramp_data.append((brightness, green, int_asserted, region))
182
183    print(f"  {brightness:>10.2f}  {green:>8,}  {str(int_asserted):>8}  {region:>18}")
184
185# ---------------------------------------------------------------------------
186# Automated checks
187# ---------------------------------------------------------------------------
188
189print()
190print("Automated checks")
191print("-" * 56)
192
193# Check 1 – At least one step landed below, inside, and above the threshold range.
194below_steps = [(b, g, ia) for b, g, ia, r in ramp_data if r == "BELOW LOW"]
195in_range_steps = [(b, g, ia) for b, g, ia, r in ramp_data if r == "IN RANGE"]
196above_steps = [(b, g, ia) for b, g, ia, r in ramp_data if r == "ABOVE HIGH"]
197
198coverage_below = len(below_steps) > 0
199coverage_in = len(in_range_steps) > 0
200coverage_above = len(above_steps) > 0
201
202all_passed = (
203    print_result(
204        "Ramp covers BELOW LOW region",
205        coverage_below,
206        f"{len(below_steps)} step(s) below {LIGHT_THRESHOLD_LOW:,}",
207    )
208    and all_passed
209)
210
211all_passed = (
212    print_result(
213        "Ramp covers IN RANGE region",
214        coverage_in,
215        f"{len(in_range_steps)} step(s) between thresholds",
216    )
217    and all_passed
218)
219
220all_passed = (
221    print_result(
222        "Ramp covers ABOVE HIGH region",
223        coverage_above,
224        f"{len(above_steps)} step(s) above {LIGHT_THRESHOLD_HIGH:,}",
225    )
226    and all_passed
227)
228
229# Check 2 – INT asserted for every BELOW LOW step.
230if below_steps:
231    below_int_ok = all(ia for _, _, ia in below_steps)
232    all_passed = (
233        print_result(
234            "INT asserted for all BELOW LOW steps",
235            below_int_ok,
236            f"{sum(ia for _, _, ia in below_steps)}/{len(below_steps)} asserted",
237        )
238        and all_passed
239    )
240else:
241    print("  [SKIP] INT check for BELOW LOW – no steps in that region")
242
243# Check 3 – INT NOT asserted for every IN RANGE step.
244if in_range_steps:
245    in_range_int_ok = all(not ia for _, _, ia in in_range_steps)
246    all_passed = (
247        print_result(
248            "INT de-asserted for all IN RANGE steps",
249            in_range_int_ok,
250            f"{sum(not ia for _, _, ia in in_range_steps)}/{len(in_range_steps)} de-asserted",
251        )
252        and all_passed
253    )
254else:
255    print("  [SKIP] INT check for IN RANGE – no steps in that region")
256
257# Check 4 – INT asserted for every ABOVE HIGH step.
258if above_steps:
259    above_int_ok = all(ia for _, _, ia in above_steps)
260    all_passed = (
261        print_result(
262            "INT asserted for all ABOVE HIGH steps",
263            above_int_ok,
264            f"{sum(ia for _, _, ia in above_steps)}/{len(above_steps)} asserted",
265        )
266        and all_passed
267    )
268else:
269    print("  [SKIP] INT check for ABOVE HIGH – no steps in that region")
270
271# Check 5 – Green counts increase monotonically with brightness.
272greens = [g for _, g, _, _ in ramp_data]
273monotonic = all(greens[i] <= greens[i + 1] for i in range(len(greens) - 1))
274all_passed = (
275    print_result(
276        "Green counts increase monotonically with brightness",
277        monotonic,
278        f"counts: {greens}",
279    )
280    and all_passed
281)
282
283# ---------------------------------------------------------------------------
284# Teardown
285# ---------------------------------------------------------------------------
286
287pixels.fill((0, 0, 0))
288pixels.brightness = 0.0
289sensor.light_interrupt_enabled = False
290clear_interrupt(sensor)
291
292print()
293print("=" * 56)
294if all_passed:
295    print("Overall result: ALL TESTS PASSED")
296else:
297    print("Overall result: ONE OR MORE TESTS FAILED")
298print("=" * 56)

Automatic RGB test

Automated test that uses NeoPixels to verify RGB light sensing functionality

examples/apds9999_automatic_rgb_test.py
  1# SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries
  2#
  3# SPDX-License-Identifier: MIT
  4"""
  5APDS9999 NeoPixel Color & Light Sensing Verification Test
  6==========================================================
  7
  8This script verifies that the APDS9999 driver's color and light sensing
  9capabilities are working correctly by using on-board NeoPixels as a
 10controlled light source and then checking that the sensor readings match
 11the expected dominant color channel.
 12
 13Test sequence
 14-------------
 151. **RED**   – NeoPixel set to pure red.   Red channel must dominate.
 162. **GREEN** – NeoPixel set to pure green. Green channel must dominate.
 173. **BLUE**  – NeoPixel set to pure blue.  Blue channel must dominate.
 184. **WHITE** – NeoPixel set to white.      All channels (R, G, B) must be high.
 195. **DARK**  – NeoPixel off.               All channels must drop significantly
 20               compared to the white reading.
 21
 22Hardware
 23--------
 24* Any CircuitPython board with on-board NeoPixels and I2C port.
 25* APDS9999 sensor wired to the board's I2C bus (SDA/SCL).
 26
 27The sensor should be placed **very close** (ideally face-down on top of) the
 28NeoPixel so that the emitted light is the dominant light source being sensed.
 29Ambient light should be minimised where possible.
 30"""
 31
 32import time
 33
 34import board
 35import neopixel
 36
 37from adafruit_apds9999 import APDS9999, LightGain, LightMeasurementRate, LightResolution
 38
 39# ---------------------------------------------------------------------------
 40# Configuration
 41# ---------------------------------------------------------------------------
 42
 43# NeoPixel brightness (0.0–1.0).  Keep this moderate so the sensor is not
 44# saturated but still well above the noise floor.
 45NEOPIXEL_BRIGHTNESS = 0.3
 46
 47# How long (seconds) to let the sensor settle after changing the NeoPixel
 48# colour before taking a reading.
 49SETTLE_TIME = 0.5
 50
 51# How many readings to average for each colour test.
 52NUM_SAMPLES = 5
 53
 54# Dominance factor: the winning channel must be at least this many times
 55# larger than each of the other colour channels to pass.
 56DOMINANCE_FACTOR = 1.5
 57
 58# For the WHITE test, all three colour channels must be at least this fraction
 59# of the maximum channel value.
 60WHITE_BALANCE_FRACTION = 0.4
 61
 62# For the DARK test, all channels must be at most this fraction of the
 63# corresponding WHITE reading.
 64DARK_FRACTION = 0.25
 65
 66# ---------------------------------------------------------------------------
 67# Helpers
 68# ---------------------------------------------------------------------------
 69
 70
 71def average_readings(sensor, n=NUM_SAMPLES, delay=0.15):
 72    """Return averaged (r, g, b, ir) from *n* consecutive sensor reads."""
 73    totals = [0, 0, 0, 0]
 74    for _ in range(n):
 75        r, g, b, ir = sensor.rgb_ir
 76        totals[0] += r
 77        totals[1] += g
 78        totals[2] += b
 79        totals[3] += ir
 80        time.sleep(delay)
 81    return tuple(v // n for v in totals)
 82
 83
 84def print_result(name, passed, detail=""):
 85    status = "PASS" if passed else "FAIL"
 86    msg = f"  [{status}] {name}"
 87    if detail:
 88        msg += f" – {detail}"
 89    print(msg)
 90    return passed
 91
 92
 93# ---------------------------------------------------------------------------
 94# Setup
 95# ---------------------------------------------------------------------------
 96
 97print("=" * 50)
 98print("APDS9999 NeoPixel Verification Test")
 99print("=" * 50)
100
101# Initialise NeoPixel
102pixels = neopixel.NeoPixel(board.NEOPIXEL, 5, brightness=NEOPIXEL_BRIGHTNESS, auto_write=True)
103pixels.fill((0, 0, 0))  # Start with NeoPixel off
104
105# Initialise the APDS9999 sensor.
106i2c = board.I2C()
107sensor = APDS9999(i2c)
108
109# Enable the light sensor in RGB mode.
110sensor.light_sensor_enabled = True
111sensor.rgb_mode = True
112
113# Use 18-bit resolution (100 ms conversion) with a 200 ms measurement rate.
114sensor.light_resolution = LightResolution.RES_18BIT
115sensor.light_measurement_rate = LightMeasurementRate.RATE_200MS
116
117# 9x gain
118sensor.light_gain = LightGain.GAIN_9X
119
120# Allow the sensor to perform its first measurement after being enabled.
121time.sleep(0.5)
122
123print(f"\nSensor initialised – PART_ID check passed")
124print(
125    f"Settings: resolution=18-bit, rate=200ms, gain=9x, neopixel brightness={NEOPIXEL_BRIGHTNESS}\n"
126)
127
128# ---------------------------------------------------------------------------
129# Test cases
130# ---------------------------------------------------------------------------
131
132all_passed = True
133results = {}
134
135# ---- 1. RED ----------------------------------------------------------------
136print("Test 1: RED")
137pixels.fill((255, 0, 0))
138time.sleep(SETTLE_TIME)
139r, g, b, ir = average_readings(sensor)
140print(f"  Readings  r={r:>7}  g={g:>7}  b={b:>7}  ir={ir:>7}")
141
142red_dominates = (r > g * DOMINANCE_FACTOR) and (r > b * DOMINANCE_FACTOR) and r > 0
143passed = print_result(
144    "Red channel dominates",
145    red_dominates,
146    f"r={r} vs g={g}, b={b} (factor {DOMINANCE_FACTOR}x)",
147)
148all_passed = all_passed and passed
149results["red"] = (r, g, b, ir)
150
151# ---- 2. GREEN --------------------------------------------------------------
152print("\nTest 2: GREEN")
153pixels.fill((0, 255, 0))
154time.sleep(SETTLE_TIME)
155r, g, b, ir = average_readings(sensor)
156print(f"  Readings  r={r:>7}  g={g:>7}  b={b:>7}  ir={ir:>7}")
157
158green_dominates = (g > r * DOMINANCE_FACTOR) and (g > b * DOMINANCE_FACTOR) and g > 0
159passed = print_result(
160    "Green channel dominates",
161    green_dominates,
162    f"g={g} vs r={r}, b={b} (factor {DOMINANCE_FACTOR}x)",
163)
164all_passed = all_passed and passed
165results["green"] = (r, g, b, ir)
166
167# ---- 3. BLUE ---------------------------------------------------------------
168print("\nTest 3: BLUE")
169pixels.fill((0, 0, 255))
170time.sleep(SETTLE_TIME)
171r, g, b, ir = average_readings(sensor)
172print(f"  Readings  r={r:>7}  g={g:>7}  b={b:>7}  ir={ir:>7}")
173
174blue_dominates = (b > r * DOMINANCE_FACTOR) and (b > g * DOMINANCE_FACTOR) and b > 0
175passed = print_result(
176    "Blue channel dominates",
177    blue_dominates,
178    f"b={b} vs r={r}, g={g} (factor {DOMINANCE_FACTOR}x)",
179)
180all_passed = all_passed and passed
181results["blue"] = (r, g, b, ir)
182
183# ---- 4. WHITE --------------------------------------------------------------
184print("\nTest 4: WHITE")
185pixels.fill((255, 255, 255))
186time.sleep(SETTLE_TIME)
187r, g, b, ir = average_readings(sensor)
188print(f"  Readings  r={r:>7}  g={g:>7}  b={b:>7}  ir={ir:>7}")
189
190white_max = max(r, g, b)
191threshold = int(white_max * WHITE_BALANCE_FRACTION)
192white_balanced = white_max > 0 and r >= threshold and g >= threshold and b >= threshold
193passed = print_result(
194    "All colour channels active (white balance)",
195    white_balanced,
196    f"r={r}, g={g}, b={b}, "
197    f"min threshold={threshold} ({int(WHITE_BALANCE_FRACTION * 100)}% of max={white_max})",
198)
199all_passed = all_passed and passed
200results["white"] = (r, g, b, ir)
201
202# Also verify lux is a positive, plausible number.
203lux = sensor.calculate_lux(g)
204lux_ok = lux > 0
205passed2 = print_result(
206    "Lux value is positive",
207    lux_ok,
208    f"lux={lux:.2f}",
209)
210all_passed = all_passed and passed2
211
212# ---- 5. DARK ---------------------------------------------------------------
213print("\nTest 5: DARK (NeoPixel OFF)")
214pixels.fill((0, 0, 0))
215time.sleep(SETTLE_TIME)
216r_dark, g_dark, b_dark, ir_dark = average_readings(sensor)
217print(f"  Readings  r={r_dark:>7}  g={g_dark:>7}  b={b_dark:>7}  ir={ir_dark:>7}")
218
219wr, wg, wb, _ = results["white"]
220dark_r_ok = wr == 0 or r_dark <= wr * DARK_FRACTION
221dark_g_ok = wg == 0 or g_dark <= wg * DARK_FRACTION
222dark_b_ok = wb == 0 or b_dark <= wb * DARK_FRACTION
223dark_ok = dark_r_ok and dark_g_ok and dark_b_ok
224passed = print_result(
225    "All channels drop in darkness",
226    dark_ok,
227    f"dark r={r_dark} (white={wr}), g={g_dark} (white={wg}), b={b_dark} (white={wb})",
228)
229all_passed = all_passed and passed
230results["dark"] = (r_dark, g_dark, b_dark, ir_dark)
231
232# ---- 6. main_status sanity check -------------------------------------------
233print("\nTest 6: main_status data-ready flags")
234pixels.fill((255, 255, 255))
235time.sleep(0.4)  # Let sensor complete one measurement cycle
236ps_ready, ps_int, ps_logic, ls_ready, ls_int, por = sensor.main_status
237# After a measurement cycle, light_data_ready should have been True at some
238# point.  We re-enable sensors and wait to catch it reliably.
239sensor.light_sensor_enabled = False
240time.sleep(0.05)
241sensor.light_sensor_enabled = True
242sensor.rgb_mode = True
243time.sleep(0.4)
244_, _, _, ls_ready2, _, _ = sensor.main_status
245passed = print_result(
246    "Light data-ready flag observed",
247    ls_ready2,
248    f"light_data_ready={ls_ready2}",
249)
250all_passed = all_passed and passed
251
252# ---------------------------------------------------------------------------
253# Summary
254# ---------------------------------------------------------------------------
255pixels.fill((0, 0, 0))  # Turn off NeoPixel at end of test.
256
257print("\n" + "=" * 50)
258if all_passed:
259    print("Overall result: ALL TESTS PASSED")
260else:
261    print("Overall result: ONE OR MORE TESTS FAILED")
262print("=" * 50)