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)
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
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()
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
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
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
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
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)
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)
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)
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)
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')
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()
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)
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')
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)
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'
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)
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}")
def clear_configuration_and_reload(hardware): robot_configs.clear() new_config = robot_configs.load() hardware.set_config(new_config)
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
@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'),
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
def mock_config(): yield robot_configs.load() robot_configs.clear()
def mock_config(): """Robot config setup and teardown""" yield robot_configs.load() robot_configs.clear()
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
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)
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()
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