Illumination Things for sangaboard and simulator

This commit is contained in:
jaknapper 2026-02-10 18:42:59 +00:00
parent 8435f6ae98
commit 5682b4d28d
3 changed files with 65 additions and 27 deletions

View file

@ -140,6 +140,7 @@ class SimulatedCamera(BaseCamera):
self._capture_thread: Optional[Thread] = None
self._capture_enabled = False
self.generate_sprites()
self.mult = 1
repeating: bool = lt.property(default=False)
@ -339,14 +340,20 @@ class SimulatedCamera(BaseCamera):
pl_img = Image.fromarray(np_img.astype("uint8"))
return pl_img.resize((self.shape[1], self.shape[0]), Image.Resampling.BILINEAR)
@lt.action
def set_led(self, led_on: bool = True, led_channel=None):
if led_on:
self.mult = 1
else:
self.mult = 0
def generate_frame(self) -> Image.Image:
"""Generate a frame with blobs based on the stage coordinates."""
pos = self._stage.instantaneous_position
frame = self.generate_image((pos["y"], pos["x"], pos["z"]))
# Simulate LED turning off by setting all channels to 0
if not self._stage.led_on:
frame = np.full((self.shape[0], self.shape[1], 3), 0, dtype=np.uint8)
return frame
return frame * self.mult
def __enter__(self) -> Self:
"""Start the capture thread when the Thing context manager is opened."""

View file

@ -3,19 +3,12 @@ from typing import Literal
import labthings_fastapi as lt
from .stage import BaseStage
from .camera import BaseCam
class Illumination(lt.Thing):
"""Abstract illumination controller."""
brightness: float = lt.property(
default=0.0,
description="Illumination brightness (0-1)",
)
@lt.action
def set_brightness(self, brightness: float) -> None:
self.brightness = brightness
@lt.action
def flash(
self,
@ -23,22 +16,14 @@ class Illumination(lt.Thing):
dt: float = 0.5,
) -> None:
"""Flash the illumination source."""
for _ in range(number_of_flashes):
self.set_brightness(1.0)
time.sleep(dt)
self.set_brightness(0.0)
time.sleep(dt)
raise NotImplementedError(
'Flashing the LED can only be done from the simulator or sangaboard'
)
class SangaIllumination(Illumination):
"""Illumination driven by a Sangaboard."""
sangaboard = lt.thing_slot(description="Sangaboard providing LED control")
@lt.action
def set_brightness(self, brightness: float) -> None:
self.sangaboard.set_led_brightness(brightness)
self.brightness = brightness
_stage: BaseStage = lt.thing_slot()
@lt.action
def flash(
@ -48,7 +33,25 @@ class SangaIllumination(Illumination):
led_channel: Literal["cc"] = "cc",
) -> None:
for _ in range(number_of_flashes):
self.sangaboard.set_led(True, led_channel)
self._stage.set_led(False, led_channel)
time.sleep(dt)
self.sangaboard.set_led(False, led_channel)
self._stage.set_led(True, led_channel)
time.sleep(dt)
class SimulatorIllumination(Illumination):
"""Illumination control in the simulator."""
_cam: BaseCam = lt.thing_slot()
@lt.action
def flash(
self,
number_of_flashes: int = 10,
dt: float = 0.5,
led_channel: Literal["cc"] = "cc",
) -> None:
for _ in range(number_of_flashes):
self._cam.set_led(False, led_channel)
time.sleep(dt)
self._cam.set_led(True, led_channel)
time.sleep(dt)

View file

@ -6,7 +6,7 @@ import threading
from contextlib import contextmanager
from copy import copy
from types import TracebackType
from typing import Any, Iterator, Optional, Self
from typing import Any, Iterator, Literal, Optional, Self
import semver
@ -172,3 +172,31 @@ class SangaboardThing(BaseStage):
with self.sangaboard() as sb:
sb.zero_position()
self.update_position()
@lt.action
def set_led(
self,
led_on: bool = True,
led_channel: Literal["cc"] = "cc",
) -> None:
"""Flash the LED to identify the board.
This is intended to be useful in situations where there are multiple
Sangaboards in use, and it is necessary to identify which one is
being addressed.
"""
led_command = f"led_{led_channel}"
with self.sangaboard() as sb:
return_value = sb.query(f"{led_command}?")
if not return_value.startswith("CC LED:"):
raise IOError("The sangaboard does not support LED control")
# Reading and setting LED brightness suffers from repeated reads and writes
# decreasing the value. Rather than use the value the code warns that the value
# cannot be used.
if led_on:
on_brightness = 0.32
sb.query(f"{led_command} {on_brightness}")
else:
sb.query(f"{led_command} 0")