Skip to content

Commit e28c526

Browse files
Added support for 2 light sources avainable in the MS2000
1 parent 7710166 commit e28c526

File tree

1 file changed

+83
-28
lines changed

1 file changed

+83
-28
lines changed

microscope/controllers/asi.py

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import serial
3434

3535
import microscope.abc
36+
import microscope._utils
3637

3738
_logger = logging.getLogger(__name__)
3839

@@ -156,7 +157,7 @@
156157

157158

158159
def parse_info(
159-
info: list,
160+
info: list,
160161
) -> dict[str, dict[str, Union[typing.Optional[str], Any]]]:
161162
items = []
162163
for line in info:
@@ -183,7 +184,7 @@ def parse_info(
183184
return settings
184185

185186

186-
class _ASIMotionController:
187+
class _ASIController:
187188
"""Connection to a ASI Controller and wrapper to its commands.
188189
189190
Tested with MS2000 controller and xy stage.
@@ -230,14 +231,20 @@ def __init__(self, port: str, baudrate: int, timeout: float) -> None:
230231
except:
231232
print("Unable to read configuration. Is ASI controller connected?")
232233
return
233-
# parse config responce which tells us what devices are present
234+
235+
self._led_mapper = {"1": b"X", "2": b"Y"}
236+
237+
# parse config response which tells us what devices are present
234238
# on this controller.
235239

236240
def is_busy(self):
237241
pass
238242

239243
def get_number_axes(self):
240-
return len(self.axis_info)
244+
return len(self._axis_mapper)
245+
246+
def get_number_leds(self):
247+
return len(self._led_mapper)
241248

242249
def command(self, command: bytes) -> None:
243250
"""Send command to device."""
@@ -283,7 +290,7 @@ def wait_until_idle(self) -> None:
283290
"""Keep sending the ``STATUS`` comand until it responds ``0\\r``"""
284291
self._command_and_validate(b"STATUS", b"N")
285292

286-
def _command_and_validate(self, command: bytes, expected: bytes) -> None:
293+
def _command_and_validate(self, command: bytes, expected: bytes) -> bytes:
287294
with self._lock:
288295
answer = self.get_command(command)
289296
if answer == b":A \r\n":
@@ -298,6 +305,29 @@ def get_command(self, command: bytes) -> bytes:
298305
self.command(command)
299306
return self.readline()
300307

308+
def set_command(self, command: bytes) -> None:
309+
"""Send a set command and check return value."""
310+
# Property type commands that set certain status respond with
311+
# zero. They respond with a zero even if there are invalid
312+
# arguments in the command.
313+
self._command_and_validate(command, b"0\r")
314+
315+
def get_description(self, command: bytes) -> bytes:
316+
"""Send a get description command and return it."""
317+
with self._lock:
318+
self.command(command)
319+
return self._serial.read_until(b"\rEND\r")
320+
321+
@contextlib.contextmanager
322+
def changed_timeout(self, new_timeout: float):
323+
previous = self._serial.timeout
324+
try:
325+
self._serial.timeout = new_timeout
326+
yield
327+
finally:
328+
self._serial.timeout = previous
329+
330+
# Motion related methods #
301331
def move_command(self, command: bytes) -> None:
302332
"""Send a move command and check return value."""
303333
# Movement commands respond with ":A \n" but the move is then
@@ -355,31 +385,21 @@ def get_absolute_position(self, axis: int) -> float:
355385
else:
356386
return float(position.strip()[2:])
357387

358-
def set_command(self, command: bytes) -> None:
359-
"""Send a set command and check return value."""
360-
# Property type commands that set certain status respond with
361-
# zero. They respond with a zero even if there are invalid
362-
# arguments in the command.
363-
self._command_and_validate(command, b"0\r")
388+
# Light related methods #
389+
def is_led_on(self, channel):
390+
return bool(self.get_led_power(channel))
364391

365-
def get_description(self, command: bytes) -> bytes:
366-
"""Send a get description command and return it."""
367-
with self._lock:
368-
self.command(command)
369-
return self._serial.read_until(b"\rEND\r")
392+
def get_led_power(self, channel):
393+
answer = self.get_command(b"LED " + self._led_mapper[channel] + b"?")
394+
return int(answer[3:-4]) / 100
370395

371-
@contextlib.contextmanager
372-
def changed_timeout(self, new_timeout: float):
373-
previous = self._serial.timeout
374-
try:
375-
self._serial.timeout = new_timeout
376-
yield
377-
finally:
378-
self._serial.timeout = previous
396+
def set_led_power(self, channel, power):
397+
power = str(int(power * 100)).encode()
398+
self.get_command(b"LED " + self._led_mapper[channel] + b"=" + power)
379399

380400

381401
class _ASIStageAxis(microscope.abc.StageAxis):
382-
def __init__(self, dev_conn: _ASIMotionController, axis: int) -> None:
402+
def __init__(self, dev_conn: _ASIController, axis: int) -> None:
383403
super().__init__()
384404
self._dev_conn = dev_conn
385405
self._axis = axis
@@ -449,7 +469,7 @@ def find_limits(self, speed=100):
449469

450470

451471
class _ASIStage(microscope.abc.Stage):
452-
def __init__(self, conn: _ASIMotionController, **kwargs) -> None:
472+
def __init__(self, conn: _ASIController, **kwargs) -> None:
453473
super().__init__(**kwargs)
454474
self._dev_conn = conn
455475
self._axes = {
@@ -565,6 +585,38 @@ def move_to(self, position: typing.Mapping[str, float]) -> None:
565585
self._dev_conn.wait_until_idle()
566586

567587

588+
class _ASILED(microscope._utils.OnlyTriggersBulbOnSoftwareMixin, microscope.abc.LightSource):
589+
# In principle the light source of the MS2000 and the tiger controllers support triggers, but as not yet implemented
590+
# we set this class as oly triggerable by software
591+
# TODO: Look into implementation of triggers
592+
593+
def __init__(self, dev_conn: _ASIController, channel: str) -> None:
594+
super().__init__()
595+
self._dev_conn = dev_conn
596+
self._channel = channel
597+
598+
def get_status(self) -> typing.List[str]:
599+
return super().get_status() # TODO: Verify what am I doing here. Just copying from the Zaber led controller
600+
601+
def get_is_on(self) -> bool:
602+
return self._dev_conn.is_led_on(self._channel)
603+
604+
def _do_get_power(self) -> float:
605+
return self._dev_conn.get_led_power(self._channel)
606+
607+
def _do_set_power(self, power: float) -> None:
608+
self._dev_conn.set_led_power(self._channel, power)
609+
610+
def _do_enable(self):
611+
self._do_set_power(self._set_point)
612+
613+
def _do_disable(self):
614+
self._do_set_power(0.0)
615+
616+
def _do_shutdown(self) -> None:
617+
self._do_disable()
618+
619+
568620
# def assert_filterwheel_number(self, number: int) -> None:
569621
# assert number > 0 and number < 4
570622

@@ -622,12 +674,15 @@ class ASIMS2000(microscope.abc.Controller):
622674
"""
623675

624676
def __init__(
625-
self, port: str, baudrate: int = 9600, timeout: float = 0.5, **kwargs
677+
self, port: str, baudrate: int = 9600, timeout: float = 0.5, **kwargs
626678
) -> None:
627679
super().__init__(**kwargs)
628-
self._conn = _ASIMotionController(port, baudrate, timeout)
680+
self._conn = _ASIController(port, baudrate, timeout)
629681
self._devices: typing.Mapping[str, microscope.abc.Device] = {}
630682
self._devices["stage"] = _ASIStage(self._conn)
683+
self._devices["ligth_465"] = _ASILED(self._conn, "1")
684+
self._devices["ligth_560"] = _ASILED(self._conn, "2")
685+
631686

632687
@property
633688
def devices(self) -> typing.Mapping[str, microscope.abc.Device]:

0 commit comments

Comments
 (0)