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
This commit is contained in:
@@ -74,7 +74,7 @@ VENDOR_ID = 0x0e6f
|
|||||||
PRODUCT_ID = 0x0241
|
PRODUCT_ID = 0x0241
|
||||||
|
|
||||||
# Module version
|
# Module version
|
||||||
__version__ = "1.2.1"
|
__version__ = "1.2.2"
|
||||||
|
|
||||||
|
|
||||||
def get_usb_backend():
|
def get_usb_backend():
|
||||||
@@ -183,6 +183,9 @@ class LegoDimensionsReader:
|
|||||||
# Active tags currently on pads (pad_num -> TagInfo)
|
# Active tags currently on pads (pad_num -> TagInfo)
|
||||||
self._active_tags: Dict[int, 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
|
# Callbacks
|
||||||
self.on_tag_insert: Optional[Callable[[TagInfo], None]] = None
|
self.on_tag_insert: Optional[Callable[[TagInfo], None]] = None
|
||||||
self.on_tag_remove: 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]]
|
command = [0x55, 0x06, 0xc0, 0x02, pad.value, color[0], color[1], color[2]]
|
||||||
self._send_command(command)
|
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,
|
def flash_pad(self, pad: Pad, color: list, on_time: int = 10,
|
||||||
off_time: int = 10, count: int = 5):
|
off_time: int = 10, count: int = 5):
|
||||||
"""
|
"""
|
||||||
@@ -618,19 +637,44 @@ class LegoDimensionsReader:
|
|||||||
else:
|
else:
|
||||||
pads_to_flash = [pad]
|
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():
|
def _flash_thread():
|
||||||
for _ in range(actual_count):
|
for _ in range(actual_count):
|
||||||
|
# Check if we should stop (device disconnected or stop requested)
|
||||||
if not self._running and not self.dev:
|
if not self._running and not self.dev:
|
||||||
break
|
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
|
# ON
|
||||||
for p in pads_to_flash:
|
for p in pads_to_flash:
|
||||||
|
if not stop_events[p.value].is_set():
|
||||||
self.set_pad_color(p, color)
|
self.set_pad_color(p, color)
|
||||||
time.sleep(on_secs)
|
time.sleep(on_secs)
|
||||||
|
# Check again before OFF
|
||||||
|
if any(stop_events[p.value].is_set() for p in pads_to_flash):
|
||||||
|
break
|
||||||
# OFF
|
# OFF
|
||||||
for p in pads_to_flash:
|
for p in pads_to_flash:
|
||||||
|
if not stop_events[p.value].is_set():
|
||||||
self.set_pad_color(p, COLORS['OFF'])
|
self.set_pad_color(p, COLORS['OFF'])
|
||||||
time.sleep(off_secs)
|
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
|
# Run flash in background thread so it doesn't block
|
||||||
flash_thread = threading.Thread(target=_flash_thread, daemon=True)
|
flash_thread = threading.Thread(target=_flash_thread, daemon=True)
|
||||||
flash_thread.start()
|
flash_thread.start()
|
||||||
@@ -720,7 +764,8 @@ class LegoDimensionsReader:
|
|||||||
if pad_num in self._active_tags:
|
if pad_num in self._active_tags:
|
||||||
del self._active_tags[pad_num]
|
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'])
|
self.set_pad_color(pad, COLORS['OFF'])
|
||||||
|
|
||||||
if self.on_tag_remove:
|
if self.on_tag_remove:
|
||||||
|
|||||||
Reference in New Issue
Block a user