@@ -51,6 +51,10 @@ class AdvertisingPacket:
51
51
"""Incomplete list of 128 bit service UUIDs."""
52
52
ALL_128_BIT_SERVICE_UUIDS = 0x07
53
53
"""Complete list of 128 bit service UUIDs."""
54
+ SOLICITED_16_BIT_SERVICE_UUIDS = 0x14
55
+ """List of 16 bit service UUIDs solicited by a peripheral."""
56
+ SOLICITED_128_BIT_SERVICE_UUIDS = 0x15
57
+ """List of 128 bit service UUIDs solicited by a peripheral."""
54
58
SHORT_LOCAL_NAME = 0x08
55
59
"""Short local device name (shortened to fit)."""
56
60
COMPLETE_LOCAL_NAME = 0x09
@@ -131,101 +135,142 @@ def get(self, element_type, default=None):
131
135
except KeyError :
132
136
return default
133
137
138
+ @property
139
+ def length (self ):
140
+ """Current number of bytes in packet."""
141
+ return len (self ._packet_bytes )
142
+
134
143
@property
135
144
def bytes_remaining (self ):
136
145
"""Number of bytes still available for use in the packet."""
137
- return self ._max_length - len ( self ._packet_bytes )
146
+ return self ._max_length - self .length
138
147
139
148
def _check_length (self ):
140
- if len ( self ._packet_bytes ) > self ._max_length :
149
+ if self .length > self ._max_length :
141
150
raise IndexError ("Advertising data too long" )
142
151
152
+ def add_flags (self , flags = (FLAG_GENERAL_DISCOVERY | FLAG_LE_ONLY )):
153
+ """Add advertising flags."""
154
+ self .add_field (self .FLAGS , struct .pack ("<B" , flags ))
155
+
143
156
def add_field (self , field_type , field_data ):
144
- """Append an advertising data field to the current packet, of the given type.
157
+ """Append byte data to the current packet, of the given type.
145
158
The length field is calculated from the length of field_data."""
146
159
self ._packet_bytes .append (1 + len (field_data ))
147
160
self ._packet_bytes .append (field_type )
148
161
self ._packet_bytes .extend (field_data )
149
162
self ._check_length ()
150
163
151
- def add_flags (self , flags = (FLAG_GENERAL_DISCOVERY | FLAG_LE_ONLY )):
152
- """Add default or custom advertising flags."""
153
- self .add_field (self .FLAGS , struct .pack ("<B" , flags ))
154
-
155
- def add_16_bit_uuids (self , uuids ):
156
- """Add a complete list of 16 bit service UUIDs."""
157
- for uuid in uuids :
158
- self .add_field (self .ALL_16_BIT_SERVICE_UUIDS , struct .pack ("<H" , uuid .uuid16 ))
159
-
160
- def add_128_bit_uuids (self , uuids ):
161
- """Add a complete list of 128 bit service UUIDs."""
162
- for uuid in uuids :
163
- self .add_field (self .ALL_128_BIT_SERVICE_UUIDS , uuid .uuid128 )
164
-
165
164
def add_mfr_specific_data (self , mfr_id , data ):
166
165
"""Add manufacturer-specific data bytes."""
167
166
self .add_field (self .MANUFACTURER_SPECIFIC_DATA , struct .pack ('<H' , mfr_id ) + data )
168
167
168
+ def add_tx_power (self , tx_power ):
169
+ """Add transmit power value."""
170
+ self .add_field (AdvertisingPacket .TX_POWER , struct .pack ("<b" , tx_power ))
169
171
170
- class ServerAdvertisement :
171
- """
172
- Data to advertise a peripheral's services.
172
+ def add_appearance ( self , appearance ) :
173
+ """Add BLE Appearance value. """
174
+ self . add_field ( AdvertisingPacket . APPEARANCE , struct . pack ( "<H" , appearance ))
173
175
174
- The advertisement consists of an advertising data packet and an optional scan response packet,
175
- The scan response packet is created only if there is not room in the
176
- advertising data packet for the complete peripheral name.
177
176
178
- :param peripheral Peripheral the Peripheral to advertise. Use its services and name
179
- :param int tx_power: transmit power in dBm at 0 meters (8 bit signed value). Default 0 dBm
180
- """
181
-
182
- def __init__ (self , peripheral , * , tx_power = 0 ):
183
- self ._peripheral = peripheral
177
+ class Advertisement :
178
+ """Superclass for common code to construct a BLE advertisement,
179
+ consisting of an advertising data packet and an optional scan response packet.
184
180
185
- packet = AdvertisingPacket ()
186
- packet .add_flags ()
181
+ :param int flags: advertising flags. Default is general discovery, and BLE only (not classic)
182
+ """
183
+ def __init__ (self , flags = None , tx_power = None ):
184
+ self ._packet = AdvertisingPacket ()
187
185
self ._scan_response_packet = None
186
+ if flags :
187
+ self ._packet .add_flags (flags )
188
+ else :
189
+ self ._packet .add_flags ()
188
190
189
- # Need to check service.secondary
190
- uuids_16_bits = [service .uuid for service in peripheral .services
191
- if service .uuid .size == 16 and not service .secondary ]
192
- if uuids_16_bits :
193
- packet .add_16_bit_uuids (uuids_16_bits )
194
-
195
- uuids_128_bits = [service .uuid for service in peripheral .services
196
- if service .uuid .size == 128 and not service .secondary ]
197
- if uuids_128_bits :
198
- packet .add_128_bit_uuids (uuids_128_bits )
199
-
200
- packet .add_field (AdvertisingPacket .TX_POWER , struct .pack ("<b" , tx_power ))
191
+ if tx_power is not None :
192
+ self ._packet .add_tx_power (tx_power )
201
193
194
+ def add_name (self , name ):
195
+ """Add name to advertisement. If it doesn't fit, add truncated name to packet,
196
+ and add complete name to scan response packet.
197
+ """
202
198
# 2 bytes needed for field length and type.
203
- bytes_available = packet .bytes_remaining - 2
199
+ bytes_available = self . _packet .bytes_remaining - 2
204
200
if bytes_available <= 0 :
205
201
raise IndexError ("No room for name" )
206
202
207
- name_bytes = bytes (peripheral . name , 'utf-8' )
203
+ name_bytes = bytes (name , 'utf-8' )
208
204
if bytes_available >= len (name_bytes ):
209
- packet .add_field (AdvertisingPacket .COMPLETE_LOCAL_NAME , name_bytes )
205
+ self . _packet .add_field (AdvertisingPacket .COMPLETE_LOCAL_NAME , name_bytes )
210
206
else :
211
- packet .add_field (AdvertisingPacket .SHORT_LOCAL_NAME , name_bytes [:bytes_available ])
207
+ self . _packet .add_field (AdvertisingPacket .SHORT_LOCAL_NAME , name_bytes [:bytes_available ])
212
208
self ._scan_response_packet = AdvertisingPacket ()
213
209
try :
214
210
self ._scan_response_packet .add_field (AdvertisingPacket .COMPLETE_LOCAL_NAME ,
215
211
name_bytes )
216
212
except IndexError :
217
213
raise IndexError ("Name too long" )
218
214
219
- self ._advertising_data_packet = packet
215
+ def add_uuids (self , uuids , field_type_16_bit_uuids , field_type_128_bit_uuids ):
216
+ """Add 16-bit and 128-bit uuids to the packet, using the given field types."""
217
+ concatenated_16_bit_uuids = b'' .join (
218
+ struct .pack ("<H" , uuid .uuid16 ) for uuid in uuids if uuid .size == 16 )
219
+ if concatenated_16_bit_uuids :
220
+ self ._packet .add_field (field_type_16_bit_uuids , concatenated_16_bit_uuids )
221
+
222
+ uuids_128_bits = [uuid for uuid in uuids if uuid .size == 128 ]
223
+ if len (uuids_128_bits ) > 1 :
224
+ raise ValueError ("Only one 128 bit UUID will fit" )
225
+ if uuids_128_bits :
226
+ self ._packet .add_field (field_type_128_bit_uuids , uuids_128_bits [0 ].uuid128 )
220
227
221
228
@property
222
229
def advertising_data_bytes (self ):
223
230
"""The raw bytes for the initial advertising data packet."""
224
- return self ._advertising_data_packet .packet_bytes
231
+ return self ._packet .packet_bytes
225
232
226
233
@property
227
234
def scan_response_bytes (self ):
228
235
"""The raw bytes for the scan response packet. None if there is no response packet."""
229
236
if self ._scan_response_packet :
230
237
return self ._scan_response_packet .packet_bytes
231
238
return None
239
+
240
+
241
+ class ServerAdvertisement (Advertisement ):
242
+ """Build an advertisement for a peripheral's services.
243
+
244
+ There is room in the packet for only one 128-bit UUID. Giving UUIDs in the scan response
245
+ is not yet implemented.
246
+
247
+ :param Peripheral peripheral: the Peripheral to advertise. Use its services and name.
248
+ :param int tx_power: transmit power in dBm at 0 meters (8 bit signed value). Default 0 dBm
249
+ """
250
+
251
+ def __init__ (self , peripheral , * , tx_power = 0 ):
252
+ super ().__init__ ()
253
+ uuids = [service .uuid for service in peripheral .services if not service .secondary ]
254
+ self .add_uuids (uuids ,
255
+ AdvertisingPacket .ALL_16_BIT_SERVICE_UUIDS ,
256
+ AdvertisingPacket .ALL_128_BIT_SERVICE_UUIDS )
257
+ self .add_name (peripheral .name )
258
+
259
+
260
+ class SolicitationAdvertisement (Advertisement ):
261
+ """Build an advertisement for a peripheral to solicit one or more services from a central.
262
+
263
+ There is room in the packet for only one 128-bit UUID. Giving UUIDs in the scan response
264
+ is not yet implemented.
265
+
266
+ :param string name: Name to use in advertisement.
267
+ :param iterable service_uuids: One or more services requested from a central
268
+ :param int tx_power: transmit power in dBm at 0 meters (8 bit signed value). Default 0 dBm.
269
+ """
270
+
271
+ def __init__ (self , name , service_uuids , * , tx_power = 0 ):
272
+ super ().__init__ ()
273
+ self .add_uuids (service_uuids ,
274
+ AdvertisingPacket .SOLICITED_16_BIT_SERVICE_UUIDS ,
275
+ AdvertisingPacket .SOLICITED_128_BIT_SERVICE_UUIDS )
276
+ self .add_name (name )
0 commit comments