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"
10#include "cdc_log.h"
11
12#include <cstdio>
13#include <cstring>
14#include "freertos/FreeRTOS.h"
15#include "freertos/task.h"
16#include "esp_timer.h"
17
18namespace cdc::ui {
19
20static const char* TAG = "BleMenu";
21
23
33
34static constexpr uint8_t BT_MENU_MAX_ITEMS = 16;
35// Single source of truth: mirror the controller's scan-buffer capacity.
38static constexpr uint32_t BLE_SCAN_TIMEOUT_MS = 8000;
39
41
42static ListView* s_bluetoothMenu = nullptr;
45static uint8_t s_bluetoothModuleCount = 0;
46
48static ListView* s_bleScanView = nullptr;
51static uint8_t s_bleScanCount = 0;
52
54static ListView* s_pairedView = nullptr;
58static uint8_t s_pairedCount = 0;
60
62static constexpr size_t BLE_STATUS_BUF_SIZE = 256;
63static EXT_RAM_BSS_ATTR char s_bleStatusBuf[BLE_STATUS_BUF_SIZE];
64
66static void onBluetoothMenuSelect(uint16_t index, void* userData);
68static void toggleBluetoothEnable();
70static void showBluetoothStatus();
72static void startBluetoothScan();
74static void showPairedDevices();
75
89static bool renderBleRow(Gdey029T94* gfx, const ListItem& item,
90 uint16_t index, int x, int y, int w, int h,
91 bool selected, void* userCtx) {
92 (void)index;
93 (void)h;
94 (void)w;
95 (void)userCtx;
96 if (!gfx || !item.userData) return false;
97
98 const auto* dev = reinterpret_cast<const hal::BleScanResult*>(item.userData);
99
100 gfx->setFont(nullptr);
101 gfx->setTextSize(1);
102 int baseline = y + 6;
103
104 drawSignalBars(gfx, x + 4, baseline - 6, dev->rssi, selected);
105
106 // Device name (truncated to fit available width)
107 char nameDisplay[36];
108 strncpy(nameDisplay, dev->name, sizeof(nameDisplay) - 1);
109 nameDisplay[sizeof(nameDisplay) - 1] = '\0';
110 gfx->setCursor(x + 22, baseline);
111 render::printText(gfx, nameDisplay);
112
113 // RSSI value on the right
114 char rssiBuf[8];
115 snprintf(rssiBuf, sizeof(rssiBuf), "%d", dev->rssi);
116 int16_t rx1, ry1;
117 uint16_t rw, rh;
118 gfx->getTextBounds(rssiBuf, 0, 0, &rx1, &ry1, &rw, &rh);
119 gfx->setCursor(x + w - rw - 4, baseline);
120 gfx->print(rssiBuf);
121
122 return true;
123}
124
128static void sortBleScanResults() {
129 for (uint8_t i = 0; i < s_bleScanCount; i++) {
130 for (uint8_t j = i + 1; j < s_bleScanCount; j++) {
131 if (s_bleScanResults[j].rssi > s_bleScanResults[i].rssi) {
134 s_bleScanResults[j] = temp;
135 }
136 }
137 }
138}
139
145 bool enabled = ble && ble->isEnabled();
146
147 if (enabled) {
148 s_bluetoothItems[BT_IDX_ENABLE] = {ui::tr("core.bluetooth_on"), '*', false, nullptr};
149 } else {
150 s_bluetoothItems[BT_IDX_ENABLE] = {ui::tr("core.bluetooth_off"), 0, false, nullptr};
151 }
152 s_bluetoothItems[BT_IDX_PAIR] = {ui::tr("core.ble_pairing_menu"), 0, false, nullptr};
153 s_bluetoothItems[BT_IDX_PAIRED] = {ui::tr("core.ble_paired_devices"), 0, !enabled, nullptr};
154 s_bluetoothItems[BT_IDX_STATUS] = {ui::tr("core.ble_status"), 0, false, nullptr};
155 s_bluetoothItems[BT_IDX_SCAN] = {ui::tr("core.ble_scan"), 0, !enabled, nullptr};
156 s_bluetoothItems[BT_IDX_FORGET_BONDS] = {ui::tr("core.ble_forget_all"), 0, !enabled, nullptr};
157
158 auto& moduleReg = core::ModuleRegistry::instance();
159 s_bluetoothModuleCount = moduleReg.getMenuItems(
163 );
164
165 for (uint8_t i = 0; i < s_bluetoothModuleCount; i++) {
166 s_bluetoothItems[BT_IDX_FIXED_COUNT + i] = {s_bluetoothModuleItems[i].label, 0, false, nullptr};
167 }
168
169 if (s_bluetoothMenu) {
171 }
172}
173
186
192static void onBluetoothMenuSelect(uint16_t index, void* userData) {
193 (void)userData;
194
195 switch (index) {
196 case BT_IDX_ENABLE:
198 return;
199 case BT_IDX_PAIR: {
200 static BlePairingView s_blePairingView;
201 ViewStack::instance().push(&s_blePairingView);
202 return;
203 }
204 case BT_IDX_PAIRED:
206 return;
207 case BT_IDX_STATUS:
209 return;
210 case BT_IDX_SCAN:
212 return;
214 askConfirm(ui::tr("core.ble_forget_all_confirm"), [](void*) {
216 if (ble) {
217 ble->clearAllBonds();
218 showToastSuccess(ui::tr("core.ble_bonds_cleared"));
219 }
220 });
221 return;
222 }
223
224 uint8_t moduleIdx = index - BT_IDX_FIXED_COUNT;
225 if (moduleIdx < s_bluetoothModuleCount) {
226 auto& item = s_bluetoothModuleItems[moduleIdx];
227 if (item.getView) {
228 IView* view = item.getView();
229 if (view) {
231 }
232 } else if (item.onSelect) {
233 item.onSelect();
234 }
236 }
237}
238
244 if (!ble) {
245 showToastError(ui::tr("core.hw_not_available"));
246 return;
247 }
248
249 if (ble->isEnabled()) {
250 ble->disable();
251 showToastInfo(ui::tr("core.bluetooth_off"));
252 } else {
253 if (ble->enable()) {
254 showToastSuccess(ui::tr("core.bluetooth_on"));
255 } else {
256 showToastError(ui::tr("core.failed"));
257 }
258 }
259
262}
263
267static void showBluetoothStatus() {
269 char* info = s_bleStatusBuf;
270 memset(info, 0, BLE_STATUS_BUF_SIZE);
271 size_t pos = 0;
272
273 auto append = [&](const char* fmt, ...) {
274 if (pos >= BLE_STATUS_BUF_SIZE) return;
275 va_list args;
276 va_start(args, fmt);
277 int written = vsnprintf(info + pos, BLE_STATUS_BUF_SIZE - pos, fmt, args);
278 va_end(args);
279 if (written > 0) {
280 size_t w = static_cast<size_t>(written);
281 pos += (w < (BLE_STATUS_BUF_SIZE - pos)) ? w : (BLE_STATUS_BUF_SIZE - pos - 1);
282 }
283 };
284
285 if (ble) {
286 append("BLE: %s\n", ble->isEnabled() ? "ON" : "OFF");
287
288 uint8_t mac[6] = {};
289 if (ble->getMacAddress(mac)) {
290 append("MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
291 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
292 }
293
294 if (ble->isConnected()) {
295 append("\n%s\n", ui::tr("core.ble_connected_to"));
296 append("%s: %d dBm\n", ui::tr("core.signal"), ble->getRssi());
297 } else {
298 append("\n%s\n", ui::tr("core.ble_not_connected"));
299 }
300
301 append("\nName: %s\n", ble->getDeviceName());
302 }
303
304 showInfo(ui::tr("core.ble_status"), info);
305}
306
307// ===========================================================================
308// Async BLE device-name resolution
309//
310// Devices that advertise no name show their MAC. Once the scan list is on
311// screen, this state machine iterates over those devices, connects, reads the
312// GATT Device Name characteristic (0x2A00 in the Generic Access service
313// 0x1800), and updates the row in place. One device at a time, hard-capped at
314// BLE_RESOLVE_DEVICE_TIMEOUT_MS each. GATT callbacks run on the NimBLE host
315// task and only set flags/data; the UI tick drives transitions and the
316// (display-touching) row update. Aborted when the view closes.
317// ===========================================================================
318
319static constexpr uint16_t BLE_GAP_SVC_UUID = 0x1800; // Generic Access
320static constexpr uint16_t BLE_DEV_NAME_UUID = 0x2A00; // Device Name
321static constexpr uint32_t BLE_RESOLVE_DEVICE_TIMEOUT_MS = 5000;
322static constexpr uint32_t BLE_RESOLVE_SETTLE_TIMEOUT_MS = 800;
323
325
327static bool s_resolveActive = false;
328static uint8_t s_resolveIndex = 0;
329static uint16_t s_resolveConn = 0xFFFF;
330static uint16_t s_resolveNameHandle = 0;
331static uint32_t s_resolveDeviceStartMs = 0;
332static uint32_t s_resolveSettleStartMs = 0;
333
334// Set by NimBLE-task callbacks, consumed by the UI tick.
335static volatile bool s_evtConnected = false;
336static volatile bool s_evtDiscovered = false;
337static volatile bool s_evtGotName = false;
338static volatile bool s_evtDisconnected = false;
339static volatile uint16_t s_evtConnHandle = 0xFFFF;
340static volatile uint16_t s_evtNameHandle = 0;
341static char s_resolveNameBuf[32];
342
349
350static void bleResolveStop();
351static void bleResolveBeginNext(uint32_t nowMs);
352
354static void bleResolveMac(const hal::BleScanResult& d, char* out, size_t n) {
355 snprintf(out, n, "%02X:%02X:%02X:%02X:%02X:%02X",
356 d.mac[5], d.mac[4], d.mac[3], d.mac[2], d.mac[1], d.mac[0]);
357}
358
360static bool bleDeviceNeedsName(uint8_t i) {
361 char mac[18];
362 bleResolveMac(s_bleScanResults[i], mac, sizeof(mac));
363 return strcmp(s_bleScanResults[i].name, mac) == 0;
364}
365
367 s_evtConnected = false;
368 s_evtDiscovered = false;
369 s_evtGotName = false;
370 s_evtDisconnected = false;
371 s_evtConnHandle = 0xFFFF;
372 s_evtNameHandle = 0;
373}
374
375// ---- GATT callbacks (NimBLE host task) ----
376static void rOnConnect(uint16_t connHandle) {
377 if (!s_resolveActive) return;
378 s_evtConnHandle = connHandle;
379 s_evtConnected = true;
380}
381static void rOnDisconnect(uint16_t connHandle, int /*reason*/) {
382 if (!s_resolveActive) return;
383 if (connHandle == s_resolveConn) s_evtDisconnected = true;
384}
385static void rOnServiceDiscovered(uint16_t connHandle,
386 const hal::IBluetoothController::DiscoveredService* svc, bool complete) {
387 if (!s_resolveActive || connHandle != s_resolveConn) return;
388 if (svc) {
390 for (uint8_t i = 0; i < svc->numCharacteristics; i++) {
391 if (svc->characteristics[i].uuid == want) {
393 break;
394 }
395 }
396 }
397 if (complete) s_evtDiscovered = true;
398}
399static void rOnCharRead(uint16_t connHandle, uint16_t /*attrHandle*/,
400 const uint8_t* data, uint16_t len) {
401 if (!s_resolveActive || connHandle != s_resolveConn) return;
402 uint16_t n = 0;
403 if (data && len > 0) {
404 const uint16_t cap = sizeof(s_resolveNameBuf) - 1;
405 n = len < cap ? len : cap;
406 memcpy(s_resolveNameBuf, data, n);
407 }
408 s_resolveNameBuf[n] = '\0';
409 s_evtGotName = true;
410}
411
413static void bleResolveCleanupToSettle(uint32_t nowMs) {
415 if (ble) {
416 ble->cancelConnect();
417 if (s_resolveConn != 0xFFFF) ble->disconnectHandle(s_resolveConn);
418 }
419 s_evtDisconnected = false;
422}
423
425static void bleResolveBeginNext(uint32_t nowMs) {
429 }
430 if (!ble || s_resolveIndex >= s_bleScanCount) {
431 LOG_I(TAG, "Name resolve done");
433 return;
434 }
436 s_resolveConn = 0xFFFF;
438 const auto& dev = s_bleScanResults[s_resolveIndex];
439 LOG_I(TAG, "Resolving [%u] %02X:%02X:%02X:%02X:%02X:%02X (addrType %u)",
440 s_resolveIndex, dev.mac[5], dev.mac[4], dev.mac[3],
441 dev.mac[2], dev.mac[1], dev.mac[0], dev.addrType);
442 if (!ble->connect(dev.mac, dev.addrType)) {
443 LOG_W(TAG, "connect() rejected for index %u", s_resolveIndex);
445 bleResolveBeginNext(nowMs); // bounded by scan count
446 return;
447 }
450}
451
452static void bleResolveStart() {
453 if (s_resolveActive) return;
455 if (!ble || !ble->isEnabled() || s_bleScanCount == 0) {
456 LOG_W(TAG, "Name resolve not started (ble=%d enabled=%d count=%u)",
457 ble != nullptr, ble && ble->isEnabled(), s_bleScanCount);
458 return;
459 }
460
461 uint8_t need = 0;
462 for (uint8_t i = 0; i < s_bleScanCount; i++) {
463 if (bleDeviceNeedsName(i)) need++;
464 }
465 LOG_I(TAG, "Name resolve start: %u of %u device(s) need a name", need, s_bleScanCount);
466
467 s_tokResConn = ble->addConnectionCallback(rOnConnect);
468 s_tokResDisc = ble->addDisconnectionCallback(rOnDisconnect);
469 s_tokResSvc = ble->addServiceDiscoveryCallback(rOnServiceDiscovered);
470 s_tokResRead = ble->addCharacteristicReadCallback(rOnCharRead);
471
472 s_resolveActive = true;
473 s_resolveIndex = 0;
474 s_resolveConn = 0xFFFF;
478}
479
480static void bleResolveStop() {
482 if (ble) {
483 ble->cancelConnect();
484 if (s_resolveConn != 0xFFFF) ble->disconnectHandle(s_resolveConn);
485 if (s_tokResConn != kInvalidListener) ble->removeConnectionCallback(s_tokResConn);
486 if (s_tokResDisc != kInvalidListener) ble->removeDisconnectionCallback(s_tokResDisc);
487 if (s_tokResSvc != kInvalidListener) ble->removeServiceDiscoveryCallback(s_tokResSvc);
488 if (s_tokResRead != kInvalidListener) ble->removeCharacteristicReadCallback(s_tokResRead);
489 }
491 s_resolveActive = false;
493 s_resolveConn = 0xFFFF;
495}
496
497static void bleResolveTick(uint32_t nowMs) {
498 if (!s_resolveActive) return;
500 if (!ble) { bleResolveStop(); return; }
501
502 const bool deadline = (nowMs - s_resolveDeviceStartMs) > BLE_RESOLVE_DEVICE_TIMEOUT_MS;
503
504 switch (s_resolvePhase) {
506 bleResolveBeginNext(nowMs);
507 break;
508
510 if (s_evtConnected) {
512 s_evtConnected = false;
513 LOG_I(TAG, "Connected (handle=%u), discovering Generic Access", s_resolveConn);
514 if (ble->discoverServiceByUuid(s_resolveConn, hal::BleUuid::from16(BLE_GAP_SVC_UUID))) {
516 } else {
517 LOG_W(TAG, "discoverServiceByUuid failed");
519 }
520 } else if (deadline) {
521 LOG_W(TAG, "Connect timeout for index %u", s_resolveIndex);
523 }
524 break;
525
527 if (s_evtDiscovered) {
528 s_evtDiscovered = false;
529 if (s_evtNameHandle != 0 &&
530 ble->readCharacteristic(s_resolveConn, s_evtNameHandle)) {
532 LOG_I(TAG, "Reading Device Name (handle=%u)", s_resolveNameHandle);
534 } else {
535 LOG_W(TAG, "No Device Name characteristic (handle=%u)", s_evtNameHandle);
537 }
538 } else if (deadline) {
539 LOG_W(TAG, "Discovery timeout for index %u", s_resolveIndex);
541 }
542 break;
543
545 if (s_evtGotName) {
546 s_evtGotName = false;
547 if (s_resolveNameBuf[0] != '\0' && s_bleScanView) {
548 char* dst = s_bleScanResults[s_resolveIndex].name;
549 size_t cap = sizeof(s_bleScanResults[s_resolveIndex].name);
550 strncpy(dst, s_resolveNameBuf, cap - 1);
551 dst[cap - 1] = '\0';
552 LOG_I(TAG, "Resolved [%u] name '%s'", s_resolveIndex, dst);
553 s_bleScanView->updateItem(s_resolveIndex);
554 } else {
555 LOG_W(TAG, "Empty Device Name for index %u", s_resolveIndex);
556 }
558 } else if (deadline) {
559 LOG_W(TAG, "Read timeout for index %u", s_resolveIndex);
561 }
562 break;
563
565 if (s_evtDisconnected ||
568 bleResolveBeginNext(nowMs);
569 }
570 break;
571
573 default:
574 break;
575 }
576}
577
582class BleScanView : public ListView {
583public:
584 void onEnter(void* context) override { ListView::onEnter(context); bleResolveStart(); }
586 void onExit() override { bleResolveStop(); ListView::onExit(); }
587 void onTick(uint32_t nowMs) override { bleResolveTick(nowMs); }
588 const char* getName() const override { return "BleScanView"; }
589};
590
594static void startBluetoothScan() {
596 if (!ble || !ble->isEnabled()) {
597 showToastError(ui::tr("core.hw_not_available"));
598 return;
599 }
600
601 showToastInfo(ui::tr("core.ble_scanning"), 0);
602
603 s_bleScanCount = 0;
604 if (ble->startScan(BLE_SCAN_TIMEOUT_MS)) {
605 uint32_t startMs = esp_timer_get_time() / 1000;
606 while (!ble->isScanComplete()) {
607 vTaskDelay(pdMS_TO_TICKS(100));
608 if ((esp_timer_get_time() / 1000 - startMs) > BLE_SCAN_TIMEOUT_MS + 1000) break;
609 }
610
612 }
613
616
617 LOG_I(TAG, "BLE scan complete: %u device(s) found", s_bleScanCount);
618
619 if (s_bleScanCount == 0) {
620 showToastInfo(ui::tr("core.ble_no_devices"));
621 return;
622 }
623
624 if (!s_bleScanView) {
626 s_bleScanView->setItemRenderer(renderBleRow, nullptr);
627 }
628
629 for (uint8_t i = 0; i < s_bleScanCount; i++) {
630 s_bleScanItems[i] = {s_bleScanResults[i].name, 0, false, &s_bleScanResults[i]};
631 }
632
633 // Persisted: ListView::init() stores the title pointer, it must not be a
634 // stack buffer that dangles after this function returns.
635 static char title[32];
636 snprintf(title, sizeof(title), "%s (%d)", ui::tr("core.ble_scan"), s_bleScanCount);
639}
640
641// ===========================================================================
642// Paired (bonded) device list
643// ===========================================================================
644
648static void rebuildPairedList() {
650 s_pairedCount = ble ? ble->getBondedDevices(s_pairedBonds, BLE_MAX_BONDS) : 0;
651
652 for (uint8_t i = 0; i < s_pairedCount; i++) {
653 const auto& b = s_pairedBonds[i];
654 snprintf(s_pairedLabels[i], sizeof(s_pairedLabels[i]),
655 "%02X:%02X:%02X:%02X:%02X:%02X %s",
656 b.addr[5], b.addr[4], b.addr[3], b.addr[2], b.addr[1], b.addr[0],
657 b.addrType == 0 ? "pub" : "rnd");
659 static_cast<uint8_t>(b.connected ? '*' : 0),
660 false, &s_pairedBonds[i]};
661 }
662
663 if (s_pairedView) {
664 s_pairedView->init(ui::tr("core.ble_paired_devices"), s_pairedItems, s_pairedCount);
665 s_pairedView->setEmptyText(ui::tr("core.ble_no_paired"));
666 }
667}
668
672static void onForgetConfirm(void*) {
674 if (ble) {
675 ble->forgetBond(s_forgetTarget.addr, s_forgetTarget.addrType);
676 showToastSuccess(ui::tr("core.deleted"));
677 }
679}
680
686static void onPairedSelect(uint16_t index, void* userData) {
687 (void)userData;
688 if (index >= s_pairedCount) return;
690 askConfirm(ui::tr("core.ble_forget_one"), onForgetConfirm);
691}
692
696static void showPairedDevices() {
698 if (!ble || !ble->isEnabled()) {
699 showToastError(ui::tr("core.hw_not_available"));
700 return;
701 }
702
703 if (!s_pairedView) {
704 s_pairedView = new ListView();
705 s_pairedView->setOnSelect(onPairedSelect);
706 }
707
710}
711
712} // 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:53
void push(IView *view, void *context=nullptr)
IBluetoothController * getBluetoothControllerInstance()
Returns singleton Bluetooth stub when NimBLE is unavailable.
void printText(Adafruit_GFX *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:209
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:535
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:211
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]