10 KiB
tft-app — fallback-рендер (safe-mode)
«Аварийный» режим индикации: минимум зависимостей, никаких ассетов и файловой системы. Это же — первый экран walking-skeleton (Фаза 1) и гарантированная деградация, когда ассеты/layout недоступны. Проектная основа — ARCH.md §11; реализация — fallback.c.
1. Что такое fallback
default-layout asset-free и FS-free: не монтирует QSPI-FAT/SD, не трогает TLV-ридер ассетов.
Зависимости — только bsp_display + gfx (framebuffer, вкомпилированные шрифты, примитив стрелки)
- домен. Рисует на чёрном фоне, без звука. Устройство никогда не «гаснет»: даже при полностью битых ассетах есть чем показать этаж.
Принцип degradation, а не отказа (ARCH §3): сбой ассетов/layout/связи ведёт к безопасному упрощению индикации, а не к отказу.
2. Что рисуется
Презентация решает по разрешённому режиму (indication_task_t.mode, см.
MODE_PRIORITY.md) — и по диспетчерскому входу (§2.1), который
проверяется первым и безусловно перекрывает всё остальное:
flowchart TD
START([render mode, result, dispatcher]) --> CLR["gfx_clear() — AS прозрачен,<br/>фон даёт чёрный PS"]
CLR --> DSP{"dispatcher != NONE?"}
DSP -->|да| DLBL["метка ВЫЗОВ/ОТВЕТ (SystemFont)"]
DLBL --> DONE([готово])
DSP -->|нет| Q{"mode == NORMAL?"}
Q -->|да| POS["этаж (FloorFontFallback), по центру"]
POS --> ARR{"direction UP/DOWN?"}
ARR -->|да| DRAWARR["примитив-стрелка"]
ARR -->|нет| DONE
DRAWARR --> DONE
Q -->|нет| LBL["метка режима (SystemFont)<br/>ПОЖАР/ПЕРЕГРУЗ/СЕРВИС/…"]
LBL --> LAD{"погрузка и lading_secs>0?"}
LAD -->|да| CNT["обратный отсчёт (крупно)"]
LAD -->|нет| DONE
CNT --> DONE
- Обычный режим — номер этажа (
FloorFontFallback) + стрелка вверх/вниз (примитив, без спрайтов). Двойная стрелка (SUL_DIR_DOUBLE) в fallback не рисуется — это спрайтовая индикация Фазы 4/5. - Спецрежим — короткая текстовая метка системным шрифтом (
SystemFont). Это safe-mode индикация: богатая полноэкранная графика режимов (фон+спрайты) появится в Фазах 4–5. Для временной погрузки дополнительно рисуется обратный отсчёт (lading_secs). - Диспетчерский вход (§3.4) — та же safe-mode текстовая метка (переиспользует слот режимов:
MODE_Y/SystemFont), но проверяется ПЕРВЫМ, доmode. Слова временные, «ВЫЗОВ»/«ОТВЕТ».
Следующий этаж (next) в fallback не показывается — это элемент богатого layout (Фаза 5),
поэтому на next_pending перерисовки нет.
2.1 Диспетчерский вход — не данные СУЛ, отдельный вход в рендер
opto IN1 («вызов подан») / IN2 («вызов принят») — локальный вход (ARCH §8 п.3), не
sul_result_t: не идёт через controller/таблицу приоритетов режимов (эта таблица — только
для ортогональных сигналов СУЛ, см. MODE_PRIORITY.md). Дизайн-диаграмма
ARCH §4 рисует его именно так — отдельным входом прямо в ui.render(task, model, settings), не
через controller:
flowchart LR
OPTO["bsp_opto IN1/IN2<br/>(app/dispatcher.c)"] -->|"g_dispatcher_indication"| RENDER["ui_fallback_render*()"]
CTRL["controller.process()<br/>(только sul_result_t)"] -->|"indication_task_t"| RENDER
Приоритет — высший из всех, согласовано с пользователем: перекрывает обычную индикацию И
любой режим СУЛ (пожар/перегруз/…), работает даже без связи со станцией (dispatcher — независимое
оборудование, не станция). ОТВЕТ (IN2) перебивает ВЫЗОВ (IN1), если оба почему-то активны
одновременно. Референс поведения (не архитектуры) —
OLD_PROJECT_TFT8_UKL/source/main_programm.c (tft_refresh_task): там icon_img_ptr
перезаписывается диспетчерской проверкой ПОСЛЕДНЕЙ в каждом кадре, безусловно — тот же эффект,
что здесь достигается проверкой ПЕРВОЙ в render() с return.
3. По-символьный fallback шрифта
Второй уровень деградации — на уровне отдельного символа. Активный шрифт может не покрывать
кодпойнт, который выдал декодер (напр. FloorFontFallback знает только 0–9, -, пробел, а
позиция пришла кириллицей). gfx_draw_string() подставляет - вместо отсутствующего глифа —
по-символьно, не обрывая всю строку на первом неизвестном символе. Если даже - нет в шрифте —
символ пропускается (нулевая ширина).
flowchart LR
CH["символ строки"] --> HAS{"есть глиф<br/>в активном шрифте?"}
HAS -->|да| DRAW["нарисовать глиф"]
HAS -->|нет| SUB{"есть глиф '-'?"}
SUB -->|да| DASH["нарисовать '-'"]
SUB -->|нет| SKIP["пропустить (ширина 0)"]
Механизм есть с Фазы 1. Выделенная чистая функция проверки покрытия шрифта + её host-тест — остаток Фазы 2 (см. PLAN.md).
4. Когда перерисовываем
ui_fallback_render() перерисовывает кадр целиком (без dirty-rect — gfx минимален) при
изменении того, что fallback реально показывает: позиция, стрелка или режим
(pos_pending || direction_pending || mode_pending). Иначе — no-op. И этот путь, и
безусловный старт (ниже) принимают диспетчерский вход (§2.1) отдельным параметром — сам он
НЕ входит в pos_pending/direction_pending/mode_pending (это diff sul_result_t, opto —
локальный вход, другая природа изменения).
Первую отрисовку презентация делает безусловно один раз при старте
(ui_fallback_render_initial()): кэш контроллера засеян дефолтом, и если первый реальный кадр
совпадёт с дефолтом, diff придёт «ничего не изменилось» — без безусловного старта экран остался бы
пустым. Тот же безусловный вызов переиспользуется как ТРЕТЬЯ причина полной перерисовки (кроме
старта и закрытия меню) — смена диспетчерского входа САМА ПО СЕБЕ, без нового кадра СУЛ в
очереди: render_task (см. TASKS.md §3) сравнивает текущее
g_dispatcher_indication с прошлым и, если отличается, зовёт ui_fallback_render_initial()
из последнего известного sul_result_t + нового диспетчерского значения.
5. Как кадр попадает на экран (Фаза 3.2.4 — фундамент рендера)
Ограничение одного framebuffer'а Фазы 1 снято. render() рисует off-screen в
альфа-поверхность AS (ARGB8888); показ — отдельным вызовом gfx_present() у владельца
дисплея (task_render.c): PXP компонует AS над
чёрным фоном PS и пишет RGB565 в задний framebuffer, затем tear-free свап синхронно с
ELCDIF (FRAME_DONE). Гибрид bpp: рисование и AA — в полных 8 битах (AS), 565 — только на
самом выходе (сплошные цвета/текст квантуются незаметно; проверено на панели).
Индикация всегда рендерится полным кадром (в отличие от меню, где после открытия перекомпоновывается только окно — см. MENU.md §5). Подробности компоновщика и разбивка по задачам — TASKS.md.