From a3481de468e41520aafae574f0040bdec3f63ae2 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 09:58:57 +0000 Subject: [PATCH 1/7] add y_drive_mm_per_increment information --- .../liquid_handling/backends/hamilton/STAR.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index e75aa3bb26..370aa8fdc1 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7176,15 +7176,25 @@ async def stop_temperature_control_at_hhc(self, device_number: int): # -------------- Extra - Probing labware with STAR - making STAR into a CMM -------------- + y_drive_mm_per_increment= 0.046302082 z_drive_mm_per_increment = 0.01072765 + @staticmethod + def mm_to_y_drive_increment(value_mm: float) -> int: + return round(value_mm / STAR.y_drive_mm_per_increment) + + @staticmethod + def y_drive_increment_to_mm(value_mm: int) -> float: + return round(value_mm * STAR.y_drive_mm_per_increment, 2) + @staticmethod def mm_to_z_drive_increment(value_mm: float) -> int: return round(value_mm / STAR.z_drive_mm_per_increment) @staticmethod - def z_drive_increment_to_mm(value_mm: int) -> float: - return round(value_mm * STAR.z_drive_mm_per_increment, 2) + def z_drive_increment_to_mm(value_increments: int) -> float: + return round(value_increments * STAR.z_drive_mm_per_increment, 2) + async def clld_probe_z_height_using_channel( self, @@ -7206,7 +7216,7 @@ async def clld_probe_z_height_using_channel( Args: channel_idx: The index of the channel to use for probing. Backmost channel = 0. lowest_immers_pos: The lowest immersion position in mm. - start_pos_lld_search: The start position for z-touch search in mm. + start_pos_lld_search: The start position for clld search in mm. channel_speed: The speed of channel movement in mm/sec. channel_acceleration: The acceleration of the channel in mm/sec**2. detection_edge: The edge steepness at capacitive LLD detection. From 827771fdec90a8db053f565fb474143ef97e5695 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 16:14:30 +0000 Subject: [PATCH 2/7] create clld_probe_y_position_using_channel --- .../liquid_handling/backends/hamilton/STAR.py | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index 370aa8fdc1..95c47c7b4b 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7294,6 +7294,178 @@ async def clld_probe_z_height_using_channel( return result_in_mm + + async def clld_probe_y_position_using_channel( + self, + channel_idx: int, # 0-based indexing of channels! + probing_direction: Literal["forward", "backward"], + start_pos_search: Optional[float] = None, # mm + end_pos_search: Optional[float] = None, # mm + channel_speed: float = 10.0, # mm/sec + channel_acceleration_int: Literal[1, 2, 3, 4] = 4, # * 5_000 steps/sec**2 == 926 mm/sec**2 + detection_edge: int = 10, + current_limit_int: Literal[1, 2, 3, 4, 5, 6, 7] = 7, + post_detection_dist: float = 2.0, # mm + ) -> float: + """ + Probes the y-position at which a conductive material is detected using + the channel's capacitive Liquid Level Detection (cLLD) capability. + + This method aims to provide safe probing within defined boundaries to + avoid collisions or damage to the system. It is specifically designed + for conductive materials. + + Args: + channel_idx (int): Index of the channel to use for probing (0-based). + The backmost channel is 0. + probing_direction (Literal["forward", "backward"]): Direction to move + the channel during probing. "forward" increases y-position, + "backward" decreases y-position. + start_pos_search (float, optional): Initial y-position for the search + (in mm). Defaults to the current y-position of the channel. + end_pos_search (float, optional): Final y-position for the search (in mm). + Defaults to the maximum y-position the channel can move to safely. + channel_speed (float): Speed of the channel's movement (in mm/sec). + Defaults to 10.0 mm/sec (i.e. slow default for safety). + channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level, + corresponding to 1–4 (* 5,000 steps/sec²). Defaults to 4. + detection_edge (int): Steepness of the edge for capacitive detection. + Must be between 0 and 1023. Defaults to 10. + current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level, + from 1 to 7. Defaults to 7. + post_detection_dist (float): Distance to move away from the detected + material after detection (in mm). Defaults to 2.0 mm. + + Returns: + float: The detected y-position of the conductive material (in mm). + + Raises: + ValueError: + - If the probing direction is invalid. + - If the specified start or end positions are outside the safe range. + - If no conductive material is detected during the probing process. + """ + + assert probing_direction in ["forward", "backward"], ( + f"Probing direction must be either 'forward' or 'backward', is {probing_direction}." + ) + + # Anti-channel-crash feature + if channel_idx > 0: + channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx-1) + else: + channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(15_000) + if channel_idx < (self.num_channels-1): + channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx+1) + else: + channel_idx_plus_one_y_pos = 6 + # Insight: STAR machines appear to lose connection to a channel below y-position=6 mm + + max_safe_upper_y_pos = channel_idx_minus_one_y_pos - 9 + max_safe_lower_y_pos = channel_idx_plus_one_y_pos + 9 if channel_idx_plus_one_y_pos != 0 else 6 + + # Enable safe start and end positions + if start_pos_search: + assert max_safe_lower_y_pos <= start_pos_search <= max_safe_upper_y_pos, ( + f"Start position for y search must be between \n{max_safe_lower_y_pos} and" + + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." + ) + await self.move_channel_y(y=start_pos_search, channel=channel_idx) + + if end_pos_search: + assert max_safe_lower_y_pos <= end_pos_search <= max_safe_upper_y_pos, ( + f"End position for y search must be between \n{max_safe_lower_y_pos} and" + + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." + ) + + current_channel_y_pos = await self.request_y_pos_channel_n(channel_idx) + + # Set safe y-search end position based on the probing direction + if probing_direction == "forward": + max_y_search_pos = end_pos_search or max_safe_upper_y_pos + if max_y_search_pos < current_channel_y_pos: + raise ValueError( + f"Channel {channel_idx} cannot move forwards: " + f"End position = {max_y_search_pos} < current position = {current_channel_y_pos}" + f"\nDid you mean to move backwards?" + ) + else: # probing_direction == "backwards" + max_y_search_pos = end_pos_search or max_safe_lower_y_pos + if max_y_search_pos > current_channel_y_pos: + raise ValueError( + f"Channel {channel_idx} cannot move backwards: " + f"End position = {max_y_search_pos} > current position = {current_channel_y_pos}" + f"\nDid you mean to move forwards?" + ) + + # Convert mm to increments + max_y_search_pos_increments = STAR.mm_to_y_drive_increment(max_y_search_pos) + channel_speed_increments = STAR.mm_to_y_drive_increment(channel_speed) + + # Machine-compatability check of calculated parameters + assert 0 <= max_y_search_pos_increments <= 15_000, ( + f"Maximum y search position must be between \n0 and" + + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR + ) + assert 20 <= channel_speed_increments <= 8_000, ( + f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" # STAR + + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR + ) + assert channel_acceleration_int in [1, 2, 3, 4], ( + f"Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" + + f", is {channel_speed} mm/sec" + ) + assert ( + 0 <= detection_edge <= 1_0234 + ), "Edge steepness at capacitive LLD detection must be between 0 and 1023" + assert current_limit_int in [1, 2, 3, 4, 5, 6, 7], ( + f"Currrent limit must be any([1, 2, 3, 4], 5, 6, 7), is {channel_speed} mm/sec" + ) + + max_y_search_pos_str = f"{max_y_search_pos_increments:05}" + channel_speed_str = f"{channel_speed_increments:04}" + channel_acceleration_str = f"{channel_acceleration_int}" + detection_edge_str = f"{detection_edge:04}" + current_limit_str = f"{current_limit_int}" + + # Move channel for cLLD (Note: does not return detected y-position!) + await self.send_command( + module=f"P{channel_idx+1}", + command="YL", + ya=max_y_search_pos_str, # Maximum search position [steps] + gt= detection_edge_str, # Edge steepness at capacitive LLD detection + gl= f"{0:04}", # Offset after edge detection -> always 0 to measure y-pos! + yv=channel_speed_str, # Max speed [steps/second] + yr=channel_acceleration_str, # Acceleration ramp [yr * 5_000 steps/second**2] + yw=current_limit_str, # Current limit + ) + + detected_material_y_pos = await self.request_y_pos_channel_n(channel_idx) + + # Dynamically evaluate post-detection distance to avoid crashes + if probing_direction == "forward": + if channel_idx == self.num_channels - 1: # safe default + adjacent_y_pos = 6 + else: # next channel + adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) + + max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9 + move_target = detected_material_y_pos - min(post_detection_dist, max_safe_y_mov_dist_post_detection) + + else: # probing_direction == "backwards" + if channel_idx == 0: # safe default + adjacent_y_pos = STAR.y_drive_increment_to_mm(15_000) + else: # previous channel + adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) + + max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9 + move_target = detected_material_y_pos + min(post_detection_dist, max_safe_y_mov_dist_post_detection) + + await self.move_channel_y(y=move_target, channel=channel_idx) + + return detected_material_y_pos + + async def request_tip_len_on_channel( self, channel_idx: int, # 0-based indexing of channels! From 3bb196804d4a935ea23f76896d8785bb6b5ac136 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 16:17:46 +0000 Subject: [PATCH 3/7] `make format` --- .../liquid_handling/backends/hamilton/STAR.py | 162 +++++++++--------- 1 file changed, 82 insertions(+), 80 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index 95c47c7b4b..e19e37245a 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7176,7 +7176,7 @@ async def stop_temperature_control_at_hhc(self, device_number: int): # -------------- Extra - Probing labware with STAR - making STAR into a CMM -------------- - y_drive_mm_per_increment= 0.046302082 + y_drive_mm_per_increment = 0.046302082 z_drive_mm_per_increment = 0.01072765 @staticmethod @@ -7195,7 +7195,6 @@ def mm_to_z_drive_increment(value_mm: float) -> int: def z_drive_increment_to_mm(value_increments: int) -> float: return round(value_increments * STAR.z_drive_mm_per_increment, 2) - async def clld_probe_z_height_using_channel( self, channel_idx: int, # 0-based indexing of channels! @@ -7294,7 +7293,6 @@ async def clld_probe_z_height_using_channel( return result_in_mm - async def clld_probe_y_position_using_channel( self, channel_idx: int, # 0-based indexing of channels! @@ -7306,110 +7304,111 @@ async def clld_probe_y_position_using_channel( detection_edge: int = 10, current_limit_int: Literal[1, 2, 3, 4, 5, 6, 7] = 7, post_detection_dist: float = 2.0, # mm - ) -> float: + ) -> float: """ - Probes the y-position at which a conductive material is detected using + Probes the y-position at which a conductive material is detected using the channel's capacitive Liquid Level Detection (cLLD) capability. This method aims to provide safe probing within defined boundaries to - avoid collisions or damage to the system. It is specifically designed + avoid collisions or damage to the system. It is specifically designed for conductive materials. Args: channel_idx (int): Index of the channel to use for probing (0-based). The backmost channel is 0. - probing_direction (Literal["forward", "backward"]): Direction to move - the channel during probing. "forward" increases y-position, + probing_direction (Literal["forward", "backward"]): Direction to move + the channel during probing. "forward" increases y-position, "backward" decreases y-position. - start_pos_search (float, optional): Initial y-position for the search + start_pos_search (float, optional): Initial y-position for the search (in mm). Defaults to the current y-position of the channel. - end_pos_search (float, optional): Final y-position for the search (in mm). + end_pos_search (float, optional): Final y-position for the search (in mm). Defaults to the maximum y-position the channel can move to safely. - channel_speed (float): Speed of the channel's movement (in mm/sec). + channel_speed (float): Speed of the channel's movement (in mm/sec). Defaults to 10.0 mm/sec (i.e. slow default for safety). - channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level, + channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level, corresponding to 1–4 (* 5,000 steps/sec²). Defaults to 4. detection_edge (int): Steepness of the edge for capacitive detection. Must be between 0 and 1023. Defaults to 10. - current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level, + current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level, from 1 to 7. Defaults to 7. - post_detection_dist (float): Distance to move away from the detected + post_detection_dist (float): Distance to move away from the detected material after detection (in mm). Defaults to 2.0 mm. Returns: float: The detected y-position of the conductive material (in mm). Raises: - ValueError: + ValueError: - If the probing direction is invalid. - If the specified start or end positions are outside the safe range. - If no conductive material is detected during the probing process. """ - assert probing_direction in ["forward", "backward"], ( - f"Probing direction must be either 'forward' or 'backward', is {probing_direction}." - ) + assert probing_direction in [ + "forward", + "backward", + ], f"Probing direction must be either 'forward' or 'backward', is {probing_direction}." # Anti-channel-crash feature if channel_idx > 0: - channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx-1) + channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) else: - channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(15_000) - if channel_idx < (self.num_channels-1): - channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx+1) + channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(15_000) + if channel_idx < (self.num_channels - 1): + channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) else: - channel_idx_plus_one_y_pos = 6 - # Insight: STAR machines appear to lose connection to a channel below y-position=6 mm - + channel_idx_plus_one_y_pos = 6 + # Insight: STAR machines appear to lose connection to a channel below y-position=6 mm + max_safe_upper_y_pos = channel_idx_minus_one_y_pos - 9 max_safe_lower_y_pos = channel_idx_plus_one_y_pos + 9 if channel_idx_plus_one_y_pos != 0 else 6 - + # Enable safe start and end positions if start_pos_search: - assert max_safe_lower_y_pos <= start_pos_search <= max_safe_upper_y_pos, ( - f"Start position for y search must be between \n{max_safe_lower_y_pos} and" - + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." - ) - await self.move_channel_y(y=start_pos_search, channel=channel_idx) + assert max_safe_lower_y_pos <= start_pos_search <= max_safe_upper_y_pos, ( + f"Start position for y search must be between \n{max_safe_lower_y_pos} and" + + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." + ) + await self.move_channel_y(y=start_pos_search, channel=channel_idx) if end_pos_search: - assert max_safe_lower_y_pos <= end_pos_search <= max_safe_upper_y_pos, ( - f"End position for y search must be between \n{max_safe_lower_y_pos} and" - + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." - ) - + assert max_safe_lower_y_pos <= end_pos_search <= max_safe_upper_y_pos, ( + f"End position for y search must be between \n{max_safe_lower_y_pos} and" + + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." + ) + current_channel_y_pos = await self.request_y_pos_channel_n(channel_idx) # Set safe y-search end position based on the probing direction if probing_direction == "forward": - max_y_search_pos = end_pos_search or max_safe_upper_y_pos - if max_y_search_pos < current_channel_y_pos: - raise ValueError( - f"Channel {channel_idx} cannot move forwards: " - f"End position = {max_y_search_pos} < current position = {current_channel_y_pos}" - f"\nDid you mean to move backwards?" - ) + max_y_search_pos = end_pos_search or max_safe_upper_y_pos + if max_y_search_pos < current_channel_y_pos: + raise ValueError( + f"Channel {channel_idx} cannot move forwards: " + f"End position = {max_y_search_pos} < current position = {current_channel_y_pos}" + f"\nDid you mean to move backwards?" + ) else: # probing_direction == "backwards" - max_y_search_pos = end_pos_search or max_safe_lower_y_pos - if max_y_search_pos > current_channel_y_pos: - raise ValueError( - f"Channel {channel_idx} cannot move backwards: " - f"End position = {max_y_search_pos} > current position = {current_channel_y_pos}" - f"\nDid you mean to move forwards?" - ) + max_y_search_pos = end_pos_search or max_safe_lower_y_pos + if max_y_search_pos > current_channel_y_pos: + raise ValueError( + f"Channel {channel_idx} cannot move backwards: " + f"End position = {max_y_search_pos} > current position = {current_channel_y_pos}" + f"\nDid you mean to move forwards?" + ) # Convert mm to increments max_y_search_pos_increments = STAR.mm_to_y_drive_increment(max_y_search_pos) channel_speed_increments = STAR.mm_to_y_drive_increment(channel_speed) - + # Machine-compatability check of calculated parameters assert 0 <= max_y_search_pos_increments <= 15_000, ( f"Maximum y search position must be between \n0 and" - + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR + + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR ) assert 20 <= channel_speed_increments <= 8_000, ( - f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" # STAR - + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR + f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" # STAR + + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR ) assert channel_acceleration_int in [1, 2, 3, 4], ( f"Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" @@ -7430,42 +7429,45 @@ async def clld_probe_y_position_using_channel( # Move channel for cLLD (Note: does not return detected y-position!) await self.send_command( - module=f"P{channel_idx+1}", - command="YL", - ya=max_y_search_pos_str, # Maximum search position [steps] - gt= detection_edge_str, # Edge steepness at capacitive LLD detection - gl= f"{0:04}", # Offset after edge detection -> always 0 to measure y-pos! - yv=channel_speed_str, # Max speed [steps/second] - yr=channel_acceleration_str, # Acceleration ramp [yr * 5_000 steps/second**2] - yw=current_limit_str, # Current limit - ) + module=f"P{channel_idx+1}", + command="YL", + ya=max_y_search_pos_str, # Maximum search position [steps] + gt=detection_edge_str, # Edge steepness at capacitive LLD detection + gl=f"{0:04}", # Offset after edge detection -> always 0 to measure y-pos! + yv=channel_speed_str, # Max speed [steps/second] + yr=channel_acceleration_str, # Acceleration ramp [yr * 5_000 steps/second**2] + yw=current_limit_str, # Current limit + ) detected_material_y_pos = await self.request_y_pos_channel_n(channel_idx) # Dynamically evaluate post-detection distance to avoid crashes if probing_direction == "forward": - if channel_idx == self.num_channels - 1: # safe default - adjacent_y_pos = 6 - else: # next channel - adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) - - max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9 - move_target = detected_material_y_pos - min(post_detection_dist, max_safe_y_mov_dist_post_detection) - + if channel_idx == self.num_channels - 1: # safe default + adjacent_y_pos = 6 + else: # next channel + adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) + + max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9 + move_target = detected_material_y_pos - min( + post_detection_dist, max_safe_y_mov_dist_post_detection + ) + else: # probing_direction == "backwards" - if channel_idx == 0: # safe default - adjacent_y_pos = STAR.y_drive_increment_to_mm(15_000) - else: # previous channel - adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) - - max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9 - move_target = detected_material_y_pos + min(post_detection_dist, max_safe_y_mov_dist_post_detection) - + if channel_idx == 0: # safe default + adjacent_y_pos = STAR.y_drive_increment_to_mm(15_000) + else: # previous channel + adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) + + max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9 + move_target = detected_material_y_pos + min( + post_detection_dist, max_safe_y_mov_dist_post_detection + ) + await self.move_channel_y(y=move_target, channel=channel_idx) return detected_material_y_pos - async def request_tip_len_on_channel( self, channel_idx: int, # 0-based indexing of channels! From 228b96b6a05b11a7229059094e9e01485c0db070 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 16:57:01 +0000 Subject: [PATCH 4/7] fix type checking --- pylabrobot/liquid_handling/backends/hamilton/STAR.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index e19e37245a..cbcee8e332 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7403,7 +7403,7 @@ async def clld_probe_y_position_using_channel( # Machine-compatability check of calculated parameters assert 0 <= max_y_search_pos_increments <= 15_000, ( - f"Maximum y search position must be between \n0 and" + "Maximum y search position must be between \n0 and" + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR ) assert 20 <= channel_speed_increments <= 8_000, ( @@ -7411,7 +7411,7 @@ async def clld_probe_y_position_using_channel( + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR ) assert channel_acceleration_int in [1, 2, 3, 4], ( - f"Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" + "Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" + f", is {channel_speed} mm/sec" ) assert ( @@ -7444,11 +7444,11 @@ async def clld_probe_y_position_using_channel( # Dynamically evaluate post-detection distance to avoid crashes if probing_direction == "forward": if channel_idx == self.num_channels - 1: # safe default - adjacent_y_pos = 6 + adjacent_y_pos = 6.0 else: # next channel adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) - max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9 + max_safe_y_mov_dist_post_detection = detected_material_y_pos - adjacent_y_pos - 9.0 move_target = detected_material_y_pos - min( post_detection_dist, max_safe_y_mov_dist_post_detection ) @@ -7459,7 +7459,7 @@ async def clld_probe_y_position_using_channel( else: # previous channel adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) - max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9 + max_safe_y_mov_dist_post_detection = adjacent_y_pos - detected_material_y_pos - 9.0 move_target = detected_material_y_pos + min( post_detection_dist, max_safe_y_mov_dist_post_detection ) From 59375dfba76a5da120c208183aa3772925fc122a Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Mon, 6 Jan 2025 17:00:43 +0000 Subject: [PATCH 5/7] silly `make format`-forced multi-line split of list --- pylabrobot/liquid_handling/backends/hamilton/STAR.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index cbcee8e332..c10e6e2d2f 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7417,9 +7417,15 @@ async def clld_probe_y_position_using_channel( assert ( 0 <= detection_edge <= 1_0234 ), "Edge steepness at capacitive LLD detection must be between 0 and 1023" - assert current_limit_int in [1, 2, 3, 4, 5, 6, 7], ( - f"Currrent limit must be any([1, 2, 3, 4], 5, 6, 7), is {channel_speed} mm/sec" - ) + assert current_limit_int in [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + ], f"Currrent limit must be in [1, 2, 3, 4, 5, 6, 7], is {channel_speed} mm/sec" max_y_search_pos_str = f"{max_y_search_pos_increments:05}" channel_speed_str = f"{channel_speed_increments:04}" From 3cf2f4f0fca70b1d235c915d745275abedc03025 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Tue, 7 Jan 2025 16:21:21 +0000 Subject: [PATCH 6/7] update safe back position / protect channel_0 --- .../liquid_handling/backends/hamilton/STAR.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index c10e6e2d2f..3c67ac2a78 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7328,7 +7328,7 @@ async def clld_probe_y_position_using_channel( channel_acceleration_int (Literal[1, 2, 3, 4]): Acceleration level, corresponding to 1–4 (* 5,000 steps/sec²). Defaults to 4. detection_edge (int): Steepness of the edge for capacitive detection. - Must be between 0 and 1023. Defaults to 10. + Must be between 0 and 1024. Defaults to 10. current_limit_int (Literal[1, 2, 3, 4, 5, 6, 7]): Current limit level, from 1 to 7. Defaults to 7. post_detection_dist (float): Distance to move away from the detected @@ -7353,7 +7353,7 @@ async def clld_probe_y_position_using_channel( if channel_idx > 0: channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) else: - channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(15_000) + channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(13_714) + 9 # y-position=635 mm if channel_idx < (self.num_channels - 1): channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) else: @@ -7366,14 +7366,14 @@ async def clld_probe_y_position_using_channel( # Enable safe start and end positions if start_pos_search: assert max_safe_lower_y_pos <= start_pos_search <= max_safe_upper_y_pos, ( - f"Start position for y search must be between \n{max_safe_lower_y_pos} and" + f"Start position for y search must be between \n{max_safe_lower_y_pos} and " + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." ) await self.move_channel_y(y=start_pos_search, channel=channel_idx) if end_pos_search: assert max_safe_lower_y_pos <= end_pos_search <= max_safe_upper_y_pos, ( - f"End position for y search must be between \n{max_safe_lower_y_pos} and" + f"End position for y search must be between \n{max_safe_lower_y_pos} and " + f"{max_safe_upper_y_pos} mm, is {end_pos_search} mm. Otherwise channel will crash." ) @@ -7402,13 +7402,13 @@ async def clld_probe_y_position_using_channel( channel_speed_increments = STAR.mm_to_y_drive_increment(channel_speed) # Machine-compatability check of calculated parameters - assert 0 <= max_y_search_pos_increments <= 15_000, ( + assert 0 <= max_y_search_pos_increments <= 13_714, ( "Maximum y search position must be between \n0 and" - + f"{STAR.y_drive_increment_to_mm(15_000)} mm, is {max_y_search_pos_increments} mm" # STAR + + f"{STAR.y_drive_increment_to_mm(13_714)+9} mm, is {max_y_search_pos_increments} mm" ) assert 20 <= channel_speed_increments <= 8_000, ( - f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" # STAR - + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" # STAR + f"LLD search speed must be between \n{STAR.y_drive_increment_to_mm(20)}" + + f"and {STAR.y_drive_increment_to_mm(8_000)} mm/sec, is {channel_speed} mm/sec" ) assert channel_acceleration_int in [1, 2, 3, 4], ( "Channel speed must be in [1, 2, 3, 4] (* 5_000 steps/sec**2)" @@ -7461,7 +7461,7 @@ async def clld_probe_y_position_using_channel( else: # probing_direction == "backwards" if channel_idx == 0: # safe default - adjacent_y_pos = STAR.y_drive_increment_to_mm(15_000) + adjacent_y_pos = STAR.y_drive_increment_to_mm(13_714)+9 # y-position=635 mm else: # previous channel adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) From 14eb82ec5fa1a28826720c378471bc7f9af14d10 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Wed, 8 Jan 2025 09:13:54 +0000 Subject: [PATCH 7/7] `make format` --- pylabrobot/liquid_handling/backends/hamilton/STAR.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/hamilton/STAR.py b/pylabrobot/liquid_handling/backends/hamilton/STAR.py index 3c67ac2a78..e4372020e4 100644 --- a/pylabrobot/liquid_handling/backends/hamilton/STAR.py +++ b/pylabrobot/liquid_handling/backends/hamilton/STAR.py @@ -7353,7 +7353,7 @@ async def clld_probe_y_position_using_channel( if channel_idx > 0: channel_idx_minus_one_y_pos = await self.request_y_pos_channel_n(channel_idx - 1) else: - channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(13_714) + 9 # y-position=635 mm + channel_idx_minus_one_y_pos = STAR.y_drive_increment_to_mm(13_714) + 9 # y-position=635 mm if channel_idx < (self.num_channels - 1): channel_idx_plus_one_y_pos = await self.request_y_pos_channel_n(channel_idx + 1) else: @@ -7461,7 +7461,7 @@ async def clld_probe_y_position_using_channel( else: # probing_direction == "backwards" if channel_idx == 0: # safe default - adjacent_y_pos = STAR.y_drive_increment_to_mm(13_714)+9 # y-position=635 mm + adjacent_y_pos = STAR.y_drive_increment_to_mm(13_714) + 9 # y-position=635 mm else: # previous channel adjacent_y_pos = await self.request_y_pos_channel_n(channel_idx - 1)