def __init__(self, name, protocol, hardware, loop, broker, motion_lock): self._broker = broker self._default_logger = self._broker.logger self._sim_logger = self._broker.logger.getChild('sim') self._run_logger = self._broker.logger.getChild('run') self._loop = loop self.name = name self._protocol = protocol self.api_level = getattr(self._protocol, 'api_level', APIVersion(2, 0)) self._use_v2 = self.api_level >= APIVersion(2, 0) log.info(f'Protocol API Version: {self.api_level}; ' f'Protocol kind: {type(self._protocol)}') # self.metadata is exposed via rpc self.metadata = getattr(self._protocol, 'metadata', {}) self._hardware = hardware self._simulating_ctx = ProtocolContext.build_using(self._protocol, loop=self._loop, broker=self._broker) self.state: 'State' = None #: The current state self.stateInfo: 'StateInfo' = {} #: A message associated with the current state self.commands = [] self.command_log = {} self.errors = [] self._containers = [] self._instruments = [] self._modules = [] self._interactions = [] self.instruments = None self.containers = None self.modules = None self.protocol_text = protocol.text self.startTime: Optional[float] = None self._motion_lock = motion_lock self._event_watcher = None self.door_state: Optional[str] = None self.blocked: Optional[bool] = None self._run_lock: Lock = Lock() self._is_running: Event = Event()
def __init__(self, name, protocol, hardware, loop, broker, motion_lock): self._broker = broker self._default_logger = self._broker.logger self._sim_logger = self._broker.logger.getChild('sim') self._run_logger = self._broker.logger.getChild('run') self._loop = loop self.name = name self._protocol = protocol self.api_level = getattr(self._protocol, 'api_level', APIVersion(2, 0)) self._use_v2 = self.api_level >= APIVersion(2, 0) log.info(f'Protocol API Version: {self.api_level}; ' f'Protocol kind: {type(self._protocol)}') # self.metadata is exposed via rpc self.metadata = getattr(self._protocol, 'metadata', {}) self._hardware = hardware self._simulating_ctx = ProtocolContext.build_using(self._protocol, loop=self._loop, broker=self._broker) self.state = None self.commands = [] self.command_log = {} self.errors = [] self._containers = [] self._instruments = [] self._modules = [] self._interactions = [] self.instruments = None self.containers = None self.modules = None self.protocol_text = protocol.text self.startTime = None self._motion_lock = motion_lock
def _run(self): def on_command(message): if message['$'] == 'before': self.log_append() if message['name'] == command_types.PAUSE: self.set_state('paused') if message['name'] == command_types.RESUME: self.set_state('running') self._reset() _unsubscribe = self._broker.subscribe(command_types.COMMAND, on_command) self.startTime = now() self.set_state('running') try: if self._use_v2: self.resume() self._pre_run_hooks() self._hardware.cache_instruments() ctx = ProtocolContext.build_using(self._protocol, loop=self._loop, broker=self._broker, extra_labware=getattr( self._protocol, 'extra_labware', {})) ctx.connect(self._hardware) ctx.home() run_protocol(self._protocol, context=ctx) else: robot.broker = self._broker assert isinstance(self._protocol, PythonProtocol),\ 'Internal error: v1 should only be used for python' if not robot.is_connected(): robot.connect() self.resume() self._pre_run_hooks() robot.cache_instrument_models() robot.discover_modules() exec(self._protocol.contents, {}) # If the last command in a protocol was a pause, the protocol # will immediately finish executing because there's no smoothie # command to block... except the home that's about to happen, # which will confuse the app and lock it up. So we need to # do our own pause here, and sleep the thread until/unless the # app resumes us. # # Cancelling from the app during this pause will result in the # smoothie giving us an error during the subsequent home, which # is tragic but expected. while self.state == 'paused': sleep(0.1) self.set_state('finished') self._hw_iface().home() except SmoothieAlarm: log.info("Protocol cancelled") except Exception as e: log.exception("Exception during run:") self.error_append(e) self.set_state('error') raise e finally: _unsubscribe()
def _simulate(self): self._reset() stack = [] res = [] commands = [] 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', '') } sim = adapters.SynchronousAdapter.build( API.build_hardware_simulator, instrs, [mod.name() for mod in self._hardware.attached_modules], strict_attached_instruments=False) sim.home() self._simulating_ctx = ProtocolContext.build_using( self._protocol, loop=self._loop, hardware=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.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 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 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