From 3bcf573160caa37145eae1ba6a7930be84b3e4d8 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Fri, 20 Feb 2026 23:12:03 +1100 Subject: [PATCH] fix: Stop LED flash when tag is removed (v1.2.2) - Add per-pad stop event system (_flash_stop_events dictionary) - Add stop_flash(pad) method to cancel active flash animations - flash_pad now checks stop events at multiple points in loop - Tag removal handler calls stop_flash before setting pad to OFF - Fixes issue where LED flash continued indefinitely after figure removed --- lego_dimensions_reader.py | 53 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/lego_dimensions_reader.py b/lego_dimensions_reader.py index cdc3a51..15e98d5 100644 --- a/lego_dimensions_reader.py +++ b/lego_dimensions_reader.py @@ -74,7 +74,7 @@ VENDOR_ID = 0x0e6f PRODUCT_ID = 0x0241 # Module version -__version__ = "1.2.1" +__version__ = "1.2.2" def get_usb_backend(): @@ -183,6 +183,9 @@ class LegoDimensionsReader: # Active tags currently on pads (pad_num -> TagInfo) self._active_tags: Dict[int, TagInfo] = {} + # Flash stop events per pad (for cancelling software flash) + self._flash_stop_events: Dict[int, threading.Event] = {} + # Callbacks self.on_tag_insert: Optional[Callable[[TagInfo], None]] = None self.on_tag_remove: Optional[Callable[[TagInfo], None]] = None @@ -590,6 +593,22 @@ class LegoDimensionsReader: command = [0x55, 0x06, 0xc0, 0x02, pad.value, color[0], color[1], color[2]] self._send_command(command) + def stop_flash(self, pad: Pad): + """ + Stop any active flash animation on a pad. + + Args: + pad: Which pad to stop flashing (Pad.ALL stops all pads) + """ + if pad == Pad.ALL: + pads_to_stop = [Pad.CENTER, Pad.LEFT, Pad.RIGHT] + else: + pads_to_stop = [pad] + + for p in pads_to_stop: + if p.value in self._flash_stop_events: + self._flash_stop_events[p.value].set() + def flash_pad(self, pad: Pad, color: list, on_time: int = 10, off_time: int = 10, count: int = 5): """ @@ -618,18 +637,43 @@ class LegoDimensionsReader: else: pads_to_flash = [pad] + # Stop any existing flash on these pads + for p in pads_to_flash: + if p.value in self._flash_stop_events: + self._flash_stop_events[p.value].set() + + # Create new stop events for this flash + stop_events = {} + for p in pads_to_flash: + stop_events[p.value] = threading.Event() + self._flash_stop_events[p.value] = stop_events[p.value] + def _flash_thread(): for _ in range(actual_count): + # Check if we should stop (device disconnected or stop requested) if not self._running and not self.dev: break + # Check if stop was requested for any of our pads + if any(stop_events[p.value].is_set() for p in pads_to_flash): + break # ON for p in pads_to_flash: - self.set_pad_color(p, color) + if not stop_events[p.value].is_set(): + self.set_pad_color(p, color) time.sleep(on_secs) + # Check again before OFF + if any(stop_events[p.value].is_set() for p in pads_to_flash): + break # OFF for p in pads_to_flash: - self.set_pad_color(p, COLORS['OFF']) + if not stop_events[p.value].is_set(): + self.set_pad_color(p, COLORS['OFF']) time.sleep(off_secs) + + # Cleanup stop events when done + for p in pads_to_flash: + if p.value in self._flash_stop_events and self._flash_stop_events[p.value] == stop_events[p.value]: + del self._flash_stop_events[p.value] # Run flash in background thread so it doesn't block flash_thread = threading.Thread(target=_flash_thread, daemon=True) @@ -720,7 +764,8 @@ class LegoDimensionsReader: if pad_num in self._active_tags: del self._active_tags[pad_num] - # Turn off pad LED + # Stop any active flash and turn off pad LED + self.stop_flash(pad) self.set_pad_color(pad, COLORS['OFF']) if self.on_tag_remove: