diff --git a/api/src/opentrons/config/pipette_config.py b/api/src/opentrons/config/pipette_config.py index e02a74bfc9b..aa2f2949217 100644 --- a/api/src/opentrons/config/pipette_config.py +++ b/api/src/opentrons/config/pipette_config.py @@ -72,7 +72,7 @@ def load(pipette_model: str) -> pipette_config: with open(config_file) as cfg_file: cfg = json.load(cfg_file).get(pipette_model, {}) - plunger_pos = cfg.get('plungerPositions') + plunger_pos = cfg.get('plungerPositions', {}) res = pipette_config( plunger_positions={ diff --git a/api/src/opentrons/drivers/smoothie_drivers/driver_3_0.py b/api/src/opentrons/drivers/smoothie_drivers/driver_3_0.py index 8ead8277e47..a743e6e5ad2 100755 --- a/api/src/opentrons/drivers/smoothie_drivers/driver_3_0.py +++ b/api/src/opentrons/drivers/smoothie_drivers/driver_3_0.py @@ -2,7 +2,7 @@ import logging from time import sleep from threading import Event -from typing import Dict +from typing import Dict, Optional from serial.serialutil import SerialException @@ -349,29 +349,32 @@ def _recursive_update_position(retries): self._update_position(updated_position) - def read_pipette_id(self, mount): + def read_pipette_id(self, mount) -> Optional[str]: ''' - Reads in an attached pipette's UUID - The UUID is unique to this pipette, and is a string of unknown length + Reads in an attached pipette's ID + The ID is unique to this pipette, and is a string of unknown length - mount: - String (str) with value 'left' or 'right' + :param mount: string with value 'left' or 'right' + :return id string, or None ''' + res: Optional[str] = None if self.simulating: res = '1234567890' else: - res = self._read_from_pipette(GCODES['READ_INSTRUMENT_ID'], mount) - if res: - ret = {'pipette_id': res} - else: - ret = {'message': 'Error: Pipette ID read failed'} - return ret + try: + res = self._read_from_pipette( + GCODES['READ_INSTRUMENT_ID'], mount) + except UnicodeDecodeError: + log.exception("Failed to decode pipette ID string:") + res = None + return res - def read_pipette_model(self, mount): + def read_pipette_model(self, mount) -> Optional[str]: ''' Reads an attached pipette's MODEL The MODEL is a unique string for this model of pipette + :param mount: string with value 'left' or 'right' :return model string, or None ''' if self.simulating: @@ -393,8 +396,8 @@ def read_pipette_model(self, mount): def write_pipette_id(self, mount, data_string): ''' - Writes to an attached pipette's UUID memory location - The UUID is unique to this pipette, and is a string of unknown length + Writes to an attached pipette's ID memory location + The ID is unique to this pipette, and is a string of unknown length NOTE: To enable write-access to the pipette, it's button must be held @@ -959,7 +962,7 @@ def _setup(self): self.pop_axis_max_speed() self.pop_speed() - def _read_from_pipette(self, gcode, mount): + def _read_from_pipette(self, gcode, mount) -> Optional[str]: ''' Read from an attached pipette's internal memory. The gcode used determines which portion of memory is read and returned. @@ -992,6 +995,7 @@ def _read_from_pipette(self, gcode, mount): return _byte_array_to_ascii_string(res[mount]) except (ParseError, AssertionError, SmoothieError): pass + return None def _write_to_pipette(self, gcode, mount, data_string): ''' diff --git a/api/src/opentrons/hardware_control/__init__.py b/api/src/opentrons/hardware_control/__init__.py index a1ead202abe..a996fbad89e 100644 --- a/api/src/opentrons/hardware_control/__init__.py +++ b/api/src/opentrons/hardware_control/__init__.py @@ -112,7 +112,7 @@ def build_hardware_controller( @classmethod def build_hardware_simulator( cls, - attached_instruments: Dict[top_types.Mount, str] = None, + attached_instruments: Dict[top_types.Mount, Dict[str, Optional[str]]] = None, # noqa E501 attached_modules: List[str] = None, config: robot_configs.robot_config = None, loop: asyncio.AbstractEventLoop = None) -> 'API': @@ -175,17 +175,21 @@ async def cache_instruments(self, checked_require = require or {} self._log.info("Updating instrument model cache") found = self._backend.get_attached_instruments(checked_require) - self._attached_instruments = {mount: Pipette(instrument_model) - if instrument_model else None - for mount, instrument_model - in found.items()} - mod_log.info("Instruments found: {}" - .format(self._attached_instruments)) + for mount, instrument_data in found.items(): + model = instrument_data.get('model') + if model is not None: + p = Pipette(model, instrument_data['id']) + self._attached_instruments[mount] = p + else: + self._attached_instruments[mount] = None + mod_log.info("Instruments found: {}".format( + self._attached_instruments)) @property def attached_instruments(self): configs = ['name', 'min_volume', 'max_volume', - 'aspirate_flow_rate', 'dispense_flow_rate'] + 'aspirate_flow_rate', 'dispense_flow_rate', + 'pipette_id'] instruments = {top_types.Mount.LEFT: {}, top_types.Mount.RIGHT: {}} for mount in top_types.Mount: diff --git a/api/src/opentrons/hardware_control/controller.py b/api/src/opentrons/hardware_control/controller.py index 21d3f4481a8..f47a4edb445 100644 --- a/api/src/opentrons/hardware_control/controller.py +++ b/api/src/opentrons/hardware_control/controller.py @@ -86,7 +86,8 @@ def fast_home(self, axis: str, margin: float) -> Dict[str, float]: return self._smoothie_driver.fast_home(axis, margin) def get_attached_instruments( - self, expected: Dict[Mount, str]) -> Dict[Mount, Optional[str]]: + self, expected: Dict[Mount, str])\ + -> Dict[Mount, Dict[str, Optional[str]]]: """ Find the instruments attached to our mounts. :param expected: A dict that may contain a mapping from mount to @@ -97,19 +98,26 @@ def get_attached_instruments( match. :raises RuntimeError: If an instrument is expected but not found. - :returns: A dict of mount to either instrument model names or `None`. + :returns: A dict with mounts as the top-level keys. Each mount value is + a dict with keys 'mount' (containing an instrument model name or + `None`) and 'id' (containing the serial number of the pipette + attached to that mount, or `None`). """ - to_return: Dict[Mount, Optional[str]] = {} + to_return: Dict[Mount, Dict[str, Optional[str]]] = {} for mount in Mount: - found = self._smoothie_driver.read_pipette_model( + found_model = self._smoothie_driver.read_pipette_model( + mount.name.lower()) + found_id = self._smoothie_driver.read_pipette_id( mount.name.lower()) expected_instr = expected.get(mount, None) if expected_instr and\ - (not found or not found.startswith(expected_instr)): + (not found_model or not found_model.startswith(expected_instr)): raise RuntimeError( 'mount {}: expected instrument {} but got {}' - .format(mount.name, expected_instr, found)) - to_return[mount] = found + .format(mount.name, expected_instr, found_model)) + to_return[mount] = { + 'model': found_model, + 'id': found_id} return to_return def set_active_current(self, axis, amp): diff --git a/api/src/opentrons/hardware_control/pipette.py b/api/src/opentrons/hardware_control/pipette.py index bf9334f9105..13ffc6ba157 100644 --- a/api/src/opentrons/hardware_control/pipette.py +++ b/api/src/opentrons/hardware_control/pipette.py @@ -1,6 +1,6 @@ """ Classes and functions for pipette state tracking """ -from typing import Any, Dict, Union +from typing import Any, Dict, Union, Optional from opentrons.types import Point from opentrons.config import pipette_config @@ -13,11 +13,12 @@ class Pipette: control API. Its only purpose is to gather state. """ - def __init__(self, model: str) -> None: + def __init__(self, model: str, pipette_id: str = None) -> None: self._config = pipette_config.load(model) self._name = model self._current_volume = 0.0 self._has_tip = False + self._pipette_id = pipette_id @property def config(self) -> pipette_config.pipette_config: @@ -30,6 +31,10 @@ def update_config_item(self, elem_name: str, elem_val: Any): def name(self) -> str: return self._name + @property + def pipette_id(self) -> Optional[str]: + return self._pipette_id + @property def critical_point(self) -> Point: """ The vector from the pipette's origin to its critical point """ @@ -98,5 +103,6 @@ def as_dict(self) -> Dict[str, Union[str, float]]: config_dict = self.config._asdict() config_dict.update({'current_volume': self.current_volume, 'name': self.name, + 'pipette_id': self.pipette_id, 'has_tip': self.has_tip}) return config_dict diff --git a/api/src/opentrons/hardware_control/simulator.py b/api/src/opentrons/hardware_control/simulator.py index aba0bcb775e..0d83ecd6e06 100644 --- a/api/src/opentrons/hardware_control/simulator.py +++ b/api/src/opentrons/hardware_control/simulator.py @@ -8,8 +8,10 @@ def find_config(prefix: str) -> str: - """ Find the most recent config matching `prefix` or None """ + """ Find the most recent config matching `prefix` """ matches = [conf for conf in configs if conf.startswith(prefix)] + if not matches: + raise KeyError('No match found for prefix {}'.format(prefix)) if prefix in matches: return prefix else: @@ -25,10 +27,11 @@ class Simulator: hardware actions. It is suitable for use on a dev machine or on a robot with no smoothie connected. """ - def __init__(self, - attached_instruments: Dict[types.Mount, str], - attached_modules: List[str], - config, loop) -> None: + def __init__( + self, + attached_instruments: Dict[types.Mount, Dict[str, Optional[str]]], + attached_modules: List[str], + config, loop) -> None: self._config = config self._loop = loop self._attached_instruments = attached_instruments @@ -53,7 +56,7 @@ def fast_home(self, axis: str, margin: float) -> Dict[str, float]: def get_attached_instruments( self, expected: Dict[types.Mount, str])\ - -> Dict[types.Mount, Optional[str]]: + -> Dict[types.Mount, Dict[str, Optional[str]]]: """ Update the internal cache of attached instruments. This method allows after-init-time specification of attached simulated @@ -72,23 +75,34 @@ def get_attached_instruments( :raises RuntimeError: If an instrument is expected but not found. :returns: A dict of mount to either instrument model names or `None`. """ - to_return: Dict[types.Mount, Optional[str]] = {} + to_return: Dict[types.Mount, Dict[str, Optional[str]]] = {} for mount in types.Mount: expected_instr = expected.get(mount, None) - init_instr = self._attached_instruments.get(mount, None) - if expected_instr and init_instr\ - and not init_instr.startswith(expected_instr): + init_instr = self._attached_instruments.get(mount, {}) + found_model = init_instr.get('model', '') + if expected_instr and found_model\ + and not found_model.startswith(expected_instr): raise RuntimeError( 'mount {}: expected instrument {} but got {}' .format(mount.name, expected_instr, init_instr)) - elif init_instr and expected_instr: + elif found_model and expected_instr: + # Instrument detected matches instrument expected (note: + # "instrument detected" means passed as an argument to the + # constructor of this class) to_return[mount] = init_instr - elif init_instr: + elif found_model: + # Instrument detected and no expected instrument specified to_return[mount] = init_instr elif expected_instr: - to_return[mount] = find_config(expected_instr) + # Expected instrument specified and no instrument detected + to_return[mount] = { + 'model': find_config(expected_instr), + 'id': None} else: - to_return[mount] = None + # No instrument detected or expected + to_return[mount] = { + 'model': None, + 'id': None} return to_return def set_active_current(self, axis, amp): diff --git a/api/src/opentrons/legacy_api/robot/robot.py b/api/src/opentrons/legacy_api/robot/robot.py index 3a150ac801b..acbf567eb09 100755 --- a/api/src/opentrons/legacy_api/robot/robot.py +++ b/api/src/opentrons/legacy_api/robot/robot.py @@ -109,7 +109,8 @@ def __init__(self, config=None): self.INSTRUMENT_DRIVERS_CACHE = {} self._instruments = {} - self.model_by_mount = {'left': None, 'right': None} + self.model_by_mount = {'left': {'model': None, 'id': None}, + 'right': {'model': None, 'id': None}} # TODO (artyom, 09182017): once protocol development experience # in the light of Session concept is fully fleshed out, we need @@ -227,10 +228,43 @@ def reset(self): return self def cache_instrument_models(self): + """ + Queries Smoothie for the model and ID strings of attached pipettes, and + saves them so they can be reported without querying Smoothie again (as + this could interrupt a command if done during a run or other movement). + + Shape of return dict should be: + + ``` + { + "left": { + "model": "" or None, + "id": "" or None + }, + "right": { + "model": "" or None, + "id": "" or None + } + } + ``` + + :return: a dict with pipette data (shape described above) + """ log.debug("Updating instrument model cache") for mount in self.model_by_mount.keys(): - self.model_by_mount[mount] = self._driver.read_pipette_model(mount) - log.debug("{}: {}".format(mount, self.model_by_mount[mount])) + model_value = self._driver.read_pipette_model(mount) + if model_value: + id_response = self._driver.read_pipette_id(mount) + else: + id_response = None + self.model_by_mount[mount] = { + 'model': model_value, + 'id': id_response + } + log.debug("{}: {} [{}]".format( + mount, + self.model_by_mount[mount]['model'], + self.model_by_mount[mount]['id'])) def turn_on_button_light(self): self._driver.turn_on_blue_button_light() @@ -800,7 +834,8 @@ def get_attached_pipettes(self): left_data = { 'mount_axis': 'z', 'plunger_axis': 'b', - 'model': self.model_by_mount['left'], + 'model': self.model_by_mount['left']['model'], + 'id': self.model_by_mount['left']['id'] } left_model = left_data.get('model') if left_model: @@ -810,7 +845,8 @@ def get_attached_pipettes(self): right_data = { 'mount_axis': 'a', 'plunger_axis': 'c', - 'model': self.model_by_mount['right'] + 'model': self.model_by_mount['right']['model'], + 'id': self.model_by_mount['right']['id'] } right_model = right_data.get('model') if right_model: diff --git a/api/src/opentrons/server/endpoints/control.py b/api/src/opentrons/server/endpoints/control.py index fd027153960..5bb75a2a1fe 100644 --- a/api/src/opentrons/server/endpoints/control.py +++ b/api/src/opentrons/server/endpoints/control.py @@ -28,13 +28,15 @@ async def get_attached_pipettes(request): 'model': 'p300_single_v1', 'tip_length': 51.7, 'mount_axis': 'z', - 'plunger_axis': 'b' + 'plunger_axis': 'b', + 'id': '' }, 'right': { 'model': 'p10_multi_v1', 'tip_length': 40, 'mount_axis': 'a', - 'plunger_axis': 'c' + 'plunger_axis': 'c', + 'id': '' } } ``` diff --git a/api/src/opentrons/tools/write_pipette_memory.py b/api/src/opentrons/tools/write_pipette_memory.py index 8421c83271f..348a63e5cfd 100644 --- a/api/src/opentrons/tools/write_pipette_memory.py +++ b/api/src/opentrons/tools/write_pipette_memory.py @@ -41,7 +41,7 @@ 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']) + _assert_the_same(new_id, read_id) robot._driver.write_pipette_model(mount, new_model) read_model = robot._driver.read_pipette_model(mount) _assert_the_same(new_model, read_model) @@ -49,7 +49,6 @@ def write_identifiers(robot, mount, new_id, new_model): def check_previous_data(robot, mount): old_id = robot._driver.read_pipette_id(mount) - old_id = old_id.get('pipette_id') old_model = robot._driver.read_pipette_model(mount) if old_id and old_model: print( diff --git a/api/tests/opentrons/drivers/test_driver.py b/api/tests/opentrons/drivers/test_driver.py index a1395a7ff4a..0886a26ae8e 100755 --- a/api/tests/opentrons/drivers/test_driver.py +++ b/api/tests/opentrons/drivers/test_driver.py @@ -472,7 +472,7 @@ def _new_send_message(self, command, timeout=None): driver.simulating = False read_id = driver.read_pipette_id('left') driver.simulating = True - assert read_id == {'pipette_id': test_id} + assert read_id == test_id driver.write_pipette_model('left', test_model) driver.simulating = False diff --git a/api/tests/opentrons/hardware_control/test_instruments.py b/api/tests/opentrons/hardware_control/test_instruments.py index 6394357e2d1..0c80d5e2fda 100644 --- a/api/tests/opentrons/hardware_control/test_instruments.py +++ b/api/tests/opentrons/hardware_control/test_instruments.py @@ -2,40 +2,39 @@ from opentrons import types from opentrons import hardware_control as hc from opentrons.hardware_control.types import Axis -from opentrons.hardware_control.pipette import Pipette + + +LEFT_PIPETTE_PREFIX = 'p10_single' +LEFT_PIPETTE_MODEL = '{}_v1'.format(LEFT_PIPETTE_PREFIX) +LEFT_PIPETTE_ID = 'testy' @pytest.fixture def dummy_instruments(): - dummy_instruments_attached = {types.Mount.LEFT: 'p10_single_v1', - types.Mount.RIGHT: None} + dummy_instruments_attached = { + types.Mount.LEFT: { + 'model': LEFT_PIPETTE_MODEL, + 'id': LEFT_PIPETTE_ID + }, + types.Mount.RIGHT: { + 'model': None, + 'id': None + } + } return dummy_instruments_attached -def attached_instruments(inst): - """ - Format inst dict like the public 'attached_instruments' property - """ - configs = ['name', 'min_volume', 'max_volume', - 'aspirate_flow_rate', 'dispense_flow_rate'] - instr_objects = {mount: Pipette(model) if model else None - for mount, model in inst.items()} - instruments = {types.Mount.LEFT: {}, types.Mount.RIGHT: {}} - for mount in types.Mount: - if not instr_objects[mount]: - continue - for key in configs: - instruments[mount][key] = instr_objects[mount].as_dict()[key] - return instruments - - async def test_cache_instruments(dummy_instruments, loop): + expected_keys = [ + 'name', 'min_volume', 'max_volume', 'aspirate_flow_rate', + 'dispense_flow_rate', 'pipette_id'] + hw_api = hc.API.build_hardware_simulator( attached_instruments=dummy_instruments, loop=loop) await hw_api.cache_instruments() - assert hw_api.attached_instruments == attached_instruments( - dummy_instruments) + assert sorted(hw_api.attached_instruments[types.Mount.LEFT].keys()) == \ + sorted(expected_keys) @pytest.mark.skipif(not hc.Controller, @@ -44,32 +43,53 @@ async def test_cache_instruments(dummy_instruments, loop): async def test_cache_instruments_hc(monkeypatch, dummy_instruments, hardware_controller_lockfile, running_on_pi, cntrlr_mock_connect, loop): + expected_keys = [ + 'name', 'min_volume', 'max_volume', 'aspirate_flow_rate', + 'dispense_flow_rate', 'pipette_id'] hw_api_cntrlr = hc.API.build_hardware_controller(loop=loop) - def mock_driver_method(mount): - attached_pipette = {'left': 'p10_single_v1', 'right': None} + def mock_driver_model(mount): + attached_pipette = {'left': LEFT_PIPETTE_MODEL, 'right': None} + return attached_pipette[mount] + + def mock_driver_id(mount): + attached_pipette = {'left': LEFT_PIPETTE_ID, 'right': None} return attached_pipette[mount] + + monkeypatch.setattr(hw_api_cntrlr._backend._smoothie_driver, + 'read_pipette_model', mock_driver_model) monkeypatch.setattr(hw_api_cntrlr._backend._smoothie_driver, - 'read_pipette_model', mock_driver_method) + 'read_pipette_id', mock_driver_id) + await hw_api_cntrlr.cache_instruments() - assert hw_api_cntrlr.attached_instruments == attached_instruments( - dummy_instruments) + + assert sorted( + hw_api_cntrlr.attached_instruments[types.Mount.LEFT].keys()) == \ + sorted(expected_keys) + # If we pass a conflicting expectation we should get an error with pytest.raises(RuntimeError): await hw_api_cntrlr.cache_instruments({types.Mount.LEFT: 'p300_multi'}) + # If we pass a matching expects it should work - await hw_api_cntrlr.cache_instruments({types.Mount.LEFT: 'p10_single'}) - assert hw_api_cntrlr.attached_instruments\ - == attached_instruments(dummy_instruments) + await hw_api_cntrlr.cache_instruments( + {types.Mount.LEFT: LEFT_PIPETTE_PREFIX}) + assert sorted( + hw_api_cntrlr.attached_instruments[types.Mount.LEFT].keys()) == \ + sorted(expected_keys) async def test_cache_instruments_sim(loop, dummy_instruments): + expected_keys = [ + 'name', 'min_volume', 'max_volume', 'aspirate_flow_rate', + 'dispense_flow_rate', 'pipette_id'] + sim = hc.API.build_hardware_simulator(loop=loop) # With nothing specified at init or expected, we should have nothing await sim.cache_instruments() - assert sim.attached_instruments == {types.Mount.LEFT: {}, - types.Mount.RIGHT: {}} + assert sim.attached_instruments == { + types.Mount.LEFT: {}, types.Mount.RIGHT: {}} # When we expect instruments, we should get what we expect since nothing # was specified at init time await sim.cache_instruments({types.Mount.LEFT: 'p10_single_v1.3'}) @@ -84,7 +104,9 @@ async def test_cache_instruments_sim(loop, dummy_instruments): sim = hc.API.build_hardware_simulator( attached_instruments=dummy_instruments) await sim.cache_instruments() - assert sim.attached_instruments == attached_instruments(dummy_instruments) + assert sorted( + sim.attached_instruments[types.Mount.LEFT].keys()) == \ + sorted(expected_keys) # If we specify conflicting expectations and init arguments we should # get a RuntimeError with pytest.raises(RuntimeError): diff --git a/api/tests/opentrons/hardware_control/test_moves.py b/api/tests/opentrons/hardware_control/test_moves.py index 34ab81e4347..3147d4f2057 100644 --- a/api/tests/opentrons/hardware_control/test_moves.py +++ b/api/tests/opentrons/hardware_control/test_moves.py @@ -124,8 +124,8 @@ async def test_mount_offset_applied(hardware_api): async def test_critical_point_applied(hardware_api, monkeypatch): await hardware_api.home() hardware_api._backend._attached_instruments\ - = {types.Mount.LEFT: None, - types.Mount.RIGHT: 'p10_single_v1'} + = {types.Mount.LEFT: {'model': None, 'id': None}, + types.Mount.RIGHT: {'model': 'p10_single_v1', 'id': 'testyness'}} await hardware_api.cache_instruments() # Our critical point is now the tip of the nozzle await hardware_api.move_to(types.Mount.RIGHT, types.Point(0, 0, 0)) diff --git a/api/tests/opentrons/hardware_control/test_pipette.py b/api/tests/opentrons/hardware_control/test_pipette.py index a8290867dec..85df5d1e40c 100644 --- a/api/tests/opentrons/hardware_control/test_pipette.py +++ b/api/tests/opentrons/hardware_control/test_pipette.py @@ -5,7 +5,7 @@ def test_tip_tracking(): - pip = pipette.Pipette('p10_single_v1') + pip = pipette.Pipette('p10_single_v1', 'testID') with pytest.raises(AssertionError): pip.remove_tip() assert not pip.has_tip @@ -22,7 +22,7 @@ def test_tip_tracking(): def test_critical_points(): for config in pipette_config.configs: loaded = pipette_config.load(config) - pip = pipette.Pipette(config) + pip = pipette.Pipette(config, 'testID') mod_offset = Point(*loaded.model_offset) assert pip.critical_point == mod_offset pip.add_tip() @@ -35,7 +35,7 @@ def test_critical_points(): def test_volume_tracking(): for config in pipette_config.configs: loaded = pipette_config.load(config) - pip = pipette.Pipette(config) + pip = pipette.Pipette(config, 'testID') assert pip.current_volume == 0.0 assert pip.available_volume == loaded.max_volume assert pip.ok_to_add_volume(loaded.max_volume - 0.1) diff --git a/api/tests/opentrons/server/test_control_endpoints.py b/api/tests/opentrons/server/test_control_endpoints.py index 5b7e91713ca..475550900e2 100644 --- a/api/tests/opentrons/server/test_control_endpoints.py +++ b/api/tests/opentrons/server/test_control_endpoints.py @@ -22,12 +22,14 @@ def mock_parse_fail(self, gcode, mount): "left": { "mount_axis": "z", "plunger_axis": "b", - "model": None + "model": None, + "id": None }, "right": { "mount_axis": "a", "plunger_axis": "c", - "model": None + "model": None, + "id": None } } @@ -42,11 +44,16 @@ def mock_parse_fail(self, gcode, mount): async def test_get_pipettes( virtual_smoothie_env, loop, test_client, monkeypatch): test_model = 'p300_multi_v1' + test_id = '123abc' def dummy_read_model(mount): return test_model + def dummy_read_id(mount): + return test_id + monkeypatch.setattr(robot._driver, 'read_pipette_model', dummy_read_model) + monkeypatch.setattr(robot._driver, 'read_pipette_id', dummy_read_id) robot.reset() app = init(loop) @@ -58,13 +65,15 @@ def dummy_read_model(mount): 'model': test_model, 'tip_length': model.tip_length, 'mount_axis': 'z', - 'plunger_axis': 'b' + 'plunger_axis': 'b', + 'id': test_id }, 'right': { 'model': test_model, 'tip_length': model.tip_length, 'mount_axis': 'a', - 'plunger_axis': 'c' + 'plunger_axis': 'c', + 'id': test_id } } @@ -101,11 +110,16 @@ def stub_discover_modules(): async def test_get_cached_pipettes( virtual_smoothie_env, loop, test_client, monkeypatch): test_model = 'p300_multi_v1' + test_id = '123abc' def dummy_read_model(mount): return test_model + def dummy_read_id(mount): + return test_id + monkeypatch.setattr(robot._driver, 'read_pipette_model', dummy_read_model) + monkeypatch.setattr(robot._driver, 'read_pipette_id', dummy_read_id) robot.reset() app = init(loop) @@ -117,13 +131,15 @@ def dummy_read_model(mount): 'model': test_model, 'tip_length': model.tip_length, 'mount_axis': 'z', - 'plunger_axis': 'b' + 'plunger_axis': 'b', + 'id': test_id }, 'right': { 'model': test_model, 'tip_length': model.tip_length, 'mount_axis': 'a', - 'plunger_axis': 'c' + 'plunger_axis': 'c', + 'id': test_id } } @@ -134,11 +150,16 @@ def dummy_read_model(mount): name1 = 'p10_single_v1.3' model1 = pipette_config.load(name1) + id1 = 'fgh876' def dummy_model(mount): return name1 + def dummy_id(mount): + return id1 + monkeypatch.setattr(robot._driver, 'read_pipette_model', dummy_model) + monkeypatch.setattr(robot._driver, 'read_pipette_id', dummy_id) resp1 = await cli.get('/pipettes') text1 = await resp1.text() @@ -150,13 +171,15 @@ def dummy_model(mount): 'model': name1, 'tip_length': model1.tip_length, 'mount_axis': 'z', - 'plunger_axis': 'b' + 'plunger_axis': 'b', + 'id': id1 }, 'right': { 'model': name1, 'tip_length': model1.tip_length, 'mount_axis': 'a', - 'plunger_axis': 'c' + 'plunger_axis': 'c', + 'id': id1 } }