lift_indicator_suite/tools/service_tui
2026-07-20 15:52:22 +03:00
..
app # bootloader, service-tui: ready for release 2026-07-20 15:52:22 +03:00
assets # Refactoring: service-tui directory changed 2026-07-07 18:37:26 +03:00
custom_binaries # Refactoring: service-tui directory changed 2026-07-07 18:37:26 +03:00
docs # bootloader, service-tui: ready for release 2026-07-20 15:52:22 +03:00
tests # bootloader, service-tui: ready for release 2026-07-20 15:52:22 +03:00
main.py # Refactoring: service-tui directory changed 2026-07-07 18:37:26 +03:00
pyproject.toml # bootloader, service-tui: ready for release 2026-07-20 15:52:22 +03:00
README.md # bootloader, service-tui: ready for release 2026-07-20 15:52:22 +03:00
service_tui.spec # Refactoring: service-tui directory changed 2026-07-07 18:37:26 +03:00
uv.lock # bootloader, service-tui: ready for release 2026-07-20 15:52:22 +03:00

service-tui — TUI сервисного инженера

TUI-приложение для диагностики и прошивки платы MIMXRT1052CVJ5B на сервисе. Написано на Python + Textual. Работает на Linux, macOS, Windows.

Внутреннее устройство, диаграммы архитектуры и заметки для разработчиков — в ARCHITECTURE.md.


Экраны приложения

Ожидание подключения (WaitingScreen)

Стартовый экран. Автодетект USB — как только плата подключена, TUI сам определяет режим (прошивка или диагностика) и переключается.

┌────────────────────────────────────────────────────┐
│              service_tool  v0.2.0                   │
│                                                      │
│                 [LOGO_ART]                          │
│                                                      │
│   Подключите плату индикатора к USB...          ⠋   │
│                                                      │
│              [ ✕ Выйти из приложения ]              │
└────────────────────────────────────────────────────┘

Версия читается из pyproject.toml — при бампе версии мокап выше не нужно обновлять руками, TUI подставит актуальную сама.

После детекта платы подсказка меняется на «Плата найдена, подключаемся...», спиннер продолжает крутиться — экран остаётся на месте ещё несколько секунд (до ~10 с, если подключён HIL-стенд M5StampPLC и его агент только что перезапустился при открытии порта), пока TUI подключается к плате и, опционально, к M5. Это не зависание.

При потере соединения на любом другом экране сессия разрывается полностью — TUI не пытается восстановить прежнее состояние, а возвращается сюда. Над подсказкой на 4 секунды появляется строка ⚠ <причина> (например, «⚠ Соединение с платой потеряно»), затем скрывается сама — обычный автодетект продолжается без вмешательства оператора.

Прошивка платы (FlashScreen)

Триггер: обнаружена плата в режиме BootROM SDP.

┌────────────────────────────────────────────────────┐
│  ⚡ Загрузка прошивки на плату индикатора            │
│     (режим BootROM)                                │
│                                                    │
│  Выбор загружаемой прошивки                        │
│  ◉ Диагностическая прошивка (firmware_test)        │
│  ○ Серийная прошивка (загрузчик)                   │
│    ○ Верификация (smoke-test, требует BOOT_MOD)    │  ← только если «Серийная»
│  ○ Другое                                          │
│    Файл (custom_binaries/)                         │
│    [ TFT_BOOTLOADER_NEW.bin              ▾ ]       │  ← только если «Другое»
│    Память платы                                    │
│    [ W25Q128 / W25Q64                    ▾ ]       │
│    ○ Использует SDRAM (DCD)                        │
│                                                    │
│  [ ▶ Загрузить ] [ ⚠ Очистить память ] [ ✕ Выйти из приложения ] │
│                                                    │
│  ████████████░░░░░░  ← без числового %             │
│  ┌────────────────────────────────────────────┐    │
│  │ ▶ Сборка HAB-образа (HabImage)...          │    │
│  │ ▶ Прошивка: TFT_BOOTLOADER_NEW              │    │
│  │ ...                                         │    │
│  └────────────────────────────────────────────┘    │
└────────────────────────────────────────────────────┘

Лог виден постоянно (не только во время прошивки), прогресс-бар — только во время активной операции (скрыт в простое), без числового % — только полоса и построчный лог в реальном времени. Панель выбора прошивки ограничена по высоте и скроллится сама, если разрастается (варианты "Другое") — лог снизу гарантированно не сжимается меньше 6 строк.

