From a1172e679664e7bdcf66d788845b51e4e71640b0 Mon Sep 17 00:00:00 2001 From: Dario Cambie Date: Thu, 4 Nov 2021 10:27:51 +0100 Subject: [PATCH 1/2] Port all test to pint version (except withdrawing commands, pump for testing was infuse only) --- .../devices/Harvard_Apparatus/HA_elite11.py | 22 +++++++++- tests/test_hw_elite11.py | 42 ++++++++++++------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/flowchem/devices/Harvard_Apparatus/HA_elite11.py b/flowchem/devices/Harvard_Apparatus/HA_elite11.py index d4d3e37e..5ee355c6 100644 --- a/flowchem/devices/Harvard_Apparatus/HA_elite11.py +++ b/flowchem/devices/Harvard_Apparatus/HA_elite11.py @@ -458,6 +458,7 @@ def __init__( Elite11._io_instances.add(self.pump_io) # See above for details. self.address: int = address if address else None # type: ignore + self._version = None # Set in initialize self.name = f"Pump {self.pump_io.name}:{address}" if name is None else name # diameter and syringe volume - these will be set in initialize() - check values here though. @@ -521,7 +522,7 @@ async def initialize(self): self.pump_io._serial.write("\r\n".encode("ascii")) self.pump_io._serial.readline() prompt = self.pump_io._serial.readline() - self.address = int(prompt[0:2]) + self.address = 0 if prompt[0:2] == b":" else int(prompt[0:2]) self.log.debug(f"Address autodetected as {self.address}") await self.set_syringe_diameter(self._diameter) await self.set_syringe_volume(self._syringe_volume) @@ -535,7 +536,10 @@ async def initialize(self): self._withdraw_enabled = not pump_info.infuse_only # makes sure that a 'clean' pump is initialized. - await self.clear_volumes() + self._version = self.parse_version(await self.version()) + + if self._version[0] >= 3: + await self.clear_volumes() def ensure_withdraw_is_enabled(self): """ To be used on methods that need withdraw capabilities """ @@ -591,6 +595,13 @@ async def bound_rate_to_pump_limits(self, rate: AnyQuantity) -> float: return set_rate.to("ml/min").magnitude + def parse_version(self, version_text: str) -> Tuple[int, int, int]: + """ Extract semver from version string """ + + numbers = version_text.split(" ")[-1] + version_digits = numbers.split(".") + return int(version_digits[0]), int(version_digits[1]), int(version_digits[2]) + async def version(self) -> str: """ Returns the current firmware version reported by the pump """ return await self.send_command_and_read_reply( @@ -722,6 +733,9 @@ async def get_withdrawn_volume(self) -> str: async def clear_infused_volume(self): """ Reset the pump infused volume counter to 0 """ + if self._version[0] < 3: + warnings.warn("Command not supported by pump, update firmware!") + return await self.send_command_and_read_reply(Elite11Commands.CLEAR_INFUSED_VOLUME) async def clear_withdrawn_volume(self): @@ -732,6 +746,9 @@ async def clear_withdrawn_volume(self): async def clear_infused_withdrawn_volume(self): """ Reset both the pump infused and withdrawn volume counters to 0 """ self.ensure_withdraw_is_enabled() + if self._version[0] < 3: + warnings.warn("Command not supported by pump, update firmware!") + return await self.send_command_and_read_reply( Elite11Commands.CLEAR_INFUSED_WITHDRAWN_VOLUME ) @@ -740,6 +757,7 @@ async def clear_infused_withdrawn_volume(self): async def clear_volumes(self): """ Set all pump volumes to 0 """ await self.set_target_volume(0) + if self._withdraw_enabled: await self.clear_infused_withdrawn_volume() else: diff --git a/tests/test_hw_elite11.py b/tests/test_hw_elite11.py index c5284945..ca2b66ef 100644 --- a/tests/test_hw_elite11.py +++ b/tests/test_hw_elite11.py @@ -12,6 +12,7 @@ PumpStatus ) from flowchem.exceptions import DeviceError +from flowchem.units import flowchem_ureg async def move_infuse(pump): @@ -31,7 +32,7 @@ def event_loop(request): @pytest.fixture(scope="session") async def pump(): """ Change to match your hardware ;) """ - pump = Elite11.from_config(port="COM4", address=6, syringe_volume=5, diameter=20) + pump = Elite11.from_config(port="COM11", syringe_volume=5, diameter=20) await pump.initialize() return pump @@ -79,13 +80,14 @@ async def test_is_moving(pump: Elite11): @pytest.mark.HApump @pytest.mark.asyncio async def test_syringe_volume(pump: Elite11): - assert isinstance(await pump.get_syringe_volume(), (float, int)) await pump.set_syringe_volume(10) - assert await pump.get_syringe_volume() == 10 + assert await pump.get_syringe_volume() == "10 ml" await pump.set_syringe_volume(math.pi) - assert math.isclose(await pump.get_syringe_volume(), math.pi, abs_tol=10e-4) - await pump.set_syringe_volume(3.2e-05) - assert math.isclose(await pump.get_syringe_volume(), 3.2e-5) + vol = flowchem_ureg.Quantity(await pump.get_syringe_volume()).magnitude + assert math.isclose(vol, math.pi, abs_tol=10e-4) + await pump.set_syringe_volume(3e-05) + vol = flowchem_ureg.Quantity(await pump.get_syringe_volume()).magnitude + assert math.isclose(vol, 3e-5) await pump.set_syringe_volume(50) # Leave a sensible value otherwise other tests will fail! @@ -97,25 +99,29 @@ async def test_infusion_rate(pump: Elite11): assert await pump.get_infusion_rate() with pytest.warns(UserWarning): await pump.set_infusion_rate(121) - assert math.isclose(await pump.get_infusion_rate(), 12.49, rel_tol=0.01) + rate = flowchem_ureg.Quantity(await pump.get_infusion_rate()).magnitude + assert math.isclose(rate, 12.49, rel_tol=0.01) with pytest.warns(UserWarning): await pump.set_infusion_rate(0) - assert math.isclose(await pump.get_infusion_rate(), 1e-05, abs_tol=1e-5) + rate = flowchem_ureg.Quantity(await pump.get_infusion_rate()).magnitude + assert math.isclose(rate, 1e-05, abs_tol=1e-5) await pump.set_infusion_rate(math.pi) - assert math.isclose(await pump.get_infusion_rate(), math.pi, abs_tol=0.001) + rate = flowchem_ureg.Quantity(await pump.get_infusion_rate()).magnitude + assert math.isclose(rate, math.pi, abs_tol=0.001) @pytest.mark.HApump @pytest.mark.asyncio async def test_get_infused_volume(pump: Elite11): await pump.clear_volumes() - assert await pump.get_infused_volume() == 0 + assert await pump.get_infused_volume() == "0 ul" await pump.set_syringe_diameter(30) await pump.set_infusion_rate(5) await pump.set_target_volume(0.05) await pump.infuse_run() await asyncio.sleep(2) - assert math.isclose(await pump.get_infused_volume(), 0.05, abs_tol=1e-4) + vol = flowchem_ureg.Quantity(await pump.get_infused_volume()).to("ml").magnitude + assert math.isclose(vol, 0.05, abs_tol=1e-4) @pytest.mark.HApump @@ -126,7 +132,8 @@ async def test_get_withdrawn_volume(pump: Elite11): await pump.set_target_volume(0.1) await pump.withdraw_run() await asyncio.sleep(1) - assert await pump.get_withdrawn_volume() == 0.1 + vol = flowchem_ureg.Quantity(await pump.get_withdrawn_volume()).to("ml").magnitude + assert math.isclose(vol, 0.1, abs_tol=1e-4) @pytest.mark.HApump @@ -146,7 +153,7 @@ async def test_force(pump: Elite11): @pytest.mark.asyncio async def test_diameter(pump: Elite11): await pump.set_syringe_diameter(10) - assert await pump.get_syringe_diameter() == 10 + assert await pump.get_syringe_diameter() == "10.0000 mm" with pytest.warns(UserWarning): await pump.set_syringe_diameter(34) @@ -155,7 +162,8 @@ async def test_diameter(pump: Elite11): await pump.set_syringe_diameter(0.01) await pump.set_syringe_diameter(math.pi) - math.isclose(await pump.get_syringe_diameter(), math.pi) + dia = flowchem_ureg.Quantity(await pump.get_syringe_diameter()).magnitude + math.isclose(dia, math.pi) @pytest.mark.HApump @@ -163,6 +171,8 @@ async def test_diameter(pump: Elite11): async def test_target_volume(pump: Elite11): await pump.set_syringe_volume(10) await pump.set_target_volume(math.pi) - assert math.isclose(await pump.get_target_volume(), math.pi, abs_tol=10e-4) + vol = flowchem_ureg.Quantity(await pump.get_target_volume()).magnitude + assert math.isclose(vol, math.pi, abs_tol=10e-4) await pump.set_target_volume(1e-04) - assert math.isclose(await pump.get_target_volume(), 1e-4, abs_tol=10e-4) + vol = flowchem_ureg.Quantity(await pump.get_target_volume()).magnitude + assert math.isclose(vol, 1e-4, abs_tol=10e-4) From 7d8a0a81573cc092c74acc106ea8fbd5ec6165fd Mon Sep 17 00:00:00 2001 From: Dario Cambie Date: Mon, 22 Nov 2021 14:56:39 +0100 Subject: [PATCH 2/2] Fix Knauer pump read_errors() --- flowchem/components/devices/Knauer/AzuraCompactPump.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/flowchem/components/devices/Knauer/AzuraCompactPump.py b/flowchem/components/devices/Knauer/AzuraCompactPump.py index 90ad463a..428d62d3 100644 --- a/flowchem/components/devices/Knauer/AzuraCompactPump.py +++ b/flowchem/components/devices/Knauer/AzuraCompactPump.py @@ -3,6 +3,8 @@ """ import asyncio import warnings +from typing import List + from loguru import logger from enum import Enum @@ -101,7 +103,8 @@ async def initialize(self): def error_present(reply: str) -> bool: """True if there are errors, False otherwise. Warns for errors.""" - if not reply.startswith("ERROR"): + # ERRORS: is the expected answer to read_errors() + if not reply.startswith("ERROR") or reply.startswith("ERRORS:"): return False if "ERROR:1" in reply: @@ -357,11 +360,12 @@ async def read_extflow(self) -> float: logger.debug(f"Extflow reading returns {ext_flow}") return float(ext_flow) - async def read_errors(self): + async def read_errors(self) -> List[int]: """Returns the last 5 errors.""" last_5_errors = await self.create_and_send_command(ERRORS) logger.debug(f"Error reading returns {last_5_errors}") - return last_5_errors + parsed_errors = [int(err_code) for err_code in last_5_errors.split(",")] + return parsed_errors async def read_motor_current(self): """Returns motor current, relative in percent 0-100."""