3333import serial
3434
3535import microscope .abc
36+ import microscope ._utils
3637
3738_logger = logging .getLogger (__name__ )
3839
156157
157158
158159def 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"\r END\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"\r END\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
381401class _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
451471class _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