def __init__(self, address=None): if not address: address = "00:00:00:00:00:00" # TODO: is defaulting this a good idea? # expect a string of the format "xx:xx:xx:xx:xx:xx" # or a 6 element; tuple, list, bytes or bytearray if isinstance(address, str): # TODO: regex based check if len(address) != 6 * 2 + 5: raise ValueError( 'Unexpected address length of {} for {}'.format( len(address), address)) address = address elif isinstance(address, list) \ or isinstance(address, tuple) \ or isinstance(address, bytes) \ or isinstance(address, bytearray): if len(address) != 6: raise ValueError( 'Unexpected address length of {} for {}'.format( len(address), address)) address = ':'.join( [format(c, '02x') for c in list(reversed(address))]) else: raise TypeError('Unsupported address type: {}'.format( type(address))) # TODO: keep this for hashing but return bytearray for getter (defaulted to little endian) self.address = address.upper() log.debug(self)
def tlm(self, tlm): self._tlm = tlm if tlm: self.advertisement.raw_data = self.eddystone_type_adv_data( tlm, FRAME_TYPE_TLM) log.debug("Beacon Adv TLM raw data = {}".format( self.advertisement.raw_data))
def on_advertisement(advertisement): log.debug(advertisement) if advertisement.address.address.startswith(GOVEE_BT_mac_OUI_PREFIX): mac = advertisement.address.address if mac not in govee_devices: govee_devices[mac] = {} if H5075_UPDATE_UUID16 in advertisement.uuid16s: # HACK: Proper decoding is done in bleson > 0.10 name = advertisement.name.split("'")[1] encoded_data = int(advertisement.mfg_data.hex()[6:12], 16) battery = int(advertisement.mfg_data.hex()[12:14], 16) govee_devices[mac]["address"] = mac govee_devices[mac]["name"] = name govee_devices[mac]["mfg_data"] = advertisement.mfg_data govee_devices[mac]["data"] = encoded_data govee_devices[mac]["tempInC"] = decode_temp_in_c(encoded_data) govee_devices[mac]["tempInF"] = decode_temp_in_f(encoded_data) govee_devices[mac]["humidity"] = decode_humidity(encoded_data) govee_devices[mac]["battery"] = battery print_values(mac) if advertisement.rssi is not None and advertisement.rssi != 0: govee_devices[mac]["rssi"] = advertisement.rssi print_rssi(mac) log.debug(govee_devices[mac])
def eid(self, eid): self._eid = eid if eid: self.advertisement.raw_data = self.eddystone_type_adv_data( eid, FRAME_TYPE_EID) log.debug("Beacon Adv EID raw data = {}".format( self.advertisement.raw_data))
def __init__(self, uuid, little_endian=True): """ Create UUID128, non-int types must be little endian (e.g. 'reversed' order w.r.t. displayed UUID)""" # TODO: accept 32bit and convert, xxxxxxxx-0000-1000-8000-00805F9B34FB log.debug(("UUID128 type: {} value={}".format(type(uuid), uuid))) if isinstance(uuid, memoryview): uuid = uuid.tobytes() # Massage 'uuid' into a string that the built-in UUID() contructor accepts if isinstance(uuid, int): if not 0 <= uuid <= 0xffff: raise ValueError('Invalid UUID16 value {} fro UUID128 promotion'.format(uuid)) uuid = UUID("0000{:04x}-0000-1000-8000-00805F9B34FB".format(uuid)).hex elif isinstance(uuid, str): if not len(uuid) == 36: raise ValueError('Invalid UUID128 value {}'.format(uuid)) elif isinstance(uuid, list) or isinstance(uuid, tuple) or isinstance(uuid, bytes) or isinstance(uuid, bytearray): if len(uuid) != 16: raise ValueError('Unexpected address length of {} for {}'.format(len(uuid), uuid)) uuid = ''.join([format(c, '02x') for c in reversed(uuid)] ) else: raise TypeError('Unsupported UUID128 initialiser type: {}'.format(type(uuid))) self._uuid_obj = UUID(uuid) if not little_endian: self._uuid_obj = UUID(bytes=bytes(reversed(self._uuid_obj.bytes))) self._uuid=self._uuid_obj.urn.replace('urn:uuid:', '') log.debug(self)
def url(self, url): self._url = url if url: self.advertisement.raw_data = self.eddystone_type_adv_data( url, FRAME_TYPE_URL) log.debug("Beacon Adv URL raw data = {}".format( self.advertisement.raw_data))
def __init__(self, uuid, little_endian=True): log.debug(("UUID16 type: {} value={}".format(type(uuid), uuid))) if isinstance(uuid, memoryview): uuid = uuid.tolist() if isinstance(uuid, int): if not 0 <= uuid <= 0xffff: raise ValueError('Invalid UUID16 value {}'.format(uuid)) elif isinstance(uuid, list) or isinstance(uuid, tuple): if len(uuid) != 2: raise ValueError('Unexpected address length of {} for {}'.format(len(uuid), uuid)) uuid = (uuid[1] << 8) + (uuid[0] & 0xff) elif isinstance(uuid, bytes) or isinstance(uuid, bytearray): if len(uuid) != 2: raise ValueError('Unexpected address length of {} for {}'.format(len(uuid), uuid)) uuid = (uuid[1] << 8) + (uuid[0] & 0xff) else: raise TypeError('Unsupported UUID16 initialiser type: {}'.format(type(uuid))) if not little_endian: # swap uuid = ( (uuid & 0xff) << 8 ) | ( (uuid & 0xff00) >>8) self._uuid=uuid log.debug(self)
def __init__(self, name=None, address=None, rssi=None, tx_power=None, raw_data=None): #TODO: use kwargs self.flags = 6 # uint8 # default to LE_GENERAL_DISCOVERABLE | BREDR_NOT_SUPPORTED self.type = None self.address_type = None self.address = address self._name = name self.name_is_complete = False # unsigned self.tx_pwr_lvl = tx_power # unsigned self.appearance = None # unit16 self.uuid16s = [] # uuid16[] self.uuid32s = [] # uuid32[] self.uuid128s = [] # uuid12[] self.service_data = None #slave_itvl_range self.svc_data_uuid16 = None # uuid16[] self.public_tgt_addr = None # uint8[] self.adv_itvl = None # uint16 self.svc_data_uuid32 = None # uint8 self.svc_data_uuid128 = None # uint8 self.uri = None # unit8 self.mfg_data = None # unit8 self.rssi = rssi # really only part of an Advertisement Report... self.raw_data = raw_data log.debug(self)
def peripheral_didDiscoverCharacteristicsForService_error_( self, peripheral, service, error): log.debug("peripheral_didDiscoverCharacteristicsForService_error_") for characteristic in self.service.characteristics(): if characteristic.UUID().UUIDString( ) == crtp_characteristic.UUIDString(): self.crtp_characteristic = characteristic self.peripheral.setNotifyValue_forCharacteristic_( True, self.crtp_characteristic)
def test_parse_heartrate_advertisment_report(self): hci_packet = parse_hci_event_packet(HEARTRATE_ADV_REPORT) log.debug(hci_packet) advertisement = AdvertisingDataConverters.from_hcipacket(hci_packet) self.assertEqual(0x1a, advertisement.flags) self.assertEqual(-55, advertisement.rssi) self.assertEqual('Heart Rate', advertisement.name) self.assertEqual(True, advertisement.name_is_complete) self.assertEqual(True, UUID16(0x180a) in advertisement.uuid16s) self.assertEqual(True, UUID16(0x180d) in advertisement.uuid16s) self.assertEqual(2, len(advertisement.uuid16s))
def _handle_meta_event(self, hci_packet): log.debug("EVT_LE_META_EVENT") if hci_packet.subevent_code == EVT_LE_ADVERTISING_REPORT: log.debug('LE Advertising Report') if self.on_advertising_data: advertisement = AdvertisingDataConverters.from_hcipacket( hci_packet) self.on_advertising_data(advertisement) else: log.warning( "TODO: unhandled HCI Meta Event packet, type={}".format( hci_packet))
def test_parse_nrf5x_lefacy_dfu_advertisement_report(self): hci_packet = parse_hci_event_packet(NRF51X_LEGACY_DFU_ADV_REPORT) log.debug(hci_packet) advertisement = AdvertisingDataConverters.from_hcipacket(hci_packet) self.assertEqual(0x1a, advertisement.flags) self.assertEqual(-71, advertisement.rssi) self.assertEqual('nRF5x', advertisement.name) log.debug(advertisement.uuid128s) self.assertEqual( True, UUID128('00001530-1212-efde-1523-785feabcd123') in advertisement.uuid128s) self.assertEqual(1, len(advertisement.uuid128s))
def test_parse_thermometer_advertisment_report(self): hci_packet = parse_hci_event_packet(THERMOMETER_ADV_REPORT) log.debug(hci_packet) advertisement = AdvertisingDataConverters.from_hcipacket(hci_packet) self.assertEqual(0x1a, advertisement.flags) self.assertEqual(-74, advertisement.rssi) self.assertEqual('Health Thermometer', advertisement.name) self.assertEqual(True, advertisement.name_is_complete) log.debug(advertisement.uuid16s) self.assertEqual(True, UUID16(0x180a) in advertisement.uuid16s) self.assertEqual(True, UUID16(0x1809) in advertisement.uuid16s) self.assertEqual(2, len(advertisement.uuid16s))
def test_all_examples(self): path = os.path.abspath(__file__) dir_path = os.path.dirname(path) examples_path = os.path.join(dir_path, '..', 'examples') examples_glob = os.path.join(examples_path, '*.py') scripts = glob.glob(examples_glob) log.debug(scripts) for script in scripts: log.info("Running {}".format(script)) sys.argv = ['', str(TEST_DURATION)] # if sys.platform.lower().startswith('darwin'): # log.warning("Remove workaround for macOS native teardown issue") # os.system("python3 {} {}".format(script, TEST_DURATION)) # else: # runpy.run_path(script) runpy.run_path(script)
def centralManager_didDiscoverPeripheral_advertisementData_RSSI_( self, manager, peripheral, data, rssi): try: log.debug('Found: name={} rssi={} data={} '.format( peripheral.name(), rssi, data)) if self.on_advertising_data: advertisement = Advertisement() advertisement.flags = 0 # Not available advertisement.name = peripheral.name() advertisement.rssi = rssi advertisement.raw_data = data if 'kCBAdvDataTxPowerLevel' in data: advertisement.tx_pwr_lvl = int( data['kCBAdvDataTxPowerLevel']) if data['kCBAdvDataIsConnectable']: # TODO: handle: kCBAdvDataIsConnectable correctly advertisement.type = 0x01 # BLE_GAP_ADV_TYPE_ADV_DIRECT_IND if 'kCBAdvDataServiceUUIDs' in data: log.debug('kCBAdvDataServiceUUIDs:') for cbuuid in data['kCBAdvDataServiceUUIDs']: uuid_bytes = cbuuid.data().bytes().tobytes() if 2 == len(uuid_bytes): uuid = UUID16(uuid_bytes, little_endian=False) advertisement.uuid16s.append(uuid) elif 16 == len(uuid_bytes): uuid = UUID128(uuid_bytes, little_endian=False) advertisement.uuid128s.append(uuid) else: log.error( "Unsupporten UUID length for UUID bytes={}". format(uuid_bytes)) log.debug('Service UUID: {} {}'.format( type(cbuuid), cbuuid)) if 'kCBAdvDataManufacturerData' in data: mfg_data = data['kCBAdvDataManufacturerData'] log.debug('kCBAdvDataManufacturerData={}'.format(mfg_data)) advertisement.mfg_data = mfg_data self.on_advertising_data(advertisement) except Exception as e: log.exception(e)
def _on_data(self, data): log.debug( "----------------------------------------------------------------------" ) log.debug("Socket data: len={}, data={}".format( len(data), hex_string(data))) if data[0] == HCI_EVENT_PKT: hci_event_packet = parse_hci_event_packet(data[1:-1]) log.debug(hci_event_packet) if data[1] == EVT_CMD_COMPLETE: self._handle_command_complete(data) elif data[1] == EVT_DISCONN_COMPLETE: self._handle_disconnection_complete(data) elif data[1] == EVT_LE_META_EVENT: self._handle_meta_event(hci_event_packet) else: log.warning("TODO: unhandled HCI Event packet, type={}".format( hci_event_packet)) else: log.warning("TODO: Unhandled HCI packet, type={}".format(data[0]))
def _on_advertising_data(self, data): try: log.debug('Found: {}'.format(data)) if self.on_advertising_data: advertisement = Advertisement() advertisement.flags = 0 advertisement.rssi = data['RSSI'] if 'ADDRESS' in data: advertisement.address = BDAddress(data['ADDRESS']) if 'LOCALNAME' in data: advertisement.name = data['LOCALNAME'] if 'TXPOWER' in data: advertisement.tx_pwr_lvl = int(data['TXPOWER']) self.on_advertising_data(advertisement) except Exception as e: log.exception(e)
def get_provider(): global _provider if _provider is None: if sys.platform.startswith('linux'): from bleson.providers.linux.linux_provider import LinuxProvider _provider = LinuxProvider() elif sys.platform.startswith('darwin'): from bleson.providers.macos.macos_provider import MacOSProvider _provider = MacOSProvider() elif sys.platform.startswith('win32'): from bleson.providers.win32.win32_provider import Win32Provider _provider = Win32Provider() else: raise RuntimeError('Platform {0} is not supported!'.format( sys.platform)) log.debug("Provider is {}".format(_provider)) return _provider
def centralManager_didDisconnectPeripheral_error_(self, manager, peripheral, error): log.debug("centralManager_didDisconnectPeripheral_error_") self.connected = False
def __init__(self, address:BDAddress=None, name=None, rssi=None): self.address = address self.name = name self.rssi = rssi log.debug(self)
def from_hcipayload(cls, data): data_info = "Data: {}".format(hex_string(data)) pos_info = "POS : {}".format(''.join('{:02} '.format(x) for x in range(0, len(data)))) log.debug(data_info) log.debug(pos_info) num_reports = data[0] log.debug("Num Reports {}".format(num_reports)) if num_reports != 1: log.error( "TODO: Only 1 Advertising report is supported, creating emtpy Advertisement" ) # don't make it fatal return Advertisement() # TODO: move these 2 LUTs to a better place gap_adv_type = [ 'ADV_IND', 'ADV_DIRECT_IND', 'ADV_SCAN_IND', 'ADV_NONCONN_IND', 'SCAN_RSP' ][data[1]] gap_addr_type = [ 'PUBLIC', 'RANDOM', 'PUBLIC_IDENTITY', 'RANDOM_STATIC' ][data[2]] gap_addr = data[3:9] rssi = rssi_from_byte(data[-1]) advertisement = Advertisement(address=BDAddress(gap_addr), rssi=rssi) advertisement.type = gap_adv_type advertisement.address_type = gap_addr_type pos = 10 while pos < len(data) - 1: log.debug("POS={}".format(pos)) length = data[pos] gap_type = data[pos + 1] payload = data[pos + 2:pos + 2 + length - 1] log.debug("Pos={} Type=0x{:02x} Len={} Payload={}".format( pos, gap_type, length, hex_string(payload))) if GAP_FLAGS == gap_type: advertisement.flags = payload[0] log.debug("Flags={:02x}".format(advertisement.flags)) elif GAP_UUID_16BIT_COMPLETE == gap_type: uuids = [] byte_pos = 0 if len(payload) % 2 != 0: raise ValueError( "PAyload is not divisible by 2 for UUID16") while byte_pos < len(payload): log.debug('byte_pos={}'.format(byte_pos)) byte_pair = payload[byte_pos:byte_pos + 2] log.debug('byte pair = {}'.format(byte_pair)) uuid = UUID16(byte_pair) uuids.append(uuid) byte_pos += 2 advertisement.uuid16s = uuids elif GAP_UUID_128BIT_COMPLETE == gap_type: # if length-1 > 16: # log.warning("TODO: >1 UUID128's found, not yet split into individual elements") #advertisement.uuid128s=[UUID128(payload)] uuids = [] byte_pos = 0 if len(payload) % 16 != 0: raise ValueError( "Payload is not divisible by 16 for UUID128") while byte_pos < len(payload): log.debug('byte_pos={}'.format(byte_pos)) byte_list = payload[byte_pos:byte_pos + 16] log.debug('byte_list = {}'.format(byte_list)) uuid = UUID128(byte_list) uuids.append(uuid) byte_pos += 16 advertisement.uuid128s = uuids log.debug(advertisement.uuid128s) elif GAP_NAME_INCOMPLETE == gap_type: advertisement.name = payload advertisement.name_is_complete = False log.debug("Incomplete Name={}".format(advertisement.name)) elif GAP_NAME_COMPLETE == gap_type: advertisement.name = payload advertisement.name_is_complete = True log.debug("Complete Name={}".format(advertisement.name)) elif GAP_SERVICE_DATA == gap_type: advertisement.service_data = payload log.debug("Service Data={}".format(advertisement.service_data)) elif GAP_MFG_DATA == gap_type: advertisement.mfg_data = payload log.debug("Manufacturer Data={}".format( advertisement.mfg_data)) else: log.warning( "TODO: Unhandled GAP type, pos={} type=0x{:02x} len={}". format(pos, gap_type, length)) log.warning(data_info) log.warning(pos_info) pos += length + 1 log.debug(advertisement) return advertisement
def stop_scanning(self): log.debug("") if self._manager: self._manager.stopScan()
def on(self): log.debug("TODO: adatper on")
def peripheral_didUpdateValueForCharacteristic_error_( self, peripheral, characteristic, error): log.debug("peripheral_didUpdateValueForCharacteristic_error_") log.debug(repr(characteristic.value().bytes().tobytes()))
def peripheral_didUpdateNotificationStateForCharacteristic_error_( self, peripheral, characteristic, error): log.debug( "peripheral_didUpdateNotificationStateForCharacteristic_error_")
def peripheral_didWriteValueForCharacteristic_error_( self, peripheral, characteristic, error): log.debug("peripheral_didWriteValueForCharacteristic_error_") if error != None: log.error(repr(error))
def centralManagerDidUpdateState_(self, manager): state = manager.state() log.debug("State: {}".format(state))
def centralManager_didConnectPeripheral_(self, manager, peripheral): log.debug('Connected: ' + peripheral.name()) self.connected = True self.peripheral.setDelegate_(self) self.peripheral.readRSSI()
def from_hcipayload(cls, data): data_info = "Data: {}".format(hex_string(data)) pos_info = "POS : {}".format("".join("{:02} ".format(x) for x in range(0, len(data)))) log.debug(data_info) log.debug(pos_info) num_reports = data[0] log.debug("Num Reports {}".format(num_reports)) if num_reports != 1: log.error( "TODO: Only 1 Advertising report is supported, creating empty Advertisement" ) # don't make it fatal return Advertisement() # TODO: move these 2 LUTs to a better place gap_adv_type = [ "ADV_IND", "ADV_DIRECT_IND", "ADV_SCAN_IND", "ADV_NONCONN_IND", "SCAN_RSP", ][data[1]] gap_addr_type = [ "PUBLIC", "RANDOM", "PUBLIC_IDENTITY", "RANDOM_STATIC" ][data[2]] gap_addr = data[3:9] rssi = rssi_from_byte(data[-1]) advertisement = Advertisement(address=BDAddress(gap_addr), rssi=rssi, raw_data=data) advertisement.type = gap_adv_type advertisement.address_type = gap_addr_type pos = 10 while pos < len(data) - 1: log.debug("POS={}".format(pos)) length = data[pos] gap_type = data[pos + 1] payload = data[pos + 2:pos + 2 + length - 1] log.debug("Pos={} Type=0x{:02x} Len={} Payload={}".format( pos, gap_type, length, hex_string(payload))) if GAP_FLAGS == gap_type: advertisement.flags = payload[0] log.debug("Flags={:02x}".format(advertisement.flags)) elif GAP_UUID_16BIT_COMPLETE == gap_type: uuids = [] byte_pos = 0 if len(payload) % 2 != 0: raise ValueError( "PAyload is not divisible by 2 for UUID16") while byte_pos < len(payload): log.debug("byte_pos={}".format(byte_pos)) byte_pair = payload[byte_pos:byte_pos + 2] log.debug("byte pair = {}".format(byte_pair)) uuid = UUID16(byte_pair) uuids.append(uuid) byte_pos += 2 advertisement.uuid16s = uuids elif GAP_UUID_128BIT_INCOMPLETE == gap_type: # if length-1 > 16: # log.warning("TODO: >1 UUID128's found, not yet split into individual elements") # advertisement.uuid128s=[UUID128(payload)] uuids = [] byte_pos = 0 if len(payload) % 16 != 0: raise ValueError( "Payload is not divisible by 16 for UUID128") while byte_pos < len(payload): log.debug("byte_pos={}".format(byte_pos)) byte_list = payload[byte_pos:byte_pos + 16] log.debug("byte_list = {}".format(byte_list)) uuid = UUID128(byte_list) uuids.append(uuid) byte_pos += 16 advertisement.uuid128s_incomplete = uuids log.debug(advertisement.uuid128s_incomplete) elif GAP_UUID_128BIT_COMPLETE == gap_type: # if length-1 > 16: # log.warning("TODO: >1 UUID128's found, not yet split into individual elements") # advertisement.uuid128s=[UUID128(payload)] uuids = [] byte_pos = 0 if len(payload) % 16 != 0: raise ValueError( "Payload is not divisible by 16 for UUID128") while byte_pos < len(payload): log.debug("byte_pos={}".format(byte_pos)) byte_list = payload[byte_pos:byte_pos + 16] log.debug("byte_list = {}".format(byte_list)) uuid = UUID128(byte_list) uuids.append(uuid) byte_pos += 16 advertisement.uuid128s = uuids log.debug(advertisement.uuid128s) elif GAP_NAME_INCOMPLETE == gap_type: advertisement.name = payload advertisement.name_is_complete = False log.debug("Incomplete Name={}".format(advertisement.name)) elif GAP_NAME_COMPLETE == gap_type: advertisement.name = payload advertisement.name_is_complete = True log.debug("Complete Name={}".format(advertisement.name)) elif GAP_SERVICE_DATA == gap_type: advertisement.service_data = payload log.debug("Service Data={}".format(advertisement.service_data)) elif GAP_MFG_DATA == gap_type: advertisement.mfg_data = payload log.debug("Manufacturer Data={}".format( advertisement.mfg_data)) elif GAP_TX_POWER == gap_type: if payload == None: advertisement.tx_pwr_lvl = payload else: advertisement.tx_pwr_lvl = int.from_bytes( payload, byteorder="little") log.debug("TX Power={}".format(advertisement.tx_pwr_lvl)) elif GAP_APPEARANCE == gap_type: # https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.gap.appearance.xml # "The external appearance of this device. The values are composed of a category (10-bits) and sub-categories (6-bits)." # TODO: Add Appearance Names as well as IDs likey as "appearance: {id: , description:}" advertisement.appearance = int.from_bytes(payload, byteorder="little") log.debug("GAP Appearance={}".format(advertisement.appearance)) else: log.warning( "TODO: Unhandled GAP type, pos={} type=0x{:02x} len={}". format(pos, gap_type, length)) log.warning(data_info) log.warning(pos_info) pos += length + 1 log.debug(advertisement) return advertisement
def peripheral_didDiscoverServices_(self, peripheral, error): log.debug("peripheral_didDiscoverServices_") if (error == None): self.service = self.peripheral.services()[0]