def __init__(self, implementation, *, loop=SharedLoop):
        super(SocketDeviceAdapter, self).__init__(loop=loop)

        # Configuration
        self.set_config('default_timeout', 10.0)
        self.set_config('expiration_time', 60.0)
        self.set_config('max_connections', 100)
        self.set_config('probe_required', True)
        self.set_config('probe_supported', True)

        # Set logger
        self.logger = logging.getLogger(__name__)
        self.logger.addHandler(logging.NullHandler())

        self._report_parser = IOTileReportParser()

        self.client = AsyncSocketClient(implementation, loop=loop)
        self.client.register_event(OPERATIONS.NOTIFY_DEVICE_FOUND,
                                   self._on_device_found,
                                   NOTIFICATIONS.ScanEvent)
        self.client.register_event(OPERATIONS.NOTIFY_TRACE,
                                   self._on_trace_notification,
                                   NOTIFICATIONS.TraceEvent)
        self.client.register_event(OPERATIONS.NOTIFY_REPORT,
                                   self._on_report_notification,
                                   NOTIFICATIONS.ReportEvent)
        self.client.register_event(OPERATIONS.NOTIFY_BROADCAST,
                                   self._on_broadcast_notification,
                                   NOTIFICATIONS.ReportEvent)
        self.client.register_event(OPERATIONS.NOTIFY_PROGRESS,
                                   self._on_progress_notification,
                                   NOTIFICATIONS.ProgressEvent)
        self.client.register_event(OPERATIONS.NOTIFY_SOCKET_DISCONNECT,
                                   self._on_socket_disconnect, NoneVerifier())
    def __init__(self, port, report_callback):
        super(WSIOTileClient,
              self).__init__(port, protocols=['iotile-ws', 'iotile-ws-text'])

        self.connection_established = threading.Event()
        self.messages = Queue()
        self.binary = True
        self.report_callback = report_callback
        self.report_parser = IOTileReportParser()
Exemple #3
0
    def _probe_characteristics_finished(self, result):
        """Callback when BLE adapter has finished probing services and characteristics for a device

        Args:
            result (dict): Result from the probe_characteristics command
        """

        handle = result['context']['handle']
        conn_id = result['context']['connection_id']

        conndata = self._get_connection(handle, 'preparing')

        if conndata is None:
            self._logger.info(
                'Connection disconnected before probe_char... finished, conn_id=%d',
                conn_id)
            return

        callback = conndata['callback']

        if result['result'] is False:
            conndata['failed'] = True
            conndata['failure_reason'] = 'Could not probe GATT characteristics'
            self.disconnect_async(conn_id, self._on_connection_failed)
            return

        # Validate that this is a proper IOTile device
        services = result['return_value']['services']
        if TileBusService not in services:
            conndata['failed'] = True
            conndata[
                'failure_reason'] = 'TileBus service not present in GATT services'
            self.disconnect_async(conn_id, self._on_connection_failed)
            return

        conndata['chars_done_time'] = time.time()
        service_time = conndata['services_done_time'] - conndata['connect_time']
        char_time = conndata['chars_done_time'] - conndata['services_done_time']
        total_time = service_time + char_time
        conndata['state'] = 'connected'
        conndata['services'] = services

        # Create a report parser for this connection for when reports are streamed to us
        conndata['parser'] = IOTileReportParser(
            report_callback=self._on_report,
            error_callback=self._on_report_error)
        conndata['parser'].context = conn_id

        del conndata['disconnect_handler']

        with self.count_lock:
            self.connecting_count -= 1

        self._logger.info(
            "Total time to connect to device: %.3f (%.3f enumerating services, %.3f enumerating chars)",
            total_time, service_time, char_time)
        callback(conndata['connection_id'], self.id, True, None)
Exemple #4
0
def test_report_parser():
    """Make sure we can parse this report in an IOTileReportParser."""

    reading = IOTileReading(0, 0x5000, 100)
    report = BroadcastReport.FromReadings(1, [reading, reading])
    encoded = report.encode()

    parser = IOTileReportParser()
    parser.add_data(encoded)

    assert len(parser.reports) == 1
    assert parser.reports[0].visible_readings == report.visible_readings
Exemple #5
0
    def _open_streaming_interface(self, connection_id, callback):
        """Enable streaming interface for this IOTile device

        Args:
            connection_id (int): The unique identifier for the connection
            callback (callback): Callback to be called when this command finishes
                callback(conn_id, adapter_id, success, failure_reason)
        """

        try:
            context = self.connections.get_context(connection_id)
        except ArgumentError:
            callback(connection_id, self.id, False,
                     "Could not find connection information")
            return

        self._logger.info("Attempting to enable streaming")
        self.connections.begin_operation(connection_id, 'open_interface',
                                         callback,
                                         self.get_config('default_timeout'))

        try:
            characteristic = context['services'][TileBusService][StreamingChar]
        except KeyError:
            self.connections.finish_operation(
                connection_id, False,
                "Can't find characteristic to open streaming interface")
            return

        context['parser'] = IOTileReportParser(
            report_callback=self._on_report,
            error_callback=self._on_report_error)
        context['parser'].context = connection_id

        def on_report_chunk_received(report_chunk):
            """Callback function called when a report chunk has been received."""
            context['parser'].add_data(report_chunk)

        # Register our callback function in the notifications callbacks
        self._register_notification_callback(context['connection_handle'],
                                             characteristic.value_handle,
                                             on_report_chunk_received)

        self.bable.set_notification(
            enabled=True,
            connection_handle=context['connection_handle'],
            characteristic=characteristic,
            on_notification_set=[self._on_interface_opened, context],
            on_notification_received=self._on_notification_received,
            timeout=1.0,
            sync=False)
Exemple #6
0
    def __init__(self, port, name=__name__, loop=SharedLoop, **kwargs):
        super(JLinkAdapter, self).__init__(name, loop)
        self._default_device_info = None
        self._device_info = None
        self._control_info = None
        self._jlink_serial = None
        self._mux_func = None
        self._channel = None
        self._jlink_async = AsyncJLink(self, loop)
        self._task = loop.add_task(None,
                                   name="JLINK Adapter stopper",
                                   finalizer=self.stop)
        self._connection_id = None
        self.jlink = None
        self.connected = False
        self.opened_interfaces = {
            "rpc": False,
            "tracing": False,
            "streaming": False,
            "debug": False,
            "script": False
        }
        self.report_parser = IOTileReportParser(
            report_callback=self._on_report,
            error_callback=self._on_report_error)

        self.set_config('probe_required', True)
        self.set_config('probe_supported', True)

        self._parse_port(port)
Exemple #7
0
    def received_message(self, message):
        unpacked_message = self.unpack(message.data)

        if 'type' in unpacked_message and unpacked_message['type'] == 'report':
            report = IOTileReportParser.DeserializeReport(
                unpacked_message['value'])
            self.report_callback(report)
        else:
            self.messages.put(unpacked_message)
Exemple #8
0
def load_report(filename, received_regex=None):
    """Load a SignedListReport from a file.

    If received_regex is not None, it must be a regular express pattern string
    that will be used to extract the report's received timestmap from the
    filename.

    The first match group in the pattern must be the received time and it
    should be in isoformat except that the time part may be separated by `-`
    instead of `:` so that it can be used as part of a path.
    """

    base_dir = os.path.join(os.path.dirname(__file__), 'data')
    file_path = os.path.join(base_dir, filename)

    with open(file_path, "rb") as infile:
        data = infile.read()

    parser = IOTileReportParser()
    parser.add_data(data)

    assert len(parser.reports) == 1

    report = parser.reports[0]
    assert isinstance(report, SignedListReport)

    if received_regex is not None:
        result = re.match(received_regex, filename)
        assert result is not None

        datetime_string = result.group(1)

        if 'T' in datetime_string:
            date_part, _, time_part = datetime_string.partition('T')
            time_part = time_part.replace('-', ':')
            datetime_string = "T".join([date_part, time_part])

        datetime_obj = dateutil.parser.parse(datetime_string,
                                             fuzzy=True,
                                             ignoretz=True)
        report.received_time = datetime_obj

    return report
