예제 #1
0
async def reset(request: web.Request) -> web.Response:  # noqa(C901)
    """ Execute a reset of the requested parts of the user configuration.
    """
    data = await request.json()
    ok, bad_key = _check_reset(data)
    if not ok:
        return web.json_response(
            {'message': '{} is not a valid reset option'.format(bad_key)},
            status=400)
    log.info("Reset requested for {}".format(', '.join(data.keys())))
    if data.get('tipProbe'):
        config = rc.load()
        if ff.use_protocol_api_v2():
            config = config._replace(
                instrument_offset=rc.build_fallback_instrument_offset({}))
        else:
            config.tip_length.clear()
        rc.save_robot_settings(config)
    if data.get('labwareCalibration'):
        labware.clear_calibrations()
        if not ff.use_protocol_api_v2():
            db.reset()

    if data.get('customLabware'):
        labware.delete_all_custom_labware()

    if data.get('bootScripts'):
        if IS_ROBOT:
            if os.path.exists('/data/boot.d'):
                shutil.rmtree('/data/boot.d')
        else:
            log.debug('Not on pi, not removing /data/boot.d')
    return web.json_response({}, status=200)
예제 #2
0
def test_save_and_clear_config(mock_config, sync_hardware, loop):
    # Clear should happen automatically after the following import, resetting
    # the deck cal to the default value
    hardware = sync_hardware
    from opentrons.deck_calibration import dc_main
    import os

    hardware.config.gantry_calibration[0][3] = 10

    old_gantry = copy.copy(hardware.config.gantry_calibration)
    base_filename = CONFIG['deck_calibration_file']

    tag = "testing"
    root, ext = os.path.splitext(base_filename)
    filename = "{}-{}{}".format(root, tag, ext)
    dc_main.backup_configuration_and_reload(hardware, tag=tag)
    # After reset gantry calibration should be I(4,4)
    assert hardware.config.gantry_calibration\
        == robot_configs.DEFAULT_DECK_CALIBRATION
    # Mount calibration should be defaulted
    assert hardware.config.mount_offset == robot_configs.DEFAULT_MOUNT_OFFSET

    # Check that we properly saved the old deck calibration
    saved_config = robot_configs.load(filename)
    assert saved_config.gantry_calibration == old_gantry
    # XXX This shouldn't be necessary but the config isn't properly cleared
    # otherwise
    hardware.config.gantry_calibration[0][3] = 0
예제 #3
0
    def __init__(self, config=None, broker=None):
        """
        Initializes a robot instance.

        Notes
        -----
        This class is a singleton. That means every time you call
        :func:`__init__` the same instance will be returned. There's
        only once instance of a robot.
        """
        super().__init__(broker)
        self.config = config or load()
        self._driver = driver_3_0.SmoothieDriver_3_0_0(config=self.config)
        self._attached_modules: Dict[str, Any] = {}  # key is port + model
        self.fw_version = self._driver.get_fw_version()

        self.INSTRUMENT_DRIVERS_CACHE = {}
        self._instruments = {}
        self.model_by_mount = {
            'left': {
                'model': None,
                'id': None,
                'name': None
            },
            'right': {
                'model': None,
                'id': None,
                'name': None
            }
        }

        self._commands = []
        self._unsubscribe_commands = None
        self._smoothie_lock = Lock()
        self.reset()
예제 #4
0
def initialize(
        hardware_server: bool = False,
        hardware_server_socket: str = None) \
        -> ThreadManager:
    """
    Initialize the Opentrons hardware returning a hardware instance.

    :param hardware_server: Run a jsonrpc server allowing rpc to the  hardware
     controller. Only works on buildroot because extra dependencies are
     required.
    :param hardware_server_socket: Override for the hardware server socket
    """
    checked_socket = hardware_server_socket\
        or "/var/run/opentrons-hardware.sock"
    robot_conf = robot_configs.load()
    logging_config.log_init(robot_conf.log_level)

    log.info(f"API server version:  {__version__}")
    log.info(f"Robot Name: {name()}")

    loop = asyncio.get_event_loop()
    hardware = loop.run_until_complete(initialize_robot())

    if hardware_server:
        #  TODO: BC 2020-02-25 adapt hardware socket server to ThreadManager
        loop.run_until_complete(
            install_hardware_server(checked_socket, hardware))  # type: ignore

    return hardware
