# Спека реализации: v0.4 — MCU/thermal fail-safe (тепловой триггер + MCU-протокол + fail-safe-таймер) > Веха `v0.4` роадмапы: «аппаратный фундамент питания/тепла». Capabilities **A12** (тепловой мониторинг + > базовый throttling), **B08** (MCU-копилот shutdown-протокол), **B09** (MCU аппаратный fail-safe-таймер), > **B10** (thermal shutdown: триггер + hysteresis + UX). Поверх **v0.3** (живой `PowerFsm` + graceful shutdown). > Источники: `docs/domains/b-power-lifecycle.md` §4/§5/§6/§1a, `docs/contracts/hardware.md` §3/§1a, > `docs/contracts/ipc.md` §3, `docs/contracts/safety.md`, `docs/roadmap.md` §v0.4. Приёмка роадмапы: > **«thermal-trip → graceful; MCU-таймер режет питание, если SoC завис»**. > > **Решение скоупа (брейнсторм):** разрабатываем без платы (принцип #13). MCU-копилот принят как > **reference-архитектура** (доки рек.); делаем **софт + симуляцию**, физический выбор **B08/B09** > (MCU vs supercap-only) и реальное железо — отложены в **HW-bring-up-подфазу**. Симметрия с v0.3: > чистое ядро (политика/кодек) → абстракция (trait) → dev-mock. --- ## 1. Цель и первый артефакт Замкнуть **тепловой** и **MCU**-швы домена B поверх живого FSM v0.3: (а) тепловой монитор с гистерезисом кормит существующий `Event::ThermalTrip` → graceful shutdown; (б) SoC↔MCU shutdown-протокол (heartbeat / `safe-to-cut` / wait-for-completion) с защищённым кодеком; (в) модель **независимого fail-safe-таймера** — MCU детерминированно «режет питание», если SoC завис. **Первый артефакт (в Lima, мок-MCU/sensor):** 1. **thermal-trip:** `SetTemp ≥ critical` → `ThermalTrip` → `ShutdownImminent(thermal)` → graceful (реюз v0.3-пути, `/data` консистентен); восстановление по гистерезису. 2. **MCU fail-safe:** `HangSoc` (heartbeat пропал в `running`) → мок-MCU детерминированно снимает питание (в VM = форс-`off` сервиса) — «MCU режет питание, если SoC завис»; `/data` консистентен. 3. **throttling:** `SetTemp` в warn/throttle-банде → throttle-действие записано (VM `Noop`), без shutdown; гистерезис на спаде (нет осцилляции). **Не цель v0.4:** реальный UART/I2C-драйвер, реальный cpufreq-эффект, прошивка MCU, физический B09-чип/supercap, полный supercap-only-путь (остаётся абстракцией-fallback), thermal-рендер в Shell (**v0.5**), sleep/wake/ battery-cutoff (**v1/v2**), числовой тюнинг порогов/hold-up — **RK3588**. Перф/тепловой вердикт — на таргете. --- ## 2. Скоуп и границы ### 2.1 В скоупе (делаем сейчас) - **Тепловая политика (A12/B10):** чистый `ThermalPolicy` — `temp + предыдущий уровень → ThermalLevel ∈ {Normal, Warn, Throttle(n), Critical}` с **гистерезисом** (раздельные пороги вверх/вниз — нет осцилляции). Юнит-тестируемый без I/O. Пороги — placeholder-константы (`// тюнинг на RK3588`, hardware §1a). - **Источник температуры (`TempSource` trait):** real = sysfs `/sys/class/thermal/thermal_zone*/temp` (max по зонам); VM = `MockTempSource` (значение из dev-D-Bus `SetTemp`). В v0.4 активен mock. - **Throttler (`Throttler` trait):** real = cpufreq-cap (best-effort, HW); VM = `NoopThrottler` (запись уровня для E2E/лог). Эффект — абстракция; реальное снижение частоты — HW. - **Thermal-монитор:** периодический poll на **монотонике** → политика → throttle + на `Critical` кормит `Event::ThermalTrip` в FSM (тот же `apply_event` из v0.3). Восстановление до PONR → `Event::ThermalCleared` (abort thermal-shutdown, симметрия с re-power; гейт по `reason == Thermal`). - **SoC↔MCU протокол (B08):** типы сообщений `SocToMcu`/`McuToSoc`; **чистый кодек** (framing + seq + CRC16 + **replay/desync-guard**) — закрывает требование «защита линка от replay/мусора/десинка» (B §5, hardware §4). Байты текут через in-memory-канал (codec исполняется по-настоящему и в integration). - **Coprocessor (`Coprocessor` trait):** real = `SerialCoprocessor` (UART — **стаб**, HW-подфаза); VM = `MockCoprocessor` (in-process, кормится через dev-D-Bus). - **SoC-side `CoprocessorClient`:** heartbeat в `running`; на `ShutdownImminent` → **wait-for-completion** (расширенный таймаут ≥ shutdown-бюджет, B §6) — не короткий keepalive посреди unmount; `safe-to-cut` после PONR → немедленный cut. MCU — **fail-safe-авторитет** (SoC не командует cut-на-ходу — B §5). - **Fail-safe-таймер (B09) — модель:** `MockCoprocessor` моделирует независимый таймер: heartbeat пропал (`running`) ИЛИ бюджет истёк без `safe-to-cut` → детерминированный cut (в VM = форс-`off` power-сервиса). - **D-Bus-контракт (ipc §3):** property `ThermalState ∈ {normal, warn, throttle, critical}` + сигнал `ThermalChanged(state, celsius)`; `ShutdownImminent(thermal)` уже есть. **Контракт сейчас — Shell рисует в v0.5.** - **Харнесс:** юниты (политика/кодек/клиент/таймер) + integration (session-шина) + E2E-блок v0.4 (§9). ### 2.2 Явно НЕ в скоупе (отложено, с указателем «куда») - **Физический выбор B08/B09** (MCU-копилот vs supercap-only), реальный MCU-чип, прошивка, реальный hold-up cap/supercap, реальный UART/I2C-драйвер + GPIO ACC-детект — **HW-bring-up-подфаза** (hardware §3). - **Реальный cpufreq/DDR/GPU-throttling-эффект + числовые тепловые пороги/дератинг** — **RK3588** (hardware §1a). - **Thermal-UX рендер в Shell** («перегрев»-overlay) — **v0.5** (живой shell; v0.4 даёт только контракт). - **Supercap-only полный путь** (SoC-таймер + разряд cap, ACC-детект в софте) — остаётся **абстракцией-fallback** (`Coprocessor` trait), тело — HW-подфаза при выборе supercap. - **Sleep/wake/scheduled-wake/battery-cutoff** — **v1/v2** (B §7); состояния зарезервированы (как в v0.3). - **Реальный `/dev/watchdog` арминг + MCU-watchdog-бэкстоп железом** — **HW** (в v0.4 — дисциплина/модель, как v0.3). - **Перф-вердикт** (время до cut, hold-time, тепловая инерция) — **RK3588** (performance §2). В VM — функционально. ### 2.3 Частично в скоупе (каркас сейчас, тело — позже) - **`Throttler`** — trait + `Noop` (лог уровня); реальный cpufreq — HW. - **`Coprocessor`** — trait + `MockCoprocessor`; `SerialCoprocessor` (UART) — стаб, HW-подфаза. - **`TempSource`** — trait + `MockTempSource`; `SysfsTempSource` — каркас (читает зоны, в Lima зоны статичны). - **MCU-watchdog/линк-fail-safe** — логика клиента + модель таймера; реальный независимый чип — HW. ### 2.4 Трассируемость ID → статус | ID | Веха | Статус в v0.4 | |----|------|----------------| | A12 | Тепловой мониторинг + базовый throttling | `ThermalPolicy` + `TempSource`/`Throttler` (VM mock/noop); пороги placeholder, эффект cpufreq — HW | | B08 | MCU-копилот shutdown-протокол | типы + кодек (CRC/replay/desync) + `CoprocessorClient` (heartbeat/wait/safe-to-cut); транспорт = in-memory (UART — HW) | | B09 | MCU аппаратный fail-safe-таймер | **модель** в `MockCoprocessor` (hang/budget → детерминированный cut); реальный независимый чип — HW | | B10 | Thermal shutdown (триггер + hysteresis + UX) | триггер `ThermalTrip` (реюз FSM v0.3) + гистерезис + abort `ThermalCleared`; **UX-рендер — v0.5** (контракт-property/сигнал сейчас) | --- ## 3. Красные линии, безопасность - **MCU/Power — только питание устройства, не CAN/actuator (#1/#2):** копроцессор мониторит зажигание/напряжение и коммутирует **рейл питания SoC** — никаких узлов авто, никаких write/actuator-путей. Протокол SoC↔MCU не несёт автомобильных команд. (Engine-state/OBD — домен E, read-only; Power **не трогает CAN**.) - **MCU — fail-safe-авторитет (B §5):** ни одно SoC-сообщение не может (а) снять питание на ходу, (б) держать вечно и разрядить АКБ. В модели `MockCoprocessor` cut инициируется **только** таймером MCU (hang/budget) или `safe-to-cut` после PONR — не произвольной SoC-командой. - **Защита линка:** кодек отбрасывает replay (seq ≤ last)/мусор (битый CRC)/десинк (resync по SYNC) — юнит-доказано (§9.1). Аналог защиты CAN-линка (hardware §4). - **Durability-инвариант v0.3 сохраняется:** thermal-trip и MCU-cut идут через тот же graceful-путь до PONR (durable-barrier `sync` до unmount); после усечённого shutdown `/data` консистентен (atomic-write A §3). - **prod-build-gate:** `--no-default-features` (без `dev-mocks`) → `PowerMock1`/`SetTemp`/`HangSoc` не регистрируются (как в v0.3). dev-D-Bus-policy — dev-only drop-in. --- ## 4. Раскладка (новые/изменённые артефакты) - **Create** `crates/core/shturman-power/src/thermal.rs` — `ThermalLevel`, `ThermalPolicy` (чистая, гистерезис), `TempSource` trait (`SysfsTempSource`/`MockTempSource`), `Throttler` trait (`Cpufreq`-стаб/`NoopThrottler`), `ThermalMonitor` (poll → политика → throttle + `ThermalTrip`/`ThermalCleared`). - **Create** `crates/core/shturman-power/src/protocol.rs` — `SocToMcu`/`McuToSoc` (типы сообщений). - **Create** `crates/core/shturman-power/src/codec.rs` — кадр (SYNC/LEN/SEQ/TYPE/PAYLOAD/CRC16) + encode/decode + replay/desync-guard. Юнит-тесты в файле. - **Create** `crates/core/shturman-power/src/coprocessor.rs` — `Coprocessor` trait, `MockCoprocessor` (in-process + B09-таймер-модель), `SerialCoprocessor` (UART-стаб), `CoprocessorClient` (SoC-side: heartbeat/wait/safe-to-cut). - **Modify** `crates/core/shturman-power/src/fsm.rs` — `Event::ThermalCleared` (+ переход abort из `ShuttingDown{Abortable, reason: Thermal}` → `Running`); `Event::FailsafeCut` (→ `off` из любого не-`off`, необратимо — MCU-авторитет); подтвердить армы `ThermalTrip`. - **Modify** `crates/core/shturman-power/src/service.rs` — владеть `ThermalMonitor` + `CoprocessorClient` (кормят FSM); property `ThermalState` + сигнал `ThermalChanged`; dev-mock расширить: `SetTemp(d)`, `HangSoc()`, `McuLinkLoss()`. - **Modify** `crates/core/shturman-power/src/lib.rs` — `pub mod thermal; pub mod protocol; pub mod codec; pub mod coprocessor;`. - **Modify** `crates/shturman-ipc/src/proxy.rs` — `Power1Proxy`: property `ThermalState` + сигнал `ThermalChanged`. - **Modify** `crates/core/shturman-power/tests/integration.rs` — thermal-trip / abort / fail-safe-cut по session-шине. - **Modify** `tests/e2e/run.sh` — блок v0.4 (thermal-trip → graceful; MCU fail-safe; throttling/гистерезис). - **Modify (швы §10)** `docs/domains/b-power-lifecycle.md`, `docs/contracts/hardware.md`, `docs/contracts/ipc.md` §3, `docs/capability-catalog.md` (A12/B08/B09/B10), `CLAUDE.md`. --- ## 5. Тепловая подсистема (A12/B10) — контракт ``` ThermalLevel = Normal | Warn | Throttle(level: u8) | Critical ``` **Пороги (placeholder-константы, тюнинг на RK3588 — hardware §1a; Tjmax RK3588 ~100 °C):** | Переход | Порог вверх | Порог вниз (гистерезис) | |---------|-------------|--------------------------| | Normal → Warn | `WARN_C = 75` | `WARN_C − HYST` | | Warn → Throttle | `THROTTLE_C = 85` | `THROTTLE_C − HYST` | | Throttle → Critical | `CRITICAL_C = 95` | `CRITICAL_C − HYST` | | `HYST = 5 °C` | | | - **`ThermalPolicy::next(prev: ThermalLevel, temp_c) -> ThermalLevel`** — чистая; гистерезис = переход вниз только ниже `(порог − HYST)`, иначе уровень держится (нет осцилляции на границе). - **`ThermalMonitor`** (tokio-интервал на монотонике, ~`POLL_SECS = 1`): `temp = TempSource::read()` → `lvl = policy.next(prev, temp)`; при смене уровня: `Throttler::apply(lvl)` + `ThermalChanged(state, temp)`; **на входе в `Critical`** → `apply_event(Event::ThermalTrip)`; **на выходе из `Critical`** (по гистерезису), если FSM ещё в `ShuttingDown{Abortable, reason: Thermal}` → `apply_event(Event::ThermalCleared)`. - **`ThermalState` (D-Bus property)** = проекция текущего `ThermalLevel` (`Throttle(_)` → `"throttle"`). --- ## 6. SoC↔MCU протокол (B08) + fail-safe-таймер (B09) — контракт ### 6.1 Сообщения ``` SocToMcu = Heartbeat { seq } // периодический keepalive в running/accessory | ShutdownImminent { budget } // вход в shutdown → MCU в wait-for-completion (таймаут ≥ budget) | SafeToCut // после PONR → MCU снимает питание немедленно McuToSoc = Ack { seq } | Acc { on } // дебаунснутый ACC (источник зажигания; в VM кормит FSM AccOn/AccOff) | Voltage { mv } // напряжение бортсети (под under-voltage backstop) | CutWarning // бюджет почти истёк (диагностика) ``` ### 6.2 Кодек (`codec.rs`) - **Кадр:** `[SYNC=0xA5][LEN u8][SEQ u8][TYPE u8][PAYLOAD…][CRC16-CCITT]`, CRC по `LEN..=PAYLOAD`. - **Replay/dup guard:** приёмник держит `last_seq` на направление; кадр с `seq ≤ last_seq` (в окне) — **drop**. - **Desync/мусор:** битый CRC или нет SYNC → **resync** (скан до следующего `SYNC`), кадр отброшен. - Юнит-тесты: round-trip всех типов; corruption (флип бита → drop); replay (повтор seq → drop); desync (мусор перед SYNC → восстановление на следующем валидном кадре). ### 6.3 SoC-side `CoprocessorClient` - В `running`/`accessory`: `Heartbeat{seq++}` каждые `HEARTBEAT_SECS` (монотоника); ждёт `Acc`/`Voltage` от MCU → кормит FSM (`AccOn`/`AccOff`/`UnderVoltage`). - На `ShutdownImminent` (FSM вошёл в shutdown): шлёт `SocToMcu::ShutdownImminent{budget}` → переходит в **wait-for-completion** (heartbeat останавливается, ждёт завершения секвенсинга; таймаут ≥ shutdown-бюджет, B §6). - После PONR (commit): `SafeToCut` → MCU режет немедленно. - **Не** шлёт power-команд с эффектом cut-на-ходу (red-line §3). ### 6.4 Fail-safe-таймер (B09) — модель в `MockCoprocessor` - **Hang-детект:** нет `Heartbeat` дольше `FAILSAFE_MISS × HEARTBEAT_SECS` в `running` → SoC завис → cut. - **Budget-таймер:** после `ShutdownImminent` без `SafeToCut` за `HOLDUP_BUDGET_SECS` → cut (детерминированно). - **Cut (в VM):** мок-MCU зовёт `apply_event(Event::FailsafeCut)` → FSM → `off` (необратимо, MCU-авторитет) + лог «MCU cut». В E2E дополнительно реюзаем v0.3 power-cut (SIGKILL до fsync) для durability-доказательства (`fsck` clean, durable-value цел). Это **VM-модель**: реальный зависший SoC теряет питание извне, в модели cut = событие не-реально-зависшего процесса (симметрично v0.3 «abort/PONR = stop+umount+remount»). - Значения (`HEARTBEAT_SECS=1`, `FAILSAFE_MISS=3`, `HOLDUP_BUDGET_SECS` ~ grace+запас) — **placeholder** (реальный hold-up sizing — hardware §3, RK3588). --- ## 7. D-Bus `ru.shturman.Power` — v0.4 расширяет - **+ Property `ThermalState: s`** ∈ `{normal, warn, throttle, critical}` (+ `PropertiesChanged`). - **+ Сигнал `ThermalChanged(s state, i celsius)`** — для Shell (рендер «перегрев» — **v0.5**). - `ShutdownImminent(u seconds, s reason)` — `reason=thermal` уже объявлен (ipc §3); v0.4 его **реально эмитит** на thermal-trip. - **dev-mock `ru.shturman.dev.PowerMock1` (feature `dev-mocks`)** дополняется: - `SetTemp(i celsius)` → кормит `MockTempSource` → монитор → политика. - `HangSoc()` → останавливает heartbeat → провоцирует B09-таймер. - `McuLinkLoss()` → тишина линка: **SoC-сторона деградирует** (лог/маркер degraded, **не** self-cut — red-line §3). MCU-сторонняя политика cut-vs-hold при тишине — **B §12-open → HW**. - Прод-гейт `--no-default-features` — не регистрируются (как v0.3 §3). --- ## 8. Watchdog / монотоника (реюз v0.3) - Все новые таймеры (poll, heartbeat, wait-for-completion, fail-safe, hold-up) — на **`CLOCK_MONOTONIC`** (`shturman_common::monotonic_secs`), НЕ wall-clock (B §8). - MCU-watchdog/линк-fail-safe — **логика клиента + модель** (реальный независимый чип/арминг — HW). systemd `RuntimeWatchdogSec`/`RebootWatchdogSec` — уже из v0.3 (новых юнитов не требуется; thermal/coprocessor живут внутри `shturman-power.service`). --- ## 9. Dev-харнесс и план тестирования ### 9.1 Unit (чистые модули) - **`thermal.rs`:** банды Normal/Warn/Throttle/Critical; **гистерезис** (нет осцилляции на границе ±HYST); `ThermalTrip` на входе в Critical, `ThermalCleared` на выходе. - **`codec.rs`:** round-trip всех типов; corruption→drop; replay(seq)→drop; desync→resync. - **`coprocessor.rs`:** клиент heartbeat→wait-for-completion→safe-to-cut; B09-таймер (hang→cut, budget→cut); MCU игнорит небезопасные SoC-команды (red-line). - **`fsm.rs`:** `ThermalCleared` abort только из `ShuttingDown{Abortable, reason: Thermal}` (из `AccOff`-shutdown — no-op); committed — no-op. `FailsafeCut` → `off` из любого не-`off` (необратимо). ### 9.2 Integration (session-шина, `#[ignore]`, `just test-integration`) - `SetTemp ≥ critical` → наблюдаем `ShutdownImminent(thermal)` + `ThermalChanged(critical)`; state `shutting_down`. - `SetTemp` recovery до grace → `ShutdownAborted` (через `ThermalCleared`) + `running`. - `HangSoc` → наблюдаем fail-safe-cut (state → off / forced-off). ### 9.3 E2E (Lima, гибрид — расширение `run.sh`, после блока power-safe v0.3) - **thermal-trip:** `SetTemp ≥ critical` → `ShutdownImminent(thermal)` → graceful (реюз v0.3: stop→umount(PONR)→ remount) → `/data` консистентен; затем `SetTemp` норма → `running`. - **MCU fail-safe:** `HangSoc` (heartbeat пропал) → мок-MCU режет питание (наблюдаем forced-off / SIGKILL-эквивалент) → `fsck` clean, durable-value цел (как v0.3 power-cut). - **throttling/гистерезис:** `SetTemp` в warn/throttle → `ThermalState` меняется, throttle записан, **без** shutdown; спад чуть выше нижнего порога — уровень держится (нет осцилляции). - **Регресс v0.1/v0.2/v0.3 зелёный**; machine-id стабилен; `E2E OK ✅`. ### 9.4 Критерии приёмки (роадмапа + спека) - [ ] thermal-trip → graceful (`ShutdownImminent(thermal)`→commit→`/data` консистентен); гистерезис — нет осцилляции. - [ ] MCU fail-safe-таймер: SoC-hang/бюджет → **детерминированный cut** (модель); `/data` консистентен. - [ ] Throttling-политика по бандам применена (запись в VM; числа — RK3588). - [ ] Кодек: replay/desync/corruption отбиты (unit). - [ ] `ThermalState`/`ThermalChanged` на шине; `Uptime`/таймеры монотонны. - [ ] Регресс v0.1–v0.3 зелёный; `just ci` зелёный; prod-build-gate (нет `PowerMock1`/`SetTemp`); красные линии целы (MCU/Power — только питание, нет CAN/actuator). --- ## 10. Двунаправленные швы (синхронизировать при реализации) - **`domain B`:** §4 (thermal-trip-путь реализован), §5 (MCU shutdown-протокол + кодек + клиент — софт/модель; физический MCU/fail-safe-чип/supercap → HW-подфаза), §6 (wait-for-completion реализован), §1a (тепловые пороги — placeholder, тюнинг RK3588). Пометить **A12/B08/B09/B10** реализованными (VM-модель). - **`hardware §3`/`§1a`:** **B08/B09** физический выбор (MCU vs supercap-only) остаётся **🟡 → HW-bring-up-подфаза**; тепловой конверт/класс/охлаждение — 🟡 (числа на таргете). - **`ipc.md §3`:** Power + `ThermalState`/`ThermalChanged`; `ShutdownImminent(thermal)` реально эмитится. - **`capability-catalog`:** A12 ✅ (политика+абстракция), B10 ✅ (триггер+гистерезис; UX→v0.5), B08/B09 — софт/модель реализованы, физический выбор 🟡 → HW. - **`CLAUDE.md`:** статус v0.4 (после реализации) → следующее v0.5 shell. --- ## 11. Дальше по ритму `v0.4` (после утверждения спеки) → **План 8** (`docs/specs/plans/08-v0.4-mcu-thermal.md`, writing-plans) → TDD → verify в Lima → коммиты `feat/v0.4-mcu-thermal`. Затем **v0.5 — полный shell** (живой weston-shell; замкнёт thermal-UX-рендер). Физический **HW-bring-up** (MCU/supercap выбор, реальный UART/cpufreq/B09-чип, тепловой вердикт) — отдельной подфазой при появлении RK3588-платы.