diff --git a/docs/contracts/data-model.md b/docs/contracts/data-model.md index 7a481df..00a26f2 100644 --- a/docs/contracts/data-model.md +++ b/docs/contracts/data-model.md @@ -96,8 +96,9 @@ PID** (Mode 01, PID `0x00/0x20/0x40/…` — битовые маски подд **оценка** из MAF (предполагает бензин/стехиометрию — помечаем как `estimate`, зависит от типа топлива). - **Пробег поездки / средний расход** (интеграл по времени; нужен trip-стейт). -- **Состояние машины** (`off` / `accessory` / `running`) — грубо из RPM/зажигания; - нужно ассистенту, чтобы отличать «двигатель заглушен» от «сигнал недоступен» от «ноль». +- **`engine_running`** (running/stopped/unknown, из RPM) — узкий дискриминатор «двигатель + заглушен» vs «сигнал недоступен». **Lifecycle-состояние** (off/accessory/running) — канонический + `Power.IgnitionState` (домен B), здесь не дублируется. - Помечаются `source = computed`; зависят от наличия исходных сигналов. ## 6. Модель DTC (диагностические коды) diff --git a/docs/contracts/ipc.md b/docs/contracts/ipc.md index 29c6af7..13eae67 100644 --- a/docs/contracts/ipc.md +++ b/docs/contracts/ipc.md @@ -56,10 +56,10 @@ - **Properties (горячие):** `Speed`, `Rpm`, `CoolantTemp`, `ModuleVoltage` (питание ЭБУ ≈ бортсеть, не клеммы АКБ), `Online`. - ⛔ **Методов записи нет** — ни `SetSignal`, ни actuator-команд, ни clear-DTC. Архитектурная гарантия read-only (принцип #2). Типы — [data-model.md](data-model.md). -### `ru.shturman.Power` — питание и жизненный цикл -- **Методы:** `GetPowerState() → state`, `RequestSleep()` (внутр.). -- **Сигналы:** `AccChanged(on)`, `ShutdownImminent(seconds)`, `Sleep()`, `Wake()`. -- **Properties:** `IgnitionState`, `Uptime`, `PowerSource`. +### `ru.shturman.Power` — питание и жизненный цикл (домен B) +- **Методы:** `GetPowerState() → state` (enum `off`/`accessory`/`running`/`shutting_down`/`sleep`/`battery_cutoff`), `RequestSleep()` (внутр.). +- **Сигналы:** `AccChanged(on)`, `ShutdownImminent(seconds, reason)` (`reason ∈ acc_off|under_voltage|thermal|battery_cutoff`), **`ShutdownAborted()`** (re-power до PONR), `Sleep()`, `Wake()`. +- **Properties:** `IgnitionState` (off/accessory/running — **канон**; E зеркалит, не дублирует), `Uptime` (монотонные часы), `PowerSource` (`vehicle_12v`/`holdup_cap`/`sleep_rail`/`low_battery`). ### `ru.shturman.Settings` — конфигурация и состояние - **Методы:** `Get(key) → value`, `Set(key, value)`, `List(prefix) → [key]`, `Reset(key)`. diff --git a/docs/contracts/security-privacy.md b/docs/contracts/security-privacy.md index 8e483ff..aa54431 100644 --- a/docs/contracts/security-privacy.md +++ b/docs/contracts/security-privacy.md @@ -144,8 +144,9 @@ PipeWire-capture/location). Для `audio_in`/`location` — **while-in-use**: обработчика ПДн и правовое основание передачи (→ legal-трек). **Локализация ≠ покрытие 152-ФЗ:** «данные в РФ» (где хранятся) ≠ основание на обработку/передачу (согласие). -**Микрофон:** wake-word слушает постоянно, но **локально**; в обработку идёт только -после слова-активатора; включённый микрофон показывается **видимым индикатором**. +**Микрофон:** wake-word слушает **в состояниях `running`/`accessory`** (гейт Power — **не** +безусловно always-on; в `sleep`/`off` выключен, риск разряда АКБ, принцип #5; домен B §7 / D §8), +всё **локально**; в обработку идёт только после слова-активатора; включённый микрофон — **видимый индикатор**. **Охват гарантий и рисковые комбинации.** Таблица выше описывает поведение first-party (ассистент). Сторонний плагин с `network` **+** чувствительными данными diff --git a/docs/domains/a-base-system.md b/docs/domains/a-base-system.md index 2a54eaf..ff7acf7 100644 --- a/docs/domains/a-base-system.md +++ b/docs/domains/a-base-system.md @@ -125,8 +125,8 @@ - Онлайн-синк: `systemd-timesyncd` (легче) / chrony. - **GPS как offline-first источник UTC** (NMEA `$GPRMC/$GPZDA` → gpsd → chrony SHM/PPS) — основной фолбэк без сети (принцип #3). -- **Персист last-known-time в `/data`** (`fake-hwclock`) при graceful shutdown (владение - save-on-shutdown → домен B), чтобы холодный boot не стартовал с 1970. +- **Персист last-known-time в `/data`** (`fake-hwclock`) — **периодически + на синке + на graceful + shutdown** (владение → домен B §8), чтобы холодный boot/резкий обрыв не откатывали часы к 1970. - **Boot-политика:** до любого TLS-egress к LLM часы должны быть «вменяемыми» (gated). ## 8. Память (zram / OOM) diff --git a/docs/domains/b-power-lifecycle.md b/docs/domains/b-power-lifecycle.md new file mode 100644 index 0000000..d384750 --- /dev/null +++ b/docs/domains/b-power-lifecycle.md @@ -0,0 +1,216 @@ +# Домен B — Power / Lifecycle + +> Привилегированный core-сервис: владеет power-path, детектом зажигания, **graceful +> shutdown**, протоколом MCU-копилота, watchdog, sleep/wake и сохранением времени. +> **Вторая половина «power-safe с дня 1»** (с доменом A). + +Статус: **v2 (на ревью).** v2 — после adversarial-ревью (22 находки). +Связано с: [architecture.md](../architecture.md) (§6) · [hardware.md](../contracts/hardware.md) (§3 питание/MCU) · [a-base-system.md](a-base-system.md) (§5–§11) · [ipc.md](../contracts/ipc.md) (`Power`) · [principles.md](../principles.md) (#5) · домены D (гейт wake-word), E (engine-state) + +--- + +## 1. Назначение и границы + +- **Что делает:** детект зажигания, секвенсинг питания, graceful shutdown, watchdog-арбитраж, + sleep/wake, сохранение времени; публикует `ru.shturman.Power`. +- **Границы:** не трогает CAN и не управляет узлами авто; координирует **только питание устройства**. +- **Power-safe (#5) = A + B вместе:** A — read-only rootfs / ФС / atomic-write; B — graceful + shutdown + hold-up-секвенсинг + watchdog. +- **Владение состоянием:** `Power.IgnitionState` (off/accessory/running) — **единственный + канонический источник** lifecycle-состояния (voltage/GPIO/MCU). E владеет лишь + `engine_running` (узко, из RPM — отличить «заглушен» от «нет данных»), не дублирует + IgnitionState (см. §9, e-vehicle-data §2). + +## 2. Машина состояний питания + +``` + ┌─────┐ ACC-on ┌───────────┐ напряжение>порог ┌──────────┐ + │ off │──────────▶│ accessory │──(генератор)──────▶│ running │ + └─────┘ └───────────┘◀──напряжение<порог─└──────────┘ + ▲ ▲ │ │ + │ │ ACC-off(debounced)/under-voltage-held/thermal-trip│ + │ │ ▼ │ + │ │ ┌──────────────────────┐◀─────────────────┘ + │ │ │ shutting-down(ABORTABLE)│ pre-PONR: re-power → назад в accessory/running + │ │ └──────────┬────────────┘ + │ │ unmount (PONR) ▼ + │ └──── cut ◀─ ┌──────────────────────┐ committed: только → off + │ │ shutting-down(COMMITTED)│ + │ └──────────────────────┘ + off ◀─ battery-cutoff ◀─ sleep ⇄ accessory/running (sleep: wake по ACC/таймеру/реверсу) +``` + +- **`running` детектится по напряжению бортсети** (генератор ~13.8–14.4 В vs покой ~12.2–12.6 В), + доступному Power/MCU напрямую через ADC того же рейла, где hardware §3 ставит brown-out-пороги. + RPM/CAN — **опциональное уточнение в v2** (когда поднимется E), не обязательное. +- **`shutting-down` разбит на ABORTABLE (до point-of-no-return) и COMMITTED (с unmount).** +- **`battery-cutoff`:** при долгой стоянке — forced off для сохранения энергии на запуск (§7). +- **Триггеры shutting-down:** ACC-off (debounced) · under-voltage held · **thermal-trip** (§4). + +## 3. Детект зажигания (ACC) — кондиционированный + +Не голый уровень GPIO (hardware §3): **debounce/glitch** + минимальная стабильная длительность +ACC-off **больше** worst-case cold-crank-просадки; **гистерезис** + **приоритет crank**. +Владелец: **MCU-копилот** (рек.) либо **софт на SoC** (supercap-only). + +## 4. Graceful shutdown (секвенсинг) + +**Триггеры (три, все ведут полный путь — есть время на флаш, кроме самого резкого обрыва):** +- **ACC-off (debounced, окно > crank)** — авторитетное решение «выключаемся». +- **Under-voltage ниже brown-out-порога с гистерезисом (hardware §3), удержанное дольше + worst-case crank** — backstop при реальном обвале рейла (НЕ rate-of-change: «резкое падение» + срабатывало бы на каждом запуске, cold-crank до ~6 В — НОРМА, ride-through). +- **Thermal-trip** (критическая температура, a-base §10) — причина ≠ ACC, UX «перегрев» в Shell. + +**Порядок (sizing энергии — hardware §3; секвенсинг — здесь):** +1. `Power` эмитит `ShutdownImminent(seconds, reason)`. +2. **Load-shedding** (политика по триггеру): + - *резкий обрыв/hold-up:* агрессивно сбросить всё, кроме SoC+хранилище; + - *управляемый ACC-off:* мягко, с коротким grace-окном на дисплей/камеру-рейл (парковка с реверсом — §7, B-COMPL-04). +3. **Ordered teardown:** App-Host останавливает апы/плагины (`StopApp`) с per-app ack/save-таймаутом + (сумма ≤ hold-up-бюджет); **E на `ShutdownImminent` гасит активный OBD-TX и закрывает ISO-TP-сессии** + (B даёт сигнал, E владеет закрытием — B не трогает CAN). +4. Settings флашится в `/data` + критичные логи `fsync` (a-base §9) — **критичный fsync завершается ДО PONR**. +5. Сохранение last-known-time (§8). +6. Размонтирование RW → **point-of-no-return**. +7. **Sn-питания** по `safe-to-cut` (§5). + +**Abort (re-power до PONR):** стабильное восстановление ACC/напряжения (через debounce/гистерезис) +→ обратный переход; **откат:** переподнять сброшенные нагрузки, снять у апов save-режим; +`Power` эмитит **`ShutdownAborted`** (= канонизированный сигнал отмены). После PONR abort недоступен. + +**Деградированный путь (исчерпание hold-up):** часть шагов — critical-and-atomic (должны +завершиться), часть — best-effort (могут быть потеряны); инвариант **«после усечённого shutdown +`/data` консистентен»** держится atomic-write-контрактом A §3. + +## 5. MCU-копилот (прошивка + протокол) + +- **Делает:** мониторит зажигание (дебаунснутый ACC) и напряжение, управляет hold-up/секвенсингом, + сигналит SoC, держит **watchdog**, sleep/wake. + +**Shutdown-протокол (специфицируем СЕЙЧАС — shutdown это MVP, не §12-задел):** +1. ACC-off → MCU стартует hold-up-таймер (= hold-time hardware §3); **это и есть тот же бюджет**, + что fail-safe-таймер §5 (один когерентный, не два). +2. SoC ведёт секвенсинг (§4 шаги 2–6); **heartbeat SoC→MCU во время shutdown** — MCU отличает + «SoC работает» (держать питание до бюджета) от «SoC завис» (cut по таймеру). +3. SoC шлёт **`safe-to-cut`** после PONR → MCU снимает питание немедленно. +4. Таймер истёк без `safe-to-cut` → MCU снимает всё равно (детерминированно; durability — до последнего fsync). +5. **Потеря линка** (обрыв шлейфа): fail-safe в ОБЕ стороны определён явно — MCU при тишине в + running трактует по политике (cut/reboot vs hold — §12); SoC при тишине MCU — деградирует. +6. **Supercap-only:** аналог на SoC-таймере + разряд cap (нет умного MCU для cut). + +**Безопасность линка:** MCU — **fail-safe-авторитет**, НЕ подчиняется слепо командам SoC с +power-эффектом; ни одно SoC-сообщение не должно (а) снять питание на ходу, (б) держать вечно и +разрядить АКБ. Защита от replay/мусора/десинка на UART/I2C (аналог защиты CAN-линка hardware §4). + +**Критичность MCU:** прошивка **обновляема** (бутлоадер — баг не кирпичит); **независимый +аппаратный fail-safe-таймер** снятия питания при отказе самого MCU; **позиция в цепочке доверия** +(подмена прошивки не обходит red-line). ⚠️ **Если v0-таргет = MCU-вариант — аппаратный fail-safe- +таймер ОБЯЗАН быть в v0** (иначе v0 power-safe не закрыт); прошивка-update — v1. + +### MCU-вариант vs supercap-only + +| Функция | MCU-вариант | supercap-only | Потеря/замена | +|---------|-------------|---------------|---------------| +| Детект ACC | MCU (дебаунс) | софт на SoC | — | +| Hold-up секвенсинг | MCU-таймер + safe-to-cut | SoC-таймер + разряд cap | при зависшем SoC некому shed/cut → нужен внешний таймер или принятый риск | +| Fail-safe при зависшем SoC | независимый MCU-таймер | **отсутствует** | внешний WDT-чип ЛИБО письменный риск единой точки | +| Watchdog-бэкстоп | независимый от SoC-WDT | только SoC-WDT | см. §6 | +| Sleep / scheduled-wake | always-on MCU | ограничено (нет always-on) | глубокий sleep/таймер-wake может быть недоступен | + +## 6. Watchdog (по фазам жизненного цикла) + +- **Runtime:** on-SoC RK3588 WDT — **единственный userspace-владелец** (`systemd RuntimeWatchdogSec`), + второй пэтящий клиент запрещён. +- **Shutdown-фаза:** `RuntimeWatchdogSec` её НЕ покрывает → отдельный таймаут: systemd + `RebootWatchdogSec` (путь reboot) **либо** детерминированный дедлайн MCU/supercap на финальный + unmount/sync (шаги 4–6). Зависание в sync/unmount (PONR) не оставляет устройство под питанием вечно. + **MCU на `shutdown-imminent` переключается в wait-for-completion** (расширенный таймаут ≥ shutdown-бюджет), + не продолжает короткий keepalive — чтобы не снять питание посреди unmount. +- **MCU-watchdog** — независимый бэкстоп. +- **Boot-окно:** WDT вооружён до userspace (U-Boot / always-on MCU). +- **System-of-record форс-ресета (v0)** = SoC-WDT + MCU-backstop + reboot, стыкуемый с **базовым + recovery A §6 (v0)**. Интеграция force-reset → **bootcount/slot-switch/откат A/B (A §5) — v4** (с OTA); + v0-watchdog от bootcount-handshake НЕ зависит. + +## 7. Sleep / wake и защита АКБ + +- **Низкопотребление** при заглушённом двигателе (быстрый/scheduled wake). +- **Источники пробуждения:** ACC-on, таймер, **реверс-передача** (Stage-0 камера, §4/J), опц. CAN-wakeup. +- **`battery-cutoff` (отдельно от ACC-off):** при долгой стоянке порог напряжения (раньше/выше + operating-brown-out hardware §3 — зарезервировать энергию на запуск) → sleep → forced off; + гистерезис на wake-on-ACC. Бюджет разряда — §12 (с hardware). +- **Гейт wake-word:** D §8 слушает только в `running`/`accessory`, **не** в `sleep`/`off` (читает сигналы `Power`). + +## 8. Сохранение и монотонность времени + +- **Save last-known-time** (`fake-hwclock`) в `/data` **не только на graceful shutdown**, но и: + (а) **периодически** systemd-таймером (~1–5 мин, только вперёд, по порогу прироста, atomic-write A §3); + (б) на каждом успешном NTP/GPS-синке (a-base §7). Save-on-shutdown — *финальный*, не единственный + (после резкого обрыва часы откатываются максимум на один интервал, не на всю сессию). +- **Lifecycle-таймеры на МОНОТОННЫХ часах** (`CLOCK_MONOTONIC`): `ShutdownImminent`-отсчёт, hold-up-бюджет, + watchdog keepalive, `Uptime`, sleep/wake — НЕ wall-clock (плата без RTC, wall-clock легитимно прыгает при синке). +- **Первый boot/factory-reset** (нет файла fake-hwclock): wall-clock с epoch до первого fix → TLS-egress + gated (a-base §7), метки критичных/audit-логов помечаются «до синка»; lifecycle-таймеры работают (монотонные). +- **Монотонность «только вперёд»:** при boot wall-clock = `max(fake-hwclock, build-epoch, last-log-ts)` — + не идти назад относительно последней записи (порядок audit-log не ломается). + +## 9. IPC (`ru.shturman.Power`) + +Реестр — [ipc.md](../contracts/ipc.md) §3. Уточнения этого домена: +- `GetPowerState() → state` — enum по §2 (`off`/`accessory`/`running`/`shutting_down`/`sleep`/`battery_cutoff`). +- `IgnitionState` (ось зажигания: off/accessory/running) — **канонический**; E зеркалит/потребляет, не дублирует. +- `PowerSource` — enum `{ vehicle_12v, holdup_cap, sleep_rail, low_battery }`; переход в `holdup_cap` + сообщает потребителям «питание от cap, времени мало». +- `ShutdownImminent(seconds, reason)` — `reason ∈ {acc_off, under_voltage, thermal, battery_cutoff}`. +- **`ShutdownAborted()`** — отмена (re-power до PONR). + +## 10. Функции + +| функция | MVP/later | зависит от | фаза | +|---------|-----------|------------|------| +| Детект ACC (debounce + crank) + `running` по напряжению | **MVP (день 1)** | hardware/MCU | v0 | +| Graceful shutdown sequencing (+ ordered teardown) | **MVP (день 1)** | a-base, hold-up (hardware §3) | v0 | +| Машина состояний (+ abort/committed) | **MVP** | — | v0 | +| `ru.shturman.Power` сервис | **MVP** | ipc | v0 | +| Watchdog (runtime + shutdown-фаза + boot-окно) | **MVP** | hardware, a-base | v0 | +| Load-shedding при power-loss | **MVP** | селективные рейлы (hardware §6) | v0 | +| Save last-known-time + периодика | **MVP** | a-base §7 | v0 | +| MCU-копилот shutdown-протокол (если MCU) | **MVP** | hardware §3 | v0 | +| **MCU аппаратный fail-safe-таймер** | **MVP** | hardware §3 | v0 | +| Thermal shutdown (триггер + hysteresis + UX) | **MVP** | a-base §10, hardware §1a | v0/v1 | +| MCU прошивка: update path | later | hardware | v1 | +| Sleep/wake + battery-cutoff | later | — | v1/v2 | +| Гейт wake-word по состояниям (с D) | **MVP** | D | v1 | +| Реверс-wake (Stage-0 камера) · scheduled/CAN-wake | later | hardware §4, J | v2/later | + +## 11. Зависимости + +- **Вниз:** hardware (power-path, **селективные рейлы**, ACC-GPIO, ADC напряжения, MCU + fail-safe-таймер, hold-up sizing, secure-boot trust). +- **Вбок:** A (RO-rootfs/ФС/atomic-write/время-синк/recovery — **вместе делают power-safe**); E (engine_running, закрытие ISO-TP на shutdown). +- **Вверх/потребители:** все апы (`ShutdownImminent`→«сохранись», `ShutdownAborted`→откат); Settings; D (гейт wake-word); Shell (UX «перегрев»). + +## 12. Открытые вопросы + +- 🟡 **MCU-копилот vs supercap-only** (hardware §3) — определяет владельца ACC/watchdog, **наличие + независимого бэкстопа и fail-safe-снятия при зависшем SoC**, и доступность scheduled-wake. +- ◻️ **Протокол SoC↔MCU** (UART/I2C/GPIO, формат, keepalive, политика тишины-линка, replay-защита) — + shutdown-подмножество уже специфицировано в §4/§5, остальное здесь. +- ◻️ **Бюджет разряда АКБ** (sleep, ACC-off listening, battery-cutoff порог) — числа с hardware. +- ◻️ **Тепловые пороги** (critical-trip + recovery-hysteresis) — согласовать с a-base §10 + hardware §1a. +- ◻️ **Удержание дисплей/камера-рейла после ACC-off** — кто арбитрит grace-hold (B состояние, C/J запрос); с реверс-камерой Stage 0. + +--- + +## Журнал решений (домен B) + +| Решение | Выбор | Дата | +|---------|-------|------| +| Детект | ACC (debounce+crank) + `running` по напряжению (RPM — v2-уточнение); IgnitionState — канон B | 2026-06-16 | +| Триггеры shutdown | ACC-off / under-voltage-held (не rate-of-change) / thermal-trip; cold-crank — ride-through | 2026-06-16 | +| Shutdown | sequencing + ordered teardown (E закрывает ISO-TP); abort до PONR (`ShutdownAborted`); degraded path | 2026-06-16 | +| MCU-протокол | hold-up-таймер + heartbeat + `safe-to-cut`; fail-safe при потере линка; MCU — fail-safe-авторитет | 2026-06-16 | +| MCU фазы | HW fail-safe-таймер = v0 (с MCU-вариантом); прошивка-update = v1 | 2026-06-16 | +| Watchdog | runtime (RuntimeWatchdogSec) + shutdown-фаза (Reboot/MCU дедлайн) + boot-окно; v0=SoC+MCU+reboot, bootcount=v4 | 2026-06-16 | +| Время | save периодически + on-sync + on-shutdown; lifecycle-таймеры монотонны; «только вперёд» | 2026-06-16 | +| Состояние | `Power.IgnitionState` канон; E владеет узким `engine_running` | 2026-06-16 | diff --git a/docs/domains/e-vehicle-data.md b/docs/domains/e-vehicle-data.md index da99d49..e02bdbd 100644 --- a/docs/domains/e-vehicle-data.md +++ b/docs/domains/e-vehicle-data.md @@ -26,7 +26,7 @@ | **MIL-статус + DTC count** (PID 01) | **MVP** | — | v2 | | Чтение DTC (Mode 03) + расшифровка по базе | **MVP** | dtc-база | v2 | | Пробинг поддерживаемых PID | **MVP** | — | v2 | -| Состояние машины (`off`/`acc`/`running`) | **MVP** | — | v2 | +| `engine_running` (running/stopped/unknown, из RPM) — НЕ дублирует `Power.IgnitionState` | **MVP** | — | v2 | | Подписка с rate-cap (~10–20 Гц) | **MVP** | ipc | v2 | | Pending/permanent DTC (Mode 07/0A) | later | — | v2+ | | Мгновенный расход (computed, **core**) | later | fuel_rate/MAF | v2/v3 | @@ -97,6 +97,8 @@ - **ISO-TP/multiframe таймауты** — отдельно от single-frame `GetSignal`-таймаута. - **При падении транспорта:** `Online=false` (ipc), затронутые сигналы → `unavailable` (не `stale`), без зависания/краха (#4); при восстановлении — re-probe. +- **На `ShutdownImminent` (от Power, домен B §4):** немедленно **гасим активный OBD-TX и закрываем + ISO-TP-сессии** (не оставить ECU в открытой диагностической сессии) — до снятия питания. ## 6. Зависимости