def test_stream(connected_virtual_interface):
    """Test to stream reports after streaming interface has been opened."""
    # Get the device configuration from the configuration file.
    with open(get_report_device_string().split('@')[-1], "r") as conf_file:
        config = json.load(conf_file)
        num_reports = int(config['device']['num_readings']) / int(config['device']['report_length'])

    mock_bable, interface = connected_virtual_interface
    connection_handle = interface._connection_handle

    reports_received = threading.Event()
    reports = []

    def on_report(report, connection_id):
        """Callback function called when a report has been processed."""
        reports.append(report)
        if len(reports) == num_reports:  # If all the reports have been received, we are done
            reports_received.set()

    # Create a report parser and register our on_report callback
    parser = IOTileReportParser(report_callback=on_report)

    def on_notification_received(success, result, failure_reason):
        """Callback function called when a notification has been received (the virtual interface sends a notification
        to stream reports)"""
        assert success is True
        assert result['controller_id'] == interface.controller_id
        assert result['connection_handle'] == connection_handle
        assert result['attribute_handle'] == StreamingChar.value_handle

        parser.add_data(result['value'])  # Add the received report chunk to the report parser

    # Register our notification received callback into the mock_bable.
    controller_state = mock_bable.controllers_state[interface.controller_id]
    controller_state['connected'][connection_handle]['on_notification_received'] = (on_notification_received, [])

    # Open the streaming interface
    assert interface.streaming is False
    mock_bable.write_without_response(connection_handle, StreamingChar.config_handle, b'\x01\x00')
    assert interface.streaming is True

    flag = reports_received.wait(timeout=5.0)
    assert flag is True
Exemple #10
0
class WSIOTileClient(WebSocketClient):
    def __init__(self, port, report_callback):
        super(WSIOTileClient,
              self).__init__(port, protocols=['iotile-ws', 'iotile-ws-text'])

        self.connection_established = threading.Event()
        self.messages = Queue()
        self.binary = True
        self.report_callback = report_callback
        self.report_parser = IOTileReportParser()

    def start(self):
        try:
            self.connect()
        except Exception as exc:
            raise HardwareError("Unable to connect to websockets host",
                                reason=str(exc))

        self.connection_established.wait()

    def opened(self):
        protocols = self.protocols

        # Workaround bug in ws4py protocol handling
        if isinstance(protocols, (str, bytes)) and b'i,o,t,i,l,e' in protocols:
            protocols = [protocols.replace(b',', b'')]

        if 'iotile-ws-text' in protocols and 'iotile-ws' not in protocols:
            self.binary = False

        self.connection_established.set()

    def closed(self, code, reason):
        self.connection_established.clear()

    def unpack(self, msg):
        if self.binary is False:
            msg = base64.standard_b64decode(msg)

        return msgpack.unpackb(msg,
                               raw=False,
                               object_hook=self.decode_datetime)

    @classmethod
    def decode_datetime(cls, obj):
        if b'__datetime__' in obj:
            obj = datetime.datetime.strptime(obj[b'as_str'].decode(),
                                             "%Y%m%dT%H:%M:%S.%fZ")
        return obj

    @classmethod
    def encode_datetime(cls, obj):
        if isinstance(obj, datetime.datetime):
            obj = {
                '__datetime__': True,
                'as_str': obj.strftime("%Y%m%dT%H:%M:%S.%fZ").encode()
            }
        return obj

    def received_message(self, message):
        unpacked_message = self.unpack(message.data)

        if 'type' in unpacked_message and unpacked_message['type'] == 'report':
            report = self.report_parser.deserialize_report(
                unpacked_message['value'])
            self.report_callback(report)
        else:
            self.messages.put(unpacked_message)