"Другое" — для бинарников, собранных не в этом репозитории. В custom_binaries/ кладётся сырой образ (код + таблица векторов, без FCB/IVT/DCD — то же самое, что build/Debug/bootloader.bin до сборки HAB). TUI сама собирает из него загружаемый образ на лету, in-process через Python API spsdk (без вызова внешних CLI-утилит):

  1. HabImage (spsdk) — добавляет IVT (+DCD, если включён тумблер "Использует SDRAM")
  2. в Flash пишется явный FCB под выбранную память платы (не тот же auto-config, что для штатных firmware_test/bootloader/app — для W25Q256/512 он ненадёжен, см. docs/ARCHITECTURE.md)
  3. образ прошивается с 0x60001000, как обычно

Выбор запоминается на весь запуск TUI — файл, память платы, DCD и чек-бокс «Верификация» не нужно выставлять заново на каждой следующей плате: прошили одну, вынули USB, вставили следующую такую же — TUI уже подставила прошлый выбор, остаётся нажать "Загрузить". Сбрасывается только при перезапуске TUI.

Верификация записи — два независимых уровня. После записи, перед reset, TUI всегда читает записанный диапазон обратно и сверяет с образом (silent-corruption ловится автоматически, для любой прошивки — в логе видна строка «Верификация записи: OK», отдельного экрана не требует). Чек-бокс «Верификация» у «Серийной прошивки» — это другой, более дорогой уровень: живой smoke-test самого загрузчика по USB CDC (SDRAM + идентификация QSPI-чипа), требует физической смены BOOT_MOD оператором — см. VerifyScreen ниже. OFF по умолчанию, чтобы не мешать массовой заливке партии плат.

Живая проверка загрузчика (VerifyScreen)

Показывается только после успешной серийной прошивки при включённом чек-боксе «Верификация» (иначе — сразу WaitingScreen, как раньше).

┌────────────────────────────────────────────────────┐
│  🔎 Верификация загрузчика                          │
│                                                    │
│  После серийной прошивки плата осталась в режиме   │
│  BootROM. Чтобы проверить загрузчик, переведите её  │
│  в нормальный режим: BOOT_MOD_1 → GND → Reset       │
│                                                    │
│  ⠋  Плата найдена, проверяю загрузчик...            │
│                                                    │
│  ✅ Загрузчик отвечает: v0.1.0                      │
│  ✅ SDRAM smoke-test: пройден                       │
│  ✅ QSPI-чип: W25Q128 (16 МБ)                       │
│  ✅ Верификация пройдена                            │
│                                                    │
│  [ ✓ Готово ]   [ ✕ Выйти из приложения ]           │
└────────────────────────────────────────────────────┘

Загрузчик и firmware_test используют один и тот же CDC VID:PID (намеренно, чтобы переиспользовать клиентский код) — но у загрузчика нет list_tests, поэтому эта проверка не идёт через обычный WaitingScreen-автодетект (который вёл бы в DiagScreen), а отдельным экраном сразу после прошивки. «⚠» вместо «» на отдельном пункте означает «нет ответа» (таймаут/старая прошивка без команды), не обязательно провал. Таймаут ожидания BOOT_MOD — 45с, после — сообщение об этом и переход дальше по кнопке. «Пропустить проверку» доступна в любой момент ожидания.

Переход в рабочий режим (PostFlashScreen)

Показывается только после успешной прошивки firmware_test (для Production/Custom этот шаг не нужен — у Production при включённой верификации свой отдельный экран, см. VerifyScreen выше).

┌────────────────────────────────────────────────────┐
│  ✅ firmware_test успешно записан                  │
│                                                    │
│  Переведите плату в нормальный режим:              │
│  BOOT_MOD_1 → GND → Reset                         │
│                                                    │
│  [ ✓ Готово ]   [ ✕ Выйти из приложения ]         │
│  Автопереход через: 40с                            │
└────────────────────────────────────────────────────┘

Диагностика (DiagScreen)

Триггер: плата видна как CDC-устройство и отвечает на связь.

┌────────────────────────────────────────────────────────────────┐
│  fw: 0.2.0     MCU ID: A1B2C3D4E5F60011     M5 Bench: ✓ подключён │
├───────────────────────────┬──────────────────────────────────────┤
│  Доступные тесты           │  Тест        M5 Bench Статус  Время  │
│  [Выбрать все][Снять все]  │  microSD     [-]      ✗ FAIL  0.1с   │
│  ☐ SDRAM 32 MB             │    no card detected (без обрезки)    │
│  ☐ QSPI Flash              │  SDRAM 32 MB [-]      ✓ PASS  1.8с   │
│  ☐ microSD                 │  QSPI Flash  [-]      ✓ PASS  0.6с   │
│  ☐ TFT Display             │  TFT Display [-]      …              │
│  ☐ CAN loopback  [HIL]     │  Кнопки      [-]                     │
│  ☐ Оптовходы     [HIL]     │                                      │
├──────────────────────────────────────────────────────────────────┤
│  ████████░░░░  Тест: usd — mount: ok         ← только во время прогона │
├──────────────────────────────────────────────────────────────────┤
│  [▶ Запустить выбранные тесты] [▶▶ Запустить все тесты] [✕ Выйти из приложения] │
├──────────────────────────────────────────────────────────────────┤
│  ⚠  Экран залит красным цветом?                          28с      │
│  [ ✓ Да ]   [ ✗ Нет ]                    ← только во время confirm │
└────────────────────────────────────────────────────────────────┘

