Skip to content

Commit bc8b7f3

Browse files
committed
1. To accomodate scan response packets:
Rename Advertisement to AdvertisingPacket. Make adding advertising flags is now done explicitly by callers. 2. ServerAdvertisement creates a scan response packet to send the the full name, if it doesn't fit in the initial advertising data packet. 3. Allow UARTServer to specify peripheral name.
1 parent 839fb34 commit bc8b7f3

File tree

3 files changed

+77
-43
lines changed

3 files changed

+77
-43
lines changed

adafruit_ble/advertising.py

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131

3232
import struct
3333

34-
class Advertisement:
35-
"""Build up a BLE advertising data packet."""
34+
class AdvertisingPacket:
35+
"""Build up a BLE advertising data or scan response packet."""
3636
# BR/EDR flags not included here, since we don't support BR/EDR.
3737
FLAG_LIMITED_DISCOVERY = 0x01
3838
"""Discoverable only for a limited time period."""
@@ -81,28 +81,38 @@ class Advertisement:
8181
MAX_DATA_SIZE = 31
8282
"""Data size in a regular BLE packet."""
8383

84-
def __init__(self, flags=(FLAG_GENERAL_DISCOVERY | FLAG_LE_ONLY), max_length=MAX_DATA_SIZE):
85-
"""Initalize an advertising packet, with the given flags, no larger than max_length."""
86-
self.data = bytearray((2, self.FLAGS, flags))
84+
def __init__(self, *, max_length=MAX_DATA_SIZE):
85+
"""Create an empty advertising packet, no larger than max_length."""
86+
self._packet_bytes = bytearray()
8787
self._max_length = max_length
8888
self._check_length()
8989

90+
@property
91+
def packet_bytes(self):
92+
"""The raw packet bytes."""
93+
return self._packet_bytes
94+
9095
@property
9196
def bytes_remaining(self):
92-
return self._max_length - len(self.data)
97+
"""Number of bytes still available for use in the packet."""
98+
return self._max_length - len(self._packet_bytes)
9399

94100
def _check_length(self):
95-
if len(self.data) > self._max_length:
101+
if len(self._packet_bytes) > self._max_length:
96102
raise IndexError("Advertising data too long")
97103

98104
def add_field(self, field_type, field_data):
99105
"""Append an advertising data field to the current packet, of the given type.
100106
The length field is calculated from the length of field_data."""
101-
self.data.append(1 + len(field_data))
102-
self.data.append(field_type)
103-
self.data.extend(field_data)
107+
self._packet_bytes.append(1 + len(field_data))
108+
self._packet_bytes.append(field_type)
109+
self._packet_bytes.extend(field_data)
104110
self._check_length()
105111

112+
def add_flags(self, flags=(FLAG_GENERAL_DISCOVERY | FLAG_LE_ONLY)):
113+
"""Add default or custom advertising flags."""
114+
self.add_field(self.FLAGS, struct.pack("<B", flags))
115+
106116
def add_16_bit_uuids(self, uuids):
107117
"""Add a complete list of 16 bit service UUIDs."""
108118
for uuid in uuids:
@@ -119,43 +129,64 @@ def add_mfr_specific_data(self, mfr_id, data):
119129

120130

121131
class ServerAdvertisement:
122-
def __init__(self, peripheral, *, tx_power=0):
123-
"""Create an advertisement to advertise a peripheral's services.
132+
"""
133+
Data to advertise a peripheral's services.
124134
125-
:param peripheral Peripheral the Peripheral to advertise. Use its services and name
126-
:param int tx_power: transmit power in dBm at 0 meters (8 bit signed value). Default 0 dBm
127-
"""
135+
The advertisement consists of an advertising data packet and an optional scan response packet,
136+
The scan response packet is created only if there is not room in the
137+
advertising data packet for the complete peripheral name.
128138
139+
:param peripheral Peripheral the Peripheral to advertise. Use its services and name
140+
:param int tx_power: transmit power in dBm at 0 meters (8 bit signed value). Default 0 dBm
141+
"""
142+
143+
def __init__(self, peripheral, *, tx_power=0):
129144
self._peripheral = peripheral
130145

