Files
shturman/docs/specs/v0.3-power-safe.md
kk0t9 4fe5103e88 docs(v0.3): спека power-safe ядра (FSM + graceful shutdown)
Веха 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 <noreply@anthropic.com>
Signed-off-by: Alexander <akotenev2003@gmail.com>
2026-06-24 20:51:58 +03:00

242 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Спека реализации: 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<Action>`. Чистый, без 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` (~15 мин, 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<Action>`.
### 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) параллельно.