20"""Lumencor Spectra Light Engine.
22The implementation here is limited to the Lumencor Spectra III but
23should be trivial to make it work for other Lumencor light engines.
24We only need access to other such devices.
28 The engine is expected to be on the standard mode communications
29 (
not legacy). This can be changed via the device web interface.
37import microscope._utils
41class _SpectraIIIConnection:
42 """Connection to a Spectra III Light Engine.
44 This module makes checks for Spectra III light engine
and it was
45 only tested
for it. But it should work
with other lumencor light
46 engines
with little work though,
if only we got access to them.
61 self.
_serial.write(b
"GET MODEL\n")
62 answer = self.
_serial.readline()
63 if not answer.startswith(b
"A MODEL Spectra III"):
65 "Not a Lumencor Spectra III Light Engine"
68 def command_and_answer(self, *TX_tokens: bytes) -> bytes:
73 assert len(TX_tokens) >= 2,
"invalid command with less than two tokens"
74 assert TX_tokens[0]
in (
77 ),
"invalid command (not SET/GET)"
79 TX_command = b
" ".join(TX_tokens) + b
"\n"
82 answer = self.
_serial.readline()
83 RX_tokens = answer.split(maxsplit=2)
89 or RX_tokens[0] != b
"A"
90 or RX_tokens[1] != TX_tokens[1]
93 "command %s failed: %s" % (TX_command, answer)
97 def get_command(self, command: bytes, *args: bytes) -> bytes:
102 return answer[3 + len(command) : -2]
104 def set_command(self, command: bytes, *args: bytes) ->
None:
107 def get_channel_map(self) -> typing.List[typing.Tuple[int, str]]:
109 return list(enumerate(answer.decode().split()))
113 """Commands for a channel in a Lumencor light engine."""
115 def __init__(self, connection: _SpectraIIIConnection, index: int) ->
None:
116 self.
_conn = connection
120 """On (True) or off (False) state"""
133 """Turn light on (True) or off (False)."""
134 state_arg = b
"1" if state
else b
"0"
138 """Maximum valid intensity that can be applied to a light channel."""
142 """Current intensity setting between 0 and maximum intensity."""
146 """Set light intensity between 0 and maximum intensity."""
151 """Spectra III Light Engine.
154 port: port name (Windows) or path to port (everything
else) to
155 connect to. For example, `/dev/ttyS1`, `COM1`,
or
158 The names used on the devices dict are the ones provided by the
159 Spectra engine. These are the colour names
in capitals such
as
160 `
'BLUE'`, `
'NIR'`,
or `
'VIOLET'`.
162 Not all sources may be turned on simultaneously. To prevent
163 exceeding the capacity of the DC power supply, power consumption
164 is tracked by the Spectra onboard computer. If a set limit
is
165 exceeded, either by increasing intensity settings
for sources that
166 are already on,
or by turning on additional sources, commands will
167 be rejected. To clear the error condition, reduce intensities of
168 sources that are on
or turn off additional sources.
172 def __init__(self, port: str, **kwargs) ->
None:
173 super().__init__(**kwargs)
177 serial_conn = serial.Serial(
181 bytesize=serial.EIGHTBITS,
182 stopbits=serial.STOPBITS_ONE,
183 parity=serial.PARITY_NONE,
191 for index, name
in connection.get_channel_map():
193 name
not in self._lights
194 ),
"light with name '%s' already mapped"
198 def devices(self) -> typing.Mapping[str, microscope.abc.Device]:
206 """A single light channel from a light engine.
208 A channel may be an LED, luminescent light pipe, or a laser.
211 def __init__(self, connection: _SpectraIIIConnection, index: int) ->
None:
220 def _do_shutdown(self) -> None:
227 status: typing.List[str] = []
231 self.
_conn.set_light_state(
True)
234 self.
_conn.set_light_state(
False)
237 return self.
_conn.get_light_state()
239 def _do_set_power(self, power: float) ->
None:
242 def _do_get_power(self) -> float:
None set_intensity(self, int intensity)
None set_light_state(self, bool state)
bool get_light_state(self)
int get_max_intensity(self)
bytes command_and_answer(self, *bytes TX_tokens)
bytes get_command(self, bytes command, *bytes args)
typing.List[str] get_status(self)