CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
BluetoothMenuUi.cpp
Go to the documentation of this file.
1
5
6#include "AppUiInternal.h"
9#include "cdc_msg/MessageTransfer.h"
11#include "cdc_log.h"
12
13#include <cstdio>
14#include <cstring>
15#include "freertos/FreeRTOS.h"
16#include "freertos/task.h"
17#include "esp_timer.h"
18
19namespace cdc::ui {
20
21static const char* TAG = "BleMenu";
22
24
35
36static constexpr uint8_t BT_MENU_MAX_ITEMS = 16;
37// Single source of truth: mirror the controller's scan-buffer capacity.
40static constexpr uint32_t BLE_SCAN_TIMEOUT_MS = 8000;
41
43
44static ListView* s_bluetoothMenu = nullptr;
47static uint8_t s_bluetoothModuleCount = 0;
48
50static ListView* s_bleScanView = nullptr;
53static uint8_t s_bleScanCount = 0;
54
56static ListView* s_pairedView = nullptr;
60static uint8_t s_pairedCount = 0;
62
64static constexpr size_t BLE_STATUS_BUF_SIZE = 256;
65static EXT_RAM_BSS_ATTR char s_bleStatusBuf[BLE_STATUS_BUF_SIZE];
66
68static void onBluetoothMenuSelect(uint16_t index, void* userData);
70static void toggleBluetoothEnable();
72static void showBluetoothStatus();
74static void startBluetoothScan();
76static void showPairedDevices();
77
91static bool renderBleRow(Gdey029T94* gfx, const ListItem& item,
92 uint16_t index, int x, int y, int w, int h,
93 bool selected, void* userCtx) {
94 (void)index;
95 (void)h;
96 (void)w;
97 (void)userCtx;
98 if (!gfx || !item.userData) return false;
99
100 const auto* dev = reinterpret_cast<const hal::BleScanResult*>(item.userData);
101
102 gfx->setFont(nullptr);
103 gfx->setTextSize(1);
104 int baseline = y + 6;
105
106 drawSignalBars(gfx, x + 4, baseline - 6, dev->rssi, selected);
107
108 // Device name (truncated to fit available width)
109 char nameDisplay[36];
110 strncpy(nameDisplay, dev->name, sizeof(nameDisplay) - 1);
111 nameDisplay[sizeof(nameDisplay) - 1] = '\0';
112 gfx->setCursor(x + 22, baseline);
113 render::printText(gfx, nameDisplay);
114
115 // RSSI value on the right
116 char rssiBuf[8];
117 snprintf(rssiBuf, sizeof(rssiBuf), "%d", dev->rssi);
118 int16_t rx1, ry1;
119 uint16_t rw, rh;
120 gfx->getTextBounds(rssiBuf, 0, 0, &rx1, &ry1, &rw, &rh);
121 gfx->setCursor(x + w - rw - 4, baseline);
122 gfx->print(rssiBuf);
123
124 return true;
125}
126
130static void sortBleScanResults() {
131 for (uint8_t i = 0; i < s_bleScanCount; i++) {
132 for (uint8_t j = i + 1; j < s_bleScanCount; j++) {
133 if (s_bleScanResults[j].rssi > s_bleScanResults[i].rssi) {
136 s_bleScanResults[j] = temp;
137 }
138 }
139 }
140}
141
147 bool enabled = ble && ble->isEnabled();
148
149 if (enabled) {
150 s_bluetoothItems[BT_IDX_ENABLE] = {ui::tr("core.bluetooth_on"), '*', false, nullptr};
151 } else {
152 s_bluetoothItems[BT_IDX_ENABLE] = {ui::tr("core.bluetooth_off"), 0, false, nullptr};
153 }
154 s_bluetoothItems[BT_IDX_PAIR] = {ui::tr("core.ble_pairing_menu"), 0, false, nullptr};
155 s_bluetoothItems[BT_IDX_PAIRED] = {ui::tr("core.ble_paired_devices"), 0, !enabled, nullptr};
156 s_bluetoothItems[BT_IDX_STATUS] = {ui::tr("core.ble_status"), 0, false, nullptr};
157 s_bluetoothItems[BT_IDX_SCAN] = {ui::tr("core.ble_scan"), 0, !enabled, nullptr};
158 {
159 auto& msg = cdc::msg::MessageTransfer::instance();
161 msg.isBeaconEnabled() ? ui::tr("core.msg_beacon_on") : ui::tr("core.msg_beacon_off"),
162 static_cast<uint8_t>(msg.isBeaconActive() ? '*' : 0), false, nullptr};
163 }
164 s_bluetoothItems[BT_IDX_FORGET_BONDS] = {ui::tr("core.ble_forget_all"), 0, !enabled, nullptr};
165
166 auto& moduleReg = core::ModuleRegistry::instance();
167 s_bluetoothModuleCount = moduleReg.getMenuItems(
171 );
172
173 for (uint8_t i = 0; i < s_bluetoothModuleCount; i++) {
174 s_bluetoothItems[BT_IDX_FIXED_COUNT + i] = {s_bluetoothModuleItems[i].label, 0, false, nullptr};
175 }
176
177 if (s_bluetoothMenu) {
179 }
180}
181
194
200static void onBluetoothMenuSelect(uint16_t index, void* userData) {
201 (void)userData;
202
203 switch (index) {
204 case BT_IDX_ENABLE:
206 return;
207 case BT_IDX_PAIR: {
208 static BlePairingView s_blePairingView;
209 ViewStack::instance().push(&s_blePairingView);
210 return;
211 }
212 case BT_IDX_PAIRED:
214 return;
215 case BT_IDX_STATUS:
217 return;
218 case BT_IDX_SCAN:
220 return;
221 case BT_IDX_BEACON: {
222 auto& msg = cdc::msg::MessageTransfer::instance();
223 msg.setBeaconEnabled(!msg.isBeaconEnabled());
225 return;
226 }
228 askConfirm(ui::tr("core.ble_forget_all_confirm"), [](void*) {
230 if (ble) {
231 ble->clearAllBonds();
232 showToastSuccess(ui::tr("core.ble_bonds_cleared"));
233 }
234 });
235 return;
236 }
237
238 uint8_t moduleIdx = index - BT_IDX_FIXED_COUNT;
239 if (moduleIdx < s_bluetoothModuleCount) {
240 auto& item = s_bluetoothModuleItems[moduleIdx];
241 if (item.getView) {
242 IView* view = item.getView();
243 if (view) {
245 }
246 } else if (item.onSelect) {
247 item.onSelect();
248 }
250 }
251}
252
258 if (!ble) {
259 showToastError(ui::tr("core.hw_not_available"));
260 return;
261 }
262
263 if (ble->isEnabled()) {
264 ble->disable();
265 showToastInfo(ui::tr("core.bluetooth_off"));
266 } else {
267 if (ble->enable()) {
268 showToastSuccess(ui::tr("core.bluetooth_on"));
269 } else {
270 showToastError(ui::tr("core.failed"));
271 }
272 }
273
276}
277
281static void showBluetoothStatus() {
283 char* info = s_bleStatusBuf;
284 memset(info, 0, BLE_STATUS_BUF_SIZE);
285 size_t pos = 0;
286
287 auto append = [&](const char* fmt, ...) {
288 if (pos >= BLE_STATUS_BUF_SIZE) return;
289 va_list args;
290 va_start(args, fmt);
291 int written = vsnprintf(info + pos, BLE_STATUS_BUF_SIZE - pos, fmt, args);
292 va_end(args);
293 if (written > 0) {
294 size_t w = static_cast<size_t>(written);
295 pos += (w < (BLE_STATUS_BUF_SIZE - pos)) ? w : (BLE_STATUS_BUF_SIZE - pos - 1);
296 }
297 };
298
299 if (ble) {
300 append("BLE: %s\n", ble->isEnabled() ? "ON" : "OFF");
301
302 uint8_t mac[6] = {};
303 if (ble->getMacAddress(mac)) {
304 append("MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
305 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
306 }
307
308 if (ble->isConnected()) {
309 append("\n%s\n", ui::tr("core.ble_connected_to"));
310 append("%s: %d dBm\n", ui::tr("core.ble_signal"), ble->getRssi());
311 } else {
312 append("\n%s\n", ui::tr("core.ble_not_connected"));
313 }
314
315 append("\nName: %s\n", ble->getDeviceName());
316 }
317
318 showInfo(ui::tr("core.ble_status"), info);
319}
320
321// ===========================================================================
322// Async BLE device-name resolution
323//
324// Devices that advertise no name show their MAC. Once the scan list is on
325// screen, this state machine iterates over those devices, connects, reads the
326// GATT Device Name characteristic (0x2A00 in the Generic Access service
327// 0x1800), and updates the row in place. One device at a time, hard-capped at
328// BLE_RESOLVE_DEVICE_TIMEOUT_MS each. GATT callbacks run on the NimBLE host
329// task and only set flags/data; the UI tick drives transitions and the
330// (display-touching) row update. Aborted when the view closes.
331// ===========================================================================
332
333static constexpr uint16_t BLE_GAP_SVC_UUID = 0x1800; // Generic Access
334static constexpr uint16_t BLE_DEV_NAME_UUID = 0x2A00; // Device Name
335static constexpr uint32_t BLE_RESOLVE_DEVICE_TIMEOUT_MS = 5000;
336static constexpr uint32_t BLE_RESOLVE_SETTLE_TIMEOUT_MS = 800;
337
339
341static bool s_resolveActive = false;
342static uint8_t s_resolveIndex = 0;
343static uint16_t s_resolveConn = 0xFFFF;
344static uint16_t s_resolveNameHandle = 0;
345static uint32_t s_resolveDeviceStartMs = 0;
346static uint32_t s_resolveSettleStartMs = 0;
347
348// Set by NimBLE-task callbacks, consumed by the UI tick.
349static volatile bool s_evtConnected = false;
350static volatile bool s_evtDiscovered = false;
351static volatile bool s_evtGotName = false;
352static volatile bool s_evtDisconnected = false;
353static volatile uint16_t s_evtConnHandle = 0xFFFF;
354static volatile uint16_t s_evtNameHandle = 0;
355static char s_resolveNameBuf[32];
356
363
364static void bleResolveStop();
365static void bleResolveBeginNext(uint32_t nowMs);
366
368static void bleResolveMac(const hal::BleScanResult& d, char* out, size_t n) {
369 snprintf(out, n, "%02X:%02X:%02X:%02X:%02X:%02X",
370 d.mac[5], d.mac[4], d.mac[3], d.mac[2], d.mac[1], d.mac[0]);
371}
372
374static bool bleDeviceNeedsName(uint8_t i) {
375 char mac[18];
376 bleResolveMac(s_bleScanResults[i], mac, sizeof(mac));
377 return strcmp(s_bleScanResults[i].name, mac) == 0;
378}
379
381 s_evtConnected = false;
382 s_evtDiscovered = false;
383 s_evtGotName = false;
384 s_evtDisconnected = false;
385 s_evtConnHandle = 0xFFFF;
386 s_evtNameHandle = 0;
387}
388
389// ---- GATT callbacks (NimBLE host task) ----
390static void rOnConnect(uint16_t connHandle) {
391 if (!s_resolveActive) return;
392 s_evtConnHandle = connHandle;
393 s_evtConnected = true;
394}
395static void rOnDisconnect(uint16_t connHandle, int /*reason*/) {
396 if (!s_resolveActive) return;
397 if (connHandle == s_resolveConn) s_evtDisconnected = true;
398}
399static void rOnServiceDiscovered(uint16_t connHandle,
400 const hal::IBluetoothController::DiscoveredService* svc, bool complete) {
401 if (!s_resolveActive || connHandle != s_resolveConn) return;
402 if (svc) {
404 for (uint8_t i = 0; i < svc->numCharacteristics; i++) {
405 if (svc->characteristics[i].uuid == want) {
407 break;
408 }
409 }
410 }
411 if (complete) s_evtDiscovered = true;
412}
413static void rOnCharRead(uint16_t connHandle, uint16_t /*attrHandle*/,
414 const uint8_t* data, uint16_t len) {
415 if (!s_resolveActive || connHandle != s_resolveConn) return;
416 uint16_t n = 0;
417 if (data && len > 0) {
418 const uint16_t cap = sizeof(s_resolveNameBuf) - 1;
419 n = len < cap ? len : cap;
420 memcpy(s_resolveNameBuf, data, n);
421 }
422 s_resolveNameBuf[n] = '\0';
423 s_evtGotName = true;
424}
425
427static void bleResolveCleanupToSettle(uint32_t nowMs) {
429 if (ble) {
430 ble->cancelConnect();
431 if (s_resolveConn != 0xFFFF) ble->disconnectHandle(s_resolveConn);
432 }
433 s_evtDisconnected = false;
436}
437
439static void bleResolveBeginNext(uint32_t nowMs) {
443 }
444 if (!ble || s_resolveIndex >= s_bleScanCount) {
445 LOG_I(TAG, "Name resolve done");
447 return;
448 }
450 s_resolveConn = 0xFFFF;
452 const auto& dev = s_bleScanResults[s_resolveIndex];
453 LOG_I(TAG, "Resolving [%u] %02X:%02X:%02X:%02X:%02X:%02X (addrType %u)",
454 s_resolveIndex, dev.mac[5], dev.mac[4], dev.mac[3],
455 dev.mac[2], dev.mac[1], dev.mac[0], dev.addrType);
456 if (!ble->connect(dev.mac, dev.addrType)) {
457 LOG_W(TAG, "connect() rejected for index %u", s_resolveIndex);
459 bleResolveBeginNext(nowMs); // bounded by scan count
460 return;
461 }
464}
465
466static void bleResolveStart() {
467 if (s_resolveActive) return;
469 if (!ble || !ble->isEnabled() || s_bleScanCount == 0) {
470 LOG_W(TAG, "Name resolve not started (ble=%d enabled=%d count=%u)",
471 ble != nullptr, ble && ble->isEnabled(), s_bleScanCount);
472 return;
473 }
474
475 uint8_t need = 0;
476 for (uint8_t i = 0; i < s_bleScanCount; i++) {
477 if (bleDeviceNeedsName(i)) need++;
478 }
479 LOG_I(TAG, "Name resolve start: %u of %u device(s) need a name", need, s_bleScanCount);
480
481 s_tokResConn = ble->addConnectionCallback(rOnConnect);
482 s_tokResDisc = ble->addDisconnectionCallback(rOnDisconnect);
483 s_tokResSvc = ble->addServiceDiscoveryCallback(rOnServiceDiscovered);
484 s_tokResRead = ble->addCharacteristicReadCallback(rOnCharRead);
485
486 s_resolveActive = true;
487 s_resolveIndex = 0;
488 s_resolveConn = 0xFFFF;
492}
493
494static void bleResolveStop() {
496 if (ble) {
497 ble->cancelConnect();
498 if (s_resolveConn != 0xFFFF) ble->disconnectHandle(s_resolveConn);
499 if (s_tokResConn != kInvalidListener) ble->removeConnectionCallback(s_tokResConn);
500 if (s_tokResDisc != kInvalidListener) ble->removeDisconnectionCallback(s_tokResDisc);
501 if (s_tokResSvc != kInvalidListener) ble->removeServiceDiscoveryCallback(s_tokResSvc);
502 if (s_tokResRead != kInvalidListener) ble->removeCharacteristicReadCallback(s_tokResRead);
503 }
505 s_resolveActive = false;
507 s_resolveConn = 0xFFFF;
509}
510
511static void bleResolveTick(uint32_t nowMs) {
512 if (!s_resolveActive) return;
514 if (!ble) { bleResolveStop(); return; }
515
516 const bool deadline = (nowMs - s_resolveDeviceStartMs) > BLE_RESOLVE_DEVICE_TIMEOUT_MS;
517
518 switch (s_resolvePhase) {
520 bleResolveBeginNext(nowMs);
521 break;
522
524 if (s_evtConnected) {
526 s_evtConnected = false;
527 LOG_I(TAG, "Connected (handle=%u), discovering Generic Access", s_resolveConn);
528 if (ble->discoverServiceByUuid(s_resolveConn, hal::BleUuid::from16(BLE_GAP_SVC_UUID))) {
530 } else {
531 LOG_W(TAG, "discoverServiceByUuid failed");
533 }
534 } else if (deadline) {
535 LOG_W(TAG, "Connect timeout for index %u", s_resolveIndex);
537 }
538 break;
539
541 if (s_evtDiscovered) {
542 s_evtDiscovered = false;
543 if (s_evtNameHandle != 0 &&
544 ble->readCharacteristic(s_resolveConn, s_evtNameHandle)) {
546 LOG_I(TAG, "Reading Device Name (handle=%u)", s_resolveNameHandle);
548 } else {
549 LOG_W(TAG, "No Device Name characteristic (handle=%u)", s_evtNameHandle);
551 }
552 } else if (deadline) {
553 LOG_W(TAG, "Discovery timeout for index %u", s_resolveIndex);
555 }
556 break;
557
559 if (s_evtGotName) {
560 s_evtGotName = false;
561 if (s_resolveNameBuf[0] != '\0' && s_bleScanView) {
562 char* dst = s_bleScanResults[s_resolveIndex].name;
563 size_t cap = sizeof(s_bleScanResults[s_resolveIndex].name);
564 strncpy(dst, s_resolveNameBuf, cap - 1);
565 dst[cap - 1] = '\0';
566 LOG_I(TAG, "Resolved [%u] name '%s'", s_resolveIndex, dst);
567 s_bleScanView->updateItem(s_resolveIndex);
568 } else {
569 LOG_W(TAG, "Empty Device Name for index %u", s_resolveIndex);
570 }
572 } else if (deadline) {
573 LOG_W(TAG, "Read timeout for index %u", s_resolveIndex);
575 }
576 break;
577
579 if (s_evtDisconnected ||
582 bleResolveBeginNext(nowMs);
583 }
584 break;
585
587 default:
588 break;
589 }
590}
591
596class BleScanView : public ListView {
597public:
598 void onEnter(void* context) override { ListView::onEnter(context); bleResolveStart(); }
600 void onExit() override { bleResolveStop(); ListView::onExit(); }
601 void onTick(uint32_t nowMs) override { bleResolveTick(nowMs); }
602 const char* getName() const override { return "BleScanView"; }
603};
604
608static void startBluetoothScan() {
610 if (!ble || !ble->isEnabled()) {
611 showToastError(ui::tr("core.hw_not_available"));
612 return;
613 }
614
615 showToastInfo(ui::tr("core.ble_scanning"), 0);
616
617 s_bleScanCount = 0;
618 if (ble->startScan(BLE_SCAN_TIMEOUT_MS)) {
619 uint32_t startMs = esp_timer_get_time() / 1000;
620 while (!ble->isScanComplete()) {
621 vTaskDelay(pdMS_TO_TICKS(100));
622 if ((esp_timer_get_time() / 1000 - startMs) > BLE_SCAN_TIMEOUT_MS + 1000) break;
623 }
624
626 }
627
630
631 LOG_I(TAG, "BLE scan complete: %u device(s) found", s_bleScanCount);
632
633 if (s_bleScanCount == 0) {
634 showToastInfo(ui::tr("core.ble_no_devices"));
635 return;
636 }
637
638 if (!s_bleScanView) {
640 s_bleScanView->setItemRenderer(renderBleRow, nullptr);
641 }
642
643 for (uint8_t i = 0; i < s_bleScanCount; i++) {
644 s_bleScanItems[i] = {s_bleScanResults[i].name, 0, false, &s_bleScanResults[i]};
645 }
646
647 // Persisted: ListView::init() stores the title pointer, it must not be a
648 // stack buffer that dangles after this function returns.
649 static char title[32];
650 snprintf(title, sizeof(title), "%s (%d)", ui::tr("core.ble_scan"), s_bleScanCount);
653}
654
655// ===========================================================================
656// Paired (bonded) device list
657// ===========================================================================
658
662static void rebuildPairedList() {
664 s_pairedCount = ble ? ble->getBondedDevices(s_pairedBonds, BLE_MAX_BONDS) : 0;
665
666 for (uint8_t i = 0; i < s_pairedCount; i++) {
667 const auto& b = s_pairedBonds[i];
668 snprintf(s_pairedLabels[i], sizeof(s_pairedLabels[i]),
669 "%02X:%02X:%02X:%02X:%02X:%02X %s",
670 b.addr[5], b.addr[4], b.addr[3], b.addr[2], b.addr[1], b.addr[0],
671 b.addrType == 0 ? "pub" : "rnd");
673 static_cast<uint8_t>(b.connected ? '*' : 0),
674 false, &s_pairedBonds[i]};
675 }
676
677 if (s_pairedView) {
678 s_pairedView->init(ui::tr("core.ble_paired_devices"), s_pairedItems, s_pairedCount);
679 s_pairedView->setEmptyText(ui::tr("core.ble_no_paired"));
680 }
681}
682
686static void onForgetConfirm(void*) {
688 if (ble) {
689 ble->forgetBond(s_forgetTarget.addr, s_forgetTarget.addrType);
690 showToastSuccess(ui::tr("core.deleted"));
691 }
693}
694
700static void onPairedSelect(uint16_t index, void* userData) {
701 (void)userData;
702 if (index >= s_pairedCount) return;
704 askConfirm(ui::tr("core.ble_forget_one"), onForgetConfirm);
705}
706
710static void showPairedDevices() {
712 if (!ble || !ble->isEnabled()) {
713 showToastError(ui::tr("core.hw_not_available"));
714 return;
715 }
716
717 if (!s_pairedView) {
718 s_pairedView = new ListView();
719 s_pairedView->setOnSelect(onPairedSelect);
720 }
721
724}
725
726} // namespace cdc::ui
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
static ModuleRegistry & instance()
Returns the singleton module registry instance.
static constexpr uint8_t MAX_SCAN_RESULTS
static constexpr ListenerToken INVALID_LISTENER
static constexpr uint8_t MAX_BONDED_DEVICES
Discoverable/pairing-mode screen.
ListView for the BLE scan results that drives async name resolution while visible and cancels it when...
void onEnter(void *context) override
const char * getName() const override
void onTick(uint32_t nowMs) override
virtual void onResume()=0
virtual void onExit()=0
virtual void onEnter(void *context=nullptr)=0
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:34
void push(IView *view, void *context=nullptr)
IBluetoothController * getBluetoothControllerInstance()
Returns singleton Bluetooth stub when NimBLE is unavailable.
void printText(Gdey029T94 *gfx, const char *text)
Draws CP437 text with the built-in 6x8 glyph font, byte-for-byte.
Centralized key-code constants for cdc_views.
Definition IModule.h:8
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:208
static constexpr BleListenerToken kInvalidListener
void askConfirm(const char *message, ConfirmView::ConfirmCallback onYes, void *userData=nullptr)
Definition ConfirmView.h:94
static void startBluetoothScan()
Starts BLE scan and opens result list.
static constexpr uint8_t BLE_MAX_BONDS
static char s_bleStatusBuf[BLE_STATUS_BUF_SIZE]
static void sortBleScanResults()
Sorts BLE scan results by RSSI descending.
static constexpr uint8_t BT_MENU_MAX_ITEMS
static void rebuildPairedList()
Rebuilds the paired-device list from the current bond store.
static void bleResolveStop()
static void rOnDisconnect(uint16_t connHandle, int)
static BleListenerToken s_tokResSvc
static uint16_t s_resolveNameHandle
static void bleResolveCleanupToSettle(uint32_t nowMs)
Tears down the current attempt and waits briefly for a clean stack.
static constexpr uint32_t BLE_RESOLVE_DEVICE_TIMEOUT_MS
static BleListenerToken s_tokResRead
static volatile bool s_evtConnected
InfoView * showInfo(const char *title, const char *text, const char *hint=nullptr)
Shows a shared info view instance and pushes it onto the view stack.
Definition InfoView.cpp:310
static constexpr uint32_t BLE_SCAN_TIMEOUT_MS
static void showBluetoothStatus()
Shows BLE status and adapter information.
static volatile uint16_t s_evtConnHandle
static char s_pairedLabels[BLE_MAX_BONDS][24]
void rebuildToolsMenu()
Rebuilds tools menu entries including dynamic module tools.
Definition AppUi.cpp:496
static volatile uint16_t s_evtNameHandle
static void rOnServiceDiscovered(uint16_t connHandle, const hal::IBluetoothController::DiscoveredService *svc, bool complete)
static void bleResolveStart()
static constexpr size_t BLE_STATUS_BUF_SIZE
PSRAM-backed text buffer used for BLE status details.
static ListView * s_bluetoothMenu
Bluetooth menu and scan state.
static char s_resolveNameBuf[32]
hal::IBluetoothController::ListenerToken BleListenerToken
static constexpr uint16_t BLE_GAP_SVC_UUID
static constexpr uint32_t BLE_RESOLVE_SETTLE_TIMEOUT_MS
static hal::BleScanResult s_bleScanResults[BLE_MAX_SCAN_RESULTS]
static void rOnConnect(uint16_t connHandle)
static uint8_t s_pairedCount
static uint8_t s_bluetoothModuleCount
static ListView * s_bleScanView
BLE scan result list state.
static volatile bool s_evtDiscovered
static void bleResolveMac(const hal::BleScanResult &d, char *out, size_t n)
Formats a scan result's MAC the same way as the no-name fallback.
static bool renderBleRow(Gdey029T94 *gfx, const ListItem &item, uint16_t index, int x, int y, int w, int h, bool selected, void *userCtx)
Renders one BLE scan row with signal bars and RSSI text.
static uint16_t s_resolveConn
static void onPairedSelect(uint16_t index, void *userData)
Asks for confirmation to forget the selected paired device.
static ListItem s_pairedItems[BLE_MAX_BONDS]
static constexpr uint8_t BLE_MAX_SCAN_RESULTS
static bool bleDeviceNeedsName(uint8_t i)
True when the row still shows the MAC fallback (no name yet).
static BleResolvePhase s_resolvePhase
static bool s_resolveActive
static void onBluetoothMenuSelect(uint16_t index, void *userData)
Handles Bluetooth menu selection.
static void bleResolveClearEvents()
static volatile bool s_evtGotName
static ListView * s_pairedView
Paired (bonded) device list state.
static uint32_t s_resolveSettleStartMs
static volatile bool s_evtDisconnected
static void toggleBluetoothEnable()
Toggles BLE controller enabled state.
void drawSignalBars(Gdey029T94 *gfx, int x, int y, int8_t rssi, bool inverted)
Draws RSSI signal bars using the shared lock-screen visual style.
Definition AppUi.cpp:199
static void showPairedDevices()
Shows the list of paired (bonded) devices.
void showToastSuccess(const char *message, uint16_t durationMs=1500)
Shows a success toast message.
static uint32_t s_resolveDeviceStartMs
static constexpr uint16_t BLE_DEV_NAME_UUID
static ListItem s_bleScanItems[BLE_MAX_SCAN_RESULTS]
void showBluetoothMenu()
Shows top-level Bluetooth menu.
static BleListenerToken s_tokResDisc
static const char * TAG
static void onForgetConfirm(void *)
Confirmed-forget handler: unpairs the selected device and refreshes.
static void bleResolveTick(uint32_t nowMs)
static hal::BleBondInfo s_pairedBonds[BLE_MAX_BONDS]
static ListItem s_bluetoothItems[BT_MENU_MAX_ITEMS]
void rebuildBluetoothMenu()
Rebuilds Bluetooth menu entries and dynamic module items.
void showToastInfo(const char *message, uint16_t durationMs=1500)
Shows an informational toast message.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.
static BleListenerToken s_tokResConn
static uint8_t s_resolveIndex
BluetoothMenuIdx
Bluetooth fixed menu indices and capacity limits.
static void rOnCharRead(uint16_t connHandle, uint16_t, const uint8_t *data, uint16_t len)
static uint8_t s_bleScanCount
static void bleResolveBeginNext(uint32_t nowMs)
Starts resolving the next device that still shows its MAC.
static hal::BleBondInfo s_forgetTarget
static core::ModuleMenuItem s_bluetoothModuleItems[12]
Menu item registered by a module.
Definition IModule.h:29
static BleUuid from16(uint16_t v)
DiscoveredCharacteristic characteristics[MAX_DISCOVERED_CHARS]