def start(self, aproxy, signals, bad_channels=None, min_duration=.15): ''' # TODO - incorporate `execute()` coroutine - add ''' if self.is_alive(): raise RuntimeError('Executor is already running.') channel_plan, completed_transfers = self.channel_plan() @asyncio.coroutine def execute_test(*args, **kwargs): yield asyncio.From(set_capacitance_update_interval()) try: result = yield asyncio\ .From(qc.ui.plan.transfer_windows(*args, **kwargs)) except qc.ui.plan.TransferFailed as exception: # Save intermediate result. result = dict( channel_plan=exception.channel_plan, completed_transfers=exception.completed_transfers) signals.signal('test-interrupt').send(caller_name(0), **result) self.completed_results.append(result) yield asyncio.From( aproxy.set_state_of_channels(pd.Series(), append=False)) # result = dict(channel_plan=channel_plan_i, # completed_transfers=completed_transfers_i) raise asyncio.Return(result) @asyncio.coroutine def set_capacitance_update_interval(): state = yield asyncio.From(aproxy.state) max_update_interval = int(.5 * min_duration * 1e3) if state.capacitance_update_interval_ms > max_update_interval \ or state.capacitance_update_interval_ms == 0: yield asyncio\ .From(aproxy.update_state(capacitance_update_interval_ms= max_update_interval)) looped_channel_plan = ( channel_plan + nx.shortest_path(self.channels_graph, channel_plan[-1], self.base_channel_plan[0])[1:]) self._task = aioh.cancellable(execute_test) transfer_liquid = ft.partial(qc.ui.plan.transfer_liquid, aproxy, min_duration=min_duration) self._thread = threading.Thread(target=self._task, args=(signals, looped_channel_plan, completed_transfers, transfer_liquid), kwargs={'n': 3}) self._thread.daemon = True self._thread.start()
def monitor(): signals = blinker.Namespace() @asyncio.coroutine def dropbot_monitor(*args): try: yield asyncio.From(db.monitor.monitor(*args)) except asyncio.CancelledError: _L().info('Stopped DropBot monitor.') monitor_task = cancellable(dropbot_monitor) thread = threading.Thread(target=monitor_task, args=(signals, )) thread.daemon = True thread.start() return monitor_task
def _connect(*args): signals.clear() signals.signal('chip-inserted').connect(dump, weak=False) signals.signal('connected').connect(on_connected, weak=False) signals.signal('disconnected').connect(on_disconnected, weak=False) signals.signal('shorts-detected').connect(dump, weak=False) signals.signal('version-mismatch').connect(ignore, weak=False) monitor_task = cancellable(db.monitor.monitor) thread = threading.Thread(target=monitor_task, args=(signals, )) thread.daemon = True thread.start() while not connected.wait(1): pass monitor_task.proxy = connected.proxy return monitor_task
def on_plugin_enable(self): ''' .. versionchanged:: 2.25 Start asyncio event loop in background thread to process ZeroMQ hub execution requests. ''' super(ZmqHubPlugin, self).on_plugin_enable() app_values = self.get_app_values() self.cleanup() self.hub_process = Process(target=_safe_run_hub, args=(MicroDropHub(app_values['hub_uri'], self.name), getattr(logging, app_values['log_level'] .upper()))) # Set process as daemonic so it terminate when main process terminates. self.hub_process.daemon = True self.hub_process.start() _L().info('ZeroMQ hub process (pid=%s, daemon=%s)', self.hub_process.pid, self.hub_process.daemon) zmq_ready = threading.Event() @asyncio.coroutine def _exec_task(): self.zmq_plugin = ZmqPlugin('microdrop', get_hub_uri()) self.zmq_plugin.reset() zmq_ready.set() event = asyncio.Event() try: yield asyncio.From(event.wait()) except asyncio.CancelledError: _L().info('closing ZeroMQ execution event loop') self.zmq_exec_task = cancellable(_exec_task) self.exec_thread = threading.Thread(target=self.zmq_exec_task) self.exec_thread.deamon = True self.exec_thread.start() zmq_ready.wait()
def watch_plugin(executor, plugin, wait_duration_s=0.01, callback=None): ''' .. versionadded:: 0.4 Launch cancellable background thread to monitor plugin socket(s). Parameters ---------- executor : concurrent.futures.ThreadPoolExecutor Executor to use for launching background thread. plugin : zmq_plugin.plugin.Plugin Plugin instance (without `reset()` called yet). wait_duration_s : float, optional Duration (in seconds) to wait between polls of ZeroMQ socket(s). callback : function, optional Callback function to call after each wait duration. If not specified, process any outstanding requests on the plugin command socket. Returns ------- function Function submitted to background thread, with attached `cancel()` method to allow thread to be stopped/cancelled. ''' import threading from asyncio_helpers import cancellable import trollius as asyncio if callback is None: def callback(): msg_frames = (plugin.command_socket.recv_multipart(zmq.NOBLOCK)) plugin.on_command_recv(msg_frames) zmq_ready = threading.Event() @asyncio.coroutine def _check_command_socket(): ''' Process each incoming message on the ZeroMQ plugin command socket. Stop listening if :data:`stopped` event is set. ''' # Initialize sockets. plugin.reset() zmq_ready.set() while True: try: callback() except zmq.Again: # No message ready. yield asyncio.From(asyncio.sleep(wait_duration_s)) except ValueError: # Message was empty or not valid JSON. pass task = cancellable(_check_command_socket) executor.submit(task) zmq_ready.wait() return task
def monitor(client=None): if client is None: client = Client() client.on_connect = on_connect client.connect_async('localhost') client.loop_start() client_created = True else: client_created = False signals = blinker.Namespace() @asyncio.coroutine def _on_dropbot_connected(sender, **message): monitor_task.connected.clear() dropbot_ = message['dropbot'] monitor_task.dropbot = dropbot_ client.on_message = ft.partial(on_message, 'dropbot', proxy=dropbot_) device_id = str(dropbot_.uuid) connect_topic = '/dropbot/%(uuid)s/signal' % {'uuid': device_id} send_topic = '/dropbot/%(uuid)s/send-signal' % {'uuid': device_id} # Bind blinker signals namespace to corresponding MQTT topics. bind(signals=signals, paho_client=client, connect_topic=connect_topic, send_topic=send_topic) dropbot_.update_state(event_mask=EVENT_CHANNELS_UPDATED | EVENT_SHORTS_DETECTED | EVENT_ENABLE) client.publish('/dropbot/%(uuid)s/properties' % {'uuid': device_id}, payload=dropbot_.properties.to_json(), qos=1, retain=True) prefix = '/dropbot/' + device_id monitor_task.device_id = device_id monitor_task.property = ft.partial(wait_for_result, client, 'property', prefix) monitor_task.call = ft.partial(wait_for_result, client, 'call', prefix) monitor_task.connected.set() @asyncio.coroutine def _on_dropbot_disconnected(sender, **message): monitor_task.connected.clear() monitor_task.dropbot = None unbind(signals) client.publish('/dropbot/%(uuid)s/properties' % {'uuid': monitor_task.device_id}, payload=None, qos=1, retain=True) signals.signal('connected').connect(_on_dropbot_connected, weak=False) signals.signal('disconnected').connect(_on_dropbot_disconnected, weak=False) def stop(): if getattr(monitor_task, 'dropbot', None) is not None: monitor_task.dropbot.set_state_of_channels(pd.Series(), append=False) monitor_task.dropbot.update_state(capacitance_update_interval_ms=0, hv_output_enabled=False) try: unbind(monitor_task.signals) except RuntimeError as e: _L().warning('%s', e) monitor_task.cancel() if client_created: client.loop_stop() client.disconnect() monitor_task = cancellable(catch_cancel(db.monitor.monitor)) monitor_task.connected = threading.Event() thread = threading.Thread(target=monitor_task, args=(signals, )) thread.daemon = True thread.start() monitor_task.signals = signals monitor_task.stop = stop monitor_task.close = stop return monitor_task
def on_plugin_enable(self): # Start joypad listener. self.task = ah.cancellable(check_joypad) self.signals.clear() liquid_state = {} def _on_changed(message): self._most_recent_message = message if ((abs(message['new']['axes']['x']) > .4) ^ (abs(message['new']['axes']['y']) > .4)): # Either **x** or **y** (_not_ both) is pressed. if message['new']['axes']['x'] > .4: # Right. direction = 'right' elif message['new']['axes']['x'] < -.4: # Left. direction = 'left' if message['new']['axes']['y'] > .4: # Down. direction = 'down' elif message['new']['axes']['y'] < -.4: # Up. direction = 'up' if all((direction in ('right', 'left'), liquid_state, message['new']['button_states'][3])): electrodes = liquid_state['electrodes'] if 'i' in liquid_state: i = liquid_state['i'] + (1 if direction == 'right' else -1) else: i = (0 if direction == 'right' else len(electrodes) - 1) i = i % len(electrodes) hub_execute_async('dropbot_plugin', 'identify_electrode', electrode_id=electrodes[i]) liquid_state['i'] = i else: hub_execute_async('microdrop.electrode_controller_plugin', 'set_electrode_direction_states', direction=direction) def _on_buttons_changed(message): if message['buttons'] == {0: True}: # Button 0 was pressed. hub_execute_async('microdrop.electrode_controller_plugin', 'clear_electrode_states') elif message['buttons'] == {3: True}: # Button 3 was pressed. i = liquid_state.get('i') liquid_state.clear() def _on_found(zmq_response): data = decode_content_data(zmq_response) liquid_state['electrodes'] = data if i is not None and i < len(liquid_state['electrodes']): liquid_state['i'] = i hub_execute_async('dropbot_plugin', 'find_liquid', callback=_on_found) elif message['buttons'] == {3: False}: # Button 3 was released. _L().info('Button 3 was released. `%s`', liquid_state) i = liquid_state.get('i') if i is not None: selected_electrode = liquid_state['electrodes'][i] electrode_states = pd.Series(1, index=[selected_electrode]) hub_execute_async('microdrop.electrode_controller_plugin', 'clear_electrode_states') hub_execute_async('microdrop.electrode_controller_plugin', 'set_electrode_states', electrode_states=electrode_states) elif message['buttons'] == {4: True}: # Button 4 was pressed. if message['new']['button_states'][8]: # Button 8 was also held down. hub_execute_async('microdrop.gui.protocol_controller', 'first_step') else: hub_execute_async('microdrop.gui.protocol_controller', 'prev_step') elif message['buttons'] == {5: True}: # Button 5 was pressed. if message['new']['button_states'][8]: # Button 8 was also held down. hub_execute_async('microdrop.gui.protocol_controller', 'last_step') else: hub_execute_async('microdrop.gui.protocol_controller', 'next_step') elif message['buttons'] == {9: True}: # Button 9 was pressed. hub_execute_async('microdrop.gui.protocol_controller', 'run_protocol') elif all(message['buttons'].values()): _L().info('%s', message) self.signals.signal('state-changed').connect(_on_changed, weak=False) self.signals.signal('buttons-changed').connect(_on_buttons_changed, weak=False) thread = threading.Thread(target=self.task, args=(self.signals, 0)) thread.daemon = True thread.start()
def wrapped(sender, **kwargs): signals.signal('chip-detected').disconnect(on_chip_detected) uuid = kwargs['decoded_objects'][0].data ready.uuid = uuid ready.set() # Wait for chip to be detected. response = None while response != QMessageBox.StandardButton.Yes: response = question('Chip detected: `%s`.\n\nReady to load ' 'electrode %s?' % (uuid, start_electrode), title='Chip detected') proxy.stop_switching_matrix() proxy.turn_off_all_channels() @asyncio.coroutine def _run(): dropbot_events = [] def log_event(message): # Add UTC timestamp to each event. message['utc_time'] = dt.datetime.utcnow().isoformat() dropbot_events.append(message) # Log route events in memory. def log_route_event(event, message): # Tag kwargs with route event name. message['event'] = event # Add chip, DropBot, and version info to `test-start` # message. if event == 'test-start': message['uuid'] = uuid message['dropbot.__version__'] = db.__version__ message['dropbot_chip_qc.__version__'] = __version__ message['dropbot'] = { 'system_info': db.self_test.system_info(proxy), 'i2c_scan': db.self_test.test_i2c(proxy) } message['dmf_chip.__version'] = dc.__version__ message['chip-info'] = copy.deepcopy(proxy.chip_info) log_event(message) # Log results of shorts detection tests. proxy.signals.signal('shorts-detected').connect(log_event) if MULTI_SENSING_ENABLED and multi_sensing: # Log multi-sensing capacitance events (in memory). proxy.signals.signal('sensitive-capacitances')\ .connect(log_event) # Use multi-sensing test implementation. _run_test = _multi_run_test else: # Use single-drop test implementation. _run_test = _single_run_test loggers = { e: ft.partial( lambda event, sender, **kwargs: log_route_event( event, kwargs), e) for e in ('electrode-success', 'electrode-fail', 'electrode-skip', 'test-start', 'test-complete') } for event, logger in loggers.items(): signals.signal(event).connect(logger) # Explicitly execute a shorts detection test. for shorted_channel in proxy.detect_shorts(): if shorted_channel in G: print('remove shorted channel: %s' % shorted_channel, file=sys.stderr) G.remove_node(shorted_channel) try: start = time.time() yield asyncio.From( _run_test(signals, proxy, G, way_points, start=start_electrode)) if video_dir: # A video directory was provided. Look for a video # corresponding to the same timeline as the test. # Only consider videos that were created within 1 # minute of the start of the test. videos = sorted( (p for p in video_dir.expand().files('*.mp4') if abs(p.ctime - start) < 60), key=lambda x: -x.ctime) if videos: loop.call_soon_threadsafe(update_video, videos[-1], uuid) except nx.NetworkXNoPath as exception: logging.error('QC test failed: `%s`', exception, exc_info=True) def write_results(): # Substitute UUID into output directory path as necessary. path_subs_dict = {'uuid': uuid} path_subs_dict.update(_date_subs_dict()) output_dir_ = ph.path(output_dir % path_subs_dict).expand().realpath() output_dir_.makedirs_p() # Write logged events to file. output_path = \ output_dir_.joinpath('Chip test report - %s.html' % uuid) if not output_path.exists() or overwrite or \ (question('Output `%s` exists. Overwrite?' % output_path, title='Overwrite?') == QMessageBox.StandardButton.Yes): render_summary(dropbot_events, output_path, svg_source=svg_source) logging.info('wrote events log to: `%s`', output_path) if launch: # Launch result using default system viewer. output_path.launch() loop.call_soon_threadsafe(write_results) signals.signal('chip-detected').connect(on_chip_detected) qc_task = cancellable(_run) thread = threading.Thread(target=qc_task) thread.daemon = True thread.start()