From 55cd9e8273333b53412c8162eddd1ffb0e9b4a12 Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Mon, 29 Jun 2026 19:54:44 +0300 Subject: [PATCH] # firmware_test: TUI app current state --- .env.example | 4 + bsp/usb_cdc/src/usb_device_descriptor.h | 4 +- firmware/test/CMakeLists.txt | 11 + firmware/test/src/cli.c | 6 + firmware/test/src/protocol.c | 9 + firmware/test/src/protocol.h | 11 +- firmware/test/src/version.h.in | 25 + tools/production/README.md | 694 ++++++++++++++---- tools/production/TUI_REPORT.md | 209 ++++++ tools/production/app/app.py | 87 +++ tools/production/app/app.tcss | 283 +++++++ tools/production/app/firmware_client.py | 34 +- tools/production/app/flasher.py | 32 +- tools/production/app/m5_client.py | 8 +- tools/production/app/screens/__init__.py | 7 + tools/production/app/screens/diag/__init__.py | 261 +++++++ .../app/screens/diag/confirm_panel.py | 140 ++++ tools/production/app/screens/diag/results.py | 115 +++ .../production/app/screens/diag/test_list.py | 83 +++ tools/production/app/screens/flash.py | 197 +++++ tools/production/app/screens/waiting.py | 83 +++ tools/production/main.py | 42 ++ tools/production/pyproject.toml | 1 + tools/production/service_tui.log | 662 +++++++++++++++++ tools/production/uv.lock | 264 +++++++ 25 files changed, 3104 insertions(+), 168 deletions(-) create mode 100644 firmware/test/src/version.h.in create mode 100644 tools/production/TUI_REPORT.md create mode 100644 tools/production/app/app.py create mode 100644 tools/production/app/app.tcss create mode 100644 tools/production/app/screens/__init__.py create mode 100644 tools/production/app/screens/diag/__init__.py create mode 100644 tools/production/app/screens/diag/confirm_panel.py create mode 100644 tools/production/app/screens/diag/results.py create mode 100644 tools/production/app/screens/diag/test_list.py create mode 100644 tools/production/app/screens/flash.py create mode 100644 tools/production/app/screens/waiting.py create mode 100644 tools/production/service_tui.log create mode 100644 tools/production/uv.lock diff --git a/.env.example b/.env.example index 5b80699..2b17a6e 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,10 @@ FLASHLOADER_PID=0073 SERVICE_CDC_VID=1996 SERVICE_CDC_PID=00ad + +# Тип сборки firmware_test для TUI (Debug | Release) +FIRMWARE_BUILD_TYPE=Debug + # --- Paths --- # BUILD_DIR и TOOLS_DIR задаются абсолютно в корневом justfile # через justfile_directory(), здесь можно переопределить если нужно diff --git a/bsp/usb_cdc/src/usb_device_descriptor.h b/bsp/usb_cdc/src/usb_device_descriptor.h index c8a4dc8..b52e2a3 100644 --- a/bsp/usb_cdc/src/usb_device_descriptor.h +++ b/bsp/usb_cdc/src/usb_device_descriptor.h @@ -19,8 +19,8 @@ #define USB_DEVICE_DEMO_BCD_VERSION (0x0101U) /* ---- VID / PID --------------------------------------------------------- */ -#define USB_DEVICE_VID (0x1996U) /* TODO: заменить на свой */ -#define USB_DEVICE_PID (0x00ADU) /* TODO: заменить на свой */ +#define USB_DEVICE_VID (0x1996U) +#define USB_DEVICE_PID (0x00ADU) /* ---- CDC коды классов -------------------------------------------------- */ #define CDC_COMM_CLASS (0x02U) diff --git a/firmware/test/CMakeLists.txt b/firmware/test/CMakeLists.txt index 798068e..fb3586d 100644 --- a/firmware/test/CMakeLists.txt +++ b/firmware/test/CMakeLists.txt @@ -1,8 +1,17 @@ # firmware/test/CMakeLists.txt Тестовая прошивка — входной контроль платы на # производстве +cmake_minimum_required(VERSION 3.20) +project( + firmware_test + VERSION 0.0.1 + LANGUAGES C ASM) set(TARGET_NAME firmware_test) +# Генерация version.h из шаблона +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/version.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/generated/version.h" @ONLY) + add_subdirectory(fatfs) add_executable( @@ -25,6 +34,8 @@ add_executable( target_include_directories(firmware_test PRIVATE src/) +target_include_directories(firmware_test + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated") # __STARTUP_INITIALIZE_RAMFUNCTION - очистка секции .ram_function и копирование # туда данных из __ram_function_flash_start; __STARTUP_INITIALIZE_NONCACHEDATA - # инициализация некешируемое секции нулями diff --git a/firmware/test/src/cli.c b/firmware/test/src/cli.c index 738d58f..70ff248 100644 --- a/firmware/test/src/cli.c +++ b/firmware/test/src/cli.c @@ -331,6 +331,12 @@ static void handle_cmd(const char *p_line) return; } + if (strcmp(cmd_name, "get_version") == 0) + { + protocol_send_version_response(); + return; + } + if (strcmp(cmd_name, "list_tests") == 0) { test_runner_send_list(); diff --git a/firmware/test/src/protocol.c b/firmware/test/src/protocol.c index a8db0c9..4fc3b62 100644 --- a/firmware/test/src/protocol.c +++ b/firmware/test/src/protocol.c @@ -157,4 +157,13 @@ void protocol_send_uid_response(const uint8_t *p_uid) (unsigned int) p_uid[3U], (unsigned int) p_uid[4U], (unsigned int) p_uid[5U], (unsigned int) p_uid[6U], (unsigned int) p_uid[7U]); cli_send(buf); +} + +void protocol_send_version_response(void) +{ + char buf[PROTO_BUF_SIZE]; + (void) snprintf(buf, sizeof(buf), + "{\"type\":\"version_response\"," + "\"fw\":\"" FIRMWARE_TEST_VERSION "\"}\n"); + cli_send(buf); } \ No newline at end of file diff --git a/firmware/test/src/protocol.h b/firmware/test/src/protocol.h index 0bbff09..b4c7988 100644 --- a/firmware/test/src/protocol.h +++ b/firmware/test/src/protocol.h @@ -19,12 +19,14 @@ #define PROTOCOL_H_ #include "test_module.h" +#include "version.h" #include #include #include + /** @brief Строка версии прошивки, вставляемая в session_start. */ -#define FIRMWARE_TEST_VERSION "0.1.4" +#define FIRMWARE_TEST_VERSION FIRMWARE_TEST_VERSION_STR /** @brief Таймаут подтверждения по умолчанию, мс. */ #define PROTOCOL_CONFIRM_TIMEOUT_MS 30000U @@ -100,4 +102,11 @@ void protocol_send_error(const char *p_code); */ void protocol_send_uid_response(const uint8_t *p_uid); +/** + * @brief Отправить ответ на команду get_version. + * + * Формат: {"type":"version_response","fw":"X.Y.Z"} + */ +void protocol_send_version_response(void); + #endif /* PROTOCOL_H_ */ \ No newline at end of file diff --git a/firmware/test/src/version.h.in b/firmware/test/src/version.h.in new file mode 100644 index 0000000..a80b12c --- /dev/null +++ b/firmware/test/src/version.h.in @@ -0,0 +1,25 @@ +/** + * @file version.h + * @brief Версия firmware_test — генерируется CMake из CMakeLists.txt. + * + * НЕ редактировать вручную. Версию менять в firmware/test/CMakeLists.txt: + * project(firmware_test VERSION X.Y.Z) + */ + +#ifndef VERSION_H_ +#define VERSION_H_ + +/** @brief Мажорная версия. */ +#define FIRMWARE_TEST_VERSION_MAJOR @firmware_test_VERSION_MAJOR@ + +/** @brief Минорная версия. */ +#define FIRMWARE_TEST_VERSION_MINOR @firmware_test_VERSION_MINOR@ + +/** @brief Патч-версия. */ +#define FIRMWARE_TEST_VERSION_PATCH @firmware_test_VERSION_PATCH@ + +/** @brief Версия строкой для протокола: "X.Y.Z". */ +#define FIRMWARE_TEST_VERSION_STR \ + "@firmware_test_VERSION_MAJOR@.@firmware_test_VERSION_MINOR@.@firmware_test_VERSION_PATCH@" + +#endif /* VERSION_H_ */ \ No newline at end of file diff --git a/tools/production/README.md b/tools/production/README.md index 9bcd2e2..2d4c0cc 100644 --- a/tools/production/README.md +++ b/tools/production/README.md @@ -5,200 +5,605 @@ TUI-приложение для диагностики и прошивки пл --- +## Структура проекта + +```bash +tools/production/ +├── main.py ← точка входа (10 строк) +├── pyproject.toml ← зависимости uv +├── uv.lock +└── app/ + ├── app.py ← ServiceApp — роутинг экранов, жизненный цикл клиентов + ├── models.py ← все типы данных (dataclass/Enum) + ├── firmware_client.py ← async USB CDC клиент firmware_test + ├── m5_client.py ← async M5StampPLC клиент (Serial JSON-lines) + ├── flasher.py ← subprocess-обёртка над tools/host/flash_usb.py + ├── orchestrator.py ← маршрутизация confirm_request + └── screens/ + ├── __init__.py ← реэкспорт: WaitingScreen, FlashScreen, DiagScreen + ├── waiting.py ← WaitingScreen — ожидание USB + ├── flash.py ← FlashScreen — прошивка / chip erase + └── diag/ + ├── __init__.py ← DiagScreen — координатор диагностики + ├── test_list.py ← TestListPanel — чекбоксы тестов + ├── results.py ← ResultsPanel — строки результатов + ├── confirm_panel.py ← ConfirmPanel — prompt оператора + countdown + └── styles/ + ├── waiting.tcss + ├── flash.tcss + └── diag.tcss +``` + +--- + ## Концепция ```mermaid graph LR subgraph PC["Сервисный ПК"] TUI["service-tui\n(Textual App)"] - subgraph tools["tools/"] + subgraph app["app/"] FC["firmware_client.py\nUSB CDC ACM"] M5["m5_client.py\nSerial JSON-lines"] - FL["flasher.py\n→ tools/host/flash_usb.py"] + FL["flasher.py\nsubprocess"] + OR["orchestrator.py\nconfirm router"] end - TUI --> FC - TUI --> M5 - TUI --> FL + TUI --> FC & M5 & FL & OR end subgraph Board["Плата TFT (MIMXRT1052)"] FW["firmware_test\n(USB CDC)"] - ROM["BootROM\n(SDP 1FC9:0130)"] + ROM["BootROM SDP\n(1FC9:0130)"] end subgraph HIL["HIL стенд (опционально)"] M5HW["M5StampPLC\nRLY1–4 + CAN"] end - FC <-->|"JSON-lines\nVID:PID 1996:00AD"| FW - FL -->|"sdphost + blhost\nVID:PID 1FC9:0130"| ROM - M5 <-->|"JSON-lines\nSerial"| M5HW - M5HW -->|"RLY1–4"| Board + subgraph Host["tools/host/"] + FU["flash_usb.py\nsdphost + blhost"] + end + + FC <-->|"JSON-lines\nVID:PID 1996:00AD"| FW + FL -->|"subprocess uv run"| FU + FU -->|"sdphost + blhost\nVID:PID 1FC9:0130"| ROM + M5 <-->|"JSON-lines\nSerial"| M5HW + M5HW -->|"RLY1–4"| Board ``` --- ## Два режима работы -Режим определяется автодетектом USB при старте и меняется динамически. +Режим определяется автодетектом USB и меняется динамически без перезапуска TUI. ```mermaid stateDiagram-v2 [*] --> WAITING : запуск TUI - WAITING --> FLASHING : обнаружен VID:PID 1FC9:0130\n(BootROM SDP) - WAITING --> DIAGNOSING : обнаружен VID:PID 1996:00AD\n+ session_start по CDC + WAITING --> FLASHING : VID:PID 1FC9:0130\n(BootROM SDP) + WAITING --> DIAGNOSING : VID:PID 1996:00AD\n+ ping→pong по CDC - FLASHING --> WAITING : прошивка завершена / плата отключена - FLASHING --> DIAGNOSING: плата перезагружена в нормальный режим + FLASHING --> WAITING : FlashDone / ESC / плата отключена + FLASHING --> DIAGNOSING: плата перезагружена после прошивки - DIAGNOSING --> WAITING : плата отключена - DIAGNOSING --> FLASHING: плата переведена в SDP (перемычка) + DIAGNOSING --> WAITING : DiagDone / ESC / плата отключена + DIAGNOSING --> FLASHING: плата переведена в SDP (перемычка BOOT_MOD) ``` ### Режим A — Прошивка -Триггер: обнаружен BootROM SDP (`1FC9:0130`). +Триггер: BootROM SDP `1FC9:0130` виден в `serial.tools.list_ports`. ```bash ┌─ Прошивка платы ─────────────────────────────────┐ -│ Обнаружен BootROM (SDP режим) │ +│ ⚡ BootROM SDP обнаружен │ │ │ │ Что прошить? │ -│ ◉ firmware_test (диагностика) │ +│ ◉ firmware_test (диагностическая прошивка) │ │ ○ Production (bootloader + tft_app) │ +│ ○ Кастомный бинарь... │ │ │ -│ ████████████░░░░░░ 64% Запись во Flash... │ +│ [ ▶ Прошить ] [ ⚠ Chip Erase ] │ +│ │ +│ ████████████░░░░░░ 64% blhost 64% │ +│ ┌────────────────────────────────────────────┐ │ +│ │ ▶ Прошивка: firmware_test │ │ +│ │ $ blhost -u 0x15A2,0x0073 -- write-memory… │ │ +│ └────────────────────────────────────────────┘ │ └────────────────────────────────────────────────────┘ ``` ### Режим B — Диагностика -Триггер: CDC-порт `1996:00AD` обнаружен и `ping→pong` прошёл. +Триггер: CDC-порт `1996:00AD` виден + `ping→pong` прошёл. +```bash +┌─ Диагностика fw:0.1.4 UID:A1B2C3D4E5F60011 ────────────────┐ +│ M5: ✓ подключён │ +├────────────────────────────┬───────────────────────────────────┤ +│ Тесты │ Результаты │ +│ ☑ SDRAM 32 MB │ sdram ✓ PASS │ +│ ☑ QSPI Flash │ qspi ✓ PASS │ +│ ☑ microSD │ usd ✗ FAIL mount err: 5 │ +│ ☑ TFT Display │ display ✓ PASS │ +│ ☑ Кнопки │ buttons ✓ PASS │ +│ ☑ MQS Audio │ mqs ✓ PASS │ +│ ☑ CAN loopback [HIL] │ can … running │ +│ ☑ Оптовходы [HIL] │ opto pending │ +├────────────────────────────┴───────────────────────────────────┤ +│ [ ▶ Запустить выбранные ] [ ▶▶ Все тесты ] │ +│ ████████████████░░░░ 80% Тест: can │ +├────────────────────────────────────────────────────────────────┤ +│ ⚠ Экран залит красным цветом? 28с │ +│ [ ✓ Да ] [ ✗ Нет ] │ +└────────────────────────────────────────────────────────────────┘ ``` -┌─ Диагностика fw:0.1.4 UID:A1B2C3D4E5F60011 ─────┐ -│ M5StampPLC: ✓ │ Результаты: │ -├───────────────────────────┤ sdram ✓ PASS │ -│ ☑ SDRAM 32 MB │ qspi ✓ PASS │ -│ ☑ QSPI Flash │ usd ✗ FAIL │ -│ ☑ microSD │ mount failed: 5 │ -│ ☑ TFT Display │ display ✓ PASS │ -│ ☑ Кнопки │ buttons ✓ PASS │ -│ ☑ MQS Audio │ mqs ✓ PASS │ -│ ☑ CAN loopback [HIL] │ can … running │ -│ ☑ Оптовходы [HIL] │ opto ○ pending │ -├───────────────────────────┴─────────────────────────┤ -│ [ Запустить выбранные ] [ Все тесты ] │ -│ ████████████████░░░░ 80% Тест: can │ -├─────────────────────────────────────────────────────┤ -│ ⚠ Экран залит красным цветом? │ -│ [ ✓ Да ] [ ✗ Нет ] │ -└─────────────────────────────────────────────────────┘ -``` + +HIL-тесты без M5StampPLC отображаются серыми и не выбираются автоматически. --- -## Архитектура приложения +## Диаграмма классов ```mermaid -graph TB - subgraph TUI["tui.py — Textual App"] - WS["WaitingScreen"] - FS["FlashScreen"] - DS["DiagScreen"] - end +classDiagram + direction TB - subgraph Core["app/"] - OR["orchestrator.py\nмаршрутизация confirm_request"] - FW["firmware_client.py\nasync CDC клиент"] - M5["m5_client.py\nasync M5 клиент"] - FL["flasher.py\nsubprocess flash_usb.py"] - MD["models.py\nTestInfo · TestResult\nSessionState · ConfirmRequest"] - end + %% ── Точка входа ────────────────────────────────────────── + class ServiceApp { + -_fw: FirmwareClient + -_m5: M5Client + +on_mount() + +_on_device_detected(event) + +_on_flash_done(event) + +_on_diag_done() + +_connect_and_diagnose() + +_disconnect() + } - DS --> OR - OR --> FW - OR --> M5 - FS --> FL - DS --> MD - OR --> MD + %% ── Экраны ─────────────────────────────────────────────── + class WaitingScreen { + -_spinner_idx: int + -_detect_timer: Timer + -_spin_timer: Timer + +on_mount() + +on_unmount() + -_poll_usb() + -_spin() + -_stop_timers() + } + class WaitingScreen.DeviceDetected { + +mode: AppMode + } + + class FlashScreen { + -_flasher: Flasher + -_flashing: bool + +compose() + -_on_radio_changed(event) + -_on_flash_pressed() + -_on_erase_pressed() + -_do_flash(target, bin_path) + -_do_erase() + -_resolve_target() + -_on_progress(progress) + -_set_busy(busy) + } + class FlashScreen.FlashDone { + +success: bool + } + + class DiagScreen { + -_fw: FirmwareClient + -_m5: M5Client + -_orchestrator: Orchestrator + -_session: SessionState + -_running: bool + +on_mount() + -_init_session() + -_update_header() + -_on_run_selected() + -_on_run_all() + -_on_confirmed(event) + -_start_run(test_ids) + -_run_worker(test_ids) + -_handle_event(event, total, done) + -_on_summary(summary) + } + class DiagScreen.DiagDone + + %% ── Виджеты DiagScreen ─────────────────────────────────── + class TestListPanel { + -_checkboxes: dict + +populate(tests, m5_connected) + +get_selected_ids() list + +set_enabled(enabled) + } + + class ResultsPanel { + +populate(tests) + +set_running(test_id) + +set_result(result) + +reset() + -_update(test_id, status, detail) + } + + class ConfirmPanel { + -_timer: Timer + -_remaining: int + +show_operator(prompt, timeout_ms) + +show_buttons_hint(prompt) + +hide() + -_tick() + -_start_timer() + -_stop_timer() + } + class ConfirmPanel.Confirmed { + +confirmed: bool + } + + %% ── Клиенты ────────────────────────────────────────────── + class FirmwareClient { + -_port: str + -_ser: Serial + -_lock: Lock + +connect() + +disconnect() + +ping() bool + +list_tests() list + +run_selected(test_ids) AsyncGenerator + +send_confirm(id, confirmed) + +get_uid() str + +auto_connect(vid, pid)$ + +find_port(vid, pid)$ + } + + class M5Client { + -_port: str + -_ser: Serial + -_lock: Lock + +connect() + +disconnect() + +ping() bool + +relay_set(relay, state) bool + +relay_get(relay) bool + +can_send(id, data) bool + +can_recv(timeout_ms) dict + +auto_connect()$ + +find_port()$ + } + + class Flasher { + -_proc: Process + +detect_sdp()$ bool + +detect_cdc()$ bool + +flash(target, progress_cb, bin_path) bool + +erase_chip(progress_cb) bool + -_run_flash(firmware, build_type, cb) + -_run_flash_bin(bin_path, cb) + -_run_cmd(cmd, label, cb) + } + + %% ── Оркестратор ────────────────────────────────────────── + class Orchestrator { + -_fw: FirmwareClient + -_m5: M5Client + -_operator_queue: Queue + +run_tests(test_ids) AsyncGenerator + +resolve_operator_confirm(confirmed) + -_handle_confirm(confirm) + -_handle_hil_opto(confirm) + -_handle_hil_can_rx(confirm) + -_handle_hil_can_tx(confirm) + -_handle_operator_confirm(confirm) + } + + %% ── Модели ─────────────────────────────────────────────── + class SessionState { + +fw_version: str + +chip_uid: str + +m5_connected: bool + +tests: list + +results: dict + +get_result(id) + +set_result(result) + } + + class OrchestratorEvent { + +type: OrchestratorEventType + +test_id: str + +result: TestResult + +confirm: ConfirmRequest + +summary: dict + +message: str + } + + class TestInfo { + +id: str + +name: str + +critical: bool + +requires_hil: bool + } + + class TestResult { + +id: str + +status: TestStatus + +duration_ms: int + +detail: str + } + + class ConfirmRequest { + +id: str + +prompt: str + +timeout_ms: int + } + + class FlashProgress { + +phase: str + +percent: int + +message: str + } + + %% ── Enum ───────────────────────────────────────────────── + class AppMode { + <> + WAITING + FLASHING + DIAGNOSING + } + + class TestStatus { + <> + PENDING + RUNNING + PASS + FAIL + SKIP + } + + class FlashTarget { + <> + FIRMWARE_TEST + PRODUCTION + CUSTOM + } + + class OrchestratorEventType { + <> + TEST_BEGIN + TEST_RESULT + CONFIRM_NEEDED + CONFIRM_RESOLVED + BUTTONS_PROMPT + SUMMARY + ERROR + } + + %% ── Связи ──────────────────────────────────────────────── + + %% App → экраны + ServiceApp --> WaitingScreen : push/switch + ServiceApp --> FlashScreen : switch + ServiceApp --> DiagScreen : switch + ServiceApp --> FirmwareClient : создаёт + ServiceApp --> M5Client : создаёт + + %% Сообщения от экранов + WaitingScreen ..> WaitingScreen.DeviceDetected : posts + FlashScreen ..> FlashScreen.FlashDone : posts + DiagScreen ..> DiagScreen.DiagDone : posts + ConfirmPanel ..> ConfirmPanel.Confirmed : posts + + %% App слушает сообщения + ServiceApp ..> WaitingScreen.DeviceDetected : on() + ServiceApp ..> FlashScreen.FlashDone : on() + ServiceApp ..> DiagScreen.DiagDone : on() + + %% Экраны → компоненты + FlashScreen --> Flasher : использует + DiagScreen --> Orchestrator : создаёт + DiagScreen --> TestListPanel : монтирует + DiagScreen --> ResultsPanel : монтирует + DiagScreen --> ConfirmPanel : монтирует + DiagScreen --> SessionState : владеет + DiagScreen ..> ConfirmPanel.Confirmed : on() + + %% Оркестратор + Orchestrator --> FirmwareClient : вызывает + Orchestrator --> M5Client : вызывает + Orchestrator ..> OrchestratorEvent : yields + + %% Flasher детект + WaitingScreen --> Flasher : detect_sdp/cdc + + %% Модели + Flasher --> FlashProgress + Orchestrator --> ConfirmRequest + Orchestrator --> TestResult + DiagScreen --> OrchestratorEvent + SessionState --> TestInfo + SessionState --> TestResult + + %% Enum использование + ServiceApp --> AppMode + WaitingScreen.DeviceDetected --> AppMode + TestResult --> TestStatus + Flasher --> FlashTarget + Orchestrator --> OrchestratorEventType + OrchestratorEvent --> OrchestratorEventType ``` --- ## Обработка confirm_request -Маршрутизация определяется по `id` поля `confirm_request`: +Маршрутизация реализована в `Orchestrator._handle_confirm()` по значению `confirm_request.id`: ```mermaid flowchart TD CR["confirm_request\nот firmware_test"] - CR --> R{confirm_request.id} - R -->|"opto_*"| HIL_OPTO["HIL: M5 relay_set\n→ settle → confirm"] - R -->|"can_rx_ready"| HIL_CAN_RX["HIL: M5 can_send\n→ confirm"] - R -->|"can_tx_verify"| HIL_CAN_TX["HIL: M5 can_recv\n→ verify → confirm"] - R -->|"btn*"| BTN["показать инструкцию\nне отправлять confirm\nждать test_result"] - R -->|"всё остальное"| OP["показать оператору\nprompt + OK/FAIL\n+ countdown"] + R -->|"opto_in1_active\nopto_in1_inactive\nopto_in2_active\nopto_in2_inactive\nopto_rs_active\nopto_rs_inactive"| HIL_OPTO + R -->|"can_rx_ready"| HIL_CAN_RX + R -->|"can_tx_verify"| HIL_CAN_TX + R -->|"btn*"| BTN + R -->|"всё остальное"| OP - HIL_OPTO --> AUTO["CONFIRM_RESOLVED\n(автоматически)"] - HIL_CAN_RX --> AUTO - HIL_CAN_TX --> AUTO - OP --> WAIT["CONFIRM_NEEDED\nждём resolve_operator_confirm()"] - WAIT --> SEND["send_confirm(id, confirmed)"] - AUTO --> SEND + HIL_OPTO["M5: relay_set(rly, state)\nsleep(settle)\nsend_confirm(true/false)"] + HIL_CAN_RX["M5: can_send(0x100, data)\nsend_confirm(ok)"] + HIL_CAN_TX["M5: can_recv()\nverify id+data\nsend_confirm(verified)"] + BTN["DiagScreen: show_buttons_hint()\nНЕ отправлять confirm\nтаргет сам детектирует нажатие"] + OP["DiagScreen: show_operator()\nCountdown таймер\nОжидать resolve_operator_confirm()"] + + HIL_OPTO --> RAUTO["CONFIRM_RESOLVED → прогресс"] + HIL_CAN_RX --> RAUTO + HIL_CAN_TX --> RAUTO + OP --> RNEED["CONFIRM_NEEDED → UI панель\nОператор нажимает OK/Нет"] + RNEED --> RESOLVE["ConfirmPanel.Confirmed\n→ resolve_operator_confirm()\n→ send_confirm()"] ``` -| confirm id | Кто отвечает | Действие TUI | -| --------------- | ------------ | -------------------------------------- | -| `opto_*` | M5 авто | показать прогресс | -| `can_rx_ready` | M5 авто | показать прогресс | -| `can_tx_verify` | M5 авто | показать прогресс | -| `btn*` | физика | показать инструкцию, ждать test_result | -| всё остальное | оператор | prompt + OK/FAIL + countdown | +| `confirm_request.id` | Кто отвечает | Действие TUI | Реле M5 | +| -------------------- | ------------ | -------------------- | ---------- | +| `opto_in1_active` | M5 авто | прогресс | RLY3 ON | +| `opto_in1_inactive` | M5 авто | прогресс | RLY3 OFF | +| `opto_in2_active` | M5 авто | прогресс | RLY4 ON | +| `opto_in2_inactive` | M5 авто | прогресс | RLY4 OFF | +| `opto_rs_active` | M5 авто | прогресс | RLY2 ON | +| `opto_rs_inactive` | M5 авто | прогресс | RLY2 OFF | +| `can_rx_ready` | M5 авто | прогресс | — (CAN TX) | +| `can_tx_verify` | M5 авто | прогресс | — (CAN RX) | +| `btn*` | физика | инструкция оператору | — | +| всё остальное | оператор | prompt + countdown | — | --- -## Структура +## Архитектура экранов -```bash -tools/production/ -├── pyproject.toml ← зависимости: textual, pyserial, python-dotenv, pyinstaller -├── uv.lock -├── main.py ← точка входа: asyncio + Textual App -├── app/ -│ ├── tui.py ← Textual App, экраны (WaitingScreen, FlashScreen, DiagScreen) -│ ├── firmware_client.py ← async USB CDC клиент firmware_test -│ ├── m5_client.py ← async M5StampPLC клиент -│ ├── flasher.py ← subprocess → tools/host/flash_usb.py -│ ├── orchestrator.py ← confirm_request маршрутизатор -│ └── models.py ← AppMode, TestInfo, TestResult, SessionState, … -└── README.md ← этот файл +```mermaid +graph TB + subgraph ServiceApp["ServiceApp (app.py)"] + direction LR + WS["WaitingScreen"] + FS["FlashScreen"] + DS["DiagScreen"] + end + + subgraph DiagInternals["DiagScreen (screens/diag/)"] + TL["TestListPanel\ntest_list.py"] + RP["ResultsPanel\nresults.py"] + CP["ConfirmPanel\nconfirm_panel.py"] + OR["Orchestrator\norchestrator.py"] + end + + subgraph Clients["Клиенты"] + FC["FirmwareClient"] + M5["M5Client"] + FL["Flasher"] + end + + WS -->|"DeviceDetected(FLASHING)"| FS + WS -->|"DeviceDetected(DIAGNOSING)"| DS + FS -->|"FlashDone"| WS + DS -->|"DiagDone"| WS + + FS --> FL + DS --> OR + DS --> TL + DS --> RP + DS --> CP + CP -->|"Confirmed"| DS + OR --> FC + OR --> M5 + WS --> FL +``` + +--- + +## Жизненный цикл сессии + +```mermaid +sequenceDiagram + participant OP as Оператор + participant TUI as ServiceApp + participant WS as WaitingScreen + participant DS as DiagScreen + participant FW as firmware_test + participant M5 as M5StampPLC + + OP->>TUI: запустить service_tui + TUI->>WS: push_screen() + WS->>WS: poll USB каждые 1.5 с + + OP->>FW: подключить плату USB + WS->>TUI: DeviceDetected(DIAGNOSING) + TUI->>FW: auto_connect() → ping→pong + TUI->>M5: auto_connect() (опционально) + TUI->>DS: switch_screen() + + DS->>FW: list_tests() → TestInfo×8 + DS->>FW: get_uid() → "A1B2C3D4..." + DS->>DS: populate TestListPanel + ResultsPanel + + OP->>DS: выбрать тесты → Запустить + DS->>FW: run_selected([...]) + + loop Для каждого теста + FW-->>DS: test_begin + DS->>DS: ResultsPanel.set_running() + + alt HIL confirm (opto / can) + FW-->>DS: confirm_request + DS->>M5: relay_set() / can_send() / can_recv() + DS->>FW: send_confirm(true/false) + DS->>DS: прогресс CONFIRM_RESOLVED + else Оператор (display / mqs) + FW-->>DS: confirm_request + DS->>DS: ConfirmPanel.show_operator() + OP->>DS: OK / Нет + DS->>FW: send_confirm(true/false) + else Кнопки + FW-->>DS: confirm_request + DS->>DS: ConfirmPanel.show_buttons_hint() + OP->>FW: физическое нажатие + end + + FW-->>DS: test_result + DS->>DS: ResultsPanel.set_result() + end + + FW-->>DS: summary + DS->>DS: показать итог PASS / FAIL + OP->>DS: ESC → DiagDone + TUI->>WS: switch_screen() ``` --- ## Конфигурация (`.env`) -Файл `.env` в корне репозитория — единый источник конфигурации. +Файл `.env` в корне репозитория — единый источник. Загружается через `python-dotenv` в `main.py` до импорта app-модулей. ```ini -# USB VID:PID — BootROM SDP (менять нельзя, NXP ROM) +# 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 -# Пути к бинарям (опционально, TUI ищет в build/ автоматически) -FIRMWARE_TEST_BIN=build/Release/firmware_test_hab.bin -PRODUCTION_BIN_BOOT=build/Release/bootloader_hab.bin -PRODUCTION_BIN_APP=build/Release/tft_app_hab.bin +# Опционально: путь к директории лога TUI +# SERVICE_LOG_DIR=/tmp ``` +Пути к бинарям `flash_usb.py` вычисляет автоматически из `BUILD_DIR` (также из `.env`). + --- ## Запуск @@ -206,7 +611,7 @@ PRODUCTION_BIN_APP=build/Release/tft_app_hab.bin ### Из монорепозитория (разработчик) ```bash -# Установить зависимости +# Установить зависимости tools/production/ just host::service-setup # Запустить TUI @@ -215,7 +620,8 @@ just host::service-tui ### Standalone-бинарь (сервисник) -Скачать `service_tui` из [GitHub Releases](https://github.com/OSabuser/tft_manufacture_test/releases) и запустить двойным кликом. +Скачать `service_tui` из [GitHub Releases](https://github.com/OSabuser/tft_manufacture_test/releases) и запустить двойным кликом — Python не требуется. + Для сборки из исходников: ```bash @@ -223,52 +629,40 @@ just host::service-build # → tools/production/dist/service_tui ``` +> **Важно:** standalone-бинарь не включает `tools/host/`. Перед сборкой убедитесь что `tools/host/` инициализирован (`just host::setup-tools`) и доступен рядом с бинарём, либо измените `_FLASH_USB_SCRIPT` в `flasher.py` на абсолютный путь. + --- -## Рабочий процесс сервисника +## Рабочие процессы сервисника -```mermaid -sequenceDiagram - participant OP as Оператор - participant TUI as service-tui - participant FW as firmware_test (CDC) - participant M5 as M5StampPLC +### Диагностика (firmware_test уже прошит) - OP->>TUI: запустить service_tui - OP->>TUI: подключить плату USB (нормальный режим) - TUI->>FW: ping → pong (CDC автодетект) - TUI->>FW: list_tests - FW-->>TUI: TestInfo × 8 - TUI-->>OP: показать список тестов +```bash +1. BOOT_MOD_1 → GND, сбросить плату +2. Подключить USB к сервисному ПК +3. TUI: WaitingScreen → обнаружен CDC 1996:00AD → DiagScreen +4. Выбрать тесты (или Все тесты) → Запустить +5. Ответить на интерактивные запросы (display, mqs) +6. Получить итог PASS / FAIL +``` - OP->>TUI: выбрать тесты → Запустить - TUI->>FW: run_selected([...]) +### Перепрошивка firmware_test - loop Для каждого теста - FW-->>TUI: test_begin - TUI-->>OP: прогресс +```bash +1. BOOT_MOD_1 → 3V3, сбросить плату +2. Подключить USB → TUI: FlashScreen +3. Выбрать firmware_test → Прошить +4. BOOT_MOD_1 → GND, сбросить плату +5. TUI автоматически переходит в DiagScreen +``` - alt HIL тест (opto/can) - FW-->>TUI: confirm_request - TUI->>M5: relay_set / can_send - TUI->>FW: confirm(true) - else Интерактивный (display/mqs) - FW-->>TUI: confirm_request - TUI-->>OP: показать prompt + countdown - OP->>TUI: OK / FAIL - TUI->>FW: confirm(true/false) - else Кнопки - FW-->>TUI: confirm_request (инструкция) - TUI-->>OP: "Нажмите кнопку..." - OP->>FW: физическое нажатие - end +### Chip Erase (сброс Flash в FF) - FW-->>TUI: test_result - TUI-->>OP: результат теста - end - - FW-->>TUI: summary - TUI-->>OP: итог: PASS / FAIL +```bash +1. Плата в SDP-режиме (BOOT_MOD_1 → 3V3) +2. FlashScreen → Chip Erase (~30 с) +3. После erase: BootROM не загрузит прошивку — + необходимо перепрошить (пункт выше) ``` --- @@ -282,5 +676,19 @@ sequenceDiagram | `python-dotenv` | ≥ 1.0 | загрузка `.env` | | `pyinstaller` | ≥ 6.0 | сборка standalone-бинаря | -**Runtime зависимость (не в pyproject.toml):** -`tools/host/flash_usb.py` вызывается через `subprocess` с `uv run` — `tools/host/` uv-проект должен быть инициализирован (`just host::setup-tools`). +**Runtime-зависимость (не в `pyproject.toml`):** +`flasher.py` вызывает `tools/host/flash_usb.py` через `uv run` — uv-окружение `tools/host/` должно быть инициализировано командой `just host::setup-tools`. + +--- + +## Логирование + +TUI логирует в файл (не в stdout — Textual захватывает терминал): + +```bash +tools/production/service_tui.log ← по умолчанию +$SERVICE_LOG_DIR/service_tui.log ← если задан в .env +``` + +Уровень: `DEBUG` для всех модулей, `WARNING` для textual. +При standalone-запуске лог создаётся рядом с исполняемым файлом. diff --git a/tools/production/TUI_REPORT.md b/tools/production/TUI_REPORT.md new file mode 100644 index 0000000..ef9b6e6 --- /dev/null +++ b/tools/production/TUI_REPORT.md @@ -0,0 +1,209 @@ +# TUI Service Tool замечания + +## Прочие замечания + +После внедрения в firmware/test/CMakeLists.txt: + +```cmake +# firmware/test/CMakeLists.txt Тестовая прошивка — входной контроль платы на +# производстве +cmake_minimum_required(VERSION 3.20) +project( + firmware_test + VERSION 0.0.1 + LANGUAGES C ASM) + +set(TARGET_NAME firmware_test) + +# Генерация version.h из шаблона +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/version.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/generated/version.h" @ONLY) + +add_subdirectory(fatfs) + +add_executable( + ${TARGET_NAME} + src/main.c + src/cli.c + src/protocol.c + src/test_runner.c + src/tests/test_opto.c + src/tests/test_sdram.c + src/tests/test_can.c + src/tests/test_mqs.c + src/tests/test_qspi.c + src/tests/test_usd.c + src/tests/test_display.c + src/tests/test_buttons.c + ${BSP_GENERATED}/clock_config.c + ${BSP_STARTUP_FILE} + ${BSP_SYSCALLS_FILE}) + +target_include_directories(firmware_test PRIVATE src/) + +target_include_directories(firmware_test + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated") +# __STARTUP_INITIALIZE_RAMFUNCTION - очистка секции .ram_function и копирование +# туда данных из __ram_function_flash_start; __STARTUP_INITIALIZE_NONCACHEDATA - +# инициализация некешируемое секции нулями +target_compile_definitions( + ${TARGET_NAME} + PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512 BOARD_MPU_SDRAM=1 + DISPLAY_TEST_TYPE=BSP_DISPLAY_TFT8 __STARTUP_INITIALIZE_RAMFUNCTION + __STARTUP_CLEAR_BSS __STARTUP_INITIALIZE_NONCACHEDATA) +# +# ----------------------------------------------------------------------------- +# Зависимости — только то что нужно для входного контроля bsp_board транзитивно +# даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны +# ----------------------------------------------------------------------------- +target_link_libraries( + ${TARGET_NAME} + PRIVATE bsp_board + bsp_can + bsp_led + bsp_button + bsp_display + bsp_tick + bsp_boot_xip + bsp_usb_cdc + bsp_provisioning + bsp_sdram + bsp_qspi_flash + bsp_uart_host + bsp_opto + bsp_mqs + bsp_sd + firmware_test_fatfs) + +# ----------------------------------------------------------------------------- +# Linker script +# ----------------------------------------------------------------------------- +# --gc-sections — удалять неиспользуемые секции (работает с +# -ffunction/data-sections) --print-memory-usage — выводить таблицу +# использования Flash/RAM после линковки -Map — генерировать +# map-файл для анализа размещения символов -T — линкерный +# скрипт с описанием карты памяти IMXRT1052 +target_link_options( + ${TARGET_NAME} + PRIVATE + -Wl,--gc-sections + -Wl,--print-memory-usage + -Wl,-Map=${CMAKE_BINARY_DIR}/firmware_test.map + -Wl,--defsym=__stack_size__=0x2000 + -Wl,--defsym=__heap_size__=0x2000 + -T${PROJECT_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_flexspi_nor_sdram.ld) + +set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY + ${CMAKE_BINARY_DIR}) +# ----------------------------------------------------------------------------- +# Post-build: генерация .bin для прошивки через blhost +# ----------------------------------------------------------------------------- +add_custom_command( + TARGET firmware_test + POST_BUILD + COMMAND ${CMAKE_OBJCOPY} -O binary $ + ${CMAKE_BINARY_DIR}/firmware_test.bin + COMMAND ${CMAKE_SIZE} $ + COMMENT "Generating firmware_test.bin") + +``` + +сборка начала валиться с ошибками: + +```bash +Executing task: just build::hab-firmware-test-debug + +cmake --preset Debug +Preset CMake variables: + + CMAKE_BUILD_TYPE="Debug" + CMAKE_EXPORT_COMPILE_COMMANDS="ON" + CMAKE_TOOLCHAIN_FILE:FILEPATH="/workspace/cmake/toolchain_arm.cmake" + SEGGER_RTT_ENABLED="OFF" + UNITY_TESTING_ENABLED="OFF" + +-- ARM Toolchain: /opt/arm-toolchain +-- Build type: Debug +-- ==== Included external libraries ==== +-- SEGGER RTT ❎ +-- Unity ❎ +-- FFF ❎ +-- ==== ---------------- ==== +-- Configuring done +-- Generating done +-- Build files have been written to: /workspace/build/Debug +cmake --build --preset firmware-test-debug +[108/108] Linking C executable firmware_test.elf +FAILED: firmware_test.elf +: && /opt/arm-toolchain/bin/arm-none-eabi-gcc -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -ffreestanding -O0 -g3 -gdwarf-4 --specs=nano.specs -Wl,--no-warn-rwx-segments -Wl,--gc-sections -Wl,--print-memory-usage -Wl,-Map=/workspace/build/Debug/firmware_test.map -Wl,--defsym=__stack_size__=0x2000 -Wl,--defsym=__heap_size__=0x2000 -T/workspace/firmware/test/cmake/linker/MIMXRT1052xxxxx_flexspi_nor_sdram.ld firmware/test/CMakeFiles/firmware_test.dir/src/main.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/cli.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/protocol.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/test_runner.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/tests/test_opto.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/tests/test_sdram.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/tests/test_can.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/tests/test_mqs.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/tests/test_qspi.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/tests/test_usd.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/tests/test_display.c.obj firmware/test/CMakeFiles/firmware_test.dir/src/tests/test_buttons.c.obj firmware/test/CMakeFiles/firmware_test.dir/__/__/bsp/generated/clock_config.c.obj firmware/test/CMakeFiles/firmware_test.dir/__/__/bsp/generated/startup/startup_MIMXRT1052.S.obj firmware/test/CMakeFiles/firmware_test.dir/__/__/bsp/generated/syscalls.c.obj -o firmware_test.elf bsp/libbsp_board.a bsp/can/libbsp_can.a bsp/led/libbsp_led.a bsp/button/libbsp_button.a bsp/display/libbsp_display.a bsp/tick/libbsp_tick.a bsp/usb_cdc/libbsp_usb_cdc.a bsp/provisioning/libbsp_provisioning.a bsp/sdram/libbsp_sdram.a bsp/qspi_flash/libbsp_qspi_flash.a bsp/uart_host/libbsp_uart_host.a bsp/opto/libbsp_opto.a bsp/mqs/libbsp_mqs.a bsp/sd/libbsp_sd.a firmware/test/fatfs/libfirmware_test_fatfs.a sdk/libsdk_flexcan.a sdk/libsdk_elcdif.a sdk/libsdk_usb_device_ehci.a sdk/libsdk_usb_phy.a sdk/libsdk_semc.a sdk/libsdk_flexspi.a utils/libutils.a bsp/tick/libbsp_tick.a sdk/libsdk_gpio.a sdk/libsdk_sai_edma.a sdk/libsdk_sai.a sdk/libsdk_edma.a sdk/libsdk_dmamux.a sdk/libsdk_pwm.a sdk/libsdk_xbara.a bsp/sd/libbsp_sd.a bsp/libbsp_sdmmc_config.a bsp/libbsp_board.a sdk/libsdk_lpuart.a sdk/libsdk_sdmmc_sd.a sdk/libsdk_osa_bm.a sdk/libsdk_usdhc.a sdk/libsdk_clock.a sdk/libsdk_common.a sdk/libsdk_cache.a sdk/libsdk_device.a && cd /workspace/build/Debug/firmware/test && /opt/arm-toolchain/bin/arm-none-eabi-objcopy -O binary /workspace/build/Debug/firmware_test.elf /workspace/build/Debug/firmware_test.bin && /opt/arm-toolchain/bin/arm-none-eabi-size /workspace/build/Debug/firmware_test.elf +/opt/arm-gnu-toolchain-13.3.rel1-aarch64-arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.3.1/../../../../arm-none-eabi/bin/ld: cannot open linker script file /workspace/firmware/test/cmake/linker/MIMXRT1052xxxxx_flexspi_nor_sdram.ld: No such file or directory +collect2: error: ld returned 1 exit status +ninja: build stopped: subcommand failed. +error: Recipe `build-firmware-test-debug` failed on line 38 with exit code 1 + + * The terminal process "/bin/bash '-c', 'just build::hab-firmware-test-debug'" terminated with exit code: 1. + * Terminal will be reused by tasks, press any key to close it. +``` + +## Экран Waiting + +1. Вне зависимости от масштаба окна прямоугольник с названием программы TFT Indicator Board Service Tool всегда находится в левом верхнем углу и выглядит неуместным. Предлагаю для всех экранов для унификации ввести цветную рамку фиксированного размера 640x480/1280x1024, чтобы вне зависимости от размера окна мы всегда видели одно и то же. Если бы еще можно было запретить делать Maximize для конкретного окна - прекрасно. Мне нравится когда TUI приложение ограничено окном - рамкой. В ratatui-rust много похожих приложений. Например [binsider](https://github.com/orhun/binsider) + +## Экран Flasher + +1. progress bar с процентами выполнения: анимация работает даже в случае если ничего не выполняется +2. progress bar с процентами выполнения: при выполнении операций Cheap Erase и Прошить проценты не меняются, просто продолжается анимация. Только в конце операции появляется `done 100%` ниже progress Bar + +Надо тщательно продумать логику работы прогресс бара. Нужно запускать его только при начале операций erase/прошить, можно не показывать проценты вообще. + +3. После выполнения операции Прошить приложение сразу перезапускает экран Waiting и мы опять попадаем в то же самое меню Flasher. Если сервисник хочет провести тесты - это неудобно. Ему нужно будет с помощью Ctrl+C закрывать приложение, менять BootMode перезапускать плату и затем само приложение. Надо продумать здесь следующий механизм: если мы шьем firmware_test можно вывести экран с промптом а-ля - теперь перезапустите плату с другим режимом boot Mode, дать время секунд 40 если сервисник справится раньше - пусть жмет ОК, если он не успелприложениш просто перезапустится и в худщем случае мы опять попадем в Flasher. В общем тут надо подумать. При прошивке кастомного бинаря/production этот промпт не нужен (хотя можно оставить напоминание, что для запуска прошитого бинаря перезапустите плату с другим boot Mode. Надо в экране Flasher предусмотреть кнопку выхода из приложения. + +4. Был странный вылет из приложения (экран Flasher) когда я рандомно выделял элементы на экране приложения и кликал : + +```bash +production git:(dev) ✗ uv run python main.py +╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv/lib/python3.14/site-packages/textual/app.py:4082 in on_event │ +│ │ +│ 4079 │ │ │ │ │ │ # Shouldn't occur, since at the very least this will find the Sc │ +│ 4080 │ │ │ │ │ │ self._mouse_down_widget = None │ +│ 4081 │ │ │ │ │ +│ ❱ 4082 │ │ │ │ self.screen._forward_event(event) │ +│ 4083 │ │ │ │ │ +│ 4084 │ │ │ │ # If a MouseUp occurs at the same widget as a MouseDown, then we should │ +│ 4085 │ │ │ │ # consider it a click, and produce a Click event. │ +│ │ +│ ╭───────────────────────────────────────────────────────────────────────────────────────────────────── locals ─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ +│ │ event = MouseMove(None, x=0, y=34, pointer_x=0.0, pointer_y=34.0, delta_x=-9, delta_y=-3, button=1, style=Style(bgcolor=Color('#121212', ColorType.TRUECOLOR, triplet=ColorTriplet(red=18, green=18, blue=18)))) │ │ +│ │ self = ServiceApp(title='TFT Board Service Tool', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'}) │ │ +│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ +│ │ +│ /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv/lib/python3.14/site-packages/textual/screen.py:1841 in _forward_event │ +│ │ +│ 1838 │ │ │ │ │ if select_offset is not None: │ +│ 1839 │ │ │ │ │ │ content_widget = select_widget │ +│ 1840 │ │ │ │ │ │ content_offset = select_offset │ +│ ❱ 1841 │ │ │ │ │ │ assert isinstance(content_widget.parent, Widget) │ +│ 1842 │ │ │ │ │ │ container = content_widget.parent │ +│ 1843 │ │ │ │ │ else: │ +│ 1844 │ │ │ │ │ │ content_widget = None │ +│ │ +│ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────── locals ──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ +│ │ content_offset = Offset(x=0, y=33) │ │ +│ │ content_widget = FlashScreen() │ │ +│ │ event = MouseMove(None, x=0, y=34, pointer_x=0.0, pointer_y=34.0, delta_x=-9, delta_y=-3, button=1, style=Style(bgcolor=Color('#121212', ColorType.TRUECOLOR, triplet=ColorTriplet(red=18, green=18, blue=18)))) │ │ +│ │ select_offset = Offset(x=0, y=33) │ │ +│ │ select_widget = FlashScreen() │ │ +│ │ self = FlashScreen() │ │ +│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +AssertionError +``` + +## Экран ConfirmPanel + +1. Изначально все тесты из списка можно сделать некактивными +2. Помимо версии fw давай также будем выводить в тайтле UID камня +3. В поле результаты можно сделать симпатичную таблицу с двумя столбцами: название теста/ результат +4. Аналогично экрану Flasher: прогресс бар постоянно активен, даже в режиме простоя +5. Также можно предусмотреть кнопку выхода из приложения (аналогично Flasher) \ No newline at end of file diff --git a/tools/production/app/app.py b/tools/production/app/app.py new file mode 100644 index 0000000..da08b36 --- /dev/null +++ b/tools/production/app/app.py @@ -0,0 +1,87 @@ +""" +app.py — корневое Textual приложение. + +Управляет сменой экранов и жизненным циклом клиентов +(FirmwareClient, M5Client). +""" + +from __future__ import annotations + +import logging +import os +from typing import Optional + +from textual import on, work +from textual.app import App +from textual.binding import Binding + +from .firmware_client import FirmwareClient +from .m5_client import M5Client +from .models import AppMode +from .screens import DiagScreen, FlashScreen, WaitingScreen + +logger = logging.getLogger(__name__) + +_CDC_VID = int(os.environ.get("SERVICE_CDC_VID", "0x1996"), 16) +_CDC_PID = int(os.environ.get("SERVICE_CDC_PID", "0x00ad"), 16) + + +class ServiceApp(App): + """Корневое приложение service-tui.""" + + TITLE = "TFT Board Service Tool" + BINDINGS = [ + Binding("ctrl+c", "quit", "Выход", show=True), + Binding("ctrl+q", "quit", "Выход"), + ] + CSS_PATH = "app.tcss" + + def __init__(self) -> None: + super().__init__() + self._fw: Optional[FirmwareClient] = None + self._m5: Optional[M5Client] = None + + def on_mount(self) -> None: + self.push_screen(WaitingScreen()) + + # ── Переходы между экранами ─────────────────────────────────────────────── + + @on(WaitingScreen.DeviceDetected) + def _on_device_detected(self, event: WaitingScreen.DeviceDetected) -> None: + if event.mode == AppMode.FLASHING: + self.switch_screen(FlashScreen()) + elif event.mode == AppMode.DIAGNOSING: + self._connect_and_diagnose() + + @on(FlashScreen.FlashDone) + def _on_flash_done(self, _event: FlashScreen.FlashDone) -> None: + """После прошивки — вернуться на WaitingScreen.""" + self.switch_screen(WaitingScreen()) + + @on(DiagScreen.DiagDone) + def _on_diag_done(self) -> None: + """После диагностики — отключиться, вернуться в Waiting.""" + self._disconnect() + self.switch_screen(WaitingScreen()) + + # ── Подключение к firmware_test ─────────────────────────────────────────── + + @work(thread=False) + async def _connect_and_diagnose(self) -> None: + try: + self._fw = await FirmwareClient.auto_connect(vid=_CDC_VID, pid=_CDC_PID) + except Exception as exc: + logger.error("CDC connect failed: %s", exc) + self.switch_screen(WaitingScreen()) + return + + self._m5 = await M5Client.auto_connect() + self.switch_screen(DiagScreen(firmware=self._fw, m5=self._m5)) + + def _disconnect(self) -> None: + if self._fw is not None: + self.call_later(self._fw.disconnect) + self._fw = None + if self._m5 is not None: + self.call_later(self._m5.disconnect) + self._m5 = None diff --git a/tools/production/app/app.tcss b/tools/production/app/app.tcss new file mode 100644 index 0000000..602dffc --- /dev/null +++ b/tools/production/app/app.tcss @@ -0,0 +1,283 @@ +/* ── WaitingScreen ───────────────────────────────────────────────────────── */ + +WaitingScreen { + align: center middle; + layout: vertical; +} + +WaitingScreen > Static { + content-align: center middle; + width: 100%; +} + +#waiting-logo { + width: 52; + height: 7; + border: round $primary; + padding: 1 2; + content-align: center middle; + text-style: bold; + color: $primary; + margin-bottom: 0; +} + +#waiting-hint { + margin-top: 2; + color: $text-muted; + content-align: center middle; +} + +#waiting-spinner { + margin-top: 1; + color: $accent; + content-align: center middle; +} + +/* ── FlashScreen ─────────────────────────────────────────────────────────── */ + +FlashScreen { + padding: 1 2; +} + +#flash-title { + text-style: bold; + color: $warning; + margin-bottom: 1; +} + +#flash-target-group { + border: round $panel; + padding: 1 2; + margin-bottom: 1; + height: auto; +} + + +#flash-custom-path { + margin-top: 1; + height: auto; +} + +#flash-custom-path.hidden { + display: none; +} + +#flash-custom-input { + width: 1fr; +} + +#flash-btn-row { + height: auto; + margin-top: 1; + align: left middle; +} + +#flash-btn-flash { + margin-right: 2; +} + +#flash-btn-flash:disabled, +#flash-btn-erase:disabled { + background: $panel; + color: $text-muted; +} + +#flash-progress-bar { + margin-top: 1; +} + +#flash-progress-label { + color: $text-muted; +} + +#flash-log { + height: 1fr; + margin-top: 1; + border: round $panel; + background: $surface-darken-1; + overflow-y: auto; +} + +/* ── DiagScreen ──────────────────────────────────────────────────────────── */ + +DiagScreen { + layout: grid; + grid-size: 1; + grid-rows: 3 1fr 1 1 3 auto; +} + +/* Шапка */ +#diag-header { + height: 3; + background: $panel; + padding: 0 2; + align: left middle; +} + +#diag-header-fw { + color: $text-muted; + margin-right: 3; +} + +#diag-header-uid { + color: $text-muted; + margin-right: 3; +} + +#diag-header-m5 { + color: $success; +} + +#diag-header-m5.m5-absent { + color: $text-muted; +} + +/* Рабочая зона */ +#diag-main { + height: 1fr; +} + +/* Прогресс */ +#diag-progress-bar { + height: 1; + margin: 0 2; +} + +#diag-progress-label { + height: 1; + color: $text-muted; + padding: 0 2; +} + +/* Кнопки запуска */ +#diag-btn-row { + height: 3; + align: left middle; + padding: 0 2; + background: $panel; +} + +#diag-btn-run-selected { + margin-right: 2; +} + +#diag-btn-run-selected:disabled, +#diag-btn-run-all:disabled { + background: $panel; + color: $text-muted; +} + +/* ── TestListPanel ───────────────────────────────────────────────────────── */ + +TestListPanel { + width: 38; + border-right: solid $panel; + padding: 1 1; + overflow-y: auto; +} + +TestListPanel .section-title { + text-style: bold; + color: $text-muted; + padding: 0 0 1 0; +} + +.test-row { + align: left middle; + height: auto; +} + +.test-row-hil-badge { + width: 6; + color: $accent; +} + +.test-row-hil-badge.hil-disabled { + color: $text-muted; +} + +/* ── ResultsPanel ────────────────────────────────────────────────────────── */ + +ResultsPanel { + width: 1fr; + padding: 1 2; + overflow-y: auto; +} + +ResultsPanel .section-title { + text-style: bold; + color: $text-muted; + padding: 0 0 1 0; +} + +.result-row { + height: 1; + align: left middle; +} + +.result-id { + color: $text-muted; + width: 12; +} + +.result-status-pass { + color: $success; + width: 8; + text-style: bold; +} + +.result-status-fail { + color: $error; + width: 8; + text-style: bold; +} + +.result-status-running { + color: $warning; + width: 8; +} + +.result-status-skip { + color: $text-muted; + width: 8; +} + +.result-detail { + color: $text-muted; + width: 1fr; + overflow: hidden; +} + +/* ── ConfirmPanel ────────────────────────────────────────────────────────── */ + +ConfirmPanel { + height: auto; + background: $warning 15%; + border-top: solid $warning; + padding: 1 2; + align: left middle; +} + +ConfirmPanel.hidden { + display: none; +} + +#confirm-prompt { + width: 1fr; + color: $warning; + text-style: bold; +} + +#confirm-countdown { + color: $text-muted; + margin-right: 2; +} + +#confirm-btn-ok { + margin-right: 1; +} + +#confirm-btn-ok.hidden, +#confirm-btn-fail.hidden { + display: none; +} \ No newline at end of file diff --git a/tools/production/app/firmware_client.py b/tools/production/app/firmware_client.py index 85e7b87..a20ee8e 100644 --- a/tools/production/app/firmware_client.py +++ b/tools/production/app/firmware_client.py @@ -28,7 +28,7 @@ from typing import AsyncGenerator, Optional import serial import serial.tools.list_ports -from .models import ConfirmRequest, TestInfo, TestResult, TestStatus +from .models import TestInfo, TestStatus logger = logging.getLogger(__name__) @@ -92,7 +92,7 @@ class FirmwareClient: async def connect(self) -> None: """Открыть порт и проверить связь через ping→pong.""" - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() await loop.run_in_executor(None, self._open) ok = await self.ping() if not ok: @@ -110,7 +110,7 @@ class FirmwareClient: async def disconnect(self) -> None: """Закрыть порт.""" - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() await loop.run_in_executor(None, self._close) def _close(self) -> None: @@ -119,7 +119,9 @@ class FirmwareClient: self._ser = None @classmethod - async def auto_connect(cls, vid: int, pid: int, baudrate: int = 115200) -> "FirmwareClient": + async def auto_connect( + cls, vid: int, pid: int, baudrate: int = 115200 + ) -> "FirmwareClient": """ Найти CDC-порт по VID/PID и подключиться. @@ -152,7 +154,7 @@ class FirmwareClient: async def _send(self, obj: dict) -> None: """Отправить JSON-команду (async wrapper).""" - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() async with self._lock: await loop.run_in_executor(None, self._write_line, obj) @@ -165,7 +167,7 @@ class FirmwareClient: Читать события до получения одного из stop_types или таймаута. Генератор — yield каждого полученного события. """ - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() deadline = loop.time() + timeout_s while loop.time() < deadline: event = await loop.run_in_executor(None, self._read_line) @@ -182,7 +184,7 @@ class FirmwareClient: async def ping(self) -> bool: """Отправить ping, ждать pong. Вернуть True при успехе.""" await self._send({"type": "cmd", "cmd": "ping"}) - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() deadline = loop.time() + _PING_TIMEOUT_S while loop.time() < deadline: event = await loop.run_in_executor(None, self._read_line) @@ -191,6 +193,14 @@ class FirmwareClient: await asyncio.sleep(0) return False + async def get_version(self) -> str: + """Запросить версию firmware_test. Вернуть строку X.Y.Z или ''.""" + await self._send({"type": "cmd", "cmd": "get_version"}) + async for event in self._recv_until({"version_response"}, timeout_s=3.0): + if event.get("type") == "version_response": + return event.get("fw", "") + return "" + async def list_tests(self) -> list[TestInfo]: """Запросить список тестов. Вернуть list[TestInfo].""" await self._send({"type": "cmd", "cmd": "list_tests"}) @@ -207,9 +217,7 @@ class FirmwareClient: ] return [] - async def run_selected( - self, test_ids: list[str] - ) -> AsyncGenerator[dict, None]: + async def run_selected(self, test_ids: list[str]) -> AsyncGenerator[dict, None]: """ Запустить выбранные тесты. Возвращает async generator событий: test_begin, test_result, confirm_request, summary, error. @@ -218,7 +226,9 @@ class FirmwareClient: send_confirm() не дожидаясь следующего события. """ await self._send({"type": "cmd", "cmd": "run_selected", "tests": test_ids}) - async for event in self._recv_until({"summary"}, timeout_s=_TEST_EVENT_TIMEOUT_S): + async for event in self._recv_until( + {"summary"}, timeout_s=_TEST_EVENT_TIMEOUT_S + ): yield event async def send_confirm(self, confirm_id: str, confirmed: bool) -> None: @@ -240,4 +250,4 @@ class FirmwareClient: @staticmethod def find_port(vid: int, pid: int) -> Optional[str]: """Найти CDC-порт по VID/PID. None если не найден.""" - return _find_cdc_port(vid, pid) \ No newline at end of file + return _find_cdc_port(vid, pid) diff --git a/tools/production/app/flasher.py b/tools/production/app/flasher.py index e4d4e37..c42b0d0 100644 --- a/tools/production/app/flasher.py +++ b/tools/production/app/flasher.py @@ -40,12 +40,32 @@ _HOST_TOOLS_DIR = _FLASH_USB_SCRIPT.parent _RE_PERCENT = re.compile(r"(\d{1,3})\s*%") _RE_PHASE = re.compile(r"(sdphost|blhost|Writing|Erasing|Verifying)", re.IGNORECASE) +_FIRMWARE_BUILD_TYPE = os.environ.get("FIRMWARE_BUILD_TYPE", "Debug") ProgressCallback = Callable[[FlashProgress], Awaitable[None]] def _detect_usb(vid: int, pid: int) -> bool: - """Проверить наличие USB-устройства по VID/PID (синхронно).""" + """ + Проверить наличие USB-устройства по VID/PID (синхронно). + + Два метода детекта: + 1. pyusb (usb.core) — видит все USB-устройства включая SDP bulk/HID + (на macOS SDP не создаёт serial-порт и невидим через list_ports). + 2. serial.tools.list_ports — fallback для CDC ACM устройств + если pyusb недоступен. + """ + # Метод 1: pyusb — работает для SDP и CDC + try: + import usb.core + + dev = usb.core.find(idVendor=vid, idProduct=pid) + if dev is not None: + return True + except Exception: + pass + + # Метод 2: serial list_ports — fallback для CDC ACM try: import serial.tools.list_ports @@ -54,6 +74,7 @@ def _detect_usb(vid: int, pid: int) -> bool: return True except Exception: pass + return False @@ -127,11 +148,11 @@ class Flasher: if target == FlashTarget.FIRMWARE_TEST: if bin_path is not None: return await self._run_flash_bin(bin_path, progress_cb) - return await self._run_flash("firmware_test", "Release", progress_cb) + return await self._run_flash("firmware_test", progress_cb) elif target == FlashTarget.PRODUCTION: - ok = await self._run_flash("bootloader", "Release", progress_cb) + ok = await self._run_flash("bootloader", progress_cb) if ok: - ok = await self._run_flash("app", "Release", progress_cb) + ok = await self._run_flash("app", progress_cb) return ok elif target == FlashTarget.CUSTOM: if bin_path is None: @@ -142,7 +163,6 @@ class Flasher: async def _run_flash( # прошивка стандартного firmware по имени self, firmware: str, - build_type: str, progress_cb: Optional[ProgressCallback], ) -> bool: """Запустить flash_usb.py для одного бинаря.""" @@ -156,7 +176,7 @@ class Flasher: "--firmware", firmware, "--build-type", - build_type, + _FIRMWARE_BUILD_TYPE, ] return await self._run_cmd(cmd, firmware, progress_cb) diff --git a/tools/production/app/m5_client.py b/tools/production/app/m5_client.py index a282622..99fe3e8 100644 --- a/tools/production/app/m5_client.py +++ b/tools/production/app/m5_client.py @@ -87,7 +87,7 @@ class M5Client: async def connect(self) -> None: """Открыть порт и проверить связь через ping.""" - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() await loop.run_in_executor(None, self._open) ok = await self.ping() if not ok: @@ -103,7 +103,7 @@ class M5Client: self._ser.reset_input_buffer() async def disconnect(self) -> None: - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() await loop.run_in_executor(None, self._close) def _close(self) -> None: @@ -133,7 +133,7 @@ class M5Client: async def _cmd(self, cmd: dict) -> Optional[dict]: """Async wrapper над _send_recv.""" - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() async with self._lock: return await loop.run_in_executor(None, self._send_recv, cmd) @@ -185,4 +185,4 @@ class M5Client: @staticmethod def find_port() -> Optional[str]: """Найти порт M5StampPLC. None если не найден.""" - return _find_m5_port() \ No newline at end of file + return _find_m5_port() diff --git a/tools/production/app/screens/__init__.py b/tools/production/app/screens/__init__.py new file mode 100644 index 0000000..dfe4305 --- /dev/null +++ b/tools/production/app/screens/__init__.py @@ -0,0 +1,7 @@ +"""screens — публичный экспорт экранов TUI.""" + +from .diag import DiagScreen +from .flash import FlashScreen +from .waiting import WaitingScreen + +__all__ = ["WaitingScreen", "FlashScreen", "DiagScreen"] diff --git a/tools/production/app/screens/diag/__init__.py b/tools/production/app/screens/diag/__init__.py new file mode 100644 index 0000000..3e3e1ef --- /dev/null +++ b/tools/production/app/screens/diag/__init__.py @@ -0,0 +1,261 @@ +""" +diag/__init__.py — экран диагностики (режим B). + +DiagScreen координирует три виджета: + TestListPanel — выбор тестов (левая колонка) + ResultsPanel — результаты (правая колонка) + ConfirmPanel — confirm_request оператора (нижняя панель) + +и Orchestrator — маршрутизатор confirm_request. +""" + +from __future__ import annotations + +import logging +from typing import Optional + +from textual import on, work +from textual.app import ComposeResult +from textual.binding import Binding +from textual.containers import Horizontal +from textual.css.query import NoMatches +from textual.message import Message +from textual.screen import Screen +from textual.widgets import Button, Label, ProgressBar, Static + +from ...firmware_client import FirmwareClient +from ...m5_client import M5Client +from ...models import SessionState +from ...orchestrator import Orchestrator, OrchestratorEvent, OrchestratorEventType +from .confirm_panel import ConfirmPanel +from .results import ResultsPanel +from .test_list import TestListPanel + +logger = logging.getLogger(__name__) + + +class DiagScreen(Screen): + """ + Экран диагностики. + + Messages: + DiagDone() — сессия завершена (плата отключена или summary получен) + """ + + BINDINGS = [ + Binding("escape", "go_back", "Отключиться"), + Binding("r", "run_all", "Все тесты"), + Binding("s", "run_selected", "Выбранные"), + ] + + class DiagDone(Message): + """Сессия диагностики завершена.""" + + def __init__( + self, + firmware: FirmwareClient, + m5: Optional[M5Client] = None, + **kwargs, + ) -> None: + super().__init__(**kwargs) + self._fw = firmware + self._m5 = m5 + self._orchestrator = Orchestrator(firmware, m5) + self._session = SessionState() + self._running = False + + def compose(self) -> ComposeResult: + # Шапка + with Horizontal(id="diag-header"): + yield Static("fw: —", id="diag-header-fw") + yield Static("UID: —", id="diag-header-uid") + yield Static("M5: —", id="diag-header-m5") + + # Рабочая зона: список тестов + результаты + with Horizontal(id="diag-main"): + yield TestListPanel(id="diag-test-list") + yield ResultsPanel(id="diag-results") + + # Прогресс + yield ProgressBar(id="diag-progress-bar", show_eta=False) + yield Label("", id="diag-progress-label") + + # Кнопки запуска + with Horizontal(id="diag-btn-row"): + yield Button( + "▶ Запустить выбранные", + id="diag-btn-run-selected", + variant="primary", + disabled=True, + ) + yield Button( + "▶▶ Все тесты", + id="diag-btn-run-all", + variant="default", + disabled=True, + ) + + # Панель confirm + yield ConfirmPanel(id="diag-confirm", classes="hidden") + + def on_mount(self) -> None: + self._init_session() + + # ── Инициализация сессии ───────────────────────────────────────────────── + + @work(thread=False) + async def _init_session(self) -> None: + try: + tests = await self._fw.list_tests() + uid = await self._fw.get_uid() + version = await self._fw.get_version() + except Exception as exc: + logger.error("Session init failed: %s", exc) + self.post_message(self.DiagDone()) + return + + self._session.tests = tests + self._session.chip_uid = uid + self._session.fw_version = version + self._session.m5_connected = self._m5 is not None + + self._update_header() + + test_list = self.query_one("#diag-test-list", TestListPanel) + test_list.populate(tests, m5_connected=self._session.m5_connected) + + self.query_one("#diag-results", ResultsPanel).populate(tests) + self._set_run_buttons(enabled=True) + + def _update_header(self) -> None: + self.query_one("#diag-header-fw", Static).update( + f"fw: {self._session.fw_version or '?'}" + ) + uid_short = self._session.chip_uid[:16] if self._session.chip_uid else "—" + self.query_one("#diag-header-uid", Static).update(f"UID: {uid_short}") + + m5_widget = self.query_one("#diag-header-m5", Static) + if self._session.m5_connected: + m5_widget.update("M5: ✓ подключён") + m5_widget.remove_class("m5-absent") + else: + m5_widget.update("M5: — нет") + m5_widget.add_class("m5-absent") + + # ── Кнопки ─────────────────────────────────────────────────────────────── + + @on(Button.Pressed, "#diag-btn-run-selected") + def _on_run_selected(self) -> None: + ids = self.query_one("#diag-test-list", TestListPanel).get_selected_ids() + if ids: + self._start_run(ids) + + @on(Button.Pressed, "#diag-btn-run-all") + def _on_run_all(self) -> None: + ids = [t.id for t in self._session.tests] + if ids: + self._start_run(ids) + + def action_go_back(self) -> None: + if not self._running: + self.post_message(self.DiagDone()) + + def action_run_all(self) -> None: + self._on_run_all() + + def action_run_selected(self) -> None: + self._on_run_selected() + + # ── Confirm ─────────────────────────────────────────────────────────────── + + @on(ConfirmPanel.Confirmed) + def _on_confirmed(self, event: ConfirmPanel.Confirmed) -> None: + """Оператор ответил — передать в оркестратор.""" + self.app.call_later( + self._orchestrator.resolve_operator_confirm, event.confirmed + ) + + # ── Запуск тестов ──────────────────────────────────────────────────────── + + def _start_run(self, test_ids: list[str]) -> None: + results = self.query_one("#diag-results", ResultsPanel) + results.reset() + self._session.results.clear() + self._update_progress(0, len(test_ids), "") + self._set_run_buttons(enabled=False) + self._running = True + self._run_worker(test_ids) + + @work(exclusive=True, thread=False) + async def _run_worker(self, test_ids: list[str]) -> None: + total = len(test_ids) + done = 0 + try: + async for event in self._orchestrator.run_tests(test_ids): + self._handle_event(event, total, done) + if event.type == OrchestratorEventType.TEST_RESULT: + done += 1 + if event.type == OrchestratorEventType.SUMMARY: + break + except Exception as exc: + logger.error("run_worker error: %s", exc) + finally: + self._running = False + self._set_run_buttons(enabled=True) + try: + self.query_one("#diag-confirm", ConfirmPanel).hide() + except NoMatches: + pass + + def _handle_event(self, event: OrchestratorEvent, total: int, done: int) -> None: + results = self.query_one("#diag-results", ResultsPanel) + confirm = self.query_one("#diag-confirm", ConfirmPanel) + + if event.type == OrchestratorEventType.TEST_BEGIN: + results.set_running(event.test_id) + self._update_progress(done, total, f"Тест: {event.test_id}") + + elif event.type == OrchestratorEventType.TEST_RESULT: + assert event.result is not None + self._session.set_result(event.result) + results.set_result(event.result) + + elif event.type == OrchestratorEventType.CONFIRM_NEEDED: + assert event.confirm is not None + confirm.show_operator( + prompt=event.confirm.prompt, + timeout_ms=event.confirm.timeout_ms, + ) + + elif event.type == OrchestratorEventType.CONFIRM_RESOLVED: + self._update_progress(done, total, f"HIL: {event.message}") + + elif event.type == OrchestratorEventType.BUTTONS_PROMPT: + assert event.confirm is not None + confirm.show_buttons_hint(event.confirm.prompt) + + elif event.type == OrchestratorEventType.SUMMARY: + self._on_summary(event.summary or {}) + + elif event.type == OrchestratorEventType.ERROR: + self._update_progress(done, total, f"⚠ {event.message}") + + def _on_summary(self, summary: dict) -> None: + overall = summary.get("overall", "fail") + passed = summary.get("passed", 0) + failed = summary.get("failed", 0) + icon = "✅" if overall == "pass" else "❌" + self.query_one("#diag-progress-label", Label).update( + f"{icon} Итог: {passed} прошли, {failed} не прошли" + ) + + # ── Утилиты ─────────────────────────────────────────────────────────────── + + def _update_progress(self, done: int, total: int, msg: str) -> None: + pct = int(done / total * 100) if total else 0 + self.query_one("#diag-progress-bar", ProgressBar).update(progress=pct) + self.query_one("#diag-progress-label", Label).update(msg) + + def _set_run_buttons(self, enabled: bool) -> None: + self.query_one("#diag-btn-run-selected", Button).disabled = not enabled + self.query_one("#diag-btn-run-all", Button).disabled = not enabled diff --git a/tools/production/app/screens/diag/confirm_panel.py b/tools/production/app/screens/diag/confirm_panel.py new file mode 100644 index 0000000..0570a91 --- /dev/null +++ b/tools/production/app/screens/diag/confirm_panel.py @@ -0,0 +1,140 @@ +""" +confirm_panel.py — виджет панели подтверждения оператора. + +Показывается при confirm_request от firmware_test требующем ответа оператора. +Скрыт по умолчанию (CSS-класс 'hidden'). + +Messages: + ConfirmPanel.Confirmed(confirmed: bool) — оператор нажал OK или Нет +""" + +from __future__ import annotations + +from textual import on +from textual.app import ComposeResult +from textual.css.query import NoMatches +from textual.message import Message +from textual.timer import Timer +from textual.widget import Widget +from textual.widgets import Button, Static + +_TICK_S = 1.0 + + +class ConfirmPanel(Widget): + """ + Панель подтверждения оператора. + + Использование:: + + panel = ConfirmPanel() + panel.show(prompt="Экран залит красным?", timeout_ms=30000) + # Слушать ConfirmPanel.Confirmed в родительском экране + """ + + class Confirmed(Message): + """Оператор ответил на confirm_request.""" + + def __init__(self, confirmed: bool) -> None: + super().__init__() + self.confirmed = confirmed + + DEFAULT_CSS = "" # стили в diag.tcss + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + self._timer: Timer | None = None + self._remaining: int = 0 + self._operator_mode: bool = True # False → режим buttons (нет кнопок) + + def compose(self) -> ComposeResult: + yield Static("", id="confirm-prompt") + yield Static("", id="confirm-countdown") + yield Button("✓ Да", id="confirm-btn-ok", variant="success") + yield Button("✗ Нет", id="confirm-btn-fail", variant="error") + + # ── Public API ──────────────────────────────────────────────────────────── + + def show_operator(self, prompt: str, timeout_ms: int) -> None: + """Показать панель с кнопками OK/Нет и countdown.""" + self._operator_mode = True + self._remaining = timeout_ms // 1000 + self._set_prompt(prompt) + self._set_countdown(self._remaining) + self._show_buttons(True) + self.remove_class("hidden") + self._start_timer() + + def show_buttons_hint(self, prompt: str) -> None: + """ + Показать инструкцию для теста кнопок. + Без кнопок OK/Нет — оператор только читает, нажимает физическую кнопку. + """ + self._operator_mode = False + self._stop_timer() + self._set_prompt(f"⌨ {prompt}") + self._set_countdown("") + self._show_buttons(False) + self.remove_class("hidden") + + def hide(self) -> None: + """Скрыть панель, остановить таймер.""" + self._stop_timer() + self.add_class("hidden") + self._show_buttons(True) # восстановить на следующий раз + + # ── Обработчики ─────────────────────────────────────────────────────────── + + @on(Button.Pressed, "#confirm-btn-ok") + def _on_ok(self) -> None: + self.hide() + self.post_message(self.Confirmed(confirmed=True)) + + @on(Button.Pressed, "#confirm-btn-fail") + def _on_fail(self) -> None: + self.hide() + self.post_message(self.Confirmed(confirmed=False)) + + # ── Таймер countdown ───────────────────────────────────────────────────── + + def _start_timer(self) -> None: + self._stop_timer() + self._timer = self.set_interval(_TICK_S, self._tick) + + def _stop_timer(self) -> None: + if self._timer is not None: + self._timer.stop() + self._timer = None + + def _tick(self) -> None: + self._remaining -= 1 + self._set_countdown(self._remaining) + if self._remaining <= 0: + self.hide() + self.post_message(self.Confirmed(confirmed=False)) + + # ── Утилиты ─────────────────────────────────────────────────────────────── + + def _set_prompt(self, text: str) -> None: + try: + self.query_one("#confirm-prompt", Static).update(f"⚠ {text}") + except NoMatches: + pass + + def _set_countdown(self, value: int | str) -> None: + text = f"{value}с" if isinstance(value, int) and value > 0 else "" + try: + self.query_one("#confirm-countdown", Static).update(text) + except NoMatches: + pass + + def _show_buttons(self, visible: bool) -> None: + for btn_id in ("#confirm-btn-ok", "#confirm-btn-fail"): + try: + btn = self.query_one(btn_id, Button) + if visible: + btn.remove_class("hidden") + else: + btn.add_class("hidden") + except NoMatches: + pass diff --git a/tools/production/app/screens/diag/results.py b/tools/production/app/screens/diag/results.py new file mode 100644 index 0000000..5045b79 --- /dev/null +++ b/tools/production/app/screens/diag/results.py @@ -0,0 +1,115 @@ +""" +results.py — виджет правой колонки DiagScreen. + +Отображает строки результатов тестов: id | статус | detail. +Обновляется по событиям от Orchestrator через DiagScreen. + +Публичный API: + ResultsPanel.populate(tests) — инициализировать пустые строки + ResultsPanel.set_running(test_id) — показать "выполняется" + ResultsPanel.set_result(result) — показать финальный результат + ResultsPanel.reset() — сбросить все строки +""" + +from __future__ import annotations + +from textual.app import ComposeResult +from textual.containers import Horizontal +from textual.css.query import NoMatches +from textual.widget import Widget +from textual.widgets import Label, Static + +from ...models import TestResult, TestStatus + + +def _status_display(status: TestStatus) -> tuple[str, str]: + """Вернуть (текст, css-класс) для статуса.""" + return { + TestStatus.PASS: ("✓ PASS", "result-status-pass"), + TestStatus.FAIL: ("✗ FAIL", "result-status-fail"), + TestStatus.RUNNING: ("…", "result-status-running"), + TestStatus.SKIP: ("SKIP", "result-status-skip"), + TestStatus.PENDING: ("", "result-status-skip"), + }[status] + + +# CSS-классы статусов — для очистки перед сменой +_STATUS_CLASSES = ( + "result-status-pass", + "result-status-fail", + "result-status-running", + "result-status-skip", +) + + +class ResultsPanel(Widget): + """Правая колонка DiagScreen — результаты тестов.""" + + DEFAULT_CSS = "" # стили в diag.tcss + + def compose(self) -> ComposeResult: + yield Label("Результаты", classes="section-title") + + # ── Public API ──────────────────────────────────────────────────────────── + + def populate(self, tests: list) -> None: + """ + Инициализировать пустые строки результатов. + + :param tests: list[TestInfo] + """ + for child in list(self.children): + if not child.has_class("section-title"): + child.remove() + + for test in tests: + row = Horizontal(classes="result-row", id=f"result-row-{test.id}") + id_lbl = Static(test.id, classes="result-id") + status_lbl = Static( + "", classes="result-status-skip", id=f"result-status-{test.id}" + ) + detail_lbl = Static( + "", classes="result-detail", id=f"result-detail-{test.id}" + ) + self.mount(row) + row.mount(id_lbl) + row.mount(status_lbl) + row.mount(detail_lbl) + + def set_running(self, test_id: str) -> None: + """Пометить тест как выполняющийся.""" + self._update(test_id, TestStatus.RUNNING, "") + + def set_result(self, result: TestResult) -> None: + """Показать финальный результат теста.""" + detail = result.detail if result.status == TestStatus.FAIL else "" + self._update(result.id, result.status, detail) + + def reset(self) -> None: + """Сбросить все строки в пустое состояние.""" + for widget in self.query(Static): + wid = widget.id or "" + if wid.startswith("result-status-"): + for cls in _STATUS_CLASSES: + widget.remove_class(cls) + widget.add_class("result-status-skip") + widget.update("") + elif wid.startswith("result-detail-"): + widget.update("") + + # ── Internal ────────────────────────────────────────────────────────────── + + def _update(self, test_id: str, status: TestStatus, detail: str) -> None: + try: + status_w = self.query_one(f"#result-status-{test_id}", Static) + detail_w = self.query_one(f"#result-detail-{test_id}", Static) + except NoMatches: + return + + for cls in _STATUS_CLASSES: + status_w.remove_class(cls) + + text, css = _status_display(status) + status_w.update(text) + status_w.add_class(css) + detail_w.update(detail) diff --git a/tools/production/app/screens/diag/test_list.py b/tools/production/app/screens/diag/test_list.py new file mode 100644 index 0000000..14bcad8 --- /dev/null +++ b/tools/production/app/screens/diag/test_list.py @@ -0,0 +1,83 @@ +""" +test_list.py — виджет списка тестов с чекбоксами. + +Отображает тесты из list_tests, отмечает HIL-тесты, +серит недоступные (HIL без M5). + +Публичный API: + TestListPanel.populate(tests, m5_connected) — заполнить список + TestListPanel.get_selected_ids() — список выбранных id + TestListPanel.set_enabled(enabled) — блокировать во время прогона +""" + +from __future__ import annotations + +from textual.app import ComposeResult +from textual.containers import Horizontal +from textual.widget import Widget +from textual.widgets import Checkbox, Label + + +class TestListPanel(Widget): + """Левая колонка DiagScreen — список тестов с чекбоксами.""" + + DEFAULT_CSS = "" # стили в diag.tcss + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + # test_id → Checkbox для быстрого доступа + self._checkboxes: dict[str, Checkbox] = {} + + def compose(self) -> ComposeResult: + yield Label("Тесты", classes="section-title") + + # ── Public API ──────────────────────────────────────────────────────────── + + def populate(self, tests: list, m5_connected: bool) -> None: + """ + Заполнить список тестами. + + :param tests: list[TestInfo] + :param m5_connected: True если M5StampPLC подключён + """ + # Удалить старые строки (кроме заголовка) + for child in list(self.children): + if not child.has_class("section-title"): + child.remove() + self._checkboxes.clear() + + for test in tests: + hil_unavailable = test.requires_hil and not m5_connected + + cb = Checkbox( + test.name, + value=not hil_unavailable, + disabled=hil_unavailable, + id=f"cb-{test.id}", + classes="-textual-compact", + ) + self._checkboxes[test.id] = cb + + hil_css = ( + "test-row-hil-badge hil-disabled" + if hil_unavailable + else "test-row-hil-badge" + ) + badge_text = "[HIL]" if test.requires_hil else "" + + row = Horizontal(classes="test-row") + self.mount(row) + row.mount(cb) + row.mount(Label(badge_text, classes=hil_css)) + + def get_selected_ids(self) -> list[str]: + """Вернуть список id выбранных (checked + not disabled) тестов.""" + return [ + tid for tid, cb in self._checkboxes.items() if cb.value and not cb.disabled + ] + + def set_enabled(self, enabled: bool) -> None: + """Разрешить/запретить изменение чекбоксов во время прогона.""" + for cb in self._checkboxes.values(): + if not cb.disabled: # не трогать серые HIL-без-M5 + cb.disabled = not enabled diff --git a/tools/production/app/screens/flash.py b/tools/production/app/screens/flash.py new file mode 100644 index 0000000..a2cce04 --- /dev/null +++ b/tools/production/app/screens/flash.py @@ -0,0 +1,197 @@ +""" +flash.py — экран прошивки (режим A). + +Активен когда обнаружен BootROM SDP (1FC9:0130). +Поддерживает три варианта прошивки и chip erase. +""" + +from __future__ import annotations + +import logging +from pathlib import Path +from typing import Optional + +from textual import on, work +from textual.app import ComposeResult +from textual.binding import Binding +from textual.containers import Horizontal, Vertical +from textual.css.query import NoMatches +from textual.message import Message +from textual.screen import Screen +from textual.widgets import ( + Button, + Input, + Label, + Log, + ProgressBar, + RadioButton, + RadioSet, +) + +from ..flasher import Flasher +from ..models import FlashProgress, FlashTarget + +logger = logging.getLogger(__name__) + + +class FlashScreen(Screen): + """ + Экран прошивки. + + Messages: + FlashDone(success) — прошивка завершена + """ + + BINDINGS = [ + Binding("escape", "go_back", "Назад"), + ] + + class FlashDone(Message): + def __init__(self, success: bool) -> None: + super().__init__() + self.success = success + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + self._flasher = Flasher() + self._flashing = False + + def compose(self) -> ComposeResult: + with Vertical(): + yield Label( + "⚡ Прошивка платы — BootROM SDP обнаружен", + id="flash-title", + ) + + with Vertical(id="flash-target-group"): + yield Label("Что прошить?", classes="section-title") + with RadioSet(id="flash-radio"): + yield RadioButton( + "firmware_test (диагностическая прошивка)", + id="radio-fw-test", + value=True, + ) + yield RadioButton( + "Production (bootloader + tft_app)", + id="radio-production", + ) + yield RadioButton( + "Кастомный бинарь...", + id="radio-custom", + ) + with Horizontal(id="flash-custom-path", classes="hidden"): + yield Input( + placeholder="Путь к HAB-бинарю (.bin)", + id="flash-custom-input", + ) + + with Horizontal(id="flash-btn-row"): + yield Button( + "▶ Прошить", + id="flash-btn-flash", + variant="warning", + ) + yield Button( + "⚠ Chip Erase", + id="flash-btn-erase", + variant="error", + ) + + yield ProgressBar(id="flash-progress-bar", show_eta=False) + yield Label("", id="flash-progress-label") + yield Log(id="flash-log", auto_scroll=True) + + # ── Обработчики ─────────────────────────────────────────────────────────── + + @on(RadioSet.Changed, "#flash-radio") + def _on_radio_changed(self, event: RadioSet.Changed) -> None: + is_custom = event.pressed.id == "radio-custom" + path_row = self.query_one("#flash-custom-path") + if is_custom: + path_row.remove_class("hidden") + else: + path_row.add_class("hidden") + + @on(Button.Pressed, "#flash-btn-flash") + def _on_flash_pressed(self) -> None: + if self._flashing: + return + target, bin_path = self._resolve_target() + if target is None: + self._log("⚠ Укажите путь к бинарю") + return + self._do_flash(target, bin_path) + + @on(Button.Pressed, "#flash-btn-erase") + def _on_erase_pressed(self) -> None: + if self._flashing: + return + self._do_erase() + + def action_go_back(self) -> None: + if not self._flashing: + self.post_message(self.FlashDone(success=False)) + + # ── Workers ─────────────────────────────────────────────────────────────── + + @work(exclusive=True, thread=False) + async def _do_flash(self, target: FlashTarget, bin_path: Optional[Path]) -> None: + self._set_busy(True) + self._log(f"▶ Прошивка: {target.value}") + ok = await self._flasher.flash( + target=target, + bin_path=bin_path, + progress_cb=self._on_progress, + ) + self._set_busy(False) + self._log("✅ Готово" if ok else "❌ Ошибка") + self.post_message(self.FlashDone(success=ok)) + + @work(exclusive=True, thread=False) + async def _do_erase(self) -> None: + self._set_busy(True) + self._log("⚠ Chip erase (~30 с)...") + ok = await self._flasher.erase_chip(progress_cb=self._on_progress) + self._set_busy(False) + self._log("✅ Chip erase завершён" if ok else "❌ Chip erase: ошибка") + + # ── Вспомогательные ─────────────────────────────────────────────────────── + + def _resolve_target(self) -> tuple[Optional[FlashTarget], Optional[Path]]: + radio = self.query_one("#flash-radio", RadioSet) + pressed_id = radio.pressed_button.id if radio.pressed_button else None + + if pressed_id == "radio-fw-test": + return FlashTarget.FIRMWARE_TEST, None + if pressed_id == "radio-production": + return FlashTarget.PRODUCTION, None + if pressed_id == "radio-custom": + raw = self.query_one("#flash-custom-input", Input).value.strip() + if not raw: + return None, None + p = Path(raw) + if not p.exists(): + self._log(f"⚠ Файл не найден: {p}") + return None, None + return FlashTarget.CUSTOM, p + return None, None + + async def _on_progress(self, progress: FlashProgress) -> None: + self.query_one("#flash-progress-bar", ProgressBar).update( + progress=progress.percent + ) + self.query_one("#flash-progress-label", Label).update( + f"{progress.phase} {progress.percent}%" + ) + self._log(progress.message) + + def _set_busy(self, busy: bool) -> None: + self._flashing = busy + self.query_one("#flash-btn-flash", Button).disabled = busy + self.query_one("#flash-btn-erase", Button).disabled = busy + + def _log(self, msg: str) -> None: + try: + self.query_one("#flash-log", Log).write_line(msg) + except NoMatches: + pass diff --git a/tools/production/app/screens/waiting.py b/tools/production/app/screens/waiting.py new file mode 100644 index 0000000..379eb1a --- /dev/null +++ b/tools/production/app/screens/waiting.py @@ -0,0 +1,83 @@ +""" +waiting.py — экран ожидания подключения платы. + +Опрашивает USB каждые _DETECT_INTERVAL_S секунд через Flasher. +При обнаружении SDP или CDC отправляет DeviceDetected message в App. +""" + +from __future__ import annotations + +from textual.app import ComposeResult +from textual.css.query import NoMatches +from textual.screen import Screen +from textual.timer import Timer +from textual.widgets import Static +from textual.message import Message +from ..flasher import Flasher +from ..models import AppMode + +_DETECT_INTERVAL_S = 1.5 +_SPIN_INTERVAL_S = 0.1 +_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] + + +class WaitingScreen(Screen): + """ + Экран ожидания. + + Messages: + DeviceDetected(mode) — плата обнаружена, mode: FLASHING | DIAGNOSING + """ + + class DeviceDetected(Message): + """Плата обнаружена.""" + + def __init__(self, mode: AppMode) -> None: + super().__init__() + self.mode = mode + + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + self._spinner_idx: int = 0 + self._detect_timer: Timer | None = None + self._spin_timer: Timer | None = None + + def compose(self) -> ComposeResult: + yield Static("TFT Indicator Board\nService Tool", id="waiting-logo") + yield Static("Подключите плату к USB...", id="waiting-hint") + yield Static(_SPINNER_FRAMES[0], id="waiting-spinner") + + def on_mount(self) -> None: + self._detect_timer = self.set_interval(_DETECT_INTERVAL_S, self._poll_usb) + self._spin_timer = self.set_interval(_SPIN_INTERVAL_S, self._spin) + + def on_unmount(self) -> None: + if self._detect_timer: + self._detect_timer.stop() + if self._spin_timer: + self._spin_timer.stop() + + # ── Internal ────────────────────────────────────────────────────────────── + + def _spin(self) -> None: + self._spinner_idx = (self._spinner_idx + 1) % len(_SPINNER_FRAMES) + try: + self.query_one("#waiting-spinner", Static).update( + _SPINNER_FRAMES[self._spinner_idx] + ) + except NoMatches: + pass + + def _poll_usb(self) -> None: + if Flasher.detect_sdp(): + self._stop_timers() + self.post_message(self.DeviceDetected(AppMode.FLASHING)) + elif Flasher.detect_cdc(): + self._stop_timers() + self.post_message(self.DeviceDetected(AppMode.DIAGNOSING)) + + def _stop_timers(self) -> None: + if self._detect_timer: + self._detect_timer.stop() + if self._spin_timer: + self._spin_timer.stop() diff --git a/tools/production/main.py b/tools/production/main.py index e69de29..10ec203 100644 --- a/tools/production/main.py +++ b/tools/production/main.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""main.py — точка входа service-tui.""" + +from __future__ import annotations + +import logging +import os +import sys +from pathlib import Path + +_REPO_ROOT = Path(__file__).resolve().parents[2] +_ENV_FILE = _REPO_ROOT / ".env" + +try: + from dotenv import load_dotenv + + if _ENV_FILE.exists(): + load_dotenv(_ENV_FILE) +except ImportError: + pass + + +def _setup_logging() -> None: + log_dir = Path(os.environ.get("SERVICE_LOG_DIR", str(Path(__file__).parent))) + log_file = log_dir / "service_tui.log" + logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s %(levelname)-8s %(name)s %(message)s", + handlers=[logging.FileHandler(log_file, encoding="utf-8")], + ) + logging.getLogger("textual").setLevel(logging.WARNING) + + +def main() -> None: + _setup_logging() + from app.app import ServiceApp + + ServiceApp().run() + + +if __name__ == "__main__": + sys.exit(main() or 0) diff --git a/tools/production/pyproject.toml b/tools/production/pyproject.toml index b7f6ac8..20b5d72 100644 --- a/tools/production/pyproject.toml +++ b/tools/production/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [ "pyserial>=3.5", "python-dotenv>=1.0.0", "pyinstaller>=6.0.0", + "pyusb>=1.0.0", ] [project.scripts] diff --git a/tools/production/service_tui.log b/tools/production/service_tui.log new file mode 100644 index 0000000..ead3ed2 --- /dev/null +++ b/tools/production/service_tui.log @@ -0,0 +1,662 @@ +2026-06-29 15:20:18,732 DEBUG asyncio Using selector: EpollSelector +2026-06-29 18:21:10,465 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:21:14,987 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:21:53,409 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:22:16,437 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:28:38,572 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:36:00,529 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:36:17,980 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --erase-chip +2026-06-29 18:36:18,002 DEBUG app.flasher flash_usb [erase]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 18:36:18,002 DEBUG app.flasher flash_usb [erase]: warning: Ignoring existing virtual environment linked to non-existent Python interpreter: .venv/bin/python3 -> python +2026-06-29 18:36:18,046 DEBUG app.flasher flash_usb [erase]: Using CPython 3.10.11 interpreter at: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10 +2026-06-29 18:36:18,224 DEBUG app.flasher flash_usb [erase]: Removed virtual environment at: .venv +2026-06-29 18:36:18,224 DEBUG app.flasher flash_usb [erase]: Creating virtual environment at: .venv +2026-06-29 18:36:18,344 DEBUG app.flasher flash_usb [erase]: Installed 66 packages in 93ms +2026-06-29 18:36:23,001 DEBUG app.flasher flash_usb [erase]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 18:36:23,001 DEBUG app.flasher flash_usb [erase]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 18:36:23,177 DEBUG app.flasher flash_usb [erase]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: ════════════════════════════════════════════════════════════ +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: Операция: chip erase +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: SDP USB: 0x1FC9,0x130 +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: BL USB: 0x15A2,0x73 +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: ════════════════════════════════════════════════════════════ +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 18:36:23,197 DEBUG app.flasher flash_usb [erase]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 18:36:23,198 DEBUG app.flasher flash_usb [erase]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 18:36:23,198 DEBUG app.flasher flash_usb [erase]: +2026-06-29 18:36:24,586 DEBUG app.flasher flash_usb [erase]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 18:36:24,760 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 18:36:59,315 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 18:36:59,571 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 18:36:59,591 DEBUG app.flasher flash_usb [erase]: OK (1с) +2026-06-29 18:36:59,591 DEBUG app.flasher flash_usb [erase]: +2026-06-29 18:36:59,591 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 18:36:59,591 DEBUG app.flasher flash_usb [erase]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 18:36:59,591 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 18:36:59,591 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 18:36:59,591 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: Полная очистка Flash (chip erase, ~30 с) +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: ⚠️ После chip erase BootROM не сможет загрузить прошивку. +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: ⚠️ Используй flash_usb.py для восстановления. +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: $ blhost -t 100000 -u 0x15A2,0x73 -- flash-erase-all 9 +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: Reset +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: +2026-06-29 18:36:59,592 DEBUG app.flasher flash_usb [erase]: ✅ Chip erase завершён +2026-06-29 18:37:20,822 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --firmware firmware_test --build-type Release +2026-06-29 18:37:20,836 DEBUG app.flasher flash_usb [firmware_test]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 18:37:21,381 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 18:37:21,381 DEBUG app.flasher flash_usb [firmware_test]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 18:37:21,559 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: Прошивка: firmware_test [Release] +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Release/firmware_test_hab.bin +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: SDP USB: 0x1FC9,0x130 +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: BL USB: 0x15A2,0x73 +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:21,582 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 18:37:21,583 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 18:37:21,583 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:22,972 DEBUG app.flasher flash_usb [firmware_test]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 18:37:23,148 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:23,979 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:24,183 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:24,357 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:24,534 DEBUG app.flasher flash_usb [firmware_test]: Writing memory +2026-06-29 18:37:24,594 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:24,594 DEBUG app.flasher flash_usb [firmware_test]: Response word 1 = 22616 (0x5858) +2026-06-29 18:37:24,779 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:24,798 DEBUG app.flasher flash_usb [firmware_test]: OK (1с) +2026-06-29 18:37:24,798 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:24,798 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:24,798 DEBUG app.flasher flash_usb [firmware_test]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 18:37:24,798 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:24,798 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: Прошивка Flash: firmware_test_hab.bin +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Release/firmware_test_hab.bin +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: Размер: 22616 байт +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: Адрес: 0x60001000 +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: Стирание: 0x60000000 .. +28672 байт +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- flash-erase-region 0x60000000 28672 0 +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: Запись FCB в Flash[0x60000000] +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xF000000F word +2026-06-29 18:37:24,799 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 18:37:24,800 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- write-memory 0x60001000 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Release/firmware_test_hab.bin 0 +2026-06-29 18:37:24,800 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:24,800 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:24,800 DEBUG app.flasher flash_usb [firmware_test]: Reset +2026-06-29 18:37:24,800 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:24,800 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 18:37:24,800 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:24,800 DEBUG app.flasher flash_usb [firmware_test]: ✅ Прошивка завершена успешно +2026-06-29 18:37:46,867 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --firmware firmware_test --build-type Release +2026-06-29 18:37:46,883 DEBUG app.flasher flash_usb [firmware_test]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 18:37:47,377 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 18:37:47,377 DEBUG app.flasher flash_usb [firmware_test]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 18:37:47,548 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: Прошивка: firmware_test [Release] +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Release/firmware_test_hab.bin +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: SDP USB: 0x1FC9,0x130 +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: BL USB: 0x15A2,0x73 +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:47,568 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 18:37:47,569 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 18:37:47,569 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:48,972 DEBUG app.flasher flash_usb [firmware_test]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 18:37:49,154 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:49,987 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:50,206 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:50,386 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:50,560 DEBUG app.flasher flash_usb [firmware_test]: Writing memory +2026-06-29 18:37:50,621 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:50,621 DEBUG app.flasher flash_usb [firmware_test]: Response word 1 = 22616 (0x5858) +2026-06-29 18:37:50,800 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:37:50,818 DEBUG app.flasher flash_usb [firmware_test]: OK (1с) +2026-06-29 18:37:50,818 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:50,818 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:50,818 DEBUG app.flasher flash_usb [firmware_test]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 18:37:50,818 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:50,818 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: Прошивка Flash: firmware_test_hab.bin +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Release/firmware_test_hab.bin +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: Размер: 22616 байт +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: Адрес: 0x60001000 +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: Стирание: 0x60000000 .. +28672 байт +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- flash-erase-region 0x60000000 28672 0 +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: Запись FCB в Flash[0x60000000] +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xF000000F word +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- write-memory 0x60001000 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Release/firmware_test_hab.bin 0 +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:50,819 DEBUG app.flasher flash_usb [firmware_test]: Reset +2026-06-29 18:37:50,820 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:37:50,820 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 18:37:50,820 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:37:50,820 DEBUG app.flasher flash_usb [firmware_test]: ✅ Прошивка завершена успешно +2026-06-29 18:38:46,907 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:40:26,915 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:40:28,552 INFO app.m5_client M5StampPLC не найден +2026-06-29 18:41:26,213 ERROR app.orchestrator HIL confirm без M5: opto_in1_active +2026-06-29 18:44:38,127 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:44:39,837 INFO app.m5_client M5StampPLC не найден +2026-06-29 18:45:28,221 ERROR app.orchestrator HIL confirm без M5: opto_in1_active +2026-06-29 18:46:57,397 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:46:58,957 INFO app.m5_client M5StampPLC не найден +2026-06-29 18:50:48,270 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:51:01,877 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --firmware firmware_test --build-type Release +2026-06-29 18:51:01,900 DEBUG app.flasher flash_usb [firmware_test]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 18:51:01,900 DEBUG app.flasher flash_usb [firmware_test]: warning: Ignoring existing virtual environment linked to non-existent Python interpreter: .venv/bin/python3 -> python +2026-06-29 18:51:01,900 DEBUG app.flasher flash_usb [firmware_test]: Using CPython 3.10.11 interpreter at: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10 +2026-06-29 18:51:02,032 DEBUG app.flasher flash_usb [firmware_test]: Removed virtual environment at: .venv +2026-06-29 18:51:02,032 DEBUG app.flasher flash_usb [firmware_test]: Creating virtual environment at: .venv +2026-06-29 18:51:02,130 DEBUG app.flasher flash_usb [firmware_test]: Installed 66 packages in 75ms +2026-06-29 18:51:06,727 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 18:51:06,727 DEBUG app.flasher flash_usb [firmware_test]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 18:51:06,903 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 18:51:06,923 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:51:06,923 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 18:51:06,923 DEBUG app.flasher flash_usb [firmware_test]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 18:51:06,923 DEBUG app.flasher flash_usb [firmware_test]: Прошивка: firmware_test [Release] +2026-06-29 18:51:06,923 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Release/firmware_test_hab.bin +2026-06-29 18:51:06,923 DEBUG app.flasher flash_usb [firmware_test]: SDP USB: 0x1FC9,0x130 +2026-06-29 18:51:06,923 DEBUG app.flasher flash_usb [firmware_test]: BL USB: 0x15A2,0x73 +2026-06-29 18:51:06,923 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 18:51:06,923 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:51:06,924 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:51:06,924 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:06,924 DEBUG app.flasher flash_usb [firmware_test]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 18:51:06,924 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:06,924 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 18:51:06,924 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 18:51:06,924 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:51:08,331 DEBUG app.flasher flash_usb [firmware_test]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 18:51:08,506 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:51:09,215 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:51:09,429 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:51:09,621 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:51:09,804 DEBUG app.flasher flash_usb [firmware_test]: Writing memory +2026-06-29 18:51:09,985 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:51:09,985 DEBUG app.flasher flash_usb [firmware_test]: Response word 1 = 72116 (0x119b4) +2026-06-29 18:51:10,173 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 18:51:10,191 DEBUG app.flasher flash_usb [firmware_test]: OK (1с) +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: Прошивка Flash: firmware_test_hab.bin +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Release/firmware_test_hab.bin +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: Размер: 72116 байт +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: Адрес: 0x60001000 +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: Стирание: 0x60000000 .. +77824 байт +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- flash-erase-region 0x60000000 77824 0 +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: Запись FCB в Flash[0x60000000] +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:10,192 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xF000000F word +2026-06-29 18:51:10,193 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 18:51:10,193 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- write-memory 0x60001000 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Release/firmware_test_hab.bin 0 +2026-06-29 18:51:10,193 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:51:10,193 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:10,193 DEBUG app.flasher flash_usb [firmware_test]: Reset +2026-06-29 18:51:10,193 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 18:51:10,193 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 18:51:10,193 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 18:51:10,193 DEBUG app.flasher flash_usb [firmware_test]: ✅ Прошивка завершена успешно +2026-06-29 18:51:36,969 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:57:30,698 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 18:57:32,409 INFO app.m5_client M5StampPLC не найден +2026-06-29 19:00:37,411 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 19:00:38,953 INFO app.m5_client M5StampPLC не найден +2026-06-29 19:04:04,441 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 19:04:12,903 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --erase-chip +2026-06-29 19:04:12,918 DEBUG app.flasher flash_usb [erase]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 19:04:12,919 DEBUG app.flasher flash_usb [erase]: warning: Ignoring existing virtual environment linked to non-existent Python interpreter: .venv/bin/python3 -> python +2026-06-29 19:04:12,921 DEBUG app.flasher flash_usb [erase]: Using CPython 3.10.11 interpreter at: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10 +2026-06-29 19:04:13,106 DEBUG app.flasher flash_usb [erase]: Removed virtual environment at: .venv +2026-06-29 19:04:13,106 DEBUG app.flasher flash_usb [erase]: Creating virtual environment at: .venv +2026-06-29 19:04:13,225 DEBUG app.flasher flash_usb [erase]: Installed 66 packages in 93ms +2026-06-29 19:04:17,826 DEBUG app.flasher flash_usb [erase]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:04:17,826 DEBUG app.flasher flash_usb [erase]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 19:04:18,001 DEBUG app.flasher flash_usb [erase]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:04:18,019 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:04:18,019 DEBUG app.flasher flash_usb [erase]: ════════════════════════════════════════════════════════════ +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: Операция: chip erase +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: SDP USB: 0x1FC9,0x130 +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: BL USB: 0x15A2,0x73 +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: ════════════════════════════════════════════════════════════ +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 19:04:18,020 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:04:19,412 DEBUG app.flasher flash_usb [erase]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 19:04:19,586 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 19:04:54,910 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 19:04:55,150 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 19:04:55,168 DEBUG app.flasher flash_usb [erase]: OK (1с) +2026-06-29 19:04:55,168 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:04:55,168 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:55,168 DEBUG app.flasher flash_usb [erase]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 19:04:55,168 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:55,168 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 19:04:55,169 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:04:55,169 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:04:55,169 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:55,169 DEBUG app.flasher flash_usb [erase]: Полная очистка Flash (chip erase, ~30 с) +2026-06-29 19:04:55,169 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:55,169 DEBUG app.flasher flash_usb [erase]: ⚠️ После chip erase BootROM не сможет загрузить прошивку. +2026-06-29 19:04:55,169 DEBUG app.flasher flash_usb [erase]: ⚠️ Используй flash_usb.py для восстановления. +2026-06-29 19:04:55,170 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:04:55,170 DEBUG app.flasher flash_usb [erase]: $ blhost -t 100000 -u 0x15A2,0x73 -- flash-erase-all 9 +2026-06-29 19:04:55,170 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:04:55,170 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:55,170 DEBUG app.flasher flash_usb [erase]: Reset +2026-06-29 19:04:55,170 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:55,170 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 19:04:55,170 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:04:55,170 DEBUG app.flasher flash_usb [erase]: ✅ Chip erase завершён +2026-06-29 19:04:58,515 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --firmware firmware_test --build-type Debug +2026-06-29 19:04:58,542 DEBUG app.flasher flash_usb [firmware_test]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 19:04:58,955 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:04:58,955 DEBUG app.flasher flash_usb [firmware_test]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 19:04:59,131 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:04:59,150 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: Прошивка: firmware_test [Debug] +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: SDP USB: 0x1FC9,0x130 +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: BL USB: 0x15A2,0x73 +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 19:04:59,151 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:05:00,537 DEBUG app.flasher flash_usb [firmware_test]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 19:05:00,713 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:05:01,631 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:05:01,832 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:05:02,011 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:05:02,191 DEBUG app.flasher flash_usb [firmware_test]: Writing memory +2026-06-29 19:05:02,527 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:05:02,527 DEBUG app.flasher flash_usb [firmware_test]: Response word 1 = 135240 (0x21048) +2026-06-29 19:05:02,734 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:05:02,752 DEBUG app.flasher flash_usb [firmware_test]: OK (1с) +2026-06-29 19:05:02,752 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:05:02,752 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:05:02,752 DEBUG app.flasher flash_usb [firmware_test]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 19:05:02,752 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:05:02,752 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 19:05:02,752 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: Прошивка Flash: firmware_test_hab.bin +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: Размер: 135240 байт +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: Адрес: 0x60001000 +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: Стирание: 0x60000000 .. +143360 байт +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- flash-erase-region 0x60000000 143360 0 +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: Запись FCB в Flash[0x60000000] +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xF000000F word +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- write-memory 0x60001000 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin 0 +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: Reset +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 19:05:02,753 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:05:02,754 DEBUG app.flasher flash_usb [firmware_test]: ✅ Прошивка завершена успешно +2026-06-29 19:05:23,969 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 19:05:25,545 INFO app.m5_client M5StampPLC не найден +2026-06-29 19:07:57,549 WARNING app.firmware_client _recv_until timeout after 120.0 s +2026-06-29 19:17:54,908 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 19:18:31,735 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --erase-chip +2026-06-29 19:18:31,752 DEBUG app.flasher flash_usb [erase]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 19:18:31,752 DEBUG app.flasher flash_usb [erase]: warning: Ignoring existing virtual environment linked to non-existent Python interpreter: .venv/bin/python3 -> python +2026-06-29 19:18:31,755 DEBUG app.flasher flash_usb [erase]: Using CPython 3.10.11 interpreter at: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10 +2026-06-29 19:18:31,934 DEBUG app.flasher flash_usb [erase]: Removed virtual environment at: .venv +2026-06-29 19:18:31,935 DEBUG app.flasher flash_usb [erase]: Creating virtual environment at: .venv +2026-06-29 19:18:32,066 DEBUG app.flasher flash_usb [erase]: Installed 66 packages in 98ms +2026-06-29 19:18:36,600 DEBUG app.flasher flash_usb [erase]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:18:36,600 DEBUG app.flasher flash_usb [erase]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 19:18:36,776 DEBUG app.flasher flash_usb [erase]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: ════════════════════════════════════════════════════════════ +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: Операция: chip erase +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: SDP USB: 0x1FC9,0x130 +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: BL USB: 0x15A2,0x73 +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: ════════════════════════════════════════════════════════════ +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 19:18:36,796 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:18:36,797 DEBUG app.flasher flash_usb [erase]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 19:18:36,797 DEBUG app.flasher flash_usb [erase]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 19:18:36,797 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:18:38,238 DEBUG app.flasher flash_usb [erase]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 19:18:38,414 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 19:19:13,145 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 19:19:13,403 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 19:19:13,422 DEBUG app.flasher flash_usb [erase]: OK (1с) +2026-06-29 19:19:13,422 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:19:13,422 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:19:13,422 DEBUG app.flasher flash_usb [erase]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 19:19:13,422 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:19:13,422 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 19:19:13,423 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:19:13,423 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:19:13,423 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:19:13,423 DEBUG app.flasher flash_usb [erase]: Полная очистка Flash (chip erase, ~30 с) +2026-06-29 19:19:13,423 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:19:13,423 DEBUG app.flasher flash_usb [erase]: ⚠️ После chip erase BootROM не сможет загрузить прошивку. +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: ⚠️ Используй flash_usb.py для восстановления. +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: $ blhost -t 100000 -u 0x15A2,0x73 -- flash-erase-all 9 +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: Reset +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:19:13,424 DEBUG app.flasher flash_usb [erase]: ✅ Chip erase завершён +2026-06-29 19:20:01,708 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --firmware firmware_test --build-type Debug +2026-06-29 19:20:01,724 DEBUG app.flasher flash_usb [firmware_test]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 19:20:02,300 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:20:02,300 DEBUG app.flasher flash_usb [firmware_test]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 19:20:02,473 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: Прошивка: firmware_test [Debug] +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: SDP USB: 0x1FC9,0x130 +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: BL USB: 0x15A2,0x73 +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 19:20:02,493 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:20:03,887 DEBUG app.flasher flash_usb [firmware_test]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 19:20:04,068 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:20:04,968 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:20:05,182 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:20:05,355 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:20:05,534 DEBUG app.flasher flash_usb [firmware_test]: Writing memory +2026-06-29 19:20:05,873 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:20:05,873 DEBUG app.flasher flash_usb [firmware_test]: Response word 1 = 135240 (0x21048) +2026-06-29 19:20:06,062 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: OK (1с) +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: Прошивка Flash: firmware_test_hab.bin +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: Размер: 135240 байт +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: Адрес: 0x60001000 +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: Стирание: 0x60000000 .. +143360 байт +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- flash-erase-region 0x60000000 143360 0 +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: Запись FCB в Flash[0x60000000] +2026-06-29 19:20:06,081 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xF000000F word +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- write-memory 0x60001000 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin 0 +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: Reset +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:20:06,082 DEBUG app.flasher flash_usb [firmware_test]: ✅ Прошивка завершена успешно +2026-06-29 19:20:59,572 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --firmware firmware_test --build-type Debug +2026-06-29 19:20:59,595 DEBUG app.flasher flash_usb [firmware_test]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 19:21:00,152 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:21:00,152 DEBUG app.flasher flash_usb [firmware_test]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 19:21:00,330 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:21:00,350 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:21:00,350 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 19:21:00,350 DEBUG app.flasher flash_usb [firmware_test]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 19:21:00,350 DEBUG app.flasher flash_usb [firmware_test]: Прошивка: firmware_test [Debug] +2026-06-29 19:21:00,350 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin +2026-06-29 19:21:00,350 DEBUG app.flasher flash_usb [firmware_test]: SDP USB: 0x1FC9,0x130 +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: BL USB: 0x15A2,0x73 +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 19:21:00,351 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:21:01,734 DEBUG app.flasher flash_usb [firmware_test]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 19:21:01,904 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:21:02,832 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:21:03,049 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:21:03,224 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:21:03,447 DEBUG app.flasher flash_usb [firmware_test]: Writing memory +2026-06-29 19:21:03,786 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:21:03,787 DEBUG app.flasher flash_usb [firmware_test]: Response word 1 = 135240 (0x21048) +2026-06-29 19:21:04,008 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:21:04,028 DEBUG app.flasher flash_usb [firmware_test]: OK (1с) +2026-06-29 19:21:04,028 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:21:04,028 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:04,028 DEBUG app.flasher flash_usb [firmware_test]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 19:21:04,028 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:04,028 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: Прошивка Flash: firmware_test_hab.bin +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: Размер: 135240 байт +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: Адрес: 0x60001000 +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: Стирание: 0x60000000 .. +143360 байт +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- flash-erase-region 0x60000000 143360 0 +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: Запись FCB в Flash[0x60000000] +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xF000000F word +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- write-memory 0x60001000 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin 0 +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: Reset +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:21:04,029 DEBUG app.flasher flash_usb [firmware_test]: ✅ Прошивка завершена успешно +2026-06-29 19:21:20,423 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --erase-chip +2026-06-29 19:21:20,437 DEBUG app.flasher flash_usb [erase]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 19:21:20,936 DEBUG app.flasher flash_usb [erase]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:21:20,936 DEBUG app.flasher flash_usb [erase]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 19:21:21,112 DEBUG app.flasher flash_usb [erase]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: ════════════════════════════════════════════════════════════ +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: Операция: chip erase +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: SDP USB: 0x1FC9,0x130 +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: BL USB: 0x15A2,0x73 +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: ════════════════════════════════════════════════════════════ +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:21,133 DEBUG app.flasher flash_usb [erase]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 19:21:21,134 DEBUG app.flasher flash_usb [erase]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 19:21:21,134 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:21:22,510 DEBUG app.flasher flash_usb [erase]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 19:21:22,685 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 19:21:57,783 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 19:21:58,044 DEBUG app.flasher flash_usb [erase]: Response status = 0 (0x0) Success. +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: OK (1с) +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: Полная очистка Flash (chip erase, ~30 с) +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: ⚠️ После chip erase BootROM не сможет загрузить прошивку. +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: ⚠️ Используй flash_usb.py для восстановления. +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:21:58,064 DEBUG app.flasher flash_usb [erase]: $ blhost -t 100000 -u 0x15A2,0x73 -- flash-erase-all 9 +2026-06-29 19:21:58,065 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:21:58,065 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:58,065 DEBUG app.flasher flash_usb [erase]: Reset +2026-06-29 19:21:58,065 DEBUG app.flasher flash_usb [erase]: ──────────────────────────────────────────────────────────── +2026-06-29 19:21:58,065 DEBUG app.flasher flash_usb [erase]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 19:21:58,065 DEBUG app.flasher flash_usb [erase]: +2026-06-29 19:21:58,065 DEBUG app.flasher flash_usb [erase]: ✅ Chip erase завершён +2026-06-29 19:31:03,319 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 19:31:12,383 INFO app.flasher Running: uv run --directory /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host python /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/flash_usb.py --firmware firmware_test --build-type Debug +2026-06-29 19:31:12,399 DEBUG app.flasher flash_usb [firmware_test]: warning: `VIRTUAL_ENV=/Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/production/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead +2026-06-29 19:31:12,928 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:31:12,928 DEBUG app.flasher flash_usb [firmware_test]: Response status = 2290649224 (0x88888888) Write File Success. +2026-06-29 19:31:13,096 DEBUG app.flasher flash_usb [firmware_test]: Status (HAB mode) = 1450735702 (0x56787856) Hab Is Disabled (Unlocked). +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: MIMXRT1052 Flash Tool (USB SDP) +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: Прошивка: firmware_test [Debug] +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: SDP USB: 0x1FC9,0x130 +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: BL USB: 0x15A2,0x73 +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: ════════════════════════════════════════════════════════════ +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: Загрузка Flashloader через SDP (1FC9:0130) +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:13,117 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- write-file 0x20001C00 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/host/dcd/ivt_flashloader.bin +2026-06-29 19:31:13,118 DEBUG app.flasher flash_usb [firmware_test]: $ sdphost -u 0x1FC9,0x130 -j -- jump-address 0x20001C00 +2026-06-29 19:31:13,118 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:31:14,509 DEBUG app.flasher flash_usb [firmware_test]: Ожидание Flashloader (до 10с)...Response status = 0 (0x0) Success. +2026-06-29 19:31:14,679 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:31:15,624 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:31:15,830 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:31:16,009 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:31:16,183 DEBUG app.flasher flash_usb [firmware_test]: Writing memory +2026-06-29 19:31:16,518 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:31:16,518 DEBUG app.flasher flash_usb [firmware_test]: Response word 1 = 135240 (0x21048) +2026-06-29 19:31:16,724 DEBUG app.flasher flash_usb [firmware_test]: Response status = 0 (0x0) Success. +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: OK (1с) +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: Конфигурация FlexSPI NOR (инициализация контроллера) +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xC0000007 word +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: Прошивка Flash: firmware_test_hab.bin +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: Образ: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: Размер: 135240 байт +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: Адрес: 0x60001000 +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: Стирание: 0x60000000 .. +143360 байт +2026-06-29 19:31:16,744 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- flash-erase-region 0x60000000 143360 0 +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: Запись FCB в Flash[0x60000000] +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- fill-memory 0x2000 4 0xF000000F word +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- configure-memory 9 0x2000 +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- write-memory 0x60001000 /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin 0 +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: Reset +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: ──────────────────────────────────────────────────────────── +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: $ blhost -u 0x15A2,0x73 -- reset +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: +2026-06-29 19:31:16,745 DEBUG app.flasher flash_usb [firmware_test]: ✅ Прошивка завершена успешно +2026-06-29 19:31:45,595 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 19:31:54,684 INFO app.m5_client M5StampPLC не найден +2026-06-29 19:31:57,741 WARNING app.firmware_client _recv_until timeout after 3.0 s +2026-06-29 19:34:42,219 ERROR app.orchestrator HIL confirm без M5: opto_in1_active +2026-06-29 19:36:35,663 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 19:36:37,208 INFO app.m5_client M5StampPLC не найден +2026-06-29 19:36:40,255 WARNING app.firmware_client _recv_until timeout after 3.0 s +2026-06-29 19:36:47,180 DEBUG asyncio Using selector: KqueueSelector +2026-06-29 19:41:53,210 INFO app.m5_client M5StampPLC не найден +2026-06-29 19:41:56,267 WARNING app.firmware_client _recv_until timeout after 3.0 s diff --git a/tools/production/uv.lock b/tools/production/uv.lock new file mode 100644 index 0000000..b041381 --- /dev/null +++ b/tools/production/uv.lock @@ -0,0 +1,264 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "altgraph" +version = "0.17.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" }, +] + +[[package]] +name = "linkify-it-py" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "uc-micro-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/c9/06ea13676ef354f0af6169587ae292d3e2406e212876a413bf9eece4eb23/linkify_it_py-2.1.0.tar.gz", hash = "sha256:43360231720999c10e9328dc3691160e27a718e280673d444c38d7d3aaa3b98b", size = 29158, upload-time = "2026-03-01T07:48:47.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/de/88b3be5c31b22333b3ca2f6ff1de4e863d8fe45aaea7485f591970ec1d3e/linkify_it_py-2.1.0-py3-none-any.whl", hash = "sha256:0d252c1594ecba2ecedc444053db5d3a9b7ec1b0dd929c8f1d74dce89f86c05e", size = 19878, upload-time = "2026-03-01T07:48:46.098Z" }, +] + +[[package]] +name = "macholib" +version = "1.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[package.optional-dependencies] +linkify = [ + { name = "linkify-it-py" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d", size = 66663, upload-time = "2026-05-13T09:03:37.76Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pefile" +version = "2024.8.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyinstaller" +version = "6.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/4d/ec706c3fcf39e26888c35b39615ff4d5865d184069666c47492cff1fbe50/pyinstaller-6.21.0.tar.gz", hash = "sha256:bb9fab705983e393a2d1cac77d6972513057ad800215fd861dc15ff5272e98fd", size = 4061519, upload-time = "2026-06-13T14:15:06.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/4a/53cf98bf66daed012dc9cd78c8203f19a675d696f2fc12afcf8c5049a0e0/pyinstaller-6.21.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:327d132389f37912609e01be62810cf96b5aa95b613903e4b8692e0d12fb0eda", size = 1052350, upload-time = "2026-06-13T14:13:55.88Z" }, + { url = "https://files.pythonhosted.org/packages/30/83/b591295c352ef464c50b4c6ffff1c4f771d875c9e833f578d1b9f564f6b3/pyinstaller-6.21.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7071d4b094d5b40deeef5fa3d3b98a1b846087f7562b49209663d5f9281fe251", size = 748477, upload-time = "2026-06-13T14:14:00.327Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8f/88fff4e403873b1e22286911350e75ff00db014aa08e57045da9d4328993/pyinstaller-6.21.0-py3-none-manylinux2014_i686.whl", hash = "sha256:6b6374d652107dd4a2eeece903ff82bb4045bb5e1006c5a158a6dcdbefe84bf2", size = 760877, upload-time = "2026-06-13T14:14:04.836Z" }, + { url = "https://files.pythonhosted.org/packages/8a/13/f0e48fbdfd1d05d948157121cea8b1b823dcb89efe6934b71fdd8bdb3f0f/pyinstaller-6.21.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4e3108b3f02384560da70e39b8bf22b0ad597d02bd68a40d76ea91c1cfa00cad", size = 759194, upload-time = "2026-06-13T14:14:10.61Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d5/ea7878cf9924ed30d946d8288777424e6d069d94f5bde56b4d0890069664/pyinstaller-6.21.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:697532279f535ad572bda613db4f821540e235c7854ca6da4d3bf0373f4415ee", size = 754979, upload-time = "2026-06-13T14:14:15.226Z" }, + { url = "https://files.pythonhosted.org/packages/9f/09/51b8905714b733bac66dbc041a7821372d70d888d273ae474c4037d4202d/pyinstaller-6.21.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:605169523a6b5ace39f13dfbff21add9f2bc43df99c7daf9394fefb2c45e8b6f", size = 754812, upload-time = "2026-06-13T14:14:20.264Z" }, + { url = "https://files.pythonhosted.org/packages/4b/43/d77779439d8c6c2e27a77bcfbd1d5cc0f568ebb611bb472b11af81b5f177/pyinstaller-6.21.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:5fa56746c1e76f93634d018502301378a2d0c382553d37d8c3c34ff436c12dd1", size = 753887, upload-time = "2026-06-13T14:14:25.268Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/c22df1f6837784ac349057ba693f08e7b1ca7a0e06f9c33c63bc6280007b/pyinstaller-6.21.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:42395ec76df8e8120c36b13339d9db8cab83e316a12839ee303cc00fc941bb74", size = 753779, upload-time = "2026-06-13T14:14:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/c9/76/1ce8a27ce62ba8cf3a87c9ce6d575610f4e55d7cb0123e7512fc3f4b921a/pyinstaller-6.21.0-py3-none-win32.whl", hash = "sha256:c6b28d30d8fd99ce162ff3aab5013ed44dbfb747566b1f01b9bed7964d7c14e9", size = 1336462, upload-time = "2026-06-13T14:14:35.785Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/ca1d7e5257dd8566a9dfc0dfb02f8a8075eeb53d4b2d3c579f1276759042/pyinstaller-6.21.0-py3-none-win_amd64.whl", hash = "sha256:7fae06c494ce0ebfe6bd3055c0e409def884f63af2e3705d06bd431ad9237fc7", size = 1397487, upload-time = "2026-06-13T14:14:42.328Z" }, + { url = "https://files.pythonhosted.org/packages/dc/75/21b51523ce8d96629b71311775a0a65f5f5a872124ab0de33e5c848f8bff/pyinstaller-6.21.0-py3-none-win_arm64.whl", hash = "sha256:f13c95c9c03fb567217135919f93815c305813126780b0ed6e0123cb8acaf025", size = 1346094, upload-time = "2026-06-13T14:14:48.914Z" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2026.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/5b/c9fe0db5e83ee1c39b2258fa21d23b15e1a60786b6c5990ee5074ead8bb6/pyinstaller_hooks_contrib-2026.6.tar.gz", hash = "sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725", size = 173354, upload-time = "2026-06-08T22:37:16.152Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/31/f2d7343d8ed5f7c4678377886f6ce533e6eaaa131b252ce950114c2a7efa/pyinstaller_hooks_contrib-2026.6-py3-none-any.whl", hash = "sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3", size = 457159, upload-time = "2026-06-08T22:37:14.722Z" }, +] + +[[package]] +name = "pyserial" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "pyusb" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/6b/ce3727395e52b7b76dfcf0c665e37d223b680b9becc60710d4bc08b7b7cb/pyusb-1.3.1.tar.gz", hash = "sha256:3af070b607467c1c164f49d5b0caabe8ac78dbed9298d703a8dbf9df4052d17e", size = 77281, upload-time = "2025-01-08T23:45:01.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl", hash = "sha256:bf9b754557af4717fe80c2b07cc2b923a9151f5c08d17bdb5345dac09d6a0430", size = 58465, upload-time = "2025-01-08T23:45:00.029Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "service-tui" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pyinstaller" }, + { name = "pyserial" }, + { name = "python-dotenv" }, + { name = "pyusb" }, + { name = "textual" }, +] + +[package.metadata] +requires-dist = [ + { name = "pyinstaller", specifier = ">=6.0.0" }, + { name = "pyserial", specifier = ">=3.5" }, + { name = "python-dotenv", specifier = ">=1.0.0" }, + { name = "pyusb", specifier = ">=1.0.0" }, + { name = "textual", specifier = ">=0.80.0" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "textual" +version = "8.2.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", extra = ["linkify"] }, + { name = "mdit-py-plugins" }, + { name = "platformdirs" }, + { name = "pygments" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/7a/c519db0aba5024f86e71e9631810bfdd6866ed2c8695bd7fa34b90e7ef59/textual-8.2.7.tar.gz", hash = "sha256:658f568ff81e30ed43890c3e07520390e5cf1b4763822006e060656b0a88f105", size = 1859249, upload-time = "2026-05-19T10:52:49.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/f5/c1e18bc0707300a0e90204343abbf7d7acd6fb7ebe03a6d4893b99a234b8/textual-8.2.7-py3-none-any.whl", hash = "sha256:4caaa13a90bc4cf9c6c862c067ccd34fe84e9c161710a2a907a8026313b6bd73", size = 731129, upload-time = "2026-05-19T10:52:51.773Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "uc-micro-py" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/67/9a363818028526e2d4579334460df777115bdec1bb77c08f9db88f6389f2/uc_micro_py-2.0.0.tar.gz", hash = "sha256:c53691e495c8db60e16ffc4861a35469b0ba0821fe409a8a7a0a71864d33a811", size = 6611, upload-time = "2026-03-01T06:31:27.526Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/73/d21edf5b204d1467e06500080a50f79d49ef2b997c79123a536d4a17d97c/uc_micro_py-2.0.0-py3-none-any.whl", hash = "sha256:3603a3859af53e5a39bc7677713c78ea6589ff188d70f4fee165db88e22b242c", size = 6383, upload-time = "2026-03-01T06:31:26.257Z" }, +]