Skip to content

Commit cb745e4

Browse files
Merge pull request #7 from RobertDaleSmith/rds/multi-bond-support
Add async firmware cache clearing for multi-bond support
2 parents 493fc90 + a741309 commit cb745e4

6 files changed

Lines changed: 352 additions & 11 deletions

File tree

ncs/app/src/ble_dis.c

Lines changed: 235 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,81 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME);
3131
#define BT_UUID_DIS_MFR_NAME BT_UUID_DECLARE_16(BT_UUID_DIS_MFR_NAME_VAL)
3232

3333
/* Device Information client state */
34-
static ble_dis_info_t device_info;
34+
static ble_dis_info_t device_info; /* Active device's DIS info */
3535
static bool dis_ready = false;
3636
static struct bt_conn *current_conn = NULL;
3737
static struct bt_gatt_read_params read_params;
3838

39+
/* In-memory cache for all bonded devices' DIS info (loaded from flash at boot) */
40+
#define MAX_DIS_CACHE_ENTRIES 4
41+
struct dis_cache_entry {
42+
bt_addr_le_t addr;
43+
ble_dis_info_t info;
44+
bool valid;
45+
};
46+
static struct dis_cache_entry dis_cache[MAX_DIS_CACHE_ENTRIES];
47+
48+
/* Mutex to protect dis_cache from concurrent access */
49+
static K_MUTEX_DEFINE(dis_cache_mutex);
50+
51+
/* Forward declarations needed by work handler */
52+
static void build_dis_settings_key(const bt_addr_le_t *addr, char *key, size_t key_len);
53+
int ble_dis_load_info_for_addr(const bt_addr_le_t *addr, ble_dis_info_t *out_info);
54+
55+
/* Work queue item for deferred flash writes */
56+
static struct k_work clear_fw_cache_work;
57+
static bool clear_fw_cache_pending = false;
58+
59+
static void clear_fw_cache_work_handler(struct k_work *work)
60+
{
61+
ARG_UNUSED(work);
62+
63+
LOG_INF("Clearing cached firmware version for all bonded devices (deferred)");
64+
65+
/* Get list of bonded devices from ble_central */
66+
extern int ble_central_get_bonded_devices(struct bonded_device *out_list, size_t max_count);
67+
68+
struct bonded_device bonds[4];
69+
int count = ble_central_get_bonded_devices(bonds, 4);
70+
71+
if (count <= 0) {
72+
LOG_DBG("No bonded devices to clear firmware from");
73+
clear_fw_cache_pending = false;
74+
return;
75+
}
76+
77+
/* Clear firmware version for each bonded device */
78+
for (int i = 0; i < count; i++) {
79+
if (bonds[i].is_valid) {
80+
/* Load existing DIS info */
81+
ble_dis_info_t dis_info;
82+
int err = ble_dis_load_info_for_addr(&bonds[i].addr, &dis_info);
83+
if (err != 0) {
84+
continue;
85+
}
86+
87+
/* Clear only the firmware version field */
88+
dis_info.has_firmware_version = false;
89+
dis_info.firmware_version[0] = '\0';
90+
91+
/* Save back to flash storage */
92+
char key[64];
93+
build_dis_settings_key(&bonds[i].addr, key, sizeof(key));
94+
err = settings_save_one(key, &dis_info, sizeof(ble_dis_info_t));
95+
if (err) {
96+
LOG_ERR("Failed to save DIS info after clearing firmware (err %d)", err);
97+
} else {
98+
char addr_str[BT_ADDR_LE_STR_LEN];
99+
bt_addr_le_to_str(&bonds[i].addr, addr_str, sizeof(addr_str));
100+
LOG_INF("Cleared cached firmware in flash for: %s", addr_str);
101+
}
102+
}
103+
}
104+
105+
clear_fw_cache_pending = false;
106+
LOG_INF("Cleared cached firmware for %d device(s) in flash", count);
107+
}
108+
39109
/* Characteristic handles */
40110
static uint16_t fw_rev_handle = 0;
41111
static uint16_t hw_rev_handle = 0;
@@ -101,21 +171,65 @@ static int save_dis_info_to_settings(const bt_addr_le_t *addr)
101171
}
102172

