Skip to content

Commit 839fb34

Browse files
committed
Do server advertisement construction in Python; fix numerous bugs
1 parent 04c84be commit 839fb34

File tree

3 files changed

+75
-25
lines changed

3 files changed

+75
-25
lines changed

adafruit_ble/advertising.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
import struct
3333

34-
class AdvertisingData:
34+
class Advertisement:
3535
"""Build up a BLE advertising data packet."""
3636
# BR/EDR flags not included here, since we don't support BR/EDR.
3737
FLAG_LIMITED_DISCOVERY = 0x01
@@ -53,7 +53,7 @@ class AdvertisingData:
5353
"""Complete list of 128 bit service UUIDs."""
5454
SHORT_LOCAL_NAME = 0x08
5555
"""Short local device name (shortened to fit)."""
56-
COMPLETE_LOCALNAME = 0x09
56+
COMPLETE_LOCAL_NAME = 0x09
5757
"""Complete local device name."""
5858
TX_POWER = 0x0A
5959
"""Transmit power level"""
@@ -87,9 +87,13 @@ def __init__(self, flags=(FLAG_GENERAL_DISCOVERY | FLAG_LE_ONLY), max_length=MAX
8787
self._max_length = max_length
8888
self._check_length()
8989

90+
@property
91+
def bytes_remaining(self):
92+
return self._max_length - len(self.data)
93+
9094
def _check_length(self):
9195
if len(self.data) > self._max_length:
92-
raise IndexError("Advertising data exceeds max_length")
96+
raise IndexError("Advertising data too long")
9397

9498
def add_field(self, field_type, field_data):
9599
"""Append an advertising data field to the current packet, of the given type.
@@ -101,12 +105,57 @@ def add_field(self, field_type, field_data):
101105

102106
def add_16_bit_uuids(self, uuids):
103107
"""Add a complete list of 16 bit service UUIDs."""
104-
self.add_field(self.ALL_16_BIT_SERVICE_UUIDS, bytes(uuid.uuid16 for uuid in uuids))
108+
for uuid in uuids:
109+
self.add_field(self.ALL_16_BIT_SERVICE_UUIDS, struct.pack("<H", uuid.uuid16))
105110

106111
def add_128_bit_uuids(self, uuids):
107112
"""Add a complete list of 128 bit service UUIDs."""
108-
self.add_field(self.ALL_128_BIT_SERVICE_UUIDS, bytes(uuid.uuid128 for uuid in uuids))
113+
for uuid in uuids:
114+
self.add_field(self.ALL_128_BIT_SERVICE_UUIDS, uuid.uuid128)
109115

110116
def add_mfr_specific_data(self, mfr_id, data):
111117
"""Add manufacturer-specific data bytes."""
112118
self.add_field(self.MANUFACTURER_SPECIFIC_DATA, struct.pack('<H', mfr_id) + data)
119+
120+
121+
class ServerAdvertisement:
122+
def __init__(self, peripheral, *, tx_power=0):
123+
"""Create an advertisement to advertise a peripheral's services.
124+
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+
"""
128+
129+
self._peripheral = peripheral
130+
131+
adv = Advertisement()
132+
133+
# Need to check service.secondary
134+
uuids_16_bits = [service.uuid for service in peripheral.services
135+
if service.uuid.size == 16 and not service.secondary]
136+
if uuids_16_bits:
137+
adv.add_16_bit_uuids(uuids_16_bits)
138+
139+
uuids_128_bits = [service.uuid for service in peripheral.services
140+
if service.uuid.size == 128 and not service.secondary]
141+
if uuids_128_bits:
142+
adv.add_128_bit_uuids(uuids_128_bits)
143+
144+
adv.add_field(Advertisement.TX_POWER, struct.pack("<b", tx_power))
145+
146+
# 2 bytes needed for field length and type.
147+
bytes_available = adv.bytes_remaining - 2
148+
if bytes_available <= 0:
149+
raise IndexError("No room for name")
150+
151+
name_bytes = bytes(peripheral.name, 'utf-8')
152+
if bytes_available >= len(name_bytes):
153+
adv.add_field(Advertisement.COMPLETE_LOCAL_NAME, name_bytes)
154+
else:
155+
adv.add_field(Advertisement.SHORT_LOCAL_NAME, name_bytes[:bytes_available])
156+
157+
self._advertisement = adv
158+
159+
@property
160+
def data(self):
161+
return self._advertisement.data

adafruit_ble/beacon.py

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

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

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

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

5252
def stop(self):
5353
"""Turn off beacon."""
@@ -81,8 +81,8 @@ def __init__(self, company_id, uuid, major, minor, rssi, interval=1.0):
8181
b.start()
8282
"""
8383

84-
adv_data = AdvertisingData()
85-
adv_data.add_mfr_specific_data(
84+
adv = Advertisement()
85+
adv.add_mfr_specific_data(
8686
company_id,
8787
b''.join((
8888
# 0x02 means a beacon. 0x15 (=21) is length (16 + 2 + 2 + 1)
@@ -91,8 +91,8 @@ def __init__(self, company_id, uuid, major, minor, rssi, interval=1.0):
9191
# iBeacon and similar expect big-endian UUIDS. Usually they are little-endian.
9292
bytes(reversed(uuid.uuid128)),
9393
# major and minor are big-endian.
94-
struct.pack(">HHB", major, minor, rssi))))
95-
super().__init__(adv_data, interval=interval)
94+
struct.pack(">HHb", major, minor, rssi))))
95+
super().__init__(adv, interval=interval)
9696

