@@ -103,6 +103,8 @@ def __init__(self, bit_position):
103103 self ._bitmask = 1 << bit_position
104104
105105 def __get__ (self , obj , cls ):
106+ if obj is None :
107+ return self
106108 return (obj .flags & self ._bitmask ) != 0
107109
108110 def __set__ (self , obj , value ):
@@ -214,9 +216,17 @@ def advertising_data_type(self):
214216
215217
216218class 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
218229
219- prefix = b"\x00 " # This is an empty prefix and will match everything.
220230 flags = LazyObjectField (AdvertisingFlags , "flags" , advertising_data_type = 0x01 )
221231 short_name = String (advertising_data_type = 0x08 )
222232 """Short local device name (shortened to fit)."""
@@ -257,7 +267,11 @@ def from_entry(cls, entry):
257267 """Create an Advertisement based on the given ScanEntry. This is done automatically by
258268 `BLERadio` for all scan results."""
259269 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 )
261275 self .address = entry .address
262276 self ._rssi = entry .rssi # pylint: disable=protected-access
263277 self .connectable = entry .connectable
@@ -271,14 +285,43 @@ def rssi(self):
271285 from `BLERadio.start_scan()`. (read-only)"""
272286 return self ._rssi
273287
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+
274308 @classmethod
275309 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 )
280315
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_ )
282325
283326 def __bytes__ (self ):
284327 """The raw packet bytes."""
0 commit comments