Example #1
0
    def pair(self, force_repairing=False) -> EventWaitable[Peer, PairingCompleteEventArgs]:
        """
        Starts the pairing process with the peer with the set security parameters.

        If the peer is already bonded, initiates the encryption process unless force_repairing is set to True

        If the peer is a central and we are a local device, sends the peripheral security request to the central
        so they can start the pairing/encryption process

        :return: A waitable that will trigger when pairing is complete
        """
        if self.pairing_in_process:
            logger.warning("Attempted to pair while pairing/encryption already in progress. Returning waitable for when it finishes")
            return EventWaitable(self.on_pairing_complete)

        # 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")
                # If bonding data was created using LESC use our own LTKs, otherwise use the peer's
                if bond_entry.bonding_data.own_ltk.enc_info.lesc:
                    ltk = bond_entry.bonding_data.own_ltk
                else:
                    ltk = bond_entry.bonding_data.peer_ltk

                self.ble_device.ble_driver.ble_gap_encrypt(self.peer.conn_handle, ltk.master_id, ltk.enc_info)
                self._initiated_encryption = True
                return EventWaitable(self.on_pairing_complete)

        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)
Example #2
0
    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)
Example #3
0
    def write(self, handle, data):
        """
        Writes data to the attribute at the handle provided. Can only write to a single attribute at a time.
        If a write is in progress, raises an InvalidStateException

        :param handle: The attribute handle to write
        :param data: The data to write
        :return: A Waitable that will fire when the write finishes. see on_write_complete for the values returned from the waitable
        :rtype: EventWaitable
        """
        if self._busy:
            raise InvalidStateException("Gattc Writer is busy")
        if len(data) == 0:
            raise ValueError("Data must be at least one byte")

        self._offset = 0
        self._handle = handle
        self._data = data
        logger.debug("Starting write to handle {}, len: {}".format(self._handle, len(self._data)))
        try:
            self._busy = True
            self._write_next_chunk()
        except Exception:
            self._busy = False
            raise
        return EventWaitable(self.on_write_complete)
Example #4
0
 def read_time(
     self
 ) -> EventWaitable[CurrentTimeClient,
                    DecodedReadCompleteEventArgs[CurrentTime]]:
     """
     Reads the time from the server
     """
     self._current_time_char.read().then(self._current_time_dispatcher)
     return EventWaitable(self._on_current_time_updated_event)
    def start(self, services=None):
        self._state.reset()

        self.ble_device.ble_driver.ble_gattc_prim_srvc_disc(self.peer.conn_handle, None,
                                                            self._state.current_handle)

        self.peer.driver_event_subscribe(self._on_primary_service_discovery, nrf_events.GattcEvtPrimaryServiceDiscoveryResponse)
        self.peer.driver_event_subscribe(self._on_service_uuid_read, nrf_events.GattcEvtReadResponse)
        return EventWaitable(self.on_complete)
    def start(self, services):
        self._state.reset()
        self._state.services = services

        self.peer.driver_event_subscribe(self._on_characteristic_discovery, nrf_events.GattcEvtCharacteristicDiscoveryResponse)
        self.peer.driver_event_subscribe(self._on_char_uuid_read, nrf_events.GattcEvtReadResponse)

        self._discover_characteristics()
        return EventWaitable(self.on_complete)
Example #7
0
    def discover_services(
            self) -> EventWaitable[Peer, DatabaseDiscoveryCompleteEventArgs]:
        """
        Starts the database discovery process of the peer. This will discover all services, characteristics, and
        descriptors on the peer's database.

        :return: a Waitable that will trigger when service discovery is complete
        """
        self._discoverer.start()
        return EventWaitable(self._discoverer.on_discovery_complete)
Example #8
0
 def start(self, services):
     self._state.reset()
     self._state.services = services
     self.peer.driver_event_subscribe(
         self._on_descriptor_discovery,
         nrf_events.GattcEvtDescriptorDiscoveryResponse)
     on_complete_waitable = EventWaitable(self.on_complete)
     if not self._state.services:
         self._on_complete()
     else:
         self._discover_next_handle_range()
     return on_complete_waitable
 def start(self, services):
     self._state.reset()
     self._state.services = services
     # Compile the characteristics into a single list so its easier to iterate
     for s in services:
         self._state.characteristics.extend(s.chars)
     self.peer.driver_event_subscribe(self._on_descriptor_discovery, nrf_events.GattcEvtDescriptorDiscoveryResponse)
     on_complete_waitable = EventWaitable(self.on_complete)
     if not self._state.services:
         self._on_complete()
     else:
         self._discover_descriptors()
     return on_complete_waitable