예제 #5
0
async def test_save_z(dc_session, monkeypatch, instruments):
    dc_session.adapter.reset()
    hardware = dc_session.adapter
    model = 'p10_single_v1'
    # Z values were bleeding in from other tests, mock robot configs
    # to encapsulate this test
    fake_config = robot_configs.load()
    monkeypatch.setattr(hardware, 'config', fake_config)

    mount = types.Mount.LEFT
    hardware.reset()
    hardware.cache_instruments({
        mount: 'p10_single_v1',
        types.Mount.RIGHT: None
    })
    pip = hardware.attached_instruments[mount]
    dc_session.pipettes = {mount: pip}
    dc_session.current_mount = mount
    dc_session.current_model = model
    dc_session.tip_length = 25
    dc_session.pipettes.get(mount)['has_tip'] = True
    dc_session.pipettes.get(mount)['tip_length'] = dc_session.tip_length

    z_target = 80.0
    hardware.home()
    # Unsure whether to use move_to or move_rel
    hardware.move_to(types.Mount.LEFT, types.Point(x=0, y=0, z=z_target))

    await endpoints.save_z({})

    new_z = dc_session.z_value
    expected_z = z_target
    assert new_z == expected_z
예제 #6
0
def load_attitude_matrix() -> DeckCalibration:
    calibration_data = get.get_robot_deck_attitude()
    if not calibration_data and ff.enable_calibration_overhaul():
        gantry_cal = robot_configs.load().gantry_calibration
        if validate_gantry_calibration(gantry_cal) == DeckTransformState.OK:
            log.debug(
                "Attitude deck calibration matrix not found. Migrating "
                "existing affine deck calibration matrix to {}".format(
                    config.get_opentrons_path('robot_calibration_dir')))
            attitude = migrate_affine_xy_to_attitude(gantry_cal)
            modify.save_robot_deck_attitude(transform=attitude,
                                            pip_id=None,
                                            lw_hash=None)
            calibration_data = get.get_robot_deck_attitude()

    deck_cal_obj = None
    if calibration_data:
        try:
            deck_cal_obj = DeckCalibration(**calibration_data)
        except Exception as e:
            log.warning(f"Bad deck calibration, falling back to identity: {e}")

    if not deck_cal_obj:
        deck_cal_obj = DeckCalibration(
            attitude=robot_configs.DEFAULT_DECK_CALIBRATION_V2)
    return deck_cal_obj
예제 #7
0
def save_config(config) -> str:
    try:
        robot_configs.save_robot_settings(config)
        robot_configs.save_deck_calibration(config)
        result = robot_configs.load()
    except Exception as e:
        result = repr(e)
    return result
예제 #8
0
async def arun(config: rc.robot_config = None, port: str = None):
    """ Asynchronous entrypoint for the server

    :param config: Optional config override
    :param port: Optional smoothie port override
    """
    rconf = config or rc.load()
    hc = await API.build_hardware_controller(rconf, port)  # noqa(F841)
예제 #9
0
def test_load_corrupt_json():
    import os
    filename = os.path.join(os.path.dirname(__file__), 'bad_config.json')
    with open(filename, 'w') as file:
        file.write('')  # empty config file
    c = robot_configs.load(filename)
    assert c.version == 3
    os.remove(filename)
예제 #10
0
async def get_calibration_status(hardware: ThreadManager = Depends(
    get_hardware)) -> CalibrationStatus:
    robot_conf = robot_configs.load()
    return CalibrationStatus(
        deckCalibration=DeckCalibrationStatus(
            status=hardware.validate_calibration(),
            data=robot_conf.gantry_calibration),
        instrumentCalibration=robot_conf.instrument_offset)
예제 #11
0
def reset_tip_probe():
    config = rc.load()
    config = config._replace(
        instrument_offset=rc.build_fallback_instrument_offset({}))
    if ff.enable_calibration_overhaul():
        delete.clear_tip_length_calibration()
    else:
        config.tip_length.clear()
    rc.save_robot_settings(config)
예제 #12
0
def smoothie(monkeypatch):
    from opentrons.drivers.smoothie_drivers.driver_3_0 import \
         SmoothieDriver_3_0_0 as SmoothieDriver
    from opentrons.config import robot_configs

    monkeypatch.setenv('ENABLE_VIRTUAL_SMOOTHIE', 'true')
    driver = SmoothieDriver(robot_configs.load())
    driver.connect()
    yield driver
    driver.disconnect()
    monkeypatch.setenv('ENABLE_VIRTUAL_SMOOTHIE', 'false')
예제 #13
0
    def __init__(self, config=None, broker=None):
        """
        Initializes a robot instance.

        Notes
        -----
        This class is a singleton. That means every time you call
        :func:`__init__` the same instance will be returned. There's
        only once instance of a robot.
        """
        super().__init__(broker)
        self.config = config or load()
