From 43767d4a858148ddc515fd5813b99fbb13566f1b Mon Sep 17 00:00:00 2001 From: andySigler Date: Fri, 11 May 2018 12:45:02 -0400 Subject: [PATCH 01/27] adds v2 pipettes to plunger configs --- api/opentrons/__init__.py | 44 ++++- .../drivers/smoothie_drivers/driver_3_0.py | 5 +- api/opentrons/instruments/pipette_config.py | 168 ++++++++++++++++-- api/opentrons/robot/robot.py | 16 +- api/tests/opentrons/api/test_session.py | 2 +- .../robot-data/pipette-config.json | 133 ++++++++++++++ 6 files changed, 343 insertions(+), 25 deletions(-) diff --git a/api/opentrons/__init__.py b/api/opentrons/__init__.py index 93e6742ead2..0e1df727203 100755 --- a/api/opentrons/__init__.py +++ b/api/opentrons/__init__.py @@ -51,7 +51,9 @@ def P10_Single( aspirate_flow_rate=None, dispense_flow_rate=None): - config = pipette_config.load('p10_single_v1') + pipette_model_version = self._retrieve_version_number( + mount, 'p10_single') + config = pipette_config.load(pipette_model_version) return self._create_pipette_from_config( config=config, @@ -69,7 +71,9 @@ def P10_Multi( aspirate_flow_rate=None, dispense_flow_rate=None): - config = pipette_config.load('p10_multi_v1') + pipette_model_version = self._retrieve_version_number( + mount, 'p10_multi') + config = pipette_config.load(pipette_model_version) return self._create_pipette_from_config( config=config, @@ -87,7 +91,9 @@ def P50_Single( aspirate_flow_rate=None, dispense_flow_rate=None): - config = pipette_config.load('p50_single_v1') + pipette_model_version = self._retrieve_version_number( + mount, 'p50_single') + config = pipette_config.load(pipette_model_version) return self._create_pipette_from_config( config=config, @@ -105,7 +111,9 @@ def P50_Multi( aspirate_flow_rate=None, dispense_flow_rate=None): - config = pipette_config.load('p50_multi_v1') + pipette_model_version = self._retrieve_version_number( + mount, 'p50_multi') + config = pipette_config.load(pipette_model_version) return self._create_pipette_from_config( config=config, @@ -123,7 +131,9 @@ def P300_Single( aspirate_flow_rate=None, dispense_flow_rate=None): - config = pipette_config.load('p300_single_v1') + pipette_model_version = self._retrieve_version_number( + mount, 'p300_single') + config = pipette_config.load(pipette_model_version) return self._create_pipette_from_config( config=config, @@ -141,7 +151,9 @@ def P300_Multi( aspirate_flow_rate=None, dispense_flow_rate=None): - config = pipette_config.load('p300_multi_v1') + pipette_model_version = self._retrieve_version_number( + mount, 'p300_multi') + config = pipette_config.load(pipette_model_version) return self._create_pipette_from_config( config=config, @@ -159,7 +171,9 @@ def P1000_Single( aspirate_flow_rate=None, dispense_flow_rate=None): - config = pipette_config.load('p1000_single_v1') + pipette_model_version = self._retrieve_version_number( + mount, 'p1000_single') + config = pipette_config.load(pipette_model_version) return self._create_pipette_from_config( config=config, @@ -207,6 +221,22 @@ def _create_pipette_from_config( p.set_pick_up_current(config.pick_up_current) return p + def _retrieve_version_number(self, mount, expected_model): + # pass a default state incase the driver is simulating + default_version = expected_model + '_v2' + pipettes_attached = robot.get_attached_pipettes( + default={mount:{'model': default_version}}) + found_version = pipettes_attached[mount]['model'] + if not found_version: + found_version = default_version + + # check that the model string is a substring of the version + # eg: check that 'p10_single' is inside the found 'p10_single_v2' + if expected_model not in found_version: + raise RuntimeError( + 'Found unexpected Pipette attached: {}'.format(found_version)) + return found_version + instruments = InstrumentsWrapper(robot) containers = ContainersWrapper(robot) diff --git a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py index 1143ccf2c9b..2bb16598b3f 100755 --- a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py +++ b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py @@ -310,11 +310,12 @@ def read_pipette_model(self, mount): :return :dict with key 'model' and model string as value, or None ''' if self.simulating: - res = list(configs.values())[0].name + # res = list(configs.values())[0].name + return None else: res = self._read_from_pipette( GCODES['READ_INSTRUMENT_MODEL'], mount) - if res and not res.endswith('_v1'): + if '_v' not in res: # Backward compatibility for pipettes programmed with model # strings that did not include the _v# designation res = res + '_v1' diff --git a/api/opentrons/instruments/pipette_config.py b/api/opentrons/instruments/pipette_config.py index f6247291901..d88560bf0bc 100644 --- a/api/opentrons/instruments/pipette_config.py +++ b/api/opentrons/instruments/pipette_config.py @@ -89,7 +89,7 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: DEFAULT_ASPIRATE_SECONDS = 2 DEFAULT_DISPENSE_SECONDS = 1 -p10_single = pipette_config( +p10_single_v1 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 3, @@ -108,7 +108,26 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: tip_length=40 ) -p10_multi = pipette_config( +p10_single_v2 = pipette_config( + plunger_positions={ + 'top': 19, + 'bottom': 0, + 'blow_out': -2, + 'drop_tip': -5.5 + }, + pick_up_current=0.1, + aspirate_flow_rate=10 / DEFAULT_ASPIRATE_SECONDS, + dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, + ul_per_mm=0.77, + channels=1, + name='p10_single_v1', + model_offset=(0.0, 0.0, Z_OFFSET_P10), + plunger_current=0.3, + drop_tip_current=0.5, + tip_length=40 +) + +p10_multi_v1 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 3, @@ -127,7 +146,26 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: tip_length=40 ) -p50_single = pipette_config( +p10_multi_v2 = pipette_config( + plunger_positions={ + 'top': 19, + 'bottom': 2.5, + 'blow_out': -0.5, + 'drop_tip': -5.5 + }, + pick_up_current=0.2, + aspirate_flow_rate=10 / DEFAULT_ASPIRATE_SECONDS, + dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, + ul_per_mm=0.77, + channels=8, + name='p10_multi_v1', + model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), + plunger_current=0.5, + drop_tip_current=0.5, + tip_length=40 +) + +p50_single_v1 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 4, @@ -146,7 +184,26 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: tip_length=60 ) -p50_multi = pipette_config( +p50_single_v2 = pipette_config( + plunger_positions={ + 'top': 19, + 'bottom': 2.5, + 'blow_out': 0.5, + 'drop_tip': -4.5 + }, + pick_up_current=0.1, + aspirate_flow_rate=50 / DEFAULT_ASPIRATE_SECONDS, + dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, + ul_per_mm=3.35, + channels=1, + name='p50_single_v1', + model_offset=(0.0, 0.0, Z_OFFSET_P50), + plunger_current=0.3, + drop_tip_current=0.5, + tip_length=60 +) + +p50_multi_v1 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 4, @@ -165,7 +222,26 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: tip_length=60 ) -p300_single = pipette_config( +p50_multi_v2 = pipette_config( + plunger_positions={ + 'top': 19, + 'bottom': 2.5, + 'blow_out': 0.5, + 'drop_tip': -4 + }, + pick_up_current=0.3, + aspirate_flow_rate=50 / DEFAULT_ASPIRATE_SECONDS, + dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, + ul_per_mm=3.35, + channels=8, + name='p50_multi_v1', + model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), + plunger_current=0.5, + drop_tip_current=0.5, + tip_length=60 +) + +p300_single_v1 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -184,7 +260,26 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: tip_length=60 ) -p300_multi = pipette_config( +p300_single_v2 = pipette_config( + plunger_positions={ + 'top': 19, + 'bottom': 2.5, + 'blow_out': -0.5, + 'drop_tip': -5 + }, + pick_up_current=0.1, + aspirate_flow_rate=300 / DEFAULT_ASPIRATE_SECONDS, + dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, + ul_per_mm=18.7, + channels=1, + name='p300_single_v1', + model_offset=(0.0, 0.0, Z_OFFSET_P300), + plunger_current=0.3, + drop_tip_current=0.5, + tip_length=60 +) + +p300_multi_v1 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 3, @@ -203,7 +298,26 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: tip_length=60 ) -p1000_single = pipette_config( +p300_multi_v2 = pipette_config( + plunger_positions={ + 'top': 19, + 'bottom': 2.5, + 'blow_out': -0.5, + 'drop_tip': -5 + }, + pick_up_current=0.3, + aspirate_flow_rate=300 / DEFAULT_ASPIRATE_SECONDS, + dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, + ul_per_mm=19, + channels=8, + name='p300_multi_v1', + model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), + plunger_current=0.5, + drop_tip_current=0.5, + tip_length=60 +) + +p1000_single_v1 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 3, @@ -222,14 +336,40 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: tip_length=60 ) +p1000_single_v2 = pipette_config( + plunger_positions={ + 'top': 19, + 'bottom': 2.5, + 'blow_out': 0.5, + 'drop_tip': -4 + }, + pick_up_current=0.1, + aspirate_flow_rate=1000 / DEFAULT_ASPIRATE_SECONDS, + dispense_flow_rate=1000 / DEFAULT_DISPENSE_SECONDS, + ul_per_mm=65, + channels=1, + name='p1000_single_v1', + model_offset=(0.0, 0.0, Z_OFFSET_P1000), + plunger_current=0.5, + drop_tip_current=0.5, + tip_length=60 +) + fallback_configs = { - 'p10_single_v1': p10_single, - 'p10_multi_v1': p10_multi, - 'p50_single_v1': p50_single, - 'p50_multi_v1': p50_multi, - 'p300_single_v1': p300_single, - 'p300_multi_v1': p300_multi, - 'p1000_single_v1': p1000_single + 'p10_single_v1': p10_single_v1, + 'p10_single_v2': p10_single_v2, + 'p10_multi_v1': p10_multi_v1, + 'p10_multi_v2': p10_multi_v2, + 'p50_single_v1': p50_single_v1, + 'p50_single_v2': p50_single_v2, + 'p50_multi_v1': p50_multi_v1, + 'p50_multi_v2': p50_multi_v2, + 'p300_single_v1': p300_single_v1, + 'p300_single_v2': p300_single_v2, + 'p300_multi_v1': p300_multi_v1, + 'p300_multi_v2': p300_multi_v2, + 'p1000_single_v1': p1000_single_v1, + 'p1000_single_v2': p1000_single_v2, } diff --git a/api/opentrons/robot/robot.py b/api/opentrons/robot/robot.py index 21eb19cc7c3..3ee01a3fb17 100755 --- a/api/opentrons/robot/robot.py +++ b/api/opentrons/robot/robot.py @@ -922,7 +922,7 @@ def halt(self): self.reset() self.home() - def get_attached_pipettes(self): + def get_attached_pipettes(self, default=None): """ Gets model names of attached pipettes @@ -948,6 +948,20 @@ def get_attached_pipettes(self): if right_model: tip_length = pipette_config.configs[right_model].tip_length right_data.update({'tip_length': tip_length}) + + if self.is_simulating(): + from opentrons.instruments.pipette_config import configs + default_version = list(configs.values())[0].name + if not isinstance(default, dict): + default = { + 'left': {'model': default_version}, + 'right': {'model': default_version} + } + if 'left' in default: + left_data.update(default['left']) + if 'right' in default: + right_data.update(default['right']) + return { 'left': left_data, 'right': right_data diff --git a/api/tests/opentrons/api/test_session.py b/api/tests/opentrons/api/test_session.py index 6ddb50a82ea..54991f6f08d 100755 --- a/api/tests/opentrons/api/test_session.py +++ b/api/tests/opentrons/api/test_session.py @@ -247,7 +247,7 @@ async def test_session_model_functional(session_manager, protocol): assert [container.name for container in session.containers] == \ ['tiprack', 'trough', 'plate', 'tall-fixed-trash'] names = [instrument.name for instrument in session.instruments] - assert names == ['p300_single_v1'] + assert names == ['p300_single_v2'] # TODO(artyom 20171018): design a small protocol specifically for the test diff --git a/labware-definitions/robot-data/pipette-config.json b/labware-definitions/robot-data/pipette-config.json index 96ac75bc1f2..dbe749b979a 100644 --- a/labware-definitions/robot-data/pipette-config.json +++ b/labware-definitions/robot-data/pipette-config.json @@ -18,6 +18,25 @@ "dropTipCurrent": 0.5, "tipLength": 40 }, + "p10_single_v2": { + "displayName": "P10 Single-Channel", + "nominalMaxVolumeUl": 10, + "plungerPositions": { + "top": 19, + "bottom": 0, + "blowOut": -2, + "dropTip": -5.5 + }, + "pickUpCurrent": 0.1, + "aspirateFlowRate": 5, + "dispenseFlowRate": 10, + "ulPerMm": 0.77, + "channels": 1, + "modelOffset": [0.0, 0.0, -13], + "plungerCurrent": 0.3, + "dropTipCurrent": 0.5, + "tipLength": 40 + }, "p10_multi_v1": { "displayName": "P10 8-Channel", "nominalMaxVolumeUl": 10, @@ -37,6 +56,25 @@ "dropTipCurrent": 0.5, "tipLength": 40 }, + "p10_multi_v2": { + "displayName": "P10 8-Channel", + "nominalMaxVolumeUl": 10, + "plungerPositions": { + "top": 19, + "bottom": 2.5, + "blowOut": -0.5, + "dropTip": -5.5 + }, + "pickUpCurrent": 0.2, + "aspirateFlowRate": 5, + "dispenseFlowRate": 10, + "ulPerMm": 0.77, + "channels": 8, + "modelOffset": [0.0, 28.0, -25.8], + "plungerCurrent": 0.5, + "dropTipCurrent": 0.5, + "tipLength": 40 + }, "p50_single_v1": { "displayName": "P50 Single-Channel", "nominalMaxVolumeUl": 50, @@ -56,6 +94,25 @@ "dropTipCurrent": 0.5, "tipLength": 60 }, + "p50_single_v2": { + "displayName": "P50 Single-Channel", + "nominalMaxVolumeUl": 50, + "plungerPositions": { + "top": 19, + "bottom": 2.5, + "blowOut": 0.5, + "dropTip": -4.5 + }, + "pickUpCurrent": 0.1, + "aspirateFlowRate": 25, + "dispenseFlowRate": 50, + "ulPerMm": 3.35, + "channels": 1, + "modelOffset": [0.0, 0.0, 0.0], + "plungerCurrent": 0.3, + "dropTipCurrent": 0.5, + "tipLength": 60 + }, "p50_multi_v1": { "displayName": "P50 8-Channel", "nominalMaxVolumeUl": 50, @@ -75,6 +132,25 @@ "dropTipCurrent": 0.5, "tipLength": 60 }, + "p50_multi_v2": { + "displayName": "P50 8-Channel", + "nominalMaxVolumeUl": 50, + "plungerPositions": { + "top": 19, + "bottom": 2.5, + "blowOut": 0.5, + "dropTip": -4 + }, + "pickUpCurrent": 0.3, + "aspirateFlowRate": 25, + "dispenseFlowRate": 50, + "ulPerMm": 3.35, + "channels": 8, + "modelOffset": [0.0, 28.0, -25.8], + "plungerCurrent": 0.5, + "dropTipCurrent": 0.5, + "tipLength": 60 + }, "p300_single_v1": { "displayName": "P300 Single-Channel", "nominalMaxVolumeUl": 300, @@ -94,6 +170,25 @@ "dropTipCurrent": 0.5, "tipLength": 60 }, + "p300_single_v2": { + "displayName": "P300 Single-Channel", + "nominalMaxVolumeUl": 300, + "plungerPositions": { + "top": 19, + "bottom": 2.5, + "blowOut": -0.5, + "dropTip": -5 + }, + "pickUpCurrent": 0.1, + "aspirateFlowRate": 150, + "dispenseFlowRate": 300, + "ulPerMm": 18.7, + "channels": 1, + "modelOffset": [0.0, 0.0, 0.0], + "plungerCurrent": 0.3, + "dropTipCurrent": 0.5, + "tipLength": 60 + }, "p300_multi_v1": { "displayName": "P300 8-Channel", "nominalMaxVolumeUl": 300, @@ -113,6 +208,25 @@ "dropTipCurrent": 0.5, "tipLength": 60 }, + "p300_multi_v2": { + "displayName": "P300 8-Channel", + "nominalMaxVolumeUl": 300, + "plungerPositions": { + "top": 19, + "bottom": 2.5, + "blowOut": -0.5, + "dropTip": -5 + }, + "pickUpCurrent": 0.3, + "aspirateFlowRate": 150, + "dispenseFlowRate": 300, + "ulPerMm": 19, + "channels": 8, + "modelOffset": [0.0, 28.0, -25.8], + "plungerCurrent": 0.5, + "dropTipCurrent": 0.5, + "tipLength": 60 + }, "p1000_single_v1": { "displayName": "P1000 Single-channel", "nominalMaxVolumeUl": 1000, @@ -131,5 +245,24 @@ "plungerCurrent": 0.5, "dropTipCurrent": 0.5, "tipLength": 60 + }, + "p1000_single_v2": { + "displayName": "P1000 Single-channel", + "nominalMaxVolumeUl": 1000, + "plungerPositions": { + "top": 19, + "bottom": 2.5, + "blowOut": -0.5, + "dropTip": -4 + }, + "pickUpCurrent": 0.1, + "aspirateFlowRate": 500, + "dispenseFlowRate": 1000, + "ulPerMm": 65, + "channels": 1, + "modelOffset": [0.0, 0.0, 20.0], + "plungerCurrent": 0.5, + "dropTipCurrent": 0.5, + "tipLength": 60 } } From b772f05dfa957b8035ad322b02339ebdc4e3657a Mon Sep 17 00:00:00 2001 From: andySigler Date: Fri, 11 May 2018 13:42:19 -0400 Subject: [PATCH 02/27] tests pass --- api/opentrons/__init__.py | 2 +- .../drivers/smoothie_drivers/driver_3_0.py | 4 +-- api/opentrons/instruments/pipette_config.py | 23 +++++--------- api/opentrons/robot/robot.py | 30 +++++++++++-------- .../server/test_control_endpoints.py | 12 +++++++- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/api/opentrons/__init__.py b/api/opentrons/__init__.py index 0e1df727203..0b111719bc2 100755 --- a/api/opentrons/__init__.py +++ b/api/opentrons/__init__.py @@ -225,7 +225,7 @@ def _retrieve_version_number(self, mount, expected_model): # pass a default state incase the driver is simulating default_version = expected_model + '_v2' pipettes_attached = robot.get_attached_pipettes( - default={mount:{'model': default_version}}) + default={mount: {'model': default_version}}) found_version = pipettes_attached[mount]['model'] if not found_version: found_version = default_version diff --git a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py index 2bb16598b3f..28f67298db4 100755 --- a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py +++ b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py @@ -8,7 +8,6 @@ from opentrons.drivers.smoothie_drivers import serial_communication from opentrons.drivers.rpi_drivers import gpio -from opentrons.instruments.pipette_config import configs ''' - Driver is responsible for providing an interface for motion control - Driver is the only system component that knows about GCODES or how smoothie @@ -310,12 +309,11 @@ def read_pipette_model(self, mount): :return :dict with key 'model' and model string as value, or None ''' if self.simulating: - # res = list(configs.values())[0].name return None else: res = self._read_from_pipette( GCODES['READ_INSTRUMENT_MODEL'], mount) - if '_v' not in res: + if res and '_v' not in res: # Backward compatibility for pipettes programmed with model # strings that did not include the _v# designation res = res + '_v1' diff --git a/api/opentrons/instruments/pipette_config.py b/api/opentrons/instruments/pipette_config.py index d88560bf0bc..6e349c0064e 100644 --- a/api/opentrons/instruments/pipette_config.py +++ b/api/opentrons/instruments/pipette_config.py @@ -120,7 +120,7 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=0.77, channels=1, - name='p10_single_v1', + name='p10_single_v2', model_offset=(0.0, 0.0, Z_OFFSET_P10), plunger_current=0.3, drop_tip_current=0.5, @@ -158,7 +158,7 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=0.77, channels=8, - name='p10_multi_v1', + name='p10_multi_v2', model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), plunger_current=0.5, drop_tip_current=0.5, @@ -196,7 +196,7 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=3.35, channels=1, - name='p50_single_v1', + name='p50_single_v2', model_offset=(0.0, 0.0, Z_OFFSET_P50), plunger_current=0.3, drop_tip_current=0.5, @@ -234,7 +234,7 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=3.35, channels=8, - name='p50_multi_v1', + name='p50_multi_v2', model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), plunger_current=0.5, drop_tip_current=0.5, @@ -272,7 +272,7 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=18.7, channels=1, - name='p300_single_v1', + name='p300_single_v2', model_offset=(0.0, 0.0, Z_OFFSET_P300), plunger_current=0.3, drop_tip_current=0.5, @@ -310,7 +310,7 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=19, channels=8, - name='p300_multi_v1', + name='p300_multi_v2', model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), plunger_current=0.5, drop_tip_current=0.5, @@ -348,7 +348,7 @@ def _load_config_from_file(pipette_model: str) -> pipette_config: dispense_flow_rate=1000 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=65, channels=1, - name='p1000_single_v1', + name='p1000_single_v2', model_offset=(0.0, 0.0, Z_OFFSET_P1000), plunger_current=0.5, drop_tip_current=0.5, @@ -410,14 +410,7 @@ def select_config(model: str): configs = { model: select_config(model) - for model in [ - 'p10_single_v1', - 'p10_multi_v1', - 'p50_single_v1', - 'p50_multi_v1', - 'p300_single_v1', - 'p300_multi_v1', - 'p1000_single_v1']} + for model in fallback_configs.keys()} def load(pipette_model: str) -> pipette_config: diff --git a/api/opentrons/robot/robot.py b/api/opentrons/robot/robot.py index 3ee01a3fb17..666b3f5a525 100755 --- a/api/opentrons/robot/robot.py +++ b/api/opentrons/robot/robot.py @@ -929,30 +929,20 @@ def get_attached_pipettes(self, default=None): :return: :dict with keys 'left' and 'right' and a model string for each mount, or 'uncommissioned' if no model string available """ + # get the cached pipette model numbers left_data = { 'mount_axis': 'z', 'plunger_axis': 'b', 'model': self.model_by_mount['left'], } - left_model = left_data.get('model') - if left_model: - tip_length = pipette_config.configs[left_model].tip_length - left_data.update({'tip_length': tip_length}) - right_data = { 'mount_axis': 'a', 'plunger_axis': 'c', 'model': self.model_by_mount['right'] } - right_model = right_data.get('model') - if right_model: - tip_length = pipette_config.configs[right_model].tip_length - right_data.update({'tip_length': tip_length}) - if self.is_simulating(): - from opentrons.instruments.pipette_config import configs - default_version = list(configs.values())[0].name - if not isinstance(default, dict): + default_version = list(pipette_config.configs.values())[0].name + if not default: default = { 'left': {'model': default_version}, 'right': {'model': default_version} @@ -962,6 +952,20 @@ def get_attached_pipettes(self, default=None): if 'right' in default: right_data.update(default['right']) + # add tip-length + left_model = left_data.get('model') + if left_model: + tip_length = pipette_config.configs[left_model].tip_length + left_data.update({'tip_length': tip_length}) + right_model = right_data.get('model') + if right_model: + tip_length = pipette_config.configs[right_model].tip_length + right_data.update({'tip_length': tip_length}) + + print({ + 'left': left_data, + 'right': right_data + }) return { 'left': left_data, 'right': right_data diff --git a/api/tests/opentrons/server/test_control_endpoints.py b/api/tests/opentrons/server/test_control_endpoints.py index 6fc902af30b..14a38bee3b0 100644 --- a/api/tests/opentrons/server/test_control_endpoints.py +++ b/api/tests/opentrons/server/test_control_endpoints.py @@ -70,7 +70,17 @@ async def test_get_cached_pipettes( app = init(loop) cli = await loop.create_task(test_client(app)) + monkeypatch.setattr(robot, 'is_simulating', lambda: False) + model = list(configs.values())[0] + + def dummy_model(mount): + return model.name + + monkeypatch.setattr(robot._driver, 'read_pipette_model', dummy_model) + + robot.cache_instrument_models() # do an initial caching + expected = { 'left': { 'model': model.name, @@ -101,7 +111,7 @@ def dummy_model(mount): resp1 = await cli.get('/pipettes') text1 = await resp1.text() assert resp1.status == 200 - assert json.loads(text1) == expected + assert json.loads(text1) == expected # models aren't cached, so no change expected2 = { 'left': { From 8c6f25e63fd49111c39c071ff8dbead0f613cd83 Mon Sep 17 00:00:00 2001 From: andySigler Date: Fri, 11 May 2018 13:58:38 -0400 Subject: [PATCH 03/27] default version is v1 --- api/opentrons/__init__.py | 2 +- api/opentrons/robot/robot.py | 5 +---- api/tests/opentrons/api/test_session.py | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/api/opentrons/__init__.py b/api/opentrons/__init__.py index 0b111719bc2..71c06c44e12 100755 --- a/api/opentrons/__init__.py +++ b/api/opentrons/__init__.py @@ -223,7 +223,7 @@ def _create_pipette_from_config( def _retrieve_version_number(self, mount, expected_model): # pass a default state incase the driver is simulating - default_version = expected_model + '_v2' + default_version = expected_model + '_v1' pipettes_attached = robot.get_attached_pipettes( default={mount: {'model': default_version}}) found_version = pipettes_attached[mount]['model'] diff --git a/api/opentrons/robot/robot.py b/api/opentrons/robot/robot.py index 666b3f5a525..e9534135d90 100755 --- a/api/opentrons/robot/robot.py +++ b/api/opentrons/robot/robot.py @@ -940,6 +940,7 @@ def get_attached_pipettes(self, default=None): 'plunger_axis': 'c', 'model': self.model_by_mount['right'] } + if self.is_simulating(): default_version = list(pipette_config.configs.values())[0].name if not default: @@ -962,10 +963,6 @@ def get_attached_pipettes(self, default=None): tip_length = pipette_config.configs[right_model].tip_length right_data.update({'tip_length': tip_length}) - print({ - 'left': left_data, - 'right': right_data - }) return { 'left': left_data, 'right': right_data diff --git a/api/tests/opentrons/api/test_session.py b/api/tests/opentrons/api/test_session.py index 18903a8796a..1333eb1c953 100755 --- a/api/tests/opentrons/api/test_session.py +++ b/api/tests/opentrons/api/test_session.py @@ -235,7 +235,7 @@ async def test_session_model_functional(session_manager, protocol): assert [container.name for container in session.containers] == \ ['tiprack', 'trough', 'plate', 'tall-fixed-trash'] names = [instrument.name for instrument in session.instruments] - assert names == ['p300_single_v2'] + assert names == ['p300_single_v1'] # TODO(artyom 20171018): design a small protocol specifically for the test From 1671fb2d1550fcc886569a82c49d24dc58f49798 Mon Sep 17 00:00:00 2001 From: andySigler Date: Fri, 11 May 2018 14:07:13 -0400 Subject: [PATCH 04/27] adds some comments --- api/opentrons/__init__.py | 6 ++++-- api/opentrons/robot/robot.py | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/api/opentrons/__init__.py b/api/opentrons/__init__.py index 71c06c44e12..74a953f692b 100755 --- a/api/opentrons/__init__.py +++ b/api/opentrons/__init__.py @@ -222,7 +222,9 @@ def _create_pipette_from_config( return p def _retrieve_version_number(self, mount, expected_model): - # pass a default state incase the driver is simulating + # pass a default pipette model-version, for when robot is simulating + # this allows any pipette to be simulated, regardless of what is + # actually attached/cached on the robot's mounts default_version = expected_model + '_v1' pipettes_attached = robot.get_attached_pipettes( default={mount: {'model': default_version}}) @@ -233,7 +235,7 @@ def _retrieve_version_number(self, mount, expected_model): # check that the model string is a substring of the version # eg: check that 'p10_single' is inside the found 'p10_single_v2' if expected_model not in found_version: - raise RuntimeError( + raise TypeError( 'Found unexpected Pipette attached: {}'.format(found_version)) return found_version diff --git a/api/opentrons/robot/robot.py b/api/opentrons/robot/robot.py index e9534135d90..7a1cd2081d1 100755 --- a/api/opentrons/robot/robot.py +++ b/api/opentrons/robot/robot.py @@ -926,6 +926,10 @@ def get_attached_pipettes(self, default=None): """ Gets model names of attached pipettes + default: dict + keys are 'left' and 'right', with each value being a dict of + pipette properties to overwrite while in simulating mode + :return: :dict with keys 'left' and 'right' and a model string for each mount, or 'uncommissioned' if no model string available """ @@ -941,6 +945,9 @@ def get_attached_pipettes(self, default=None): 'model': self.model_by_mount['right'] } + # to allow any model of pipette to be simulated on a robot, pipette + # models below are updated when simulating, so regardless of what is + # currently attached/cached, any pipette model can be simulated if self.is_simulating(): default_version = list(pipette_config.configs.values())[0].name if not default: From bf6acf07bc5b54438ed60210efc5201048e63b0c Mon Sep 17 00:00:00 2001 From: andySigler Date: Fri, 11 May 2018 14:55:45 -0400 Subject: [PATCH 05/27] brings back deleted file --- .../module_drivers/test_temp_deck_driver.py | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 api/tests/opentrons/drivers/module_drivers/test_temp_deck_driver.py diff --git a/api/tests/opentrons/drivers/module_drivers/test_temp_deck_driver.py b/api/tests/opentrons/drivers/module_drivers/test_temp_deck_driver.py new file mode 100644 index 00000000000..2acc80251ca --- /dev/null +++ b/api/tests/opentrons/drivers/module_drivers/test_temp_deck_driver.py @@ -0,0 +1,150 @@ +# from tests.opentrons.conftest import fuzzy_assert + +# Simulating how the firmware will handle commands and respond +# The ACK argument given to 'write_and_return' is what the +# 'serial_communication' module searhces for. +# Once it sees those characters, it then stops reading, +# strips those ACK characters from the response, the return the response +# If you send a commmand to the serial comm module and it never sees the +# expected ACK, then it'll eventually time out and return an error + + +def test_get_temp_deck_temperature(monkeypatch): + # Get the curent and target temperatures + # If no target temp has been previously set, + # then the response will set 'T' to 'none' + from opentrons.drivers.smoothie_drivers import serial_communication + + def write_with_log(command, ack, connection, timeout=None): + current_temp = 24 + target_temp = 'none' + + if 'M105' in command: + return 'T:' + str(target_temp) \ + + ' C:' + str(current_temp) \ + + '\r\n' + + monkeypatch.setattr( + serial_communication, + 'write_and_return', + write_with_log) + + res = serial_communication.write_and_return( + 'M105\r\n', 'ok\r\nok\r\n', None) + + expected = 'T:none C:24\r\n' + assert res == expected + # fuzzy_assert(result=res, expected=expected) + + +def test_set_temp_deck_temperature(monkeypatch): + # Set target temperature + from opentrons.drivers.smoothie_drivers import serial_communication + + def write_with_log(command, ack, connection, timeout=None): + if 'M104' in command: + return '' + + monkeypatch.setattr( + serial_communication, + 'write_and_return', + write_with_log) + + res = serial_communication.write_and_return( + 'M104 S55\r\n', 'ok\r\nok\r\n', None) + + expected = '' + assert res == expected + # fuzzy_assert(result=res, expected=expected) + + +def test_fail_set_temp_deck_temperature(monkeypatch): + from opentrons.drivers.smoothie_drivers import serial_communication + + current_temp_deck_status = 'ERROR' + + def write_with_log(command, ack, connection, timeout=None): + + if 'M104' in command and current_temp_deck_status == 'ERROR': + return 'ERROR\r\n' + + monkeypatch.setattr( + serial_communication, + 'write_and_return', + write_with_log) + + res = serial_communication.write_and_return( + 'M104 S55\r\n', 'ok\r\nok\r\n', None) + + expected = 'ERROR\r\n' + assert res == expected + # fuzzy_assert(result=res, expected=expected) + + +def test_turn_off_temp_deck(monkeypatch): + from opentrons.drivers.smoothie_drivers import serial_communication + + def write_with_log(command, ack, connection, timeout=None): + if 'M18' in command: + return '' + + monkeypatch.setattr( + serial_communication, + 'write_and_return', + write_with_log) + + res = serial_communication.write_and_return( + 'M18\r\n', 'ok\r\nok\r\n', None) + + expected = '' + assert res == expected + # fuzzy_assert(result=res, expected=expected) + + +def test_get_device_info(monkeypatch): + # Get the device's Model, firmware version and Serial number + from opentrons.drivers.smoothie_drivers import serial_communication + + model = 'temp-v1' + firmware_version = 'edge-1a2b345' + serial = 'td20180102A01' + + def write_with_log(command, ack, connection, timeout=None): + if 'M115' in command: + return 'model:' + model \ + + ' version:' + firmware_version \ + + ' serial:' + serial \ + + '\r\n' + + monkeypatch.setattr( + serial_communication, + 'write_and_return', + write_with_log) + res = serial_communication.write_and_return( + 'M115\r\n', 'ok\r\nok\r\n', None) + + expected = ('model:temp-v1 ' + 'version:edge-1a2b345 ' + 'serial:td20180102A01' + '\r\n') + assert res == expected + + +def test_dfu_command(monkeypatch): + from opentrons.drivers.smoothie_drivers import serial_communication + + def write_with_log(command, ack, connection, timeout=None): + if 'dfu' in command: + return 'Entering Bootloader\r\n' + + monkeypatch.setattr( + serial_communication, + 'write_and_return', + write_with_log) + + res = serial_communication.write_and_return( + 'dfu\r\n', 'ok\r\nok\r\n', None) + + expected = 'Entering Bootloader\r\n' + assert res == expected + # fuzzy_assert(result=res, expected=expected) From b49ec489372201e3f431d69e1e7546dee971b084 Mon Sep 17 00:00:00 2001 From: andySigler Date: Mon, 14 May 2018 15:29:47 -0400 Subject: [PATCH 06/27] uses v2 pipette models in eeprom writing tool --- api/opentrons/instruments/pipette_config.py | 17 --------- api/opentrons/tools/write_pipette_memory.py | 38 ++++++++++++++------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/api/opentrons/instruments/pipette_config.py b/api/opentrons/instruments/pipette_config.py index a2a6536236a..2bfc9f20b84 100644 --- a/api/opentrons/instruments/pipette_config.py +++ b/api/opentrons/instruments/pipette_config.py @@ -396,23 +396,6 @@ def select_config(model: str): # protocol writer -# model-specific ID's, saved with each Pipette's memory -# used to identifiy what model pipette is currently connected to machine -PIPETTE_MODEL_IDENTIFIERS = { - 'single': { - '10': 'p10_single_v1', - '50': 'p50_single_v1', - '300': 'p300_single_v1', - '1000': 'p1000_single_v1' - }, - 'multi': { - '10': 'p10_multi_v1', - '50': 'p50_multi_v1', - '300': 'p300_multi_v1', - } -} - - configs = { model: select_config(model) for model in fallback_configs.keys()} diff --git a/api/opentrons/tools/write_pipette_memory.py b/api/opentrons/tools/write_pipette_memory.py index 9205face46c..0bc58ae88a3 100644 --- a/api/opentrons/tools/write_pipette_memory.py +++ b/api/opentrons/tools/write_pipette_memory.py @@ -1,17 +1,28 @@ -from opentrons.instruments.pipette_config import PIPETTE_MODEL_IDENTIFIERS +from opentrons.instruments.pipette_config import configs BAD_BARCODE_MESSAGE = 'Unexpected Serial -> {}' WRITE_FAIL_MESSAGE = 'Data not saved, HOLD BUTTON' MODELS = { - 'P10S': PIPETTE_MODEL_IDENTIFIERS['single']['10'], - 'P10M': PIPETTE_MODEL_IDENTIFIERS['multi']['10'], - 'P50S': PIPETTE_MODEL_IDENTIFIERS['single']['50'], - 'P50M': PIPETTE_MODEL_IDENTIFIERS['multi']['50'], - 'P300S': PIPETTE_MODEL_IDENTIFIERS['single']['300'], - 'P300M': PIPETTE_MODEL_IDENTIFIERS['multi']['300'], - 'P1000S': PIPETTE_MODEL_IDENTIFIERS['single']['1000'] + 'v1': { + 'P10S': configs['p10_single_v1'], + 'P10M': configs['p10_multi_v1'], + 'P50S': configs['p50_single_v1'], + 'P50M': configs['p50_multi_v1'], + 'P300S': configs['p300_single_v1'], + 'P300M': configs['p300_multi_v1'], + 'P1000S': configs['p1000_single_v1'] + }, + 'v2': { + 'P10SV13': configs['p10_single_v2'], + 'P10MV13': configs['p10_multi_v2'], + 'P50SV13': configs['p50_single_v2'], + 'P50MV13': configs['p50_multi_v2'], + 'P3HSV13': configs['p300_single_v2'], + 'P3HMV13': configs['p300_multi_v2'], + 'P1KSV13': configs['p1000_single_v2'] + } } @@ -73,10 +84,13 @@ def _user_submitted_barcode(max_length): def _parse_model_from_barcode(barcode): model = None - for key in MODELS.keys(): - if key in barcode: - model = MODELS[key] - break + # MUST iterate through v2 first, because v1 barcodes did not have + # characters to specify the version number + for version in ['v2', 'v1']: + for key in MODELS[version].keys(): + if key in barcode: + model = MODELS[version][key] + break if not model: raise Exception(BAD_BARCODE_MESSAGE.format(barcode)) return model From 40fc335f1444f3a948bb8c1715f3dbd8d14002dc Mon Sep 17 00:00:00 2001 From: andySigler Date: Mon, 14 May 2018 16:34:48 -0400 Subject: [PATCH 07/27] decrease p50_multi_v2 blowout distance by 0.5mm after testing --- api/opentrons/instruments/pipette_config.py | 2 +- shared-data/robot-data/pipette-config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/opentrons/instruments/pipette_config.py b/api/opentrons/instruments/pipette_config.py index 54a014612d7..b3ec4a0dc18 100644 --- a/api/opentrons/instruments/pipette_config.py +++ b/api/opentrons/instruments/pipette_config.py @@ -228,7 +228,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: plunger_positions={ 'top': 19, 'bottom': 2.5, - 'blow_out': 0.5, + 'blow_out': 1, 'drop_tip': -4.5 }, pick_up_current=0.1, diff --git a/shared-data/robot-data/pipette-config.json b/shared-data/robot-data/pipette-config.json index c00c133b9e1..21d3ecc3d47 100644 --- a/shared-data/robot-data/pipette-config.json +++ b/shared-data/robot-data/pipette-config.json @@ -100,7 +100,7 @@ "plungerPositions": { "top": 19, "bottom": 2.5, - "blowOut": 0.5, + "blowOut": 1, "dropTip": -4.5 }, "pickUpCurrent": 0.1, From e01e2c5682f30eacd3cf2a2c916c5c415171cfca Mon Sep 17 00:00:00 2001 From: andySigler Date: Tue, 15 May 2018 13:42:34 -0400 Subject: [PATCH 08/27] renames v2 to v13 --- api/opentrons/__init__.py | 2 +- api/opentrons/instruments/pipette_config.py | 42 ++++++++++----------- api/opentrons/tools/write_pipette_memory.py | 14 +++---- shared-data/robot-data/pipette-config.json | 14 +++---- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/api/opentrons/__init__.py b/api/opentrons/__init__.py index 6be9a9ea69f..291166e017e 100755 --- a/api/opentrons/__init__.py +++ b/api/opentrons/__init__.py @@ -235,7 +235,7 @@ def _retrieve_version_number(self, mount, expected_model): found_version = default_version # check that the model string is a substring of the version - # eg: check that 'p10_single' is inside the found 'p10_single_v2' + # eg: check that 'p10_single' is inside the found 'p10_single_v13' if expected_model not in found_version: raise TypeError( 'Found unexpected Pipette attached: {}'.format(found_version)) diff --git a/api/opentrons/instruments/pipette_config.py b/api/opentrons/instruments/pipette_config.py index b3ec4a0dc18..d8efb2728fe 100644 --- a/api/opentrons/instruments/pipette_config.py +++ b/api/opentrons/instruments/pipette_config.py @@ -148,7 +148,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=33 ) -p10_single_v2 = pipette_config( +p10_single_v13 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 0, @@ -160,7 +160,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=0.77, channels=1, - name='p10_single_v2', + name='p10_single_v13', model_offset=(0.0, 0.0, Z_OFFSET_P10), plunger_current=0.3, drop_tip_current=0.5, @@ -186,7 +186,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=33 ) -p10_multi_v2 = pipette_config( +p10_multi_v13 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -198,7 +198,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=0.77, channels=8, - name='p10_multi_v2', + name='p10_multi_v13', model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), plunger_current=0.5, drop_tip_current=0.5, @@ -224,7 +224,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=51.7 ) -p50_single_v2 = pipette_config( +p50_single_v13 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -236,7 +236,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=3.35, channels=1, - name='p50_single_v2', + name='p50_single_v13', model_offset=(0.0, 0.0, Z_OFFSET_P50), plunger_current=0.3, drop_tip_current=0.5, @@ -262,7 +262,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=51.7 ) -p50_multi_v2 = pipette_config( +p50_multi_v13 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -274,7 +274,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=3.35, channels=8, - name='p50_multi_v2', + name='p50_multi_v13', model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), plunger_current=0.5, drop_tip_current=0.5, @@ -300,7 +300,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=51.7 ) -p300_single_v2 = pipette_config( +p300_single_v13 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -312,7 +312,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=18.7, channels=1, - name='p300_single_v2', + name='p300_single_v13', model_offset=(0.0, 0.0, Z_OFFSET_P300), plunger_current=0.3, drop_tip_current=0.5, @@ -338,7 +338,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=51.7 ) -p300_multi_v2 = pipette_config( +p300_multi_v13 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -350,7 +350,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=19, channels=8, - name='p300_multi_v2', + name='p300_multi_v13', model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), plunger_current=0.5, drop_tip_current=0.5, @@ -376,7 +376,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=76.7 ) -p1000_single_v2 = pipette_config( +p1000_single_v13 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -388,7 +388,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=1000 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=65, channels=1, - name='p1000_single_v2', + name='p1000_single_v13', model_offset=(0.0, 0.0, Z_OFFSET_P1000), plunger_current=0.5, drop_tip_current=0.5, @@ -397,19 +397,19 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: fallback_configs = { 'p10_single_v1': p10_single_v1, - 'p10_single_v2': p10_single_v2, + 'p10_single_v13': p10_single_v13, 'p10_multi_v1': p10_multi_v1, - 'p10_multi_v2': p10_multi_v2, + 'p10_multi_v13': p10_multi_v13, 'p50_single_v1': p50_single_v1, - 'p50_single_v2': p50_single_v2, + 'p50_single_v13': p50_single_v13, 'p50_multi_v1': p50_multi_v1, - 'p50_multi_v2': p50_multi_v2, + 'p50_multi_v13': p50_multi_v13, 'p300_single_v1': p300_single_v1, - 'p300_single_v2': p300_single_v2, + 'p300_single_v13': p300_single_v13, 'p300_multi_v1': p300_multi_v1, - 'p300_multi_v2': p300_multi_v2, + 'p300_multi_v13': p300_multi_v13, 'p1000_single_v1': p1000_single_v1, - 'p1000_single_v2': p1000_single_v2, + 'p1000_single_v13': p1000_single_v13, } diff --git a/api/opentrons/tools/write_pipette_memory.py b/api/opentrons/tools/write_pipette_memory.py index 0bc58ae88a3..b033ae15b5f 100644 --- a/api/opentrons/tools/write_pipette_memory.py +++ b/api/opentrons/tools/write_pipette_memory.py @@ -15,13 +15,13 @@ 'P1000S': configs['p1000_single_v1'] }, 'v2': { - 'P10SV13': configs['p10_single_v2'], - 'P10MV13': configs['p10_multi_v2'], - 'P50SV13': configs['p50_single_v2'], - 'P50MV13': configs['p50_multi_v2'], - 'P3HSV13': configs['p300_single_v2'], - 'P3HMV13': configs['p300_multi_v2'], - 'P1KSV13': configs['p1000_single_v2'] + 'P10SV13': configs['p10_single_v13'], + 'P10MV13': configs['p10_multi_v13'], + 'P50SV13': configs['p50_single_v13'], + 'P50MV13': configs['p50_multi_v13'], + 'P3HSV13': configs['p300_single_v13'], + 'P3HMV13': configs['p300_multi_v13'], + 'P1KSV13': configs['p1000_single_v13'] } } diff --git a/shared-data/robot-data/pipette-config.json b/shared-data/robot-data/pipette-config.json index 21d3ecc3d47..014c7874926 100644 --- a/shared-data/robot-data/pipette-config.json +++ b/shared-data/robot-data/pipette-config.json @@ -18,7 +18,7 @@ "dropTipCurrent": 0.5, "tipLength": 33 }, - "p10_single_v2": { + "p10_single_v13": { "displayName": "P10 Single-Channel", "nominalMaxVolumeUl": 10, "plungerPositions": { @@ -56,7 +56,7 @@ "dropTipCurrent": 0.5, "tipLength": 33 }, - "p10_multi_v2": { + "p10_multi_v13": { "displayName": "P10 8-Channel", "nominalMaxVolumeUl": 10, "plungerPositions": { @@ -94,7 +94,7 @@ "dropTipCurrent": 0.5, "tipLength": 51.7 }, - "p50_single_v2": { + "p50_single_v13": { "displayName": "P50 Single-Channel", "nominalMaxVolumeUl": 50, "plungerPositions": { @@ -132,7 +132,7 @@ "dropTipCurrent": 0.5, "tipLength": 51.7 }, - "p50_multi_v2": { + "p50_multi_v13": { "displayName": "P50 8-Channel", "nominalMaxVolumeUl": 50, "plungerPositions": { @@ -170,7 +170,7 @@ "dropTipCurrent": 0.5, "tipLength": 51.7 }, - "p300_single_v2": { + "p300_single_v13": { "displayName": "P300 Single-Channel", "nominalMaxVolumeUl": 300, "plungerPositions": { @@ -208,7 +208,7 @@ "dropTipCurrent": 0.5, "tipLength": 51.7 }, - "p300_multi_v2": { + "p300_multi_v13": { "displayName": "P300 8-Channel", "nominalMaxVolumeUl": 300, "plungerPositions": { @@ -246,7 +246,7 @@ "dropTipCurrent": 0.5, "tipLength": 76.7 }, - "p1000_single_v2": { + "p1000_single_v13": { "displayName": "P1000 Single-channel", "nominalMaxVolumeUl": 1000, "plungerPositions": { From ddf973e76b6d7ef283b408fb2729328343e487f7 Mon Sep 17 00:00:00 2001 From: andySigler Date: Tue, 15 May 2018 18:15:00 -0400 Subject: [PATCH 09/27] adds v13 to pipette eeprom script --- api/opentrons/tools/write_pipette_memory.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/opentrons/tools/write_pipette_memory.py b/api/opentrons/tools/write_pipette_memory.py index b033ae15b5f..a7e4022d43f 100644 --- a/api/opentrons/tools/write_pipette_memory.py +++ b/api/opentrons/tools/write_pipette_memory.py @@ -14,7 +14,7 @@ 'P300M': configs['p300_multi_v1'], 'P1000S': configs['p1000_single_v1'] }, - 'v2': { + 'v13': { 'P10SV13': configs['p10_single_v13'], 'P10MV13': configs['p10_multi_v13'], 'P50SV13': configs['p50_single_v13'], @@ -84,12 +84,12 @@ def _user_submitted_barcode(max_length): def _parse_model_from_barcode(barcode): model = None - # MUST iterate through v2 first, because v1 barcodes did not have + # MUST iterate through v13 first, because v1 barcodes did not have # characters to specify the version number - for version in ['v2', 'v1']: - for key in MODELS[version].keys(): - if key in barcode: - model = MODELS[version][key] + for version in ['v13', 'v1']: + for barcode_substring in MODELS[version].keys(): + if barcode.startswith(barcode_substring): + model = MODELS[version][barcode_substring] break if not model: raise Exception(BAD_BARCODE_MESSAGE.format(barcode)) From 1c6a483fa0f05ccbe75c50795d12b518789a186d Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 17 May 2018 10:13:18 -0400 Subject: [PATCH 10/27] fixes errors coming from merge with edge --- api/opentrons/instruments/pipette_config.py | 18 +++++++++--------- shared-data/robot-data/pipette-config.json | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/api/opentrons/instruments/pipette_config.py b/api/opentrons/instruments/pipette_config.py index aaddeee4a0e..014573f77f4 100644 --- a/api/opentrons/instruments/pipette_config.py +++ b/api/opentrons/instruments/pipette_config.py @@ -94,7 +94,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: if os.path.exists(config_file): with open(config_file) as conf: all_configs = json.load(conf) - cfg = all_configs[pipette_model] + cfg = all_configs.get(pipette_model, cfg) return cfg @@ -161,7 +161,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: ul_per_mm=0.77, channels=1, name='p10_single_v13', - model_offset=(0.0, 0.0, Z_OFFSET_P10), + model_offset=[0.0, 0.0, Z_OFFSET_P10], plunger_current=0.3, drop_tip_current=0.5, tip_length=33 @@ -199,7 +199,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: ul_per_mm=0.77, channels=8, name='p10_multi_v13', - model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), + model_offset=[0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI], plunger_current=0.5, drop_tip_current=0.5, tip_length=33 @@ -237,7 +237,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: ul_per_mm=3.35, channels=1, name='p50_single_v13', - model_offset=(0.0, 0.0, Z_OFFSET_P50), + model_offset=[0.0, 0.0, Z_OFFSET_P50], plunger_current=0.3, drop_tip_current=0.5, tip_length=51.7 @@ -275,7 +275,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: ul_per_mm=3.35, channels=8, name='p50_multi_v13', - model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), + model_offset=[0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI], plunger_current=0.5, drop_tip_current=0.5, tip_length=51.7 @@ -313,7 +313,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: ul_per_mm=18.7, channels=1, name='p300_single_v13', - model_offset=(0.0, 0.0, Z_OFFSET_P300), + model_offset=[0.0, 0.0, Z_OFFSET_P300], plunger_current=0.3, drop_tip_current=0.5, tip_length=51.7 @@ -351,7 +351,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: ul_per_mm=19, channels=8, name='p300_multi_v13', - model_offset=(0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI), + model_offset=[0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI], plunger_current=0.5, drop_tip_current=0.5, tip_length=51.7 @@ -380,7 +380,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: plunger_positions={ 'top': 19, 'bottom': 2.5, - 'blow_out': 0.5, + 'blow_out': -0.5, 'drop_tip': -4 }, pick_up_current=0.1, @@ -389,7 +389,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: ul_per_mm=65, channels=1, name='p1000_single_v13', - model_offset=(0.0, 0.0, Z_OFFSET_P1000), + model_offset=[0.0, 0.0, Z_OFFSET_P1000], plunger_current=0.5, drop_tip_current=0.5, tip_length=76.7 diff --git a/shared-data/robot-data/pipette-config.json b/shared-data/robot-data/pipette-config.json index 9a725953944..79daa95441e 100644 --- a/shared-data/robot-data/pipette-config.json +++ b/shared-data/robot-data/pipette-config.json @@ -70,7 +70,7 @@ "dispenseFlowRate": 10, "ulPerMm": 0.77, "channels": 8, - "modelOffset": [0.0, 28.0, -25.8], + "modelOffset": [0.0, 31.5, -25.8], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, "tipLength": 33 @@ -146,7 +146,7 @@ "dispenseFlowRate": 50, "ulPerMm": 3.35, "channels": 8, - "modelOffset": [0.0, 28.0, -25.8], + "modelOffset": [0.0, 31.5, -25.8], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, "tipLength": 51.7 @@ -222,7 +222,7 @@ "dispenseFlowRate": 300, "ulPerMm": 19, "channels": 8, - "modelOffset": [0.0, 28.0, -25.8], + "modelOffset": [0.0, 31.5, -25.8], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, "tipLength": 51.7 From f8e0dad6288f32aaa34c083423819c3c95fca69c Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 17 May 2018 10:25:40 -0400 Subject: [PATCH 11/27] re-cache instruments after a simulate --- api/opentrons/api/session.py | 1 + api/opentrons/robot/robot.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/opentrons/api/session.py b/api/opentrons/api/session.py index 7e40f77cf91..d17d07f0752 100755 --- a/api/opentrons/api/session.py +++ b/api/opentrons/api/session.py @@ -121,6 +121,7 @@ def on_command(message): exec(self._protocol, {}) finally: robot._driver.connect() + robot.cache_instrument_models() unsubscribe() # Accumulate containers, instruments, interactions from commands diff --git a/api/opentrons/robot/robot.py b/api/opentrons/robot/robot.py index 6ef88dec4df..40bf404d2b5 100755 --- a/api/opentrons/robot/robot.py +++ b/api/opentrons/robot/robot.py @@ -956,7 +956,7 @@ def get_attached_pipettes(self, default=None): } # to allow any model of pipette to be simulated on a robot, pipette - # models below are updated when simulating, so regardless of what is + # models below are overwritten while simulating, so regardless of what is # currently attached/cached, any pipette model can be simulated if self.is_simulating(): default_version = list(pipette_config.configs.values())[0].name From 8678c39fff64c5e96b4dc3ce06dfa2333f045bb5 Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 17 May 2018 13:10:47 -0400 Subject: [PATCH 12/27] pass model strings, not configs --- api/opentrons/tools/write_pipette_memory.py | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/api/opentrons/tools/write_pipette_memory.py b/api/opentrons/tools/write_pipette_memory.py index a7e4022d43f..8acb04f8adc 100644 --- a/api/opentrons/tools/write_pipette_memory.py +++ b/api/opentrons/tools/write_pipette_memory.py @@ -6,22 +6,22 @@ MODELS = { 'v1': { - 'P10S': configs['p10_single_v1'], - 'P10M': configs['p10_multi_v1'], - 'P50S': configs['p50_single_v1'], - 'P50M': configs['p50_multi_v1'], - 'P300S': configs['p300_single_v1'], - 'P300M': configs['p300_multi_v1'], - 'P1000S': configs['p1000_single_v1'] + 'P10S': 'p10_single_v1', + 'P10M': 'p10_multi_v1', + 'P50S': 'p50_single_v1', + 'P50M': 'p50_multi_v1', + 'P300S': 'p300_single_v1', + 'P300M': 'p300_multi_v1', + 'P1000S': 'p1000_single_v1' }, 'v13': { - 'P10SV13': configs['p10_single_v13'], - 'P10MV13': configs['p10_multi_v13'], - 'P50SV13': configs['p50_single_v13'], - 'P50MV13': configs['p50_multi_v13'], - 'P3HSV13': configs['p300_single_v13'], - 'P3HMV13': configs['p300_multi_v13'], - 'P1KSV13': configs['p1000_single_v13'] + 'P10SV13': 'p10_single_v13', + 'P10MV13': 'p10_multi_v13', + 'P50SV13': 'p50_single_v13', + 'P50MV13': 'p50_multi_v13', + 'P3HSV13': 'p300_single_v13', + 'P3HMV13': 'p300_multi_v13', + 'P1KSV13': 'p1000_single_v13' } } From 2cbbd4af8acec80d29f7b069c137ce9fee00ac3f Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 17 May 2018 13:42:42 -0400 Subject: [PATCH 13/27] print model number after writing --- api/opentrons/tools/write_pipette_memory.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/api/opentrons/tools/write_pipette_memory.py b/api/opentrons/tools/write_pipette_memory.py index 8acb04f8adc..4402c2b9474 100644 --- a/api/opentrons/tools/write_pipette_memory.py +++ b/api/opentrons/tools/write_pipette_memory.py @@ -89,11 +89,8 @@ def _parse_model_from_barcode(barcode): for version in ['v13', 'v1']: for barcode_substring in MODELS[version].keys(): if barcode.startswith(barcode_substring): - model = MODELS[version][barcode_substring] - break - if not model: - raise Exception(BAD_BARCODE_MESSAGE.format(barcode)) - return model + return MODELS[version][barcode_substring] + raise Exception(BAD_BARCODE_MESSAGE.format(barcode)) def main(robot): @@ -102,7 +99,7 @@ def main(robot): model = _parse_model_from_barcode(barcode) check_previous_data(robot, 'right') write_identifiers(robot, 'right', barcode, model) - print('PASS: Saved -> {}'.format(barcode)) + print('PASS: Saved -> {0} (model {1})'.format(barcode, model)) except KeyboardInterrupt: exit() except Exception as e: From ef7a4188652b59610c70475fafb5bf66c3dfd142 Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 17 May 2018 20:52:32 -0400 Subject: [PATCH 14/27] simplifies logic for defaulting to pipettes while simulating --- api/opentrons/__init__.py | 24 ++++++++++-------------- api/opentrons/robot/robot.py | 19 +++++-------------- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/api/opentrons/__init__.py b/api/opentrons/__init__.py index 291166e017e..8411bf89004 100755 --- a/api/opentrons/__init__.py +++ b/api/opentrons/__init__.py @@ -223,23 +223,19 @@ def _create_pipette_from_config( p.set_pick_up_current(config.pick_up_current) return p - def _retrieve_version_number(self, mount, expected_model): + def _retrieve_version_number(self, mount, expected_model_substring): # pass a default pipette model-version, for when robot is simulating # this allows any pipette to be simulated, regardless of what is # actually attached/cached on the robot's mounts - default_version = expected_model + '_v1' - pipettes_attached = robot.get_attached_pipettes( - default={mount: {'model': default_version}}) - found_version = pipettes_attached[mount]['model'] - if not found_version: - found_version = default_version - - # check that the model string is a substring of the version - # eg: check that 'p10_single' is inside the found 'p10_single_v13' - if expected_model not in found_version: - raise TypeError( - 'Found unexpected Pipette attached: {}'.format(found_version)) - return found_version + default_model = expected_model_substring + '_v1' # default to v1 + if robot.is_simulating(): + return default_model + + attached_model = robot.get_attached_pipettes()[mount]['model'] + if expected_model_substring in attached_model: + return attached_model + else: + return default_model instruments = InstrumentsWrapper(robot) diff --git a/api/opentrons/robot/robot.py b/api/opentrons/robot/robot.py index 7fb1bd00cb3..7948372ce95 100755 --- a/api/opentrons/robot/robot.py +++ b/api/opentrons/robot/robot.py @@ -931,14 +931,10 @@ def halt(self): self.reset() self.home() - def get_attached_pipettes(self, default=None): + def get_attached_pipettes(self): """ Gets model names of attached pipettes - default: dict - keys are 'left' and 'right', with each value being a dict of - pipette properties to overwrite while in simulating mode - :return: :dict with keys 'left' and 'right' and a model string for each mount, or 'uncommissioned' if no model string available """ @@ -959,15 +955,10 @@ def get_attached_pipettes(self, default=None): # is currently attached/cached, any pipette model can be simulated if self.is_simulating(): default_version = list(pipette_config.configs.values())[0].name - if not default: - default = { - 'left': {'model': default_version}, - 'right': {'model': default_version} - } - if 'left' in default: - left_data.update(default['left']) - if 'right' in default: - right_data.update(default['right']) + if not left_data['model']: + left_data['model'] = default_version + if not right_data['model']: + right_data['model'] = default_version # add tip-length left_model = left_data.get('model') From de865f2d111f03e84c3b0908ae21fe3ae6807257 Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 17 May 2018 21:14:09 -0400 Subject: [PATCH 15/27] if attached model is none, don't parse it --- api/opentrons/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/opentrons/__init__.py b/api/opentrons/__init__.py index 8411bf89004..692db356b10 100755 --- a/api/opentrons/__init__.py +++ b/api/opentrons/__init__.py @@ -232,7 +232,7 @@ def _retrieve_version_number(self, mount, expected_model_substring): return default_model attached_model = robot.get_attached_pipettes()[mount]['model'] - if expected_model_substring in attached_model: + if attached_model and expected_model_substring in attached_model: return attached_model else: return default_model From 4da11b2e5058e66b9a603f20fc98ce7a168419da Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 17 May 2018 21:17:39 -0400 Subject: [PATCH 16/27] removes unused import from pipette eeprom writing script --- api/opentrons/tools/write_pipette_memory.py | 42 ++++++++------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/api/opentrons/tools/write_pipette_memory.py b/api/opentrons/tools/write_pipette_memory.py index fc36b3efb52..727ec078da2 100644 --- a/api/opentrons/tools/write_pipette_memory.py +++ b/api/opentrons/tools/write_pipette_memory.py @@ -2,24 +2,13 @@ WRITE_FAIL_MESSAGE = 'Data not saved, HOLD BUTTON' MODELS = { - 'v1': { - 'P10S': 'p10_single_v1', - 'P10M': 'p10_multi_v1', - 'P50S': 'p50_single_v1', - 'P50M': 'p50_multi_v1', - 'P300S': 'p300_single_v1', - 'P300M': 'p300_multi_v1', - 'P1000S': 'p1000_single_v1' - }, - 'v13': { - 'P10SV13': 'p10_single_v13', - 'P10MV13': 'p10_multi_v13', - 'P50SV13': 'p50_single_v13', - 'P50MV13': 'p50_multi_v13', - 'P3HSV13': 'p300_single_v13', - 'P3HMV13': 'p300_multi_v13', - 'P1KSV13': 'p1000_single_v13' - } + 'P10S': 'p10_single_v1', + 'P10M': 'p10_multi_v1', + 'P50S': 'p50_single_v1', + 'P50M': 'p50_multi_v1', + 'P300S': 'p300_single_v1', + 'P300M': 'p300_multi_v1', + 'P1000S': 'p1000_single_v1' } @@ -80,13 +69,14 @@ def _user_submitted_barcode(max_length): def _parse_model_from_barcode(barcode): - # MUST iterate through v13 first, because v1 barcodes did not have - # characters to specify the version number - for version in ['v13', 'v1']: - for barcode_substring in MODELS[version].keys(): - if barcode.startswith(barcode_substring): - return MODELS[version][barcode_substring] - raise Exception(BAD_BARCODE_MESSAGE.format(barcode)) + model = None + for key in MODELS.keys(): + if key in barcode: + model = MODELS[key] + break + if not model: + raise Exception(BAD_BARCODE_MESSAGE.format(barcode)) + return model def main(robot): @@ -95,7 +85,7 @@ def main(robot): model = _parse_model_from_barcode(barcode) check_previous_data(robot, 'right') write_identifiers(robot, 'right', barcode, model) - print('PASS: Saved -> {0} (model {1})'.format(barcode, model)) + print('PASS: Saved -> {}'.format(barcode)) except KeyboardInterrupt: exit() except Exception as e: From 96429fb722585febb4e09d98aaf5c65faac6a661 Mon Sep 17 00:00:00 2001 From: andySigler Date: Wed, 30 May 2018 16:45:35 -0400 Subject: [PATCH 17/27] fixes tests after merging in edge --- .../drivers/smoothie_drivers/driver_3_0.py | 3 ++- api/opentrons/robot/robot.py | 23 ++++--------------- .../server/test_control_endpoints.py | 11 +-------- 3 files changed, 8 insertions(+), 29 deletions(-) diff --git a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py index e03d4afd8de..3f793609b4b 100755 --- a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py +++ b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py @@ -8,6 +8,7 @@ from opentrons.drivers import serial_communication from opentrons.drivers.rpi_drivers import gpio +from opentrons.instruments.pipette_config import configs ''' - Driver is responsible for providing an interface for motion control - Driver is the only system component that knows about GCODES or how smoothie @@ -371,7 +372,7 @@ def read_pipette_model(self, mount): :return :dict with key 'model' and model string as value, or None ''' if self.simulating: - return None + res = list(configs.values())[0].name else: res = self._read_from_pipette( GCODES['READ_INSTRUMENT_MODEL'], mount) diff --git a/api/opentrons/robot/robot.py b/api/opentrons/robot/robot.py index 822b3e4c33b..cb9530f8989 100755 --- a/api/opentrons/robot/robot.py +++ b/api/opentrons/robot/robot.py @@ -899,38 +899,25 @@ def get_attached_pipettes(self): :return: :dict with keys 'left' and 'right' and a model string for each mount, or 'uncommissioned' if no model string available """ - # get the cached pipette model numbers left_data = { 'mount_axis': 'z', 'plunger_axis': 'b', 'model': self.model_by_mount['left'], } + left_model = left_data.get('model') + if left_model: + tip_length = pipette_config.configs[left_model].tip_length + left_data.update({'tip_length': tip_length}) + right_data = { 'mount_axis': 'a', 'plunger_axis': 'c', 'model': self.model_by_mount['right'] } - - # to allow any model of pipette to be simulated on a robot, pipette - # models below are overwritten while simulating, so regardless of what - # is currently attached/cached, any pipette model can be simulated - if self.is_simulating(): - default_version = list(pipette_config.configs.values())[0].name - if not left_data['model']: - left_data['model'] = default_version - if not right_data['model']: - right_data['model'] = default_version - - # add tip-length - left_model = left_data.get('model') - if left_model: - tip_length = pipette_config.configs[left_model].tip_length - left_data.update({'tip_length': tip_length}) right_model = right_data.get('model') if right_model: tip_length = pipette_config.configs[right_model].tip_length right_data.update({'tip_length': tip_length}) - return { 'left': left_data, 'right': right_data diff --git a/api/tests/opentrons/server/test_control_endpoints.py b/api/tests/opentrons/server/test_control_endpoints.py index 27d36bf6f5a..b53d6c09586 100644 --- a/api/tests/opentrons/server/test_control_endpoints.py +++ b/api/tests/opentrons/server/test_control_endpoints.py @@ -70,17 +70,8 @@ async def test_get_cached_pipettes( app = init(loop) cli = await loop.create_task(test_client(app)) - monkeypatch.setattr(robot, 'is_simulating', lambda: False) - model = list(configs.values())[0] - def dummy_model(mount): - return model.name - - monkeypatch.setattr(robot._driver, 'read_pipette_model', dummy_model) - - robot.cache_instrument_models() # do an initial caching - expected = { 'left': { 'model': model.name, @@ -111,7 +102,7 @@ def dummy_model(mount): resp1 = await cli.get('/pipettes') text1 = await resp1.text() assert resp1.status == 200 - assert json.loads(text1) == expected # models aren't cached, so no change + assert json.loads(text1) == expected expected2 = { 'left': { From c2c6d72547a92e6e968c13373c492e703b16b9e9 Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 31 May 2018 09:33:23 -0400 Subject: [PATCH 18/27] changes v13 to v1.3 to avoid confusion --- .../drivers/smoothie_drivers/driver_3_0.py | 5 +++ api/opentrons/instruments/pipette_config.py | 42 +++++++++---------- api/opentrons/tools/write_pipette_memory.py | 20 ++++----- api/tests/opentrons/tools/test_qc_scripts.py | 14 +++---- shared-data/robot-data/pipette-config.json | 14 +++---- 5 files changed, 50 insertions(+), 45 deletions(-) diff --git a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py index 3f793609b4b..d2be48165ab 100755 --- a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py +++ b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py @@ -380,6 +380,11 @@ def read_pipette_model(self, mount): # Backward compatibility for pipettes programmed with model # strings that did not include the _v# designation res = res + '_v1' + elif res.endswith('_v13'): + # Backward compatibility for pipettes programmed with model + # strings that did not include the "." to seperate version + # major and minor values + res = res.replace('_v13', 'v1.3') return res diff --git a/api/opentrons/instruments/pipette_config.py b/api/opentrons/instruments/pipette_config.py index 014573f77f4..c2198340d84 100644 --- a/api/opentrons/instruments/pipette_config.py +++ b/api/opentrons/instruments/pipette_config.py @@ -148,7 +148,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=33 ) -p10_single_v13 = pipette_config( +p10_single_v1_3 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 0, @@ -160,7 +160,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=0.77, channels=1, - name='p10_single_v13', + name='p10_single_v1.3', model_offset=[0.0, 0.0, Z_OFFSET_P10], plunger_current=0.3, drop_tip_current=0.5, @@ -186,7 +186,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=33 ) -p10_multi_v13 = pipette_config( +p10_multi_v1_3 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -198,7 +198,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=0.77, channels=8, - name='p10_multi_v13', + name='p10_multi_v1.3', model_offset=[0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI], plunger_current=0.5, drop_tip_current=0.5, @@ -224,7 +224,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=51.7 ) -p50_single_v13 = pipette_config( +p50_single_v1_3 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -236,7 +236,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=3.35, channels=1, - name='p50_single_v13', + name='p50_single_v1.3', model_offset=[0.0, 0.0, Z_OFFSET_P50], plunger_current=0.3, drop_tip_current=0.5, @@ -262,7 +262,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=51.7 ) -p50_multi_v13 = pipette_config( +p50_multi_v1_3 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -274,7 +274,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=3.35, channels=8, - name='p50_multi_v13', + name='p50_multi_v1.3', model_offset=[0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI], plunger_current=0.5, drop_tip_current=0.5, @@ -300,7 +300,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=51.7 ) -p300_single_v13 = pipette_config( +p300_single_v1_3 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -312,7 +312,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=18.7, channels=1, - name='p300_single_v13', + name='p300_single_v1.3', model_offset=[0.0, 0.0, Z_OFFSET_P300], plunger_current=0.3, drop_tip_current=0.5, @@ -338,7 +338,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=51.7 ) -p300_multi_v13 = pipette_config( +p300_multi_v1_3 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -350,7 +350,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=19, channels=8, - name='p300_multi_v13', + name='p300_multi_v1.3', model_offset=[0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI], plunger_current=0.5, drop_tip_current=0.5, @@ -376,7 +376,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: tip_length=76.7 ) -p1000_single_v13 = pipette_config( +p1000_single_v1_3 = pipette_config( plunger_positions={ 'top': 19, 'bottom': 2.5, @@ -388,7 +388,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: dispense_flow_rate=1000 / DEFAULT_DISPENSE_SECONDS, ul_per_mm=65, channels=1, - name='p1000_single_v13', + name='p1000_single_v1.3', model_offset=[0.0, 0.0, Z_OFFSET_P1000], plunger_current=0.5, drop_tip_current=0.5, @@ -397,19 +397,19 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: fallback_configs = { 'p10_single_v1': p10_single_v1, - 'p10_single_v13': p10_single_v13, + 'p10_single_v1.3': p10_single_v1_3, 'p10_multi_v1': p10_multi_v1, - 'p10_multi_v13': p10_multi_v13, + 'p10_multi_v1.3': p10_multi_v1_3, 'p50_single_v1': p50_single_v1, - 'p50_single_v13': p50_single_v13, + 'p50_single_v1.3': p50_single_v1_3, 'p50_multi_v1': p50_multi_v1, - 'p50_multi_v13': p50_multi_v13, + 'p50_multi_v1.3': p50_multi_v1_3, 'p300_single_v1': p300_single_v1, - 'p300_single_v13': p300_single_v13, + 'p300_single_v1.3': p300_single_v1_3, 'p300_multi_v1': p300_multi_v1, - 'p300_multi_v13': p300_multi_v13, + 'p300_multi_v1.3': p300_multi_v1_3, 'p1000_single_v1': p1000_single_v1, - 'p1000_single_v13': p1000_single_v13, + 'p1000_single_v1.3': p1000_single_v1_3, } diff --git a/api/opentrons/tools/write_pipette_memory.py b/api/opentrons/tools/write_pipette_memory.py index 8da5db733e7..1c3a7ec509d 100644 --- a/api/opentrons/tools/write_pipette_memory.py +++ b/api/opentrons/tools/write_pipette_memory.py @@ -11,14 +11,14 @@ 'P300M': 'p300_multi_v1', 'P1000S': 'p1000_single_v1' }, - 'v13': { - 'P10SV13': 'p10_single_v13', - 'P10MV13': 'p10_multi_v13', - 'P50SV13': 'p50_single_v13', - 'P50MV13': 'p50_multi_v13', - 'P3HSV13': 'p300_single_v13', - 'P3HMV13': 'p300_multi_v13', - 'P1KSV13': 'p1000_single_v13' + 'v1.3': { + 'P10SV13': 'p10_single_v1.3', + 'P10MV13': 'p10_multi_v1.3', + 'P50SV13': 'p50_single_v1.3', + 'P50MV13': 'p50_multi_v1.3', + 'P3HSV13': 'p300_single_v1.3', + 'P3HMV13': 'p300_multi_v1.3', + 'P1KSV13': 'p1000_single_v1.3' } } @@ -83,9 +83,9 @@ def _user_submitted_barcode(max_length): def _parse_model_from_barcode(barcode): - # MUST iterate through v13 first, because v1 barcodes did not have + # MUST iterate through v1.3 first, because v1 barcodes did not have # characters to specify the version number - for version in ['v13', 'v1']: + for version in ['v1.3', 'v1']: for barcode_substring in MODELS[version].keys(): if barcode.startswith(barcode_substring): return MODELS[version][barcode_substring] diff --git a/api/tests/opentrons/tools/test_qc_scripts.py b/api/tests/opentrons/tools/test_qc_scripts.py index 44e2e3632bf..62c14ff3842 100644 --- a/api/tests/opentrons/tools/test_qc_scripts.py +++ b/api/tests/opentrons/tools/test_qc_scripts.py @@ -8,13 +8,13 @@ 'P300S20180101A01': 'p300_single_v1', 'P300M20180101A01': 'p300_multi_v1', 'P1000S20180101A01': 'p1000_single_v1', - 'P10SV1318010101': 'p10_single_v13', - 'P10MV1318010102': 'p10_multi_v13', - 'P50SV1318010103': 'p50_single_v13', - 'P50MV1318010104': 'p50_multi_v13', - 'P3HSV1318010105': 'p300_single_v13', - 'P3HMV1318010106': 'p300_multi_v13', - 'P1KSV1318010107': 'p1000_single_v13' + 'P10SV1318010101': 'p10_single_v1.3', + 'P10MV1318010102': 'p10_multi_v1.3', + 'P50SV1318010103': 'p50_single_v1.3', + 'P50MV1318010104': 'p50_multi_v1.3', + 'P3HSV1318010105': 'p300_single_v1.3', + 'P3HMV1318010106': 'p300_multi_v1.3', + 'P1KSV1318010107': 'p1000_single_v1.3' } diff --git a/shared-data/robot-data/pipette-config.json b/shared-data/robot-data/pipette-config.json index 79daa95441e..e434e56866c 100644 --- a/shared-data/robot-data/pipette-config.json +++ b/shared-data/robot-data/pipette-config.json @@ -18,7 +18,7 @@ "dropTipCurrent": 0.5, "tipLength": 33 }, - "p10_single_v13": { + "p10_single_v1.3": { "displayName": "P10 Single-Channel", "nominalMaxVolumeUl": 10, "plungerPositions": { @@ -56,7 +56,7 @@ "dropTipCurrent": 0.5, "tipLength": 33 }, - "p10_multi_v13": { + "p10_multi_v1.3": { "displayName": "P10 8-Channel", "nominalMaxVolumeUl": 10, "plungerPositions": { @@ -94,7 +94,7 @@ "dropTipCurrent": 0.5, "tipLength": 51.7 }, - "p50_single_v13": { + "p50_single_v1.3": { "displayName": "P50 Single-Channel", "nominalMaxVolumeUl": 50, "plungerPositions": { @@ -132,7 +132,7 @@ "dropTipCurrent": 0.5, "tipLength": 51.7 }, - "p50_multi_v13": { + "p50_multi_v1.3": { "displayName": "P50 8-Channel", "nominalMaxVolumeUl": 50, "plungerPositions": { @@ -170,7 +170,7 @@ "dropTipCurrent": 0.5, "tipLength": 51.7 }, - "p300_single_v13": { + "p300_single_v1.3": { "displayName": "P300 Single-Channel", "nominalMaxVolumeUl": 300, "plungerPositions": { @@ -208,7 +208,7 @@ "dropTipCurrent": 0.5, "tipLength": 51.7 }, - "p300_multi_v13": { + "p300_multi_v1.3": { "displayName": "P300 8-Channel", "nominalMaxVolumeUl": 300, "plungerPositions": { @@ -246,7 +246,7 @@ "dropTipCurrent": 0.5, "tipLength": 76.7 }, - "p1000_single_v13": { + "p1000_single_v1.3": { "displayName": "P1000 Single-channel", "nominalMaxVolumeUl": 1000, "plungerPositions": { From 7a7da6d6bcd844df022597a14fe27cfcbf5e007b Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 31 May 2018 09:53:02 -0400 Subject: [PATCH 19/27] adds comments describing importance or robot.connect() after a simulate for pipette caching --- api/opentrons/api/session.py | 8 +++++--- api/opentrons/drivers/smoothie_drivers/driver_3_0.py | 2 +- api/opentrons/robot/robot.py | 5 +++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/api/opentrons/api/session.py b/api/opentrons/api/session.py index 13165dac2e7..460f15a32b6 100755 --- a/api/opentrons/api/session.py +++ b/api/opentrons/api/session.py @@ -119,14 +119,16 @@ def on_command(message): try: # TODO (artyom, 20171005): this will go away # once robot / driver simulation flow is fixed - robot._driver.disconnect() + robot.disconnect() if self._is_json_protocol: execute_protocol(self._protocol) else: exec(self._protocol, {}) finally: - robot._driver.connect() - robot.cache_instrument_models() + # physically attached pipettes are re-cached during robot.connect() + # which is important, because during a simulation, the robot could + # think that it holds a pipette model that it actually does not + robot.connect() unsubscribe() # Accumulate containers, instruments, interactions from commands diff --git a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py index d2be48165ab..16370ffa78a 100755 --- a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py +++ b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py @@ -380,7 +380,7 @@ def read_pipette_model(self, mount): # Backward compatibility for pipettes programmed with model # strings that did not include the _v# designation res = res + '_v1' - elif res.endswith('_v13'): + elif res and res.endswith('_v13'): # Backward compatibility for pipettes programmed with model # strings that did not include the "." to seperate version # major and minor values diff --git a/api/opentrons/robot/robot.py b/api/opentrons/robot/robot.py index cb9530f8989..fdc48338fde 100755 --- a/api/opentrons/robot/robot.py +++ b/api/opentrons/robot/robot.py @@ -501,6 +501,11 @@ def connect(self, port=None, options=None): for module in self.modules: module.connect() self.fw_version = self._driver.get_fw_version() + + # the below call to `cache_instrument_models` is relied upon by + # `Session._simulate()`, which calls `robot.connect()` after exec'ing a + # protocol. That protocol could very well have different pipettes than + # what are physically attached to the robot self.cache_instrument_models() def _update_axis_homed(self, *args): From 91dcb370867527523e6d2dc049df08bc5adf1b60 Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 31 May 2018 10:25:31 -0400 Subject: [PATCH 20/27] adds test to compare model version travel distances --- api/tests/opentrons/labware/test_pipette.py | 27 +++++++++++++++++++ .../server/test_control_endpoints.py | 1 - 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/api/tests/opentrons/labware/test_pipette.py b/api/tests/opentrons/labware/test_pipette.py index b89af42c35f..8f6ab6cd5b4 100755 --- a/api/tests/opentrons/labware/test_pipette.py +++ b/api/tests/opentrons/labware/test_pipette.py @@ -6,6 +6,33 @@ from numpy import isclose +def test_pipette_version_1_0_and_1_3_extended_travel(): + from opentrons.instruments import pipette_config + + models = [ + 'p10_single', 'p10_multi', 'p50_single', 'p50_multi', + 'p300_single', 'p300_multi', 'p1000_single' + ] + + for m in models: + robot.reset() + left = instruments._create_pipette_from_config( + config=pipette_config.load(m + '_v1'), + mount='left') + right = instruments._create_pipette_from_config( + config=pipette_config.load(m + '_v1.3'), + mount='right') + + # the difference between v1 and v1.3 is that the plunger's travel + # distance extended, allowing greater ranges for aspirate/dispense + # and blow-out. Test that all v1.3 pipette have larger travel thant v1 + left_poses = left.plunger_positions + left_diff = left_poses['top'] - left_poses['blow_out'] + right_poses = right.plunger_positions + right_diff = right_poses['top'] - right_poses['blow_out'] + assert right_diff > left_diff + + def test_pipette_models(): robot.reset() p = instruments.P10_Single(mount='left') diff --git a/api/tests/opentrons/server/test_control_endpoints.py b/api/tests/opentrons/server/test_control_endpoints.py index b53d6c09586..3438d0ea92a 100644 --- a/api/tests/opentrons/server/test_control_endpoints.py +++ b/api/tests/opentrons/server/test_control_endpoints.py @@ -71,7 +71,6 @@ async def test_get_cached_pipettes( cli = await loop.create_task(test_client(app)) model = list(configs.values())[0] - expected = { 'left': { 'model': model.name, From 894ad58cb2690d3c3b31ed860370d5aa79e336ab Mon Sep 17 00:00:00 2001 From: andySigler Date: Thu, 31 May 2018 10:34:25 -0400 Subject: [PATCH 21/27] add another barcode filter to remove unwanted newlines from barcode scanners --- api/opentrons/drivers/smoothie_drivers/driver_3_0.py | 2 +- api/opentrons/tools/write_pipette_memory.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py index 16370ffa78a..91195fccfcf 100755 --- a/api/opentrons/drivers/smoothie_drivers/driver_3_0.py +++ b/api/opentrons/drivers/smoothie_drivers/driver_3_0.py @@ -380,7 +380,7 @@ def read_pipette_model(self, mount): # Backward compatibility for pipettes programmed with model # strings that did not include the _v# designation res = res + '_v1' - elif res and res.endswith('_v13'): + elif res and '_v13' in res: # Backward compatibility for pipettes programmed with model # strings that did not include the "." to seperate version # major and minor values diff --git a/api/opentrons/tools/write_pipette_memory.py b/api/opentrons/tools/write_pipette_memory.py index 1c3a7ec509d..8421c83271f 100644 --- a/api/opentrons/tools/write_pipette_memory.py +++ b/api/opentrons/tools/write_pipette_memory.py @@ -42,7 +42,6 @@ def write_identifiers(robot, mount, new_id, new_model): robot._driver.write_pipette_id(mount, new_id) read_id = robot._driver.read_pipette_id(mount) _assert_the_same(new_id, read_id['pipette_id']) - robot._driver.write_pipette_model(mount, new_model) read_model = robot._driver.read_pipette_model(mount) _assert_the_same(new_model, read_model) @@ -50,10 +49,7 @@ def write_identifiers(robot, mount, new_id, new_model): def check_previous_data(robot, mount): old_id = robot._driver.read_pipette_id(mount) - if old_id.get('pipette_id'): - old_id = old_id.get('pipette_id') - else: - old_id = None + old_id = old_id.get('pipette_id') old_model = robot._driver.read_pipette_model(mount) if old_id and old_model: print( @@ -79,6 +75,7 @@ def _user_submitted_barcode(max_length): # remove all characters before the letter P # for example, remove ASCII selector code "\x1b(B" on chinese keyboards barcode = barcode[barcode.index('P'):] + barcode = barcode.split('\r')[0].split('\n')[0] # remove any newlines return barcode From f57d930f9c6204db07abef2913f2ee8b86536a97 Mon Sep 17 00:00:00 2001 From: andySigler Date: Fri, 1 Jun 2018 16:44:37 -0400 Subject: [PATCH 22/27] includes config changes from piecewise merge; updates plunger positions from QC testing --- api/opentrons/instruments/pipette_config.py | 98 ++++++++--------- api/tests/opentrons/labware/test_pipette.py | 4 +- shared-data/robot-data/pipette-config.json | 112 ++++++++++---------- 3 files changed, 107 insertions(+), 107 deletions(-) diff --git a/api/opentrons/instruments/pipette_config.py b/api/opentrons/instruments/pipette_config.py index b3e452ff795..aa3edc04310 100644 --- a/api/opentrons/instruments/pipette_config.py +++ b/api/opentrons/instruments/pipette_config.py @@ -131,10 +131,10 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: # TODO config data into the wheel file perhaps. Needs research. p10_single_v1 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 2.5, - 'blow_out': -0.5, - 'drop_tip': -4 + 'top': 19.5, + 'bottom': 2, + 'blow_out': -1, + 'drop_tip': -4.5 }, pick_up_current=0.1, aspirate_flow_rate=10 / DEFAULT_ASPIRATE_SECONDS, @@ -150,29 +150,29 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: p10_single_v1_3 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 0, - 'blow_out': -2, - 'drop_tip': -5.5 + 'top': 19.5, + 'bottom': 0.5, + 'blow_out': -2.5, + 'drop_tip': -6 }, pick_up_current=0.1, aspirate_flow_rate=10 / DEFAULT_ASPIRATE_SECONDS, dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, - ul_per_mm=0.77, channels=1, name='p10_single_v1.3', model_offset=[0.0, 0.0, Z_OFFSET_P10], plunger_current=0.3, drop_tip_current=0.5, + max_volume=10, tip_length=33 ) p10_multi_v1 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 4, - 'blow_out': 1, - 'drop_tip': -4.5 + 'top': 19.5, + 'bottom': 2, + 'blow_out': -1, + 'drop_tip': -4 }, pick_up_current=0.2, aspirate_flow_rate=10 / DEFAULT_ASPIRATE_SECONDS, @@ -188,26 +188,26 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: p10_multi_v1_3 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 2.5, - 'blow_out': -0.5, + 'top': 19.5, + 'bottom': 0.5, + 'blow_out': -2.5, 'drop_tip': -5.5 }, pick_up_current=0.2, aspirate_flow_rate=10 / DEFAULT_ASPIRATE_SECONDS, dispense_flow_rate=10 / DEFAULT_DISPENSE_SECONDS, - ul_per_mm=0.77, channels=8, name='p10_multi_v1.3', model_offset=[0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI], plunger_current=0.5, drop_tip_current=0.5, + max_volume=10, tip_length=33 ) p50_single_v1 = pipette_config( plunger_positions={ - 'top': 19, + 'top': 19.5, 'bottom': 2.5, 'blow_out': 2, 'drop_tip': -5 @@ -226,26 +226,26 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: p50_single_v1_3 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 2.5, - 'blow_out': 1, - 'drop_tip': -4.5 + 'top': 19.5, + 'bottom': 2, + 'blow_out': 0.5, + 'drop_tip': -6 }, pick_up_current=0.1, aspirate_flow_rate=50 / DEFAULT_ASPIRATE_SECONDS, dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, - ul_per_mm=3.35, channels=1, name='p50_single_v1.3', model_offset=[0.0, 0.0, Z_OFFSET_P50], plunger_current=0.3, drop_tip_current=0.5, + max_volume=50, tip_length=51.7 ) p50_multi_v1 = pipette_config( plunger_positions={ - 'top': 19, + 'top': 19.5, 'bottom': 2.5, 'blow_out': 2, 'drop_tip': -4 @@ -264,29 +264,29 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: p50_multi_v1_3 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 2.5, + 'top': 19.5, + 'bottom': 2, 'blow_out': 0.5, - 'drop_tip': -4 + 'drop_tip': -5 }, pick_up_current=0.3, aspirate_flow_rate=50 / DEFAULT_ASPIRATE_SECONDS, dispense_flow_rate=50 / DEFAULT_DISPENSE_SECONDS, - ul_per_mm=3.35, channels=8, name='p50_multi_v1.3', model_offset=[0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI], plunger_current=0.5, drop_tip_current=0.5, + max_volume=50, tip_length=51.7 ) p300_single_v1 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 2.5, - 'blow_out': 1, - 'drop_tip': -5 + 'top': 19.5, + 'bottom': 1.5, + 'blow_out': 0, + 'drop_tip': -4 }, pick_up_current=0.1, aspirate_flow_rate=300 / DEFAULT_ASPIRATE_SECONDS, @@ -302,29 +302,29 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: p300_single_v1_3 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 2.5, - 'blow_out': -0.5, - 'drop_tip': -5 + 'top': 19.5, + 'bottom': 1.5, + 'blow_out': -1.5, + 'drop_tip': -5.5 }, pick_up_current=0.1, aspirate_flow_rate=300 / DEFAULT_ASPIRATE_SECONDS, dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, - ul_per_mm=18.7, channels=1, name='p300_single_v1.3', model_offset=[0.0, 0.0, Z_OFFSET_P300], plunger_current=0.3, drop_tip_current=0.5, + max_volume=300, tip_length=51.7 ) p300_multi_v1 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 3, - 'blow_out': 1, - 'drop_tip': -3.5 + 'top': 19.5, + 'bottom': 3.5, + 'blow_out': 3, + 'drop_tip': -2 }, pick_up_current=0.3, aspirate_flow_rate=300 / DEFAULT_ASPIRATE_SECONDS, @@ -340,26 +340,26 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: p300_multi_v1_3 = pipette_config( plunger_positions={ - 'top': 19, - 'bottom': 2.5, - 'blow_out': -0.5, - 'drop_tip': -5 + 'top': 19.5, + 'bottom': 3.5, + 'blow_out': 1.5, + 'drop_tip': -3.5 }, pick_up_current=0.3, aspirate_flow_rate=300 / DEFAULT_ASPIRATE_SECONDS, dispense_flow_rate=300 / DEFAULT_DISPENSE_SECONDS, - ul_per_mm=19, channels=8, name='p300_multi_v1.3', model_offset=[0.0, Y_OFFSET_MULTI, Z_OFFSET_MULTI], plunger_current=0.5, drop_tip_current=0.5, + max_volume=300, tip_length=51.7 ) p1000_single_v1 = pipette_config( plunger_positions={ - 'top': 19, + 'top': 19.5, 'bottom': 3, 'blow_out': 1, 'drop_tip': -5 @@ -378,7 +378,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: p1000_single_v1_3 = pipette_config( plunger_positions={ - 'top': 19, + 'top': 19.5, 'bottom': 2.5, 'blow_out': -0.5, 'drop_tip': -4 @@ -386,12 +386,12 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: pick_up_current=0.1, aspirate_flow_rate=1000 / DEFAULT_ASPIRATE_SECONDS, dispense_flow_rate=1000 / DEFAULT_DISPENSE_SECONDS, - ul_per_mm=65, channels=1, name='p1000_single_v1.3', model_offset=[0.0, 0.0, Z_OFFSET_P1000], plunger_current=0.5, drop_tip_current=0.5, + max_volume=1000, tip_length=76.7 ) diff --git a/api/tests/opentrons/labware/test_pipette.py b/api/tests/opentrons/labware/test_pipette.py index a4bd52193f7..4ecdbca9fd1 100755 --- a/api/tests/opentrons/labware/test_pipette.py +++ b/api/tests/opentrons/labware/test_pipette.py @@ -197,7 +197,7 @@ def test_aspirate_move_to(): robot.poses, p300.instrument_actuator) - assert (current_pos == (7.889964, 0.0, 0.0)).all() + assert (current_pos == (6.889964, 0.0, 0.0)).all() current_pos = pose_tracker.absolute(robot.poses, p300) assert isclose(current_pos, (175.34, 127.94, 10.5)).all() @@ -225,7 +225,7 @@ def test_dispense_move_to(): current_pos = pose_tracker.absolute( robot.poses, p300.instrument_actuator) - assert (current_pos == (2.5, 0.0, 0.0)).all() + assert (current_pos == (1.5, 0.0, 0.0)).all() current_pos = pose_tracker.absolute(robot.poses, p300) assert isclose(current_pos, (175.34, 127.94, 10.5)).all() diff --git a/shared-data/robot-data/pipette-config.json b/shared-data/robot-data/pipette-config.json index e434e56866c..9360fc820c2 100644 --- a/shared-data/robot-data/pipette-config.json +++ b/shared-data/robot-data/pipette-config.json @@ -3,83 +3,83 @@ "displayName": "P10 Single-Channel", "nominalMaxVolumeUl": 10, "plungerPositions": { - "top": 19, - "bottom": 2.5, - "blowOut": -0.5, - "dropTip": -4 + "top": 19.5, + "bottom": 2, + "blowOut": -1, + "dropTip": -4.5 }, "pickUpCurrent": 0.1, "aspirateFlowRate": 5, "dispenseFlowRate": 10, - "ulPerMm": 0.77, "channels": 1, "modelOffset": [0.0, 0.0, -13], "plungerCurrent": 0.3, "dropTipCurrent": 0.5, + "maxVolume": 10, "tipLength": 33 }, "p10_single_v1.3": { "displayName": "P10 Single-Channel", "nominalMaxVolumeUl": 10, "plungerPositions": { - "top": 19, - "bottom": 0, - "blowOut": -2, - "dropTip": -5.5 + "top": 19.5, + "bottom": 0.5, + "blowOut": -2.5, + "dropTip": -6 }, "pickUpCurrent": 0.1, "aspirateFlowRate": 5, "dispenseFlowRate": 10, - "ulPerMm": 0.77, "channels": 1, "modelOffset": [0.0, 0.0, -13], "plungerCurrent": 0.3, "dropTipCurrent": 0.5, + "maxVolume": 10, "tipLength": 33 }, "p10_multi_v1": { "displayName": "P10 8-Channel", "nominalMaxVolumeUl": 10, "plungerPositions": { - "top": 19, - "bottom": 4, - "blowOut": 1, - "dropTip": -4.5 + "top": 19.5, + "bottom": 2, + "blowOut": -1, + "dropTip": -4 }, "pickUpCurrent": 0.2, "aspirateFlowRate": 5, "dispenseFlowRate": 10, - "ulPerMm": 0.77, "channels": 8, "modelOffset": [0.0, 31.5, -25.8], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, + "maxVolume": 10, "tipLength": 33 }, "p10_multi_v1.3": { "displayName": "P10 8-Channel", "nominalMaxVolumeUl": 10, "plungerPositions": { - "top": 19, - "bottom": 2.5, - "blowOut": -0.5, + "top": 19.5, + "bottom": 0.5, + "blowOut": -2.5, "dropTip": -5.5 }, "pickUpCurrent": 0.2, "aspirateFlowRate": 5, "dispenseFlowRate": 10, - "ulPerMm": 0.77, "channels": 8, "modelOffset": [0.0, 31.5, -25.8], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, + "maxVolume": 10, "tipLength": 33 }, "p50_single_v1": { "displayName": "P50 Single-Channel", "nominalMaxVolumeUl": 50, "plungerPositions": { - "top": 19, + "top": 19.5, "bottom": 2.5, "blowOut": 2, "dropTip": -5 @@ -87,37 +87,37 @@ "pickUpCurrent": 0.1, "aspirateFlowRate": 25, "dispenseFlowRate": 50, - "ulPerMm": 3.35, "channels": 1, "modelOffset": [0.0, 0.0, 0.0], "plungerCurrent": 0.3, "dropTipCurrent": 0.5, + "maxVolume": 50, "tipLength": 51.7 }, "p50_single_v1.3": { "displayName": "P50 Single-Channel", "nominalMaxVolumeUl": 50, "plungerPositions": { - "top": 19, - "bottom": 2.5, - "blowOut": 1, - "dropTip": -4.5 + "top": 19.5, + "bottom": 2, + "blowOut": 0.5, + "dropTip": -6 }, "pickUpCurrent": 0.1, "aspirateFlowRate": 25, "dispenseFlowRate": 50, - "ulPerMm": 3.35, "channels": 1, "modelOffset": [0.0, 0.0, 0.0], "plungerCurrent": 0.3, "dropTipCurrent": 0.5, + "maxVolume": 50, "tipLength": 51.7 }, "p50_multi_v1": { "displayName": "P50 8-Channel", "nominalMaxVolumeUl": 50, "plungerPositions": { - "top": 19, + "top": 19.5, "bottom": 2.5, "blowOut": 2, "dropTip": -4 @@ -125,113 +125,113 @@ "pickUpCurrent": 0.3, "aspirateFlowRate": 25, "dispenseFlowRate": 50, - "ulPerMm": 3.35, "channels": 8, "modelOffset": [0.0, 31.5, -25.8], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, + "maxVolume": 50, "tipLength": 51.7 }, "p50_multi_v1.3": { "displayName": "P50 8-Channel", "nominalMaxVolumeUl": 50, "plungerPositions": { - "top": 19, - "bottom": 2.5, + "top": 19.5, + "bottom": 2, "blowOut": 0.5, - "dropTip": -4 + "dropTip": -5 }, "pickUpCurrent": 0.3, "aspirateFlowRate": 25, "dispenseFlowRate": 50, - "ulPerMm": 3.35, "channels": 8, "modelOffset": [0.0, 31.5, -25.8], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, + "maxVolume": 50, "tipLength": 51.7 }, "p300_single_v1": { "displayName": "P300 Single-Channel", "nominalMaxVolumeUl": 300, "plungerPositions": { - "top": 19, - "bottom": 2.5, - "blowOut": 1, - "dropTip": -5 + "top": 19.5, + "bottom": 1.5, + "blowOut": 0, + "dropTip": -4 }, "pickUpCurrent": 0.1, "aspirateFlowRate": 150, "dispenseFlowRate": 300, - "ulPerMm": 18.7, "channels": 1, "modelOffset": [0.0, 0.0, 0.0], "plungerCurrent": 0.3, "dropTipCurrent": 0.5, + "maxVolume": 300, "tipLength": 51.7 }, "p300_single_v1.3": { "displayName": "P300 Single-Channel", "nominalMaxVolumeUl": 300, "plungerPositions": { - "top": 19, - "bottom": 2.5, - "blowOut": -0.5, - "dropTip": -5 + "top": 19.5, + "bottom": 1.5, + "blowOut": -1.5, + "dropTip": -5.5 }, "pickUpCurrent": 0.1, "aspirateFlowRate": 150, "dispenseFlowRate": 300, - "ulPerMm": 18.7, "channels": 1, "modelOffset": [0.0, 0.0, 0.0], "plungerCurrent": 0.3, "dropTipCurrent": 0.5, + "maxVolume": 300, "tipLength": 51.7 }, "p300_multi_v1": { "displayName": "P300 8-Channel", "nominalMaxVolumeUl": 300, "plungerPositions": { - "top": 19, - "bottom": 3, - "blowOut": 1, - "dropTip": -3.5 + "top": 19.5, + "bottom": 3.5, + "blowOut": 3, + "dropTip": -2 }, "pickUpCurrent": 0.3, "aspirateFlowRate": 150, "dispenseFlowRate": 300, - "ulPerMm": 19, "channels": 8, "modelOffset": [0.0, 31.5, -25.8], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, + "maxVolume": 300, "tipLength": 51.7 }, "p300_multi_v1.3": { "displayName": "P300 8-Channel", "nominalMaxVolumeUl": 300, "plungerPositions": { - "top": 19, - "bottom": 2.5, - "blowOut": -0.5, - "dropTip": -5 + "top": 19.5, + "bottom": 3.5, + "blowOut": 1.5, + "dropTip": -3.5 }, "pickUpCurrent": 0.3, "aspirateFlowRate": 150, "dispenseFlowRate": 300, - "ulPerMm": 19, "channels": 8, "modelOffset": [0.0, 31.5, -25.8], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, + "maxVolume": 300, "tipLength": 51.7 }, "p1000_single_v1": { "displayName": "P1000 Single-channel", "nominalMaxVolumeUl": 1000, "plungerPositions": { - "top": 19, + "top": 19.5, "bottom": 3, "blowOut": 1, "dropTip": -5 @@ -239,18 +239,18 @@ "pickUpCurrent": 0.1, "aspirateFlowRate": 500, "dispenseFlowRate": 1000, - "ulPerMm": 65, "channels": 1, "modelOffset": [0.0, 0.0, 20.0], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, + "maxVolume": 1000, "tipLength": 76.7 }, "p1000_single_v1.3": { "displayName": "P1000 Single-channel", "nominalMaxVolumeUl": 1000, "plungerPositions": { - "top": 19, + "top": 19.5, "bottom": 2.5, "blowOut": -0.5, "dropTip": -4 @@ -258,11 +258,11 @@ "pickUpCurrent": 0.1, "aspirateFlowRate": 500, "dispenseFlowRate": 1000, - "ulPerMm": 65, "channels": 1, "modelOffset": [0.0, 0.0, 20.0], "plungerCurrent": 0.5, "dropTipCurrent": 0.5, + "maxVolume": 1000, "tipLength": 76.7 } } From 72810bf19f107a46b768091e3a99da20424fa1bd Mon Sep 17 00:00:00 2001 From: andySigler Date: Fri, 1 Jun 2018 17:26:28 -0400 Subject: [PATCH 23/27] adds new pipette models to piecewise ul/mm mapping --- api/opentrons/instruments/pipette.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/api/opentrons/instruments/pipette.py b/api/opentrons/instruments/pipette.py index 3f797956db0..5db7c9fa5fb 100755 --- a/api/opentrons/instruments/pipette.py +++ b/api/opentrons/instruments/pipette.py @@ -1417,12 +1417,19 @@ def _dispense_plunger_position(self, ul): def _key_map_pipette_functions(self, model, ul, func): function_map = { 'p10_single_v1': lambda: self._p10_single_piecewise(ul, func), + 'p10_single_v1.3': lambda: self._p10_single_piecewise(ul, func), 'p10_multi_v1': lambda: self._p10_multi_piecewise(ul, func), + 'p10_multi_v1.3': lambda: self._p10_multi_piecewise(ul, func), 'p50_single_v1': lambda: self._p50_single_piecewise(ul, func), + 'p50_single_v1.3': lambda: self._p50_single_piecewise(ul, func), 'p50_multi_v1': lambda: self._p50_multi_piecewise(ul, func), + 'p50_multi_v1.3': lambda: self._p50_multi_piecewise(ul, func), 'p300_single_v1': lambda: self._p300_single_piecewise(ul, func), + 'p300_single_v1.3': lambda: self._p300_single_piecewise(ul, func), 'p300_multi_v1': lambda: self._p300_multi_piecewise(ul, func), - 'p1000_single_v1': lambda: self._p1000_piecewise(ul, func)} + 'p300_multi_v1.3': lambda: self._p300_multi_piecewise(ul, func), + 'p1000_single_v1': lambda: self._p1000_piecewise(ul, func), + 'p1000_single_v1.3': lambda: self._p1000_piecewise(ul, func)} return function_map.get(model) From c7e4eb4971aaaffecbd73ec02e78fd597f262df2 Mon Sep 17 00:00:00 2001 From: andySigler Date: Mon, 4 Jun 2018 10:34:56 -0400 Subject: [PATCH 24/27] raises error if no usable ul_per_mm; p1000 always returns a ul_per_mm --- api/opentrons/instruments/pipette.py | 34 +++++++++++----------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/api/opentrons/instruments/pipette.py b/api/opentrons/instruments/pipette.py index 5db7c9fa5fb..c551cb56e06 100755 --- a/api/opentrons/instruments/pipette.py +++ b/api/opentrons/instruments/pipette.py @@ -1385,9 +1385,7 @@ def _aspirate_plunger_position(self, ul): ul_per_mm = lambda: self.ul_per_mm else: ul_per_mm = self._key_map_pipette_functions(model, ul, 'aspirate') - millimeters = 0.0 - if ul_per_mm is not None: - millimeters = ul / ul_per_mm() + millimeters = ul / ul_per_mm() destination_mm = self._get_plunger_position('bottom') + millimeters return round(destination_mm, 6) @@ -1407,10 +1405,7 @@ def _dispense_plunger_position(self, ul): else: ul_per_mm = self._key_map_pipette_functions(model, ul, 'dispense') # Change default of 1000 ul/mm if function returns a value - millimeters = 0.0 - if ul_per_mm is not None: - # If ul_per_mm not None, it is a function so must make it callable - millimeters = ul / ul_per_mm() + millimeters = ul / ul_per_mm() destination_mm = self._get_plunger_position('bottom') + millimeters return round(destination_mm, 6) @@ -1429,9 +1424,10 @@ def _key_map_pipette_functions(self, model, ul, func): 'p300_multi_v1': lambda: self._p300_multi_piecewise(ul, func), 'p300_multi_v1.3': lambda: self._p300_multi_piecewise(ul, func), 'p1000_single_v1': lambda: self._p1000_piecewise(ul, func), - 'p1000_single_v1.3': lambda: self._p1000_piecewise(ul, func)} + 'p1000_single_v1.3': lambda: self._p1000_piecewise(ul, func) + } - return function_map.get(model) + return function_map[model] def _p10_single_piecewise(self, ul, func): # Piecewise function that calculates ul_per_mm for a p10 single @@ -1521,11 +1517,10 @@ def _p300_multi_piecewise(self, ul, func): return 0*ul + 19.29389273 def _p1000_piecewise(self, ul, func): - if ul > 0: - if func == 'aspirate': - return 65 - else: - return 65 + if func == 'aspirate': + return 65 + else: + return 65 def _volume_percentage(self, volume): """Returns the plunger percentage for a given volume. @@ -1748,10 +1743,8 @@ def set_flow_rate(self, aspirate=None, dispense=None): else: ul_per_mm = self._key_map_pipette_functions( model, ul, 'aspirate') - - if ul_per_mm is not None: - self.set_speed( - aspirate=round(aspirate / ul_per_mm(), 6)) + self.set_speed( + aspirate=round(aspirate / ul_per_mm(), 6)) if dispense: # Set the ul_per_mm for the pipette if self.ul_per_mm: @@ -1759,9 +1752,8 @@ def set_flow_rate(self, aspirate=None, dispense=None): else: ul_per_mm = self._key_map_pipette_functions( model, ul, 'dispense') - if ul_per_mm is not None: - self.set_speed( - dispense=round(dispense / ul_per_mm(), 6)) + self.set_speed( + dispense=round(dispense / ul_per_mm(), 6)) return self def set_pick_up_current(self, amperes): From 2ad6558c85f7fbf42d074a1599b50efa8e57a227 Mon Sep 17 00:00:00 2001 From: andySigler Date: Mon, 4 Jun 2018 10:35:35 -0400 Subject: [PATCH 25/27] adjust plunger positions from HW tests, in order to meet max-volume expectations --- api/opentrons/instruments/pipette_config.py | 6 +++--- shared-data/robot-data/pipette-config.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/opentrons/instruments/pipette_config.py b/api/opentrons/instruments/pipette_config.py index aa3edc04310..fd18887a407 100644 --- a/api/opentrons/instruments/pipette_config.py +++ b/api/opentrons/instruments/pipette_config.py @@ -208,9 +208,9 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: p50_single_v1 = pipette_config( plunger_positions={ 'top': 19.5, - 'bottom': 2.5, + 'bottom': 2.01, 'blow_out': 2, - 'drop_tip': -5 + 'drop_tip': -4.5 }, pick_up_current=0.1, aspirate_flow_rate=50 / DEFAULT_ASPIRATE_SECONDS, @@ -248,7 +248,7 @@ def _load_config_dict_from_file(pipette_model: str) -> dict: 'top': 19.5, 'bottom': 2.5, 'blow_out': 2, - 'drop_tip': -4 + 'drop_tip': -3.5 }, pick_up_current=0.3, aspirate_flow_rate=50 / DEFAULT_ASPIRATE_SECONDS, diff --git a/shared-data/robot-data/pipette-config.json b/shared-data/robot-data/pipette-config.json index 9360fc820c2..72681ffd35c 100644 --- a/shared-data/robot-data/pipette-config.json +++ b/shared-data/robot-data/pipette-config.json @@ -80,9 +80,9 @@ "nominalMaxVolumeUl": 50, "plungerPositions": { "top": 19.5, - "bottom": 2.5, + "bottom": 2.01, "blowOut": 2, - "dropTip": -5 + "dropTip": -4.5 }, "pickUpCurrent": 0.1, "aspirateFlowRate": 25, @@ -120,7 +120,7 @@ "top": 19.5, "bottom": 2.5, "blowOut": 2, - "dropTip": -4 + "dropTip": -3.5 }, "pickUpCurrent": 0.3, "aspirateFlowRate": 25, From 3119a58518f183737ae65395604bdc38260608ae Mon Sep 17 00:00:00 2001 From: andySigler Date: Mon, 4 Jun 2018 10:35:59 -0400 Subject: [PATCH 26/27] adds ul_per_mm to all test using Pipette() --- api/tests/opentrons/conftest.py | 2 +- api/tests/opentrons/containers/test_grid.py | 1 + api/tests/opentrons/labware/test_pipette.py | 80 ++++++++----------- .../labware/test_pipette_unittest.py | 28 +++---- 4 files changed, 49 insertions(+), 62 deletions(-) diff --git a/api/tests/opentrons/conftest.py b/api/tests/opentrons/conftest.py index dfbd0ef1f80..a535e8ba2c5 100755 --- a/api/tests/opentrons/conftest.py +++ b/api/tests/opentrons/conftest.py @@ -325,7 +325,7 @@ def model(robot): from opentrons.containers import load from opentrons.instruments.pipette import Pipette - pipette = Pipette(robot, mount='right') + pipette = Pipette(robot, ul_per_mm=18.5, mount='right') plate = load(robot, '96-flat', '1') instrument = models.Instrument(pipette) diff --git a/api/tests/opentrons/containers/test_grid.py b/api/tests/opentrons/containers/test_grid.py index a603d434fbc..d00795c562b 100644 --- a/api/tests/opentrons/containers/test_grid.py +++ b/api/tests/opentrons/containers/test_grid.py @@ -69,6 +69,7 @@ def test_serial_dilution(self): p200 = pipette.Pipette( self.robot, + ul_per_mm=18.5, trash_container=trash, tip_racks=[tiprack], min_volume=10, diff --git a/api/tests/opentrons/labware/test_pipette.py b/api/tests/opentrons/labware/test_pipette.py index 4ecdbca9fd1..ae9522f6e2c 100755 --- a/api/tests/opentrons/labware/test_pipette.py +++ b/api/tests/opentrons/labware/test_pipette.py @@ -34,61 +34,49 @@ def test_pipette_version_1_0_and_1_3_extended_travel(): assert right_diff > left_diff -def test_pipette_models(): - # Test that the configuration position values for a given pipette model - # still make sense with any changes to the piecewise functions - robot.reset() - p = instruments.P10_Single(mount='left') - ul_per_mm = Pipette._p10_single_piecewise(p, 10, 'aspirate') - p.max_volume = calculate_max_volume(p, ul_per_mm) - assert p.channels == 1 - assert p.max_volume > 10 - p = instruments.P10_Multi(mount='right') - ul_per_mm = Pipette._p10_multi_piecewise(p, 10, 'aspirate') - p.max_volume = calculate_max_volume(p, ul_per_mm) - assert p.channels == 8 - assert p.max_volume > 10 +def test_all_pipette_models_can_transfer(): + from opentrons.instruments import pipette_config - robot.reset() - p = instruments.P50_Single(mount='left') - ul_per_mm = 3.1347 # Change once config blow_out position adjusted - p.max_volume = calculate_max_volume(p, ul_per_mm) - assert p.channels == 1 - assert p.max_volume > 50 - p = instruments.P50_Multi(mount='right') - ul_per_mm = Pipette._p50_multi_piecewise(p, 50, 'aspirate') - p.max_volume = calculate_max_volume(p, ul_per_mm) - assert p.channels == 8 - assert p.max_volume > 50 + models = [ + 'p10_single', 'p10_multi', 'p50_single', 'p50_multi', + 'p300_single', 'p300_multi', 'p1000_single' + ] - robot.reset() - p = instruments.P300_Single(mount='left') - ul_per_mm = Pipette._p300_single_piecewise(p, 300, 'aspirate') - p.max_volume = calculate_max_volume(p, ul_per_mm) - assert p.channels == 1 - assert p.max_volume > 300 - p = instruments.P300_Multi(mount='right') - ul_per_mm = Pipette._p300_multi_piecewise(p, 300, 'aspirate') - p.max_volume = calculate_max_volume(p, ul_per_mm) - assert p.channels == 8 - assert p.max_volume > 300 + for m in models: + robot.reset() + left = instruments._create_pipette_from_config( + config=pipette_config.load(m + '_v1'), + mount='left') + right = instruments._create_pipette_from_config( + config=pipette_config.load(m + '_v1.3'), + mount='right') - robot.reset() - p = instruments.P1000_Single(mount='left') - ul_per_mm = Pipette._p1000_piecewise(p, 1000, 'aspirate') - p.max_volume = calculate_max_volume(p, ul_per_mm) - assert p.channels == 1 - assert p.max_volume > 1000 + left.tip_attached = True + right.tip_attached = True + left.aspirate().dispense() + right.aspirate().dispense() -def calculate_max_volume(pipette, ul_per_mm): - t = pipette._get_plunger_position('top') - b = pipette._get_plunger_position('bottom') - return (t - b) * ul_per_mm +def test_pipette_models_reach_max_volume(): + from opentrons.instruments import pipette_config + + for model, config in pipette_config.configs.items(): + robot.reset() + pipette = instruments._create_pipette_from_config( + config=config, + mount='right') + + pipette.tip_attached = True + pipette.aspirate(pipette.max_volume) + pos = pose_tracker.absolute( + robot.poses, + pipette.instrument_actuator) + assert pos[0] < pipette.plunger_positions['top'] def test_set_flow_rate(): # Test new flow-rate functionality on all pipettes with different max vols + robot.reset() p10 = instruments.P10_Single(mount='right') p10.set_flow_rate(aspirate=10) diff --git a/api/tests/opentrons/labware/test_pipette_unittest.py b/api/tests/opentrons/labware/test_pipette_unittest.py index 52d4f2137de..b410cfc7bcc 100644 --- a/api/tests/opentrons/labware/test_pipette_unittest.py +++ b/api/tests/opentrons/labware/test_pipette_unittest.py @@ -14,6 +14,7 @@ class PipetteTest(unittest.TestCase): def setUp(self): self.robot = Robot() + self.robot.reset() self.robot.home() self.trash = containers_load(self.robot, 'point', '1') self.tiprack1 = containers_load(self.robot, 'tiprack-10ul', '5') @@ -23,6 +24,7 @@ def setUp(self): self.p200 = Pipette( self.robot, + ul_per_mm=18.5, trash_container=self.trash, tip_racks=[self.tiprack1, self.tiprack2], min_volume=10, # These are variable @@ -45,7 +47,7 @@ def test_bad_volume_percentage(self): def test_add_instrument(self): self.robot.reset() - Pipette(self.robot, mount='left') + Pipette(self.robot, ul_per_mm=18.5, mount='left') self.assertRaises(RuntimeError, Pipette, self.robot, mount='left') def test_aspirate_zero_volume(self): @@ -74,17 +76,18 @@ def test_deprecated_axis_call(self): warnings.filterwarnings('error') # Check that user warning occurs when axis is called self.assertRaises( - UserWarning, Pipette, self.robot, axis='a') + RuntimeError, Pipette, self.robot, mount='left') # Check that the warning is still valid when max_volume is also used self.assertRaises( - UserWarning, Pipette, self.robot, axis='a', max_volume=300) + RuntimeError, Pipette, self.robot, mount='left', max_volume=1000) warnings.filterwarnings('default') def test_get_instruments_by_name(self): self.p1000 = Pipette( self.robot, + ul_per_mm=18.5, trash_container=self.trash, tip_racks=[self.tiprack1], min_volume=10, # These are variable @@ -133,8 +136,6 @@ def test_volume_percentage(self): self.assertRaises(RuntimeError, self.p200._volume_percentage, 300) self.assertEquals(self.p200._volume_percentage(100), 0.5) self.assertEquals(len(self.robot.get_warnings()), 0) - self.p200._volume_percentage(self.p200.min_volume / 2) - self.assertEquals(len(self.robot.get_warnings()), 1) def test_add_tip(self): """ @@ -159,6 +160,7 @@ def test_distribute(self): self.p200.reset() # Setting true instead of calling pick_up_tip because the test is # currently based on an exact command list. Should make this better. + self.p200.ul_per_mm = 18.5 self.p200.distribute( 30, self.plate[0], @@ -212,7 +214,7 @@ def test_distribute(self): ['Aspirating', '70', 'well A1'], ['Dispensing', '30', 'well H1'], ['Dispensing', '30', 'well A2'], - ['Blow', 'well A1'], + ['Blow', 'well A1'] ] fuzzy_assert(self.robot.commands(), expected=expected) self.robot.clear_commands() @@ -681,9 +683,7 @@ def test_distribute_air_gap(self): ['blow', 'Well A1'], ['drop'] ] - fuzzy_assert(self.robot.commands(), - expected=expected - ) + fuzzy_assert(self.robot.commands(), expected=expected) self.robot.clear_commands() def test_distribute_air_gap_and_disposal_vol(self): @@ -978,8 +978,6 @@ def test_mix_with_named_args(self): self.p200.dispense = mock.Mock() self.p200.mix(volume=50, repetitions=2) - print(self.p200.tip_racks) - self.assertEqual( self.p200.dispense.mock_calls, [ @@ -1047,7 +1045,8 @@ def test_tip_tracking_chain(self): mount='right', tip_racks=[self.tiprack1, self.tiprack2], trash_container=self.tiprack1, - name='pipette-for-transfer-tests' + name='pipette-for-transfer-tests', + ul_per_mm=18.5 ) self.p200.max_volume = 200 @@ -1088,7 +1087,8 @@ def test_tip_tracking_chain_multi_channel(self): tip_racks=[self.tiprack1, self.tiprack2], min_volume=10, # These are variable mount='right', - channels=8 + channels=8, + ul_per_mm=18.5 ) p200_multi.calibrate_plunger( @@ -1159,8 +1159,6 @@ def test_direct_movement_within_well(self): mock.call( self.plate[2].bottom(), instrument=self.p200, strategy='direct') ] - from pprint import pprint - pprint(self.robot.move_to.mock_calls) self.assertEqual(self.robot.move_to.mock_calls, expected) def build_pick_up_tip(self, well): From 2d806c58868242db45f1da47ecb07e241925e6e8 Mon Sep 17 00:00:00 2001 From: andySigler Date: Mon, 4 Jun 2018 11:26:35 -0400 Subject: [PATCH 27/27] brings back old tests removed in previous commit --- api/tests/opentrons/labware/test_pipette_unittest.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/tests/opentrons/labware/test_pipette_unittest.py b/api/tests/opentrons/labware/test_pipette_unittest.py index b410cfc7bcc..6e27b50045e 100644 --- a/api/tests/opentrons/labware/test_pipette_unittest.py +++ b/api/tests/opentrons/labware/test_pipette_unittest.py @@ -14,7 +14,6 @@ class PipetteTest(unittest.TestCase): def setUp(self): self.robot = Robot() - self.robot.reset() self.robot.home() self.trash = containers_load(self.robot, 'point', '1') self.tiprack1 = containers_load(self.robot, 'tiprack-10ul', '5') @@ -76,11 +75,11 @@ def test_deprecated_axis_call(self): warnings.filterwarnings('error') # Check that user warning occurs when axis is called self.assertRaises( - RuntimeError, Pipette, self.robot, mount='left') + UserWarning, Pipette, self.robot, axis='a') # Check that the warning is still valid when max_volume is also used self.assertRaises( - RuntimeError, Pipette, self.robot, mount='left', max_volume=1000) + UserWarning, Pipette, self.robot, axis='a', max_volume=300) warnings.filterwarnings('default') @@ -136,6 +135,8 @@ def test_volume_percentage(self): self.assertRaises(RuntimeError, self.p200._volume_percentage, 300) self.assertEquals(self.p200._volume_percentage(100), 0.5) self.assertEquals(len(self.robot.get_warnings()), 0) + self.p200._volume_percentage(self.p200.min_volume / 2) + self.assertEquals(len(self.robot.get_warnings()), 1) def test_add_tip(self): """ @@ -160,7 +161,6 @@ def test_distribute(self): self.p200.reset() # Setting true instead of calling pick_up_tip because the test is # currently based on an exact command list. Should make this better. - self.p200.ul_per_mm = 18.5 self.p200.distribute( 30, self.plate[0],