def __str__(self): """Get a string representing the last sample. Return: str: A string representing the last sample. """ with lock(self): sample = self._last_sample if sample is None: return self._name + ': Unknown' if not sample._data: return self._name + ': Unknown' result = '' if len(sample._data) >= 2: result = '%s(%d): Activity is \"%s\", Time is \"%s\"' \ % (self._name, sample._timestamp, str(self.get_activity(sample)), str(self.get_time(sample)) ) if len(sample._data) == 3: result += ', Algorithm is \"%d\"' \ % (self.get_algorithm(sample)) return result
def _build_debug_console(self, debug_service): """Build a debug console used to read/write debug messages from/to the Bluetooth device. Args: debug_service (Service): The BLE service. Refer to `Service <https://ianharvey.github.io/bluepy-doc/service.html>`_ for more information. Returns: :class:`blue_st_sdk.debug_console.DebugConsole`: A debug console used to read/write debug messages from/to the Bluetooth device. None if the device doesn't export the needed characteristics. """ try: stdinout = None stderr = None with lock(self): characteristics = debug_service.getCharacteristics() for characteristic in characteristics: if str(characteristic.uuid) == \ str(Debug.DEBUG_STDINOUT_BLUESTSDK_SERVICE_UUID): stdinout = characteristic elif str(characteristic.uuid) == \ str(Debug.DEBUG_STDERR_BLUESTSDK_SERVICE_UUID): stderr = characteristic if stdinout and stderr: return DebugConsole(self, stdinout, stderr) return None except BTLEException as e: self._unexpected_disconnect()
def write_feature(self, feature, data): """Synchronous request to write a feature. Args: feature (:class:`blue_st_sdk.feature.Feature`): The feature to write. data (str): The data to be written. Raises: :exc:`blue_st_sdk.utils.blue_st_exceptions.BlueSTInvalidOperationException` is raised if the feature is not enabled or the operation required is not supported. """ if not feature.is_enabled(): raise BlueSTInvalidOperationException(' The "' + feature.get_name() + '" feature is not enabled.') characteristic = feature.get_characteristic() if not self.characteristic_can_be_written(characteristic): raise BlueSTInvalidOperationException( ' The "' + feature.get_name() + '" feature is not writeable.') try: with lock(self): char_handle = characteristic.getHandle() self.writeCharacteristic(char_handle, data, True) except BTLEException as e: self._unexpected_disconnect()
def characteristic_has_other_notifying_features(self, characteristic, feature): """Check whether a characteristic has other enabled features beyond the given one. Args: characteristic (Characteristic): The BLE characteristic to check. Refer to `Characteristic <https://ianharvey.github.io/bluepy-doc/characteristic.html>`_ for more information. feature (:class:`blue_st_sdk.feature.Feature`): The given feature. Returns: True if the characteristic has other enabled features beyond the given one, False otherwise. """ with lock(self): features = self._get_corresponding_features( characteristic.getHandle()) for feature_entry in features: if feature_entry == feature: pass elif feature_entry.is_notifying(): return True return False
def _build_features_known_uuid(self, characteristic, feature_classes): """Build the given features of a BLE characteristic. After building the features, add them to the dictionary of the features to be updated. Args: characteristic (Characteristic): The BLE characteristic. Refer to `Characteristic <https://ianharvey.github.io/bluepy-doc/characteristic.html>`_ for more information. feature_classes (list): The list of feature-classes to instantiate. """ # Build the features. features = [] for feature_class in feature_classes: feature = self._build_feature_from_class(feature_class) if feature is not None: feature.set_enable(True) features.append(feature) self._available_features.append(feature) # If the features are valid, add an entry for the corresponding # characteristic. try: if features: with lock(self): self._update_char_handle_to_features_dict[ characteristic.getHandle()] = features except BTLEException as e: self._unexpected_disconnect()
def discover(self, timeout_s=SCANNING_TIME_DEFAULT_s, asynchronous=False, show_warnings=False): """Perform the discovery process. This method can be run in synchronous (blocking) or asynchronous (non-blocking) way. Default is synchronous. The discovery process will last *timeout_s* seconds if provided, a default timeout otherwise. Please note that when running a discovery process, the already connected devices get disconnected (limitation intrinsic to the bluepy library). Args: timeout_s (int, optional): Time in seconds to wait before stopping the discovery process. asynchronous (bool, optional): If True the method is run in asynchronous way, thus non-blocking the execution of the thread, the opposite otherwise. show_warnings (bool, optional): If True shows warnings, if any, when discovering devices not respecting the BlueSTSDK's advertising data format, nothing otherwise. Returns: bool: True if the synchronous discovery has finished or if the asynchronous discovery has started, False if a discovery is already running. Raises: :exc:`blue_st_sdk.utils.blue_st_exceptions.BlueSTInvalidOperationException` is raised if this method is not run as root. """ try: if not asynchronous: # Synchronous version. if self.is_discovering(): return False self._discovered_nodes = [] self._notify_discovery_change(True) with lock(self): self._scanner = \ Scanner().withDelegate(_ScannerDelegate(show_warnings)) self._scanner.scan(timeout_s) self._notify_discovery_change(False) return True else: # Asynchronous version. if not self.start_discovery(show_warnings): return False threading.Timer(timeout_s, self.stop_discovery).start() return True except BTLEException as e: msg = '\nBluetooth scanning requires root privileges, ' \ 'so please run the application with \"sudo\".' raise BlueSTInvalidOperationException(msg)
def _unexpected_disconnect(self): """Handle an unexpected disconnection.""" try: # Disconnecting. self._update_node_status(NodeStatus.UNREACHABLE) with lock(self): super(Node, self).disconnect() self._update_node_status(NodeStatus.IDLE, True) except BTLEException as e: pass
def add_logger(self, logger): """Add a logger. Args: logger (:class:`blue_st_sdk.feature.FeatureLogger`): Logger to be added. """ if logger is not None: with lock(self): if not logger in self._loggers: self._loggers.append(logger)
def _set_listener(self, listener): """Set the listener to the debug console. Args: listener (:class:`blue_st_sdk.firmware_upgrade.debug_console.DebugConsoleListener`): Listener to the debug console. """ with lock(self): self._debug_console.remove_listener(self._debug_console_listener) self._debug_console.add_listener(listener) self._debug_console_listener = listener
def remove_logger(self, logger): """Remove a logger. Args: logger (:class:`blue_st_sdk.feature.FeatureLogger`): Logger to be removed. """ if logger is not None: with lock(self): if logger in self._loggers: self._loggers.remove(logger)
def remove_listener(self, listener): """Remove a listener. Args: listener (:class:`blue_st_sdk.manager.ManagerListener`): Listener to be removed. """ if listener is not None: with lock(self): if listener in self._listeners: self._listeners.remove(listener)
def add_listener(self, listener): """Add a listener. Args: listener (:class:`blue_st_sdk.manager.ManagerListener`): Listener to be added. """ if listener is not None: with lock(self): if not listener in self._listeners: self._listeners.append(listener)
def remove_listener(self, listener): """Remove a listener. Args: listener (:class:`blue_st_sdk.firmware_upgrade.utils.firmware_upgrade.FirmwareUpgradeListener`): Listener to be removed. """ if listener is not None: with lock(self): if listener in self._listeners: self._listeners.remove(listener)
def add_listener(self, listener): """Add a listener. Args: listener (:class:`blue_st_sdk.firmware_upgrade.utils.firmware_upgrade.FirmwareUpgradeListener`): Listener to be added. """ if listener is not None: with lock(self): if not listener in self._listeners: self._listeners.append(listener)
def stop(self): """Stop the thread.""" self._stop_called.set() while not (self._process_done.isSet() or self._exc): pass try: self._exc = None with lock(self): self._scanner.stop() except BTLEException as e: # Save details of the exception raised but don't re-raise, just # complete the function. import sys self._exc = sys.exc_info()
def add_listener(self, listener): """Adding a listener. Args: listener (:class:`blue_st_sdk.debug.DebugListener`): Listener to be added. """ if listener is not None: with lock(self): if not listener in self._listeners: self._listeners.append(listener) if self._listeners: self._node.set_notification_status( self._stdinout_characteristic, True) self._node.set_notification_status( self._stderr_characteristic, True)
def remove_listener(self, listener): """Remove a listener. Args: listener (:class:`blue_st_sdk.debug.DebugListener`): Listener to be removed. """ if listener is not None: with lock(self): if listener in self._listeners: self._listeners.remove(listener) if not self._listeners: self._node.set_notification_status( self._stdinout_characteristic, False) self._node.set_notification_status( self._stderr_characteristic, False)
def read_feature(self, feature): """Synchronous request to read a feature. Args: feature (:class:`blue_st_sdk.feature.Feature`): The feature to read. Raises: :exc:`blue_st_sdk.utils.blue_st_exceptions.BlueSTInvalidOperationException` is raised if the feature is not enabled or the operation required is not supported. :exc:`blue_st_sdk.utils.blue_st_exceptions.BlueSTInvalidDataException` if the data array has not enough data to read. """ if not feature.is_enabled(): raise BlueSTInvalidOperationException(' The "' + feature.get_name() + '" feature is not enabled.') characteristic = feature.get_characteristic() if not self.characteristic_can_be_read(characteristic): raise BlueSTInvalidOperationException(' The "' + feature.get_name() + '" feature is not readable.') # Reading data. try: with lock(self): char_handle = characteristic.getHandle() data = self.readCharacteristic(char_handle) # Calling on-read callback. if self._debug_console and \ Debug.is_debug_characteristic(str(characteristic.uuid)): # Calling on-read callback for a debug characteristic. self._debug_console.on_update_characteristic( characteristic, data) else: # Calling on-read callback for the other characteristics. self._update_features(char_handle, data, False) except BlueSTInvalidDataException as e: raise e except BTLEException as e: self._unexpected_disconnect()
def characteristic_can_be_notified(self, characteristic): """Check if a characteristics can be notified. Args: characteristic (Characteristic): The BLE characteristic to check. Refer to `Characteristic <https://ianharvey.github.io/bluepy-doc/characteristic.html>`_ for more information. Returns: bool: True if the characteristic can be notified, False otherwise. """ try: if characteristic is not None: with lock(self): return "NOTIFY" in characteristic.propertiesToString() return False except BTLEException as e: self._unexpected_disconnect()
def set_notification_status(self, characteristic, status): """Ask the node to set the notification status of the given characteristic. Args: characteristic (Characteristic): The BLE characteristic to check. Refer to `Characteristic <https://ianharvey.github.io/bluepy-doc/characteristic.html>`_ for more information. status (bool): True if the notifications have to be turned on, False otherwise. """ try: with lock(self): self.writeCharacteristic( characteristic.getHandle() + 1, self._NOTIFICATION_ON if status else self._NOTIFICATION_OFF, True) except BTLEException as e: self._unexpected_disconnect()
def __init__(self, show_warnings=False, *args, **kwargs): """Constructor. Args: show_warnings (bool, optional): If True shows warnings, if any, when discovering devices not respecting the BlueSTSDK's advertising data format, nothing otherwise. """ try: super(_StoppableScanner, self).__init__(*args, **kwargs) self._stop_called = threading.Event() self._process_done = threading.Event() with lock(self): self._scanner = Scanner().withDelegate(_ScannerDelegate(show_warnings)) except BTLEException as e: # Save details of the exception raised but don't re-raise, just # complete the function. import sys self._exc = sys.exc_info()
def update(self, timestamp, data, offset, notify_update=False): """Update feature's internal data through an atomic operation, and notify the registered listeners about the update, if needed. This method has to be called by a node whenever it receives new data from the feature, not by the application. When overriding this method, please remember to update the timestamp and the last-updated value, and to acquire the write-lock. Args: timestamp (int): Package's timestamp. data (list): Feature's data. offset (int): Offset position to start reading data. notify_update (bool, optional): If True all the registered listeners are notified about the new data. Returns: int: The number of bytes read. Raises: :exc:`blue_st_sdk.utils.blue_st_exceptions.BlueSTInvalidDataException` if the data array has not enough data to read. """ # Update the feature's internal data sample = None with lock(self): try: extracted_data = self.extract_data(timestamp, data, offset) except BlueSTInvalidDataException as e: raise e sample = self._last_sample = extracted_data.get_sample() read_bytes = extracted_data.get_read_bytes() self._last_update = datetime.now() if notify_update: # Notify all the registered listeners about the new data. self._notify_update(sample) # Log the new data through all the registered loggers. self._log_update(data[offset:offset + read_bytes], sample) return read_bytes
def __str__(self): """Get a string representing the last sample. Return: str: A string representing the last sample. """ with lock(self): sample = self._last_sample if sample is None: return self._name + ': Unknown' if not sample._data: return self._name + ': Unknown' if len(sample._data) == 1: result = '%s(%d): %s %s' \ % (self._name, sample._timestamp, str(sample._data[0]), self._description[0]._unit) return result # Check on timestamp (ADPCM Audio and ADPCM Sync samples don't have # the timestamp field in order to save bandwidth.) if sample._timestamp is not None: result = '%s(%d): ( ' % (self._name, sample._timestamp) i = 0 while i < len(sample._data): result += '%s: %s %s%s' \ % (self._description[i]._name, str(sample._data[i]), self._description[i]._unit, ' ' if i < len(sample._data) - 1 else ' )') i += 1 else: # Only for Audio Features. result = str(self._name) + " - " for i in range(0, len(sample._data) - 1): result += str(sample._data[i]) + ", " result += str(sample._data[len(sample._data) - 1]) return result
def disconnect(self): """Close the connection to the node. Returns: bool: True if the disconnection to the node has been successful, False otherwise. """ try: if not self.is_connected(): return False # Disconnecting. self._update_node_status(NodeStatus.DISCONNECTING) with lock(self): super(Node, self).disconnect() self._update_node_status(NodeStatus.IDLE) return self._status == NodeStatus.IDLE except BTLEException as e: self._unexpected_disconnect()
def run(self): """Run the thread.""" self._stop_called.clear() self._process_done.clear() try: with lock(self): self._scanner.clear() self._exc = None self._scanner.start(passive=False) while True: #print('.') self._scanner.process(_ScannerDelegate._SCANNING_TIME_PROCESS_s) if self._stop_called.isSet(): self._process_done.set() break except BTLEException as e: # Save details of the exception raised but don't re-raise, just # complete the function. import sys self._exc = sys.exc_info()
def __str__(self): """Get a string representing the last sample. Return: str: A string representing the last sample. """ with lock(self): sample = self._last_sample if sample is None: return self._name + ': Unknown' if not sample._data: return self._name + ': Unknown' if len(sample._data) == 1: result = '%s(%d): Scene is \"%s\"' \ % (self._name, sample._timestamp, str(self.get_scene(sample)) ) return result
def __str__(self): """Get a string representing the last sample. Return: str: A string representing the last sample. """ with lock(self): sample = self._last_sample if sample is None: return self._name + ': Unknown' if not sample._data: return self._name + ': Unknown' if len(sample._data) == 1: switch = 'ON' if self.get_switch_status(sample) else 'OFF' result = '%s(%d): %s' \ % (self._name, sample._timestamp, switch) return result
def wait_for_notifications(self, timeout_s): """Block until a notification is received from the peripheral, or until the given timeout has elapsed. If a notification is received, the :meth:`blue_st_sdk.feature.FeatureListener.on_update` method of any added listener is called. Args: timeout_s (float): Time in seconds to wait before returning. Returns: bool: True if a notification is received before the timeout elapses, False otherwise. """ try: if self.is_connected(): with lock(self): return self.waitForNotifications(timeout_s) return False except BTLEException as e: self._unexpected_disconnect()
def _build_features(self, characteristic): """Build the exported features of a BLE characteristic. After building the features, add them to the dictionary of the features to be updated. Args: characteristic (Characteristic): The BLE characteristic. Refer to `Characteristic <https://ianharvey.github.io/bluepy-doc/characteristic.html>`_ for more information. """ try: # Extracting the feature mask from the characteristic's UUID. feature_mask = FeatureCharacteristic.extract_feature_mask( characteristic.uuid) # Looking for the exported features in reverse order to get them in # the correct order in case of characteristic that exports multiple # features. features = [] mask = 1 << 31 for i in range(0, 32): if (feature_mask & mask) != 0: if mask in self._mask_to_feature_dic: feature = self._mask_to_feature_dic[mask] if feature is not None: feature.set_enable(True) features.append(feature) mask = mask >> 1 # If the features are valid, add an entry for the corresponding # characteristic. if features: with lock(self): self._update_char_handle_to_features_dict[ characteristic.getHandle()] = features except BTLEException as e: self._node._unexpected_disconnect()
def __init__(self, scan_entry): """Constructor. Args: scan_entry (ScanEntry): BLE device. It contains device information and advertising data. Refer to `ScanEntry <https://ianharvey.github.io/bluepy-doc/scanentry.html>`_ for more information. Raises: :exc:`blue_st_sdk.utils.blue_st_exceptions.BlueSTInvalidAdvertisingDataException` is raised if the advertising data is not well formed. :exc:`blue_st_sdk.utils.blue_st_exceptions.BlueSTInvalidOperationException` is raised if the operation requested is not supported. """ # Creating an un-connected "Peripheral" object. # It is needed to call the "connect()" method on this object (passing a # device address) before it will be usable. try: with lock(self): Peripheral.__init__(self) except BTLEException as e: raise BlueSTInvalidOperationException( 'Bluetooth invalid operation.') self._friendly_name = None """Friendly name.""" self._last_rssi_update = None """Last update to the Received Signal Strength Indication.""" self._status = NodeStatus.INIT """Status.""" self._thread_pool = ThreadPoolExecutor(Node._NUMBER_OF_THREADS) """Pool of thread used to notify the listeners.""" self._listeners = [] """List of listeners to the node changes. It is a thread safe list, so a listener can subscribe itself through a callback.""" self._available_features = [] """List of all the available features as claimed by the advertising data. (No duplicates.)""" self._mask_to_feature_dic = {} """Mask to feature dictionary: there is an entry for each one-bit-high 32-bit mask.""" """UUID to list of external features dictionary: there is an entry for each list of exported external features. Note: A UUID may export more than one feature. Note: BlueSTSDK_Android: mExternalCharFeatures.""" self._external_uuid_to_features_dic = UUIDToFeatureMap() self._update_char_handle_to_features_dict = {} """Characteristic's handle to list of features dictionary: it tells which features to update when new data from a characteristic are received. Note: A UUID may export more than one feature. Note: The same feature may be added to different list of features in case more characteristics have the same corresponding bit set to high. Note: BlueSTSDK_Android: mCharFeatureMap.""" self._char_handle_to_characteristic_dict = {} """Characteristic's handle to characteristic dictionary.""" self._unwrap_timestamp = UnwrapTimestamp() """Unwrap timestamp reference.""" #self._characteristic_write_queue = Queue() """Queue of write jobs.""" # Debug console used to read/write debug messages from/to the Bluetooth # device. None if the device doesn't export the debug service. self._debug_console = None # Advertising data. try: self._device = scan_entry """BLE device. Python's "ScanEntry" object, equivalent to Android's "BluetoothDevice" object.""" with lock(self): self._advertising_data = BlueSTAdvertisingDataParser.parse( scan_entry.getScanData()) """Advertising data.""" self._rssi = scan_entry.rssi """Received Signal Strength Indication.""" except BlueSTInvalidAdvertisingDataException as e: raise e except BTLEException as e: raise BlueSTInvalidOperationException( 'Bluetooth invalid operation.') # Updating node. self._update_rssi(self._rssi) self._update_node_status(NodeStatus.IDLE) # Building available features. self._build_available_features()