131-
adv = Advertisement()
146+
packet = AdvertisingPacket()
147+
packet.add_flags()
148+
self._scan_response_packet = None
132149

133150
# Need to check service.secondary
134151
uuids_16_bits = [service.uuid for service in peripheral.services
135152
if service.uuid.size == 16 and not service.secondary]
136153
if uuids_16_bits:
137-
adv.add_16_bit_uuids(uuids_16_bits)
154+
packet.add_16_bit_uuids(uuids_16_bits)
138155

139156
uuids_128_bits = [service.uuid for service in peripheral.services
140-
if service.uuid.size == 128 and not service.secondary]
157+
if service.uuid.size == 128 and not service.secondary]
141158
if uuids_128_bits:
142-
adv.add_128_bit_uuids(uuids_128_bits)
159+
packet.add_128_bit_uuids(uuids_128_bits)
143160

144-
adv.add_field(Advertisement.TX_POWER, struct.pack("<b", tx_power))
161+
packet.add_field(AdvertisingPacket.TX_POWER, struct.pack("<b", tx_power))
145162

146163
# 2 bytes needed for field length and type.
147-
bytes_available = adv.bytes_remaining - 2
164+
bytes_available = packet.bytes_remaining - 2
148165
if bytes_available <= 0:
149166
raise IndexError("No room for name")
150167

151168
name_bytes = bytes(peripheral.name, 'utf-8')
152169
if bytes_available >= len(name_bytes):
153-
adv.add_field(Advertisement.COMPLETE_LOCAL_NAME, name_bytes)
170+
packet.add_field(AdvertisingPacket.COMPLETE_LOCAL_NAME, name_bytes)
154171
else:
155-
adv.add_field(Advertisement.SHORT_LOCAL_NAME, name_bytes[:bytes_available])
172+
packet.add_field(AdvertisingPacket.SHORT_LOCAL_NAME, name_bytes[:bytes_available])
173+
self._scan_response_packet = AdvertisingPacket()
174+
try:
175+
self._scan_response_packet.add_field(AdvertisingPacket.COMPLETE_LOCAL_NAME,
176+
name_bytes)
177+
except IndexError:
178+
raise IndexError("Name too long")
156179

157-
self._advertisement = adv
180+
self._advertising_data_packet = packet
181+
182+
@property
183+
def advertising_data_bytes(self):
184+
"""The raw bytes for the initial advertising data packet."""
185+
return self._advertising_data_packet.packet_bytes
158186

159187
@property
160-
def data(self):
161-
return self._advertisement.data
188+
def scan_response_bytes(self):
189+
"""The raw bytes for the scan response packet. None if there is no response packet."""
190+
if self._scan_response_packet:
191+
return self._scan_response_packet.packet_bytes
192+
return None

adafruit_ble/beacon.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,26 @@
3232
import struct
3333
import bleio
3434

35-
from .advertising import Advertisement
35+
from .advertising import AdvertisingPacket
3636

3737
class Beacon:
3838
"""Base class for Beacon advertisers."""
39-
def __init__(self, advertisement, interval=1.0):
40-
"""Set up a beacon with the given Advertisement.
39+
def __init__(self, advertising_packet, interval=1.0):
40+
"""Set up a beacon with the given AdvertisingPacket.
4141
42-
:param Advertisement advertisement: The advertising packet
42+
:param AdvertisingPacket advertising_packet
4343
:param float interval: Advertising interval in seconds
4444
"""
45-
self.broadcaster = bleio.Broadcaster(interval)
46-
self.advertisement = advertisement
45+
self._broadcaster = bleio.Broadcaster(interval)
46+
self._advertising_packet = advertising_packet
4747

4848
def start(self):
4949
"""Turn on beacon."""
50-
self.broadcaster.start_advertising(self.advertisement.data)
50+
self._broadcaster.start_advertising(self._advertising_packet.packet_bytes)
5151

5252
def stop(self):
5353
"""Turn off beacon."""
54-
self.broadcaster.stop_advertising()
54+
self._broadcaster.stop_advertising()
5555

5656

5757

@@ -81,7 +81,8 @@ def __init__(self, company_id, uuid, major, minor, rssi, interval=1.0):
8181
b.start()
8282
"""
8383