class SocketDeviceAdapter(StandardDeviceAdapter):
    """ A device adapter allowing connections to devices over any socket implementation

    Args:
        implementation (AbstractSocketClient): The implementation of the socket client
            that will be used to connect to the SocketDeviceServer
        loop (BackgroundEventLoop): Loop for running our websocket client.
    """
    def __init__(self, implementation, *, loop=SharedLoop):
        super(SocketDeviceAdapter, self).__init__(loop=loop)

        # Configuration
        self.set_config('default_timeout', 10.0)
        self.set_config('expiration_time', 60.0)
        self.set_config('max_connections', 100)
        self.set_config('probe_required', True)
        self.set_config('probe_supported', True)

        # Set logger
        self.logger = logging.getLogger(__name__)
        self.logger.addHandler(logging.NullHandler())

        self._report_parser = IOTileReportParser()

        self.client = AsyncSocketClient(implementation, loop=loop)
        self.client.register_event(OPERATIONS.NOTIFY_DEVICE_FOUND,
                                   self._on_device_found,
                                   NOTIFICATIONS.ScanEvent)
        self.client.register_event(OPERATIONS.NOTIFY_TRACE,
                                   self._on_trace_notification,
                                   NOTIFICATIONS.TraceEvent)
        self.client.register_event(OPERATIONS.NOTIFY_REPORT,
                                   self._on_report_notification,
                                   NOTIFICATIONS.ReportEvent)
        self.client.register_event(OPERATIONS.NOTIFY_BROADCAST,
                                   self._on_broadcast_notification,
                                   NOTIFICATIONS.ReportEvent)
        self.client.register_event(OPERATIONS.NOTIFY_PROGRESS,
                                   self._on_progress_notification,
                                   NOTIFICATIONS.ProgressEvent)
        self.client.register_event(OPERATIONS.NOTIFY_SOCKET_DISCONNECT,
                                   self._on_socket_disconnect, NoneVerifier())

    async def start(self):
        """Start the device adapter.

        See :meth:`AbstractDeviceAdapter.start`.
        """

        await self.client.start()

    async def stop(self):
        """Stop the device adapter.

        See :meth:`AbstractDeviceAdapter.stop`.
        """

        await self.client.stop()

    async def probe(self):
        """Probe for devices connected to this adapter.

        See :meth:`AbstractDeviceAdapter.probe`.
        """

        await self._send_command(OPERATIONS.PROBE, None,
                                 COMMANDS.ProbeResponse)

    async def connect(self, conn_id, connection_string):
        """Connect to a device.

        See :meth:`AbstractDeviceAdapter.connect`.
        """

        self._ensure_connection(conn_id, False)

        msg = dict(connection_string=connection_string)
        await self._send_command(OPERATIONS.CONNECT, msg,
                                 COMMANDS.ConnectResponse)

        self._setup_connection(conn_id, connection_string)

    async def disconnect(self, conn_id):
        """Disconnect from a connected device.

        See :meth:`AbstractDeviceAdapter.disconnect`.
        """

        self._ensure_connection(conn_id, True)

        msg = dict(
            connection_string=self._get_property(conn_id, "connection_string"))

        try:
            await self._send_command(OPERATIONS.DISCONNECT, msg,
                                     COMMANDS.DisconnectResponse)
        finally:
            self._teardown_connection(conn_id)

    async def open_interface(self, conn_id, interface):
        """Open an interface on an IOTile device.

        See :meth:`AbstractDeviceAdapter.open_interface`.
        """

        self._ensure_connection(conn_id, True)
        connection_string = self._get_property(conn_id, "connection_string")

        msg = dict(interface=interface, connection_string=connection_string)
        await self._send_command(OPERATIONS.OPEN_INTERFACE, msg,
                                 COMMANDS.OpenInterfaceResponse)

    async def close_interface(self, conn_id, interface):
        """Close an interface on this IOTile device.

        See :meth:`AbstractDeviceAdapter.close_interface`.
        """

        self._ensure_connection(conn_id, True)
        connection_string = self._get_property(conn_id, "connection_string")

        msg = dict(interface=interface, connection_string=connection_string)
        await self._send_command(OPERATIONS.CLOSE_INTERFACE, msg,
                                 COMMANDS.CloseInterfaceResponse)

    async def send_rpc(self, conn_id, address, rpc_id, payload, timeout):
        """Send an RPC to a device.

        See :meth:`AbstractDeviceAdapter.send_rpc`.
        """

        self._ensure_connection(conn_id, True)
        connection_string = self._get_property(conn_id, "connection_string")

        msg = dict(address=address,
                   rpc_id=rpc_id,
                   payload=base64.b64encode(payload),
                   timeout=timeout,
                   connection_string=connection_string)

        response = await self._send_command(OPERATIONS.SEND_RPC,
                                            msg,
                                            COMMANDS.SendRPCResponse,
                                            timeout=timeout)

        return unpack_rpc_response(response.get('status'),
                                   response.get('payload'),
                                   rpc_id=rpc_id,
                                   address=address)

    async def send_script(self, conn_id, data):
        """Send a a script to this IOTile device

        Args:
            conn_id (int): A unique identifier that will refer to this connection
            data (bytes): the script to send to the device
        """

        self._ensure_connection(conn_id, True)
        connection_string = self._get_property(conn_id, "connection_string")

        msg = dict(connection_string=connection_string,
                   fragment_count=1,
                   fragment_index=0,
                   script=base64.b64encode(data))
        await self._send_command(OPERATIONS.SEND_SCRIPT, msg,
                                 COMMANDS.SendScriptResponse)

    async def _on_device_found(self, device):
        """Callback function called when a new device has been scanned by the probe.

        Args:
            response (dict): The device advertisement data
        """

        await self.notify_event(device.get('connection_string'), 'device_seen',
                                device)

    async def _on_report_notification(self, event):
        """Callback function called when a report event is received.

        Args:
            event (dict): The report_event
        """

        conn_string = event.get('connection_string')
        report = self._report_parser.deserialize_report(
            event.get('serialized_report'))

        self.notify_event(conn_string, 'report', report)

    async def _on_broadcast_notification(self, event):
        """Callback function called when a broadcast event is received.

        Args:
            event (dict): The broadcast event
        """

        conn_string = event.get('connection_string')
        report = self._report_parser.deserialize_report(
            event.get('serialized_report'))

        self.notify_event(conn_string, 'broadcast', report)

    async def _on_trace_notification(self, trace_event):
        """Callback function called when a trace chunk is received.

        Args:
            trace_chunk (dict): The received trace chunk information
        """

        conn_string = trace_event.get('connection_string')
        payload = trace_event.get('payload')

        await self.notify_event(conn_string, 'trace', payload)

    async def _on_progress_notification(self, progress):
        """Callback function called when a progress notification is received.

        Args:
            progress (dict): The received notification containing the progress information
        """

        conn_string = progress.get('connection_string')
        done = progress.get('done_count')
        total = progress.get('total_count')
        operation = progress.get('operation')

        await self.notify_progress(conn_string,
                                   operation,
                                   done,
                                   total,
                                   wait=True)

    async def _on_socket_disconnect(self, _event):
        """Callback function called when we have been disconnected from the server (by error or not).
        Allows to clean all if the disconnection was unexpected."""

        self.logger.info('Forcibly disconnected from the socket server')

        conns = self._connections.copy()
        for conn_id in conns:
            conn_string = self._get_property(conn_id, 'connection_string')
            self._teardown_connection(conn_id)
            self.notify_event(conn_string, 'disconnect',
                              "Socket connection closed")

    async def _send_command(self, name, args, verifier, timeout=10.0):
        try:
            return await self.client.send_command(name,
                                                  args,
                                                  verifier,
                                                  timeout=timeout)
        except ExternalError as err:
            raise DeviceAdapterError(None, name, err.params['reason'])
        except asyncio.TimeoutError as err:
            raise DeviceAdapterError(
                None, name, 'operation timed out after %f seconds' % timeout)