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()
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)
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
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)
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)
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)
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
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)