Что важно знать:

  • Тесты изначально не выбраны — выбирайте вручную либо кнопками "Выбрать все"/"Снять все".
  • Результаты — таблица с сортировкой FAIL-наверх, FAIL-строка подсвечена целиком, длинные сообщения переносятся на несколько строк без обрезания. Колонка M5 Bench отмечает [*]/[-] — требует ли тест HIL-стенд.
  • HIL-тесты (CAN, оптовходы) без подключённого M5StampPLC — серые, недоступны для выбора.
  • Интерактивные проверки во время прогона теста бывают трёх видов:
    • HIL-тесты (CAN, оптовходы) при наличии M5 — отвечает стенд автоматически, без участия оператора;
    • тесты кнопок — экран показывает подсказку ⌨ <инструкция>, ответ не требуется, нужно физически нажать кнопку на плате;
    • остальное (дисплей, аудио) — форма "Да/Нет" с таймером ⚠ <вопрос> на экране.
  • До завершения загрузки списка тестов правая колонка показывает подсказку «Выберите тесты и нажмите «Запустить выбранные»» вместо таблицы.

Рабочие процессы сервисника

Диагностика (firmware_test уже прошит)

1. BOOT_MOD_1 → GND, сбросить плату
2. Подключить USB → DiagScreen
3. Выбрать тесты (по умолчанию ничего не выбрано) или "Выбрать все"
4. Запустить → ответить на интерактивные запросы
5. Получить итог; FAIL-тесты — наверху таблицы, detail виден полностью

Перепрошивка firmware_test

1. BOOT_MOD_1 → 3V3, сбросить плату → FlashScreen
2. Выбрать «Диагностическая прошивка (firmware_test)» → Загрузить
3. PostFlashScreen: BOOT_MOD_1 → GND, сбросить плату
4. Нажать "Готово" (или дождаться авто-перехода через 40с)
5. TUI автоматически попадает в DiagScreen при следующем подключении

Прошивка стороннего бинарника (custom_binaries/)

Для плат старых ревизий и любых образов, собранных не в этом репозитории.

1. Положить сырой бинарник (без FCB/IVT/DCD) в custom_binaries/
   (или в директорию из SERVICE_CUSTOM_BINARIES_DIR)
2. BOOT_MOD_1 → 3V3, сбросить плату → FlashScreen
3. Выбрать «Другое» → выбрать файл → выбрать память платы → DCD при необходимости
4. Загрузить — TUI сама соберёт HAB-образ и запишет правильный FCB
5. Партия одинаковых плат: вынуть USB, вставить следующую — выбор уже
   подставлен, останется нажать «Загрузить»

Chip Erase

1. Плата в SDP-режиме (BOOT_MOD_1 → 3V3)
2. FlashScreen → Очистить память (~30 с для W25Q128, дольше для W25Q512)
3. После erase BootROM не загрузит прошивку — требуется перепрошить

Известные ограничения

  • Одна плата на столе одновременно. В SDP/Flashloader-режиме плату нельзя идентифицировать по UID — авто-прошивка по факту детекта без подтверждения оператора убрала бы последний шанс заметить, что в руках не та плата. Массового программирования (несколько плат параллельно) нет и не планируется в этом виде — см. docs/ARCHITECTURE.md, §8.
  • Циклический прогон тестов (повторный автозапуск набора без ручного нажатия на каждую итерацию) — отложен на пост-релиз, не входит в текущую версию.
  • Подключение к M5StampPLC (HIL-стенд) может занять до ~10 с после детекта платы, если M5 только что был подключён к USB или агент на нём только что перезапустился (открытие serial-порта хостом перезапускает MicroPython на M5 — агенту нужно время на инициализацию I2C/AW9523/CAN перед готовностью отвечать). WaitingScreen в это время показывает «Плата найдена, подключаемся...» — это штатное поведение, не зависание. См. docs/ARCHITECTURE.md, §10.
  • macOS-бандл не подписан Apple Developer ID и не нотаризован — при первом запуске Gatekeeper блокирует каждый файл бандла по отдельности. Обход — xattr -cr на распакованную папку, см. «Запуск» → «macOS: первый запуск» выше.
  • Загрузчик, оставленный подключённым после VerifyScreen, будет неправильно маршрутизирован. WaitingScreen детектит любой CDC (у загрузчика и firmware_test один VID:PID) и ведёт в DiagScreen, которая ждёт протокол firmware_test (list_tests и т.п.) — у загрузчика его нет. На практике не мешает: после верификации плата снимается со стенда (сценарий "bootloader-only, потом массовая заливка SD"), не остаётся подключённой к TUI.

