From 4fe5103e88be5f8a39ea5ef3573209393bf4f779 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 24 Jun 2026 20:51:58 +0300 Subject: [PATCH] =?UTF-8?q?docs(v0.3):=20=D1=81=D0=BF=D0=B5=D0=BA=D0=B0=20?= =?UTF-8?q?power-safe=20=D1=8F=D0=B4=D1=80=D0=B0=20(FSM=20+=20graceful=20s?= =?UTF-8?q?hutdown)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Веха v0.3: стаб Power → реальный lifecycle-FSM. Состояния off/accessory/running/ shutting_down{abortable,committed} (sleep/battery_cutoff — каркас); graceful shutdown (ShutdownImminent→grace→durable-barrier sync→commit=PONR=unmount); abort до PONR (re-power→ShutdownAborted). dev-mock кормит входы FSM. Watchdog/save-time/ монотоника. Подход A: FSM+сигналы, teardown через systemd/харнесс. Гибрид-E2E: N=3 in-VM цикла + 1 reboot + abort + power-cut-сим. HW (hold-up/MCU/B08-B09) — v0.4. Красные линии: Power не трогает CAN, без actuator (#2). Co-Authored-By: Claude Opus 4.8 Signed-off-by: Alexander --- docs/specs/v0.3-power-safe.md | 241 ++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 docs/specs/v0.3-power-safe.md diff --git a/docs/specs/v0.3-power-safe.md b/docs/specs/v0.3-power-safe.md new file mode 100644 index 0000000..416d778 --- /dev/null +++ b/docs/specs/v0.3-power-safe.md @@ -0,0 +1,241 @@ +# Спека реализации: v0.3 — Power-safe ядро (FSM + graceful shutdown) + +> Веха `v0.3` роадмапа: «переживает срыв питания». Capabilities **B01** (детект ACC), **B02** (graceful +> shutdown sequencing), **B03** (FSM + abort/committed), **B04** (`ru.shturman.Power`), **B05** (watchdog), +> **B06** (load-shedding), **B07** (save last-known-time), **A14** (HW-watchdog + recovery). Поверх **v0.2**. +> Источники: `docs/domains/b-power-lifecycle.md`, `docs/contracts/safety.md`, `docs/contracts/hardware.md`, +> `docs/contracts/ipc.md` §3, `docs/roadmap.md` §v0.3. Приёмка роадмапа: **«N циклов зажигания без потери `/data`; +> abort до PONR»**. + +--- + +## 1. Цель и первый артефакт + +Оживить **стаб** Power (v0.1: плоский `State`, mock флипает ignition/power) в **реальный lifecycle-FSM**: +ACC → graceful shutdown с **durable-write до PONR** → переживание срыва питания. + +**Первый артефакт:** fake-ACC-off → FSM `running`→`shutting_down` → `ShutdownImminent` → grace → commit +(durable-write barrier `sync` → unmount `/data` = PONR); **N=3 цикла зажигания** — `/data` цел; **abort до PONR** +(re-power → `ShutdownAborted` → `running`); **power-cut-сим** (SIGKILL до fsync → `/data` консистентен). + +**Не цель v0.3:** реальный hold-up cap / MCU-протокол / fail-safe-таймер и выбор **B08/B09** (MCU vs supercap) — +**v0.4** (вероятно нужна аппаратная проверка); реальный `/dev/watchdog` арминг — HW; полные sleep/wake/long-park — +v1/v2; перф-вердикт — RK3588. + +--- + +## 2. Скоуп и границы + +### 2.1 В скоупе (делаем сейчас) + +- **FSM питания (B03):** чистый модуль `PowerFsm` — состояния, события, переходы, действия. Юнит-тестируемый + без D-Bus. Внутренние субсостояния `ShuttingDown{Abortable, Committed}` маппятся в D-Bus `shutting_down`. +- **Детект ACC-логика (B01):** debounce/гистерезис + crank-приоритет — **формализованы в FSM** (вход `AccOff` + принимается только как стабильный; в VM источник — fake-ACC dev-mock). Реальный GPIO/MCU-детект — HW. +- **Graceful shutdown sequencing core (B02):** `ShutdownImminent(sec, reason)` → grace-окно → **durable-write + barrier** (`sync(2)`; Settings уже синхронен) → commit → unmount (PONR). Ordered teardown апов/CAN — позже (§2.2). +- **Abort до PONR (B03):** re-power в abortable → `ShutdownAborted` → назад в `running`/`accessory`. +- **Load-shedding (B06):** хук на commit/power-loss — в v0 **лог** (реальных нагрузок нет; рейлы amp/подсветка/ + модем — HW). +- **Watchdog (B05/A14):** systemd `RuntimeWatchdogSec` (runtime) + `RebootWatchdogSec` (shutdown-фаза) — **конфиг + + дисциплина**. Реальный `/dev/watchdog`/MCU-арминг — HW. +- **Save last-known-time (B07):** `shturman-savetime.timer`+`.service` — periodic fake-hwclock save (`/data`) + + on-shutdown save (в graceful sequence до PONR). `fake-hwclock`→`/data` — уже из v0.6. +- **Монотоника (§8):** `Uptime` + grace-timer + все lifecycle-таймеры на `CLOCK_MONOTONIC` + (`shturman_common::monotonic_secs` уже есть). НЕ wall-clock. +- **Харнесс:** FSM-юниты (каждый переход) + integration (сигналы по session-шине) + E2E **гибрид** (§9). + +### 2.2 Явно НЕ в скоупе (отложено, с указателем «куда») + +- **Hold-up cap / supercap, MCU-копилот, MCU fail-safe-таймер, SoC↔MCU heartbeat/`safe-to-cut`, реальный + power-cut energy budget + дератинг по T** — **HW** (hardware §3); **выбор B08/B09 (MCU vs supercap-only) — v0.4**. +- **Реальный `/dev/watchdog` арминг + bootcount-handshake recovery** — **HW/v4** (в VM watchdog-device нет). +- **Полные sleep/wake + long-park battery-cutoff** (низкопотребление, wake-on-ACC/таймер/реверс) — **v1/v2** (B §7). + В v0.3 состояния `sleep`/`battery_cutoff` — **зарезервированы** (переходы заглушены). +- **Consumer-ack save-протокол** (`ShutdownImminent`→consumers save→ack/timeout, сумма ≤ hold-up-бюджет) — + **App-Host v3** (в v0 Settings durable-write синхронен → ack не нужен). +- **E гасит OBD-TX / закрывает ISO-TP при shutdown** — **домен E / v1** (Power **не трогает CAN**, §3). +- **Перф-вердикт** (time-to-shutdown, hold-time) — **RK3588** (performance §2). В VM — функционально. + +### 2.3 Частично в скоупе (каркас сейчас, тело — позже) + +- **`sleep`/`battery_cutoff`** — состояния в enum/FSM есть, переходы **no-op/заглушка** (тело — v1/v2). +- **Load-shedding** — лог-хук (реальные рейлы — HW). +- **Watchdog** — конфиг systemd (реальный арминг — HW). +- **ACC-детект** — debounce-логика в FSM (источник в VM — fake-ACC; реальный GPIO/MCU — HW). + +### 2.4 Трассируемость ID → статус + +| ID | Веха | Статус в v0.3 | +|----|------|----------------| +| B01 | Детект ACC (debounce + crank) | логика в FSM; источник VM = fake-ACC (реальный GPIO/MCU — HW) | +| B02 | Graceful shutdown sequencing | core: ShutdownImminent→grace→durable-barrier→commit (teardown апов/CAN — позже) | +| B03 | FSM + abort/committed | полностью (sleep/battery_cutoff — каркас) | +| B04 | `ru.shturman.Power` | оживлён из FSM (сигналы/состояние — реальные переходы) | +| B05 | Watchdog | конфиг RuntimeWatchdogSec/RebootWatchdogSec + дисциплина (реальный WDT — HW) | +| B06 | Load-shedding | лог-хук (реальные нагрузки — HW) | +| B07 | Save last-known-time | periodic timer + on-shutdown save в `/data` | +| A14 | HW-watchdog + recovery | конфиг + дисциплина (реальный арминг/recovery — HW/v4) | + +--- + +## 3. Красные линии, безопасность + +- **#2 (нерушимо):** Power **не трогает CAN** и **не имеет actuator-путей** — только software-оркестрация + lifecycle + **read-only** состояние. CAN-TX-гашение при shutdown — **домен E (v1)**, не Power. Граница — safety.md. +- **#5 (power-safe):** durable-write до PONR — главная гарантия; FSM коммитит только после grace + `sync`. Реальный + power-cut на HW; в VM — функциональная модель + атомарность файла (foundation §9.1) уже доказана. +- **Прод-гейт:** dev-mock `PowerMock1` (fake-ACC/voltage/thermal) — за фичей `dev-mocks`; прод `--no-default-features` + → не регистрируется (foundation §5.2, §8.3). + +--- + +## 4. Раскладка (новые/изменённые артефакты) + +- **`crates/core/shturman-power/src/fsm.rs`** (новый) — `PowerFsm`: `State`, `Event`, `Action`, `fn step(&mut self, + Event) -> Vec`. Чистый, без D-Bus/async. Grace-таймер — снаружи (сервис), FSM лишь даёт `StartGrace(sec)`/ + принимает `GraceExpired`. +- **`crates/core/shturman-power/src/service.rs`** — обернуть FSM: D-Bus state/properties/signals **из FSM**; + dev-mock методы **кормят FSM-события** (не флипают `State` напрямую). +- **`crates/core/shturman-power/src/main.rs`** — grace-таймер (монотоника, tokio), durable-write barrier (`sync`), + трансляция FSM-actions → D-Bus-сигналы. +- **systemd:** `shturman-power.service` drop-in `RuntimeWatchdogSec=` (дисциплина); `shturman-savetime.service`+ + `.timer` (B07 periodic save); system `RebootWatchdogSec=` (shutdown-дедлайн). Раскладка — lima/E2E. +- **harness:** `tests/e2e/run.sh` — блок power-safe (гибрид §9); integration-тесты в `crates/core/shturman-power/tests/`. + +--- + +## 5. FSM питания (B03) — контракт + +**Состояния (внутренние):** `Off`, `Accessory`, `Running`, `ShuttingDown { phase: Abortable | Committed, reason }`, +`Sleep`*, `BatteryCutoff`* (`*` — зарезервированы). **D-Bus-маппинг** (`PowerState`): `ShuttingDown{*}` → `shutting_down`; +остальные 1:1. + +**События (`Event`):** `AccOn`, `AccOff`, `EngineOn`, `EngineOff` (accessory↔running по напряжению; VM — mock), +`UnderVoltage`, `ThermalTrip`, `GraceExpired`. (Re-power = `AccOn` во время shutdown.) + +**Переходы:** + +| Из | Событие | В | Действия | +|----|---------|---|----------| +| `Off` | `AccOn` | `Accessory` | `EmitAccChanged(true)` | +| `Accessory` | `EngineOn` | `Running` | — | +| `Running` | `EngineOff` | `Accessory` | — | +| `Accessory`/`Running` | `AccOff` | `ShuttingDown{Abortable, acc_off}` | `EmitShutdownImminent(sec, acc_off)`, `StartGrace(sec)` | +| `Accessory`/`Running` | `UnderVoltage` | `ShuttingDown{Abortable, under_voltage}` | `EmitShutdownImminent(sec, under_voltage)`, `StartGrace(sec)` | +| `Accessory`/`Running` | `ThermalTrip` | `ShuttingDown{Abortable, thermal}` | `EmitShutdownImminent(sec, thermal)`, `StartGrace(sec)` | +| `ShuttingDown{Abortable}` | `AccOn` (re-power) | `Running` | `EmitShutdownAborted`, `EmitAccChanged(true)` | +| `ShuttingDown{Abortable}` | `GraceExpired` | `ShuttingDown{Committed}` | `Commit` (durable-barrier → PONR) | +| `ShuttingDown{Committed}` | — | `Off` | (cut: unmount + снятие питания — systemd/харнесс/HW) | +| `Sleep`/`BatteryCutoff` | * | (no-op) | зарезервировано (v1/v2) | + +**Действия (`Action`):** `EmitShutdownImminent(reason)`, `EmitShutdownAborted`, `EmitAccChanged(bool)`, +`StartGrace(secs)`, `Commit`. `reason ∈ {acc_off, under_voltage, thermal, battery_cutoff}`. +**Инвариант:** после `Committed` abort невозможен (только → `Off`). +**Чистота:** `step` детерминирован, без I/O; сервис исполняет действия (сигналы/таймер/`sync`). Юнит-тест — каждый переход. + +--- + +## 6. Graceful shutdown (B02/B06) — последовательность (подход A) + +Power-сервис = **FSM + сигналы + grace/abort-окно**; реальный teardown (unmount/cut) — через systemd (реальный +poweroff) либо харнесс (in-VM-цикл). На железе — MCU/supercap-sequencing (v0.4). + +1. `AccOff` (стабильный; VM — fake-ACC) → `ShuttingDown{Abortable}` → **`ShutdownImminent(sec, acc_off)`** + grace-таймер + (монотоника). Потребители (приборка/будущие апы) получают сигнал и сохраняются. +2. **Abort-окно (abortable):** `AccOn` до `GraceExpired` → `EmitShutdownAborted` → `Running`; откат load-shed (v0: лог). +3. **`GraceExpired` → Commit:** save last-known-time (B07) → **durable-write barrier** (`sync(2)`; Settings уже + синхронен по каждому Set) → `Committed` (**= PONR**). +4. **PONR = unmount `/data`** (RW→RO): на реальном poweroff — systemd; в in-VM-цикле — харнесс; на HW — MCU/supercap + дают энергию завершить unmount/sync до cut. +5. **Load-shedding (B06):** на commit/power-loss — лог `«load-shed: amp/backlight/modem (реальных нагрузок нет в v0)»`; + hold-up кормит SoC+хранилище — HW. + +**Гарантия #5:** commit (и потому unmount/PONR) наступает **только после** grace + `sync` → усечённый shutdown +оставляет `/data` консистентным (атомарность файлов — foundation §9.1). + +--- + +## 7. D-Bus `ru.shturman.Power` — v0.3 оживляет (расширение foundation §5.2) + +- **Состояние/properties из FSM** (не из плоского `State`): `GetPowerState`, `IgnitionState`, `PowerSource`, `Uptime`. +- **Сигналы из FSM-actions** (не из mock-флипа): `AccChanged`, `ShutdownImminent(sec, reason)`, `ShutdownAborted`. + `Sleep`/`Wake` — **объявлены, не эмитятся** (sleep — v1/v2). +- **`PowerSource`:** `vehicle_12v` (норма) → на under-voltage/commit сигналим `holdup_cap`/`low_battery` (потребителям + «времени мало»). `sleep_rail` — v1/v2. +- **dev-mock `ru.shturman.dev.PowerMock1` (fake-ACC, фича `dev-mocks`) — кормит входы FSM:** + - `SetAcc(on)` → `AccOn`/`AccOff`; + - `SetIgnition(state)` → `EngineOn`/`EngineOff` (accessory↔running) либо `AccOn`/`AccOff`; + - `TriggerShutdown(sec, reason)` → `UnderVoltage`/`ThermalTrip` с заданным grace; + - `AbortShutdown()` → re-power (`AccOn`) в abortable. + Прод-сборка mock не регистрирует (#3-гейт §3). Policy `send_destination=ru.shturman.Power` покрывает (foundation §13). + +--- + +## 8. Watchdog / монотоника / save-time + +- **Watchdog (B05/A14):** drop-in `shturman-power.service.d/watchdog.conf` — `RuntimeWatchdogSec=` (дисциплина: один + userspace-владелец WDT). System `systemd/system.conf.d` `RebootWatchdogSec=` — дедлайн shutdown-фазы (зависание в + unmount/sync не оставит устройство под питанием). **В VM `/dev/watchdog` нет → конфиг присутствует, реальный арминг — + HW** (VM↔HW-граница, как zram/vcan в v0.6). MCU-backstop — v0.4. +- **Монотоника (§8):** `Uptime` + grace-таймер + sleep/wake-таймеры на `CLOCK_MONOTONIC` (`monotonic_secs`). Wall-clock + легитимно прыгает на NTP/GPS-синке — lifecycle на него не завязан. +- **Save last-known-time (B07):** `shturman-savetime.service` (`fake-hwclock save` с `FILE=/data/...`, как в v0.6) + + `.timer` (~1–5 мин, monotonic). On-shutdown save — шаг 3 §6. После срыва часы откатываются максимум на интервал. + +--- + +## 9. Dev-харнесс и план тестирования + +### 9.1 Unit (FSM — `fsm.rs`) +Каждый переход §5: `Off→Accessory→Running`; `Running→ShuttingDown{abortable,reason}` для каждого reason; abort +(`Abortable+AccOn→Running`+`ShutdownAborted`); `GraceExpired→Committed`+`Commit`; `Committed` — abort игнорируется; +`Sleep`/`BatteryCutoff` — no-op. Действия проверяются по возвращаемому `Vec`. + +### 9.2 Integration (session-шина, `#[ignore]`, `just test-integration`) +`SetAcc(false)` → наблюдаем `ShutdownImminent`; `AbortShutdown` (в abortable) → `ShutdownAborted`; `SetIgnition` +→ `IgnitionState` property; `GetPowerState` отражает FSM. + +### 9.3 E2E (Lima, гибрид — расширение `run.sh`) +- **N=3 in-VM цикла зажигания:** записать `/data`-маркер (Settings `ui.theme=night` + счётчик `/data/state/power-cycles`); + цикл: fake-ACC-off → наблюдать `ShutdownImminent` → харнесс: stop `shturman-stage1.target` + `sync` + `umount /data` + → `mount /data` → restart → **маркер цел, счётчик++**. После 3 циклов: night + счётчик=3. +- **1 реальный reboot-цикл:** fake-ACC-off → commit → `systemctl poweroff` → `limactl start` → boot → `/data` цел. +- **Abort до PONR:** fake-ACC-off → `ShutdownImminent` → fake-ACC-on **до unmount** → `ShutdownAborted` наблюдаем → + `/data` RW (смонтирован) → `GetPowerState=running`. +- **Power-cut-сим:** во время shutdown (до fsync) `SIGKILL` power+settings → `/data`: remount ok, `fsck -n` clean, + последнее durable-значение присутствует (атомарность §9.1 на уровне файла). +- **Монотоника:** `Uptime` растёт; не прыгает при wall-clock-синке. +- **Watchdog/save-time:** drop-in `RuntimeWatchdogSec` у power.service присутствует; `shturman-savetime.timer` активен; + `/data/state/fake-hwclock.data` обновляется. + +### 9.4 Критерии приёмки +- [ ] FSM: все переходы §5 покрыты unit-тестами; `sleep`/`battery_cutoff` — no-op/документированы. +- [ ] `ShutdownImminent` на ACC-off; **abort до PONR → `ShutdownAborted`**; commit только после grace + durable-barrier. +- [ ] **N=3 цикла зажигания — `/data` + счётчик целы** (нет потери). +- [ ] 1 реальный reboot-цикл — `/data` цел. +- [ ] power-cut-сим — `/data` консистентен (`fsck -n` clean, last value present). +- [ ] `Uptime` монотонен; lifecycle-таймеры на `CLOCK_MONOTONIC`. +- [ ] watchdog-конфиг (`RuntimeWatchdogSec`/`RebootWatchdogSec`) на месте; `savetime.timer` активен. +- [ ] **Регресс v0.1/v0.2** (foundation §9.4 + v0.2 §9.3) зелёный (фазы/кадр/персист не сломаны). +- [ ] `just ci` зелёный; красные линии целы (нет CAN/actuator); **prod-build-gate** (`--no-default-features` → + нет `PowerMock1`) зелёный. + +--- + +## 10. Двунаправленные швы (синхронизировать при реализации) + +- **`domain B`:** пометить реализованные срезы B01–B07 (v0.3, VM-модель); abort/PONR в VM = stop+umount+remount; + HW (MCU/hold-up/heartbeat/`safe-to-cut`/fail-safe-таймер) + выбор **B08/B09** → **v0.4**. +- **`ipc.md` §3:** Power-сигналы/состояние оживлены из FSM (не mock); `Sleep`/`Wake` зарезервированы. +- **`foundation §5.2`:** «Power-стаб» → **реальный FSM** (обновить формулировку «стартует в running, без логики»). +- **`hardware §3` / `B §5`:** B08/B09 (MCU vs supercap-only) остаётся открытым 🟡 → v0.4. +- **`CLAUDE.md`:** статус v0.3 готово → следующее v0.4/v0.5. + +--- + +## 11. Дальше по ритму + +`v0.3` (эта спека) → **writing-plans** → **TDD** (FSM-юниты → сервис-обёртка → durable-barrier/grace → systemd/save-time +→ E2E-блок) → реализация → **verify в Lima** → коммит. Далее: `v0.4` (MCU/thermal — замыкает B08/B09) после v0.3; +`v0.5` (полный shell) параллельно.