From c90aac85bff0591110f3f2778437640d2bd83f3c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 12:33:23 +0000 Subject: [PATCH 1/2] app_main: add 5 s BOOT-button factory reset to recover commissioning mode If GPIO9 (BOOT button, active-low) is held at startup for 5 s, erase nvs_matter and reboot. This clears the fabric table, PAKE verifier, ACL and group keys, returning the device to commissioning mode so that BLE advertising restarts and Home Assistant can rediscover the device. Release the button within 5 s to cancel without side-effects. https://claude.ai/code/session_01SxqAiQApiRTwXefk9aQ6bR --- main/app_main.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/main/app_main.cpp b/main/app_main.cpp index e0b082d..b6e1e6b 100644 --- a/main/app_main.cpp +++ b/main/app_main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -422,6 +423,28 @@ extern "C" void app_main() gpio_set_level(GPIO_NUM_3, 0); gpio_set_level(GPIO_NUM_14, 1); + // Factory reset via BOOT button (GPIO9). + // Hold GPIO9 low for 5 s at startup to erase nvs_matter (fabric table, + // PASE verifier, ACL, group keys) and reboot into commissioning mode. + // GPIO9 is the active-low BOOT button on the XIAO ESP32-C6. + gpio_set_direction(GPIO_NUM_9, GPIO_MODE_INPUT); + gpio_set_pull_mode(GPIO_NUM_9, GPIO_PULLUP_ONLY); + if (gpio_get_level(GPIO_NUM_9) == 0) { + ESP_LOGW(kTag, "BOOT held — factory reset in 5 s, release to cancel"); + for (int i = 5; i > 0; --i) { + vTaskDelay(pdMS_TO_TICKS(1000)); + if (gpio_get_level(GPIO_NUM_9) != 0) { + ESP_LOGI(kTag, "Factory reset cancelled"); + goto skip_factory_reset; + } + ESP_LOGW(kTag, "Factory reset in %d s…", i - 1); + } + ESP_LOGW(kTag, "Erasing nvs_matter — device will reboot into commissioning mode"); + nvs_flash_erase_partition("nvs_matter"); + esp_restart(); + } + skip_factory_reset: + // General-purpose NVS partition: auto-erased on unrecoverable corruption. // Holds only non-Matter app state; safe to wipe because all Matter // credentials live on the protected nvs_matter partition. From 65974edb97263cc612be7622fac929aad191241a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 12:42:56 +0000 Subject: [PATCH 2/2] app_main: factory reset clears only fabric/config namespaces, not chip-factory Replacing nvs_flash_erase_partition("nvs_matter") with targeted NVS namespace erases: chip-config (fabric table, ACL, group keys, subscriptions) and chip-counters are cleared, but chip-factory (discriminator, PAKE verifier, DAC) is preserved. Erasing the whole partition deleted write-once attestation material that cannot be recovered without re-flashing, leaving the device unable to commission again. https://claude.ai/code/session_01SxqAiQApiRTwXefk9aQ6bR --- main/app_main.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/main/app_main.cpp b/main/app_main.cpp index b6e1e6b..750f44d 100644 --- a/main/app_main.cpp +++ b/main/app_main.cpp @@ -424,9 +424,15 @@ extern "C" void app_main() gpio_set_level(GPIO_NUM_14, 1); // Factory reset via BOOT button (GPIO9). - // Hold GPIO9 low for 5 s at startup to erase nvs_matter (fabric table, - // PASE verifier, ACL, group keys) and reboot into commissioning mode. - // GPIO9 is the active-low BOOT button on the XIAO ESP32-C6. + // Hold GPIO9 low for 5 s at startup to clear fabric/config state and + // reboot into commissioning mode. GPIO9 is the active-low BOOT button + // on the XIAO ESP32-C6. + // + // Only the chip-config and chip-counters NVS namespaces are erased; + // chip-factory (discriminator, PAKE verifier, DAC) is intentionally + // preserved so the device can commission again with its original + // credentials. Erasing the whole nvs_matter partition would destroy + // factory data that cannot be recovered without re-flashing. gpio_set_direction(GPIO_NUM_9, GPIO_MODE_INPUT); gpio_set_pull_mode(GPIO_NUM_9, GPIO_PULLUP_ONLY); if (gpio_get_level(GPIO_NUM_9) == 0) { @@ -439,8 +445,19 @@ extern "C" void app_main() } ESP_LOGW(kTag, "Factory reset in %d s…", i - 1); } - ESP_LOGW(kTag, "Erasing nvs_matter — device will reboot into commissioning mode"); - nvs_flash_erase_partition("nvs_matter"); + ESP_LOGW(kTag, "Clearing fabric/config state — preserving chip-factory"); + // Mount the partition so we can open individual namespaces. + nvs_flash_init_partition("nvs_matter"); + static const char *kClearNs[] = { "chip-config", "chip-counters" }; + for (const char *ns : kClearNs) { + nvs_handle_t h; + if (nvs_open_from_partition("nvs_matter", ns, NVS_READWRITE, &h) == ESP_OK) { + nvs_erase_all(h); + nvs_commit(h); + nvs_close(h); + ESP_LOGI(kTag, "Factory reset: cleared '%s'", ns); + } + } esp_restart(); } skip_factory_reset: