# EdgeCommand: Persistent Commands Example Demonstrates that command records — and therefore deduplication — survive an engine restart, because they are backed by NVS rather than RAM. ## Purpose - Submit a command and let it complete. - Tear the engine down completely (`edgecommand_deinit()`), which frees the entire in-memory slot table. - Bring up a brand-new engine instance against the same NVS namespace, standing in for a device reboot. - Show that `edgecommand_get_status()` still finds the finished command (loaded from NVS, not RAM) and that re-submitting the same command ID is rejected with `EDGECOMMAND_ERR_DUPLICATE`. A real power-cycle reboot demonstrates the same persistence even more directly — nothing here depends on staying in the same process, this in-process teardown/recreate is just easier to observe in a single log capture. To see it across an actual power cycle instead, submit the command, reset the board, and re-run only the second half of `main.c` (query + re-submit the same ID). ## Requirements - ESP-IDF v5.0 or newer. - Any ESP-IDF-supported target. - No external hardware required. ## Build and flash ```sh cd examples/persistent_commands idf.py set-target esp32 idf.py build flash monitor ``` ## Expected output ``` I (320) persistent_example: recording telemetry for command persist-demo-0001 I (420) persistent_example: before restart: command persist-demo-0001 finished as COMPLETED I (425) persistent_example: --- simulating restart: creating a fresh engine instance --- I (430) persistent_example: after restart: edgecommand_get_status(persist-demo-0001) -> ESP_OK, state=COMPLETED I (435) persistent_example: after restart: re-submitting persist-demo-0001 -> EDGECOMMAND_ERR_DUPLICATE (expected EDGECOMMAND_ERR_DUPLICATE) I (540) persistent_example: recording telemetry for command persist-demo-0002 I (640) persistent_example: new command persist-demo-0002 finished as COMPLETED ``` ## What's happening - `edgecommand_submit()` persists a record to NVS at every lifecycle transition (`RECEIVED` → `PERSISTED` → `PENDING` → ... → terminal), not only at the end. See `RELIABILITY.md` for exactly what's durable at each point and the crash window that still exists around the physical action a handler performs. - `edgecommand_deinit()` frees the in-memory slot table but never touches already-persisted NVS records — persistence is intentionally decoupled from process/engine lifetime. - The second `edgecommand_init()` call creates a fresh in-memory slot table (empty) but reuses the same underlying NVS namespace (`"edgecmd"` by default), so `edgecommand_dedup_check()` still finds the old record via `storage->load()` even though it's absent from the new engine's RAM. - See `examples/recovery` for what happens when a restart interrupts a command that was still `EXECUTING` or `PENDING`, which is a different (and more consequential) scenario than the already-`COMPLETED` command shown here.
To create a project from this example, run:
idf.py create-project-from-example "adhuldas/edgecommand=0.1.1:persistent_commands"