103173
LOG_INF("Saved DIS info to persistent storage: %s", key);
174+
175+
/* Also update in-memory cache (protected by mutex) */
176+
k_mutex_lock(&dis_cache_mutex, K_FOREVER);
177+
bool found = false;
178+
for (int i = 0; i < MAX_DIS_CACHE_ENTRIES; i++) {
179+
if (dis_cache[i].valid && bt_addr_le_cmp(&dis_cache[i].addr, addr) == 0) {
180+
/* Update existing entry */
181+
memcpy(&dis_cache[i].info, &device_info, sizeof(ble_dis_info_t));
182+
LOG_DBG("Updated in-memory cache entry %d", i);
183+
found = true;
184+
break;
185+
}
186+
}
187+
188+
/* If not found, add new entry */
189+
if (!found) {
190+
for (int i = 0; i < MAX_DIS_CACHE_ENTRIES; i++) {
191+
if (!dis_cache[i].valid) {
192+
memcpy(&dis_cache[i].addr, addr, sizeof(bt_addr_le_t));
193+
memcpy(&dis_cache[i].info, &device_info, sizeof(ble_dis_info_t));
194+
dis_cache[i].valid = true;
195+
LOG_INF("Added new in-memory cache entry %d", i);
196+
break;
197+
}
198+
}
199+
}
200+
k_mutex_unlock(&dis_cache_mutex);
201+
104202
return 0;
105203
}
106204

107205
static int settings_set_cb(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg)
108206
{
109207
LOG_DBG("DIS settings_set_cb called: name='%s', len=%d", name, len);
110208

111-
/* Settings are now per-device (ble_dis/<addr>/info), loaded on-demand */
112-
/* Just acknowledge all DIS settings without loading them globally */
209+
/* Settings are per-device: ble_dis/<addr>/info */
210+
/* Load all cached DIS info into memory at boot */
113211
if (len == sizeof(ble_dis_info_t)) {
114-
/* Valid DIS info size, consume it but don't load globally */
115212
ble_dis_info_t temp_info;
116213
ssize_t bytes_read = read_cb(cb_arg, &temp_info, sizeof(ble_dis_info_t));
117214
if (bytes_read == sizeof(ble_dis_info_t)) {
118-
LOG_DBG("Found DIS info in settings: %s", name);
215+
/* Parse address from key format: "<addr>/info" */
216+
bt_addr_le_t addr;
217+
if (bt_addr_le_from_str(name, "RPA", &addr) == 0 ||
218+
bt_addr_le_from_str(name, "random", &addr) == 0 ||
219+
bt_addr_le_from_str(name, "public", &addr) == 0) {
220+
/* Find empty slot in cache (protected by mutex) */
221+
k_mutex_lock(&dis_cache_mutex, K_FOREVER);
222+
for (int i = 0; i < MAX_DIS_CACHE_ENTRIES; i++) {
223+
if (!dis_cache[i].valid) {
224+
memcpy(&dis_cache[i].addr, &addr, sizeof(bt_addr_le_t));
225+
memcpy(&dis_cache[i].info, &temp_info, sizeof(ble_dis_info_t));
226+
dis_cache[i].valid = true;
227+
LOG_INF("Loaded DIS cache entry %d from flash: has_fw=%d", i, temp_info.has_firmware_version);
228+
break;
229+
}
230+
}
231+
k_mutex_unlock(&dis_cache_mutex);
232+
}
119233
return 0;
120234
}
121235
}
@@ -134,6 +248,9 @@ int ble_dis_init(void)
134248
*/
135249
dis_ready = false;
136250

251+
/* Initialize work queue for deferred flash writes */
252+
k_work_init(&clear_fw_cache_work, clear_fw_cache_work_handler);
253+
137254
LOG_INF("DIS init - device_info state: has_fw=%d, fw='%s', has_pnp=%d, vid=0x%04X, pid=0x%04X",
138255
device_info.has_firmware_version, device_info.firmware_version,
139256
device_info.has_pnp_id, device_info.vendor_id, device_info.product_id);
@@ -258,12 +375,121 @@ void ble_dis_clear_saved(void)
258375
/* This function now just clears the global cache */
259376
}
260377

