Exemple #1
0
    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()
Exemple #2
0
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
Exemple #3
0
    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
Exemple #4
0
    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()
Exemple #5
0
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
Exemple #7
0
    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()
Exemple #8
0
        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()