Конфигурация (.env)

# USB VID:PID — BootROM SDP (константы NXP, не менять)
BOOTROM_VID=1fc9
BOOTROM_PID=0130

# USB VID:PID — Flashloader (константы NXP, не менять)
FLASHLOADER_VID=15a2
FLASHLOADER_PID=0073

# USB VID:PID — firmware_test CDC (наше устройство)
SERVICE_CDC_VID=1996
SERVICE_CDC_PID=00ad

# USB VID:PID — M5StampPLC (детект HIL-стенда)
SERVICE_M5_VID=303a
SERVICE_M5_PID=4001

# Директория с сырыми кастомными бинарниками для FlashScreen → "Другое".
# По умолчанию — custom_binaries/ рядом с исполняемым файлом (или рядом
# с main.py в dev-режиме); создаётся автоматически при старте.
# SERVICE_CUSTOM_BINARIES_DIR=/path/to/custom_binaries

# Тип сборки firmware_test для прошивки (Debug | Release).
# Release временно нестабилен — по умолчанию Debug.
FIRMWARE_BUILD_TYPE=Debug

# Опционально: путь к директории лога TUI
# SERVICE_LOG_DIR=/tmp

# Уровень логирования. По умолчанию — INFO, при этом spsdk/libusbsio
# принудительно приглушены до WARNING. DEBUG включает полный дамп,
# включая сырые HID-пакеты spsdk (много строк на одну прошивку).
# SERVICE_LOG_LEVEL=DEBUG

Запуск

Из монорепозитория (разработчик)

just host::service-setup   # установить зависимости tools/service_tui/
just host::service-tui     # запустить TUI

Standalone-бинарь (сервисник)

just host::package-tui
# → tools/service_tui/dist/service-tui-vX.Y.Z-<os>/

Бандл (PyInstaller, onedir) самодостаточен — прошивка идёт напрямую через spsdk Python API (app/flash_backend.py), без вызова tools/host/ ни субпроцессом, ни как-либо иначе. Не нужны ни Zadig, ни установленный Python/uv на машине сервисника. Структура бандла и резолв путей во frozen — см. ARCHITECTURE.md, §14.

macOS: первый запуск («Apple could not verify...» на каждый файл)

Бандл не подписан Apple Developer ID и не нотаризован — Gatekeeper при первом запуске выдаёт отдельное предупреждение «не удалось проверить разработчика» на каждый файл бандла (исполняемый файл + каждая .dylib внутри _internal/, включая libusbsio), а не одно общее окно. Причина — карантинный атрибут (com.apple.quarantine), который macOS проставляет на файлы, распакованные из скачанного архива; через «Открыть» в Finder его пришлось бы снимать по одному диалогу на файл.

Снять его сразу со всего бандла одной командой в терминале (один раз, после распаковки zip):

xattr -cr service-tui-vX.Y.Z-macos/

После этого бандл запускается без единого диалога. Это стандартный путь для несигнированных бинарников, распространяемых вне App Store/сайта разработчика — не баг конкретной сборки.


Зависимости

Пакет Версия Назначение
textual ≥ 0.80 TUI фреймворк
pyserial ≥ 3.5 USB CDC ACM + M5 Serial
spsdk 3.7.0 прошивка: SDP/McuBoot (Flash, chip erase) + HabImage (сборка кастомных HAB), нативный HID (libusbsio) — без Zadig
python-dotenv ≥ 1.0 загрузка .env
pyinstaller ≥ 6.0 сборка standalone-бандла (just host::package-tui)

Никакой рантайм-зависимости на tools/host/ в смысле subprocess-вызовов нет. Прошивка выполняется in-process через spsdk (app/flash_backend.py). Из tools/host/dcd/ читаются только статичные data-блобы (dcd.bin, *_fdcb.bin, ivt_flashloader.bin) — они отслеживаются в git, just host::setup-tools для запуска TUI из исходников не требуется. tools/host/ flash_usb.py — независимый dev-CLI для just host::flash*, TUI его не вызывает (см. ARCHITECTURE.md, §1/§8).


Логирование

tools/service_tui/service_tui.log   ← по умолчанию (dev) / рядом с exe (frozen)
$SERVICE_LOG_DIR/service_tui.log   ← если задан в .env

Уровень по умолчанию — INFO; textual, spsdk и libusbsio понижены до WARNING независимо от root (иначе прошивка даёт ~135 строк сырых HID-пакетов на одну операцию). SERVICE_LOG_LEVEL=DEBUG включает полный DEBUG везде, включая эти модули — используется при разборе проблем на железе. TUI не пишет в stdout — Textual захватывает терминал.