| .. | ||
| app | ||
| assets | ||
| custom_binaries | ||
| docs | ||
| spike | ||
| tests | ||
| main.py | ||
| pyproject.toml | ||
| README.md | ||
| service_tui.spec | ||
| uv.lock | ||
service-tui — TUI сервисного инженера
TUI-приложение для диагностики и прошивки платы MIMXRT1052CVJ5B на сервисе. Написано на Python + Textual. Работает на Linux, macOS, Windows.
Внутреннее устройство, диаграммы архитектуры и заметки для разработчиков — в DEV_ARCH.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) │
│ ○ Серийная прошивка (bootloader + tft_app) │
│ ○ Другое │
│ Файл (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-утилит):
HabImage(spsdk) — добавляет IVT (+DCD, если включён тумблер "Использует SDRAM")- в Flash пишется явный FCB под выбранную память платы (не тот же
auto-config, что для штатных
firmware_test/bootloader/app— для W25Q256/512 он ненадёжен, см.DEV_ARCH.md) - образ прошивается с
0x60001000, как обычно
Выбор запоминается на весь запуск TUI — файл, память платы и DCD не нужно выставлять заново на каждой следующей плате: прошили одну, вынули USB, вставили следующую такую же — TUI уже подставила прошлый выбор, остаётся нажать "Загрузить". Сбрасывается только при перезапуске TUI.
Переход в рабочий режим (PostFlashScreen)
Показывается только после успешной прошивки firmware_test (для
Production/Custom этот шаг не нужен).
┌────────────────────────────────────────────────────┐
│ ✅ 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/DEV_ARCH.md, §8. - Циклический прогон тестов (повторный автозапуск набора без ручного нажатия на каждую итерацию) — отложен на пост-релиз, не входит в текущую версию.
- Подключение к M5StampPLC (HIL-стенд) может занять до ~10 с после
детекта платы, если M5 только что был подключён к USB или агент на нём
только что перезапустился (открытие serial-порта хостом перезапускает
MicroPython на M5 — агенту нужно время на инициализацию I2C/AW9523/CAN
перед готовностью отвечать). WaitingScreen в это время показывает
«Плата найдена, подключаемся...» — это штатное поведение, не зависание.
См.
docs/DEV_ARCH.md, §10. - macOS-бандл не подписан Apple Developer ID и не нотаризован — при
первом запуске Gatekeeper блокирует каждый файл бандла по отдельности.
Обход —
xattr -crна распакованную папку, см. «Запуск» → «macOS: первый запуск» выше.
Конфигурация (.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 —
см. DEV_ARCH.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 его не
вызывает (см. DEV_ARCH.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 захватывает терминал.