@@ -103,6 +103,8 @@ def __init__(self, bit_position):
103
103
self ._bitmask = 1 << bit_position
104
104
105
105
def __get__ (self , obj , cls ):
106
+ if obj is None :
107
+ return self
106
108
return (obj .flags & self ._bitmask ) != 0
107
109
108
110
def __set__ (self , obj , value ):
@@ -214,9 +216,17 @@ def advertising_data_type(self):
214
216
215
217
216
218
class Advertisement :
217
- """Core Advertisement type"""
219
+ """Core Advertisement type.
220
+
221
+ The class attribute ``match_prefixes``, if not ``None``, is a tuple of
222
+ bytestring prefixes to match against the multiple data structures in the advertisement.
223
+ """
224
+
225
+ match_prefixes = ()
226
+ """For Advertisement, `matches` will always return True. Subclasses may override this value."""
227
+ # cached bytes of merged prefixes.
228
+ _prefix_bytes = None
218
229
219
- prefix = b"\x00 " # This is an empty prefix and will match everything.
220
230
flags = LazyObjectField (AdvertisingFlags , "flags" , advertising_data_type = 0x01 )
221
231
short_name = String (advertising_data_type = 0x08 )
222
232
"""Short local device name (shortened to fit)."""
@@ -257,7 +267,11 @@ def from_entry(cls, entry):
257
267
"""Create an Advertisement based on the given ScanEntry. This is done automatically by
258
268
`BLERadio` for all scan results."""
259
269
self = cls ()
260
- self .data_dict = decode_data (entry .advertisement_bytes )
270
+ # If data_dict is available, use it directly. Otherwise decode the bytestring.
271
+ if hasattr (entry , "data_dict" ):
272
+ self .data_dict = entry .data_dict
273
+ else :
274
+ self .data_dict = decode_data (entry .advertisement_bytes )
261
275
self .address = entry .address
262
276
self ._rssi = entry .rssi # pylint: disable=protected-access
263
277
self .connectable = entry .connectable
@@ -271,14 +285,43 @@ def rssi(self):
271
285
from `BLERadio.start_scan()`. (read-only)"""
272
286
return self ._rssi
273
287
288
+ @classmethod
289
+ def get_prefix_bytes (cls ):
290
+ """Return a merged version of match_prefixes as a single bytes object,
291
+ with length headers.
292
+ """
293
+ # Check for deprecated `prefix` class attribute.
294
+ cls ._prefix_bytes = getattr (cls , "prefix" , None )
295
+ # Do merge once and memoize it.
296
+ if cls ._prefix_bytes is None :
297
+ cls ._prefix_bytes = (
298
+ b""
299
+ if cls .match_prefixes is None
300
+ else b"" .join (
301
+ len (prefix ).to_bytes (1 , "little" ) + prefix
302
+ for prefix in cls .match_prefixes
303
+ )
304
+ )
305
+
306
+ return cls ._prefix_bytes
307
+
274
308
@classmethod
275
309
def matches (cls , entry ):
276
- """Returns true if the given `_bleio.ScanEntry` matches all portions of the Advertisement
277
- type's prefix."""
278
- if not hasattr (cls , "prefix" ):
279
- return True
310
+ """Returns ``True`` if the given `_bleio.ScanEntry` advertisement fields
311
+ matches all of the given prefixes in the `match_prefixes` tuple attribute.
312
+ Subclasses may override this to match any instead of all.
313
+ """
314
+ return cls .matches_prefixes (entry , all_ = True )
280
315
281
- return entry .matches (cls .prefix )
316
+ @classmethod
317
+ def matches_prefixes (cls , entry , * , all_ ):
318
+ """Returns ``True`` if the given `_bleio.ScanEntry` advertisement fields
319
+ match any or all of the given prefixes in the `match_prefixes` tuple attribute.
320
+ If ``all_`` is ``True``, all the prefixes must match. If ``all_`` is ``False``,
321
+ returns ``True`` if at least one of the prefixes match.
322
+ """
323
+ # Returns True if cls.get_prefix_bytes() is empty.
324
+ return entry .matches (cls .get_prefix_bytes (), all = all_ )
282
325
283
326
def __bytes__ (self ):
284
327
"""The raw packet bytes."""
0 commit comments