24#include "freertos/FreeRTOS.h"
25#include "freertos/semphr.h"
41constexpr uint16_t BLE_MAX_PAYLOAD = 244;
42constexpr uint32_t PLUGIN_SERVICE_HANDLE = 1;
44constexpr uint8_t WRITE_RING = 4;
45constexpr uint8_t NOTIFY_RING = 4;
46constexpr uint8_t MAX_DISC_CHARS = 8;
49SemaphoreHandle_t s_lock =
nullptr;
50void lock_init() {
if (!s_lock) s_lock = xSemaphoreCreateMutex(); }
53 Guard() {
if (s_lock) held = (xSemaphoreTake(s_lock, portMAX_DELAY) == pdTRUE); }
54 ~Guard() {
if (held) xSemaphoreGive(s_lock); }
60uint16_t s_value_handles[MAX_PLUGIN_CHARS];
62 uint32_t write_action_id = 0;
65 void* plugin =
nullptr;
67 uint8_t uuid[16] = {};
68 uint8_t num_chars = 0;
69 PeriphChar chars[MAX_PLUGIN_CHARS];
76 uint8_t data[BLE_MAX_PAYLOAD];
78WriteEvt s_wring[WRITE_RING];
79volatile uint8_t s_wr_head = 0, s_wr_tail = 0;
82uint32_t s_cur_write_char = 0;
83uint16_t s_cur_write_len = 0;
84uint8_t s_cur_write_buf[BLE_MAX_PAYLOAD];
87struct NotifyEvt { uint16_t value_handle; uint16_t len; uint8_t data[BLE_MAX_PAYLOAD]; };
89 void* plugin =
nullptr;
90 bool listeners_registered =
false;
94 uint16_t conn = 0xFFFF;
95 uint32_t discover_action_id = 0;
96 uint32_t read_action_id = 0;
97 uint32_t notify_action_id = 0;
100 ble_remote_char_t disc[MAX_DISC_CHARS];
101 uint8_t disc_count = 0;
102 bool fire_discovery =
false;
105 uint8_t read_buf[BLE_MAX_PAYLOAD];
106 uint16_t read_len = 0;
107 bool fire_read =
false;
110 NotifyEvt nring[NOTIFY_RING];
111 uint8_t n_head = 0, n_tail = 0;
112} s_cen EXT_RAM_BSS_ATTR;
120 return p && p->manifest().capabilities.ble;
124void uuid_to_bytes(
const BleUuid& u, uint8_t out[16]) {
126 std::memcpy(out, u.
u128, 16);
128 static const uint8_t base[16] = {
129 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
130 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
132 std::memcpy(out, base, 16);
133 out[12] =
static_cast<uint8_t
>(u.
u16 & 0xFF);
134 out[13] =
static_cast<uint8_t
>(u.
u16 >> 8);
139const uint8_t NUS_SVC[16] = { 0x9e,0xca,0xdc,0x24,0x0e,0xe5,0xa9,0xe0,
140 0x93,0xf3,0xa3,0xb5,0x01,0x00,0x40,0x6e };
141const uint8_t VCARD_SVC[16] = { 0x01,0x1A,0x8B,0x6A,0x9D,0x4C,0x6E,0x9A,
142 0x7A,0x4D,0x5D,0x8B,0x20,0x1F,0x2F,0x8E };
143const uint8_t GPG_SVC[16] = { 0x01,0x1A,0x8B,0x6A,0x9D,0x4C,0x6E,0x9A,
144 0x7A,0x4D,0x5D,0x8B,0x30,0x1F,0x2F,0x8E };
148bool uuid_is_reserved(
const uint8_t uuid[16]) {
149 static const uint8_t sig_base[12] = {
150 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00
152 if (std::memcmp(uuid, sig_base, 12) == 0 && uuid[14] == 0 && uuid[15] == 0) {
155 return std::memcmp(uuid, NUS_SVC, 16) == 0
156 || std::memcmp(uuid, VCARD_SVC, 16) == 0
157 || std::memcmp(uuid, GPG_SVC, 16) == 0;
163 if (connHandle != s_cen.conn)
return;
166 if (n > MAX_DISC_CHARS) n = MAX_DISC_CHARS;
167 for (uint8_t i = 0; i < n; i++) {
171 s_cen.disc[i].reserved = 0;
173 s_cen.disc_count = n;
175 if (complete) s_cen.fire_discovery =
true;
178void on_char_read(uint16_t connHandle, uint16_t,
const uint8_t* data, uint16_t len) {
180 if (connHandle != s_cen.conn)
return;
181 if (len > BLE_MAX_PAYLOAD) len = BLE_MAX_PAYLOAD;
182 std::memcpy(s_cen.read_buf, data, len);
183 s_cen.read_len = len;
184 s_cen.fire_read =
true;
187void on_notification(uint16_t, uint16_t attr,
const uint8_t* data, uint16_t len) {
189 uint8_t next =
static_cast<uint8_t
>((s_cen.n_head + 1) % NOTIFY_RING);
190 if (next == s_cen.n_tail)
return;
191 NotifyEvt& e = s_cen.nring[s_cen.n_head];
192 if (len > BLE_MAX_PAYLOAD) len = BLE_MAX_PAYLOAD;
193 e.value_handle = attr;
195 std::memcpy(e.data, data, len);
199void ensure_central_listeners() {
200 if (s_cen.listeners_registered)
return;
203 b->addServiceDiscoveryCallback(on_discovery);
204 b->addCharacteristicReadCallback(on_char_read);
205 b->addNotificationCallback(on_notification);
206 s_cen.listeners_registered =
true;
228 const char*
name = b->getDeviceName();
229 std::strncpy(out,
name ?
name :
"", out_size - 1);
230 out[out_size - 1] =
'\0';
247 if (s_periph.active && s_periph.plugin != plugin)
return HOST_ERR_BUSY;
248 if (s_periph.active) {
255 for (uint32_t i = 0; i < num_chars; i++) {
256 s_value_handles[i] = 0;
264 const uint32_t char_handle = i + 1;
266 gc[i].
onWrite = [char_handle](uint16_t conn, uint16_t,
const uint8_t* d, uint16_t l) ->
int {
268 uint8_t next =
static_cast<uint8_t
>((s_wr_head + 1) % WRITE_RING);
269 if (next == s_wr_tail)
return 0;
270 WriteEvt& e = s_wring[s_wr_head];
271 if (l > BLE_MAX_PAYLOAD) l = BLE_MAX_PAYLOAD;
272 e.char_handle = char_handle;
275 std::memcpy(e.data, d, l);
287 if (!b->registerGattService(svc,
true))
return HOST_ERR_BUSY;
289 s_periph.plugin = plugin;
290 s_periph.active =
true;
291 std::memcpy(s_periph.uuid, def->
uuid, 16);
292 s_periph.num_chars =
static_cast<uint8_t
>(num_chars);
293 for (uint32_t i = 0; i < num_chars; i++) {
311static int periph_send(uint32_t char_handle,
const uint8_t* data,
size_t len,
bool indicate) {
318 uint16_t vh = s_value_handles[char_handle - 1];
319 uint16_t conn = b->getConnectionHandle();
321 bool ok = indicate ? b->sendIndication(conn, vh, data,
static_cast<uint16_t
>(len))
322 : b->sendNotification(conn, vh, data,
static_cast<uint16_t
>(len));
336 if (char_handle != s_cur_write_char)
return 0;
337 size_t n = s_cur_write_len;
338 if (n > buf_size) n = buf_size;
339 std::memcpy(buf, s_cur_write_buf, n);
340 return static_cast<int>(n);
359 uint8_t cap =
static_cast<uint8_t
>(*count > 32 ? 32 : *count);
361 uint8_t n = b->getScanResults(tmp, cap);
362 for (uint8_t i = 0; i < n; i++) {
363 std::memcpy(out[i].addr, tmp[i].mac, 6);
366 std::strncpy(out[i].
name, tmp[i].
name,
sizeof(out[i].
name) - 1);
367 out[i].
name[
sizeof(out[i].
name) - 1] =
'\0';
379 ensure_central_listeners();
386 uint16_t h = b->getConnectionHandle();
387 return h == 0xFFFF ? 0u :
static_cast<uint32_t
>(h);
394 b->disconnectHandle(
static_cast<uint16_t
>(conn));
404 s_cen.conn =
static_cast<uint16_t
>(conn);
405 s_cen.discover_action_id = action_id;
406 ensure_central_listeners();
407 return b->discoverServiceByUuid(
static_cast<uint16_t
>(conn),
BleUuid::from128(uuid))
414 uint8_t n = s_cen.disc_count;
415 if (n > *count) n =
static_cast<uint8_t
>(*count);
426 s_cen.conn =
static_cast<uint16_t
>(conn);
427 s_cen.read_action_id = action_id;
428 ensure_central_listeners();
429 return b->readCharacteristic(
static_cast<uint16_t
>(conn), value_handle)
436 size_t n = s_cen.read_len;
437 if (n > buf_size) n = buf_size;
438 std::memcpy(buf, s_cen.read_buf, n);
440 return static_cast<int>(n);
444 const uint8_t* data,
size_t len, uint8_t with_response) {
449 return b->writeCharacteristic(
static_cast<uint16_t
>(conn), value_handle,
450 data,
static_cast<uint16_t
>(len), with_response != 0)
459 s_cen.notify_action_id = action_id;
460 ensure_central_listeners();
461 return b->enableNotifications(
static_cast<uint16_t
>(conn), cccd_handle)
469 NotifyEvt& e = s_cen.nring[s_cen.n_tail];
470 *value_handle_out = e.value_handle;
472 if (n > buf_size) n = buf_size;
473 std::memcpy(buf, e.data, n);
474 s_cen.n_tail =
static_cast<uint8_t
>((s_cen.n_tail + 1) % NOTIFY_RING);
475 return static_cast<int>(n);
484 uint32_t ch = 0, aid = 0;
488 if (s_wr_tail == s_wr_head)
break;
489 WriteEvt& e = s_wring[s_wr_tail];
492 s_cur_write_len = e.len;
493 std::memcpy(s_cur_write_buf, e.data, e.len);
494 s_wr_tail =
static_cast<uint8_t
>((s_wr_tail + 1) % WRITE_RING);
496 if (s_periph.active && s_periph.plugin && ch >= 1 && ch <= s_periph.num_chars) {
497 aid = s_periph.chars[ch - 1].write_action_id;
498 s_cur_write_char = ch;
501 static_cast<pm::Plugin*
>(s_periph.plugin), aid, ch, conn);
503 s_cur_write_char = 0;
509 if (!s_cen.plugin)
return;
510 bool fd, fr, have_notif;
513 fd = s_cen.fire_discovery; s_cen.fire_discovery =
false;
514 fr = s_cen.fire_read; s_cen.fire_read =
false;
515 have_notif = (s_cen.n_tail != s_cen.n_head);
517 auto* pl =
static_cast<pm::Plugin*
>(s_cen.plugin);
519 if (fd && s_cen.discover_action_id) mgr.dispatchActionTo(pl, s_cen.discover_action_id, 0, 0);
520 if (fr && s_cen.read_action_id) mgr.dispatchActionTo(pl, s_cen.read_action_id, 0, 0);
521 if (have_notif && s_cen.notify_action_id) mgr.dispatchActionTo(pl, s_cen.notify_action_id, 0, 0);
526 if (s_periph.active && s_periph.plugin == plugin) {
531 if (s_cen.plugin == plugin) {
532 s_cen.plugin =
nullptr;
533 s_cen.discover_action_id = s_cen.read_action_id = s_cen.notify_action_id = 0;
Discovers, loads, runs and unloads WASM plugins on the badge.
Owned WAMR module instance + per-plugin state.
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
static constexpr uint8_t MAX_CHARS_PER_SERVICE
static PluginManager & instance() noexcept
void dispatchActionTo(Plugin *plugin, uint32_t action_id, uint32_t idx, uint32_t user_data)
int host_ble_consume_write(uint32_t char_handle, uint8_t *buf, size_t buf_size)
Pull the next queued inbound write for char_handle.
int host_ble_scan_start(uint32_t duration_ms)
Start a central scan for duration_ms milliseconds.
int host_ble_connect(const uint8_t addr[6], uint8_t addr_type)
Connect to a peer. Completion arrives as a BLE_CONNECTED event; read the resulting handle with host_b...
int host_ble_subscribe(uint32_t conn, uint16_t cccd_handle, uint32_t action_id)
Subscribe to notifications on a peer characteristic (by CCCD handle). Each notification fires action_...
int host_ble_send_notification(uint32_t char_handle, const uint8_t *data, size_t len)
Notify subscribers of a value on one of the plugin's characteristics.
int host_ble_discover(uint32_t conn, const uint8_t uuid[16], uint32_t action_id)
Discover the characteristics of one service on a connected peer. Completion fires action_id; read ent...
bool host_ble_scan_done(void)
True when the scan started by host_ble_scan_start() has finished.
int host_ble_read_char(uint32_t conn, uint16_t value_handle, uint32_t action_id)
Start reading a peer characteristic by value handle. Completion fires action_id; read the value with ...
#define BLE_PROP_WRITE_NO_RSP
int host_ble_disconnect(uint32_t conn)
Disconnect a connection.
int host_ble_device_name(char *out, size_t out_size)
Copy the local BLE device name into out.
int8_t host_ble_rssi(void)
Signal strength of the active BLE link in dBm, or 0 when idle.
int host_ble_unregister_service(uint32_t service_handle)
Tear down the plugin's registered GATT service.
int host_ble_scan_results(ble_scan_result_t *out, size_t *count)
Read results from the last central scan.
int host_ble_send_indication(uint32_t char_handle, const uint8_t *data, size_t len)
Indicate (acknowledged notify) a value on a plugin characteristic.
bool host_ble_is_enabled(void)
True when the BLE stack is initialised and advertising or connectable.
int host_ble_consume_notification(uint16_t *value_handle_out, uint8_t *buf, size_t buf_size)
Pull the next queued inbound notification.
int host_ble_register_service(ble_service_def_t *def, ble_char_def_t *chars, uint32_t num_chars)
Register the plugin's GATT service and its characteristics.
int host_ble_write_char(uint32_t conn, uint16_t value_handle, const uint8_t *data, size_t len, uint8_t with_response)
Write a value to a peer characteristic by value handle.
int host_ble_consume_discovery(ble_remote_char_t *out, size_t *count)
Pull discovered characteristics after a discovery action fires.
int host_ble_mac(uint8_t out[6])
Read the local BLE MAC address.
int host_ble_consume_read(uint8_t *buf, size_t buf_size)
Pull the value delivered by the last read action.
uint32_t host_ble_conn_handle(void)
Current connection handle (central or peripheral), or 0 when idle.
CDC Badge OS plugin host API - canonical C ABI contract.
#define HOST_ERR_NO_CAPABILITY
#define HOST_ERR_INVALID_ARG
#define HOST_ERR_NOT_FOUND
void plg_ble_on_unload(void *plugin)
void * plg_get_active_plugin(void)
IBluetoothController * getBluetoothControllerInstance()
Returns singleton Bluetooth stub when NimBLE is unavailable.
static int periph_send(uint32_t char_handle, const uint8_t *data, size_t len, bool indicate)
IBluetoothController * getBluetoothControllerInstance()
Returns singleton Bluetooth stub when NimBLE is unavailable.
static BleUuid from128(const uint8_t v[16])
DiscoveredCharacteristic characteristics[MAX_DISCOVERED_CHARS]
uint8_t numCharacteristics
One characteristic of a plugin GATT service (peripheral role).
One characteristic discovered on a connected peer (central role).
One device from a central scan.
A plugin GATT service definition (peripheral role). Always primary.
GattWriteCallback onWrite
uint8_t numCharacteristics
GattCharacteristic * characteristics