async def test_synch_adapter(): thread_manager = ThreadManager(API.build_hardware_simulator) synch = thread_manager.sync synch.cache_instruments({Mount.LEFT: 'p10_single'}) assert synch.attached_instruments[Mount.LEFT]['name']\ .startswith('p10_single') thread_manager.clean_up()
async def test_hw_manager(loop): # When built without an input it should build its own adapter mgr = HardwareManager(None) adapter = mgr.hardware # When "disconnecting" from its own simulator, the adapter should # be stopped and a new one created new = mgr.reset_hw() assert new is not adapter # When deleted, the self-created adapter should be stopped del mgr # When built with a hardware API input it should wrap it with a new # synchronous adapter and not build its own API sim = await API.build_hardware_simulator(loop=loop) mgr = HardwareManager(sim) assert isinstance(mgr.hardware, adapters.SynchronousAdapter) passed = mgr.hardware # When disconnecting from a real external adapter, it should create # its own simulator and should new = mgr.reset_hw() assert new is not passed thread_manager = ThreadManager(API.build_hardware_simulator) sa = thread_manager.sync # When connecting to an adapter it shouldn’t rewrap it assert mgr.set_hw(sa) is sa del mgr thread_manager.clean_up()
async def initialize_robot() -> ThreadManager: if os.environ.get("ENABLE_VIRTUAL_SMOOTHIE"): log.info("Initialized robot using virtual Smoothie") return ThreadManager(API.build_hardware_simulator) packed_smoothie_fw_file, packed_smoothie_fw_ver = _find_smoothie_file() systemd.daemon.notify("READY=1") hardware = ThreadManager(API.build_hardware_controller, threadmanager_nonblocking=True, firmware=(packed_smoothie_fw_file, packed_smoothie_fw_ver)) try: await hardware.managed_thread_ready_async() except RuntimeError: log.exception('Could not build hardware controller, forcing virtual') return ThreadManager(API.build_hardware_simulator) loop = asyncio.get_event_loop() async def blink(): while True: await hardware.set_lights(button=True) await asyncio.sleep(0.5) await hardware.set_lights(button=False) await asyncio.sleep(0.5) blink_task = loop.create_task(blink()) if not ff.disable_home_on_boot(): log.info("Homing Z axes") await hardware.home_z() blink_task.cancel() await hardware.set_lights(button=True) return hardware
def sync_hardware(request, loop, virtual_smoothie_env): thread_manager = ThreadManager(API.build_hardware_controller) hardware = thread_manager.sync try: yield hardware finally: hardware.reset() hardware.set_config(config.robot_configs.load()) thread_manager.clean_up()
async def initialize_robot() -> ThreadManager: if os.environ.get("ENABLE_VIRTUAL_SMOOTHIE"): log.info("Initialized robot using virtual Smoothie") return ThreadManager(API.build_hardware_simulator) await check_for_smoothie_update() hardware = ThreadManager(API.build_hardware_controller) if not ff.disable_home_on_boot(): log.info("Homing Z axes") await hardware.home_z() return hardware
def main(): """ The main entrypoint for the Opentrons robot API server stack. This function - creates and starts the server for both the RPC routes handled by :py:mod:`opentrons.server.rpc` and the HTTP routes handled by :py:mod:`opentrons.server.http` - initializes the hardware interaction handled by either :py:mod:`opentrons.legacy_api` or :py:mod:`opentrons.hardware_control` This function does not return until the server is brought down. """ app_settings = get_settings() if app_settings.simulator_configuration_file_path: # A path to a simulation configuration is defined. Let's use it. checked_hardware = ThreadManager( load_simulator, Path(app_settings.simulator_configuration_file_path)) else: # Create the hardware checked_hardware = initialize_api( hardware_server=app_settings.hardware_server_enable, hardware_server_socket=app_settings.hardware_server_socket_path) run(hardware=checked_hardware, hostname=app_settings.ws_host_name, port=app_settings.ws_port, path=app_settings.ws_domain_socket)
async def test_session_no_pipettes_error(): simulator = ThreadManager(API.build_hardware_simulator) with pytest.raises(RobotServerError) as e: await session.CheckCalibrationSession.build(simulator) assert e.value.status_code == 403 assert e.value.error.title == "No Pipette Attached"
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 __init__(self, hardware: Optional[HardwareToManage]): if hardware is None: self._current = ThreadManager(API.build_hardware_simulator).sync elif isinstance(hardware, SynchronousAdapter): self._current = hardware elif isinstance(hardware, ThreadManager): self._current = hardware.sync else: self._current = SynchronousAdapter(hardware)
def __init__(self, hardware: ThreadManager): self._hardware = hardware self._relate_mount = self._key_by_uuid( hardware.get_attached_instruments() ) self._deck = geometry.Deck() self._slot_options = ['8', '6'] self._labware_info = self._determine_required_labware() self._moves = self._build_deck_moves()
async def check_calibration_session_only_left(loop) \ -> session.CheckCalibrationSession: attached_instruments = { types.Mount.LEFT: { 'model': 'p300_single_v1', 'id': 'fake300pip' } } simulator = ThreadManager(API.build_hardware_simulator, attached_instruments=attached_instruments) return await session.CheckCalibrationSession.build(simulator)
def __init__(self, hardware: ThreadManager, lights_on_before: bool = False): self._hardware = hardware self._lights_on_before = lights_on_before deck_load_name = SHORT_TRASH_DECK if ff.short_fixed_trash() \ else STANDARD_DECK self._deck = geometry.Deck(load_name=deck_load_name) self._pip_info_by_mount = self._get_pip_info_by_mount( hardware.get_attached_instruments()) self._labware_info = self._determine_required_labware() self._moves = self._build_deck_moves()
async def hardware(request, loop, virtual_smoothie_env): hw_sim = ThreadManager(API.build_hardware_simulator) old_config = config.robot_configs.load() try: yield hw_sim finally: config.robot_configs.clear() hw_sim.set_config(old_config) hw_sim.clean_up()
def __init__(self, hardware: ThreadManager): self._hardware = hardware self._deck = geometry.Deck() self._pip_info_by_mount = self._get_pip_info_by_mount( hardware.get_attached_instruments()) if ff.short_fixed_trash(): trash_lw = labware.load( 'opentrons_1_trash_850ml_fixed', self._deck.position_for('12')) else: trash_lw = labware.load( 'opentrons_1_trash_1100ml_fixed', self._deck.position_for('12')) self._deck['12'] = trash_lw self._trash_lw = trash_lw self._labware_info = self._determine_required_labware() self._moves = self._build_deck_moves()
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)
def main(loop=None): """ A CLI application for performing factory calibration of an Opentrons robot Instructions: - Robot must be set up with two 300ul or 50ul single-channel pipettes installed on the right-hand and left-hand mount. - Put a GEB 300ul tip onto the pipette. - Use the arrow keys to jog the robot over slot 5 in an open space that is not an engraving or a hole. - Use the 'q' and 'a' keys to jog the pipette up and down respectively until the tip is just touching the deck surface, then press 'z'. This will save the 'Z' height. - Press '1' to automatically go to the expected location of the first calibration point. Jog the robot until the tip is actually at the point, then press 'enter'. - Repeat with '2' and '3'. - After calibrating all three points, press the space bar to save the configuration. - Optionally, press 4,5,6 or 7 to validate the new configuration. - Press 'p' to perform tip probe. Press the space bar to save again. - Press 'm' to perform mount calibration. Press enter and then space bar to save again. - Press 'esc' to exit the program. """ prompt = input( ">>> Warning! Running this tool backup and clear any previous " "calibration data. Proceed (y/[n])? ") if prompt not in ['y', 'Y', 'yes']: print('Exiting--prior configuration data not changed') sys.exit() # Notes: # - 200ul tip is 51.7mm long when attached to a pipette # - For xyz coordinates, (0, 0, 0) is the lower-left corner of the robot parser = argparse.ArgumentParser(prog='opentrons deck calibration', description=__doc__) parser.add_argument('-t', '--pickupTip', help='What to output during simulations', default=None, action='store_true') parser.add_argument('-l', '--log-level', action='store', help=('Log level for deck calibration.'), choices=['error', 'warning', 'info', 'debug'], default='info') args = parser.parse_args() sync_hardware = ThreadManager(API.build_hardware_controller).sync sync_hardware.set_lights(rails=True) # Register hook to reboot the robot after exiting this tool (regardless of # whether this process exits normally or not) atexit.register(notify_and_restart) backup_configuration_and_reload(sync_hardware) cli = CLITool(point_set=get_calibration_points(), hardware=sync_hardware, pickup_tip=args.pickupTip, loop=loop, log_level=args.log_level) cli.home() # lights help the script user to see the points on the deck cli.ui_loop.run() try: print('Robot config: \n', cli.hardware.config) except Exception: pass
async def test_session_no_pipettes_error(): simulator = ThreadManager(API.build_hardware_simulator) with pytest.raises(NoPipetteException): await session.CheckCalibrationSession.build(simulator)
def get_protocol_api( version: Union[str, APIVersion], bundled_labware: Dict[str, 'LabwareDefinition'] = None, bundled_data: Dict[str, bytes] = None, extra_labware: Dict[str, 'LabwareDefinition'] = None, ) -> protocol_api.ProtocolContext: """ Build and return a :py:class:`ProtocolContext` connected to the robot. This can be used to run protocols from interactive Python sessions such as Jupyter or an interpreter on the command line: .. code-block:: python >>> from opentrons.execute import get_protocol_api >>> protocol = get_protocol_api('2.0') >>> instr = protocol.load_instrument('p300_single', 'right') >>> instr.home() If ``extra_labware`` is not specified, any labware definitions saved in the ``labware`` directory of the Jupyter notebook directory will be available. When this function is called, modules and instruments will be recached. :param version: The API version to use. This must be lower than :py:attr:`opentrons.protocol_api.MAX_SUPPORTED_VERSION`. It may be specified either as a string (``'2.0'``) or as a :py:class:`.protocols.types.APIVersion` (``APIVersion(2, 0)``). :param bundled_labware: If specified, a mapping from labware names to labware definitions for labware to consider in the protocol. Note that if you specify this, _only_ labware in this argument will be allowed in the protocol. This is preparation for a beta feature and is best not used. :param bundled_data: If specified, a mapping from filenames to contents for data to be available in the protocol from :py:attr:`.ProtocolContext.bundled_data`. :param extra_labware: If specified, a mapping from labware names to labware definitions for labware to consider in the protocol in addition to those stored on the robot. If this is an empty dict, and this function is called on a robot, it will look in the 'labware' subdirectory of the Jupyter data directory for custom labware. :returns opentrons.protocol_api.ProtocolContext: The protocol context. """ global _THREAD_MANAGED_HW if not _THREAD_MANAGED_HW: # Build a hardware controller in a worker thread, which is necessary # because ipython runs its notebook in asyncio but the notebook # is at script/repl scope not function scope and is synchronous so # you can't control the loop from inside. If we update to # IPython 7 we can avoid this, but for now we can't _THREAD_MANAGED_HW = ThreadManager(API.build_hardware_controller) if isinstance(version, str): checked_version = version_from_string(version) elif not isinstance(version, APIVersion): raise TypeError('version must be either a string or an APIVersion') else: checked_version = version if extra_labware is None\ and IS_ROBOT\ and JUPYTER_NOTEBOOK_LABWARE_DIR.is_dir(): # type: ignore extra_labware = labware_from_paths([str(JUPYTER_NOTEBOOK_LABWARE_DIR)]) context = protocol_api.ProtocolContext(hardware=_THREAD_MANAGED_HW, bundled_labware=bundled_labware, bundled_data=bundled_data, extra_labware=extra_labware, api_version=checked_version) context._hw_manager.hardware.cache_instruments() return context
def build_driver( port: str = None)\ -> Tuple[adapters.SynchronousAdapter, SmoothieDriver_3_0_0]: hardware = ThreadManager(API.build_hardware_controller).sync driver = hardware._backend._smoothie_driver return hardware, driver
def connect_to_port(hardware): if options.port: hardware.connect(options.port) else: hardware.connect() driver = opentrons.drivers.smoothie_drivers.SimulatingDriver() try: api = opentrons.hardware_control.API adapter = opentrons.hardware_control.adapters parser = optparse.OptionParser(usage='usage: %prog [options] ') parser.add_option( "-p", "--p", dest="port", default='', type='str', help='serial port of the smoothie' ) options, _ = parser.parse_args(args=sys.argv, values=None) if options.port: port = options.port else: port = None hardware = ThreadManager(API.build_hardware_controller).sync driver = hardware._backend._smoothie_driver except AttributeError: hardware = None # type: ignore driver = None # type: ignore
def reset_hw(self): self._current = ThreadManager(API.build_hardware_simulator).sync return self._current
async def toggle_lights(hardware: ThreadManager, *args): light_state = hardware.get_lights() await hardware.set_lights(rails=not light_state.get('rails', False))
async def build(cls, hardware: ThreadManager): lights_on = hardware.get_lights()['rails'] await hardware.cache_instruments() await hardware.set_lights(rails=True) await hardware.home() return cls(hardware=hardware, lights_on_before=lights_on)
async def get_robot_light_state( hardware: ThreadManager = Depends(get_hardware)) \ -> control.RobotLightState: light_state = hardware.get_lights() # type: ignore return control.RobotLightState(on=light_state.get('rails', False))
def __init__(self, hardware: ThreadManager): self._pipettes = self._key_by_uuid(hardware.get_attached_instruments()) self._hardware = hardware
def _simulate(self): self._reset() stack: List[Dict[str, Any]] = [] res: List[Dict[str, Any]] = [] commands: List[Dict[str, Any]] = [] self._containers.clear() self._instruments.clear() self._modules.clear() self._interactions.clear() def on_command(message): payload = message['payload'] description = payload.get('text', '').format( **payload ) if message['$'] == 'before': level = len(stack) stack.append(message) commands.append(payload) res.append( { 'level': level, 'description': description, 'id': len(res)}) else: stack.pop() unsubscribe = self._broker.subscribe(command_types.COMMAND, on_command) old_robot_connect = robot.connect try: # ensure actual pipettes are cached before driver is disconnected self._hardware.cache_instruments() if self._use_v2: instrs = {} for mount, pip in self._hardware.attached_instruments.items(): if pip: instrs[mount] = {'model': pip['model'], 'id': pip.get('pipette_id', '')} sync_sim = ThreadManager( API.build_hardware_simulator, instrs, [mod.name() for mod in self._hardware.attached_modules], strict_attached_instruments=False ).sync sync_sim.home() self._simulating_ctx = ProtocolContext.build_using( self._protocol, loop=self._loop, hardware=sync_sim, broker=self._broker, extra_labware=getattr(self._protocol, 'extra_labware', {})) run_protocol(self._protocol, context=self._simulating_ctx) else: robot.broker = self._broker # we don't rely on being connected anymore so make sure we are robot.connect() robot._driver.gpio_chardev = SimulatingGPIOCharDev('sim_chip') robot.cache_instrument_models() robot.disconnect() def robot_connect_error(port=None, options=None): raise RuntimeError( 'Protocols executed through the Opentrons App may not ' 'use robot.connect(). Allowing this call would cause ' 'the robot to execute commands during simulation, and ' 'then raise an error on execution.') robot.connect = robot_connect_error # type: ignore exec(self._protocol.contents, {}) finally: # 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 if not self._use_v2: robot.connect = old_robot_connect # type: ignore robot.connect() unsubscribe() instruments, containers, modules, interactions = _accumulate( [_get_labware(command) for command in commands]) self._containers.extend(_dedupe(containers)) self._instruments.extend(_dedupe( instruments + list(self._simulating_ctx.loaded_instruments.values()))) self._modules.extend(_dedupe( modules + [m._geometry for m in self._simulating_ctx.loaded_modules.values()])) self._interactions.extend(_dedupe(interactions)) # Labware calibration happens after simulation and before run, so # we have to clear the tips if they are left on after simulation # to ensure that the instruments are in the expected state at the # beginning of the labware calibration flow if not self._use_v2: robot.clear_tips() return res