378+
void ble_dis_clear_cached_firmware_for_addr(const bt_addr_le_t *addr)
379+
{
380+
if (!addr || !IS_ENABLED(CONFIG_SETTINGS)) {
381+
return;
382+
}
383+
384+
/* Load existing DIS info */
385+
ble_dis_info_t dis_info;
386+
int err = ble_dis_load_info_for_addr(addr, &dis_info);
387+
388+
if (err != 0) {
389+
LOG_DBG("No cached DIS info to clear firmware from");
390+
return;
391+
}
392+
393+
/* Clear only the firmware version field */
394+
dis_info.has_firmware_version = false;
395+
dis_info.firmware_version[0] = '\0';
396+
397+
/* Save back to flash storage */
398+
char key[64];
399+
build_dis_settings_key(addr, key, sizeof(key));
400+
401+
err = settings_save_one(key, &dis_info, sizeof(ble_dis_info_t));
402+
if (err) {
403+
LOG_ERR("Failed to save DIS info after clearing firmware (err %d)", err);
404+
} else {
405+
char addr_str[BT_ADDR_LE_STR_LEN];
406+
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
407+
LOG_INF("Cleared cached firmware version in flash for device: %s", addr_str);
408+
}
409+
410+
/* Also clear from in-memory cache (protected by mutex) */
411+
k_mutex_lock(&dis_cache_mutex, K_FOREVER);
412+
for (int i = 0; i < MAX_DIS_CACHE_ENTRIES; i++) {
413+
if (dis_cache[i].valid && bt_addr_le_cmp(&dis_cache[i].addr, addr) == 0) {
414+
dis_cache[i].info.has_firmware_version = false;
415+
dis_cache[i].info.firmware_version[0] = '\0';
416+
LOG_INF("Cleared cached firmware version in memory cache entry %d", i);
417+
break;
418+
}
419+
}
420+
k_mutex_unlock(&dis_cache_mutex);
421+
}
422+
423+
void ble_dis_clear_all_cached_firmware(void)
424+
{
425+
LOG_INF("Clearing cached firmware version for all devices");
426+
427+
/* Clear all in-memory cache entries immediately (protected by mutex) */
428+
k_mutex_lock(&dis_cache_mutex, K_FOREVER);
429+
int cleared = 0;
430+
for (int i = 0; i < MAX_DIS_CACHE_ENTRIES; i++) {
431+
if (dis_cache[i].valid && dis_cache[i].info.has_firmware_version) {
432+
dis_cache[i].info.has_firmware_version = false;
433+
dis_cache[i].info.firmware_version[0] = '\0';
434+
cleared++;
435+
}
436+
}
437+
k_mutex_unlock(&dis_cache_mutex);
438+
439+
/* Also clear current device_info */
440+
device_info.has_firmware_version = false;
441+
device_info.firmware_version[0] = '\0';
442+
443+
LOG_INF("Cleared firmware from %d in-memory cache entries", cleared);
444+
445+
/* Submit work to clear flash asynchronously (non-blocking) */
446+
if (!clear_fw_cache_pending) {
447+
clear_fw_cache_pending = true;
448+
k_work_submit(&clear_fw_cache_work);
449+
LOG_INF("Submitted deferred flash clear work");
450+
}
451+
}
452+
453+
/* Load cached DIS from in-memory cache into device_info for the connected device */
454+
static void load_cache_for_addr(const bt_addr_le_t *addr)
455+
{
456+
if (!addr) {
457+
/* No address - clear device_info */
458+
memset(&device_info, 0, sizeof(device_info));
459+
return;
460+
}
461+
462+
/* Search for this address in the cache (protected by mutex) */
463+
k_mutex_lock(&dis_cache_mutex, K_FOREVER);
464+
bool found = false;
465+
for (int i = 0; i < MAX_DIS_CACHE_ENTRIES; i++) {
466+
if (dis_cache[i].valid && bt_addr_le_cmp(&dis_cache[i].addr, addr) == 0) {
467+
/* Found cached info for this device */
468+
memcpy(&device_info, &dis_cache[i].info, sizeof(ble_dis_info_t));
469+
found = true;
470+
break;
471+
}
472+
}
473+
k_mutex_unlock(&dis_cache_mutex);
474+
475+
if (found) {
476+
LOG_INF("Loaded cached DIS from memory: has_fw=%d, fw='%s'",
477+
device_info.has_firmware_version, device_info.firmware_version);
478+
} else {
479+
/* No cache found - clear device_info */
480+
memset(&device_info, 0, sizeof(device_info));
481+
LOG_DBG("No cached DIS found in memory for this device");
482+
}
483+
}
484+
485+
void ble_dis_load_cache_for_connected_device(const bt_addr_le_t *addr)
486+
{
487+
load_cache_for_addr(addr);
488+
}
489+
261490
bool ble_dis_has_cached_firmware(void)
262491
{
263-
/* Check if device_info has valid firmware version
264-
* This info is loaded from persistent storage on init via settings callback,
265-
* so it persists across reboots
266-
*/
492+
/* Check if device_info has valid firmware version */
267493
return device_info.has_firmware_version && (device_info.firmware_version[0] != '\0');
268494
}
269495

ncs/app/src/ble_dis.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,35 @@ void ble_dis_clear_saved_for_addr(const bt_addr_le_t *addr);
101101
*/
102102
void ble_dis_clear_saved(void);
103103

104+
/**
105+
* @brief Clear cached firmware version for a specific device
106+
*
107+
* Loads the cached DIS info, clears only the firmware version field,
108+
* and saves it back. This forces firmware version to be re-read on
109+
* next connection (useful after MouthPad firmware update).
110+
*
111+
* @param addr BLE address of the device
112+
*/
113+
void ble_dis_clear_cached_firmware_for_addr(const bt_addr_le_t *addr);
114+
115+
/**
116+
* @brief Clear cached firmware version for all bonded devices
117+
*
118+
* This should be called when initiating a firmware update flow to ensure
119+
* the dongle re-reads firmware version on next connection.
120+
*/
121+
void ble_dis_clear_all_cached_firmware(void);
122+
123+
/**
124+
* @brief Load cached DIS info from in-memory cache for connected device
125+
*
126+
* Called when device connects to populate device_info from the in-memory cache.
127+
* The in-memory cache is loaded from flash at boot.
128+
*
129+
* @param addr BLE address of the connected device
130+
*/
131+
void ble_dis_load_cache_for_connected_device(const bt_addr_le_t *addr);
132+
104133
/**
105134
* @brief Check if we have cached device info with firmware version
106135
*

ncs/app/src/ble_transport.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ static void nus_discovery_completed_cb(void)
8484
LOG_INF("=== NUS DISCOVERY COMPLETED ===");
8585
nus_discovery_complete = true;
8686

87+
/* Load cached DIS info from in-memory cache for the connected device */
88+
struct bt_conn *conn = ble_central_get_default_conn();
89+
if (conn) {
90+
const bt_addr_le_t *addr = bt_conn_get_dst(conn);
91+
extern void ble_dis_load_cache_for_connected_device(const bt_addr_le_t *addr);
92+
ble_dis_load_cache_for_connected_device(addr);
93+
}
94+
8795
/* Check if we have cached firmware version from previous connection */
8896
extern bool ble_dis_has_cached_firmware(void);
8997
if (ble_dis_has_cached_firmware()) {

ncs/app/src/main.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,22 @@ static int cmd_bonds(const struct shell *sh, size_t argc, char **argv)
170170
return 0;
171171
}
172172

173+
/* Shell command: Clear cached firmware versions */
174+
static int cmd_clear_fw_cache(const struct shell *sh, size_t argc, char **argv)
175+
{
176+
ARG_UNUSED(argc);
177+
ARG_UNUSED(argv);
178+
179+
shell_print(sh, "Clearing cached firmware versions for all bonds...");
180+
extern void ble_dis_clear_all_cached_firmware(void);
181+
ble_dis_clear_all_cached_firmware();
182+
shell_print(sh, "Done. Firmware versions will be re-read on next connection.");
183+
184+
return 0;
185+
}
186+
173187
SHELL_CMD_REGISTER(bonds, NULL, "Display bonded devices", cmd_bonds);
188+
SHELL_CMD_REGISTER(clearfwcache, NULL, "Clear cached firmware versions", cmd_clear_fw_cache);
174189
SHELL_CMD_REGISTER(dfu, NULL, "Enter DFU bootloader mode", cmd_dfu);
175190
SHELL_CMD_REGISTER(reset, NULL, "Reset stored BLE bonds", cmd_reset);
176191
SHELL_CMD_REGISTER(restart, NULL, "Restart firmware", cmd_restart);
@@ -655,6 +670,21 @@ int main(void)
655670

656671
LOG_INF("Bonds cleared successfully, sending response");
657672
usb_cdc_send_proto_message_async(response);
673+
} else if (message.which_message_body == mouthware_message_AppToRelayMessage_clear_firmware_cache_write_tag) {
674+
/* Handle ClearFirmwareCacheWrite request */
675+
LOG_INF("=== CLEAR FIRMWARE CACHE REQUEST (via protobuf) ===");
676+
677+
/* Clear cached firmware versions for all bonded devices */
678+
extern void ble_dis_clear_all_cached_firmware(void);
679+
ble_dis_clear_all_cached_firmware();
680+
681+
/* Send success response */
682+
mouthware_message_RelayToAppMessage response = mouthware_message_RelayToAppMessage_init_zero;
683+
response.which_message_body = mouthware_message_RelayToAppMessage_clear_firmware_cache_response_tag;
684+
response.message_body.clear_firmware_cache_response.success = true;
685+
686+
LOG_INF("Firmware cache cleared, sending response");
687+
usb_cdc_send_proto_message_async(response);
658688
} else if (message.which_message_body == mouthware_message_AppToRelayMessage_dfu_write_tag) {
659689
/* Handle DfuWrite request - enter bootloader mode */
660690
LOG_INF("=== DFU REQUEST (via protobuf) - entering bootloader ===");

0 commit comments

Comments
 (0)