Date: November 18, 2025 Objective: Replace state-based connection identification with UUID-switching strategy Bug: #28 (Button unresponsiveness) - FIXED Enhancement: Simplified peer/app identification via advertised UUID
Current implementation (post-Bug #9 fix):
- Always advertises Config Service UUID (
...0200) - Uses complex 4-path state-based logic to identify peer vs app connections
- Vulnerable to misidentification (Bug #27)
- 38-second grace period (30s + 8s) is confusing
User's proposed solution (SUPERIOR):
- 0-30s: Advertise ONLY Bilateral Service UUID (
...0100) - peers can discover, apps cannot - 30s+: Switch to ONLY Config Service UUID (
...0200) - apps can discover, bonded peers reconnect by address - Eliminates ALL state-based identification complexity
- Zero misidentification risk (wrong connection type physically cannot connect)
File: src/ble_manager.c
// Add after line 50 (UUID definitions section)
/**
* @brief Boot timestamp for pairing window tracking
*
* Initialized in ble_init() to esp_timer_get_time() / 1000
* Used to determine which UUID to advertise (Bilateral vs Config)
*/
static uint32_t ble_boot_time_ms = 0;
// Add constant
#define PAIRING_WINDOW_MS 30000 // 30 seconds for peer pairingIn ble_init():
ble_boot_time_ms = (uint32_t)(esp_timer_get_time() / 1000);
ESP_LOGI(TAG, "BLE boot timestamp: %lu ms", ble_boot_time_ms);File: src/ble_manager.c
/**
* @brief Determine which UUID to advertise based on timing and pairing state
* @return Pointer to UUID to advertise (Bilateral or Config)
*
* Logic:
* - No peer bonded AND within 30s: Bilateral UUID (peer discovery only)
* - Peer bonded OR after 30s: Config UUID (app discovery + bonded peer reconnect)
*/
static const ble_uuid128_t* ble_get_advertised_uuid(void) {
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000);
uint32_t elapsed_ms = now_ms - ble_boot_time_ms;
// Check if peer already bonded (NVS check)
bool peer_bonded = ble_check_bonded_peer_exists();
if (!peer_bonded && elapsed_ms < PAIRING_WINDOW_MS) {
// Within pairing window, no peer bonded yet - advertise Bilateral UUID
return &uuid_bilateral_service;
} else {
// After pairing window OR peer already bonded - advertise Config UUID
return &uuid_config_service;
}
}File: src/ble_manager.c - ble_on_sync() function (line 1754-1770)
BEFORE:
// Configure scan response with Configuration Service UUID (Phase 1b.2)
// - Configuration Service UUID (0x0200): For mobile app/PWA discovery
// - Peer discovery works via GATT service presence (devices already connecting)
// Using scan response prevents exceeding 31-byte advertising packet limit
struct ble_hs_adv_fields rsp_fields;
memset(&rsp_fields, 0, sizeof(rsp_fields));
// Advertise Configuration Service so PWA can filter and find the device
rsp_fields.uuids128 = &uuid_config_service;
rsp_fields.num_uuids128 = 1;
rsp_fields.uuids128_is_complete = 1;AFTER:
// Configure scan response with dynamic UUID (Phase 1b.3 UUID-switching)
// - 0-30s: Bilateral Service UUID (0x0100) - peer discovery only
// - 30s+: Configuration Service UUID (0x0200) - app discovery + bonded peer reconnect
// Using scan response prevents exceeding 31-byte advertising packet limit
struct ble_hs_adv_fields rsp_fields;
memset(&rsp_fields, 0, sizeof(rsp_fields));
const ble_uuid128_t *advertised_uuid = ble_get_advertised_uuid();
rsp_fields.uuids128 = advertised_uuid;
rsp_fields.num_uuids128 = 1;
rsp_fields.uuids128_is_complete = 1;
ESP_LOGI(TAG, "Advertising UUID: %s",
(advertised_uuid == &uuid_bilateral_service) ? "Bilateral (peer discovery)" : "Config (app + bonded peer)");File: src/ble_manager.c - Scan callback (line 2123-2150)
BEFORE:
// Compare against Configuration Service UUID (Phase 1b.2: simplified discovery)
// Both peer devices advertise this UUID, and it also enables PWA discovery
if (ble_uuid_cmp(&fields.uuids128[i].u, &uuid_config_service.u) == 0) {AFTER:
// Compare against Bilateral Service UUID (Phase 1b.3: UUID-switching)
// Peer devices advertise this UUID during pairing window (0-30s)
// After pairing, bonded peers reconnect by address (no scanning needed)
if (ble_uuid_cmp(&fields.uuids128[i].u, &uuid_bilateral_service.u) == 0) {Option A: BLE Task Timer Check (Recommended - already have BLE task)
File: src/ble_task.c - Main loop
// Check if UUID switch needed (30s boundary)
uint32_t elapsed_ms = (uint32_t)(esp_timer_get_time() / 1000);
if (!uuid_switched && elapsed_ms >= 30000 && !ble_check_bonded_peer_exists()) {
ESP_LOGI(TAG, "30s pairing window expired - switching to Config Service UUID");
ble_restart_advertising(); // Re-advertise with Config UUID
uuid_switched = true;
}Option B: ESP Timer (More precise, but adds complexity)
static esp_timer_handle_t uuid_switch_timer = NULL;
static void uuid_switch_timer_callback(void *arg) {
if (!ble_check_bonded_peer_exists()) {
ESP_LOGI(TAG, "30s pairing window expired - switching to Config Service UUID");
ble_restart_advertising();
}
}
// In ble_init():
esp_timer_create_args_t timer_args = {
.callback = uuid_switch_timer_callback,
.name = "uuid_switch"
};
esp_timer_create(&timer_args, &uuid_switch_timer);
esp_timer_start_once(uuid_switch_timer, 30 * 1000000); // 30s in microsecondsFile: src/ble_manager.c - Connection handler (after peer identified)
if (is_peer) {
peer_state.peer_connected = true;
peer_state.conn_handle = event->connect.conn_handle;
// Stop scanning once peer connected
if (scanning_active) {
int scan_rc = ble_gap_disc_cancel();
...
}
// NEW: Switch to Config UUID immediately after peer pairing
// This allows mobile app to connect while peer is still connected
ESP_LOGI(TAG, "Peer connected - restarting advertising with Config Service UUID");
ble_restart_advertising(); // Will use Config UUID (peer now bonded)
}File: src/ble_manager.c - Connection event handler
BEFORE (Complex 4-path logic):
// Path 1: Check cached peer address
// Path 2: Check BLE connection role
// Path 3a: Check scanning active AND no peer connected
// Path 3b: Check grace period (38s)
// Path 4: Default to appAFTER (Simple UUID-based logic):
// Determine connection type based on currently advertised UUID
const ble_uuid128_t *current_uuid = ble_get_advertised_uuid();
bool is_peer = false;
if (current_uuid == &uuid_bilateral_service) {
// Advertising Bilateral UUID - this is a peer connection
is_peer = true;
ESP_LOGI(TAG, "Peer identified (connected during Bilateral UUID window)");
} else {
// Advertising Config UUID - this is an app connection
// Exception: Bonded peer reconnecting by address
if (peer_state.peer_connected || is_bonded_to_peer(desc.peer_id_addr)) {
is_peer = true;
ESP_LOGI(TAG, "Peer identified (bonded reconnection)");
} else {
is_peer = false;
ESP_LOGI(TAG, "Mobile app connected (Config UUID window)");
}
}File: src/ble_manager.c
BEFORE:
uint32_t grace_period_end = 30000 + 8000; // 38s totalAFTER:
// No grace period needed - UUID switching handles peer discovery timing
// Connections during Bilateral UUID (0-30s) = peers
// Connections during Config UUID (30s+) = apps- Eliminates misidentification: PWAs physically cannot discover device during peer pairing window
- Simpler logic: Connection type determined by advertised UUID, not complex state machine
- Zero latency: No GATT discovery needed post-connection
- Better UX: Clear separation - peers pair first, then apps can connect
- Fewer bugs: Less complex code = fewer edge cases = fewer bugs
- Industry standard: UUID-based filtering is standard BLE practice
-
Fresh boot, peer pairing:
- Both devices boot within 30s
- Both advertise Bilateral UUID
- Both discover each other and connect
- After pairing, both switch to Config UUID
- Mobile app can now discover and connect
-
Fresh boot, single device:
- Device boots, advertises Bilateral UUID
- Mobile app cannot see device for first 30s
- At 30s, device switches to Config UUID
- Mobile app can now discover and connect
-
Bonded peer reconnection:
- Device reboots with bonded peer in NVS
- Immediately advertises Config UUID (peer already bonded)
- Bonded peer reconnects by address (not by UUID scan)
- Mobile app can also connect simultaneously
-
Late peer startup (edge case):
- Device A boots at t=0
- Device B boots at t=25s
- Both advertise Bilateral UUID
- Devices discover and pair by t=29s
- At t=30s, both switch to Config UUID
- Result: Successful pairing
-
Very late peer startup (expected failure):
- Device A boots at t=0
- Device B boots at t=35s (after 30s window)
- Device A advertises Config UUID, Device B advertises Bilateral UUID
- Devices cannot discover each other (different UUIDs)
- Result: Pairing fails (expected - outside pairing window)
- User must reboot both devices within 30s of each other
src/ble_manager.c- Main implementationsrc/ble_manager.h- Addble_restart_advertising()if neededsrc/ble_task.c- Add 30s UUID switch timer checkdocs/architecture_decisions.md- Update AD036 or create AD037CHANGELOG.md- Document UUID-switching implementation and Bug #28 fix
- Implement boot timestamp tracking ✅
- Create
ble_get_advertised_uuid()helper ✅ - Modify advertising setup ✅
- Modify scanning logic ✅
- Add 30s UUID switch trigger ✅
- Simplify connection identification ✅
- Test on hardware 🔄
- Update documentation ✅
- Bug #27: PWA misidentified as peer - ELIMINATED by UUID-switching
- Bug #26: Late peer rejection - ELIMINATED by UUID-switching (pairing window is clear)
- Bug #28: Button unresponsiveness - FIXED (removed blocking status_led_pattern calls)
- AD036: State-based identification - SUPERSEDED by UUID-switching approach
Status: Ready for implementation (plan approved by user)