2525
2626import struct
2727
28+
2829def to_hex (seq ):
2930 """Pretty prints a byte sequence as hex values."""
3031 return " " .join ("{:02x}" .format (v ) for v in seq )
3132
33+
3234def to_bytes_literal (seq ):
3335 """Prints a byte sequence as a Python bytes literal that only uses hex encoding."""
34- return "b\" " + "" .join ("\\ x{:02x}" .format (v ) for v in seq ) + "\" "
36+ return 'b"' + "" .join ("\\ x{:02x}" .format (v ) for v in seq ) + '"'
37+
3538
3639def decode_data (data , * , key_encoding = "B" ):
3740 """Helper which decodes length encoded structures into a dictionary with the given key
@@ -45,7 +48,7 @@ def decode_data(data, *, key_encoding="B"):
4548 if item_length == 0 :
4649 break
4750 key = struct .unpack_from (key_encoding , data , i )[0 ]
48- value = data [i + key_size : i + item_length ]
51+ value = data [i + key_size : i + item_length ]
4952 if key in data_dict :
5053 if not isinstance (data_dict [key ], list ):
5154 data_dict [key ] = [data_dict [key ]]
@@ -55,6 +58,7 @@ def decode_data(data, *, key_encoding="B"):
5558 i += item_length
5659 return data_dict
5760
61+
5862def compute_length (data_dict , * , key_encoding = "B" ):
5963 """Computes the length of the encoded data dictionary."""
6064 value_size = 0
@@ -66,6 +70,7 @@ def compute_length(data_dict, *, key_encoding="B"):
6670 value_size += len (value )
6771 return len (data_dict ) + len (data_dict ) * struct .calcsize (key_encoding ) + value_size
6872
73+
6974def encode_data (data_dict , * , key_encoding = "B" ):
7075 """Helper which encodes dictionaries into length encoded structures with the given key
7176 encoding."""
@@ -79,17 +84,21 @@ def encode_data(data_dict, *, key_encoding="B"):
7984 item_length = key_size + len (value )
8085 struct .pack_into ("B" , data , i , item_length )
8186 struct .pack_into (key_encoding , data , i + 1 , key )
82- data [i + 1 + key_size : i + 1 + item_length ] = bytes (value )
87+ data [i + 1 + key_size : i + 1 + item_length ] = bytes (value )
8388 i += 1 + item_length
8489 return data
8590
91+
8692class AdvertisingDataField :
8793 """Top level class for any descriptor classes that live in Advertisement or its subclasses."""
94+
8895 # pylint: disable=too-few-public-methods,unnecessary-pass
8996 pass
9097
98+
9199class AdvertisingFlag :
92100 """A single bit flag within an AdvertisingFlags object."""
101+
93102 def __init__ (self , bit_position ):
94103 self ._bitmask = 1 << bit_position
95104
@@ -102,6 +111,7 @@ def __set__(self, obj, value):
102111 else :
103112 obj .flags &= ~ self ._bitmask
104113
114+
105115class AdvertisingFlags (AdvertisingDataField ):
106116 """Standard advertising flags"""
107117
@@ -135,10 +145,12 @@ def __str__(self):
135145 parts .append (attr )
136146 return "<AdvertisingFlags {} >" .format (" " .join (parts ))
137147
148+
138149class String (AdvertisingDataField ):
139150 """UTF-8 encoded string in an Advertisement.
140151
141152 Not null terminated once encoded because length is always transmitted."""
153+
142154 def __init__ (self , * , advertising_data_type ):
143155 self ._adt = advertising_data_type
144156
@@ -152,8 +164,10 @@ def __get__(self, obj, cls):
152164 def __set__ (self , obj , value ):
153165 obj .data_dict [self ._adt ] = value .encode ("utf-8" )
154166
167+
155168class Struct (AdvertisingDataField ):
156169 """`struct` encoded data in an Advertisement."""
170+
157171 def __init__ (self , struct_format , * , advertising_data_type ):
158172 self ._format = struct_format
159173 self ._adt = advertising_data_type
@@ -171,6 +185,7 @@ def __set__(self, obj, value):
171185
172186class LazyObjectField (AdvertisingDataField ):
173187 """Non-data descriptor useful for lazily binding a complex object to an advertisement object."""
188+
174189 def __init__ (self , cls , attribute_name , * , advertising_data_type , ** kwargs ):
175190 self ._cls = cls
176191 self ._attribute_name = attribute_name
@@ -197,15 +212,17 @@ def advertising_data_type(self):
197212 # TODO: Add __set_name__ support to CircuitPython so that we automatically tell the descriptor
198213 # instance the attribute name it has and the class it is on.
199214
215+
200216class Advertisement :
201217 """Core Advertisement type"""
202- prefix = b"\x00 " # This is an empty prefix and will match everything.
218+
219+ prefix = b"\x00 " # This is an empty prefix and will match everything.
203220 flags = LazyObjectField (AdvertisingFlags , "flags" , advertising_data_type = 0x01 )
204221 short_name = String (advertising_data_type = 0x08 )
205222 """Short local device name (shortened to fit)."""
206223 complete_name = String (advertising_data_type = 0x09 )
207224 """Complete local device name."""
208- tx_power = Struct ("<b" , advertising_data_type = 0x0a )
225+ tx_power = Struct ("<b" , advertising_data_type = 0x0A )
209226 """Transmit power level"""
210227 # DEVICE_ID = 0x10
211228 # """Device identifier."""
@@ -242,7 +259,7 @@ def from_entry(cls, entry):
242259 self = cls ()
243260 self .data_dict = decode_data (entry .advertisement_bytes )
244261 self .address = entry .address
245- self ._rssi = entry .rssi # pylint: disable=protected-access
262+ self ._rssi = entry .rssi # pylint: disable=protected-access
246263 self .connectable = entry .connectable
247264 self .scan_response = entry .scan_response
248265 self .mutable = False
@@ -272,8 +289,10 @@ def __str__(self):
272289 for attr in dir (self .__class__ ):
273290 attribute_instance = getattr (self .__class__ , attr )
274291 if issubclass (attribute_instance .__class__ , AdvertisingDataField ):
275- if (issubclass (attribute_instance .__class__ , LazyObjectField ) and
276- not attribute_instance .advertising_data_type in self .data_dict ):
292+ if (
293+ issubclass (attribute_instance .__class__ , LazyObjectField )
294+ and not attribute_instance .advertising_data_type in self .data_dict
295+ ):
277296 # Skip uninstantiated lazy objects; if we get
278297 # their value, they will be be instantiated.
279298 continue
@@ -286,4 +305,6 @@ def __len__(self):
286305 return compute_length (self .data_dict )
287306
288307 def __repr__ (self ):
289- return "Advertisement(data={})" .format (to_bytes_literal (encode_data (self .data_dict )))
308+ return "Advertisement(data={})" .format (
309+ to_bytes_literal (encode_data (self .data_dict ))
310+ )
0 commit comments