# EdgeCommand: Restart Recovery Example Demonstrates what happens to a command that was interrupted mid-execution by a device restart, under each of the three recovery policies. ## Purpose A real power-loss-during-execution can't be scripted deterministically in an automated example, so this one simulates it directly: it writes an `EDGECOMMAND_STATE_EXECUTING` record straight into a storage backend — exactly what would be on flash if a previous run had reached "start executing" and then lost power before recording a terminal result — and then calls `edgecommand_start()` to observe what each policy does with it: - **`EDGECOMMAND_RECOVERY_FAIL`** (the default): the interrupted command is immediately marked `FAILED`. Safe for any handler, including ones that perform a non-idempotent physical action (actuating a relay, writing to another device), because it never re-runs anything. - **`EDGECOMMAND_RECOVERY_RETRY`**: the interrupted command is re-queued and actually re-executed — with an **empty payload**, since the original payload was never persisted. Only safe for handlers that are idempotent and that can do something useful (or fail cleanly) without their original payload. - **`EDGECOMMAND_RECOVERY_MANUAL`**: the interrupted command is left exactly as found (still `EXECUTING`) for the application to inspect and resolve explicitly — EdgeCommand takes no automatic action. ## Requirements - ESP-IDF v5.0 or newer. - Any ESP-IDF-supported target. - No external hardware required. ## Build and flash ```sh cd examples/recovery idf.py set-target esp32 idf.py build flash monitor ``` ## Expected output ``` I (300) recovery_example: === recovery policy: FAIL === I (305) recovery_example: seeded 'interrupted-fail-demo' as EXECUTING, simulating a crash mid-execution I (320) recovery_example: outcome for 'interrupted-fail-demo' under FAIL: state=FAILED I (330) recovery_example: === recovery policy: RETRY === I (335) recovery_example: seeded 'interrupted-retry-demo' as EXECUTING, simulating a crash mid-execution I (355) recovery_example: resume_task_handler running for interrupted-retry-demo (payload_len=0) I (400) recovery_example: outcome for 'interrupted-retry-demo' under RETRY: state=COMPLETED I (410) recovery_example: === recovery policy: MANUAL === I (415) recovery_example: seeded 'interrupted-manual-demo' as EXECUTING, simulating a crash mid-execution I (425) recovery_example: outcome for 'interrupted-manual-demo' under MANUAL: state=EXECUTING I (430) recovery_example: done ``` Note that `interrupted-manual-demo` is reported back as still `EXECUTING` — that's correct and expected. `MANUAL` means EdgeCommand deliberately does not resolve it; a real application would surface this to an operator or apply its own domain-specific logic (e.g. query the physical device state to determine what actually happened) rather than trust the stale state. ## What's happening - Recovery runs synchronously inside `edgecommand_start()`, before the worker task is created — there is no concurrent submission or execution activity while it runs, so it's inherently race-free. - This example calls `edgecommand_storage_nvs_create()` itself (instead of leaving `edgecommand_config_t::storage` `NULL`) specifically so it can seed a record *before* `edgecommand_start()` runs. A normal application almost never needs to touch the storage backend directly like this — see the ownership note on `edgecommand_storage_nvs_create()` in `edgecommand_storage.h` for why this example must destroy the storage instance itself afterward, unlike the default (engine-owned) case. - See `RELIABILITY.md` for the full policy tradeoff discussion, including why `RETRY` is dangerous for non-idempotent handlers and exactly what is/isn't durable about a command's payload.
To create a project from this example, run:
idf.py create-project-from-example "adhuldas/edgecommand=0.1.1:recovery"