Skip to content

Commit 40f3a56

Browse files
committed
feat(mesh): add BLE advertisement mesh bearer scaffold
1 parent 439b87b commit 40f3a56

12 files changed

Lines changed: 774 additions & 8 deletions

File tree

src/configuration.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
423423
#ifndef HAS_BLUETOOTH
424424
#define HAS_BLUETOOTH 0
425425
#endif
426+
#ifndef HAS_BLE_MESH_ADVERTISING
427+
// Keep the experimental BLE advertisement bearer opt-in by platform capability:
428+
// it currently needs a NimBLE2 build with a spare legacy advertising instance.
429+
// Bluefruit/nRF52 builds compile the shared codec but leave the bearer disabled
430+
// until there is a backend that can scan and advertise without disturbing the
431+
// normal phone GATT advertising path.
432+
#if HAS_BLUETOOTH && defined(ARCH_ESP32) && defined(NIMBLE_TWO) && defined(CONFIG_BT_NIMBLE_EXT_ADV) && \
433+
CONFIG_BT_NIMBLE_EXT_ADV && defined(CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES) && CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES > 1
434+
#define HAS_BLE_MESH_ADVERTISING 1
435+
#else
436+
#define HAS_BLE_MESH_ADVERTISING 0
437+
#endif
438+
#endif
439+
#ifndef BLE_MESH_ADVERTISEMENT_COMPANY_ID
440+
// Experimental company id used by the BLE advertisement mesh bearer. Builds
441+
// can override this once Meshtastic chooses a final Bluetooth SIG/company-data
442+
// allocation or switches the bearer to service data.
443+
#define BLE_MESH_ADVERTISEMENT_COMPANY_ID 0x05e1
444+
#endif
426445
#ifndef USE_TFTDISPLAY
427446
#define USE_TFTDISPLAY 0
428447
#endif

src/mesh/Router.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#if !MESHTASTIC_EXCLUDE_MQTT
1515
#include "mqtt/MQTT.h"
1616
#endif
17+
#if HAS_BLE_MESH_ADVERTISING
18+
#include "mesh/ble/BleAdvertisementMesh.h"
19+
#endif
1720
#include "Default.h"
1821
#if ARCH_PORTDUINO
1922
#include "Throttle.h"
@@ -381,6 +384,13 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
381384
}
382385
#endif
383386

