Power-loss-aware step sequencer for embedded C firmware.
loxseq is the Liquid Oxygen Sequencer: a small, heap-free C99 library that checkpoints step progress to caller-provided storage and computes a recovery verdict on reboot. LOX here means Liquid Oxygen; loxseq is one component of the LOX embedded C ecosystem.
-
Add the library with CMake:
find_package(loxseq CONFIG REQUIRED) target_link_libraries(your_target PRIVATE loxseq::loxseq)
For full C and C++ consumer examples, see
examples/consumer_candexamples/consumer_cpp. -
Define a step table with tags, actions, timeouts, and resume policy. The minimal pattern is shown below, and the recipe version is in
docs/cookbook.md. -
Provide a storage backend with
write_checkpoint,read_checkpoint, anderase_checkpoint. The backend must persist the portable 32-byte checkpoint bytes, not a raw C struct image. -
On boot, call
loxseq_recover(), then choose one of:loxseq_start_fresh(),loxseq_start_resume(),loxseq_start_restart(),loxseq_start_safe_init(), orloxseq_start_operator_wait(). -
Handle
LOXSEQ_RECOVERY_SAFE_INITby running your safe-init path, and treat completed/aborted/failed terminal checkpoints as tombstones that do not resume the prior step. -
For elapsed time inside a step, use
loxseq_step_age_ms(). It is boot-local and does not restore wall-clock time across reboot.
- Public header:
include/loxseq/loxseq.h - Recovery model:
docs/resume-model.md - Storage contract:
docs/storage-contract.md - Limitations:
docs/limitations.md - Cookbook / recipes:
docs/cookbook.md
- Persistent checkpoint record (CRC-protected)
- Per-step resume policy with conservative downgrades
- No heap / globals / floats; caller-owned state
- Tick-driven execution: you provide now_ms
- Optional branching via loxseq_set_next_step + LOXSEQ_STEP_BRANCH
#include "loxseq/loxseq.h"
enum { S_FILL, S_HEAT, S_DRAIN, STEP_COUNT };
static loxseq_step_status_t step_fill(loxseq_t *s, uint32_t now_ms, void *u);
static loxseq_step_status_t step_heat(loxseq_t *s, uint32_t now_ms, void *u);
static loxseq_step_status_t step_drain(loxseq_t *s, uint32_t now_ms, void *u);
static const loxseq_step_def_t steps[STEP_COUNT] = {
[S_FILL] = { .tag = "fill", .action = step_fill, .timeout_ms = 60000, .resume_policy = LOXSEQ_RESUME_AT_STEP },
[S_HEAT] = { .tag = "heat", .action = step_heat, .timeout_ms = 600000, .resume_policy = LOXSEQ_RESUME_FROM_START },
[S_DRAIN] = { .tag = "drain", .action = step_drain, .timeout_ms = 120000, .resume_policy = LOXSEQ_NEVER_RESUME },
};
#include <string.h>
static uint8_t ram_buf[64];
static size_t ram_len;
static int ram_write(const void *buf, size_t len, void *user) {
(void)user;
if (len > sizeof(ram_buf)) return -1;
memcpy(ram_buf, buf, len);
ram_len = len;
return 0;
}
static int ram_read(void *buf, size_t len, void *user) {
(void)user;
if (ram_len == 0) return -1;
if (len != ram_len) return -1;
memcpy(buf, ram_buf, len);
return 0;
}
static int ram_erase(void *user) { (void)user; ram_len = 0; return 0; }
static const loxseq_storage_t storage = {
.write_checkpoint = ram_write,
.read_checkpoint = ram_read,
.erase_checkpoint = ram_erase,
};
loxseq_t seq;
loxseq_init(&seq, steps, STEP_COUNT, &storage);
loxseq_recovery_verdict_t v = loxseq_recover(&seq, reboot_reason);
switch (v) {
case LOXSEQ_RECOVERY_COLD_START: loxseq_start_fresh(&seq, now_ms); break;
case LOXSEQ_RECOVERY_RESUME: loxseq_start_resume(&seq, now_ms); break;
case LOXSEQ_RECOVERY_RESTART: loxseq_start_restart(&seq, now_ms); break;
case LOXSEQ_RECOVERY_SAFE_INIT: loxseq_start_safe_init(&seq, now_ms); break;
case LOXSEQ_RECOVERY_OPERATOR: loxseq_start_operator_wait(&seq); break;
}
cmake -S . -B build
cmake --build build --config Release
ctest --test-dir build --output-on-failure -C Release
The unit tests currently cover:
- CRC-16/CCITT-FALSE known vector
- invalid
loxseq_init()arguments - recovery downgrade behavior and cold-start fallbacks (read failure, CRC corruption, unsupported version, out-of-range step index)
- RESUME vs RESTART
step_entered_at_mssemantics (includingloxseq_operator_resolve(..., LOXSEQ_RECOVERY_RESTART, ...)) - reboot counter increment and 0xFF saturation on restart/resume
- branching behavior (with/without pending branch target)
- precondition gating, timeout, pause/resume, abort
- completion erases checkpoint; erase failure returns
LOXSEQ_ERR_STORAGE - write failure on transition fails the sequencer
loxseq_current_tag()behavior andloxseq_step_age_ms()wraparound- compile-time record layout checks (size and field offsets)
Configured CI additionally checks:
- cross-platform build/test coverage on Ubuntu, Windows, and macOS
- sanitizer coverage with ASan/UBSan on Ubuntu
- installed-package consumer builds for
examples/consumer_candexamples/consumer_cpp - release-gate version truth against the tag, public header,
CMakeLists.txt, andCHANGELOG.md
- docs/resume-model.md
- docs/storage-contract.md
- docs/integration.md
- docs/cookbook.md
- docs/limitations.md
- docs/evidence-matrix.md
- docs/release-checklist.md
MIT (see LICENSE).