예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
파일: smp.py 프로젝트: jiemde/blatann
    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)
예제 #4
0
    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)
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
    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)
예제 #9
0
    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
예제 #10
0
    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)
예제 #11
0
    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)
예제 #12
0
    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)
예제 #13
0
 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)
예제 #14
0
    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)
예제 #15
0
    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)
예제 #16
0
    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)
예제 #17
0
    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)
예제 #18
0
파일: smp.py 프로젝트: eriknyquist/blatann
    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)
예제 #19
0
    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)
예제 #20
0
    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)
예제 #21
0
    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