84-
adv = Advertisement()
84+
adv = AdvertisingPacket()
85+
adv.add_flags()
8586
adv.add_mfr_specific_data(
8687
company_id,
8788
b''.join((
@@ -131,11 +132,12 @@ def __init__(self, url, tx_power=0, interval=1.0):
131132
132133
:param url URL to encode. Must be short enough to fit after encoding.
133134
:param int tx_power: transmit power in dBm at 0 meters (8 bit signed value)
134-
:param float interval: Advertising interval in seconds
135+
:param float interval: advertising interval in seconds
135136
"""
136137

137-
adv = Advertisement()
138-
adv.add_field(Advertisement.ALL_16_BIT_SERVICE_UUIDS, self._EDDYSTONE_ID)
138+
adv = AdvertisingPacket()
139+
adv.add_flags()
140+
adv.add_field(AdvertisingPacket.ALL_16_BIT_SERVICE_UUIDS, self._EDDYSTONE_ID)
139141
short_url = None
140142
for idx, prefix in enumerate(self._URL_SCHEMES):
141143
if url.startswith(prefix):
@@ -148,7 +150,7 @@ def __init__(self, url, tx_power=0, interval=1.0):
148150
short_url = short_url.replace(subst + '/', chr(code))
149151
for code, subst in enumerate(self._SUBSTITUTIONS, 7):
150152
short_url = short_url.replace(subst, chr(code))
151-
adv.add_field(Advertisement.SERVICE_DATA_16_BIT_UUID,
153+
adv.add_field(AdvertisingPacket.SERVICE_DATA_16_BIT_UUID,
152154
b''.join((self._EDDYSTONE_ID,
153155
b'\x10',
154156
struct.pack("<bB", tx_power, url_scheme_num),

adafruit_ble/uart.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class UARTServer:
3939
for the first character and between subsequent characters.
4040
:param int buffer_size: buffer up to this many bytes.
4141
If more bytes are received, older bytes will be discarded.
42+
:param str name: Name to advertise for server. If None, use default Peripheral name.
4243
4344
Example::
4445
@@ -57,15 +58,14 @@ class UARTServer:
5758
NUS_RX_CHAR_UUID = UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
5859
NUS_TX_CHAR_UUID = UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
5960

60-
def __init__(self, timeout=1.0, buffer_size=64):
61-
"""Define the NUS service and start advertising it."""
61+
def __init__(self, *, timeout=1.0, buffer_size=64, name=None):
6262
self._nus_tx_char = Characteristic(self.NUS_TX_CHAR_UUID, notify=True)
6363
self._nus_rx_char = Characteristic(self.NUS_RX_CHAR_UUID,
6464
write=True, write_no_response=True)
6565

6666
nus_uart_service = Service(self.NUS_SERVICE_UUID, (self._nus_tx_char, self._nus_rx_char))
6767

68-
self._periph = Peripheral((nus_uart_service,))
68+
self._periph = Peripheral((nus_uart_service,), name=name)
6969
self._rx_buffer = CharacteristicBuffer(self._nus_rx_char,
7070
timeout=timeout, buffer_size=buffer_size)
7171
self._advertisement = ServerAdvertisement(self._periph)
@@ -74,7 +74,8 @@ def start_advertising(self):
7474
"""Start advertising the service. When a client connects, advertising will stop.
7575
When the client disconnects, restart advertising by calling ``start_advertising()`` again.
7676
"""
77-
self._periph.start_advertising(data=self._advertisement.data)
77+
self._periph.start_advertising(data=self._advertisement.advertising_data_bytes,
78+
scan_response=self._advertisement.scan_response_bytes)
7879

7980
def stop_advertising(self):
8081
"""Stop advertising the service."""

0 commit comments

Comments
 (0)