예제 #14
0
def initialize_logging():
    """Initialize logging"""
    # TODO Amit 2019/04/08 Move the logging level from robot configs to
    #  robot server mutable configs.
    robot_conf = robot_configs.load()
    level = logging._nameToLevel.get(robot_conf.log_level.upper(),
                                     logging.INFO)

    if IS_ROBOT:
        c = _robot_log_config(level)
    else:
        c = _dev_log_config(level)
    dictConfig(c)
예제 #15
0
def smoothie(monkeypatch):
    from opentrons.drivers.smoothie_drivers.driver_3_0 import \
         SmoothieDriver_3_0_0 as SmoothieDriver
    from opentrons.config import robot_configs

    monkeypatch.setenv('ENABLE_VIRTUAL_SMOOTHIE', 'true')
    driver = SmoothieDriver(robot_configs.load())
    driver.connect()
    yield driver
    try:
        driver.disconnect()
    except AttributeError:
        # if the test disconnected
        pass
    monkeypatch.setenv('ENABLE_VIRTUAL_SMOOTHIE', 'false')
예제 #16
0
async def get_calibration_status(hardware: ThreadManager = Depends(
    get_hardware)) -> CalibrationStatus:
    robot_conf = robot_configs.load()
    if ff.enable_calibration_overhaul():
        deck_cal = hardware.robot_calibration.deck_calibration
        deck_cal_data = DeckCalibrationData(
            type=MatrixType.attitude,
            matrix=deck_cal.attitude,
            lastModified=deck_cal.last_modified,
            pipetteCalibratedWith=deck_cal.pipette_calibrated_with,
            tiprack=deck_cal.tiprack)
    else:
        deck_cal_data = DeckCalibrationData(
            type=MatrixType.affine, matrix=robot_conf.gantry_calibration)

    return CalibrationStatus(
        deckCalibration=DeckCalibrationStatus(
            status=hardware.validate_calibration(), data=deck_cal_data),
        instrumentCalibration=robot_conf.instrument_offset)
예제 #17
0
async def _do_fw_update(new_fw_path, new_fw_ver):
    """ Update the connected smoothie board, with retries

    When the API server boots, it talks to the motor controller board for the
    first time. Sometimes the board is in a bad state - it might have the
    wrong firmware version (i.e. this is the first boot after an update), or it
    might just not be communicating correctly. Sometimes, the motor controller
    not communicating correctly in fact means it needs a firmware update; other
    times, it might mean it just needs to be reset.

    This function is called when the API server boots if either of the above
    cases happens. Its job is to make the motor controller board ready by
    updating its firmware, regardless of the state of the rest of the stack.

    To that end, this function uses the smoothie driver directly (so it can
    ignore the rest of the stack) and has a couple retries with different
    hardware line changes in between (so it can catch all failure modes). If
    this method ultimately fails, it lets the server boot by telling it to
    consider itself virtual.

    After this function has completed, it is always safe to call
    hardware.connect() - it just might be virtual
    """
    explicit_modeset = False
    driver = SmoothieDriver_3_0_0(robot_configs.load())
    for attempts in range(3):
        try:
            await driver.update_firmware(
                new_fw_path,
                explicit_modeset=explicit_modeset)
        except RuntimeError:
            explicit_modeset = True
            continue

        if driver.get_fw_version() == new_fw_ver:
            log.info(f"Smoothie fw update complete in {attempts} tries")
            break
        else:
            log.error(
                "Failed to update smoothie: did not connect after update")
    else:
        log.error("Could not update smoothie, forcing virtual")
        os.environ['ENABLE_VIRTUAL_SMOOTHIE'] = 'true'
예제 #18
0
async def reset(request: web.Request) -> web.Response:  # noqa(C901)
    """ Execute a reset of the requested parts of the user configuration.

    POST /settings/reset {resetOption: Any}

    -> 200 OK, {"links": {"restart": uri}}
    -> 400 Bad Request, {"error": error-shortmessage, "message": str}
    """
    data = await request.json()
    ok, bad_key = _check_reset(data)
    if not ok:
        return web.json_response(
            {
                'error': 'bad-reset-option',
                'message': f'{bad_key} is not a valid reset option'
            },
            status=400)
    log.info("Reset requested for {}".format(', '.join(data.keys())))
    if data.get('tipProbe'):
        config = rc.load()
        config = config._replace(
            instrument_offset=rc.build_fallback_instrument_offset({}))
        config.tip_length.clear()
        rc.save_robot_settings(config)

    if data.get('labwareCalibration'):
        labware.clear_calibrations()
        db.reset()

    if data.get('customLabware'):
        labware.delete_all_custom_labware()

    if data.get('bootScripts'):
        if IS_ROBOT:
            if os.path.exists('/data/boot.d'):
                shutil.rmtree('/data/boot.d')
        else:
            log.debug('Not on pi, not removing /data/boot.d')
    return web.json_response({}, status=200)
