def set_value(self, value, notify_client=False): """ Sets the value of the characteristic. :param value: The value to set to. Must be an iterable type such as a str, bytearray, or list of uint8 values. Length must be less than the characteristic's max length :param notify_client: Flag whether or not to notify the client. If indications and notifications are not set up for the characteristic, will raise an InvalidOperationException :raises: InvalidOperationException if value length is too long, or notify client set and characteristic is not notifiable """ if isinstance(value, BleDataStream): value = value.value if len(value) > self.max_length: raise InvalidOperationException("Attempted to set value of {} with length greater than max " "(got {}, max {})".format(self.uuid, len(value), self.max_length)) if notify_client and not self.notifiable: raise InvalidOperationException("Cannot notify client. " "{} not set up for notifications or indications".format(self.uuid)) v = nrf_types.BLEGattsValue(value) self.ble_device.ble_driver.ble_gatts_value_set(self.peer.conn_handle, self.value_handle, v) self._value = value if notify_client and self.client_subscribed and not self._read_in_process: self.notify(None)
def notify( self, data ) -> EventWaitable[GattsCharacteristic, NotificationCompleteEventArgs]: """ Notifies the client with the data provided without setting the data into the characteristic value. If data is not provided (None), will notify with the currently-set value of the characteristic :param data: The data to notify the client with :return: An EventWaitable that will fire when the notification is successfully sent to the client. The waitable also contains the ID of the sent notification which is used in the on_notify_complete event :rtype: NotificationCompleteEventWaitable """ if isinstance(data, BleDataStream): value = data.value if isinstance(data, str): value = data.encode(self.string_encoding) if not self.notifiable: raise InvalidOperationException( "Cannot notify client. " "{} not set up for notifications or indications".format( self.uuid)) if not self.client_subscribed: raise InvalidStateException( "Client is not subscribed, cannot notify client") notification_id = self._notification_manager.notify( self, self.value_handle, self._on_notify_complete, data) return IdBasedEventWaitable(self._on_notify_complete, notification_id)
def pair(self, force_repairing=False): """ Starts the encrypting process with the peer. If the peer has already been bonded to, Starts the pairing process with the peer given the set security parameters and returns a Waitable which will fire when the pairing process completes, whether successful or not. Waitable returns two parameters: (Peer, PairingCompleteEventArgs) :return: A waitiable that will fire when pairing is complete :rtype: blatann.waitables.EventWaitable """ if self._pairing_in_process or self._initiated_encryption: raise InvalidStateException("Security manager busy") if self.security_params.reject_pairing_requests: raise InvalidOperationException( "Cannot initiate pairing while rejecting pairing requests") # if in the client role and don't want to force a re-pair, check for bonding data first if self.peer.is_peripheral and not force_repairing: bond_entry = self._find_db_entry(self.peer.peer_address) if bond_entry: logger.info("Re-establishing encryption with peer using LTKs") self.ble_device.ble_driver.ble_gap_encrypt( self.peer.conn_handle, bond_entry.bonding_data.own_ltk.master_id, bond_entry.bonding_data.own_ltk.enc_info) self._initiated_encryption = True sec_params = self._get_security_params() self.ble_device.ble_driver.ble_gap_authenticate( self.peer.conn_handle, sec_params) self._pairing_in_process = True return EventWaitable(self.on_pairing_complete)
def notify( self, data ) -> IdBasedEventWaitable[GattsCharacteristic, NotificationCompleteEventArgs]: """ Notifies the client with the data provided without setting the data into the characteristic value. If data is not provided (None), will notify with the currently-set value of the characteristic :param data: Optional data to notify the client with. If supplied, must be an iterable type such as a str, bytes, or list of uint8 values, or a BleDataStream object. Length must be less than or equal to the characteristic's max length. If a string is given, it will be encoded using the string_encoding property of the characteristic. :raises: InvalidStateException if the client is not subscribed to the characteristic :raises: InvalidOperationException if the characteristic is not configured for notifications/indications :return: An EventWaitable that will trigger when the notification is successfully sent to the client. The waitable also contains the ID of the sent notification which is used in the on_notify_complete event """ if isinstance(data, BleDataStream): value = data.value if isinstance(data, str): value = data.encode(self.string_encoding) if not self.notifiable: raise InvalidOperationException( "Cannot notify client. " "{} not set up for notifications or indications".format( self.uuid)) if not self.client_subscribed: raise InvalidStateException( "Client is not subscribed, cannot notify client") notification_id = self._notification_manager.notify( self, self._value_attr.handle, self._on_notify_complete, data) return IdBasedEventWaitable(self._on_notify_complete, notification_id)
def set_value( self, value, notify_client=False ) -> Optional[IdBasedEventWaitable[GattsCharacteristic, NotificationCompleteEventArgs]]: """ Sets the value of the characteristic. :param value: The value to set to. Must be an iterable type such as a str, bytes, or list of uint8 values, or a BleDataStream object. Length must be less than or equal to the characteristic's max length. If a string is given, it will be encoded using the string_encoding property of the characteristic. :param notify_client: Flag whether or not to notify the client. If indications and notifications are not set up for the characteristic, will raise an InvalidOperationException :raises: InvalidOperationException if value length is too long, or notify client set and characteristic is not notifiable :raises: InvalidStateException if the client is not currently subscribed to the characteristic :return: If notify_client is true, this method will return the waitable for when the notification is sent to the client """ if notify_client and not self.notifiable: raise InvalidOperationException( "Cannot notify client. " "{} not set up for notifications or indications".format( self.uuid)) self._value_attr.set_value(value) if notify_client and self.client_subscribed and not self._value_attr.read_in_process: return self.notify(None)
def subscribe( self, on_notification_handler: Callable[ [GattcCharacteristic, NotificationReceivedEventArgs], None], prefer_indications=False ) -> EventWaitable[GattcCharacteristic, SubscriptionWriteCompleteEventArgs]: """ Subscribes to the characteristic's indications or notifications, depending on what's available and the prefer_indications setting. Returns a Waitable that triggers when the subscription on the peripheral finishes. :param on_notification_handler: The handler to be called when an indication or notification is received from the peripheral. Must take two parameters: (GattcCharacteristic this, NotificationReceivedEventArgs event args) :param prefer_indications: If the peripheral supports both indications and notifications, will subscribe to indications instead of notifications :return: A Waitable that will trigger when the subscription finishes :raises: InvalidOperationException if the characteristic cannot be subscribed to (characteristic does not support indications or notifications) """ if not self.subscribable: raise InvalidOperationException( "Cannot subscribe to Characteristic {}".format(self.uuid)) if prefer_indications and self._properties.indicate or not self._properties.notify: value = gatt.SubscriptionState.INDICATION else: value = gatt.SubscriptionState.NOTIFY self._on_notification_event.register(on_notification_handler) waitable = self._cccd_attr.write( gatt.SubscriptionState.to_buffer(value)) return IdBasedEventWaitable(self._on_cccd_write_complete_event, waitable.id)
def subscribe(self, on_notification_handler, prefer_indications=False): """ Subscribes to the characteristic's indications or notifications, depending on what's available and the prefer_indications setting. Returns a Waitable that executes when the subscription on the peripheral finishes. The Waitable returns two parameters: (GattcCharacteristic this, SubscriptionWriteCompleteEventArgs event args) :param on_notification_handler: The handler to be called when an indication or notification is received from the peripheral. Must take three parameters: (GattcCharacteristic this, gatt.GattNotificationType, bytearray data) :param prefer_indications: If the peripheral supports both indications and notifications, will subscribe to indications instead of notifications :return: A Waitable that will fire when the subscription finishes :rtype: blatann.waitables.EventWaitable :raises: InvalidOperationException if the characteristic cannot be subscribed to (characteristic does not support indications or notifications) """ if not self.subscribable: raise InvalidOperationException( "Cannot subscribe to Characteristic {}".format(self.uuid)) if prefer_indications and self._properties.indicate or not self._properties.notify: value = gatt.SubscriptionState.INDICATION else: value = gatt.SubscriptionState.NOTIFY self._on_notification_event.register(on_notification_handler) write_id = self._manager.write(self.cccd_handle, gatt.SubscriptionState.to_buffer(value)) return IdBasedEventWaitable(self._on_cccd_write_complete_event, write_id)
def set_value( self, value, notify_client=False ) -> Optional[EventWaitable[GattsCharacteristic, NotificationCompleteEventArgs]]: """ Sets the value of the characteristic. :param value: The value to set to. Must be an iterable type such as a str, bytearray, or list of uint8 values. Length must be less than the characteristic's max length. If a str is given, it will be encoded using the string_encoding property. :param notify_client: Flag whether or not to notify the client. If indications and notifications are not set up for the characteristic, will raise an InvalidOperationException :raises: InvalidOperationException if value length is too long, or notify client set and characteristic is not notifiable :return: If notify_client is true, this method will return the waitable for when the notification is sent to the client """ if isinstance(value, BleDataStream): value = value.value if isinstance(value, str): value = value.encode(self.string_encoding) if len(value) > self.max_length: raise InvalidOperationException( "Attempted to set value of {} with length greater than max " "(got {}, max {})".format(self.uuid, len(value), self.max_length)) if notify_client and not self.notifiable: raise InvalidOperationException( "Cannot notify client. " "{} not set up for notifications or indications".format( self.uuid)) v = nrf_types.BLEGattsValue(value) self.ble_device.ble_driver.ble_gatts_value_set(self.peer.conn_handle, self.value_handle, v) self._value = value if notify_client and self.client_subscribed and not self._read_in_process: return self.notify(None)
def write(self, handle, value, callback): data_len = len(value) if data_len == 0: raise ValueError("Data must be at least one byte") if data_len > self.peer.mtu_size - self._WRITE_OVERHEAD: raise InvalidOperationException( f"Writing data without response must fit within a " f"single MTU minus the write overhead ({self._WRITE_OVERHEAD} bytes). " f"MTU: {self.peer.mtu_size}bytes, data: {data_len}bytes") write_task = _WriteTask(handle, value, callback, False) self._add_task(write_task) return write_task.id
def set_local_time_info(self, timezone_hrs=0.0, dst_offset=DaylightSavingsTimeOffset.standard_time): """ Sets the local time info characteristic data. Only valid if has_local_time_info is True :param timezone_hrs: The timezone to report, in hours :param dst_offset: The daylight savings time offset :type dst_offset: DaylightSavingsTimeOffset :raises: InvalidOperationException if the service was not configured with the local time info """ if not self.has_local_time_info: raise InvalidOperationException("Current Time service was not initialized with local time info") lt = LocalTimeInfo(timezone_hrs, dst_offset) self._local_time_char.set_value(lt.encode(), True)
def read( self) -> EventWaitable[GattcCharacteristic, ReadCompleteEventArgs]: """ Initiates a read of the characteristic and returns a Waitable that triggers when the read finishes with the data read. :return: A waitable that will trigger when the read finishes :raises: InvalidOperationException if characteristic not readable """ if not self.readable: raise InvalidOperationException( "Characteristic {} is not readable".format(self.uuid)) waitable = self._value_attr.read() return IdBasedEventWaitable(self._on_read_complete_event, waitable.id)
def read(self) -> EventWaitable[GattcCharacteristic, ReadCompleteEventArgs]: """ Initiates a read of the characteristic and returns a Waitable that executes when the read finishes with the data read. The Waitable returns two parameters: (GattcCharacteristic this, ReadCompleteEventArgs event args) :return: A waitable that will fire when the read finishes :raises: InvalidOperationException if characteristic not readable """ if not self.readable: raise InvalidOperationException("Characteristic {} is not readable".format(self.uuid)) read_id = self._manager.read(self.value_handle) return IdBasedEventWaitable(self._on_read_complete_event, read_id)
def delete_bonding_data(self): """ Deletes the bonding data for the peer, if any. Cannot be called during pairing, will throw an InvalidOperationException """ if self.pairing_in_process: raise InvalidOperationException("Cannot clear bonding data while pairing is in progress") if self.bond_db_entry: db_entry = self.bond_db_entry self.bond_db_entry = None self.ble_device.bond_db.delete(db_entry) self._is_previously_bonded_device = False # TODO: This doesn't belong here.. self.ble_device.bond_db_loader.save(self.ble_device.bond_db)
def set_reference_info(self, time_source=TimeSource.unknown, accuracy=TimeAccuracy.unknown, hours_since_update=None): """ Sets the time reference info characteristic data. Only valid if has_reference_time_info is True :param time_source: The time source to use :type time_source: TimeSource :param accuracy: The accuracy to report :type accuracy: TimeAccuracy :param hours_since_update: The number of hours since time reference has been updated :raises: InvalidOperationException if the service was not configured with the reference info """ if not self.has_reference_time_info: raise InvalidOperationException("Current Time service was not initialized with reference info") ri = ReferenceTimeInfo(time_source, accuracy, hours_since_update) self._ref_time_char.set_value(ri.encode(), False)
def unsubscribe(self) -> EventWaitable[GattcCharacteristic, SubscriptionWriteCompleteEventArgs]: """ Unsubscribes from indications and notifications from the characteristic and clears out all handlers for the characteristic's on_notification event handler. Returns a Waitable that executes when the unsubscription finishes. The Waitable returns two parameters: (GattcCharacteristic this, SubscriptionWriteCompleteEventArgs event args) :return: A Waitable that will fire when the unsubscription finishes """ if not self.subscribable: raise InvalidOperationException("Cannot subscribe to Characteristic {}".format(self.uuid)) value = gatt.SubscriptionState.NOT_SUBSCRIBED write_id = self._manager.write(self.cccd_handle, gatt.SubscriptionState.to_buffer(value)) self._on_notification_event.clear_handlers() return IdBasedEventWaitable(self._on_cccd_write_complete_event, write_id)
def write(self, data): """ Initiates a write of the data provided to the characteristic and returns a Waitable that executes when the write completes. The Waitable returns two parameters: (GattcCharacteristic this, WriteCompleteEventArgs event args) :param data: The data to write. Can be a string, bytearray, or anything that can be converted to a bytearray :return: A waitable that returns when the write finishes :rtype: blatann.waitables.EventWaitable :raises: InvalidOperationException if characteristic is not writable """ if not self.writable: raise InvalidOperationException( "Characteristic {} is not writable".format(self.uuid)) write_id = self._manager.write(self.value_handle, bytearray(data)) return IdBasedEventWaitable(self._on_write_complete_event, write_id)
def write( self, data ) -> EventWaitable[GattcCharacteristic, WriteCompleteEventArgs]: """ Performs a write request of the data provided to the characteristic and returns a Waitable that triggers when the write completes and the confirmation response is received from the other device. :param data: The data to write. Can be a string, bytes, or anything that can be converted to bytes :type data: str or bytes or bytearray :return: A waitable that returns when the write finishes :raises: InvalidOperationException if characteristic is not writable """ if not self.writable: raise InvalidOperationException( "Characteristic {} is not writable".format(self.uuid)) if isinstance(data, str): data = data.encode(self.string_encoding) waitable = self._value_attr.write(bytes(data), True) return IdBasedEventWaitable(self._on_write_complete_event, waitable.id)
def pair(self): """ Starts the pairing process with the peer given the set security parameters and returns a Waitable which will fire when the pairing process completes, whether successful or not. Waitable returns two parameters: (Peer, PairingCompleteEventArgs) :return: A waitiable that will fire when pairing is complete :rtype: blatann.waitables.EventWaitable """ if self._busy: raise InvalidStateException("Security manager busy") if self.security_params.reject_pairing_requests: raise InvalidOperationException( "Cannot initiate pairing while rejecting pairing requests") sec_params = self._get_security_params() self.ble_device.ble_driver.ble_gap_authenticate( self.peer.conn_handle, sec_params) self._busy = True return EventWaitable(self.on_pairing_complete)
def unsubscribe( self ) -> EventWaitable[GattcCharacteristic, SubscriptionWriteCompleteEventArgs]: """ Unsubscribes from indications and notifications from the characteristic and clears out all handlers for the characteristic's on_notification event handler. Returns a Waitable that triggers when the unsubscription finishes. :return: A Waitable that will trigger when the unsubscription operation finishes :raises: InvalidOperationException if characteristic cannot be subscribed to (characteristic does not support indications or notifications) """ if not self.subscribable: raise InvalidOperationException( "Cannot subscribe to Characteristic {}".format(self.uuid)) value = gatt.SubscriptionState.NOT_SUBSCRIBED waitable = self._cccd_attr.write( gatt.SubscriptionState.to_buffer(value)) self._on_notification_event.clear_handlers() return IdBasedEventWaitable(self._on_cccd_write_complete_event, waitable.id)
def write_without_response( self, data ) -> EventWaitable[GattcCharacteristic, WriteCompleteEventArgs]: """ Performs a write command, which does not require the peripheral to send a confirmation response packet. This is a faster but lossy operation in the case that the packet is dropped/never received by the peer. This returns a waitable that triggers when the write is transmitted to the peripheral device. .. note:: Data sent without responses must fit within a single MTU minus 3 bytes for the operation overhead. :param data: The data to write. Can be a string, bytes, or anything that can be converted to bytes :type data: str or bytes or bytearray :return: A waitable that returns when the write finishes :raises: InvalidOperationException if characteristic is not writable without responses """ if not self.writable_without_response: raise InvalidOperationException( "Characteristic {} does not accept " "writes without responses".format(self.uuid)) if isinstance(data, str): data = data.encode(self.string_encoding) waitable = self._value_attr.write(bytes(data), False) return IdBasedEventWaitable(self._on_write_complete_event, waitable.id)
def set_value(self, value): """ Sets the value of the attribute. :param value: The value to set to. Must be an iterable type such as a str, bytes, or list of uint8 values, or a BleDataStream object. Length must be less than the attribute's max length. If a str is given, it will be encoded using the string_encoding property. :raises: InvalidOperationException if value length is too long """ if isinstance(value, BleDataStream): value = value.value if isinstance(value, str): value = value.encode(self.string_encoding) if len(value) > self.max_length: raise InvalidOperationException( "Attempted to set value of {} with length greater than max " "(got {}, max {})".format(self.uuid, len(value), self.max_length)) v = nrf_types.BLEGattsValue(value) self._ble_device.ble_driver.ble_gatts_value_set( self._peer.conn_handle, self._handle, v) self._value = value