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)
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 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)
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)
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)
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
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)
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)
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)
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 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)
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)