Skip to content

Commit 24b427e

Browse files
committed
Move advertisement field lookup to AdvertisingPacket. Rename various methods for clarity
1 parent c028f0a commit 24b427e

File tree

3 files changed

+70
-46
lines changed

3 files changed

+70
-46
lines changed

adafruit_ble/advertising.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,15 @@ class AdvertisingPacket:
8181
MAX_DATA_SIZE = 31
8282
"""Data size in a regular BLE packet."""
8383

84-
def __init__(self, *, max_length=MAX_DATA_SIZE):
85-
"""Create an empty advertising packet, no larger than max_length."""
86-
self._packet_bytes = bytearray()
84+
def __init__(self, data=None, *, max_length=MAX_DATA_SIZE):
85+
"""Create an advertising packet, no larger than max_length.
86+
87+
:param buf data: if not supplied (None), create an empty packet
88+
if supplied, create a packet with supplied data. This is usually used
89+
to parse an existing packet.
90+
:param int max_length: maximum length of packet
91+
"""
92+
self._packet_bytes = bytearray(data) if data else bytearray()
8793
self._max_length = max_length
8894
self._check_length()
8995

@@ -92,6 +98,39 @@ def packet_bytes(self):
9298
"""The raw packet bytes."""
9399
return self._packet_bytes
94100

101+
@packet_bytes.setter
102+
def packet_bytes(self, value):
103+
self._packet_bytes = value
104+
105+
def __getitem__(self, element_type):
106+
"""Return the bytes stored in the advertising packet for the given element type.
107+
108+
:param int element_type: An integer designating an advertising element type.
109+
A number of types are defined in `AdvertisingPacket`,
110+
such as `AdvertisingPacket.TX_POWER`.
111+
:returns: bytes that are the value for the given element type.
112+
If the element type is not present in the packet, raise KeyError.
113+
"""
114+
i = 0
115+
adv_bytes = self.packet_bytes
116+
while i < len(adv_bytes):
117+
item_length = adv_bytes[i]
118+
if element_type != adv_bytes[i+1]:
119+
# Type doesn't match: skip to next item.
120+
i += item_length + 1
121+
else:
122+
return adv_bytes[i + 2:i + 1 + item_length]
123+
raise KeyError
124+
125+
def get(self, element_type, default=None):
126+
"""Return the bytes stored in the advertising packet for the given element type,
127+
returning the default value if not found.
128+
"""
129+
try:
130+
return self.__getitem__(element_type)
131+
except KeyError:
132+
return default
133+
95134
@property
96135
def bytes_remaining(self):
97136
"""Number of bytes still available for use in the packet."""

adafruit_ble/scanner.py

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ class Scanner:
5454
def __init__(self):
5555
self._scanner = bleio.Scanner()
5656

57-
def scan_unique(self, timeout, *, interval=0.1, window=0.1):
57+
def scan(self, timeout, *, interval=0.1, window=0.1):
5858
"""Scan for advertisements from BLE devices. Suppress duplicates
59-
in returned `ScanEntry` objects.
59+
in returned `ScanEntry` objects, so there is only one entry per address (device).
6060
6161
:param int timeout: how long to scan for (in seconds)
6262
:param float interval: the interval (in seconds) between the start
@@ -67,10 +67,11 @@ def scan_unique(self, timeout, *, interval=0.1, window=0.1):
6767
:returns a list of `adafruit_ble.ScanEntry` objects.
6868
6969
"""
70-
return ScanEntry.unique(self.scan(timeout, interval=interval, window=window))
70+
return ScanEntry.unique_devices(self.raw_scan(timeout, interval=interval, window=window))
7171

