From 2760918da2d5a4945662ab6c011f0fd51d05dc64 Mon Sep 17 00:00:00 2001 From: xbtu2 Date: Fri, 20 Feb 2026 17:00:53 -0800 Subject: [PATCH 1/6] add corning fb plate compute volume --- pylabrobot/resources/corning/plates.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pylabrobot/resources/corning/plates.py b/pylabrobot/resources/corning/plates.py index 7b2d5e802aa..57782aa6276 100644 --- a/pylabrobot/resources/corning/plates.py +++ b/pylabrobot/resources/corning/plates.py @@ -1,7 +1,9 @@ """ Corning plates. """ from pylabrobot.resources.height_volume_functions import ( + calculate_liquid_height_container_1segment_round_fbottom, calculate_liquid_height_in_container_2segments_square_vbottom, + calculate_liquid_volume_container_1segment_round_fbottom, calculate_liquid_volume_container_2segments_square_vbottom, ) from pylabrobot.resources.plate import Lid, Plate @@ -58,6 +60,8 @@ def Cor_96_wellplate_360ul_Fb(name: str, with_lid: bool = False) -> Plate: bottom_type=WellBottomType.FLAT, cross_section_type=CrossSectionType.CIRCLE, max_volume=360, + compute_volume_from_height=_compute_volume_from_height_Cor_96_wellplate_360ul_Fb, + compute_height_from_volume=_compute_height_from_volume_Cor_96_wellplate_360ul_Fb, ), ) @@ -77,6 +81,19 @@ def Cor_96_wellplate_360ul_Fb_Lid(name: str) -> Lid: ) +# Volume-height functions +def _compute_volume_from_height_Cor_96_wellplate_360ul_Fb(h: float) -> float: + return calculate_liquid_volume_container_1segment_round_fbottom( + d=6.86, h_cylinder=10.67, liquid_height=h + ) + + +def _compute_height_from_volume_Cor_96_wellplate_360ul_Fb(liquid_volume: float) -> float: + return calculate_liquid_height_container_1segment_round_fbottom( + d=6.86, h_cylinder=10.67, liquid_volume=liquid_volume + ) + + # Previous names in PLR: def Cos_96_EZWash(name: str, with_lid: bool = False) -> Plate: raise ValueError("Deprecated. You probably want to use Cor_96_wellplate_360ul_Fb instead.") From a1ab2c76d3d63f49949f6f35b4f0c85ffe1df5b3 Mon Sep 17 00:00:00 2001 From: xbtu2 Date: Fri, 20 Feb 2026 17:11:02 -0800 Subject: [PATCH 2/6] fix spectramax reliability --- .../molecular_devices/backend.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/pylabrobot/plate_reading/molecular_devices/backend.py b/pylabrobot/plate_reading/molecular_devices/backend.py index ca94a084a71..3ce05cff8ee 100644 --- a/pylabrobot/plate_reading/molecular_devices/backend.py +++ b/pylabrobot/plate_reading/molecular_devices/backend.py @@ -286,7 +286,11 @@ async def send_command( if raw_response.count(RES_TERM_CHAR) >= num_res_fields: break logger.debug("[plate reader] Command: %s, Response: %s", command, raw_response) - response = raw_response.decode("utf-8").strip().split(RES_TERM_CHAR.decode()) + response = ( + raw_response.decode("utf-8", errors="replace") + .strip() + .split(RES_TERM_CHAR.decode()) + ) response = [r.strip() for r in response if r.strip() != ""] self._parse_basic_errors(response, command) return response @@ -313,7 +317,7 @@ def _parse_basic_errors(self, response: List[str], command: str) -> None: f"Command '{command}' failed with unparsable error: {response[0]}" ) - if "OK" not in response[0]: + if not any("OK" in r for r in response): raise MolecularDevicesError(f"Command '{command}' failed with response: {response}") if "warning" in response[0].lower(): logger.warning("Warning for command '%s': %s", command, response) @@ -326,19 +330,27 @@ async def close(self, plate: Optional[Plate] = None) -> None: async def get_status(self) -> List[str]: res = await self.send_command("!STATUS") - return res[1].split() + return res[1].split() if len(res) > 1 else res async def read_error_log(self) -> str: res = await self.send_command("!ERROR") - return res[1] + return res[1] if len(res) > 1 else res[0] async def clear_error_log(self) -> None: await self.send_command("!CLEAR ERROR") async def get_temperature(self) -> Tuple[float, float]: res = await self.send_command("!TEMP") - parts = res[1].split() - return (float(parts[1]), float(parts[0])) # current, set_point + if len(res) > 1: + parts = res[1].split() + else: + parts = res[0].replace("OK", "").split() + + if len(parts) >= 2: + return (float(parts[1]), float(parts[0])) # current, set_point + elif len(parts) == 1: + return (float(parts[0]), float(parts[0])) + return (0.0, 0.0) async def set_temperature(self, temperature: float) -> None: if not (0 <= temperature <= 45): From dcae69ab4016a40abfb61f3bd4c65906fd35dd69 Mon Sep 17 00:00:00 2001 From: xbtu2 Date: Fri, 20 Feb 2026 17:24:19 -0800 Subject: [PATCH 3/6] fix format --- pylabrobot/plate_reading/molecular_devices/backend.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pylabrobot/plate_reading/molecular_devices/backend.py b/pylabrobot/plate_reading/molecular_devices/backend.py index 3ce05cff8ee..4202ac52bb4 100644 --- a/pylabrobot/plate_reading/molecular_devices/backend.py +++ b/pylabrobot/plate_reading/molecular_devices/backend.py @@ -286,11 +286,7 @@ async def send_command( if raw_response.count(RES_TERM_CHAR) >= num_res_fields: break logger.debug("[plate reader] Command: %s, Response: %s", command, raw_response) - response = ( - raw_response.decode("utf-8", errors="replace") - .strip() - .split(RES_TERM_CHAR.decode()) - ) + response = raw_response.decode("utf-8", errors="replace").strip().split(RES_TERM_CHAR.decode()) response = [r.strip() for r in response if r.strip() != ""] self._parse_basic_errors(response, command) return response From 27672f4a76c509c02378b6b31afe6ce2f31a4705 Mon Sep 17 00:00:00 2001 From: xbtu2 Date: Mon, 23 Feb 2026 11:38:02 -0800 Subject: [PATCH 4/6] remove unnecessary fixes --- .../plate_reading/molecular_devices/backend.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pylabrobot/plate_reading/molecular_devices/backend.py b/pylabrobot/plate_reading/molecular_devices/backend.py index 4202ac52bb4..8b246adea9d 100644 --- a/pylabrobot/plate_reading/molecular_devices/backend.py +++ b/pylabrobot/plate_reading/molecular_devices/backend.py @@ -326,11 +326,17 @@ async def close(self, plate: Optional[Plate] = None) -> None: async def get_status(self) -> List[str]: res = await self.send_command("!STATUS") - return res[1].split() if len(res) > 1 else res + if len(res) > 1: + return res[1].split() + else: + raise ValueError(f"Could not parse status from response: {res}") async def read_error_log(self) -> str: res = await self.send_command("!ERROR") - return res[1] if len(res) > 1 else res[0] + if len(res) > 1: + return res[1].split() + else: + raise ValueError(f"Could not parse error log from response: {res}") async def clear_error_log(self) -> None: await self.send_command("!CLEAR ERROR") @@ -344,9 +350,8 @@ async def get_temperature(self) -> Tuple[float, float]: if len(parts) >= 2: return (float(parts[1]), float(parts[0])) # current, set_point - elif len(parts) == 1: - return (float(parts[0]), float(parts[0])) - return (0.0, 0.0) + else: + raise ValueError(f"Could not parse temperature from response: {res}") async def set_temperature(self, temperature: float) -> None: if not (0 <= temperature <= 45): From 4ec33f392d9ec163314f51e679d9b9400bd9414b Mon Sep 17 00:00:00 2001 From: xbtu2 Date: Mon, 23 Feb 2026 11:43:28 -0800 Subject: [PATCH 5/6] fix typechecking --- pylabrobot/plate_reading/molecular_devices/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylabrobot/plate_reading/molecular_devices/backend.py b/pylabrobot/plate_reading/molecular_devices/backend.py index 8b246adea9d..4092d773c3b 100644 --- a/pylabrobot/plate_reading/molecular_devices/backend.py +++ b/pylabrobot/plate_reading/molecular_devices/backend.py @@ -331,7 +331,7 @@ async def get_status(self) -> List[str]: else: raise ValueError(f"Could not parse status from response: {res}") - async def read_error_log(self) -> str: + async def read_error_log(self) -> List[str]: res = await self.send_command("!ERROR") if len(res) > 1: return res[1].split() From a18895d60afce2751c5276a1bcf7663c60f616b7 Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Mon, 23 Feb 2026 11:47:44 -0800 Subject: [PATCH 6/6] Remove else after return/raise Co-Authored-By: Claude Opus 4.6 --- pylabrobot/plate_reading/molecular_devices/backend.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pylabrobot/plate_reading/molecular_devices/backend.py b/pylabrobot/plate_reading/molecular_devices/backend.py index 4092d773c3b..0ecf1a935dd 100644 --- a/pylabrobot/plate_reading/molecular_devices/backend.py +++ b/pylabrobot/plate_reading/molecular_devices/backend.py @@ -328,15 +328,13 @@ async def get_status(self) -> List[str]: res = await self.send_command("!STATUS") if len(res) > 1: return res[1].split() - else: - raise ValueError(f"Could not parse status from response: {res}") + raise ValueError(f"Could not parse status from response: {res}") async def read_error_log(self) -> List[str]: res = await self.send_command("!ERROR") if len(res) > 1: return res[1].split() - else: - raise ValueError(f"Could not parse error log from response: {res}") + raise ValueError(f"Could not parse error log from response: {res}") async def clear_error_log(self) -> None: await self.send_command("!CLEAR ERROR") @@ -350,8 +348,7 @@ async def get_temperature(self) -> Tuple[float, float]: if len(parts) >= 2: return (float(parts[1]), float(parts[0])) # current, set_point - else: - raise ValueError(f"Could not parse temperature from response: {res}") + raise ValueError(f"Could not parse temperature from response: {res}") async def set_temperature(self, temperature: float) -> None: if not (0 <= temperature <= 45):