def sync_command(self, cmd): if self._stop_event.is_set(): self._logger.warning( "Command %s sent after background processor was stopped; failing immediately", cmd) raise HardwareError( "Synchronous command %s failed because background processor was stopped" % cmd) done_event = threading.Event() results = [] def done_callback(result): results.append(result) done_event.set() self._commands.put((cmd, done_callback, True, None)) done_event.wait() success = results[0]['result'] retval = results[0]['return_value'] if not success: raise HardwareError("Error executing synchronous command", command=cmd, return_value=retval) return retval
def __init__(self, address, raw_data): self.base_address = address magic1, magic2, magic3, magic4, version, flags, length = struct.unpack_from( "<LLLLBBH", raw_data) if magic1 != self.CONTROL_MAGIC_1 or magic2 != self.CONTROL_MAGIC_2 or magic3 != self.CONTROL_MAGIC_3 or magic4 != self.CONTROL_MAGIC_4: raise HardwareError( "Invalid control structure with an incorrect magic number", base_address=address) self.version = version self.flags = flags if len(raw_data) < length: raise HardwareError( "Control structure raw data is too short for encoded length", encoded_length=length, received_length=len(raw_data)) elif len(raw_data) > length: raw_data = raw_data[:length] if version not in self.KNOWN_VERSIONS: raise HardwareError( "Unknown version embedded in control structure", version=version, known_versions=self.KNOWN_VERSIONS) self._parse_control_structure(raw_data)
def connect(self, uuid_value, wait=None): """Connect to a specific device by its uuid Attempt to connect to a device that we have previously scanned using its UUID. If wait is not None, then it is used in the same was a scan(wait) to override default wait times with an explicit value. Args: uuid_value (int): The unique id of the device that we would like to connect to. wait (float): Optional amount of time to force the device adapter to wait before attempting to connect. """ if self.connected: raise HardwareError("Cannot connect when we are already connected") if uuid_value not in self._scanned_devices: self.scan(wait=wait) with self._scan_lock: if uuid_value not in self._scanned_devices: raise HardwareError( "Could not find device to connect to by UUID", uuid=uuid_value) connstring = self._scanned_devices[uuid_value]['connection_string'] self.connect_direct(connstring)
def _try_reconnect(self): """Try to recover an interrupted connection.""" try: if self.connection_interrupted: self._connect_direct(self.connection_string) self.connection_interrupted = False self.connected = True # Reenable streaming interface if that was open before as well if self._reports is not None: res = self.adapter.open_interface_sync(0, 'streaming') if not res['success']: raise HardwareError( "Could not open streaming interface to device", reason=res['failure_reason']) # Reenable tracing interface if that was open before as well if self._traces is not None: res = self.adapter.open_interface_sync(0, 'tracing') if not res['success']: raise HardwareError( "Could not open tracing interface to device", reason=res['failure_reason']) except HardwareError as exc: raise HardwareError( "Device disconnected unexpectedly and we could not reconnect", reconnect_error=exc)
async def find_control_structure(self, start_address, search_length): """Find the control structure in RAM for this device. Returns: ControlStructure: The decoded contents of the shared memory control structure used for communication with this IOTile device. """ words = await self.read_memory(start_address, search_length, chunk_size=4, join=False) found_offset = None for i, word in enumerate(words): if word == ControlStructure.CONTROL_MAGIC_1: if (len(words) - i) < 4: continue if words[i + 1] == ControlStructure.CONTROL_MAGIC_2 and words[i + 2] == ControlStructure.CONTROL_MAGIC_3 and words[i + 3] == ControlStructure.CONTROL_MAGIC_4: found_offset = i break if found_offset is None: raise HardwareError("Could not find control structure magic value in search area") struct_info = words[found_offset + 4] _version, _flags, length = struct.unpack("<BBH", struct.pack("<L", struct_info)) if length % 4 != 0: raise HardwareError("Invalid control structure length that was not a multiple of 4", length=length) word_length = length // 4 control_data = struct.pack("<%dL" % word_length, *words[found_offset:found_offset + word_length]) logger.info("Found control stucture at address 0x%08X, word_length=%d", start_address + 4*found_offset, word_length) return ControlStructure(start_address + 4*found_offset, control_data)
def connect_direct(self, connection_string, no_rpc=False, force=False): """Directly connect to a device using its stream specific connection string. Normally, all connections to a device include opening the RPC interface to send RPCs. However, there are certain, very specific, circumstances when you would not want to or be able to open the RPC interface (such as when you are using the debug interface on a bare MCU that has not been programmed yet). In those cases you can pass no_rpc=True to not attempt to open the RPC interface. If you do not open the RPC interface at connection time, there is no public interface to open it later, so you must disconnect and reconnect to the device in order to open the interface. Args: connection_string (str): The connection string that identifies the desired device. no_rpc (bool): Do not open the RPC interface on the device (default=False). force (bool): Whether to force another connection even if we think we are currently connected. This is for internal use and not designed to be set externally. """ if not force and self.connected: raise HardwareError( "Cannot connect when we are already connected to '%s'" % self.connection_string) self._loop.run_coroutine(self.adapter.connect(0, connection_string)) try: if no_rpc: self._logger.info("Not opening RPC interface on device %s", self.connection_string) else: self._loop.run_coroutine(self.adapter.open_interface(0, 'rpc')) except HardwareError as exc: self._logger.exception("Error opening RPC interface on device %s", connection_string) self._loop.run_coroutine(self.adapter.disconnect(0)) raise exc except Exception as exc: self._logger.exception("Error opening RPC interface on device %s", connection_string) self._loop.run_coroutine(self.adapter.disconnect(0)) raise HardwareError( "Could not open RPC interface on device due to an exception: %s" % str(exc)) from exc self.connected = True self.connection_string = connection_string self.connection_interrupted = False
def set_report_size(self, size=0xFFFFFFFF): """ Sets and verifies the report size for a pod Args: size (int): The maximum size of a report """ error, = self._con.rpc(0x0A, 0x05, size, 0, arg_format="LB", result_format="L") if error: raise HardwareError("Error setting report size.", error_code=error, size=size) maxpacket, _comp1, comp2, = self._con.rpc(0x0A, 0x06, result_format="LBB") if maxpacket != size: raise HardwareError("Max Packet Size was not set as expected")
def _enable_tracing(self): self._traces = queue.Queue() res = self.adapter.open_interface_sync(0, 'tracing') if not res['success']: raise HardwareError("Could not open tracing interface to device", reason=res['failure_reason']) return self._traces
def enable_tracing(self): """Open the tracing interface and accumulate traces in a queue. This method is safe to call multiple times in a single device connection. There is no way to check if the tracing interface is opened or to close it once it is opened (apart from disconnecting from the device). The first time this method is called, it will open the tracing interface and return a queue that will be filled asynchronously with reports as they are received. Subsequent calls will just empty the queue and return the same queue without interacting with the device at all. Returns: queue.Queue: A queue that will be filled with trace data from the device. The trace data will be in disjoint bytes objects in the queue """ if not self.connected: raise HardwareError( "Cannot enable tracing if we are not in a connected state") if self._traces is not None: _clear_queue(self._traces) return self._traces self._traces = queue.Queue() self._loop.run_coroutine(self.adapter.open_interface(0, 'tracing')) return self._traces
def send_highspeed(self, data, progress_callback): """Send a script to a device at highspeed, reporting progress. This method takes a binary blob and downloads it to the device as fast as possible, calling the passed progress_callback periodically with updates on how far it has gotten. Args: data (bytes): The binary blob that should be sent to the device at highspeed. progress_callback (callable): A function that will be called periodically to report progress. The signature must be callback(done_count, total_count) where done_count and total_count will be passed as integers. """ if not self.connected: raise HardwareError( "Cannot send a script if we are not in a connected state") if isinstance(data, str) and not isinstance(data, bytes): raise ArgumentError( "You must send bytes or bytearray to _send_highspeed", type=type(data)) if not isinstance(data, bytes): data = bytes(data) try: self._on_progress = progress_callback self._loop.run_coroutine(self.adapter.send_script(0, data)) finally: self._on_progress = None
async def _try_connect(self, connection_string): """If the connecton string settings are different, try and connect to an attached device""" if self._parse_conn_string(connection_string): if self.connected is True: info = {"reason": "Reconnection", "expected": True} self.notify_event(connection_string, 'disconnection', info) self.connected = False await self.stop() if self._mux_func is not None: self._mux_func(self._channel) if self._device_info is None: raise ArgumentError( "Missing device name or alias, specify using device=name in port string " "or -c device=name in connect_direct or debug command", known_devices=[x for x in DEVICE_ALIASES.keys()]) try: await self._jlink_async.connect_jlink( self._jlink_serial, self._device_info.jlink_name) self.connected = True except pylink.errors.JLinkException as exc: if exc.code == exc.VCC_FAILURE: raise HardwareError( "No target power detected", code=exc.code, suggestion="Check jlink connection and power wiring") raise except: raise
def enable_debug(self): """Open the debug interface on the connected device.""" if not self.connected: raise HardwareError("Cannot enable debug if we are not in a connected state") self._loop.run_coroutine(self.adapter.open_interface(0, 'debug'))
def _send_rpc(self, device_info, control_info, address, rpc_id, payload, poll_interval, timeout): """Write and trigger an RPC.""" write_address, write_data = control_info.format_rpc(address, rpc_id, payload) self._jlink.memory_write32(write_address, write_data) self._trigger_rpc(device_info) start = monotonic() now = start poll_address, poll_mask = control_info.poll_info() while (now - start) < timeout: time.sleep(poll_interval) value, = self._jlink.memory_read8(poll_address, 1) if value & poll_mask: break now = monotonic() if (now - start) >= timeout: raise HardwareError("Timeout waiting for RPC response", timeout=timeout, poll_interval=poll_interval) read_address, read_length = control_info.response_info() read_data = self._read_memory(read_address, read_length, join=True) return control_info.format_response(read_data)
def disconnect_async(self, connection_id, callback): """Asynchronously disconnect from a device that has previously been connected Args: connection_id (int): a unique identifier for this connection on the DeviceManager that owns this adapter. callback (callable): A function called as callback(connection_id, adapter_id, success, failure_reason) when the disconnection finishes. Disconnection can only either succeed or timeout. """ try: context = self.connections.get_context(connection_id) except ArgumentError: callback(connection_id, self.id, False, "Could not find connection information") return connection_string = context['connection_string'] self.connections.begin_disconnection( connection_id, callback, self.get_config('default_timeout')) try: self.send_command_async(operations.DISCONNECT, connection_string=connection_string) except Exception as err: failure_reason = "Error while sending 'disconnect' command to ws server: {}".format( err) self.connections.finish_disconnection(connection_id, False, failure_reason) raise HardwareError(failure_reason)
def get(self, address, basic=False): """Create a proxy object for a tile by address. The correct proxy object is determined by asking the tile for its status information and looking up the appropriate proxy in our list of installed proxy objects. If you want to send raw RPCs, you can get a basic TileBusProxyObject by passing basic=True. """ tile = self._create_proxy('TileBusProxyObject', address) if basic: return tile name = tile.tile_name() version = tile.tile_version() # Now create the appropriate proxy object based on the name and version of the tile tile_type = self.get_proxy(name) if tile_type is None: raise HardwareError("Could not find proxy object for tile", name="'{}'".format(name), known_names=self._name_map.keys()) tile = tile_type(self.stream, address) tile._hwmanager = self return tile
async def _poll_queue_status(self, control_info): """ Read next frame from queue Returns: bool: true if queue is empty """ read_address, write_address, queue_size_address = control_info.queue_info() if read_address != (write_address - 1) and write_address != (queue_size_address - 1): raise HardwareError("Read/Write/Queue Size addresses are not algined.") queue_info = await self.read_memory(read_address, 4, chunk_size=1) read_index = queue_info[0] write_index = queue_info[1] queue_size = queue_info[2] + (queue_info[3] << 8) if read_index == write_index: return True try: await self._read_queue_frames(control_info, read_index, write_index, queue_size) except (HardwareError, pylink.errors.JLinkException): logger.debug("Queue poll exception.", exc_info=True) except: logger.exception("Unexpected queue poll exception!") if queue_size != 0: read_index = write_index await self.write_memory(read_address, [read_index], chunk_size=1) return read_index == write_index
def _send_rpc(self, address, feature, cmd, payload, **kwargs): timeout = 3.0 if 'timeout' in kwargs: timeout = float(kwargs['timeout']) # If our connection was interrupted before this RPC, try to recover it if self.connection_interrupted: self._try_reconnect() result = self.adapter.send_rpc_sync(0, address, (feature << 8) | cmd, payload, timeout) success = result['success'] status = result['status'] payload = result['payload'] # Sometimes RPCs can cause the device to go offline, so try to reconnect to it. # For example, the RPC could cause the device to reset itself. if self.connection_interrupted: self._try_reconnect() if not success: raise HardwareError("Could not send RPC", reason=result['failure_reason']) return status, payload
def _try_connect(self, connection_string): """If the connecton string settings are different, try and connect to an attached device""" if self._parse_conn_string(connection_string): self._trigger_callback('on_disconnect', self.id, self._connection_id) self.stop_sync() if self._mux_func is not None: self._mux_func(self._channel) if self._device_info is None: raise ArgumentError("Missing device name or alias, specify using device=name in port string or -c device=name in connect_direct or debug command", known_devices=[x for x in viewkeys(DEVICE_ALIASES)]) try: self.jlink = pylink.JLink() self.jlink.open(serial_no=self._jlink_serial) self.jlink.set_tif(pylink.enums.JLinkInterfaces.SWD) self.jlink.connect(self._device_info.jlink_name) self.jlink.set_little_endian() except pylink.errors.JLinkException as exc: if exc.code == exc.VCC_FAILURE: raise HardwareError("No target power detected", code=exc.code, suggestion="Check jlink connection and power wiring") raise except: raise self._control_thread = JLinkControlThread(self.jlink) self._control_thread.start() self.set_config('probe_required', True) self.set_config('probe_supported', True)
def connect_async(self, connection_id, connection_string, callback): """Asynchronously connect to a device by its connection_string Args: connection_id (int): A unique integer set by the caller for referring to this connection once created connection_string (string): A DeviceAdapter specific string that can be used to connect to a device using this DeviceAdapter. callback (callable): A callback function called when the connection has succeeded or failed """ context = {'connection_string': connection_string} self.connections.begin_connection(connection_id, connection_string, callback, context, self.get_config('default_timeout')) try: self.send_command_async(operations.CONNECT, connection_string=connection_string) except Exception as err: failure_reason = "Error while sending 'connect' command to ws server: {}".format( err) self.connections.finish_connection(connection_id, False, failure_reason) raise HardwareError(failure_reason)
def _close_interface(self, connection_id, interface, callback): """Asynchronously close an interface on the device Args: connection_id (int): the unique identifier for the connection interface (string): the interface name to open callback (callback): Callback to be called when this command finishes callback(connection_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 connection_string = context['connection_string'] self.connections.begin_operation(connection_id, 'close_interface', callback, self.get_config('default_timeout')) try: self.send_command_async(operations.CLOSE_INTERFACE, connection_string=connection_string, interface=interface) except Exception as err: failure_reason = "Error while sending 'close_interface' command to ws server: {}".format( err) self.connections.finish_operation(connection_id, False, failure_reason) raise HardwareError(failure_reason)
def _connect_direct(self, connection_string): res = self.adapter.connect_sync(0, connection_string) if not res['success']: self.adapter.periodic_callback() raise HardwareError("Could not connect to device", reason=res['failure_reason'], connection_string=connection_string) try: res = self.adapter.open_interface_sync(0, 'rpc') except Exception as exc: self.adapter.disconnect_sync(0) self.adapter.periodic_callback() raise HardwareError("Could not open RPC interface on device due to an exception", exception=str(exc)) if not res['success']: self.adapter.disconnect_sync(0) self.adapter.periodic_callback() raise HardwareError("Could not open RPC interface on device", reason=res['failure_reason'], connection_string=connection_string)
def _trigger_rpc(self, device_info): """Trigger an RPC in a device specific way.""" method = device_info.rpc_trigger if isinstance(method, devices.RPCTriggerViaSWI): self._jlink.memory_write32(method.register, [1 << method.bit]) else: raise HardwareError("Unknown RPC trigger method", method=method)
def _trigger_streamer(self, index): error, = self._con.rpc(0x20, 0x10, index, result_format="L") # This error code means that the streamer did not have any new data if error == 0x8003801f: self.logger.info("We manually triggered streamer %d but it reported that there were no new readings", index) elif error != 0: raise HardwareError("Error triggering streamer", code=error, index=index)
def send_rpc(self, address, rpc_id, call_payload, timeout=3.0): """Send an rpc to our connected device. The device must already be connected and the rpc interface open. This method will synchronously send an RPC and wait for the response. Any RPC errors will be raised as exceptions and if there were no errors, the RPC's response payload will be returned as a binary bytearray. See :meth:`AbstractDeviceAdapter.send_rpc` for documentation of the possible exceptions that can be raised here. Args: address (int): The tile address containing the RPC rpc_id (int): The ID of the RPC that we wish to call. call_payload (bytes): The payload containing encoded arguments for the RPC. timeout (float): The maximum number of seconds to wait for the RPC to finish. Defaults to 3s. Returns: bytearray: The RPC's response payload. """ if not self.connected: raise HardwareError( "Cannot send an RPC if we are not in a connected state") if timeout is None: timeout = 3.0 status = -1 payload = b'' recording = None if self.connection_interrupted: self._try_reconnect() if self._record is not None: recording = _RecordedRPC(self.connection_string, address, rpc_id, call_payload) recording.start() try: payload = self._loop.run_coroutine( self.adapter.send_rpc(0, address, rpc_id, call_payload, timeout)) status, payload = pack_rpc_response(payload, None) except VALID_RPC_EXCEPTIONS as exc: status, payload = pack_rpc_response(payload, exc) if self._record is not None: recording.finish(status, payload) self._recording.append(recording) if self.connection_interrupted: self._try_reconnect() return unpack_rpc_response(status, payload, rpc_id, address)
async def _trigger_rpc(self, device_info): """Trigger an RPC in a device specific way.""" self.rpc_response_event.clear() method = device_info.rpc_trigger if isinstance(method, devices.RPCTriggerViaSWI): await self.write_memory(method.register, [1 << method.bit], chunk_size=4) else: raise HardwareError("Unknown RPC trigger method", method=method)
def enable_tracing(self): if not self.connected: raise HardwareError( "Cannot enable tracing if we are not in a connected state") if not hasattr(self, '_enable_tracing'): raise StreamOperationNotSupportedError(command="enable_tracing") return self._enable_tracing()
def _debug_command(self, cmd, args, progress_callback=None): def _progress_callback(_finished, _total): pass res = self.adapter.debug_sync(0, cmd, args, progress_callback) if not res['success']: raise HardwareError("Could not execute debug command %s on device" % cmd, reason=res['failure_reason']) return res.get('return_value')
def send_script_async(self, connection_id, data, progress_callback, callback): """Asynchronously send a a script to this IOTile device Args: connection_id (int): A unique identifier that will refer to this connection data (string): the script to send to the device progress_callback (callable): A function to be called with status on our progress, called as: progress_callback(done_count, total_count) callback (callable): A callback for when we have finished sending the script. The callback will be called as callback(connection_id, adapter_id, success, failure_reason) 'connection_id': the connection id 'adapter_id': this adapter's id 'success': a bool indicating whether we received a response to our attempted RPC 'failure_reason': a string with the reason for the failure if success == False """ try: context = self.connections.get_context(connection_id) except ArgumentError: callback(connection_id, self.id, False, "Could not find connection information") return connection_string = context['connection_string'] context['progress_callback'] = progress_callback self.connections.begin_operation(connection_id, 'script', callback, self.get_config('default_timeout')) mtu = int(self.get_config( 'mtu', 60 * 1024)) # Split script payloads larger than this # Count number of chunks to send nb_chunks = 1 if len(data) > mtu: nb_chunks = len(data) // mtu if len(data) % mtu != 0: nb_chunks += 1 # Send the script out possibly in multiple chunks if it's larger than our maximum transmit unit for i in range(0, nb_chunks): start = i * mtu chunk = data[start:start + mtu] try: self.send_command_async(operations.SEND_SCRIPT, connection_string=connection_string, script=base64.b64encode(chunk), fragment_count=nb_chunks, fragment_index=i) except Exception as err: failure_reason = "Error while sending 'send_script' command to ws server: {}".format( err) self.connections.finish_operation(connection_id, False, failure_reason) raise HardwareError(failure_reason)
def send_highspeed(self, data, progress_callback=None): if not self.connected: raise HardwareError( "Cannot send highspeed data if we are not in a connected state" ) if not hasattr(self, '_send_highspeed'): raise StreamOperationNotSupportedError(command="send_highspeed") return self._send_highspeed(data, progress_callback)
def _done_callback(result): retval = result['return_value'] if not result['result']: future.set_exception( HardwareError("Error executing synchronous command", command=cmd, return_value=retval)) else: future.set_result(retval)