25
25
26
26
import struct
27
27
28
+
28
29
def to_hex (seq ):
29
30
"""Pretty prints a byte sequence as hex values."""
30
31
return " " .join ("{:02x}" .format (v ) for v in seq )
31
32
33
+
32
34
def to_bytes_literal (seq ):
33
35
"""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
+
35
38
36
39
def decode_data (data , * , key_encoding = "B" ):
37
40
"""Helper which decodes length encoded structures into a dictionary with the given key
@@ -45,7 +48,7 @@ def decode_data(data, *, key_encoding="B"):
45
48
if item_length == 0 :
46
49
break
47
50
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 ]
49
52
if key in data_dict :
50
53
if not isinstance (data_dict [key ], list ):
51
54
data_dict [key ] = [data_dict [key ]]
@@ -55,6 +58,7 @@ def decode_data(data, *, key_encoding="B"):
55
58
i += item_length
56
59
return data_dict
57
60
61
+
58
62
def compute_length (data_dict , * , key_encoding = "B" ):
59
63
"""Computes the length of the encoded data dictionary."""
60
64
value_size = 0
@@ -66,6 +70,7 @@ def compute_length(data_dict, *, key_encoding="B"):
66
70
value_size += len (value )
67
71
return len (data_dict ) + len (data_dict ) * struct .calcsize (key_encoding ) + value_size
68
72
73
+
69
74
def encode_data (data_dict , * , key_encoding = "B" ):
70
75
"""Helper which encodes dictionaries into length encoded structures with the given key
71
76
encoding."""
@@ -79,17 +84,21 @@ def encode_data(data_dict, *, key_encoding="B"):
79
84
item_length = key_size + len (value )
80
85
struct .pack_into ("B" , data , i , item_length )
81
86
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 )
83
88
i += 1 + item_length
84
89
return data
85
90
91
+
86
92
class AdvertisingDataField :
87
93
"""Top level class for any descriptor classes that live in Advertisement or its subclasses."""
94
+
88
95
# pylint: disable=too-few-public-methods,unnecessary-pass
89
96
pass
90
97
98
+
91
99
class AdvertisingFlag :
92
100
"""A single bit flag within an AdvertisingFlags object."""
101
+
93
102
def __init__ (self , bit_position ):
94
103
self ._bitmask = 1 << bit_position
95
104
@@ -102,6 +111,7 @@ def __set__(self, obj, value):
102
111
else :
103
112
obj .flags &= ~ self ._bitmask
104
113
114
+
105
115
class AdvertisingFlags (AdvertisingDataField ):
106
116
"""Standard advertising flags"""
107
117
@@ -135,10 +145,12 @@ def __str__(self):
135
145
parts .append (attr )
136
146
return "<AdvertisingFlags {} >" .format (" " .join (parts ))
137
147
148
+
138
149
class String (AdvertisingDataField ):
139
150
"""UTF-8 encoded string in an Advertisement.
140
151
141
152
Not null terminated once encoded because length is always transmitted."""
153
+
142
154
def __init__ (self , * , advertising_data_type ):
143
155
self ._adt = advertising_data_type
144
156
@@ -152,8 +164,10 @@ def __get__(self, obj, cls):
152
164
def __set__ (self , obj , value ):
153
165
obj .data_dict [self ._adt ] = value .encode ("utf-8" )
154
166
167
+
155
168
class Struct (AdvertisingDataField ):
156
169
"""`struct` encoded data in an Advertisement."""
170
+
157
171
def __init__ (self , struct_format , * , advertising_data_type ):
158
172
self ._format = struct_format
159
173
self ._adt = advertising_data_type
@@ -171,6 +185,7 @@ def __set__(self, obj, value):
171
185
172
186
class LazyObjectField (AdvertisingDataField ):
173
187
"""Non-data descriptor useful for lazily binding a complex object to an advertisement object."""
188
+
174
189
def __init__ (self , cls , attribute_name , * , advertising_data_type , ** kwargs ):
175
190
self ._cls = cls
176
191
self ._attribute_name = attribute_name
@@ -197,15 +212,17 @@ def advertising_data_type(self):
197
212
# TODO: Add __set_name__ support to CircuitPython so that we automatically tell the descriptor
198
213
# instance the attribute name it has and the class it is on.
199
214
215
+
200
216
class Advertisement :
201
217
"""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.
203
220
flags = LazyObjectField (AdvertisingFlags , "flags" , advertising_data_type = 0x01 )
204
221
short_name = String (advertising_data_type = 0x08 )
205
222
"""Short local device name (shortened to fit)."""
206
223
complete_name = String (advertising_data_type = 0x09 )
207
224
"""Complete local device name."""
208
- tx_power = Struct ("<b" , advertising_data_type = 0x0a )
225
+ tx_power = Struct ("<b" , advertising_data_type = 0x0A )
209
226
"""Transmit power level"""
210
227
# DEVICE_ID = 0x10
211
228
# """Device identifier."""
@@ -242,7 +259,7 @@ def from_entry(cls, entry):
242
259
self = cls ()
243
260
self .data_dict = decode_data (entry .advertisement_bytes )
244
261
self .address = entry .address
245
- self ._rssi = entry .rssi # pylint: disable=protected-access
262
+ self ._rssi = entry .rssi # pylint: disable=protected-access
246
263
self .connectable = entry .connectable
247
264
self .scan_response = entry .scan_response
248
265
self .mutable = False
@@ -272,8 +289,10 @@ def __str__(self):
272
289
for attr in dir (self .__class__ ):
273
290
attribute_instance = getattr (self .__class__ , attr )
274
291
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
+ ):
277
296
# Skip uninstantiated lazy objects; if we get
278
297
# their value, they will be be instantiated.
279
298
continue
@@ -286,4 +305,6 @@ def __len__(self):
286
305
return compute_length (self .data_dict )
287
306
288
307
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