20"""CoolLED illumination systems.
33_logger = logging.getLogger(__name__)
37 """Connection to the CoolLED controller, wraps base commands."""
52 "Not a CoolLED device, unable to get CSS"
56 """Get the global channel status map."""
59 answer = self.
_serial.readline()
60 if not answer.startswith(b
"CSS"):
62 "answer to 'CSS?' should start with 'CSS'"
63 " but got '%s' instead" % answer.decode
68 """Set status for any number of channels."""
69 assert len(css) % 6 == 0,
"css must be multiple of 6 (6 per channel)"
71 self.
_serial.write(b
"CSS" + css + b
"\n")
72 answer = self.
_serial.readline()
73 if not answer.startswith(b
"CSS"):
75 "answer to 'CSS?' should start with 'CSS'"
76 " but got '%s' instead" % answer.decode
80 """Return list of channel names (names are one character string)."""
85 return list(self.
get_css()[::6].decode())
89 """Wraps the CoolLED connection to control a single channel."""
91 def __init__(self, connection: _CoolLEDConnection, name: str) ->
None:
93 raise ValueError(
"name should be a one character string")
94 self.
_conn = connection
97 def _get_css(self) -> bytes:
98 global_css = self.
_conn.get_css()
102 """Intensity in integer percent [0 100]"""
106 """Intensity in integer percent [0 100]"""
107 percent = str(intensity).zfill(3)
111 """N (On) or F (Off)"""
112 return self.
_get_css()[2:3].decode()
115 """N (On) or F (Off)"""
116 if state
not in [
"N",
"F"]:
117 raise ValueError(
"state needs to be N (on) or F (off)")
119 self.
_conn.set_css(css[0:2] + state.encode() + css[3:])
121 def get_selected_state(self) -> str:
122 "S (Selected) or X (Unselected)" ""
123 return self.
_get_css()[1:2].decode()
126 """X (Unselected) or S (Selected)"""
127 if state
not in [
"X",
"S"]:
128 raise ValueError(
"state must be X (Unselected) or S (Selected)")
130 self.
_conn.set_css(css[0:1] + state.encode() + css[2:])
134 """Individual light devices that compose a CoolLED controller."""
137 self, connection: _CoolLEDConnection, name: str, **kwargs
139 super().__init__(**kwargs)
157 microscope.TriggerType.SOFTWARE, microscope.TriggerMode.BULB
167 self.
_conn.set_selected_state(
"S")
170 self.
_conn.set_switch_state(
"N")
173 self.
_conn.set_switch_state(
"F")
176 self.
_conn.set_selected_state(
"X")
179 selected = self.
_conn.get_selected_state()
180 assert selected
in [
"S",
"X"]
181 return selected ==
"S"
184 return self.
_conn.get_intensity() / 100.0
187 self.
_conn.set_intensity(int(power * 100.0))
190 def trigger_type(self) -> microscope.TriggerType:
191 if self.
_conn.get_selected_state() ==
"S":
194 if self.
_conn.get_switch_state() ==
"N":
195 return microscope.TriggerType.SOFTWARE
197 return microscope.TriggerType.HIGH
202 return microscope.TriggerType.SOFTWARE
204 return microscope.TriggerType.HIGH
207 def trigger_mode(self) -> microscope.TriggerMode:
208 return microscope.TriggerMode.BULB
213 if tmode
is not microscope.TriggerMode.BULB:
215 "the only trigger mode supported is 'bulb'"
217 if ttype
is microscope.TriggerType.SOFTWARE:
218 self.
_conn.set_switch_state(
"N")
220 elif ttype
is microscope.TriggerType.HIGH:
221 self.
_conn.set_switch_state(
"F")
225 "trigger type supported must be 'SOFTWARE' or 'HIGH'"
230 "trigger does not make sense in trigger mode bulb, only enable"
235 """CoolLED controller for the individual light devices.
238 port: port name (Windows) or path to port (everything else) to
239 connect to. For example, `/dev/ttyACM0`, `COM1`, or
242 The individual channels are named A to H and depend on the actual
243 device. The pE-300 have three channels named A, B, and C by
244 increasing order of wavelength of their spectral region. The
245 pE-4000 have four selectable channels named A, B, C, and D with
246 channels E-H for peripheral devices via a pE expansion box.
248 .. code-block:: python
250 # Connect to a pE-300 ultra and get the individual lights.
251 controller = CoolLED('/dev/ttyACM0')
252 violet = controller.devices['A']
253 blue = controller.devices['B']
254 red = controller.devices['C']
256 # Turn on the violet channel.
259 CoolLED controllers are often used with a control pod which can
260 select/unselect and turn on/off individual channels. The meaning
261 of these two states are:
263 * "selected" and "on": channel is always emitting light. This is
264 equivalent to being enabled with `SOFTWARE` trigger type.
266 * "selected" and "off": channel will emit light in receipt of a
267 TTL signal. This is equivalent to being enabled with `HIGH`
270 * "unselected" and "off": channel nevers emit light. This is
271 equivalent to being disabled.
273 * "unselected" and "on": this is not possible. If an "unselected"
274 channel is turned "on" it reverts back to "off".
278 If a channel is set with `TriggerType.SOFTWARE` ("on") it will
279 start emitting light once enabled ("selected"). Once enabled,
280 even though trigger type is set to software and not hardware,
281 if the channel receives a TTL signal it will switch to
282 `TriggerType.HIGH` and continue to report being set to
283 software. This seems to be an issue with the CoolLED
284 https://github.com/python-microscope/vendor-issues/issues/9
286 This was developed with a CoolLED pE-300 ultra but should work
287 with the whole pE-300 series. It should also work with the
288 pE-4000 and the pE expansion box with the exception of loading
293 def __init__(self, port: str, **kwargs) ->
None:
294 super().__init__(**kwargs)
298 serial_conn = serial.Serial(
302 bytesize=serial.EIGHTBITS,
303 stopbits=serial.STOPBITS_ONE,
304 parity=serial.PARITY_NONE,
311 for name
in connection.get_channels():
315 def devices(self) -> typing.Dict[str, microscope.abc.Device]:
None set_trigger(self, microscope.TriggerType ttype, microscope.TriggerMode tmode)
None set_switch_state(self, str state)
None set_intensity(self, int intensity)
str get_switch_state(self)
None set_selected_state(self, str state)
None _do_set_power(self, float power)
typing.List[str] get_status(self)
None set_trigger(self, microscope.TriggerType ttype, microscope.TriggerMode tmode)
float _do_get_power(self)
None set_css(self, bytes css)
typing.List[str] get_channels(self)