Example #10
0
    def discover_services(
            self
    ) -> EventWaitable[Peripheral, DatabaseDiscoveryCompleteEventArgs]:
        """
        Starts the database discovery process of the peripheral. This will discover all services, characteristics, and
        descriptors on the remote database.
        Returns an EventWaitable that will fire when the service discovery completes.
        Waitable returns 2  parameters: (Peripheral this, DatabaseDiscoveryCompleteEventArgs event args)

        :return: a Waitable that will fire when service discovery is complete
        """
        self._discoverer.start()
        return EventWaitable(self._discoverer.on_discovery_complete)
Example #11
0
    def update_phy(self,
                   phy: Phy = None
                   ) -> EventWaitable[Peer, PhyUpdatedEventArgs]:
        """
        Performs the PHY update procedure, negotiating a new PHY (1Mbps, 2Mbps, or coded PHY)
        to use for the connection. Performing this procedure does not guarantee that the PHY
        will change based on what the peer supports.

        :param phy: Optional PHY to use. If None, uses the :attr:`preferred_phy` attribute.
                    If not None, the preferred PHY is updated to this value.
        :return: An event waitable that triggers when the phy process completes
        """
        if phy is None:
            phy = self._preferred_phy
        else:
            self._preferred_phy = phy
        self._ble_device.ble_driver.ble_gap_phy_update(self.conn_handle, phy,
                                                       phy)
        return EventWaitable(self._on_phy_updated)
Example #12
0
    def read(self, handle):
        """
        Reads the attribute value from the handle provided. Can only read from a single attribute at a time. If a
        read is in progress, raises an InvalidStateException

        :param handle: the attribute handle to read
        :return: A waitable that will fire when the read finishes.
                 See on_read_complete for the values returned from the waitable
        :rtype: EventWaitable
        """
        if self._busy:
            raise InvalidStateException("Gattc Reader is busy")
        self._handle = handle
        self._offset = 0
        self._data = bytearray()
        logger.debug("Starting read from handle {}".format(handle))
        self._read_next_chunk()
        self._busy = True
        return EventWaitable(self.on_read_complete)
Example #13
0
    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)
Example #14
0
    def exchange_mtu(self, mtu_size=None) -> EventWaitable[Peer, MtuSizeUpdatedEventArgs]:
        """
        Initiates the MTU Exchange sequence with the peer device.

        If the MTU size is not provided the preferred_mtu_size value will be used.
        If an MTU size is provided the preferred_mtu_size will be updated to this

        :param mtu_size: Optional MTU size to use. If provided, it will also updated the preferred MTU size
        :return: A waitable that will fire when the MTU exchange completes
        """
        # If the MTU size has already been negotiated we need to use the same value
        # as the previous exchange (Vol 3, Part F 3.4.2.2)
        if self._negotiated_mtu_size is None:
            if mtu_size is not None:
                self._validate_mtu_size(mtu_size)
                self._negotiated_mtu_size = mtu_size
            else:
                self._negotiated_mtu_size = self.preferred_mtu_size

        self._ble_device.ble_driver.ble_gattc_exchange_mtu_req(self.conn_handle, self._negotiated_mtu_size)
        return EventWaitable(self._on_mtu_exchange_complete)
Example #15
0
    def update_data_length(
        self,
        data_length: int = None
    ) -> EventWaitable[Peripheral, DataLengthUpdatedEventArgs]:
        """
        Starts the process which updates the link layer data length to the optimal value given the MTU.
        For best results call this method after the MTU is set to the desired size.

        :param data_length: Optional value to override the data length to.
                            If not provided, uses the optimal value based on the current MTU
        :return: A waitable that will trigger when the process finishes
        """
        if data_length is not None:
            if data_length > DLE_MAX or data_length < DLE_MIN:
                raise ValueError(
                    f"Data length must be between {DLE_MIN} and {DLE_MAX} (inclusive)"
                )
        else:
            data_length = self.mtu_size + DLE_OVERHEAD

        params = BLEGapDataLengthParams(data_length, data_length)
        self._ble_device.ble_driver.ble_gap_data_length_update(
            self.conn_handle, params)
        return EventWaitable(self._on_data_length_updated)