@@ -3487,6 +3487,28 @@ async def drop_tips96(
34873487 ),
34883488 )
34893489
3490+ async def _core96_wait_for_idle (self , timeout : float = 600 , poll_interval : float = 5 ):
3491+ """Poll the CoRe 96 head until it finishes its current operation.
3492+
3493+ Sends the "move to Z safety" command (C0 EV), which goes through the master's 96-head
3494+ task queue. If the head is busy, the master responds with trace 46. When the head finishes,
3495+ EV succeeds and harmlessly ensures the Z axis is at the safe position.
3496+ """
3497+ start = asyncio .get_event_loop ().time ()
3498+ while asyncio .get_event_loop ().time () - start < timeout :
3499+ await asyncio .sleep (poll_interval )
3500+ try :
3501+ await self .send_command (module = "C0" , command = "EV" , read_timeout = 10 )
3502+ logger .info ("CoRe 96 head finished (EV succeeded)" )
3503+ return
3504+ except STARFirmwareError as e :
3505+ master_error = e .errors .get ("Master" )
3506+ if master_error is not None and master_error .trace_information == 46 :
3507+ logger .debug ("CoRe 96 head still busy, waiting..." )
3508+ continue
3509+ raise
3510+ raise TimeoutError ("CoRe 96 head did not become idle within timeout" )
3511+
34903512 @_requires_head96
34913513 async def aspirate96 (
34923514 self ,
@@ -3707,43 +3729,50 @@ async def aspirate96(
37073729 settling_time = settling_time or (hlc .aspiration_settling_time if hlc is not None else 0.5 )
37083730
37093731 x_direction = 0 if position .x >= 0 else 1
3710- return await self .aspirate_core_96 (
3711- x_position = abs (round (position .x * 10 )),
3712- x_direction = x_direction ,
3713- y_positions = round (position .y * 10 ),
3714- aspiration_type = aspiration_type ,
3715- minimum_traverse_height_at_beginning_of_a_command = round (
3716- (minimum_traverse_height_at_beginning_of_a_command or self ._channel_traversal_height ) * 10
3717- ),
3718- min_z_endpos = round ((min_z_endpos or self ._channel_traversal_height ) * 10 ),
3719- lld_search_height = round (lld_search_height * 10 ),
3720- liquid_surface_no_lld = round (liquid_height * 10 ),
3721- pull_out_distance_transport_air = round (pull_out_distance_transport_air * 10 ),
3722- minimum_height = round ((minimum_height or position .z ) * 10 ),
3723- second_section_height = round (second_section_height * 10 ),
3724- second_section_ratio = round (second_section_ratio * 10 ),
3725- immersion_depth = round (immersion_depth * 10 ),
3726- immersion_depth_direction = immersion_depth_direction or (0 if (immersion_depth >= 0 ) else 1 ),
3727- surface_following_distance = round (surface_following_distance * 10 ),
3728- aspiration_volumes = round (volume * 10 ),
3729- aspiration_speed = round (flow_rate * 10 ),
3730- transport_air_volume = round (transport_air_volume * 10 ),
3731- blow_out_air_volume = round (blow_out_air_volume * 10 ),
3732- pre_wetting_volume = round (pre_wetting_volume * 10 ),
3733- lld_mode = int (use_lld ),
3734- gamma_lld_sensitivity = gamma_lld_sensitivity ,
3735- swap_speed = round (swap_speed * 10 ),
3736- settling_time = round (settling_time * 10 ),
3737- mix_volume = round (aspiration .mix .volume * 10 ) if aspiration .mix is not None else 0 ,
3738- mix_cycles = aspiration .mix .repetitions if aspiration .mix is not None else 0 ,
3739- mix_position_from_liquid_surface = round (mix_position_from_liquid_surface * 10 ),
3740- mix_surface_following_distance = round (mix_surface_following_distance * 10 ),
3741- speed_of_mix = round (aspiration .mix .flow_rate * 10 ) if aspiration .mix is not None else 1200 ,
3742- channel_pattern = [True ] * 12 * 8 ,
3743- limit_curve_index = limit_curve_index ,
3744- tadm_algorithm = False ,
3745- recording_mode = 0 ,
3746- )
3732+ try :
3733+ return await self .aspirate_core_96 (
3734+ x_position = abs (round (position .x * 10 )),
3735+ x_direction = x_direction ,
3736+ y_positions = round (position .y * 10 ),
3737+ aspiration_type = aspiration_type ,
3738+ minimum_traverse_height_at_beginning_of_a_command = round (
3739+ (minimum_traverse_height_at_beginning_of_a_command or self ._channel_traversal_height ) * 10
3740+ ),
3741+ min_z_endpos = round ((min_z_endpos or self ._channel_traversal_height ) * 10 ),
3742+ lld_search_height = round (lld_search_height * 10 ),
3743+ liquid_surface_no_lld = round (liquid_height * 10 ),
3744+ pull_out_distance_transport_air = round (pull_out_distance_transport_air * 10 ),
3745+ minimum_height = round ((minimum_height or position .z ) * 10 ),
3746+ second_section_height = round (second_section_height * 10 ),
3747+ second_section_ratio = round (second_section_ratio * 10 ),
3748+ immersion_depth = round (immersion_depth * 10 ),
3749+ immersion_depth_direction = immersion_depth_direction or (0 if (immersion_depth >= 0 ) else 1 ),
3750+ surface_following_distance = round (surface_following_distance * 10 ),
3751+ aspiration_volumes = round (volume * 10 ),
3752+ aspiration_speed = round (flow_rate * 10 ),
3753+ transport_air_volume = round (transport_air_volume * 10 ),
3754+ blow_out_air_volume = round (blow_out_air_volume * 10 ),
3755+ pre_wetting_volume = round (pre_wetting_volume * 10 ),
3756+ lld_mode = int (use_lld ),
3757+ gamma_lld_sensitivity = gamma_lld_sensitivity ,
3758+ swap_speed = round (swap_speed * 10 ),
3759+ settling_time = round (settling_time * 10 ),
3760+ mix_volume = round (aspiration .mix .volume * 10 ) if aspiration .mix is not None else 0 ,
3761+ mix_cycles = aspiration .mix .repetitions if aspiration .mix is not None else 0 ,
3762+ mix_position_from_liquid_surface = round (mix_position_from_liquid_surface * 10 ),
3763+ mix_surface_following_distance = round (mix_surface_following_distance * 10 ),
3764+ speed_of_mix = round (aspiration .mix .flow_rate * 10 ) if aspiration .mix is not None else 1200 ,
3765+ channel_pattern = [True ] * 12 * 8 ,
3766+ limit_curve_index = limit_curve_index ,
3767+ tadm_algorithm = False ,
3768+ recording_mode = 0 ,
3769+ )
3770+ except STARFirmwareError as e :
3771+ if self ._is_core96_slave_timeout (e ):
3772+ logger .warning ("Firmware slave timeout during aspirate96, polling for completion" )
3773+ await self ._core96_wait_for_idle ()
3774+ else :
3775+ raise
37473776
37483777 @_requires_head96
37493778 async def dispense96 (
@@ -3981,44 +4010,51 @@ async def dispense96(
39814010 swap_speed = swap_speed or (hlc .dispense_swap_speed if hlc is not None else 100 )
39824011 settling_time = settling_time or (hlc .dispense_settling_time if hlc is not None else 5 )
39834012
3984- return await self .dispense_core_96 (
3985- dispensing_mode = dispense_mode ,
3986- x_position = abs (round (position .x * 10 )),
3987- x_direction = 0 if position .x >= 0 else 1 ,
3988- y_position = round (position .y * 10 ),
3989- minimum_traverse_height_at_beginning_of_a_command = round (
3990- (minimum_traverse_height_at_beginning_of_a_command or self ._channel_traversal_height ) * 10
3991- ),
3992- min_z_endpos = round ((min_z_endpos or self ._channel_traversal_height ) * 10 ),
3993- lld_search_height = round (lld_search_height * 10 ),
3994- liquid_surface_no_lld = round (liquid_height * 10 ),
3995- pull_out_distance_transport_air = round (pull_out_distance_transport_air * 10 ),
3996- minimum_height = round ((minimum_height or position .z ) * 10 ),
3997- second_section_height = round (second_section_height * 10 ),
3998- second_section_ratio = round (second_section_ratio * 10 ),
3999- immersion_depth = round (immersion_depth * 10 ),
4000- immersion_depth_direction = immersion_depth_direction or (0 if (immersion_depth >= 0 ) else 1 ),
4001- surface_following_distance = round (surface_following_distance * 10 ),
4002- dispense_volume = round (volume * 10 ),
4003- dispense_speed = round (flow_rate * 10 ),
4004- transport_air_volume = round (transport_air_volume * 10 ),
4005- blow_out_air_volume = round (blow_out_air_volume * 10 ),
4006- lld_mode = int (use_lld ),
4007- gamma_lld_sensitivity = gamma_lld_sensitivity ,
4008- swap_speed = round (swap_speed * 10 ),
4009- settling_time = round (settling_time * 10 ),
4010- mixing_volume = round (dispense .mix .volume * 10 ) if dispense .mix is not None else 0 ,
4011- mixing_cycles = dispense .mix .repetitions if dispense .mix is not None else 0 ,
4012- mix_position_from_liquid_surface = round (mix_position_from_liquid_surface * 10 ),
4013- mix_surface_following_distance = round (mix_surface_following_distance * 10 ),
4014- speed_of_mixing = round (dispense .mix .flow_rate * 10 ) if dispense .mix is not None else 1200 ,
4015- channel_pattern = [True ] * 12 * 8 ,
4016- limit_curve_index = limit_curve_index ,
4017- tadm_algorithm = False ,
4018- recording_mode = 0 ,
4019- cut_off_speed = round (cut_off_speed * 10 ),
4020- stop_back_volume = round (stop_back_volume * 10 ),
4021- )
4013+ try :
4014+ return await self .dispense_core_96 (
4015+ dispensing_mode = dispense_mode ,
4016+ x_position = abs (round (position .x * 10 )),
4017+ x_direction = 0 if position .x >= 0 else 1 ,
4018+ y_position = round (position .y * 10 ),
4019+ minimum_traverse_height_at_beginning_of_a_command = round (
4020+ (minimum_traverse_height_at_beginning_of_a_command or self ._channel_traversal_height ) * 10
4021+ ),
4022+ min_z_endpos = round ((min_z_endpos or self ._channel_traversal_height ) * 10 ),
4023+ lld_search_height = round (lld_search_height * 10 ),
4024+ liquid_surface_no_lld = round (liquid_height * 10 ),
4025+ pull_out_distance_transport_air = round (pull_out_distance_transport_air * 10 ),
4026+ minimum_height = round ((minimum_height or position .z ) * 10 ),
4027+ second_section_height = round (second_section_height * 10 ),
4028+ second_section_ratio = round (second_section_ratio * 10 ),
4029+ immersion_depth = round (immersion_depth * 10 ),
4030+ immersion_depth_direction = immersion_depth_direction or (0 if (immersion_depth >= 0 ) else 1 ),
4031+ surface_following_distance = round (surface_following_distance * 10 ),
4032+ dispense_volume = round (volume * 10 ),
4033+ dispense_speed = round (flow_rate * 10 ),
4034+ transport_air_volume = round (transport_air_volume * 10 ),
4035+ blow_out_air_volume = round (blow_out_air_volume * 10 ),
4036+ lld_mode = int (use_lld ),
4037+ gamma_lld_sensitivity = gamma_lld_sensitivity ,
4038+ swap_speed = round (swap_speed * 10 ),
4039+ settling_time = round (settling_time * 10 ),
4040+ mixing_volume = round (dispense .mix .volume * 10 ) if dispense .mix is not None else 0 ,
4041+ mixing_cycles = dispense .mix .repetitions if dispense .mix is not None else 0 ,
4042+ mix_position_from_liquid_surface = round (mix_position_from_liquid_surface * 10 ),
4043+ mix_surface_following_distance = round (mix_surface_following_distance * 10 ),
4044+ speed_of_mixing = round (dispense .mix .flow_rate * 10 ) if dispense .mix is not None else 1200 ,
4045+ channel_pattern = [True ] * 12 * 8 ,
4046+ limit_curve_index = limit_curve_index ,
4047+ tadm_algorithm = False ,
4048+ recording_mode = 0 ,
4049+ cut_off_speed = round (cut_off_speed * 10 ),
4050+ stop_back_volume = round (stop_back_volume * 10 ),
4051+ )
4052+ except STARFirmwareError as e :
4053+ if self ._is_core96_slave_timeout (e ):
4054+ logger .warning ("Firmware slave command timeout during dispense96, waiting for head to finish" )
4055+ await self ._core96_wait_for_idle ()
4056+ else :
4057+ raise
40224058
40234059 async def iswap_move_picked_up_resource (
40244060 self ,
@@ -7771,6 +7807,7 @@ async def aspirate_core_96(
77717807 surface_following_distance_during_mix : int = 0 ,
77727808 tube_2nd_section_ratio : int = 3425 ,
77737809 tube_2nd_section_height_measured_from_zm : int = 0 ,
7810+ _bypass_fw_wait : bool = False ,
77747811 ):
77757812 """aspirate CoRe 96
77767813
@@ -7953,6 +7990,8 @@ async def aspirate_core_96(
79537990 return await self .send_command (
79547991 module = "C0" ,
79557992 command = "EA" ,
7993+ read_timeout = max (300 , self .read_timeout ),
7994+ wait = not _bypass_fw_wait ,
79567995 aa = aspiration_type ,
79577996 xs = f"{ x_position :05} " ,
79587997 xd = x_direction ,
@@ -8038,6 +8077,7 @@ async def dispense_core_96(
80388077 surface_following_distance_during_mixing : int = 0 ,
80398078 pull_out_distance_to_take_transport_air_in_function_without_lld : int = 50 ,
80408079 tube_2nd_section_height_measured_from_zm : int = 0 ,
8080+ _bypass_fw_wait : bool = False ,
80418081 ):
80428082 """Dispensing of liquid using CoRe 96
80438083
@@ -8227,6 +8267,8 @@ async def dispense_core_96(
82278267 return await self .send_command (
82288268 module = "C0" ,
82298269 command = "ED" ,
8270+ read_timeout = max (300 , self .read_timeout ),
8271+ wait = not _bypass_fw_wait ,
82308272 da = dispensing_mode ,
82318273 xs = f"{ x_position :05} " ,
82328274 xd = x_direction ,
0 commit comments