72-
def scan(self, timeout, *, interval=0.1, window=0.1):
73-
"""Scan for advertisements from BLE devices.
72+
def raw_scan(self, timeout, *, interval=0.1, window=0.1):
73+
"""Scan for advertisements from BLE devices. Include every scan entry,
74+
even duplicates.
7475
7576
:param int timeout: how long to scan for (in seconds)
7677
:param float interval: the interval (in seconds) between the start
@@ -92,63 +93,46 @@ class ScanEntry:
9293
"""
9394

9495
def __init__(self, scan_entry):
95-
self._bleio_scan_entry = scan_entry
96-
97-
def item(self, item_type):
98-
"""Return the bytes in the advertising packet for given the element type.
99-
100-
:param int element_type: An integer designating an element type.
101-
A number are defined in `AdvertisingPacket`, such as `AdvertisingPacket.TX_POWER`.
102-
:returns: bytes that are the value for the given element type.
103-
If the element type is not present in the packet, return ``None``.
104-
"""
105-
i = 0
106-
adv_bytes = self.advertisement_bytes
107-
while i < len(adv_bytes):
108-
item_length = adv_bytes[i]
109-
if item_type != adv_bytes[i+1]:
110-
# Type doesn't match: skip to next item.
111-
i += item_length + 1
112-
else:
113-
return adv_bytes[i + 2:i + 1 + item_length]
114-
return None
96+
self._rssi = scan_entry.rssi
97+
self._address = scan_entry.address
98+
self._packet = AdvertisingPacket(scan_entry.advertisement_bytes)
11599

116100
@property
117-
def advertisement_bytes(self):
118-
"""The raw bytes of the received advertisement."""
119-
return self._bleio_scan_entry.advertisement_bytes
101+
def advertisement_packet(self):
102+
"""The received advertising packet."""
103+
return self._packet
120104

121105
@property
122106
def rssi(self):
123107
"""The signal strength of the device at the time of the scan. (read-only)."""
124-
return self._bleio_scan_entry.rssi
108+
return self._rssi
125109

126110
@property
127111
def address(self):
128112
"""The address of the device. (read-only)."""
129-
return self._bleio_scan_entry.address
113+
return self._address
130114

131115
@property
132116
def name(self):
133117
"""The name of the device. (read-only)"""
134-
name = self.item(AdvertisingPacket.COMPLETE_LOCAL_NAME)
135-
return name if name else self.item(AdvertisingPacket.SHORT_LOCAL_NAME)
118+
name = self._packet.get(AdvertisingPacket.COMPLETE_LOCAL_NAME)
119+
return name if name else self._packet.get(AdvertisingPacket.SHORT_LOCAL_NAME)
136120

137121
@property
138122
def service_uuids(self):
139123
"""List of all the service UUIDs in the advertisement."""
140124
uuid_values = []
141125

142-
concat_uuids = self.item(AdvertisingPacket.ALL_16_BIT_SERVICE_UUIDS)
143-
concat_uuids = concat_uuids if concat_uuids else self.item(
126+
concat_uuids = self._packet.get(AdvertisingPacket.ALL_16_BIT_SERVICE_UUIDS)
127+
concat_uuids = concat_uuids if concat_uuids else self._packet.get(
144128
AdvertisingPacket.SOME_16_BIT_SERVICE_UUIDS)
145129

146130
if concat_uuids:
147131
for i in range(0, len(concat_uuids), 2):
148132
uuid_values.extend(struct.unpack("<H", concat_uuids[i:i+2]))
149133

150-
concat_uuids = self.item(AdvertisingPacket.ALL_128_BIT_SERVICE_UUIDS)
151-
concat_uuids = concat_uuids if concat_uuids else self.item(
134+
concat_uuids = self._packet.get(AdvertisingPacket.ALL_128_BIT_SERVICE_UUIDS)
135+
concat_uuids = concat_uuids if concat_uuids else self._packet.get(
152136
AdvertisingPacket.SOME_128_BIT_SERVICE_UUIDS)
153137

154138
if concat_uuids:
@@ -160,29 +144,30 @@ def service_uuids(self):
160144
@property
161145
def manufacturer_specific_data(self):
162146
"""Manufacturer-specific data in the advertisement, returned as bytes."""
163-
return self.item(AdvertisingPacket.MANUFACTURER_SPECIFIC_DATA)
147+
return self._packet.get(AdvertisingPacket.MANUFACTURER_SPECIFIC_DATA)
164148

165-
def matches(self, other):
149+
def same_device(self, other):
166150
"""True if two scan entries appear to be from the same device. Their
167-
addresses and advertisement_bytes must match.
151+
addresses and advertisement must match.
168152
"""
169153
return (self.address == other.address and
170-
self.advertisement_bytes == other.advertisement_bytes)
154+
self.advertisement_packet.packet_bytes ==
155+
other.advertisement_packet.packet_bytes)
171156

172157
@staticmethod
173158
def with_service_uuid(scan_entries, service_uuid):
174159
"""Return all scan entries advertising the given service_uuid."""
175160
return [se for se in scan_entries if service_uuid in se.service_uuids]
176161

177162
@staticmethod
178-
def unique(scan_entries):
163+
def unique_devices(scan_entries):
179164
"""Discard duplicate scan entries that appear to be from the same device.
180165
181166
:param sequence scan_entries: ScanEntry objects
182167
:returns list: list with duplicates removed
183168
"""
184169
unique = []
185170
for entry in scan_entries:
186-
if not any(entry.matches(unique_entry) for unique_entry in unique):
171+
if not any(entry.same_device(unique_entry) for unique_entry in unique):
187172
unique.append(entry)
188173
return unique

adafruit_ble/uart_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def scan(scanner=None, scan_time=2):
105105
scanner = Scanner()
106106

107107
return [se.address for se in
108-
ScanEntry.with_service_uuid(scanner.scan_unique(scan_time), NUS_SERVICE_UUID)]
108+
ScanEntry.with_service_uuid(scanner.scan(scan_time), NUS_SERVICE_UUID)]
109109

110110
def disconnect(self):
111111
"""Disconnect from the peripheral."""

0 commit comments

Comments
 (0)