예제 #19
0
async def check_for_smoothie_update():
    driver = SmoothieDriver_3_0_0(robot_configs.load())

    try:
        driver.connect()
        fw_version = driver.get_fw_version()
    except Exception as e:
        # The most common reason for this exception (aside from hardware
        # failures such as a disconnected smoothie) is that the smoothie
        # is in programming mode. If it is, then we still want to update
        # it (so it can boot again), but we don’t have to do the GPIO
        # manipulations that _put_ it in programming mode
        log.exception("Error while connecting to motor driver: {}".format(e))
        fw_version = None

    log.info(f"Smoothie FW version: {fw_version}")
    packed_smoothie_fw_file, packed_smoothie_fw_ver = _find_smoothie_file()
    if fw_version != packed_smoothie_fw_ver:
        log.info(f"Executing smoothie update: current vers {fw_version},"
                 f" packed vers {packed_smoothie_fw_ver}")
        await _do_fw_update(driver, packed_smoothie_fw_file,
                            packed_smoothie_fw_ver)
    else:
        log.info(f"FW version OK: {packed_smoothie_fw_ver}")
예제 #20
0
def clear_configuration_and_reload(hardware):
    robot_configs.clear()
    new_config = robot_configs.load()
    hardware.set_config(new_config)
예제 #21
0
def test_old_probe_height(short_trash_flag):
    cfg = robot_configs.load()

    assert cfg.tip_probe.center[2] == 55.0
    assert cfg.tip_probe.dimensions[2] == 60.0
예제 #22
0
@pytest.mark.parametrize('paramtype,native,serializable', [
    (Mount, Mount.LEFT, 'LEFT'),
    (Axis, Axis.A, 'A'),
    (CriticalPoint, CriticalPoint.NOZZLE, 'NOZZLE'),
    (Point, Point(1, 2, 3), [1, 2, 3]),
    (Dict[Mount, str], {
        Mount.LEFT: 'way hay'
    }, {
        'LEFT': 'way hay'
    }),
    (Dict[Axis, bool], {
        Axis.X: True
    }, {
        'X': True
    }),
    (robot_configs.robot_config, robot_configs.load(),
     list(robot_configs.config_to_save(robot_configs.load()))),
    (Dict[Mount, Pipette.DictType], {
        Mount.LEFT: {
            'asdasd': 2,
            'asda': False
        }
    }, {
        'LEFT': {
            'asdasd': 2,
            'asda': False
        }
    }),
    (List[Axis], [Axis.X, Axis.A], ['X', 'A']),
    (Optional[CriticalPoint], None, None),
    (Optional[CriticalPoint], CriticalPoint.NOZZLE, 'NOZZLE'),
예제 #23
0
def test_default_probe_height():
    cfg = robot_configs.load()
    assert cfg.tip_probe.center[2] == 74.3
    assert cfg.tip_probe.dimensions[2] == 79.3
예제 #24
0
def mock_config():
    yield robot_configs.load()
    robot_configs.clear()
예제 #25
0
def mock_config():
    """Robot config setup and teardown"""
    yield robot_configs.load()
    robot_configs.clear()
예제 #26
0
async def test_old_probe_height(short_trash_flag):
    cfg = robot_configs.load()

    assert cfg.tip_probe.center[2] == 55.0
    assert cfg.tip_probe.dimensions[2] == 60.0
    assert cfg.gantry_calibration == DEFAULT_SIMULATION_CALIBRATION
예제 #27
0
def reset_tip_probe():
    config = rc.load()
    config = config._replace(
        instrument_offset=rc.build_fallback_instrument_offset({}))
    config.tip_length.clear()
    rc.save_robot_settings(config)
예제 #28
0
def clear_configuration_and_reload(hardware):
    robot_configs.clear()
    new_config = robot_configs.load()
    hardware.set_config(new_config)
    if not feature_flags.use_protocol_api_v2():
        hardware.reset()
예제 #29
0
def test_default_probe_height():
    cfg = robot_configs.load()
    assert cfg.tip_probe.center[2] == 74.3
    assert cfg.tip_probe.dimensions[2] == 79.3
    assert cfg.gantry_calibration == DEFAULT_SIMULATION_CALIBRATION