class DFUAdapter(BLEDriverObserver, BLEAdapterObserver): BASE_UUID = BLEUUIDBase([ 0x8E, 0xC9, 0x00, 0x00, 0xF3, 0x15, 0x4F, 0x60, 0x9F, 0xB8, 0x83, 0x88, 0x30, 0xDA, 0xEA, 0x50 ]) # Buttonless characteristics BLE_DFU_BUTTONLESS_CHAR_UUID = BLEUUID(0x0003, BASE_UUID) BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID = BLEUUID(0x0004, BASE_UUID) SERVICE_CHANGED_UUID = BLEUUID(0x2A05) # Bootloader characteristics CP_UUID = BLEUUID(0x0001, BASE_UUID) DP_UUID = BLEUUID(0x0002, BASE_UUID) CONNECTION_ATTEMPTS = 3 ERROR_CODE_POS = 2 LOCAL_ATT_MTU = 247 def __init__(self, adapter, bonded=False, keyset=None): super(DFUAdapter, self).__init__() self.evt_sync = EvtSync([ 'connected', 'disconnected', 'sec_params', 'auth_status', 'conn_sec_update' ]) self.conn_handle = None self.adapter = adapter self.bonded = bonded self.keyset = keyset self.notifications_q = queue.Queue() self.indication_q = queue.Queue() self.att_mtu = ATT_MTU_DEFAULT self.packet_size = self.att_mtu - 3 self.adapter.observer_register(self) self.adapter.driver.observer_register(self) def open(self): self.adapter.driver.open() assert nrf_sd_ble_api_ver in [2, 5] if nrf_sd_ble_api_ver == 2: self.adapter.driver.ble_enable( BLEEnableParams( vs_uuid_count=10, service_changed=True, periph_conn_count=0, central_conn_count=1, central_sec_count=1, )) if nrf_sd_ble_api_ver == 5: self.adapter.driver.ble_cfg_set( BLEConfig.conn_gatt, BLEConfigConnGatt(att_mtu=DFUAdapter.LOCAL_ATT_MTU), ) self.adapter.driver.ble_enable() self.adapter.driver.ble_vs_uuid_add(DFUAdapter.BASE_UUID) def close(self): if self.conn_handle is not None: logger.info('BLE: Disconnecting from target') self.adapter.disconnect(self.conn_handle) self.evt_sync.wait('disconnected') self.conn_handle = None self.evt_sync = None self.adapter.observer_unregister(self) self.adapter.driver.observer_unregister(self) self.adapter.driver.close() def connect(self, target_device_name, target_device_addr): """ Connect to Bootloader or Application with Buttonless Service. Args: target_device_name (str): Device name to scan for. target_device_addr (str): Device addr to scan for. """ self.target_device_name = target_device_name self.target_device_addr = target_device_addr logger.info('BLE: Scanning for {}'.format(self.target_device_name)) self.adapter.driver.ble_gap_scan_start() self.verify_stable_connection() if self.conn_handle is None: raise NordicSemiException('Timeout. Target device not found.') logger.info('BLE: Service Discovery') self.adapter.service_discovery(conn_handle=self.conn_handle) # Check if connected peer has Buttonless service. if self.adapter.db_conns[self.conn_handle].get_cccd_handle( DFUAdapter.BLE_DFU_BUTTONLESS_CHAR_UUID): self.jump_from_buttonless_mode_to_bootloader( DFUAdapter.BLE_DFU_BUTTONLESS_CHAR_UUID) elif self.adapter.db_conns[self.conn_handle].get_cccd_handle( DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID): self.jump_from_buttonless_mode_to_bootloader( DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID) if self.bonded: # For combined Updates with bonds enabled, re-encryption is needed self.encrypt() if nrf_sd_ble_api_ver >= 3: if DFUAdapter.LOCAL_ATT_MTU > ATT_MTU_DEFAULT: logger.info('BLE: Enabling longer ATT MTUs') self.att_mtu = self.adapter.att_mtu_exchange( self.conn_handle, DFUAdapter.LOCAL_ATT_MTU) else: logger.info('BLE: Using default ATT MTU') logger.debug('BLE: Enabling Notifications') self.adapter.enable_notification(conn_handle=self.conn_handle, uuid=DFUAdapter.CP_UUID) return self.target_device_name, self.target_device_addr def jump_from_buttonless_mode_to_bootloader(self, buttonless_uuid): """ Function for going to bootloader mode from application with buttonless service. It supports both bonded and unbonded buttonless characteristics. Args: buttonless_uuid: UUID of discovered buttonless characteristic. """ if buttonless_uuid == DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID: logger.info("Bonded Buttonless characteristic discovered -> Bond") self.bond() else: logger.info( "Un-bonded Buttonless characteristic discovered -> Increment target device addr" ) self.target_device_addr = "{:X}".format( int(self.target_device_addr, 16) + 1) self.target_device_addr_type.addr[-1] += 1 # Enable indication for Buttonless DFU Service self.adapter.enable_indication(self.conn_handle, buttonless_uuid) # Enable indication for Service changed Service, if present. if self.adapter.db_conns[self.conn_handle].get_char_handle( DFUAdapter.SERVICE_CHANGED_UUID): self.adapter.enable_indication(self.conn_handle, DFUAdapter.SERVICE_CHANGED_UUID) # Enter DFU mode self.adapter.write_req(self.conn_handle, buttonless_uuid, [0x01]) response = self.indication_q.get( timeout=DfuTransportBle.DEFAULT_TIMEOUT) if response[DFUAdapter.ERROR_CODE_POS] != 0x01: raise Exception("Error - Unexpected response") # Wait for buttonless peer to disconnect self.evt_sync.wait('disconnected') # Reconnect self.target_device_name = None self.adapter.driver.ble_gap_scan_start() self.verify_stable_connection() if self.conn_handle is None: raise NordicSemiException('Timeout. Target device not found.') logger.info('BLE: Connected to target') logger.debug('BLE: Service Discovery') self.adapter.service_discovery(conn_handle=self.conn_handle) def verify_stable_connection(self): """ Verify connection event, and verify that unexpected disconnect events are not received. Returns: True if connected, else False. """ self.conn_handle = self.evt_sync.wait('connected') if self.conn_handle is not None: retries = DFUAdapter.CONNECTION_ATTEMPTS while retries: if self.evt_sync.wait('disconnected', timeout=1) is None: break logger.warning("Received unexpected disconnect event, " "trying to re-connect to: {}".format( self.target_device_addr)) time.sleep(1) self.adapter.connect(address=self.target_device_addr_type, conn_params=self.conn_params) self.conn_handle = self.evt_sync.wait('connected') retries -= 1 else: if self.evt_sync.wait('disconnected', timeout=1) is not None: raise Exception("Failure - Connection failed due to 0x3e") logger.info("Successfully Connected") return self.adapter.driver.ble_gap_scan_stop() raise Exception("Connection Failure - Device not found!") def setup_keyset(self): """ Setup keyset structure. """ self.keyset = driver.ble_gap_sec_keyset_t() self.id_key_own = driver.ble_gap_id_key_t() self.id_key_peer = driver.ble_gap_id_key_t() self.enc_key_own = driver.ble_gap_enc_key_t() self.enc_key_peer = driver.ble_gap_enc_key_t() self.sign_info_own = driver.ble_gap_sign_info_t() self.sign_info_peer = driver.ble_gap_sign_info_t() self.lesc_pk_own = driver.ble_gap_lesc_p256_pk_t() self.lesc_pk_peer = driver.ble_gap_lesc_p256_pk_t() self.keyset.keys_own.p_enc_key = self.enc_key_own self.keyset.keys_own.p_id_key = self.id_key_own self.keyset.keys_own.p_sign_key = self.sign_info_own self.keyset.keys_own.p_pk = self.lesc_pk_own self.keyset.keys_peer.p_enc_key = self.enc_key_peer self.keyset.keys_peer.p_id_key = self.id_key_peer self.keyset.keys_peer.p_sign_key = self.sign_info_peer self.keyset.keys_peer.p_pk = self.lesc_pk_peer def setup_sec_params(self): """ Setup Security parameters. """ self.kdist_own = BLEGapSecKDist(enc=True, id=True, sign=False, link=False) self.kdist_peer = BLEGapSecKDist(enc=True, id=True, sign=False, link=False) self.sec_params = BLEGapSecParams(bond=True, mitm=False, lesc=False, keypress=False, io_caps=BLEGapIOCaps.none, oob=False, min_key_size=7, max_key_size=16, kdist_own=self.kdist_own, kdist_peer=self.kdist_peer) def bond(self): """ Bond to Application with Buttonless Service. """ self.bonded = True self.setup_sec_params() self.setup_keyset() self.adapter.driver.ble_gap_authenticate(self.conn_handle, self.sec_params) self.evt_sync.wait(evt="sec_params") self.adapter.driver.ble_gap_sec_params_reply(self.conn_handle, BLEGapSecStatus.success, None, self.keyset, None) result = self.evt_sync.wait(evt="auth_status") if result != BLEGapSecStatus.success: raise NordicSemiException( "Auth Status returned error code: {}".format(result)) def encrypt(self): """ Re-encrypt to bootloader. """ logger.info("Re-encryption to bootloader") self.adapter.driver.ble_gap_encrypt( self.conn_handle, self.keyset.keys_peer.p_enc_key.master_id, self.keyset.keys_peer.p_enc_key.enc_info) self.evt_sync.wait('conn_sec_update') def write_control_point(self, data): self.adapter.write_req(self.conn_handle, DFUAdapter.CP_UUID, data) def write_data_point(self, data): self.adapter.write_cmd(self.conn_handle, DFUAdapter.DP_UUID, data) def on_gap_evt_sec_params_request(self, ble_driver, conn_handle, peer_params): logger.info("Got sec params req") self.evt_sync.notify(evt='sec_params', data=conn_handle) def on_gap_evt_auth_status(self, ble_driver, conn_handle, auth_status): logger.info("Got auth status:{}".format(auth_status)) self.evt_sync.notify(evt='auth_status', data=auth_status) def on_gap_evt_conn_sec_update(self, ble_driver, conn_handle): logger.info("Got Conn sec update") self.evt_sync.notify(evt='conn_sec_update', data=conn_handle) def on_gap_evt_connected(self, ble_driver, conn_handle, peer_addr, role, conn_params): self.evt_sync.notify(evt='connected', data=conn_handle) logger.info('BLE: Connected to {}'.format(peer_addr.addr)) def on_gap_evt_disconnected(self, ble_driver, conn_handle, reason): self.evt_sync.notify(evt='disconnected', data=conn_handle) self.conn_handle = None logger.info('BLE: Disconnected with reason: {}'.format(reason)) def on_gap_evt_adv_report(self, ble_driver, conn_handle, peer_addr, rssi, adv_type, adv_data): dev_name_list = [] if BLEAdvData.Types.complete_local_name in adv_data.records: dev_name_list = adv_data.records[ BLEAdvData.Types.complete_local_name] elif BLEAdvData.Types.short_local_name in adv_data.records: dev_name_list = adv_data.records[BLEAdvData.Types.short_local_name] dev_name = "".join(chr(e) for e in dev_name_list) address_string = "".join("{0:02X}".format(b) for b in peer_addr.addr) logger.info( 'Received advertisement report, address: 0x{}, device_name: {}'. format(address_string, dev_name)) if (dev_name == self.target_device_name) or (address_string == self.target_device_addr): self.conn_params = BLEGapConnParams(min_conn_interval_ms=7.5, max_conn_interval_ms=30, conn_sup_timeout_ms=4000, slave_latency=0) logger.info( 'BLE: Found target advertiser, address: 0x{}, name: {}'.format( address_string, dev_name)) logger.info('BLE: Connecting to 0x{}'.format(address_string)) # Connect must specify tag=1 to enable the settings # set with BLEConfigConnGatt (that implictly operates # on connections with tag 1) to allow for larger MTU. self.adapter.connect(address=peer_addr, conn_params=self.conn_params, tag=1) # store the address for subsequent connections self.target_device_addr = address_string self.target_device_addr_type = peer_addr def on_notification(self, ble_adapter, conn_handle, uuid, data): if self.conn_handle != conn_handle: return if DFUAdapter.CP_UUID.value != uuid.value: return self.notifications_q.put(data) def on_indication(self, ble_adapter, conn_handle, uuid, data): if self.conn_handle != conn_handle: return if DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID.value != uuid.value and \ DFUAdapter.BLE_DFU_BUTTONLESS_CHAR_UUID.value != uuid.value: return self.indication_q.put(data) def on_gattc_evt_exchange_mtu_rsp(self, ble_driver, conn_handle, *, status, att_mtu): logger.info('ATT MTU exchanged: conn_handle={} att_mtu={}'.format( conn_handle, att_mtu)) self.att_mtu = att_mtu self.packet_size = att_mtu - 3
class BleSerial(BLEDriverObserver, BLEAdapterObserver): BASE_UUID = BLEUUIDBase([0x6E, 0x40, 0x00, 0x00, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E]) RX_UUID = BLEUUID(0x0003, BASE_UUID) TX_UUID = BLEUUID(0x0002, BASE_UUID) LOCAL_ATT_MTU = 23 def __init__(self, com_port, periph_name, s_conn): driver = BLEDriver(serial_port=com_port, baud_rate=1000000) adapter = BLEAdapter(driver) self.evt_sync = EvtSync(['connected', 'disconnected']) self.target_device_name = periph_name self.target_device_addr = 0 self.conn_handle = None self.adapter = adapter self.notifications_q = queue.Queue() self.adapter.observer_register(self) self.adapter.driver.observer_register(self) self.s_conn = s_conn self.adapter.driver.open() if nrf_sd_ble_api_ver >= 3: logger.info("\nBLE: ble_enable with local ATT MTU: {}".format(DFUAdapter.LOCAL_ATT_MTU)) gatt_cfg = BLEConfigConnGatt() gatt_cfg.att_mtu = self.adapter.default_mtu gatt_cfg.tag = CFG_TAG self.adapter.driver.ble_cfg_set(BLEConfig.conn_gatt, gatt_cfg) self.adapter.driver.ble_enable() self.adapter.driver.ble_vs_uuid_add(BleSerial.BASE_UUID) self.connect() def connect(self): logger.debug('BLE: connect: target address: 0x{}'.format(self.target_device_addr)) logger.info('BLE: Scanning...') self.adapter.driver.ble_gap_scan_start() self.conn_handle = self.evt_sync.wait('connected') if self.conn_handle is None: raise NordicSemiException('Timeout. Target device not found.') logger.info('BLE: Connected to target') logger.debug('BLE: Service Discovery') if nrf_sd_ble_api_ver >= 3: if DFUAdapter.LOCAL_ATT_MTU > ATT_MTU_DEFAULT: logger.info('BLE: Enabling longer ATT MTUs') self.att_mtu = self.adapter.att_mtu_exchange(self.conn_handle, BleSerial.LOCAL_ATT_MTU) logger.info('BLE: ATT MTU: {}'.format(self.att_mtu)) else: logger.info('BLE: Using default ATT MTU') self.adapter.service_discovery(conn_handle=self.conn_handle) logger.debug('BLE: Enabling Notifications') self.adapter.enable_notification(conn_handle=self.conn_handle, uuid=BleSerial.RX_UUID) return self.target_device_name, self.target_device_addr def on_gap_evt_connected(self, ble_driver, conn_handle, peer_addr, role, conn_params): self.evt_sync.notify(evt = 'connected', data = conn_handle) def on_gap_evt_disconnected(self, ble_driver, conn_handle, reason): self.evt_sync.notify(evt = 'disconnected', data = conn_handle) logger.info('BLE: Disconnected...') self.connect() def on_gap_evt_adv_report(self, ble_driver, conn_handle, peer_addr, rssi, adv_type, adv_data): dev_name_list = [] if BLEAdvData.Types.complete_local_name in adv_data.records: dev_name_list = adv_data.records[BLEAdvData.Types.complete_local_name] elif BLEAdvData.Types.short_local_name in adv_data.records: dev_name_list = adv_data.records[BLEAdvData.Types.short_local_name] dev_name = "".join(chr(e) for e in dev_name_list) address_string = "".join("{0:02X}".format(b) for b in peer_addr.addr) logger.debug('Received advertisement report, address: 0x{}, device_name: {}'.format(address_string, dev_name)) if dev_name == self.target_device_name: conn_params = BLEGapConnParams(min_conn_interval_ms = 15, max_conn_interval_ms = 30, conn_sup_timeout_ms = 4000, slave_latency = 0) logger.info('BLE: Found target advertiser, address: 0x{}, name: {}'.format(address_string, dev_name)) logger.info('BLE: Connecting to 0x{}'.format(address_string)) self.adapter.connect(address = peer_addr, conn_params = conn_params) # store the name and address for subsequent connections def on_notification(self, ble_adapter, conn_handle, uuid, data): if self.conn_handle != conn_handle: return if BleSerial.RX_UUID.value != uuid.value: return logger.debug("Received notification {}".format(len(data))) # send to the socket buf = [chr(x) for x in data] buf = ''.join(buf) buf = bytes(buf, 'utf-8') self.s_conn.sendall(buf) def write_data(self, data): self.adapter.write_req(self.conn_handle, BleSerial.TX_UUID, data)
class DFUAdapter(BLEDriverObserver, BLEAdapterObserver): BASE_UUID = BLEUUIDBase([0x8E, 0xC9, 0x00, 0x00, 0xF3, 0x15, 0x4F, 0x60, 0x9F, 0xB8, 0x83, 0x88, 0x30, 0xDA, 0xEA, 0x50]) # Buttonless characteristics BLE_DFU_BUTTONLESS_CHAR_UUID = BLEUUID(0x0003, BASE_UUID) BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID = BLEUUID(0x0004, BASE_UUID) SERVICE_CHANGED_UUID = BLEUUID(0x2A05) # Bootloader characteristics CP_UUID = BLEUUID(0x0001, BASE_UUID) DP_UUID = BLEUUID(0x0002, BASE_UUID) CONNECTION_ATTEMPTS = 3 ERROR_CODE_POS = 2 LOCAL_ATT_MTU = 247 def __init__(self, adapter, bonded=False, keyset=None): super(DFUAdapter, self).__init__() self.evt_sync = EvtSync(['connected', 'disconnected', 'sec_params', 'auth_status', 'conn_sec_update']) self.conn_handle = None self.adapter = adapter self.bonded = bonded self.keyset = keyset self.notifications_q = Queue.Queue() self.indication_q = Queue.Queue() self.att_mtu = ATT_MTU_DEFAULT self.packet_size = self.att_mtu - 3 self.adapter.observer_register(self) self.adapter.driver.observer_register(self) def open(self): self.adapter.driver.open() ble_enable_params = BLEEnableParams(vs_uuid_count = 10, service_changed = True, periph_conn_count = 0, central_conn_count = 1, central_sec_count = 1) if nrf_sd_ble_api_ver >= 3: logger.info("\nBLE: ble_enable with local ATT MTU: {}".format(DFUAdapter.LOCAL_ATT_MTU)) ble_enable_params.att_mtu = DFUAdapter.LOCAL_ATT_MTU self.adapter.driver.ble_enable(ble_enable_params) self.adapter.driver.ble_vs_uuid_add(DFUAdapter.BASE_UUID) def close(self): if self.conn_handle is not None: logger.info('BLE: Disconnecting from target') self.adapter.disconnect(self.conn_handle) self.evt_sync.wait('disconnected') self.conn_handle = None self.evt_sync = None self.adapter.observer_unregister(self) self.adapter.driver.observer_unregister(self) self.adapter.driver.close() def connect(self, target_device_name, target_device_addr): """ Connect to Bootloader or Application with Buttonless Service. Args: target_device_name (str): Device name to scan for. target_device_addr (str): Device addr to scan for. """ self.target_device_name = target_device_name self.target_device_addr = target_device_addr logger.info('BLE: Scanning for {}'.format(self.target_device_name)) self.adapter.driver.ble_gap_scan_start() self.verify_stable_connection() if self.conn_handle is None: raise NordicSemiException('Timeout. Target device not found.') logger.info('BLE: Service Discovery') self.adapter.service_discovery(conn_handle=self.conn_handle) # Check if connected peer has Buttonless service. if self.adapter.db_conns[self.conn_handle].get_cccd_handle(DFUAdapter.BLE_DFU_BUTTONLESS_CHAR_UUID): self.jump_from_buttonless_mode_to_bootloader(DFUAdapter.BLE_DFU_BUTTONLESS_CHAR_UUID) elif self.adapter.db_conns[self.conn_handle].get_cccd_handle(DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID): self.jump_from_buttonless_mode_to_bootloader(DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID) if self.bonded: # For combined Updates with bonds enabled, re-encryption is needed self.encrypt() if nrf_sd_ble_api_ver >= 3: if DFUAdapter.LOCAL_ATT_MTU > ATT_MTU_DEFAULT: logger.info('BLE: Enabling longer ATT MTUs') self.att_mtu = self.adapter.att_mtu_exchange(self.conn_handle) else: logger.info('BLE: Using default ATT MTU') logger.debug('BLE: Enabling Notifications') self.adapter.enable_notification(conn_handle=self.conn_handle, uuid=DFUAdapter.CP_UUID) return self.target_device_name, self.target_device_addr def jump_from_buttonless_mode_to_bootloader(self, buttonless_uuid): """ Function for going to bootloader mode from application with buttonless service. It supports both bonded and unbonded buttonless characteristics. Args: buttonless_uuid: UUID of discovered buttonless characteristic. """ if buttonless_uuid == DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID: logger.info("Bonded Buttonless characteristic discovered -> Bond") self.bond() else: logger.info("Un-bonded Buttonless characteristic discovered -> Increment target device addr") self.target_device_addr = "{:X}".format(int(self.target_device_addr, 16) + 1) self.target_device_addr_type.addr[-1] += 1 # Enable indication for Buttonless DFU Service self.adapter.enable_indication(self.conn_handle, buttonless_uuid) # Enable indication for Service changed Service, if present. if self.adapter.db_conns[self.conn_handle].get_char_handle(DFUAdapter.SERVICE_CHANGED_UUID): self.adapter.enable_indication(self.conn_handle, DFUAdapter.SERVICE_CHANGED_UUID) # Enter DFU mode self.adapter.write_req(self.conn_handle, buttonless_uuid, [0x01]) response = self.indication_q.get(timeout=DfuTransportBle.DEFAULT_TIMEOUT) if response[DFUAdapter.ERROR_CODE_POS] != 0x01: raise Exception("Error - Unexpected response") # Wait for buttonless peer to disconnect self.evt_sync.wait('disconnected') # Reconnect self.target_device_name = None self.adapter.driver.ble_gap_scan_start() self.verify_stable_connection() if self.conn_handle is None: raise NordicSemiException('Timeout. Target device not found.') logger.info('BLE: Connected to target') logger.debug('BLE: Service Discovery') self.adapter.service_discovery(conn_handle=self.conn_handle) def verify_stable_connection(self): """ Verify connection event, and verify that unexpected disconnect events are not received. Returns: True if connected, else False. """ self.conn_handle = self.evt_sync.wait('connected') if self.conn_handle is not None: retries = DFUAdapter.CONNECTION_ATTEMPTS while retries: if self.evt_sync.wait('disconnected', timeout=1) is None: break logger.warning("Received unexpected disconnect event, " "trying to re-connect to: {}".format(self.target_device_addr)) time.sleep(1) self.adapter.connect(address=self.target_device_addr_type, conn_params=self.conn_params) self.conn_handle = self.evt_sync.wait('connected') retries -= 1 else: if self.evt_sync.wait('disconnected', timeout=1) is not None: raise Exception("Failure - Connection failed due to 0x3e") logger.info("Successfully Connected") return self.adapter.driver.ble_gap_scan_stop() raise Exception("Connection Failure - Device not found!") def setup_keyset(self): """ Setup keyset structure. """ self.keyset = driver.ble_gap_sec_keyset_t() self.id_key_own = driver.ble_gap_id_key_t() self.id_key_peer = driver.ble_gap_id_key_t() self.enc_key_own = driver.ble_gap_enc_key_t() self.enc_key_peer = driver.ble_gap_enc_key_t() self.sign_info_own = driver.ble_gap_sign_info_t() self.sign_info_peer = driver.ble_gap_sign_info_t() self.lesc_pk_own = driver.ble_gap_lesc_p256_pk_t() self.lesc_pk_peer = driver.ble_gap_lesc_p256_pk_t() self.keyset.keys_own.p_enc_key = self.enc_key_own self.keyset.keys_own.p_id_key = self.id_key_own self.keyset.keys_own.p_sign_key = self.sign_info_own self.keyset.keys_own.p_pk = self.lesc_pk_own self.keyset.keys_peer.p_enc_key = self.enc_key_peer self.keyset.keys_peer.p_id_key = self.id_key_peer self.keyset.keys_peer.p_sign_key = self.sign_info_peer self.keyset.keys_peer.p_pk = self.lesc_pk_peer def setup_sec_params(self): """ Setup Security parameters. """ self.kdist_own = BLEGapSecKDist(enc=True, id=True, sign=False, link=False) self.kdist_peer = BLEGapSecKDist(enc=True, id=True, sign=False, link=False) self.sec_params = BLEGapSecParams(bond=True, mitm=False, lesc=False, keypress=False, io_caps=BLEGapIOCaps.none, oob=False, min_key_size=7, max_key_size=16, kdist_own=self.kdist_own, kdist_peer=self.kdist_peer) def bond(self): """ Bond to Application with Buttonless Service. """ self.bonded = True self.setup_sec_params() self.setup_keyset() self.adapter.driver.ble_gap_authenticate(self.conn_handle, self.sec_params) self.evt_sync.wait(evt="sec_params") self.adapter.driver.ble_gap_sec_params_reply(self.conn_handle, BLEGapSecStatus.success, None, self.keyset, None) result = self.evt_sync.wait(evt="auth_status") if result != BLEGapSecStatus.success: raise NordicSemiException("Auth Status returned error code: {}".format(result)) def encrypt(self): """ Re-encrypt to bootloader. """ logger.info("Re-encryption to bootloader") self.adapter.driver.ble_gap_encrypt(self.conn_handle, self.keyset.keys_peer.p_enc_key.master_id, self.keyset.keys_peer.p_enc_key.enc_info) self.evt_sync.wait('conn_sec_update') def write_control_point(self, data): self.adapter.write_req(self.conn_handle, DFUAdapter.CP_UUID, data) def write_data_point(self, data): self.adapter.write_cmd(self.conn_handle, DFUAdapter.DP_UUID, data) def on_gap_evt_sec_params_request(self, ble_driver, conn_handle, peer_params): logger.info("Got sec params req") self.evt_sync.notify(evt='sec_params', data=conn_handle) def on_gap_evt_auth_status(self, ble_driver, conn_handle, auth_status): logger.info("Got auth status:{}".format(auth_status)) self.evt_sync.notify(evt='auth_status', data=auth_status) def on_gap_evt_conn_sec_update(self, ble_driver, conn_handle): logger.info("Got Conn sec update") self.evt_sync.notify(evt='conn_sec_update', data=conn_handle) def on_gap_evt_connected(self, ble_driver, conn_handle, peer_addr, role, conn_params): self.evt_sync.notify(evt = 'connected', data = conn_handle) logger.info('BLE: Connected to {}'.format(peer_addr.addr)) def on_gap_evt_disconnected(self, ble_driver, conn_handle, reason): self.evt_sync.notify(evt = 'disconnected', data = conn_handle) self.conn_handle = None logger.info('BLE: Disconnected with reason: {}'.format(reason)) def on_gap_evt_adv_report(self, ble_driver, conn_handle, peer_addr, rssi, adv_type, adv_data): dev_name_list = [] if BLEAdvData.Types.complete_local_name in adv_data.records: dev_name_list = adv_data.records[BLEAdvData.Types.complete_local_name] elif BLEAdvData.Types.short_local_name in adv_data.records: dev_name_list = adv_data.records[BLEAdvData.Types.short_local_name] dev_name = "".join(chr(e) for e in dev_name_list) address_string = "".join("{0:02X}".format(b) for b in peer_addr.addr) logger.info('Received advertisement report, address: 0x{}, device_name: {}'.format(address_string, dev_name)) if (dev_name == self.target_device_name) or (address_string == self.target_device_addr): self.conn_params = BLEGapConnParams(min_conn_interval_ms = 7.5, max_conn_interval_ms = 30, conn_sup_timeout_ms = 4000, slave_latency = 0) logger.info('BLE: Found target advertiser, address: 0x{}, name: {}'.format(address_string, dev_name)) logger.info('BLE: Connecting to 0x{}'.format(address_string)) self.adapter.connect(address = peer_addr, conn_params = self.conn_params) # store the address for subsequent connections self.target_device_addr = address_string self.target_device_addr_type = peer_addr def on_notification(self, ble_adapter, conn_handle, uuid, data): if self.conn_handle != conn_handle: return if DFUAdapter.CP_UUID.value != uuid.value: return self.notifications_q.put(data) def on_indication(self, ble_adapter, conn_handle, uuid, data): if self.conn_handle != conn_handle: return if DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID.value != uuid.value and \ DFUAdapter.BLE_DFU_BUTTONLESS_CHAR_UUID.value != uuid.value: return self.indication_q.put(data) def on_att_mtu_exchanged(self, ble_driver, conn_handle, att_mtu): logger.info('ATT MTU exchanged: conn_handle={} att_mtu={}'.format(conn_handle, att_mtu)) self.att_mtu = att_mtu self.packet_size = att_mtu - 3 def on_gattc_evt_exchange_mtu_rsp(self, ble_driver, conn_handle, **kwargs): pass
class DFUAdapter(BLEDriverObserver, BLEAdapterObserver): BASE_UUID = BLEUUIDBase([ 0x8E, 0xC9, 0x00, 0x00, 0xF3, 0x15, 0x4F, 0x60, 0x9F, 0xB8, 0x83, 0x88, 0x30, 0xDA, 0xEA, 0x50 ]) CP_UUID = BLEUUID(0x0001, BASE_UUID) DP_UUID = BLEUUID(0x0002, BASE_UUID) LOCAL_ATT_MTU = 23 def __init__(self, adapter): super(DFUAdapter, self).__init__() self.evt_sync = EvtSync(['connected', 'disconnected']) self.conn_handle = None self.adapter = adapter self.notifications_q = Queue.Queue() self.adapter.observer_register(self) self.adapter.driver.observer_register(self) def open(self): self.adapter.driver.open() ble_enable_params = BLEEnableParams(vs_uuid_count=10, service_changed=False, periph_conn_count=0, central_conn_count=1, central_sec_count=1) if nrf_sd_ble_api_ver >= 3: logger.info("\nBLE: ble_enable with local ATT MTU: {}".format( DFUAdapter.LOCAL_ATT_MTU)) ble_enable_params.att_mtu = DFUAdapter.LOCAL_ATT_MTU self.adapter.driver.ble_enable(ble_enable_params) self.adapter.driver.ble_vs_uuid_add(DFUAdapter.BASE_UUID) def connect(self, target_device_name, target_device_addr): self.target_device_name = target_device_name self.target_device_addr = target_device_addr logger.debug('BLE: connect: target address: 0x{}'.format( self.target_device_addr)) logger.info('BLE: Scanning...') self.adapter.driver.ble_gap_scan_start() self.conn_handle = self.evt_sync.wait('connected') if self.conn_handle is None: raise NordicSemiException('Timeout. Target device not found.') logger.info('BLE: Connected to target') logger.debug('BLE: Service Discovery') if nrf_sd_ble_api_ver >= 3: if DFUAdapter.LOCAL_ATT_MTU > ATT_MTU_DEFAULT: logger.info('BLE: Enabling longer ATT MTUs') self.att_mtu = self.adapter.att_mtu_exchange(self.conn_handle) logger.info('BLE: ATT MTU: {}'.format(self.att_mtu)) else: logger.info('BLE: Using default ATT MTU') self.adapter.service_discovery(conn_handle=self.conn_handle) logger.debug('BLE: Enabling Notifications') self.adapter.enable_notification(conn_handle=self.conn_handle, uuid=DFUAdapter.CP_UUID) return self.target_device_name, self.target_device_addr def close(self): if self.conn_handle is not None: logger.info('BLE: Disconnecting from target') self.adapter.disconnect(self.conn_handle) self.evt_sync.wait('disconnected') self.adapter.driver.close() def write_control_point(self, data): self.adapter.write_req(self.conn_handle, DFUAdapter.CP_UUID, data) def write_data_point(self, data): self.adapter.write_cmd(self.conn_handle, DFUAdapter.DP_UUID, data) def on_gap_evt_connected(self, ble_driver, conn_handle, peer_addr, role, conn_params): self.evt_sync.notify(evt='connected', data=conn_handle) logger.info('BLE: Connected to {}'.format(peer_addr)) def on_gap_evt_disconnected(self, ble_driver, conn_handle, reason): self.evt_sync.notify(evt='disconnected', data=conn_handle) self.conn_handle = None logger.info('BLE: Disconnected') def on_gap_evt_adv_report(self, ble_driver, conn_handle, peer_addr, rssi, adv_type, adv_data): dev_name_list = [] if BLEAdvData.Types.complete_local_name in adv_data.records: dev_name_list = adv_data.records[ BLEAdvData.Types.complete_local_name] elif BLEAdvData.Types.short_local_name in adv_data.records: dev_name_list = adv_data.records[BLEAdvData.Types.short_local_name] dev_name = "".join(chr(e) for e in dev_name_list) address_string = "".join("{0:02X}".format(b) for b in peer_addr.addr) logger.debug( 'Received advertisement report, address: 0x{}, device_name: {}'. format(address_string, dev_name)) if (dev_name == self.target_device_name) or (address_string == self.target_device_addr): conn_params = BLEGapConnParams(min_conn_interval_ms=15, max_conn_interval_ms=30, conn_sup_timeout_ms=4000, slave_latency=0) logger.info( 'BLE: Found target advertiser, address: 0x{}, name: {}'.format( address_string, dev_name)) logger.info('BLE: Connecting to 0x{}'.format(address_string)) self.adapter.connect(address=peer_addr, conn_params=conn_params) # store the name and address for subsequent connections self.target_device_name = dev_name self.target_device_addr = address_string def on_notification(self, ble_adapter, conn_handle, uuid, data): if self.conn_handle != conn_handle: return if DFUAdapter.CP_UUID.value != uuid.value: return #logger.debug(data) self.notifications_q.put(data) def on_att_mtu_exchanged(self, ble_driver, conn_handle, att_mtu): logger.info('ATT MTU exchanged: conn_handle={} att_mtu={}'.format( conn_handle, att_mtu)) def on_gattc_evt_exchange_mtu_rsp(self, ble_driver, conn_handle, **kwargs): logger.info( 'ATT MTU exchange response: conn_handle={}'.format(conn_handle))
class DFUAdapter(BLEDriverObserver, BLEAdapterObserver): BASE_UUID = BLEUUIDBase([0x8E, 0xC9, 0x00, 0x00, 0xF3, 0x15, 0x4F, 0x60, 0x9F, 0xB8, 0x83, 0x88, 0x30, 0xDA, 0xEA, 0x50]) CP_UUID = BLEUUID(0x0001, BASE_UUID) DP_UUID = BLEUUID(0x0002, BASE_UUID) LOCAL_ATT_MTU = 23 def __init__(self, adapter): super(DFUAdapter, self).__init__() self.evt_sync = EvtSync(['connected', 'disconnected']) self.conn_handle = None self.adapter = adapter self.notifications_q = Queue.Queue() self.adapter.observer_register(self) self.adapter.driver.observer_register(self) def open(self): self.adapter.driver.open() ble_enable_params = BLEEnableParams(vs_uuid_count = 10, service_changed = False, periph_conn_count = 0, central_conn_count = 1, central_sec_count = 1) if nrf_sd_ble_api_ver >= 3: logger.info("\nBLE: ble_enable with local ATT MTU: {}".format(DFUAdapter.LOCAL_ATT_MTU)) ble_enable_params.att_mtu = DFUAdapter.LOCAL_ATT_MTU self.adapter.driver.ble_enable(ble_enable_params) self.adapter.driver.ble_vs_uuid_add(DFUAdapter.BASE_UUID) def connect(self, target_device_name, target_device_addr): self.target_device_name = target_device_name self.target_device_addr = target_device_addr logger.debug('BLE: connect: target address: 0x{}'.format(self.target_device_addr)) logger.info('BLE: Scanning...') self.adapter.driver.ble_gap_scan_start() self.conn_handle = self.evt_sync.wait('connected') if self.conn_handle is None: raise NordicSemiException('Timeout. Target device not found.') logger.info('BLE: Connected to target') logger.debug('BLE: Service Discovery') if nrf_sd_ble_api_ver >= 3: if DFUAdapter.LOCAL_ATT_MTU > ATT_MTU_DEFAULT: logger.info('BLE: Enabling longer ATT MTUs') self.att_mtu = self.adapter.att_mtu_exchange(self.conn_handle) logger.info('BLE: ATT MTU: {}'.format(self.att_mtu)) else: logger.info('BLE: Using default ATT MTU') self.adapter.service_discovery(conn_handle=self.conn_handle) logger.debug('BLE: Enabling Notifications') self.adapter.enable_notification(conn_handle=self.conn_handle, uuid=DFUAdapter.CP_UUID) return self.target_device_name, self.target_device_addr def close(self): if self.conn_handle is not None: logger.info('BLE: Disconnecting from target') self.adapter.disconnect(self.conn_handle) self.evt_sync.wait('disconnected') self.adapter.driver.close() def write_control_point(self, data): self.adapter.write_req(self.conn_handle, DFUAdapter.CP_UUID, data) def write_data_point(self, data): self.adapter.write_cmd(self.conn_handle, DFUAdapter.DP_UUID, data) def on_gap_evt_connected(self, ble_driver, conn_handle, peer_addr, role, conn_params): self.evt_sync.notify(evt = 'connected', data = conn_handle) logger.info('BLE: Connected to {}'.format(peer_addr)) def on_gap_evt_disconnected(self, ble_driver, conn_handle, reason): self.evt_sync.notify(evt = 'disconnected', data = conn_handle) self.conn_handle = None logger.info('BLE: Disconnected') def on_gap_evt_adv_report(self, ble_driver, conn_handle, peer_addr, rssi, adv_type, adv_data): dev_name_list = [] if BLEAdvData.Types.complete_local_name in adv_data.records: dev_name_list = adv_data.records[BLEAdvData.Types.complete_local_name] elif BLEAdvData.Types.short_local_name in adv_data.records: dev_name_list = adv_data.records[BLEAdvData.Types.short_local_name] dev_name = "".join(chr(e) for e in dev_name_list) address_string = "".join("{0:02X}".format(b) for b in peer_addr.addr) logger.debug('Received advertisement report, address: 0x{}, device_name: {}'.format(address_string, dev_name)) if (dev_name == self.target_device_name) or (address_string == self.target_device_addr): conn_params = BLEGapConnParams(min_conn_interval_ms = 15, max_conn_interval_ms = 30, conn_sup_timeout_ms = 4000, slave_latency = 0) logger.info('BLE: Found target advertiser, address: 0x{}, name: {}'.format(address_string, dev_name)) logger.info('BLE: Connecting to 0x{}'.format(address_string)) self.adapter.connect(address = peer_addr, conn_params = conn_params) # store the name and address for subsequent connections self.target_device_name = dev_name self.target_device_addr = address_string def on_notification(self, ble_adapter, conn_handle, uuid, data): if self.conn_handle != conn_handle: return if DFUAdapter.CP_UUID.value != uuid.value: return #logger.debug(data) self.notifications_q.put(data) def on_att_mtu_exchanged(self, ble_driver, conn_handle, att_mtu): logger.info('ATT MTU exchanged: conn_handle={} att_mtu={}'.format(conn_handle, att_mtu)) def on_gattc_evt_exchange_mtu_rsp(self, ble_driver, conn_handle, **kwargs): logger.info('ATT MTU exchange response: conn_handle={}'.format(conn_handle))