25
25
Resource ,
26
26
Coordinate ,
27
27
Liquid ,
28
+ TecanPlateCarrier ,
28
29
TecanTipRack ,
29
30
TecanPlate ,
30
31
TecanTip ,
@@ -41,9 +42,9 @@ class TecanLiquidHandler(USBBackend, metaclass=ABCMeta):
41
42
@abstractmethod
42
43
def __init__ (
43
44
self ,
44
- packet_read_timeout : int = 3 ,
45
- read_timeout : int = 30 ,
46
- write_timeout : int = 30 ,
45
+ packet_read_timeout : int = 120 ,
46
+ read_timeout : int = 300 ,
47
+ write_timeout : int = 300 ,
47
48
):
48
49
"""
49
50
@@ -155,11 +156,11 @@ class EVO(TecanLiquidHandler):
155
156
def __init__ (
156
157
self ,
157
158
diti_count : int = 0 ,
158
- packet_read_timeout : int = 10 ,
159
- read_timeout : int = 30 ,
160
- write_timeout : int = 30 ,
159
+ packet_read_timeout : int = 120 ,
160
+ read_timeout : int = 300 ,
161
+ write_timeout : int = 300 ,
161
162
):
162
- """ Create a new STAR interface.
163
+ """ Create a new EVO interface.
163
164
164
165
Args:
165
166
packet_read_timeout: timeout in seconds for reading a single packet.
@@ -243,14 +244,17 @@ async def setup(self):
243
244
if self .roma_connected : # position_initialization_x in reverse order from setup_arm
244
245
self .roma = RoMa (self , EVO .ROMA )
245
246
await self .roma .position_initialization_x ()
247
+ # move to home position (TBD) after initialization
248
+ await self .roma .set_vector_coordinate_position (1 , 9000 , 2000 , 2464 , 1800 , None , 1 , 0 )
249
+ await self .roma .action_move_vector_coordinate_position ()
246
250
if self .liha_connected :
247
251
self .liha = LiHa (self , EVO .LIHA )
248
252
await self .liha .position_initialization_x ()
249
253
250
254
self ._num_channels = await self .liha .report_number_tips ()
251
255
self ._x_range = await self .liha .report_x_param (5 )
252
256
self ._y_range = (await self .liha .report_y_param (5 ))[0 ]
253
- self ._z_range = (await self .liha .report_z_param (5 ))[0 ] # TODO: assert all are same?
257
+ self ._z_range = (await self .liha .report_z_param (5 ))[0 ]
254
258
255
259
# Initialize plungers. Assumes wash station assigned at rail 1.
256
260
await self .liha .set_z_travel_height ([self ._z_range ] * self .num_channels )
@@ -263,8 +267,6 @@ async def setup(self):
263
267
await self .liha .move_plunger_relative ([- 100 ] * self .num_channels )
264
268
await self .liha .position_absolute_all_axis (45 , 1031 , 90 , [self ._z_range ] * self .num_channels )
265
269
266
- # TODO: cache arm positions to prevent collisions
267
-
268
270
async def setup_arm (self , module ):
269
271
try :
270
272
await self .send_command (module , command = "PIA" )
@@ -276,6 +278,10 @@ async def setup_arm(self, module):
276
278
await self .send_command (module , command = "BMX" , params = [2 ])
277
279
return True
278
280
281
+ async def _park_liha (self ):
282
+ await self .liha .set_z_travel_height ([self ._z_range ] * self .num_channels )
283
+ await self .liha .position_absolute_all_axis (45 , 1031 , 90 , [self ._z_range ] * self .num_channels )
284
+
279
285
# ============== LiquidHandlerBackend methods ==============
280
286
281
287
async def aspirate (
@@ -495,28 +501,29 @@ async def move_resource(self, move: Move):
495
501
""" Pick up a resource and move it to a new location. """
496
502
497
503
# TODO: implement PnP for moving tubes
504
+ assert self .roma_connected
498
505
499
- x , y , z = self ._roma_positions (move .resource , move .resource .get_absolute_location () )
506
+ z_range = await self .roma .report_z_param (5 )
507
+ x , y , z = self ._roma_positions (move .resource , move .resource .get_absolute_location (), z_range )
500
508
h = int (move .resource .get_size_y () * 10 )
501
- xt , yt , zt = self ._roma_positions (move .resource , move .to )
509
+ xt , yt , zt = self ._roma_positions (move .resource , move .to , z_range )
502
510
503
511
# move to resource
504
- # TODO: check collision with other arms
505
512
await self .roma .set_smooth_move_x (1 )
506
513
await self .roma .set_fast_speed_x (10000 )
507
514
await self .roma .set_fast_speed_y (5000 , 1500 )
508
- await self .roma .set_fast_speed_z (1000 )
515
+ await self .roma .set_fast_speed_z (1300 )
509
516
await self .roma .set_fast_speed_r (5000 , 1500 )
510
- await self .roma .set_vector_coordinate_position (1 , x , y , 1608 , 900 , None , 1 , 0 )
517
+ await self .roma .set_vector_coordinate_position (1 , x , y , z [ "safe" ] , 900 , None , 1 , 0 )
511
518
await self .roma .action_move_vector_coordinate_position ()
512
519
await self .roma .set_smooth_move_x (0 )
513
520
514
521
# pick up resource
515
522
await self .roma .position_absolute_g (900 ) # TODO: verify
516
- await self .roma .set_target_window_class (1 , 0 , 0 , 55 , 135 , 0 )
517
- await self .roma .set_vector_coordinate_position (1 , x , y , 1241 , 900 , None , 1 , 1 )
523
+ await self .roma .set_target_window_class (1 , 0 , 0 , 0 , 135 , 0 )
524
+ await self .roma .set_vector_coordinate_position (1 , x , y , z [ "travel" ] , 900 , None , 1 , 1 )
518
525
# TODO verify z param
519
- await self .roma .set_vector_coordinate_position (1 , x , y , z , 900 , None , 1 , 0 )
526
+ await self .roma .set_vector_coordinate_position (1 , x , y , z [ "end" ] , 900 , None , 1 , 0 )
520
527
await self .roma .action_move_vector_coordinate_position ()
521
528
await self .roma .set_fast_speed_y (3500 , 1000 )
522
529
await self .roma .set_fast_speed_r (2000 , 600 )
@@ -528,21 +535,21 @@ async def move_resource(self, move: Move):
528
535
await self .roma .set_target_window_class (2 , 0 , 0 , 0 , 53 , 0 )
529
536
await self .roma .set_target_window_class (3 , 0 , 0 , 0 , 55 , 0 )
530
537
await self .roma .set_target_window_class (4 , 45 , 0 , 0 , 0 , 0 )
531
- await self .roma .set_vector_coordinate_position (1 , x , y , z , 900 , None , 1 , 1 )
532
- await self .roma .set_vector_coordinate_position (2 , x , y , 1241 , 900 , None , 1 , 2 )
533
- await self .roma .set_vector_coordinate_position (3 , x , y , 1608 , 900 , None , 1 , 3 )
534
- await self .roma .set_vector_coordinate_position (4 , xt , yt , 1608 , 900 , None , 1 , 4 )
535
- await self .roma .set_vector_coordinate_position (5 , xt , yt , 1241 , 900 , None , 1 , 3 )
536
- await self .roma .set_vector_coordinate_position (6 , xt , yt , zt , 900 , None , 1 , 0 )
538
+ await self .roma .set_vector_coordinate_position (1 , x , y , z [ "end" ] , 900 , None , 1 , 1 )
539
+ await self .roma .set_vector_coordinate_position (2 , x , y , z [ "travel" ] , 900 , None , 1 , 2 )
540
+ await self .roma .set_vector_coordinate_position (3 , x , y , z [ "safe" ] , 900 , None , 1 , 3 )
541
+ await self .roma .set_vector_coordinate_position (4 , xt , yt , zt [ "safe" ] , 900 , None , 1 , 4 )
542
+ await self .roma .set_vector_coordinate_position (5 , xt , yt , zt [ "travel" ] , 900 , None , 1 , 3 )
543
+ await self .roma .set_vector_coordinate_position (6 , xt , yt , zt [ "end" ] , 900 , None , 1 , 0 )
537
544
await self .roma .action_move_vector_coordinate_position ()
538
545
539
546
# release resource
540
547
await self .roma .position_absolute_g (900 )
541
548
await self .roma .set_fast_speed_y (5000 , 1500 )
542
549
await self .roma .set_fast_speed_r (5000 , 1500 )
543
- await self .roma .set_vector_coordinate_position (1 , xt , yt , zt , 900 , None , 1 , 1 )
544
- await self .roma .set_vector_coordinate_position (2 , xt , yt , 1241 , 900 , None , 1 , 2 )
545
- await self .roma .set_vector_coordinate_position (3 , xt , yt , 1608 , 900 , None , 1 , 0 )
550
+ await self .roma .set_vector_coordinate_position (1 , xt , yt , zt [ "end" ] , 900 , None , 1 , 1 )
551
+ await self .roma .set_vector_coordinate_position (2 , xt , yt , zt [ "travel" ] , 900 , None , 1 , 2 )
552
+ await self .roma .set_vector_coordinate_position (3 , xt , yt , zt [ "safe" ] , 900 , None , 1 , 0 )
546
553
await self .roma .action_move_vector_coordinate_position ()
547
554
await self .roma .set_fast_speed_y (3500 , 1000 )
548
555
await self .roma .set_fast_speed_r (2000 , 600 )
@@ -590,7 +597,7 @@ def get_z_position(z, z_off, tip_length):
590
597
for i , channel in enumerate (use_channels ):
591
598
location = ops [i ].resource .get_absolute_location () + ops [i ].resource .center ()
592
599
x_positions [channel ] = int ((location .x - 100 ) * 10 )
593
- y_positions [channel ] = int ((345 - location .y ) * 10 ) # TODO: verify
600
+ y_positions [channel ] = int ((346.5 - location .y ) * 10 ) # TODO: verify
594
601
595
602
par = ops [i ].resource .parent
596
603
if not isinstance (par , (TecanPlate , TecanTipRack )):
@@ -634,11 +641,11 @@ def _aspirate_airgap(
634
641
assert tlc is not None
635
642
pvl [channel ] = 0
636
643
if airgap == "lag" :
637
- sep [channel ] = int (tlc .aspirate_lag_speed * 6 ) # 12 ? TODO: verify step unit
638
- ppr [channel ] = int (tlc .aspirate_lag_volume * 3 ) # 6 ?
644
+ sep [channel ] = int (tlc .aspirate_lag_speed * 12 ) # 6 ? TODO: verify step unit
645
+ ppr [channel ] = int (tlc .aspirate_lag_volume * 6 ) # 3 ?
639
646
elif airgap == "tag" :
640
- sep [channel ] = int (tlc .aspirate_tag_speed * 6 ) # 12 ?
641
- ppr [channel ] = int (tlc .aspirate_tag_volume * 3 ) # 6 ?
647
+ sep [channel ] = int (tlc .aspirate_tag_speed * 12 ) # 6 ?
648
+ ppr [channel ] = int (tlc .aspirate_tag_volume * 6 ) # 3 ?
642
649
643
650
return pvl , sep , ppr
644
651
@@ -703,9 +710,9 @@ def _aspirate_action(
703
710
tlc = tecan_liquid_classes [i ]
704
711
z = zadd [channel ]
705
712
assert tlc is not None and z is not None
706
- sep [channel ] = int (tlc .aspirate_speed * 6 ) # 12 ?
713
+ sep [channel ] = int (tlc .aspirate_speed * 12 ) # 6 ?
707
714
ssz [channel ] = round (z * tlc .aspirate_speed / ops [i ].volume )
708
- mtr [channel ] = round (ops [i ].volume * 3 ) # 6 ?
715
+ mtr [channel ] = round (ops [i ].volume * 6 ) # 3 ?
709
716
ssz_r [channel ] = int (tlc .aspirate_retract_speed * 10 )
710
717
711
718
return ssz , sep , stz , mtr , ssz_r
@@ -733,29 +740,49 @@ def _dispense_action(
733
740
for i , channel in enumerate (use_channels ):
734
741
tlc = tecan_liquid_classes [i ]
735
742
assert tlc is not None
736
- sep [channel ] = int (tlc .dispense_speed * 6 ) # 12 ?
737
- spp [channel ] = int (tlc .dispense_breakoff * 6 ) # 12 ?
743
+ sep [channel ] = int (tlc .dispense_speed * 12 ) # 6 ?
744
+ spp [channel ] = int (tlc .dispense_breakoff * 12 ) # 6 ?
738
745
stz [channel ] = 0
739
- mtr [channel ] = - round (ops [i ].volume * 3 ) # 6 ?
746
+ mtr [channel ] = - round (ops [i ].volume * 6 ) # 3 ?
740
747
741
748
return sep , spp , stz , mtr
742
749
743
750
def _roma_positions (
744
751
self ,
745
752
resource : Resource ,
746
- offset : Coordinate
747
- ) -> Tuple [int , int , int ]:
753
+ offset : Coordinate ,
754
+ z_range : int
755
+ ) -> Tuple [int , int , Dict [str , int ]]:
748
756
""" Creates x, y, and z positions used by RoMa ops. """
749
757
750
- center = offset + resource .center ()
751
- return int ((center .x - 100 )* 10 ) + 1240 , int ((344.5 - center .y ) * 10 ), int (center .z * 10 ) + 17
758
+ par = resource .parent
759
+ if par is None :
760
+ raise ValueError (f"Operation is not supported by resource { resource } ." )
761
+ par = par .parent
762
+ if not isinstance (par , TecanPlateCarrier ):
763
+ raise ValueError (f"Operation is not supported by resource { par } ." )
764
+
765
+ if par .roma_x is None or par .roma_y is None or par .roma_z_safe is None \
766
+ or par .roma_z_travel is None or par .roma_z_end is None :
767
+ raise ValueError (f"Operation is not supported by resource { par } ." )
768
+ x_position = int ((offset .x - 100 )* 10 + par .roma_x )
769
+ y_position = int ((347.1 - (offset .y + resource .get_size_y ())) * 10 + par .roma_y )
770
+ z_positions = {
771
+ "safe" : z_range - int (par .roma_z_safe ),
772
+ "travel" : z_range - int (par .roma_z_travel - offset .z * 10 ),
773
+ "end" : z_range - int (par .roma_z_end - offset .z * 10 )
774
+ }
775
+
776
+ return x_position , y_position , z_positions
752
777
753
778
754
779
class EVOArm :
755
780
"""
756
- Provides firmware commands for EVO arms
781
+ Provides firmware commands for EVO arms. Caches arm positions.
757
782
"""
758
783
784
+ _pos_cache : Dict [str , int ] = {}
785
+
759
786
def __init__ (
760
787
self ,
761
788
backend : EVO ,
@@ -791,17 +818,6 @@ async def report_y_param(self, param: int) -> List[int]:
791
818
command = "RPY" , params = [param ]))["data" ]
792
819
return resp
793
820
794
- async def report_z_param (self , param : int ) -> List [int ]:
795
- """ Report current parameters for z-axis.
796
-
797
- Args:
798
- param: 0 - current position, 5 - actual machine range
799
- """
800
-
801
- resp : List [int ] = (await self .backend .send_command (module = self .module ,
802
- command = "RPZ" , params = [param ]))["data" ]
803
- return resp
804
-
805
821
806
822
class LiHa (EVOArm ):
807
823
"""
@@ -816,6 +832,17 @@ async def initialize_plunger(self, tips):
816
832
"""
817
833
await self .backend .send_command (module = self .module , command = "PID" , params = [tips ])
818
834
835
+ async def report_z_param (self , param : int ) -> List [int ]:
836
+ """ Report current parameters for z-axis.
837
+
838
+ Args:
839
+ param: 0 - current position, 5 - actual machine range
840
+ """
841
+
842
+ resp : List [int ] = (await self .backend .send_command (module = self .module ,
843
+ command = "RPZ" , params = [param ]))["data" ]
844
+ return resp
845
+
819
846
async def report_number_tips (self ) -> int :
820
847
""" Report number of tips on arm. """
821
848
@@ -832,10 +859,22 @@ async def position_absolute_all_axis(self, x: int, y: int, ys: int, z: List[int]
832
859
ys: absolute y spacing in 1/10 mm, must be between 90 and 380
833
860
z: absolute z position in 1/10 mm for each channel, must be in
834
861
allowed machine range
862
+
863
+ Raises:
864
+ TecanError: if moving to the target position causes a collision
835
865
"""
836
866
867
+ cur_x = EVOArm ._pos_cache .setdefault (self .module , await self .report_x_param (0 ))
868
+ for module , pos in EVOArm ._pos_cache .items ():
869
+ if module == self .module :
870
+ continue
871
+ if cur_x < pos < x or x < pos < cur_x or abs (pos - x ) < 1500 :
872
+ raise TecanError ("Invalid command (collision)" , self .module , 2 )
873
+
837
874
await self .backend .send_command (module = self .module , command = "PAA" , params = list ([x , y , ys ] + z ))
838
875
876
+ EVOArm ._pos_cache [self .module ] = x
877
+
839
878
async def position_valve_logical (self , param : List [Optional [int ]]):
840
879
""" Position valve logical for each channel.
841
880
@@ -1021,6 +1060,17 @@ class RoMa(EVOArm):
1021
1060
Provides firmware commands for the RoMa plate robot
1022
1061
"""
1023
1062
1063
+ async def report_z_param (self , param : int ) -> int :
1064
+ """ Report current parameter for z-axis.
1065
+
1066
+ Args:
1067
+ param: 0 - current position, 5 - actual machine range
1068
+ """
1069
+
1070
+ resp : List [int ] = (await self .backend .send_command (module = self .module ,
1071
+ command = "RPZ" , params = [param ]))["data" ]
1072
+ return resp [0 ]
1073
+
1024
1074
async def report_r_param (self , param : int ) -> int :
1025
1075
""" Report current parameter for r-axis.
1026
1076
@@ -1095,10 +1145,10 @@ async def set_fast_speed_r(self, speed: Optional[int], accel: Optional[int] = No
1095
1145
async def set_vector_coordinate_position (
1096
1146
self ,
1097
1147
v : int ,
1098
- x : Optional [ int ] ,
1099
- y : Optional [ int ] ,
1100
- z : Optional [ int ] ,
1101
- r : Optional [ int ] ,
1148
+ x : int ,
1149
+ y : int ,
1150
+ z : int ,
1151
+ r : int ,
1102
1152
g : Optional [int ],
1103
1153
speed : int ,
1104
1154
tw : int = 0
@@ -1114,8 +1164,18 @@ async def set_vector_coordinate_position(
1114
1164
g: aboslute g position in 1/10 mm
1115
1165
speed: speed select, 0 - slow, 1 - fast
1116
1166
tw: target window class, set with STW
1167
+
1168
+ Raises:
1169
+ TecanError: if moving to the target position causes a collision
1117
1170
"""
1118
1171
1172
+ cur_x = EVOArm ._pos_cache .setdefault (self .module , await self .report_x_param (0 ))
1173
+ for module , pos in EVOArm ._pos_cache .items ():
1174
+ if module == self .module :
1175
+ continue
1176
+ if cur_x < pos < x or x < pos < cur_x or abs (pos - x ) < 1500 :
1177
+ raise TecanError ("Invalid command (collision)" , self .module , 2 )
1178
+
1119
1179
await self .backend .send_command (module = self .module , command = "SAA" ,
1120
1180
params = [v , x , y , z , r , g , speed , 0 , tw ])
1121
1181
@@ -1124,6 +1184,8 @@ async def action_move_vector_coordinate_position(self):
1124
1184
1125
1185
await self .backend .send_command (module = self .module , command = "AAC" )
1126
1186
1187
+ EVOArm ._pos_cache [self .module ] = await self .report_x_param (0 )
1188
+
1127
1189
async def position_absolute_g (self , g : int ):
1128
1190
""" Position absolute for G-axis
1129
1191
0 commit comments