# firmware_test: TUI app current state

This commit is contained in:
Dmitry Akimov 2026-06-29 19:54:44 +03:00
parent 054bbd70f5
commit 55cd9e8273
25 changed files with 3104 additions and 168 deletions

View file

@ -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(), здесь можно переопределить если нужно

View file

@ -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)

View file

@ -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 -
# инициализация некешируемое секции нулями

View file

@ -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();

View file

@ -158,3 +158,12 @@ void protocol_send_uid_response(const uint8_t *p_uid)
(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);
}

View file

@ -19,12 +19,14 @@
#define PROTOCOL_H_
#include "test_module.h"
#include "version.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/** @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_ */

View file

@ -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_ */

View file

@ -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\nRLY14 + CAN"]
end
FC <-->|"JSON-lines\nVID:PID 1996:00AD"| FW
FL -->|"sdphost + blhost\nVID:PID 1FC9:0130"| ROM
M5 <-->|"JSON-lines\nSerial"| M5HW
M5HW -->|"RLY14"| 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 -->|"RLY14"| 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 {
<<enumeration>>
WAITING
FLASHING
DIAGNOSING
}
class TestStatus {
<<enumeration>>
PENDING
RUNNING
PASS
FAIL
SKIP
}
class FlashTarget {
<<enumeration>>
FIRMWARE_TEST
PRODUCTION
CUSTOM
}
class OrchestratorEventType {
<<enumeration>>
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-запуске лог создаётся рядом с исполняемым файлом.

File diff suppressed because one or more lines are too long

View file

@ -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

View file

@ -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;
}

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -0,0 +1,7 @@
"""screens — публичный экспорт экранов TUI."""
from .diag import DiagScreen
from .flash import FlashScreen
from .waiting import WaitingScreen
__all__ = ["WaitingScreen", "FlashScreen", "DiagScreen"]

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -8,6 +8,7 @@ dependencies = [
"pyserial>=3.5",
"python-dotenv>=1.0.0",
"pyinstaller>=6.0.0",
"pyusb>=1.0.0",
]
[project.scripts]

View file

@ -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

264
tools/production/uv.lock Normal file
View file

@ -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" },
]