9797

9898
class EddystoneURLBeacon(Beacon):
@@ -134,8 +134,8 @@ def __init__(self, url, tx_power=0, interval=1.0):
134134
:param float interval: Advertising interval in seconds
135135
"""
136136

137-
adv_data = AdvertisingData()
138-
adv_data.add_field(AdvertisingData.ALL_16_BIT_SERVICE_UUIDS, self._EDDYSTONE_ID)
137+
adv = Advertisement()
138+
adv.add_field(Advertisement.ALL_16_BIT_SERVICE_UUIDS, self._EDDYSTONE_ID)
139139
short_url = None
140140
for idx, prefix in enumerate(self._URL_SCHEMES):
141141
if url.startswith(prefix):
@@ -148,9 +148,9 @@ def __init__(self, url, tx_power=0, interval=1.0):
148148
short_url = short_url.replace(subst + '/', chr(code))
149149
for code, subst in enumerate(self._SUBSTITUTIONS, 7):
150150
short_url = short_url.replace(subst, chr(code))
151-
adv_data.add_field(AdvertisingData.SERVICE_DATA_16_BIT_UUID,
152-
b''.join((self._EDDYSTONE_ID,
153-
b'\x10',
154-
struct.pack("<BB", tx_power, url_scheme_num),
155-
bytes(short_url, 'ascii'))))
156-
super().__init__(adv_data, interval)
151+
adv.add_field(Advertisement.SERVICE_DATA_16_BIT_UUID,
152+
b''.join((self._EDDYSTONE_ID,
153+
b'\x10',
154+
struct.pack("<bB", tx_power, url_scheme_num),
155+
bytes(short_url, 'ascii'))))
156+
super().__init__(adv, interval)

adafruit_ble/uart.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
3030
"""
3131
from bleio import UUID, Characteristic, Service, Peripheral, CharacteristicBuffer
32-
32+
from .advertising import ServerAdvertisement
3333

3434
class UARTServer:
3535
"""
@@ -68,12 +68,13 @@ def __init__(self, timeout=1.0, buffer_size=64):
6868
self._periph = Peripheral((nus_uart_service,))
6969
self._rx_buffer = CharacteristicBuffer(self._nus_rx_char,
7070
timeout=timeout, buffer_size=buffer_size)
71+
self._advertisement = ServerAdvertisement(self._periph)
7172

7273
def start_advertising(self):
7374
"""Start advertising the service. When a client connects, advertising will stop.
7475
When the client disconnects, restart advertising by calling ``start_advertising()`` again.
7576
"""
76-
self._periph.start_advertising()
77+
self._periph.start_advertising(data=self._advertisement.data)
7778

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

0 commit comments

Comments
 (0)