diff --git a/.gitignore b/.gitignore index 7e833bb..8c8d60b 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,8 @@ tools/host/.venv-host-win/ .zed/ project_tree.txt tools/service_tui/dist/ + +# Референсные дампы старых серийных проектов (не коммитим) +firmware/tft_app/OLD_PROJECT/ +firmware/tft_app/OLD_PROJECT_*/ +firmware/tft_app/special/ diff --git a/firmware/tft_app/ARCH.md b/firmware/tft_app/ARCH.md new file mode 100644 index 0000000..9dbee7b --- /dev/null +++ b/firmware/tft_app/ARCH.md @@ -0,0 +1,278 @@ +# tft-app — архитектура + +> Прошивка лифтового индикатора для семейства **MIMXRT1052 TFT (4 / 7 / 8 / 10")**. +> Документ — живой источник истины по слоистой архитектуре. Статус фаз разработки — +> в [PLAN.md](PLAN.md). + +--- + +## Часть I. Требования (бизнес-логика) + +### 1. Основные функции + +**1.1 Индикация в реальном времени.** Местоположение кабины, спецрежимы, музыкальное +сопровождение при движении, озвучка сигналов. +- Сигналы от СУЛ приходят по последовательному интерфейсу (+24 В UART, кастомный + бинарный протокол) либо по CAN. +- Отображение состояния двух диспетчерских оптовходов: «Вызов подан» / «Вызов принят» / пусто. + +**1.2 Обновление ПО в поле** через microSD (сам образ прошивки — задача bootloader, +A/B Direct-XIP; ассеты/layout — задача tft-app, см. §12). + +**1.3 Графический интерфейс настроек** в рантайме (меню, навигация двумя кнопками). + +### 2. Частности + +**2.1 Отображение.** 4 типа дисплея (TFT4/7/8/10); одна кодовая база, две сборки +(см. §9). Ассеты (иконки, картинки, звук) — на встроенной флеш, обновляются с microSD. +Шрифты — C-массивы ([LCD Image Converter](https://lcd-image-converter.riuson.com/)). +Макеты UI унифицированы и упрощены; по запросу клиента — кастом. + +**2.2 Связь с СУЛ.** Общий программный интерфейс ко всем СУЛ; одна прошивка — множество +протоколов; выбор протокола через меню. Протоколы: УЭЛ, УКЛ, НКУ-CAN, НКУ-SD7, УИМ (список +открыт). + +**2.3 Настройки.** Общий набор параметров + кастом под заказ. Настройки влияют на UI и на +работу протокола (напр. адрес индикатора: 0..15 у НКУ-CAN, 1..50 у УИМ) — все связи +предусмотрены (см. §6, §8). + +--- + +## Часть II. Архитектура + +### 3. Принципы + +1. **Домен не знает железа.** Декодеры протоколов и доменная модель — чистый C без единого + HAL-вызова, тестируются на хосте (Unity + fff) тем же способом, что модули bootloader. +2. **Одна прошивка — много протоколов.** Протокол выбирается в рантайме из настроек через + реестр драйверов (§6). Новый протокол = реализовать декодер + зарегистрировать, без правок + в остальных слоях. +3. **Деградация, а не отказ.** Любой сбой ассетов/layout/связи ведёт к безопасному упрощению + индикации (§7, §11), устройство не «кирпичится» и не гаснет. +4. **Данные, а не код.** Макет, каталог ассетов, таблица приоритетов режимов, дескрипторы + настроек протоколов — данные (компилируемые или загружаемые), не разветвления в коде. +5. **Границы модулей = конвенции репозитория.** Каждый слой — статическая библиотека CMake, + публичные заголовки в `include/`, приватное в `src/`. Аппаратные модули — через `bsp/*`. + +### 4. Слои + +``` +┌──────────────────────────────────────────────────────────────────────┐ +│ app/ FreeRTOS-задачи, wiring, main (firmware/tft_app)│ L4 +├──────────────────────────────────────────────────────────────────────┤ +│ ui/ view-model → экран (layout-движок, темы, fallback-рендер) │ +│ menu/ экран настроек, навигация 2 кнопки │ L3 презентация +│ audio_policy доменное событие → команды звука │ +├──────────────────────────────────────────────────────────────────────┤ +│ controller редьюсер: sul_result_t (+кэш) → indication_task (diff) │ +│ sul/ реестр драйверов; драйвер = decoder(pure) + transport(hw) │ L2 ДОМЕН +│ elevator_model канонический sul_result_t, приоритеты режимов │ (чистый C, host-тесты) +├──────────────────────────────────────────────────────────────────────┤ +│ gfx (compositor+PXP+fonts) audio_engine (WAV/playlist над bsp_mqs) │ +│ assets (TLV-ридер, image cache) settings_store fs (FatFS SD/QSPI) │ L1 сервисы (адаптеры к железу) +├──────────────────────────────────────────────────────────────────────┤ +│ bsp/* can uart_host opto button mqs sd qspi_flash sdram display led … │ L0 БОЕВОЙ, не трогаем +└──────────────────────────────────────────────────────────────────────┘ +``` + +**Поток данных** (адаптирован из проекта `special`, поверх FreeRTOS): + +``` +транспорт (CAN/UART) ─► sul_driver.transport ─► sul_driver.decode(frame) ─► sul_result_t + │ poll + timeout→default + ▼ + controller.process(sul_result_t) ─► indication_task (dirty-флаги) + │ + ┌────────────────────────────────────────────────┬─────────┘ + ▼ ▼ + ui.render(task, model, settings) audio_policy(task, model, settings) + (+ локальные входы: opto диспетчер, меню) │ + audio_engine (bsp_mqs) +``` + +### 5. Размещение и тестируемость + +| Слой | Где живёт | Тип | Host-тест | +|---|---|---|---| +| `elevator_model`, `sul` (декодеры), `controller`, `audio_policy`, layout-солвер | `firmware/tft_app/src/domain/*`, `.../ui/layout/*` | чистый C | **да** (golden-векторы) | +| `sul` транспорт-адаптеры | `firmware/tft_app/src/domain/sul/transport/*` | HW | HIL | +| `gfx`, `audio_engine`, `assets`, `settings_store`, `fs` | `firmware/tft_app/src/services/*` | HW/адаптеры | частично (парсеры/TLV — да) | +| `ui`, `menu`, `app` | `firmware/tft_app/src/{ui,menu,app}/*` | app | вид/меню — HIL | + +Host-тесты — `tests/host/tft_app_*` (Unity + fff, компилируем `.c` домена против моков bsp, +по образцу `tests/host/mcuboot_port`, `tests/host/protocol`). + +### 6. Связь с СУЛ (`sul`) + +**Канонический выход любого протокола** — `sul_result_t` (надмножество; простой протокол не +заполняет лишнее): + +```c +#define SUL_POS_MAX 4 /* сейчас значимы 2; запас до 4 */ + +typedef struct { + char pos[SUL_POS_MAX * 4 + 1]; /* UTF-8, позиция кабины (напр. "12","-1","П") */ + char next[SUL_POS_MAX * 4 + 1];/* UTF-8, следующий этаж (или пусто) */ + uint8_t direction; /* none / up / down / double */ + /* Ортогональные сигналы (могут сосуществовать; приоритет разрешает controller): */ + bool arrival; /* гонг */ + bool movement; /* начало движения */ + bool overload; /* перегруз */ + bool fire_alarm; /* пожарная тревога */ + bool lading; /* погрузка */ + bool maintenance; /* сервисный режим */ + bool fireman; /* режим пожарного */ + bool seismic; /* сейсмоопасность */ + bool error; /* авария */ + uint16_t lading_secs; /* обратный отсчёт погрузки, 0 = нет */ + uint8_t floor_num; /* производный числовой этаж для озвучки (0 = н/д) */ +} sul_result_t; +``` + +- **Позиция — UTF-8 строка** (у нас реальные шрифты ASCII + кириллица), а не число: + универсально для всех протоколов, host-тест сравнивает строки. Декодер не знает про шрифт. +- **Escape-hatch под кастом не закладываем** (YAGNI); совместимость обеспечивает версия схемы. +- **Валидация рендеримости** (§11): декодер может выдать кодпойнт вне покрытия активного + шрифта (кастомные коды протокола) → по-символьный fallback на этапе рендера. + +**Драйвер = чистый декодер + транспорт-адаптер:** + +```c +typedef struct { const uint8_t *data; uint16_t len; uint32_t id; uint8_t bus; } sul_frame_t; + +/* Чистая функция — без железа, host-тестируется golden-векторами. */ +typedef sul_status_t (*sul_decode_fn)(void *ctx, const sul_frame_t *frame, sul_result_t *out); + +typedef struct { + uint8_t id; /* идентификатор протокола (стабильный) */ + const char *name; /* для меню */ + sul_decode_fn decode; /* pure */ + const sul_settings_desc_t *settings; /* per-protocol параметры (§8) */ + /* транспорт (CAN/UART) — отдельный тонкий адаптер, привязан к драйверу */ +} sul_driver_t; +``` + +- **Реестр** `sul_registry[]` — таблица драйверов по `id`. Активный выбирается из настроек. + Добавление протокола = запись в таблицу. +- **Poll + timeout.** `sul` опрашивается периодически; при отсутствии кадров дольше таймаута + выдаётся `default` (потеря связи → сброс режимного состояния), как в `special`/OLD_PROJECT. + +### 7. Контроллер и приоритеты режимов + +`controller.process(sul_result_t*) → indication_task_t` (dirty-флаги: `pos_pending`, +`direction_pending`, `mode_pending`, `arrival_pending`, …) — презентация перерисовывает и +озвучивает только изменившееся (кэш прошлого состояния внутри контроллера). + +- Ортогональные булевы сигналы `sul_result_t` сходятся в **один экранный режим** через + **таблицу приоритетов** (пожар > перегруз > сейсмо > сервис > … > норма). +- **Таблица приоритетов — данные, потенциально клиентские**, живёт рядом с клиентским + конфигом/layout, а не хардкодом в контроллере. Контроллер применяет активную таблицу. + +### 8. Настройки + +Три раздельных источника (не смешивать): + +1. **`sul_result_t`** — только данные от СУЛ (§6). +2. **`settings`** — конфиг устройства/пользователя: громкости, лого, серийник, ёмкость, год, + выбранный протокол, панель (provisioning). Хранится на QSPI (сектор настроек), формат с + магиком/версией/CRC (развитие `settings_manager` из OLD_PROJECT). +3. **Локальные входы** — оптовходы (диспетчерский вызов/ответ) и кнопки/меню; вливаются на + уровне контроллера/презентации, не часть протокольных данных. + +**Per-protocol настройки.** Каждый протокол регистрирует `sul_settings_desc_t` — какие у него +параметры, диапазоны, подписи для меню (напр. адрес 0..15 у НКУ-CAN, 1..50 у УИМ). `settings` +держит слайс под активный протокол; `driver` получает свой конфиг; меню строится из дескриптора +(развитие `settings_mgr_bind_menu`). Общие настройки — отдельно от протокольных. + +### 9. Дисплеи и платы + +Платы отличаются **только RGB-интерфейсом LCDIF** (TFT4 — 40pin без пинов ориентации; TFT7/8/10 +— 50pin с U/D·L/R). SDRAM, SEMC, периферия — одинаковы. Панели TFT7/8/10 уже разведены рантаймом +в `bsp_display` (таблица `panel_config[]`: тайминги, клок, `has_orientation_pins`). + +| Профиль сборки | LCDIF | Панель | Клок | +|---|---|---|---| +| `app-tft4` | 40pin, без ориентации | TFT4 (фикс) | Video PLL | +| `app-big` | 50pin, с U/D·L/R | 7 / 8 / 10 — **рантайм** из provisioning | PLL2 | + +Различие изолировано в одном board-файле пин-мукса LCDIF. **Тип панели — provisioning-параметр** +(пишется service_tui), не пользовательская настройка. Матрица сборки — два buildPreset +(`app-tft4`, `app-big`), как `bootloader`/`firmware-test`. + +### 10. Карта QSPI (W25Q128, 16 МБ) + +| Регион | Смещение | Размер | Владелец | +|---|---|---|---| +| bootloader | `0x000000` | 256 КБ | bootloader | +| slot A (tft_app) | `0x040000` | 2 МБ | MCUboot | +| slot Б (tft_app) | `0x240000` | 2 МБ | MCUboot | +| **layout-регион** | `0x440000` | 64 КБ | tft_app | +| **assets-регион** | `0x450000` | ≈ 11 МБ | tft_app | +| settings | `0xFFE000` | 4 КБ | tft_app | + +layout- и assets-регионы — **вне** flash-area загрузчика (bootutil про них не знает). Разметка +фиксируется в **едином partition-заголовке**, из которого читают app и генератор для service_tui. + +### 11. Ассеты, layout и fallback + +**Разделяем два артефакта** вместо монолитного `style.img`: + +- **layout** — декларативная таблица виджетов (что рисуем: позиция/стрелка/режим/лого/…, якорь, + размер в %/единицах, шрифт, привязка к полю модели). Якорное позиционирование → один макет + раскладывается под 480×272 и под 1024×600. Формат — **TLV**, схема версионируется. + Layout-солвер (якорь→пиксели) — чистый C, host-тест (golden-render). Виджеты бывают + **примитивные** (text/rect/line/arrow) и **спрайтовые** (sprite-by-id) — единый движок. +- **assets** — упакованный индексированный **TLV-бандл** (host-утилита `tools/`): PNG → сырой + ARGB8888 на этапе пака (в рантайме нет lodepng и boot-time декодирования; XIP-mmap с FlexSPI, + блит без копии). Звук — WAV в том же бандле. + +**Загрузка layout при старте:** сначала layout-регион QSPI (магик+версия схемы+CRC+совместимость); +если валиден — берём его; иначе — **встроенный default**. + +- **Модель 1 (baked):** нужный layout вкомпилен как default в app-слот → цельный подписанный + образ, тестируется целиком, деплой через bootloader A/B. layout-регион пуст. +- **Модель 2 (injected):** базовый бинарь; service_tui пишет клиентский layout-блоб в регион + (вне слота → app-обновление его не трогает). Пересборка не нужна. Валидация блоба — на host + (схема + golden-render), поэтому проверка клиентского layout не требует сборки клиентского + бинаря. + +**Fallback (safe-mode).** `default`-layout — **asset-free и FS-free**: не монтирует QSPI-FAT/SD, +не трогает TLV-ридер. Зависимости — только `bsp_display` + `bsp_can`/`sul` + домен + +один вкомпилированный шрифт + примитивы `gfx`. Рисует **только этаж + стрелки (вверх/вниз)**, +чёрный фон, без звука. Это же — первый экран walking-skeleton (§PLAN, Фаза 1). + +**Деградация — по-виджетно.** layout битый → полный fallback. layout валиден, но ассет +отсутствует/битый → рисуем **примитивную форму этого виджета**, а не полный откат. Символ вне +покрытия шрифта → по-символьный fallback. Решение защёлкивается на старте (при XIP-mmap рантайм +не ревалидирует — иначе битое чтение = hardfault); валидация ассетов — CRC в индексе TLV. + +### 12. Обновление в поле + +- **Ассеты/layout — app-side updater** (доменные данные; app знает схему и валидацию; + переиспользуем `bsp_qspi` + FatFS-на-SD, как `update_style_task` в OLD_PROJECT). Запись — + потоковая, erase-before-write по 4 КБ, **заголовок-валидатор пишется последним** (торн-запись + → регион невалиден → безопасный fallback, не кирпич). +- **Provisioning-заливка** (service_tui, заводской/сервисный контекст): канал — USB-CDC + загрузчика или USB-SDP ROM, **при условии приемлемой скорости**. Если заливка ассетов этим + путём окажется слишком медленной — отказываемся и делаем **только через microSD**. Решение — + по замеру на Фазе обновлений (§PLAN). + +### 13. Тестирование + +- **Host (Unity + fff):** декодеры всех протоколов (golden-векторы кадров → `sul_result_t`), + контроллер (diff + приоритеты), layout-солвер (golden-render), TLV-ридер/паковщик, парсеры + настроек, валидация рендеримости. +- **HIL:** gfx/PXP/ELCDIF, audio/MQS, SD/QSPI, реальные транспорты CAN/UART (по образцу + `tests/target/*` и `06_test_firmware_*`). + +--- + +## Приложение. Заимствования из проектов + +- `special` — эталон доменного дизайна: `UnifiedProtocolData`→`sul_result_t`, `proto_handler` + (poll+timeout)→реестр `sul`, `controller`+`IndicationTask`. Адаптирован под FreeRTOS и снейк-кейс. +- `OLD_PROJECT` (НКУ-CAN, TFT7/10) — боевые `bsp`, compositor (PXP), audio-движок, + `settings_manager`, декодер НКУ-CAN (PACKET1..5, адресация, удалённая установка адреса). +- `OLD_PROJECT_TFT8_UKL` — наш `bsp` (button/opto/sd/w25q/settings/file_loader), UART+CAN. +- `TFT10_UIM`, `TFT4_UIM`, `TFT4_SD7`, `TFT4_UEL` — декодеры УИМ / НКУ-SD7 / УЭЛ, матрица + «протокол × дисплей». diff --git a/firmware/tft_app/PLAN.md b/firmware/tft_app/PLAN.md new file mode 100644 index 0000000..9160480 --- /dev/null +++ b/firmware/tft_app/PLAN.md @@ -0,0 +1,204 @@ +# tft-app — план разработки + +> Пофазовый план. Стратегия — **walking skeleton**: сначала тонкий сквозной срез +> (CAN → модель → этаж на экране), затем наращивание слой за слоем. Архитектура — +> [ARCH.md](ARCH.md). Этот файл — **источник истины по статусу фаз**. +> +> Каждая фаза: цель · объём · артефакты · тесты · критерий выхода. Ранние фазы детальны, +> поздние укрупнены и детализируются по мере приближения. +> +> Легенда статуса: ⬜ не начата · 🟨 в работе · ✅ готова. + +--- + +## Обзор фаз + +| # | Фаза | Статус | +|---|---|---| +| 0 | Каркас проекта и сборка | ⬜ | +| 1 | Walking skeleton (CAN → модель → этаж + стрелки) | ⬜ | +| 2 | Домен: полный `sul_result_t`, приоритеты, таймаут | ⬜ | +| 3 | Настройки и меню, локальные входы (opto) | ⬜ | +| 4 | Ассеты: TLV-бандл, паковщик, XIP-блит | ⬜ | +| 5 | Layout-движок: схема, солвер, темы, injected-регион | ⬜ | +| 6 | Аудио: движок над MQS, `audio_policy` | ⬜ | +| 7 | Обновление в поле (SD) + provisioning-канал | ⬜ | +| 8 | Мультипротокол: УИМ / SD7 / УЭЛ / УКЛ + per-client | ⬜ | +| 9 | Профиль `app-tft4` (40pin LCDIF, Video PLL) | ⬜ | + +--- + +## Фаза 0 — Каркас проекта и сборка ⬜ + +**Цель.** `firmware/tft_app` собирается как ARM-таргет, грузится через bootloader, мигает +heartbeat; host-тест-харнесс на месте. Пустой, но живой каркас всех слоёв. + +**Объём.** +- `firmware/tft_app/{CMakeLists.txt, src/, README.md}`; подкаталоги `src/{domain,services,ui,menu,app}`. +- `main.c`: `BOARD_ConfigMPU/InitPins/BootClock`, FreeRTOS, задача heartbeat (`bsp_led`, `bsp_tick`). +- Подключить в корневой `CMakeLists.txt` (заменить `firmware/bootloader/test_stub`). +- CMakePresets: buildPresets `app-big-debug/release` (профиль `app-big`). `app-tft4` — заглушка до Фазы 9. +- Линковка как MCUboot-слот-образа (Direct-XIP, `flexspi_nor.ld` + slot-offset), HAB-таргет. +- `tests/host/tft_app_smoke/` — пустой Unity-таргет, зелёный (проверка харнесса). +- `.vscode/launch.json` — конфигурация `🐛 Debug: tft_app (FreeRTOS)` (уже упомянута в DEV_ARCH). + +**Тесты.** `just build::build-app-big-debug` собирает ELF; host smoke-тест зелёный. + +**Критерий выхода.** ELF грузится bootloader'ом из слота, heartbeat мигает (проверка — пользователь +на железе). Удалён `test_stub` из корневого CMake. + +--- + +## Фаза 1 — Walking skeleton ⬜ + +**Цель.** Сквозной срез: реальный CAN-кадр НКУ → чистый декодер → контроллер → **fallback-рендер +этажа и стрелки** на экране. Никаких ассетов, FS, TLV, меню. + +**Объём.** +- `domain/elevator_model`: `sul_result_t`, `sul_status_t`, дефолт-состояние. +- `domain/sul`: тип `sul_driver_t`, реестр (одна запись); **чистый декодер `sul_nku_can`** + (подмножество: `pos`, `direction`) — портирован из OLD_PROJECT `msg_receiver_task` PACKET1/3. +- `domain/sul/transport/can`: тонкий адаптер `bsp_can` → `sul_frame_t`. +- `domain/controller`: `process()` → `indication_task_t` (diff), кэш, poll + timeout→default. +- `services/gfx`: минимум над `bsp_display` — один вкомпилированный шрифт + примитивы (стрелка). +- `ui/fallback`: рендер `pos` + стрелка (asset-free, FS-free) — §11 ARCH. +- `app`: задачи `sul_rx` (poll CAN) и `render`; wiring. + +**Тесты (host).** +- `tests/host/tft_app_sul_nku`: golden-векторы CAN-кадров → ожидаемый `sul_result_t`. +- `tests/host/tft_app_controller`: последовательность `sul_result_t` → корректные dirty-флаги, + таймаут→default. + +**Критерий выхода.** На стенде: подан НКУ-CAN трафик → на экране меняется номер этажа и стрелка +направления; при пропадании трафика — «--». Домен-тесты зелёные в CI. + +--- + +## Фаза 2 — Домен: полный контракт ⬜ + +**Цель.** `sul_result_t` в полном объёме (§6 ARCH), таблица приоритетов режимов, устойчивый +таймаут/потеря связи. + +**Объём.** Все поля модели (next, сигналы, `lading_secs`, `floor_num`); полный декодер НКУ-CAN +(PACKET1..5, режимы, удалённая установка адреса); `controller` разрешает приоритет режимов по +**таблице-данным** (§7); валидация рендеримости позиции (renderable-множество шрифта + +по-символьный fallback). + +**Тесты (host).** Расширенные golden-векторы НКУ (все режимы, спецсостояния); тесты таблицы +приоритетов; тесты валидации/fallback символов. + +**Критерий выхода.** Все режимы НКУ-CAN корректно доходят до fallback-рендера; полное host-покрытие +декодера и приоритетов. + +--- + +## Фаза 3 — Настройки и меню ⬜ + +**Цель.** Персистентные настройки на QSPI, меню на двух кнопках, локальные входы (opto). + +**Объём.** `services/settings_store` (магик/версия/CRC, сектор настроек §10); дескрипторы +per-protocol настроек (§8); `menu/` (навигация, редактирование, привязка к дескрипторам); +`bsp_button` + `bsp_opto` (диспетчерский вызов/ответ) → контроллер/презентация. + +**Тесты (host).** Сериализация/дефолты/CRC настроек; логика навигации меню; связывание +дескрипторов. + +**Критерий выхода.** Меню редактирует и сохраняет настройки; opto-иконки отображаются; выбор +протокола и его параметров работает (пока протокол один). + +--- + +## Фаза 4 — Ассеты (TLV-бандл) ⬜ + +**Цель.** Ассеты в упакованном индексированном бандле на QSPI, XIP-блит без рантайм-декодирования. + +**Объём.** Формат TLV (индекс, per-entry CRC, версия); host-утилита `tools/` (PNG→ARGB8888, WAV, +пак); `services/assets` (TLV-ридер, XIP-mmap, image cache); partition-заголовок (§10). + +**Тесты (host).** Раунд-трип паковщик↔ридер; целостность индекса/CRC; кросс-валидация id. + +**Критерий выхода.** Спрайты грузятся из assets-региона и блитятся; битый/отсутствующий ассет → +по-виджетная деградация. + +--- + +## Фаза 5 — Layout-движок ⬜ + +**Цель.** Декларативные макеты: схема TLV, якорный солвер, темы/шаблоны, injected-регион + +baked default. + +**Объём.** Схема layout (примитивные + спрайтовые виджеты, привязка к полям модели); солвер +якорь→пиксели (чистый C); загрузчик (регион QSPI → иначе baked default, §11); порт боевых макетов +НКУ (style_1/style_2) на новую схему; расширение host-утилиты (компиляция layout-исходника → TLV). + +**Тесты (host).** Golden-render солвера на 480×272 и 1024×600; валидация схемы/версии; выбор +region/baked. + +**Критерий выхода.** Богатый макет рисуется из данных; injected-layout валидируется на host без +сборки бинаря; клиентский кастом = новая таблица данных. + +--- + +## Фаза 6 — Аудио ⬜ + +**Цель.** Озвучка и музыка через MQS, отделённая политика «событие → звук». + +**Объём.** `services/audio_engine` (WAV-парсер, плейлист/приоритеты, воспроизведение над +`bsp_mqs` + eDMA); `audio_policy` (доменное событие/`indication_task` → команды звука: озвучка +этажа, гонг, тревоги, музыка); звук в TLV-бандле. + +**Тесты (host).** WAV-парсер; логика плейлиста/приоритетов/вытеснения; маппинг `audio_policy` +(событие→очередь) на golden-сценариях. + +**Критерий выхода.** Полная звуковая индикация НКУ (этажи, гонги, тревоги, музыка) с приоритетами. + +--- + +## Фаза 7 — Обновление в поле ⬜ + +**Цель.** Обновление ассетов/layout с microSD; замер и решение по provisioning-каналу. + +**Объём.** App-side updater (SD→QSPI-регион, потоковая запись, заголовок-валидатор последним, +§12); интеграция в service_tui; **замер скорости** заливки через CDC/SDP → решение SD-only или +CDC/SDP (§12). + +**Тесты.** HIL: обновление региона с SD, устойчивость к торн-записи (→ fallback). Замер скорости. + +**Критерий выхода.** Ассеты/layout обновляются с SD; провижининг-путь выбран по факту замера. + +--- + +## Фаза 8 — Мультипротокол ⬜ + +**Цель.** Остальные протоколы + клиентская специфика. + +**Объём.** Чистые декодеры УИМ, НКУ-SD7, УЭЛ, УКЛ (+ UART-транспорт-адаптеры) в реестр; их +per-protocol дескрипторы настроек; клиентские таблицы приоритетов и макеты. + +**Тесты (host).** Golden-векторы по каждому протоколу; HIL по CAN и UART транспортам. + +**Критерий выхода.** Любой протокол выбирается в меню и работает без правок остальных слоёв; +добавление нового протокола = декодер + запись в реестр. + +--- + +## Фаза 9 — Профиль `app-tft4` ⬜ + +**Цель.** Вторая сборка под 40-pin плату TFT4. + +**Объём.** Board-файл пин-мукса LCDIF (40pin, без ориентации); Video PLL клок (закрыть TODO в +`bsp_display`); buildPreset `app-tft4`; проверка размеров буферов/RAM; макеты под 480×272. + +**Тесты.** HIL на плате TFT4. + +**Критерий выхода.** `app-tft4` собирается и работает на плате TFT4; `app-big` — на TFT7/8/10 с +рантайм-выбором панели. + +--- + +## Открытые вопросы (уточняются по мере приближения фаз) + +- Точная скорость provisioning-заливки через CDC/SDP → SD-only? (Фаза 7) +- Формат исходника layout для host-утилиты (JSON/TOML) и позже GUI поверх компилятора. (Фаза 5) +- Renderable-множество символов на протокол vs общий шрифт; политика по-символьного fallback. (Фаза 2) +- Звук в бандле: WAV как есть или препарсенный PCM. (Фаза 6)