387+
#if HAS_BLE_MESH_ADVERTISING
388+
if (bleAdvertisementMesh &&
389+
config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_BLE_ADVERTISEMENT_BROADCAST) {
390+
bleAdvertisementMesh->onSend(p);
391+
}
392+
#endif
393+
384394
assert(iface); // This should have been detected already in sendLocal (or we just received a packet from outside)
385395
return iface->send(p);
386396
}
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
#include "configuration.h"
2+
#include "mesh/ble/BleAdvertisementMesh.h"
3+
#include "mesh/mesh-pb-constants.h"
4+
5+
#if HAS_BLE_MESH_ADVERTISING
6+
#include "Router.h"
7+
#include "main.h"
8+
#endif
9+
#include <pb_decode.h>
10+
#include <pb_encode.h>
11+
#include <string.h>
12+
13+
namespace
14+
{
15+
uint16_t crc16ccitt(const uint8_t *data, size_t length)
16+
{
17+
uint16_t crc = 0xffff;
18+
for (size_t i = 0; i < length; i++) {
19+
crc ^= static_cast<uint16_t>(data[i]) << 8;
20+
for (uint8_t bit = 0; bit < 8; bit++) {
21+
crc = (crc & 0x8000) ? static_cast<uint16_t>((crc << 1) ^ 0x1021) : static_cast<uint16_t>(crc << 1);
22+
}
23+
}
24+
return crc;
25+
}
26+
27+
void putLe16(uint8_t *p, uint16_t v)
28+
{
29+
p[0] = static_cast<uint8_t>(v);
30+
p[1] = static_cast<uint8_t>(v >> 8);
31+
}
32+
33+
void putLe32(uint8_t *p, uint32_t v)
34+
{
35+
p[0] = static_cast<uint8_t>(v);
36+
p[1] = static_cast<uint8_t>(v >> 8);
37+
p[2] = static_cast<uint8_t>(v >> 16);
38+
p[3] = static_cast<uint8_t>(v >> 24);
39+
}
40+
41+
uint16_t getLe16(const uint8_t *p)
42+
{
43+
return static_cast<uint16_t>(p[0]) | (static_cast<uint16_t>(p[1]) << 8);
44+
}
45+
46+
uint32_t getLe32(const uint8_t *p)
47+
{
48+
return static_cast<uint32_t>(p[0]) | (static_cast<uint32_t>(p[1]) << 8) | (static_cast<uint32_t>(p[2]) << 16) |
49+
(static_cast<uint32_t>(p[3]) << 24);
50+
}
51+
} // namespace
52+
53+
BleAdvertisementMeshCodec::BleAdvertisementMeshCodec() {}
54+
55+
uint8_t BleAdvertisementMeshCodec::frameCount(const meshtastic_MeshPacket *mp)
56+
{
57+
if (!mp || mp->which_payload_variant != meshtastic_MeshPacket_encrypted_tag) {
58+
return 0;
59+
}
60+
61+
uint8_t encoded[meshtastic_MeshPacket_size] = {0};
62+
size_t encodedLength = pb_encode_to_bytes(encoded, sizeof(encoded), &meshtastic_MeshPacket_msg, mp);
63+
if (encodedLength == 0) {
64+
return 0;
65+
}
66+
67+
return (encodedLength + MAX_FRAGMENT_PAYLOAD_SIZE - 1) / MAX_FRAGMENT_PAYLOAD_SIZE;
68+
}
69+
70+
bool BleAdvertisementMeshCodec::forEachFrame(const meshtastic_MeshPacket *mp, EmitFrame emit, void *context)
71+
{
72+
if (!mp || !emit || mp->which_payload_variant != meshtastic_MeshPacket_encrypted_tag) {
73+
return false;
74+
}
75+
76+
uint8_t encoded[meshtastic_MeshPacket_size] = {0};
77+
size_t encodedLength = pb_encode_to_bytes(encoded, sizeof(encoded), &meshtastic_MeshPacket_msg, mp);
78+
if (encodedLength == 0) {
79+
return false;
80+
}
81+
82+
uint8_t fragmentCount = (encodedLength + MAX_FRAGMENT_PAYLOAD_SIZE - 1) / MAX_FRAGMENT_PAYLOAD_SIZE;
83+
if (fragmentCount == 0 || fragmentCount > MAX_FRAGMENTS) {
84+
return false;
85+
}
86+
87+
uint16_t crc = crc16ccitt(encoded, encodedLength);
88+
for (uint8_t fragment = 0; fragment < fragmentCount; fragment++) {
89+
size_t offset = fragment * MAX_FRAGMENT_PAYLOAD_SIZE;
90+
size_t remaining = encodedLength - offset;
91+
uint8_t payloadLength =
92+
static_cast<uint8_t>(remaining > MAX_FRAGMENT_PAYLOAD_SIZE ? MAX_FRAGMENT_PAYLOAD_SIZE : remaining);
93+
94+
uint8_t frame[MAX_FRAME_SIZE] = {0};
95+
frame[0] = FRAME_MAGIC_0;
96+
frame[1] = FRAME_MAGIC_1;
97+
frame[2] = FRAME_VERSION;
98+
frame[3] = fragment;
99+
frame[4] = fragmentCount;
100+
frame[5] = payloadLength;
101+
putLe32(&frame[6], mp->from);
102+
putLe32(&frame[10], mp->id);
103+
putLe16(&frame[14], crc);
104+
memcpy(&frame[FRAME_HEADER_SIZE], &encoded[offset], payloadLength);
105+
106+
if (!emit(frame, FRAME_HEADER_SIZE + payloadLength, context)) {
107+
return false;
108+
}
109+
}
110+
111+
return true;
112+
}
113+
114+
void BleAdvertisementMeshCodec::reset(uint32_t from, uint32_t packetId, uint16_t crc, uint8_t fragmentCount)
115+
{
116+
currentFrom = from;
117+
currentPacketId = packetId;
118+
currentCrc = crc;
119+
expectedFragments = fragmentCount;
120+
lastFragmentLength = 0;
121+
receivedMask = 0;
122+
memset(encodedPacket, 0, sizeof(encodedPacket));
123+
}
124+
125+
bool BleAdvertisementMeshCodec::receiveFrame(const uint8_t *frame, size_t frameLength, meshtastic_MeshPacket *out)
126+
{
127+
if (!frame || !out || frameLength < FRAME_HEADER_SIZE || frameLength > MAX_FRAME_SIZE || frame[0] != FRAME_MAGIC_0 ||
128+
frame[1] != FRAME_MAGIC_1 || frame[2] != FRAME_VERSION) {
129+
return false;
130+
}
131+
132+
uint8_t fragment = frame[3];
133+
uint8_t fragmentCount = frame[4];
134+
uint8_t payloadLength = frame[5];
135+
uint32_t from = getLe32(&frame[6]);
136+
uint32_t packetId = getLe32(&frame[10]);
137+
uint16_t crc = getLe16(&frame[14]);
138+
139+
if (fragmentCount == 0 || fragmentCount > MAX_FRAGMENTS || fragment >= fragmentCount ||
140+
payloadLength > MAX_FRAGMENT_PAYLOAD_SIZE || frameLength != FRAME_HEADER_SIZE + payloadLength) {
141+
return false;
142+
}
143+
144+
size_t offset = fragment * MAX_FRAGMENT_PAYLOAD_SIZE;
145+
if (offset + payloadLength > sizeof(encodedPacket)) {
146+
return false;
147+
}
148+
149+
if (from != currentFrom || packetId != currentPacketId || crc != currentCrc || fragmentCount != expectedFragments) {
150+
reset(from, packetId, crc, fragmentCount);
151+
}
152+
153+
memcpy(&encodedPacket[offset], &frame[FRAME_HEADER_SIZE], payloadLength);
154+
receivedMask |= 1ULL << fragment;
155+
if (fragment == fragmentCount - 1) {
156+
lastFragmentLength = payloadLength;
157+
}
158+
159+
uint64_t completeMask = fragmentCount == 64 ? UINT64_MAX : ((1ULL << fragmentCount) - 1);
160+
if (receivedMask != completeMask || lastFragmentLength == 0) {
161+
return false;
162+
}
163+
164+
size_t encodedLength = (fragmentCount - 1) * MAX_FRAGMENT_PAYLOAD_SIZE + lastFragmentLength;
165+
if (crc16ccitt(encodedPacket, encodedLength) != currentCrc) {
166+
reset(0, 0, 0, 0);
167+
return false;
168+
}
169+
170+
memset(out, 0, sizeof(*out));
171+
bool decoded = pb_decode_from_bytes(encodedPacket, encodedLength, &meshtastic_MeshPacket_msg, out);
172+
reset(0, 0, 0, 0);
173+
return decoded;
174+
}
175+
176+
#if HAS_BLE_MESH_ADVERTISING
177+
178+
BleAdvertisementMesh *bleAdvertisementMesh = nullptr;
179+
180+
namespace
181+
{
182+
struct PlatformEmitContext
183+
{
184+
BleAdvertisementMesh::QueuedFrame *frames;
185+
uint8_t maxFrames;
186+
uint8_t count;
187+
};
188+
189+
bool emitPlatformFrame(const uint8_t *frame, size_t frameLength, void *context)
190+
{
191+
auto *platformContext = static_cast<PlatformEmitContext *>(context);
192+
if (!platformContext || !frame || frameLength == 0 || frameLength > BleAdvertisementMeshCodec::MAX_FRAME_SIZE ||
193+
platformContext->count >= platformContext->maxFrames) {
194+
return false;
195+
}
196+
197+
BleAdvertisementMesh::QueuedFrame &queued = platformContext->frames[platformContext->count++];
198+
memcpy(queued.data, frame, frameLength);
199+
queued.length = frameLength;
200+
return true;
201+
}
202+
} // namespace
203+
204+
BleAdvertisementMesh::BleAdvertisementMesh(PlatformAdvertiseFrame advertiseFrame)
205+
: concurrency::OSThread("BleAdvMesh"), advertiseFrame(advertiseFrame)
206+
{
207+
}
208+
209+
bool BleAdvertisementMesh::onSend(const meshtastic_MeshPacket *mp)
210+
{
211+
if (!mp || !advertiseFrame ||
212+
mp->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_ADVERTISEMENT) {
213+
return false;
214+
}
215+
216+
uint8_t neededFrames = BleAdvertisementMeshCodec::frameCount(mp);
217+
uint16_t queuedCopies = static_cast<uint16_t>(neededFrames) * FRAME_REPEAT_ROUNDS;
218+
if (neededFrames == 0 || queuedCopies > freeSlots()) {
219+
LOG_WARN("Drop BLE advertisement broadcast id=%u, fragments=%u free=%u", mp->id, neededFrames, freeSlots());
220+
return false;
221+
}
222+
223+
QueuedFrame frames[BleAdvertisementMeshCodec::MAX_FRAGMENTS];
224+
PlatformEmitContext context = {frames, BleAdvertisementMeshCodec::MAX_FRAGMENTS, 0};
225+
if (!BleAdvertisementMeshCodec::forEachFrame(mp, emitPlatformFrame, &context) || context.count != neededFrames) {
226+
return false;
227+
}
228+
229+
LOG_DEBUG("Queue packet for BLE advertisements (id=%u fragments=%u rounds=%u)", mp->id, neededFrames, FRAME_REPEAT_ROUNDS);
230+
for (uint8_t round = 0; round < FRAME_REPEAT_ROUNDS; round++) {
231+
for (uint8_t i = 0; i < context.count; i++) {
232+
const QueuedFrame &frame = context.frames[(round + i) % context.count];
233+
if (!enqueueFrame(frame.data, frame.length)) {
234+
return false;
235+
}
236+
}
237+
}
238+
239+
return true;
240+
}
241+
242+
void BleAdvertisementMesh::onAdvertisement(const uint8_t *frame, size_t frameLength)
243+
{
244+
meshtastic_MeshPacket mp = {};
245+
if (!codec.receiveFrame(frame, frameLength, &mp)) {
246+
return;
247+
}
248+
249+
if (!rememberPacket(mp.from, mp.id)) {
250+
LOG_DEBUG("Drop duplicate BLE advertisement packet (from=0x%x id=%u)", mp.from, mp.id);
251+
return;
252+
}
253+
254+
if (router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
255+
mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_ADVERTISEMENT;
256+
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(mp);
257+
p->rx_snr = 0;
258+
p->rx_rssi = 0;
259+
router->enqueueReceivedMessage(p.release());
260+
}
261+
}
262+
263+
bool BleAdvertisementMesh::enqueueFrame(const uint8_t *frame, size_t frameLength)
264+
{
265+
if (!frame || frameLength == 0 || frameLength > BleAdvertisementMeshCodec::MAX_FRAME_SIZE || queuedFrames >= FRAME_QUEUE_SIZE) {
266+
return false;
267+
}
268+
269+
QueuedFrame &queued = queue[queueTail];
270+
memcpy(queued.data, frame, frameLength);
271+
queued.length = frameLength;
272+
queueTail = (queueTail + 1) % FRAME_QUEUE_SIZE;
273+
queuedFrames++;
274+
setIntervalFromNow(0);
275+
return true;
276+
}
277+
278+
bool BleAdvertisementMesh::rememberPacket(uint32_t from, uint32_t packetId)
279+
{
280+
for (uint8_t i = 0; i < RECENT_PACKET_COUNT; i++) {
281+
if (recentFrom[i] == from && recentPacketId[i] == packetId) {
282+
return false;
283+
}
284+
}
285+
286+
recentFrom[recentPacketIndex] = from;
287+
recentPacketId[recentPacketIndex] = packetId;
288+
recentPacketIndex = (recentPacketIndex + 1) % RECENT_PACKET_COUNT;
289+
return true;
290+
}
291+
292+
int32_t BleAdvertisementMesh::runOnce()
293+
{
294+
if (!advertiseFrame || queuedFrames == 0) {
295+
return 1000;
296+
}
297+
298+
QueuedFrame queued = queue[queueHead];
299+
queueHead = (queueHead + 1) % FRAME_QUEUE_SIZE;
300+
queuedFrames--;
301+
302+
if (!advertiseFrame(queued.data, queued.length)) {
303+
LOG_WARN("BLE advertisement frame transmit failed");
304+
}
305+
306+
return queuedFrames > 0 ? FRAME_INTERVAL_MS : 1000;
307+
}
308+
309+
#endif // HAS_BLE_MESH_ADVERTISING

0 commit comments

Comments
 (0)