# Simple usb-cdc cli + host tests
This commit is contained in:
parent
6f0e082849
commit
5f2876e89f
13 changed files with 1472 additions and 555 deletions
16
.vscode/tasks.json
vendored
16
.vscode/tasks.json
vendored
|
|
@ -31,7 +31,7 @@
|
||||||
// HOST-ТЕСТЫ
|
// HOST-ТЕСТЫ
|
||||||
// =============================================================
|
// =============================================================
|
||||||
{
|
{
|
||||||
"label": "🧪 Host Tests (Debug)",
|
"label": "Host Tests",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
|
|
@ -45,14 +45,14 @@
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"statusbar": {
|
"statusbar": {
|
||||||
"color": "#c800ff",
|
"color": "#c800ff",
|
||||||
"label": "$(test-view-icon) Host Tests",
|
"label": "$(test-view-icon) Host Tests (Debug)",
|
||||||
"detail": "Run host unit tests"
|
"detail": "Run host unit tests"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "🧪 Host Tests (Release)",
|
"label": "🔨Host Tests (Release)",
|
||||||
"group": "test",
|
"group": "test",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "just",
|
"command": "just",
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"statusbar": {
|
"statusbar": {
|
||||||
"color": "#00e5ff",
|
"color": "#00e5ff",
|
||||||
"label": "$(circuit-board) HIL Build",
|
"label": "$(circuit-board) HIL Tests (Debug)",
|
||||||
"detail": "Build HIL target firmware"
|
"detail": "Build HIL target firmware"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -91,7 +91,7 @@
|
||||||
// HAB-ОБРАЗЫ
|
// HAB-ОБРАЗЫ
|
||||||
// =============================================================
|
// =============================================================
|
||||||
{
|
{
|
||||||
"label": "📦 HAB Image",
|
"label": "HAB Image",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "just",
|
"command": "just",
|
||||||
|
|
@ -102,14 +102,14 @@
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"statusbar": {
|
"statusbar": {
|
||||||
"color": "#03b3ff",
|
"color": "#03b3ff",
|
||||||
"label": "$(package) HAB",
|
"label": "$(package) HAB Image Build",
|
||||||
"detail": "Build HAB image"
|
"detail": "Build HAB image"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "📦 HAB All (Debug)",
|
"label": "📦 HAB Build All (Debug)",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "just",
|
"command": "just",
|
||||||
|
|
@ -122,7 +122,7 @@
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "📦 HAB All (Release)",
|
"label": "📦 HAB Build All (Release)",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "just",
|
"command": "just",
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@
|
||||||
"test_bsp_can",
|
"test_bsp_can",
|
||||||
"uart_host_mock_example",
|
"uart_host_mock_example",
|
||||||
"test_ring_buffer",
|
"test_ring_buffer",
|
||||||
|
"test_cli",
|
||||||
"test_timeout_pattern",
|
"test_timeout_pattern",
|
||||||
"uart_host_mock_example"
|
"uart_host_mock_example"
|
||||||
]
|
]
|
||||||
|
|
@ -160,6 +161,7 @@
|
||||||
"test_bsp_opto",
|
"test_bsp_opto",
|
||||||
"test_bsp_button",
|
"test_bsp_button",
|
||||||
"test_bsp_can",
|
"test_bsp_can",
|
||||||
|
"test_cli",
|
||||||
"uart_host_mock_example",
|
"uart_host_mock_example",
|
||||||
"test_ring_buffer",
|
"test_ring_buffer",
|
||||||
"test_timeout_pattern",
|
"test_timeout_pattern",
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,12 @@
|
||||||
|
|
||||||
set(TARGET_NAME firmware_test)
|
set(TARGET_NAME firmware_test)
|
||||||
|
|
||||||
add_executable(${TARGET_NAME} main.c ${BSP_GENERATED}/clock_config.c
|
add_executable(
|
||||||
|
${TARGET_NAME} src/main.c src/cli.c ${BSP_GENERATED}/clock_config.c
|
||||||
${BSP_STARTUP_FILE} ${BSP_SYSCALLS_FILE})
|
${BSP_STARTUP_FILE} ${BSP_SYSCALLS_FILE})
|
||||||
|
|
||||||
|
target_include_directories(firmware_test PRIVATE src/)
|
||||||
|
|
||||||
target_compile_definitions(${TARGET_NAME}
|
target_compile_definitions(${TARGET_NAME}
|
||||||
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512)
|
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512)
|
||||||
#
|
#
|
||||||
|
|
@ -14,16 +17,8 @@ target_compile_definitions(${TARGET_NAME}
|
||||||
# даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны
|
# даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
${TARGET_NAME}
|
${TARGET_NAME} PRIVATE bsp_board bsp_led bsp_tick bsp_boot_xip bsp_status
|
||||||
PRIVATE bsp_board
|
bsp_usb_cdc)
|
||||||
bsp_led
|
|
||||||
bsp_tick
|
|
||||||
bsp_uart_host
|
|
||||||
bsp_boot_xip
|
|
||||||
bsp_usb_cdc
|
|
||||||
port_log_uart
|
|
||||||
lib_external # SEGGER RTT если включён через SEGGER_RTT_ENABLED
|
|
||||||
)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Linker script
|
# Linker script
|
||||||
|
|
|
||||||
559
firmware/test/PLAN.md
Normal file
559
firmware/test/PLAN.md
Normal file
|
|
@ -0,0 +1,559 @@
|
||||||
|
# firmware_test — Plan of Development
|
||||||
|
|
||||||
|
> Документ для нового треда. Содержит все принятые решения, текущий статус и
|
||||||
|
> пошаговый план дальнейшей разработки.
|
||||||
|
> Версия: 0.2 | Обновлён после реализации скелета + bsp_usb_cdc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Контекст проекта
|
||||||
|
|
||||||
|
**Цель прошивки:** входной контроль платы MIMXRT1052CVJ5B на производстве.
|
||||||
|
Запускается через BootROM (USB SDP), без предварительной прошивки загрузчика.
|
||||||
|
|
||||||
|
**Стенд:**
|
||||||
|
- Хост подключается через USB CDC ACM (J2) — единственный канал
|
||||||
|
- Тесты с внешними сигналами управляются через M5StampPLC (реле, оптовходы)
|
||||||
|
- Отдельный компьютер-сервер запускает pytest
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Статус на момент создания документа
|
||||||
|
|
||||||
|
| Компонент | Статус | Примечание |
|
||||||
|
|---|---|---|
|
||||||
|
| `bsp_usb_cdc` | ✅ Готов | HIL тест пройден (`05_test_usb_cdc.py`) |
|
||||||
|
| `firmware_test` скелет | ✅ Готов | `main.c` + `cli.c` + PING работает |
|
||||||
|
| Host-тест CLI | ✅ Готов | `test_cli.c`, 8 тестов, зелёные |
|
||||||
|
| `bsp_sdram` | ⬜ Не начат | |
|
||||||
|
| `bsp_qspi` | ⬜ Не начат | |
|
||||||
|
| `bsp_usd` | ⬜ Не начат | |
|
||||||
|
| Протокол v2 | ⬜ Не начат | Эволюция от cmd/ok к event-driven |
|
||||||
|
| Test runner | ⬜ Не начат | |
|
||||||
|
| Provisioning | ⬜ Не начат | |
|
||||||
|
|
||||||
|
### Закрытые архитектурные решения
|
||||||
|
|
||||||
|
> Не пересматривать без явного запроса.
|
||||||
|
|
||||||
|
- **[DECISION] Транспорт:** USB CDC ACM — единственный канал. UART не используется в firmware_test.
|
||||||
|
- **[DECISION] Парсинг JSON:** без cJSON, строковый `strstr`. Входящее поле всегда `"cmd"` / `"type"`.
|
||||||
|
- **[DECISION] SDRAM и DCD:** SEMC инициализируется DCD до `main()`. `bsp_sdram_init()` только верифицирует.
|
||||||
|
- **[DECISION] SDRAM тест — не HIL ELF:** тест прогоняется командами через firmware_test, не отдельным ELF.
|
||||||
|
- **[DECISION] IR и RTC:** не реализуются.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Протокол v2 — решение, требующее принятия в новом треде
|
||||||
|
|
||||||
|
Текущий скелет использует упрощённый протокол:
|
||||||
|
```json
|
||||||
|
{"cmd":"PING"} → {"ok":true,"result":"PONG"}
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO.md описывает расширенный протокол с типами событий:
|
||||||
|
```json
|
||||||
|
{"type":"session_start","fw":"0.1.0","target":"IMXRT1052","uptime_ms":0}
|
||||||
|
{"type":"test_begin","id":"sdram","name":"SDRAM 32MB","critical":true}
|
||||||
|
{"type":"test_result","id":"sdram","status":"pass","ms":312}
|
||||||
|
{"type":"summary","passed":7,"failed":0,"overall":"pass"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Вопросы для обсуждения в треде:**
|
||||||
|
|
||||||
|
1. Переходить ли на v2 сразу или итерационно (сначала SDRAM с cmd/ok, потом рефакторинг)?
|
||||||
|
2. Как обрабатывать `confirm_request` (display test) в cli.c — отдельный тип входящего сообщения?
|
||||||
|
3. `session_start` — посылать ли при каждом `READY` или только по команде `START_SESSION`?
|
||||||
|
|
||||||
|
**Рекомендация:** реализовать v2 до добавления первого теста, иначе рефакторинг CLI затронет уже написанные тест-модули.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура файлов — целевое состояние
|
||||||
|
```
|
||||||
|
firmware/test/
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── README.md
|
||||||
|
└── src/
|
||||||
|
├── main.c # ✅ готов
|
||||||
|
├── cli.h / cli.c # ✅ готов (v1), требует эволюции до v2
|
||||||
|
├── protocol.h / protocol.c # ⬜ новый: сериализация ответов
|
||||||
|
├── test_runner.h / .c # ⬜ новый: реестр + sequencer
|
||||||
|
├── provisioning.h / .c # ⬜ новый: chip UID + provision_ack
|
||||||
|
└── tests/
|
||||||
|
├── test_sdram.h / .c # ⬜
|
||||||
|
├── test_qspi.h / .c # ⬜
|
||||||
|
├── test_usd.h / .c # ⬜
|
||||||
|
├── test_display.h / .c # ⬜ интерактивный (кнопки)
|
||||||
|
├── test_can.h / .c # ⬜ HIL (M5StampPLC)
|
||||||
|
├── test_uart.h / .c # ⬜ HIL TTL + ISO
|
||||||
|
└── test_opto.h / .c # ⬜ HIL (M5StampPLC)
|
||||||
|
|
||||||
|
bsp/
|
||||||
|
├── sdram/ # ⬜ CMakeLists.txt + sdram.c (структура есть)
|
||||||
|
├── qspi/ # ⬜ новый модуль
|
||||||
|
└── usd/ # ⬜ новый модуль
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Матрица тестов
|
||||||
|
|
||||||
|
| ID | Название | Тип | Critical | HIL (M5) | HIL ELF | BSP | Статус |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| — | PING | cmd | — | — | — | — | ✅ |
|
||||||
|
| `sdram` | SDRAM 32MB | self | ✅ | ❌ | ❌ | `bsp_sdram` | ⬜ |
|
||||||
|
| `qspi` | QSPI Flash | self | ✅ | ❌ | ❌ | `bsp_qspi` | ⬜ |
|
||||||
|
| `usd` | uSD (SDIO) | self | ✅ | ❌ | ❌ | `bsp_usd` | ⬜ |
|
||||||
|
| `display` | Display RGB888 | interactive | ❌ | ❌ | ❌ | существующий BSP | ⬜ |
|
||||||
|
| `can` | CAN | HIL | ❌ | ✅ | ❌ | `bsp_can` ✅ | ⬜ |
|
||||||
|
| `uart_ttl` | UART TTL | HIL | ❌ | ✅ | ❌ | `bsp_uart_host` ✅ | ⬜ |
|
||||||
|
| `uart_iso` | UART ISO +24V | HIL | ❌ | ✅ | ❌ | уточнить | ⬜ |
|
||||||
|
| `opto` | Opto-in +24V | HIL | ❌ | ✅ | ❌ | `bsp_opto` ✅ | ⬜ |
|
||||||
|
|
||||||
|
**Колонка "HIL ELF":** отдельная RAM-прошивка через pyOCD. Для firmware_test тестов — не нужна, тесты идут через USB CDC.
|
||||||
|
|
||||||
|
**Колонка "HIL (M5)":** нужна ли M5StampPLC для управления внешними сигналами.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Пошаговый план
|
||||||
|
|
||||||
|
### Этап 0 — Вопросы, требующие ответа до кода
|
||||||
|
|
||||||
|
Обсудить в начале треда:
|
||||||
|
|
||||||
|
- Протокол v2: переходить сейчас или после SDRAM?
|
||||||
|
- `uart_iso`: есть отдельный BSP модуль или это тот же `bsp_uart_host` с другими параметрами?
|
||||||
|
- `usd`: карта вставлена постоянно на плате или оператор вставляет перед тестом?
|
||||||
|
- QSPI: какой конкретно чип (W25Q128?), есть ли уже sdk_flexspi таргет в CMake?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 1 — Протокол v2 + Test runner скелет
|
||||||
|
|
||||||
|
**Цель:** эволюция cli.c → protocol.c + test_runner.c. После этого этапа добавление каждого теста — одна строка в реестре.
|
||||||
|
|
||||||
|
**Файлы:**
|
||||||
|
```
|
||||||
|
firmware/test/src/
|
||||||
|
├── cli.c ← упрощается: только IO (read/write/buffer)
|
||||||
|
├── protocol.h/.c ← новый: сериализация всех типов сообщений
|
||||||
|
├── test_module.h ← новый: интерфейс тест-модуля
|
||||||
|
└── test_runner.h/.c ← новый: реестр + sequencer
|
||||||
|
```
|
||||||
|
|
||||||
|
**Интерфейс тест-модуля (`test_module.h`):**
|
||||||
|
```c
|
||||||
|
typedef enum { TEST_STATUS_PASS = 0, TEST_STATUS_FAIL, TEST_STATUS_SKIP } test_status_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
test_status_t status;
|
||||||
|
uint32_t duration_ms;
|
||||||
|
char detail[96];
|
||||||
|
} test_result_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *id;
|
||||||
|
const char *name;
|
||||||
|
bool critical;
|
||||||
|
bool requires_hil;
|
||||||
|
void (*init)(void);
|
||||||
|
test_result_t (*run)(void);
|
||||||
|
void (*deinit)(void);
|
||||||
|
} test_module_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Сериализация (protocol.h):** функции `protocol_send_session_start()`,
|
||||||
|
`protocol_send_test_begin()`, `protocol_send_test_result()`,
|
||||||
|
`protocol_send_summary()`, `protocol_send_confirm_request()` — все через `cli_send()`.
|
||||||
|
|
||||||
|
**Команды хоста v2:**
|
||||||
|
|
||||||
|
| Входящий тип | Поле | Действие |
|
||||||
|
|---|---|---|
|
||||||
|
| `cmd` | `"run_all"` | Запустить все тесты по реестру |
|
||||||
|
| `cmd` | `"run"` + `"id"` | Запустить один тест |
|
||||||
|
| `cmd` | `"ping"` | `{"type":"pong"}` |
|
||||||
|
| `confirm` | `"id"` + `"confirmed"` | Ответ оператора на display тест |
|
||||||
|
|
||||||
|
**Host-тесты:** расширить `test_cli.c` + добавить `test_protocol.c` (Категория A).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 2 — `bsp_sdram`
|
||||||
|
|
||||||
|
API уже описан в `firmware_test_plan.md` (закрытое решение):
|
||||||
|
```c
|
||||||
|
bsp_sdram_status_t bsp_sdram_init(void);
|
||||||
|
bsp_sdram_status_t bsp_sdram_test_fast(bsp_sdram_result_t *result);
|
||||||
|
bsp_sdram_status_t bsp_sdram_test_full(bsp_sdram_result_t *result);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Файлы:**
|
||||||
|
```
|
||||||
|
bsp/sdram/
|
||||||
|
├── CMakeLists.txt ← создать
|
||||||
|
├── include/bsp/sdram.h ← по spec из firmware_test_plan.md
|
||||||
|
└── src/sdram.c
|
||||||
|
```
|
||||||
|
|
||||||
|
**Тест модуль firmware_test:**
|
||||||
|
```
|
||||||
|
firmware/test/src/tests/test_sdram.c
|
||||||
|
```
|
||||||
|
|
||||||
|
Команды через реестр: `run` + `id: "sdram"`. Отдельной HIL ELF нет.
|
||||||
|
|
||||||
|
**HIL pytest (`tools/hil/test_sdram.py`):** подключается к firmware_test через USB CDC.
|
||||||
|
Фикстура `cdc_firmware_test` — firmware_test прошит в Flash, pytest открывает CDC порт.
|
||||||
|
|
||||||
|
**Just рецепты:** `hil-sdram` (без `--slow`), `hil-sdram-full` (с `@pytest.mark.slow`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 3 — `bsp_qspi` + QSPI тест
|
||||||
|
|
||||||
|
**Аппаратный контекст:** W25Q64FVSSIG (из схемы, 8MB SPI NOR Flash), интерфейс FlexSPI.
|
||||||
|
|
||||||
|
**API:**
|
||||||
|
```c
|
||||||
|
bsp_qspi_status_t bsp_qspi_init(void);
|
||||||
|
bsp_qspi_status_t bsp_qspi_read_jedec_id(uint8_t *manufacturer, uint16_t *device_id);
|
||||||
|
bsp_qspi_status_t bsp_qspi_test(bsp_qspi_result_t *result); /* erase sector + write + verify */
|
||||||
|
```
|
||||||
|
|
||||||
|
**Что проверяет тест:**
|
||||||
|
|
||||||
|
1. JEDEC ID совпадает с ожидаемым для W25Q64 (`0xEF`, `0x4017`)
|
||||||
|
2. Erase тестового сектора (последний сектор, чтобы не трогать прошивку)
|
||||||
|
3. Write + Read + Compare 256 байт
|
||||||
|
|
||||||
|
**Нет HIL ELF, нет M5.** Тест полностью самостоятельный.
|
||||||
|
|
||||||
|
**Вопрос перед началом:** проверить есть ли `sdk_flexspi` таргет в `sdk/CMakeLists.txt`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 4 — `bsp_usd` + uSD тест
|
||||||
|
|
||||||
|
**Аппаратный контекст:** SDMMC (uSD слот), интерфейс USDHC. SDK таргет `sdk_usdhc` есть в матрице.
|
||||||
|
|
||||||
|
**Стратегия:** использовать FatFS из SDK middleware (уже vendored в `middleware/fatfs/`).
|
||||||
|
|
||||||
|
**API:**
|
||||||
|
```c
|
||||||
|
bsp_usd_status_t bsp_usd_init(void); /* USDHC init + mount FAT */
|
||||||
|
bsp_usd_status_t bsp_usd_test(bsp_usd_result_t *result); /* write + read + verify */
|
||||||
|
bsp_usd_status_t bsp_usd_deinit(void); /* unmount */
|
||||||
|
```
|
||||||
|
|
||||||
|
**Поведение при отсутствии карты:** `TEST_STATUS_SKIP` (критически важно для
|
||||||
|
производственного прогона — карта может быть не вставлена).
|
||||||
|
|
||||||
|
**Вопрос:** карта вставлена постоянно или оператор вставляет? Если оператор — нужен `confirm_request` перед тестом.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 5 — Display тест (интерактивный)
|
||||||
|
|
||||||
|
**Что проверяем:** RGB888 интерфейс + подсветка + реакция оператора через кнопки.
|
||||||
|
|
||||||
|
**Последовательность:**
|
||||||
|
```
|
||||||
|
firmware → LCD: залить R (красный)
|
||||||
|
firmware → хост: {"type":"confirm_request","id":"display_red","timeout_ms":15000}
|
||||||
|
оператор: нажать кнопку PASS (Test_But_1) или FAIL (Test_But_2)
|
||||||
|
firmware → хост: {"type":"confirm_ack","id":"display_red","confirmed":true/false}
|
||||||
|
повторить для G, B, W
|
||||||
|
итоговый результат = AND всех подтверждений
|
||||||
|
```
|
||||||
|
|
||||||
|
**Особенности:**
|
||||||
|
|
||||||
|
- Одновременно тестируются кнопки (`bsp_button` уже есть с HIL тестами)
|
||||||
|
- Таймаут 15 с → `TEST_STATUS_SKIP`
|
||||||
|
- Маркер `@pytest.mark.interactive` в pytest — не входит в `hil-run`
|
||||||
|
|
||||||
|
**HIL pytest:** `tools/hil/test_display.py` с `_operator_prompt()` через `/dev/tty`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 6 — HIL тесты: CAN, UART, Opto в firmware_test
|
||||||
|
|
||||||
|
Эти BSP модули уже реализованы и имеют отдельные HIL ELF тесты.
|
||||||
|
Задача этапа — **интегрировать их в firmware_test** как тест-модули,
|
||||||
|
запускаемые через протокол v2.
|
||||||
|
|
||||||
|
#### 6.1 CAN (`bsp_can` ✅)
|
||||||
|
|
||||||
|
- Стенд: M5StampPLC подключён к CAN шине платы через интерфейсную плату
|
||||||
|
- Тест: M5 посылает CAN фрейм → плата принимает → сравниваем
|
||||||
|
|
||||||
|
#### 6.2 UART TTL (`bsp_uart_host` ✅)
|
||||||
|
|
||||||
|
- Стенд: M5 UART ↔ UART TTL платы (loopback или echo)
|
||||||
|
- Уточнить: какой UART порт на плате (LPUART1 занят MCU-Link, какой свободен?)
|
||||||
|
|
||||||
|
#### 6.3 UART ISO
|
||||||
|
|
||||||
|
- Уточнить наличие отдельного BSP модуля или это конфигурация `bsp_uart_host`
|
||||||
|
- +24V уровни через интерфейсную плату
|
||||||
|
|
||||||
|
#### 6.4 Opto (`bsp_opto` ✅)
|
||||||
|
|
||||||
|
- Стенд: M5 реле → оптовходы EXT_IN1, EXT_IN2, RS_RX
|
||||||
|
- Логика уже отработана в `02_test_opto.py` (HIL ELF)
|
||||||
|
- Переиспользовать: тот же M5 агент, другой транспорт (USB CDC вместо UART)
|
||||||
|
|
||||||
|
**Важно для всех HIL тестов этапа 6:** pytest для firmware_test использует
|
||||||
|
`cdc_firmware_test` фикстуру (USB CDC), а не `uart_<n>` (UART + pyOCD).
|
||||||
|
M5StampPLC управляет сигналами так же, как в существующих HIL ELF тестах.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 7 — Provisioning
|
||||||
|
|
||||||
|
**Источник UID:** OCOTP регистры через `OCOTP_GetFuseData()` (NXP HAL).
|
||||||
|
```c
|
||||||
|
void provisioning_run(provision_info_t *out);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Поток:**
|
||||||
|
```
|
||||||
|
firmware → хост: {"type":"provision_ready","chip_uid":"A3F2...","fw":"0.1.0"}
|
||||||
|
хост → БД: uid ↔ fw_version (логика на хосте)
|
||||||
|
хост → firmware: {"type":"cmd","cmd":"provision_ack","fw":"1.0.0","bootloader":"1.0.0"}
|
||||||
|
firmware → хост: {"type":"provision_done","recorded":true}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Запускается только при `overall == pass`.** В pytest отдельная фикстура
|
||||||
|
`provision_firmware_test`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Зависимости между этапами
|
||||||
|
```
|
||||||
|
Этап 1 (протокол v2 + runner)
|
||||||
|
├── Этап 2 (bsp_sdram)
|
||||||
|
│ └── HIL: test_sdram.py
|
||||||
|
├── Этап 3 (bsp_qspi)
|
||||||
|
│ └── HIL: test_qspi.py
|
||||||
|
├── Этап 4 (bsp_usd)
|
||||||
|
│ └── HIL: test_usd.py
|
||||||
|
├── Этап 5 (display, interactive)
|
||||||
|
│ └── HIL: test_display.py (@pytest.mark.interactive)
|
||||||
|
└── Этап 6 (CAN + UART + Opto)
|
||||||
|
└── HIL: test_can.py, test_uart.py, test_opto.py
|
||||||
|
|
||||||
|
Этап 7 (provisioning) → зависит от всех предыдущих
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BSP модули — итог
|
||||||
|
|
||||||
|
| BSP | Статус | Нужен HIL ELF | Нужен в firmware_test |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `bsp_usb_cdc` | ✅ | ✅ (есть) | ✅ (есть) |
|
||||||
|
| `bsp_led` | ✅ | ❌ | ✅ |
|
||||||
|
| `bsp_tick` | ✅ | ❌ | ✅ |
|
||||||
|
| `bsp_button` | ✅ | ✅ (есть) | ✅ (display тест) |
|
||||||
|
| `bsp_opto` | ✅ | ✅ (есть) | ✅ этап 6 |
|
||||||
|
| `bsp_can` | ✅ | ✅ (есть) | ✅ этап 6 |
|
||||||
|
| `bsp_uart_host` | ✅ | ✅ (есть) | ✅ этап 6 |
|
||||||
|
| `bsp_sdram` | ⬜ | ❌ | ✅ этап 2 |
|
||||||
|
| `bsp_qspi` | ⬜ | ❌ | ✅ этап 3 |
|
||||||
|
| `bsp_usd` | ⬜ | ❌ | ✅ этап 4 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Контекст для нового треда — что передать
|
||||||
|
```
|
||||||
|
Системный промпт: тот же (роль + правила).
|
||||||
|
|
||||||
|
Приложить файлы:
|
||||||
|
- firmware_test_plan.md (обновлённый)
|
||||||
|
- usb_cdc.md
|
||||||
|
- firmware/test/src/main.c (текущий)
|
||||||
|
- firmware/test/src/cli.h/.c (текущий)
|
||||||
|
- firmware/test/CMakeLists.txt (текущий)
|
||||||
|
- tests/host/CMakeLists.txt
|
||||||
|
- tests/host/cli/test_cli.c
|
||||||
|
- этот план (PLAN.md)
|
||||||
|
|
||||||
|
Первый вопрос нового треда:
|
||||||
|
"Начинаем Этап 1 — протокол v2 и test_runner.
|
||||||
|
Ответы на открытые вопросы: [...]"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Открытый вопрос: ПО на стороне хоста для производственного прогона
|
||||||
|
|
||||||
|
### Контекст и разделение ответственности
|
||||||
|
|
||||||
|
Принципиально важно разделить два окружения:
|
||||||
|
|
||||||
|
| Окружение | Кто запускает | Инструмент | Что тестирует |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Разработка** | Разработчик, локальный ПК | pytest (`just host::hil-*`) | Отдельные BSP модули через HIL ELF + UART |
|
||||||
|
| **Производство / Сервис** | Оператор, сервер | ??? | Плата целиком через firmware_test + USB CDC |
|
||||||
|
|
||||||
|
Это два **принципиально разных** use case с разными требованиями к UX,
|
||||||
|
надёжности и изоляции. Смешивать их в одном pytest-прогоне нельзя.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Почему "просто pytest" недостаточно для производства
|
||||||
|
|
||||||
|
Производственный прогон отличается от HIL тестов разработчика по нескольким осям:
|
||||||
|
|
||||||
|
**Оператор — не разработчик.** Он не читает pytest output в терминале.
|
||||||
|
Ему нужно видеть: какой тест сейчас идёт, прошёл или нет, что делать дальше
|
||||||
|
(вставить карту, посмотреть на дисплей, нажать кнопку).
|
||||||
|
|
||||||
|
**Последовательность фиксирована.** Прогон всегда идёт по реестру:
|
||||||
|
flash → READY → run_all → summary → provisioning. Никакого выбора тестов.
|
||||||
|
|
||||||
|
**Результат — не лог, а запись в БД.** `chip_uid` + результат + версия прошивки
|
||||||
|
должны сохраняться. pytest ничего не знает о БД.
|
||||||
|
|
||||||
|
**Интерактивные тесты** (display) требуют управляемого диалога с оператором,
|
||||||
|
а не `input()` в терминале с флагом `-s`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Варианты хостового ПО — анализ
|
||||||
|
|
||||||
|
#### Вариант A: pytest + плагины + HTML отчёт
|
||||||
|
|
||||||
|
Обернуть весь прогон в pytest, добавить `pytest-html` для отчётов,
|
||||||
|
интерактивные тесты через `@pytest.mark.interactive` с `-s`.
|
||||||
|
|
||||||
|
**Плюсы:** минимум нового кода, знакомый инструмент.
|
||||||
|
|
||||||
|
**Минусы:** оператор смотрит в терминал; интерактивность через `/dev/tty` хрупкая;
|
||||||
|
provisioning (запись в БД) — костыль в фикстуре; нет живого статуса
|
||||||
|
"тест N из M идёт X секунд". Для производства неприемлемо.
|
||||||
|
|
||||||
|
#### Вариант B: TUI — Python + Textual / Rich
|
||||||
|
|
||||||
|
Самостоятельное Python-приложение с текстовым интерфейсом.
|
||||||
|
Управляет всем: flash через spsdk, M5StampPLC, USB CDC, provisioning, БД.
|
||||||
|
pytest не используется как runner — только как библиотека для assert-логики
|
||||||
|
(или вообще не используется).
|
||||||
|
|
||||||
|
**Плюсы:** полный контроль над UX; живой прогресс; чёткие диалоги оператора;
|
||||||
|
нативная запись в БД; изолировано от HIL тестов разработчика полностью.
|
||||||
|
|
||||||
|
**Минусы:** значительный объём разработки хостового ПО;
|
||||||
|
нужен отдельный репозиторий или директория `tools/production/`.
|
||||||
|
|
||||||
|
#### Вариант C: pytest как backend, TUI/GUI как frontend
|
||||||
|
|
||||||
|
pytest запускается программно через `pytest.main()` или subprocess,
|
||||||
|
результаты передаются через JSON reporter (`pytest-json-report`) в
|
||||||
|
отдельное GUI/TUI приложение которое их отображает.
|
||||||
|
|
||||||
|
**Плюсы:** переиспользуем pytest инфраструктуру (фикстуры, параллелизм).
|
||||||
|
|
||||||
|
**Минусы:** архитектурно сложно; интерактивные тесты всё равно требуют
|
||||||
|
кастомного канала; два процесса вместо одного.
|
||||||
|
|
||||||
|
#### Вариант D: pytest только для разработки, отдельный runner для производства
|
||||||
|
|
||||||
|
**Разработка:** pytest (`just host::hil-*`) — тесты отдельных BSP модулей
|
||||||
|
через HIL ELF. Остаётся как есть.
|
||||||
|
|
||||||
|
**Производство:** отдельное Python-приложение (`tools/production/run.py`)
|
||||||
|
без pytest. Использует те же низкоуровневые библиотеки
|
||||||
|
(`pyserial`, `spsdk`, M5 агент) но со своим runner-ом и TUI.
|
||||||
|
|
||||||
|
Тест-логика на стороне прошивки (в `test_runner.c`) — единственная
|
||||||
|
точка истины. Хостовое ПО только отправляет команды и интерпретирует события.
|
||||||
|
|
||||||
|
**Это рекомендуемый вариант** — наиболее чистое разделение.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Рекомендуемая архитектура (Вариант D, детально)
|
||||||
|
```
|
||||||
|
tools/
|
||||||
|
├── hil/ # СУЩЕСТВУЮЩИЙ — разработчик, локальный ПК
|
||||||
|
│ ├── conftest.py # pytest фикстуры
|
||||||
|
│ ├── 01_test_uart.py # HIL тесты отдельных BSP модулей
|
||||||
|
│ ├── 02_test_opto.py
|
||||||
|
│ └── ...
|
||||||
|
│
|
||||||
|
└── production/ # НОВЫЙ — производство / сервис
|
||||||
|
├── pyproject.toml # отдельное окружение uv
|
||||||
|
├── run.py # точка входа: python run.py
|
||||||
|
├── runner/
|
||||||
|
│ ├── flasher.py # spsdk: flash firmware_test.elf
|
||||||
|
│ ├── cdc_client.py # USB CDC: send cmd, recv events
|
||||||
|
│ ├── m5_client.py # переиспользовать tools/hil/m5/
|
||||||
|
│ ├── test_sequence.py # порядок: flash→READY→run_all→provision
|
||||||
|
│ └── db.py # запись chip_uid + результатов
|
||||||
|
└── ui/
|
||||||
|
├── tui.py # Textual TUI: живой прогресс + диалоги
|
||||||
|
└── report.py # HTML / JSON отчёт после прогона
|
||||||
|
```
|
||||||
|
|
||||||
|
**Поток производственного прогона:**
|
||||||
|
```
|
||||||
|
Оператор запускает: python tools/production/run.py
|
||||||
|
│
|
||||||
|
├── flasher.py: spsdk → flash firmware_test.elf → Reset
|
||||||
|
├── cdc_client: ждёт {"type":"session_start",...}
|
||||||
|
├── TUI: показывает "Подключение... ОК"
|
||||||
|
│
|
||||||
|
├── cdc_client: → {"type":"cmd","cmd":"run_all"}
|
||||||
|
│
|
||||||
|
├── [цикл событий]
|
||||||
|
│ ├── {"type":"test_begin","id":"sdram"} → TUI: "SDRAM... ⏳"
|
||||||
|
│ ├── {"type":"test_result","id":"sdram","status":"pass"} → TUI: "SDRAM ✅ 47ms"
|
||||||
|
│ ├── {"type":"confirm_request","id":"display_red"} →
|
||||||
|
│ │ TUI: диалог оператора "Экран красный? [PASS/FAIL]"
|
||||||
|
│ │ cdc_client: → {"type":"confirm","id":"display_red","confirmed":true}
|
||||||
|
│ └── ...
|
||||||
|
│
|
||||||
|
├── {"type":"summary","overall":"pass"} → TUI: финальный результат
|
||||||
|
├── {"type":"provision_ready","chip_uid":"..."} →
|
||||||
|
│ db.py: записать в БД
|
||||||
|
│ cdc_client: → {"type":"cmd","cmd":"provision_ack",...}
|
||||||
|
└── TUI: "Плата принята ✅ | UID: A3F2..." | печать этикетки?
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Изоляция от HIL тестов разработчика
|
||||||
|
|
||||||
|
| Аспект | HIL тесты (разработка) | Production runner |
|
||||||
|
|---|---|---|
|
||||||
|
| Инструмент | pytest | Самостоятельный Python runner |
|
||||||
|
| Директория | `tools/hil/` | `tools/production/` |
|
||||||
|
| Окружение uv | `tools/hil/pyproject.toml` | `tools/production/pyproject.toml` |
|
||||||
|
| Just рецепты | `just host::hil-*` | `just host::production-run` |
|
||||||
|
| Прошивка | HIL ELF в RAM (pyOCD) | firmware_test в Flash (spsdk) |
|
||||||
|
| Транспорт | UART CLI (pyserial) | USB CDC JSON-lines |
|
||||||
|
| UX | Терминал / pytest output | TUI (Textual) |
|
||||||
|
| БД | Нет | Да |
|
||||||
|
| M5StampPLC | Да (реле для сигналов) | Да (те же реле) |
|
||||||
|
| CI | Да (автоматический) | Нет |
|
||||||
|
|
||||||
|
Общий код (M5 агент, низкоуровневый spsdk wrapper) можно вынести в
|
||||||
|
`tools/shared/` и подключать как локальный пакет в обоих `pyproject.toml`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Открытые вопросы для обсуждения
|
||||||
|
|
||||||
|
- [ ] **TUI библиотека:** Textual (современный, богатый) или Rich (проще, достаточно)?
|
||||||
|
Или вообще минималистичный вывод без TUI фреймворка на первой итерации?
|
||||||
|
- [ ] **БД:** SQLite локально на сервере или REST API на внешний сервис?
|
||||||
|
- [ ] **Этикетка:** нужна ли автоматическая печать после provisioning?
|
||||||
|
- [ ] **Несколько стендов:** один сервер управляет несколькими платами параллельно
|
||||||
|
или всегда одна плата?
|
||||||
|
- [ ] **Первая итерация:** допустимо ли начать с простого `run.py` без TUI
|
||||||
|
(plain print + input) и добавить TUI позже?
|
||||||
|
|
@ -1,244 +1,162 @@
|
||||||
# firmware_test — Architecture
|
# firmware_test
|
||||||
|
|
||||||
> Target: NXP IMXRT1052CVJ5B
|
Bare-metal прошивка входного контроля платы **MIMXRT1052CVJ5B**.
|
||||||
> Версия документа: 0.1
|
Загружается через USB ROM (SDP) в Flash. Запускается автономно при включении.
|
||||||
> Статус: draft
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Назначение
|
## Канал связи
|
||||||
|
|
||||||
`firmware_test` — входная тестовая прошивка для проверки работоспособности платы при производстве и во время разработки. Запускается напрямую через BootROM (USB Serial Download), без предварительной прошивки загрузчика. После успешного прохождения всех тестов инициирует фазу провижининга (привязка Chip UID к версиям ПО).
|
Единственный транспорт: **USB CDC ACM** (USB1 / EHCI0, разъём J2).
|
||||||
|
UART (MCU-Link VCOM) не используется — только в HIL ELF прошивках.
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Workflow прошивки платы
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
BootROM (USB Serial Download, встроен в IMXRT1052)
|
Хост (pytest / minicom / pyserial)
|
||||||
↓
|
└── USB CDC ACM (J2)
|
||||||
firmware_test (залит напрямую)
|
└── firmware_test
|
||||||
↓ [тесты прошли, provisioning выполнен]
|
└── cli.c → dispatch → обработчик команды
|
||||||
Флашим: Bootloader + App
|
|
||||||
↓
|
|
||||||
Ждём heartbeat Bootloader → App
|
|
||||||
↓
|
|
||||||
Плата принята
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Вариант с предварительной заливкой загрузчика не используется — BootROM является надёжным и всегда доступным recovery-path, не зависящим от состояния Flash.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Высокоуровневая архитектура
|
## Протокол
|
||||||
|
|
||||||
|
**JSON-lines**: каждая строка завершается `\n`.
|
||||||
|
|
||||||
|
| Направление | Формат |
|
||||||
|
|---|---|
|
||||||
|
| Запрос | `{"cmd":"NAME"}\n` |
|
||||||
|
| Успех | `{"ok":true,...}\n` |
|
||||||
|
| Ошибка | `{"ok":false,"error":"CODE"}\n` |
|
||||||
|
|
||||||
|
Ограничения:
|
||||||
|
|
||||||
|
- Максимальная длина строки: **128 байт** включая `\n` (`CLI_LINE_BUF_SIZE`)
|
||||||
|
- Терминатор: `\n` (LF, `0x0A`). `\r\n` (CR+LF) тоже принимается
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
| Команда | Запрос | Успешный ответ | Описание |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `PING` | `{"cmd":"PING"}` | `{"ok":true,"result":"PONG"}` | Проверка связи |
|
||||||
|
| `SDRAM_TEST` | `{"cmd":"SDRAM_TEST"}` | `{"ok":true,"time_ms":N}` | Быстрый тест SDRAM (≤100 мс) |
|
||||||
|
| `SDRAM_TEST_FULL` | `{"cmd":"SDRAM_TEST_FULL"}` | `{"ok":true,"time_ms":N}` | Полный тест SDRAM (~30–60 с) |
|
||||||
|
|
||||||
|
Коды ошибок:
|
||||||
|
|
||||||
|
| Код | Причина |
|
||||||
|
|---|---|
|
||||||
|
| `UNKNOWN_CMD` | Команда не найдена в таблице |
|
||||||
|
| `PARSE_ERR` | Не найдено поле `"cmd"` в JSON |
|
||||||
|
| `LINE_TOO_LONG` | Строка превысила `CLI_LINE_BUF_SIZE` |
|
||||||
|
| `SDRAM_FAIL` | Тест SDRAM выявил ошибку чтения/записи |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Последовательность старта
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
┌─────────────────────────────────────────────────────┐
|
board_hw_init() тактирование, MPU, кэш, пины
|
||||||
│ firmware_test │
|
bsp_tick_init() SysTick 1 мс
|
||||||
│ │
|
bsp_led_init() оба LED выключены
|
||||||
│ USB CDC ──► Protocol ──► Test runner ──► Local UI │
|
bsp_usb_cdc_init() PHY + USB стек + NVIC
|
||||||
│ (JSON-lines) (sequencer) (LED+disp) │
|
|
||||||
│ │
|
[ожидание хоста] LED_HEARTBEAT мигает 200 мс
|
||||||
│ ┌─────────────────────┐ ┌──────────────────────┐ │
|
bsp_usb_cdc_is_ready() == true
|
||||||
│ │ self-tests │ │ HIL tests │ │
|
|
||||||
│ │ SDRAM QSPI uSD │ │ CAN UART Opto-in │ │
|
LED_APP on
|
||||||
│ │ RTC Display IR │ │ UART ISO IR burst │ │
|
→ {"ok":true,"result":"READY"} сигнал готовности хосту
|
||||||
│ └─────────────────────┘ └──────────────────────┘ │
|
|
||||||
│ │
|
[главный цикл]
|
||||||
│ HAL / BSP │
|
bsp_usb_cdc_poll()
|
||||||
└─────────────────────────────────────────────────────┘
|
cli_process()
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Компоненты
|
## Структура файлов
|
||||||
|
|
||||||
### 4.1 USB CDC
|
|
||||||
|
|
||||||
Единственный канал связи с внешним миром. Представляется хосту как виртуальный COM-порт. Инициализируется первым, до запуска тестов. При старте ожидает подключения хоста с таймаутом — если хост не подключился, тесты запускаются автономно.
|
|
||||||
|
|
||||||
### 4.2 Protocol
|
|
||||||
|
|
||||||
Протокол — **JSON-lines**: каждое сообщение является отдельным JSON-объектом, завершённым символом `\n`. Библиотека: cJSON из NXP SDK.
|
|
||||||
|
|
||||||
Направление **хост → плата** (команды):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"type":"cmd","cmd":"run_all"}
|
|
||||||
{"type":"cmd","cmd":"run","id":"sdram"}
|
|
||||||
{"type":"confirm","id":"display","confirmed":true}
|
|
||||||
```
|
|
||||||
|
|
||||||
Направление **плата → хост** (события):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"type":"session_start","fw":"0.1.0","target":"IMXRT1052","uptime_ms":0}
|
|
||||||
{"type":"test_begin","id":"sdram","name":"SDRAM 32MB","critical":true}
|
|
||||||
{"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":"32MB R/W OK"}
|
|
||||||
{"type":"confirm_request","id":"display","timeout_ms":15000}
|
|
||||||
{"type":"abort","reason":"critical_fail","id":"usd"}
|
|
||||||
{"type":"summary","passed":7,"failed":0,"skipped":2,"aborted":false,"overall":"pass"}
|
|
||||||
{"type":"provision_ready","chip_uid":"A3F2C1B400E70012"}
|
|
||||||
{"type":"provision_ack","fw":"1.0.0","bootloader":"1.0.0","recorded":true}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Примечание:** `uptime_ms` вместо Unix timestamp — RTC может быть не инициализирован на новой плате. Хост приклеивает реальное время самостоятельно.
|
|
||||||
|
|
||||||
### 4.3 Test runner
|
|
||||||
|
|
||||||
Центральный компонент. Хранит реестр тест-модулей, управляет порядком запуска, обрабатывает критические сбои, формирует `summary`.
|
|
||||||
|
|
||||||
**Порядок выполнения:**
|
|
||||||
|
|
||||||
1. Self-tests в порядке реестра
|
|
||||||
2. Проверка критических сбоев — если есть, HIL не запускается
|
|
||||||
3. HIL tests (только если Firefly подключён и self-tests прошли)
|
|
||||||
4. Summary report
|
|
||||||
5. Provisioning (только при `overall == pass`)
|
|
||||||
|
|
||||||
**Интерфейс тест-модуля (`test_module.h`):**
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
typedef enum {
|
|
||||||
TEST_STATUS_PASS = 0,
|
|
||||||
TEST_STATUS_FAIL,
|
|
||||||
TEST_STATUS_SKIP,
|
|
||||||
} test_status_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
test_status_t status;
|
|
||||||
uint32_t duration_ms;
|
|
||||||
char detail[96]; /* диагностическая строка, опционально */
|
|
||||||
} test_result_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const char *id; /* "sdram", "qspi", "can" — ключ в JSON */
|
|
||||||
const char *name; /* "SDRAM 32MB" — для display/лога */
|
|
||||||
bool critical; /* abort HIL если FAIL */
|
|
||||||
bool requires_hil; /* пропустить если Firefly не готов */
|
|
||||||
void (*init)(void);
|
|
||||||
test_result_t (*run)(void);
|
|
||||||
void (*deinit)(void);
|
|
||||||
} test_module_t;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.4 Local UI
|
|
||||||
|
|
||||||
Отображает текущее состояние тестирования на светодиодах и дисплее. Получает события от Test runner. Дисплей при этом является частью тест-процесса (display_test).
|
|
||||||
|
|
||||||
**LED-паттерны:**
|
|
||||||
|
|
||||||
| Состояние | LED1 | LED2 |
|
|
||||||
|------------------------|-------------|-------------|
|
|
||||||
| Тест выполняется | мигает | выкл |
|
|
||||||
| Все тесты PASS | вкл | выкл |
|
|
||||||
| Есть FAIL | выкл | вкл |
|
|
||||||
| Ожидание подтверждения | оба мигают | |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Тест-модули
|
|
||||||
|
|
||||||
### 5.1 Self-tests
|
|
||||||
|
|
||||||
| ID | Название | Critical | Описание |
|
|
||||||
|------------|------------------|----------|--------------------------------------------------|
|
|
||||||
| `sdram` | SDRAM 32MB | ✅ | Write/read паттерны по всему объёму |
|
|
||||||
| `qspi` | QSPI Flash | ✅ | JEDEC ID + запись/чтение тестового сектора |
|
|
||||||
| `usd` | uSD (SDIO) | ✅ | Mount + R/W тестового файла (SKIP если нет карты)|
|
|
||||||
| `rtc` | RTC BM8563 | — | I2C presence, set/get time |
|
|
||||||
| `display` | Display RGB888 | — | R/G/B/W заливки, подтверждение оператором |
|
|
||||||
| `ir` | IR receiver | — | GPIO idle state HIGH, peripheral init |
|
|
||||||
|
|
||||||
**Display test — логика подтверждения:**
|
|
||||||
|
|
||||||
- Плата посылает `confirm_request` с `timeout_ms: 15000`
|
|
||||||
- Оператор нажимает **одну** кнопку: PASS или FAIL
|
|
||||||
- Если кнопка не нажата за 15 секунд — статус `SKIP` (ответственность на операторе)
|
|
||||||
- Одновременно проверяются обе кнопки — это полноценный тест кнопок
|
|
||||||
|
|
||||||
### 5.2 HIL tests (требуют Firefly AIO-3588Q)
|
|
||||||
|
|
||||||
| ID | Название | Стенд | Описание |
|
|
||||||
|--------------|------------------|------------------------------|----------------------------------------|
|
|
||||||
| `can` | CAN | Firefly CAN | Обмен фреймами, full-duplex |
|
|
||||||
| `uart_ttl` | UART TTL | Firefly UART | Echo паттерн |
|
|
||||||
| `uart_iso` | UART ISO +24V | Firefly UART + интерф. плата | Только RX, Firefly посылает |
|
|
||||||
| `opto` | Opto-in +24V | Firefly GPIO + интерф. плата | Все каналы, Firefly дёргает GPIO |
|
|
||||||
| `ir_hil` | IR burst | Firefly GPIO + IR LED | Приём burst 38 кГц, факт прерывания |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Provisioning
|
|
||||||
|
|
||||||
Выполняется после `summary: overall == pass`. Не является тестом — это отдельный этап жизненного цикла платы.
|
|
||||||
|
|
||||||
**Источник UID:** регистры OCOTP (One-Time Programmable fuses), 64-bit Chip UID. Читается через HAL.
|
|
||||||
|
|
||||||
**Интерфейс (`provisioning.h`):**
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef struct {
|
|
||||||
char chip_uid[17]; /* 64-bit UID как hex-строка, null-terminated */
|
|
||||||
char fw_version[16];
|
|
||||||
char bootloader_version[16];
|
|
||||||
bool provisioned;
|
|
||||||
} provision_info_t;
|
|
||||||
|
|
||||||
/* вызывается только при overall == PASS */
|
|
||||||
void provisioning_run(provision_info_t *out);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Поток:**
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
плата посылает provision_ready + chip_uid
|
firmware/test/
|
||||||
↓
|
├── CMakeLists.txt
|
||||||
хост записывает в БД: uid ↔ fw_version ↔ bootloader_version
|
├── README.md ← этот файл
|
||||||
↓
|
└── src/
|
||||||
хост посылает provision_ack
|
├── main.c # точка входа, init, main loop
|
||||||
↓
|
├── cli.h # публичный API CLI
|
||||||
плата устанавливает provisioned = true
|
├── cli.c # буферизация RX, парсинг "cmd", dispatch
|
||||||
```
|
└── tests/
|
||||||
|
├── test_sdram.c # SDRAM_TEST / SDRAM_TEST_FULL [TODO]
|
||||||
Вся логика на стороне хоста (запись в БД, генерация сертификата, привязка партии). Прошивка только читает UID и ждёт подтверждения.
|
├── test_sdram.h
|
||||||
|
├── test_led.c # LED_ON / LED_OFF [TODO]
|
||||||
---
|
├── test_led.h
|
||||||
|
├── test_button.c # BUTTON_READ [TODO]
|
||||||
## 7. Реестр тестов
|
└── test_button.h
|
||||||
|
|
||||||
```c
|
|
||||||
/* test_registry.c */
|
|
||||||
static const test_module_t *tests[] = {
|
|
||||||
&test_sdram, /* critical */
|
|
||||||
&test_qspi, /* critical */
|
|
||||||
&test_usd, /* critical, SKIP если нет карты */
|
|
||||||
&test_rtc,
|
|
||||||
&test_display, /* operator confirm */
|
|
||||||
&test_ir, /* self-test уровень */
|
|
||||||
&test_can, /* requires_hil */
|
|
||||||
&test_uart_ttl, /* requires_hil */
|
|
||||||
&test_uart_iso, /* requires_hil */
|
|
||||||
&test_opto, /* requires_hil */
|
|
||||||
&test_ir_hil, /* requires_hil, SKIP если нет IR на стенде */
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Статусы тестов
|
## Добавление новой команды
|
||||||
|
|
||||||
| Статус | Значение |
|
1. В `cli.c` объявить обработчик: `static void cmd_foo(void);`
|
||||||
|--------|-------------------------------------------------------|
|
2. Добавить строку в таблицу `k_cmds[]`: `{ "FOO", cmd_foo }`
|
||||||
| `PASS` | Тест прошёл успешно |
|
3. Реализовать обработчик в секции `Command handlers`
|
||||||
| `FAIL` | Тест провален, в `detail` диагностическая информация |
|
4. Добавить тест в `tests/host/cli/test_cli.c`
|
||||||
| `SKIP` | Тест пропущен (нет карты, нет Firefly, таймаут оператора) |
|
5. Обновить таблицу команд в этом README
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Открытые вопросы
|
## Сборка и прошивка
|
||||||
|
|
||||||
- [ ] Формат `detail` при FAIL для каждого теста (договориться между разработчиками)
|
```bash
|
||||||
- [ ] Handshake-протокол между firmware_test и Firefly (как плата узнаёт о готовности стенда)
|
# devcontainer
|
||||||
- [ ] Полная схема интерфейсной платы для Firefly (оптовходы, IR LED, уровни +24V)
|
just build::build-firmware-test-debug
|
||||||
- [ ] GUI на сервере: формат отображения `summary` и хранение истории плат
|
just build::hab-firmware-test-debug
|
||||||
|
|
||||||
|
# хост (плата в SDP-режиме: BOOT_MOD_1 → 3V3 → Reset)
|
||||||
|
just host::flash-test-debug
|
||||||
|
|
||||||
|
# вернуть в нормальный режим
|
||||||
|
# BOOT_MOD_1 → GND → Reset
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Host unit-тесты CLI парсера (devcontainer, без железа)
|
||||||
|
just build::test-host # → test_cli
|
||||||
|
|
||||||
|
# HIL тест USB CDC (хост, требует подключённой платы)
|
||||||
|
just host::hil-usb-cdc
|
||||||
|
|
||||||
|
# HIL тест SDRAM (хост, firmware_test прошит в Flash) ????????
|
||||||
|
just host::hil-sdram # быстрый
|
||||||
|
just host::hil-sdram-full # полный (~60 с)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Переменные окружения
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HIL_USB_CDC_PORT= # порт USB CDC таргета, например /dev/ttyACM1
|
||||||
|
HIL_USB_CDC_BAUD= # 115200
|
||||||
|
HIL_USB_CDC_TIMEOUT= # 5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Шаблон: `.env.example`. Актуальные значения: `.env` (не коммитится).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Известные ограничения
|
||||||
|
|
||||||
|
- **Один пакет за цикл**: `cli_process()` читает один USB bulk-пакет за вызов
|
||||||
|
главного цикла. При потоке команд без задержки буфер может не успеть
|
||||||
|
обработаться — добавить задержку на стороне хоста между командами (≥10 мс).
|
||||||
|
- **TX неблокирующий**: если TX занят — ответ теряется. Хост должен ждать
|
||||||
|
предыдущий ответ перед отправкой следующей команды.
|
||||||
|
- **Нет персистентного состояния**: при сбросе питания все результаты теряются.
|
||||||
|
pytest должен повторно подключаться и дожидаться `READY`.
|
||||||
|
|
|
||||||
244
firmware/test/TODO.md
Normal file
244
firmware/test/TODO.md
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
# firmware_test — Architecture
|
||||||
|
|
||||||
|
> Target: NXP IMXRT1052CVJ5B
|
||||||
|
> Версия документа: 0.1
|
||||||
|
> Статус: draft
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Назначение
|
||||||
|
|
||||||
|
`firmware_test` — входная тестовая прошивка для проверки работоспособности платы при производстве и во время разработки. Запускается напрямую через BootROM (USB Serial Download), без предварительной прошивки загрузчика. После успешного прохождения всех тестов инициирует фазу провижининга (привязка Chip UID к версиям ПО).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Workflow прошивки платы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BootROM (USB Serial Download, встроен в IMXRT1052)
|
||||||
|
↓
|
||||||
|
firmware_test (залит напрямую)
|
||||||
|
↓ [тесты прошли, provisioning выполнен]
|
||||||
|
Флашим: Bootloader + App
|
||||||
|
↓
|
||||||
|
Ждём heartbeat Bootloader → App
|
||||||
|
↓
|
||||||
|
Плата принята
|
||||||
|
```
|
||||||
|
|
||||||
|
Вариант с предварительной заливкой загрузчика не используется — BootROM является надёжным и всегда доступным recovery-path, не зависящим от состояния Flash.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Высокоуровневая архитектура
|
||||||
|
|
||||||
|
```bash
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ firmware_test │
|
||||||
|
│ │
|
||||||
|
│ USB CDC ──► Protocol ──► Test runner ──► Local UI │
|
||||||
|
│ (JSON-lines) (sequencer) (LED+disp) │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────┐ ┌──────────────────────┐ │
|
||||||
|
│ │ self-tests │ │ HIL tests │ │
|
||||||
|
│ │ SDRAM QSPI uSD │ │ CAN UART Opto-in │ │
|
||||||
|
│ │ RTC Display IR │ │ UART ISO IR burst │ │
|
||||||
|
│ └─────────────────────┘ └──────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ HAL / BSP │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Компоненты
|
||||||
|
|
||||||
|
### 4.1 USB CDC
|
||||||
|
|
||||||
|
Единственный канал связи с внешним миром. Представляется хосту как виртуальный COM-порт. Инициализируется первым, до запуска тестов. При старте ожидает подключения хоста с таймаутом — если хост не подключился, тесты запускаются автономно.
|
||||||
|
|
||||||
|
### 4.2 Protocol
|
||||||
|
|
||||||
|
Протокол — **JSON-lines**: каждое сообщение является отдельным JSON-объектом, завершённым символом `\n`. Библиотека: cJSON из NXP SDK.
|
||||||
|
|
||||||
|
Направление **хост → плата** (команды):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"type":"cmd","cmd":"run_all"}
|
||||||
|
{"type":"cmd","cmd":"run","id":"sdram"}
|
||||||
|
{"type":"confirm","id":"display","confirmed":true}
|
||||||
|
```
|
||||||
|
|
||||||
|
Направление **плата → хост** (события):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"type":"session_start","fw":"0.1.0","target":"IMXRT1052","uptime_ms":0}
|
||||||
|
{"type":"test_begin","id":"sdram","name":"SDRAM 32MB","critical":true}
|
||||||
|
{"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":"32MB R/W OK"}
|
||||||
|
{"type":"confirm_request","id":"display","timeout_ms":15000}
|
||||||
|
{"type":"abort","reason":"critical_fail","id":"usd"}
|
||||||
|
{"type":"summary","passed":7,"failed":0,"skipped":2,"aborted":false,"overall":"pass"}
|
||||||
|
{"type":"provision_ready","chip_uid":"A3F2C1B400E70012"}
|
||||||
|
{"type":"provision_ack","fw":"1.0.0","bootloader":"1.0.0","recorded":true}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Примечание:** `uptime_ms` вместо Unix timestamp — RTC может быть не инициализирован на новой плате. Хост приклеивает реальное время самостоятельно.
|
||||||
|
|
||||||
|
### 4.3 Test runner
|
||||||
|
|
||||||
|
Центральный компонент. Хранит реестр тест-модулей, управляет порядком запуска, обрабатывает критические сбои, формирует `summary`.
|
||||||
|
|
||||||
|
**Порядок выполнения:**
|
||||||
|
|
||||||
|
1. Self-tests в порядке реестра
|
||||||
|
2. Проверка критических сбоев — если есть, HIL не запускается
|
||||||
|
3. HIL tests (только если Firefly подключён и self-tests прошли)
|
||||||
|
4. Summary report
|
||||||
|
5. Provisioning (только при `overall == pass`)
|
||||||
|
|
||||||
|
**Интерфейс тест-модуля (`test_module.h`):**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
typedef enum {
|
||||||
|
TEST_STATUS_PASS = 0,
|
||||||
|
TEST_STATUS_FAIL,
|
||||||
|
TEST_STATUS_SKIP,
|
||||||
|
} test_status_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
test_status_t status;
|
||||||
|
uint32_t duration_ms;
|
||||||
|
char detail[96]; /* диагностическая строка, опционально */
|
||||||
|
} test_result_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *id; /* "sdram", "qspi", "can" — ключ в JSON */
|
||||||
|
const char *name; /* "SDRAM 32MB" — для display/лога */
|
||||||
|
bool critical; /* abort HIL если FAIL */
|
||||||
|
bool requires_hil; /* пропустить если Firefly не готов */
|
||||||
|
void (*init)(void);
|
||||||
|
test_result_t (*run)(void);
|
||||||
|
void (*deinit)(void);
|
||||||
|
} test_module_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Local UI
|
||||||
|
|
||||||
|
Отображает текущее состояние тестирования на светодиодах и дисплее. Получает события от Test runner. Дисплей при этом является частью тест-процесса (display_test).
|
||||||
|
|
||||||
|
**LED-паттерны:**
|
||||||
|
|
||||||
|
| Состояние | LED1 | LED2 |
|
||||||
|
|------------------------|-------------|-------------|
|
||||||
|
| Тест выполняется | мигает | выкл |
|
||||||
|
| Все тесты PASS | вкл | выкл |
|
||||||
|
| Есть FAIL | выкл | вкл |
|
||||||
|
| Ожидание подтверждения | оба мигают | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Тест-модули
|
||||||
|
|
||||||
|
### 5.1 Self-tests
|
||||||
|
|
||||||
|
| ID | Название | Critical | Описание |
|
||||||
|
|------------|------------------|----------|--------------------------------------------------|
|
||||||
|
| `sdram` | SDRAM 32MB | ✅ | Write/read паттерны по всему объёму |
|
||||||
|
| `qspi` | QSPI Flash | ✅ | JEDEC ID + запись/чтение тестового сектора |
|
||||||
|
| `usd` | uSD (SDIO) | ✅ | Mount + R/W тестового файла (SKIP если нет карты)|
|
||||||
|
| `rtc` | RTC BM8563 | — | I2C presence, set/get time |
|
||||||
|
| `display` | Display RGB888 | — | R/G/B/W заливки, подтверждение оператором |
|
||||||
|
| `ir` | IR receiver | — | GPIO idle state HIGH, peripheral init |
|
||||||
|
|
||||||
|
**Display test — логика подтверждения:**
|
||||||
|
|
||||||
|
- Плата посылает `confirm_request` с `timeout_ms: 15000`
|
||||||
|
- Оператор нажимает **одну** кнопку: PASS или FAIL
|
||||||
|
- Если кнопка не нажата за 15 секунд — статус `SKIP` (ответственность на операторе)
|
||||||
|
- Одновременно проверяются обе кнопки — это полноценный тест кнопок
|
||||||
|
|
||||||
|
### 5.2 HIL tests (требуют Firefly AIO-3588Q)
|
||||||
|
|
||||||
|
| ID | Название | Стенд | Описание |
|
||||||
|
|--------------|------------------|------------------------------|----------------------------------------|
|
||||||
|
| `can` | CAN | Firefly CAN | Обмен фреймами, full-duplex |
|
||||||
|
| `uart_ttl` | UART TTL | Firefly UART | Echo паттерн |
|
||||||
|
| `uart_iso` | UART ISO +24V | Firefly UART + интерф. плата | Только RX, Firefly посылает |
|
||||||
|
| `opto` | Opto-in +24V | Firefly GPIO + интерф. плата | Все каналы, Firefly дёргает GPIO |
|
||||||
|
| `ir_hil` | IR burst | Firefly GPIO + IR LED | Приём burst 38 кГц, факт прерывания |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Provisioning
|
||||||
|
|
||||||
|
Выполняется после `summary: overall == pass`. Не является тестом — это отдельный этап жизненного цикла платы.
|
||||||
|
|
||||||
|
**Источник UID:** регистры OCOTP (One-Time Programmable fuses), 64-bit Chip UID. Читается через HAL.
|
||||||
|
|
||||||
|
**Интерфейс (`provisioning.h`):**
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
char chip_uid[17]; /* 64-bit UID как hex-строка, null-terminated */
|
||||||
|
char fw_version[16];
|
||||||
|
char bootloader_version[16];
|
||||||
|
bool provisioned;
|
||||||
|
} provision_info_t;
|
||||||
|
|
||||||
|
/* вызывается только при overall == PASS */
|
||||||
|
void provisioning_run(provision_info_t *out);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Поток:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
плата посылает provision_ready + chip_uid
|
||||||
|
↓
|
||||||
|
хост записывает в БД: uid ↔ fw_version ↔ bootloader_version
|
||||||
|
↓
|
||||||
|
хост посылает provision_ack
|
||||||
|
↓
|
||||||
|
плата устанавливает provisioned = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Вся логика на стороне хоста (запись в БД, генерация сертификата, привязка партии). Прошивка только читает UID и ждёт подтверждения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Реестр тестов
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* test_registry.c */
|
||||||
|
static const test_module_t *tests[] = {
|
||||||
|
&test_sdram, /* critical */
|
||||||
|
&test_qspi, /* critical */
|
||||||
|
&test_usd, /* critical, SKIP если нет карты */
|
||||||
|
&test_rtc,
|
||||||
|
&test_display, /* operator confirm */
|
||||||
|
&test_ir, /* self-test уровень */
|
||||||
|
&test_can, /* requires_hil */
|
||||||
|
&test_uart_ttl, /* requires_hil */
|
||||||
|
&test_uart_iso, /* requires_hil */
|
||||||
|
&test_opto, /* requires_hil */
|
||||||
|
&test_ir_hil, /* requires_hil, SKIP если нет IR на стенде */
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Статусы тестов
|
||||||
|
|
||||||
|
| Статус | Значение |
|
||||||
|
|--------|-------------------------------------------------------|
|
||||||
|
| `PASS` | Тест прошёл успешно |
|
||||||
|
| `FAIL` | Тест провален, в `detail` диагностическая информация |
|
||||||
|
| `SKIP` | Тест пропущен (нет карты, нет Firefly, таймаут оператора) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Открытые вопросы
|
||||||
|
|
||||||
|
- [ ] Формат `detail` при FAIL для каждого теста (договориться между разработчиками)
|
||||||
|
- [ ] Handshake-протокол между firmware_test и Firefly (как плата узнаёт о готовности стенда)
|
||||||
|
- [ ] Полная схема интерфейсной платы для Firefly (оптовходы, IR LED, уровни +24V)
|
||||||
|
- [ ] GUI на сервере: формат отображения `summary` и хранение истории плат
|
||||||
|
|
@ -1,268 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1360" viewBox="0 0 680 1180">
|
|
||||||
<defs>
|
|
||||||
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
|
||||||
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</marker>
|
|
||||||
<style>
|
|
||||||
text { font-family: system-ui, sans-serif; fill: #1a1a1a; }
|
|
||||||
.th { font-size: 14px; font-weight: 500; }
|
|
||||||
.ts { font-size: 12px; font-weight: 400; fill: #555; }
|
|
||||||
.label-section { font-size: 11px; font-weight: 400; fill: #888; }
|
|
||||||
|
|
||||||
/* teal */
|
|
||||||
.c-teal rect, .c-teal circle { fill: #E1F5EE; stroke: #0F6E56; }
|
|
||||||
.c-teal .th { fill: #085041; }
|
|
||||||
.c-teal .ts { fill: #0F6E56; }
|
|
||||||
|
|
||||||
/* purple */
|
|
||||||
.c-purple rect { fill: #EEEDFE; stroke: #534AB7; }
|
|
||||||
.c-purple .th { fill: #3C3489; }
|
|
||||||
.c-purple .ts { fill: #534AB7; }
|
|
||||||
|
|
||||||
/* amber */
|
|
||||||
.c-amber rect { fill: #FAEEDA; stroke: #854F0B; }
|
|
||||||
.c-amber .th { fill: #633806; }
|
|
||||||
.c-amber .ts { fill: #854F0B; }
|
|
||||||
|
|
||||||
/* gray */
|
|
||||||
.c-gray rect { fill: #F1EFE8; stroke: #5F5E5A; }
|
|
||||||
.c-gray .th { fill: #2C2C2A; }
|
|
||||||
.c-gray .ts { fill: #5F5E5A; }
|
|
||||||
|
|
||||||
/* red */
|
|
||||||
.c-red rect { fill: #FCEBEB; stroke: #A32D2D; }
|
|
||||||
.c-red .th { fill: #501313; }
|
|
||||||
.c-red .ts { fill: #A32D2D; }
|
|
||||||
|
|
||||||
.arr { stroke: #888; stroke-width: 1; fill: none; }
|
|
||||||
.box-dashed { fill: none; stroke: #bbb; stroke-width: 0.8; stroke-dasharray: 5 3; }
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════════════
|
|
||||||
DIAGRAM 1: High-level architecture
|
|
||||||
═══════════════════════════════════════════════════════ -->
|
|
||||||
|
|
||||||
<text x="340" y="30" text-anchor="middle" class="th" style="font-size:16px;fill:#1a1a1a;">firmware_test · high-level architecture</text>
|
|
||||||
<text x="340" y="48" text-anchor="middle" class="ts">NXP IMXRT1052CVJ5B</text>
|
|
||||||
|
|
||||||
<rect x="20" y="60" width="640" height="440" rx="16" class="box-dashed"/>
|
|
||||||
<text x="340" y="78" text-anchor="middle" class="label-section">firmware_test</text>
|
|
||||||
|
|
||||||
<!-- USB CDC -->
|
|
||||||
<g class="c-teal">
|
|
||||||
<rect x="40" y="88" width="160" height="50" rx="8" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="120" y="108" text-anchor="middle" dominant-baseline="central">USB CDC</text>
|
|
||||||
<text class="ts" x="120" y="126" text-anchor="middle" dominant-baseline="central">virtual serial port</text>
|
|
||||||
</g>
|
|
||||||
<text class="ts" x="212" y="107" fill="#888">↔ Host PC</text>
|
|
||||||
<text class="ts" x="212" y="123" fill="#888">(терминал / GUI)</text>
|
|
||||||
|
|
||||||
<line x1="120" y1="138" x2="120" y2="180" class="arr" marker-end="url(#arrow)"/>
|
|
||||||
|
|
||||||
<!-- Protocol -->
|
|
||||||
<g class="c-purple">
|
|
||||||
<rect x="40" y="180" width="160" height="50" rx="8" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="120" y="200" text-anchor="middle" dominant-baseline="central">Protocol</text>
|
|
||||||
<text class="ts" x="120" y="218" text-anchor="middle" dominant-baseline="central">JSON-lines · cJSON</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- Test runner -->
|
|
||||||
<g class="c-purple">
|
|
||||||
<rect x="250" y="180" width="190" height="50" rx="8" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="345" y="200" text-anchor="middle" dominant-baseline="central">Test runner</text>
|
|
||||||
<text class="ts" x="345" y="218" text-anchor="middle" dominant-baseline="central">sequencer + registry</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- Local UI -->
|
|
||||||
<g class="c-amber">
|
|
||||||
<rect x="490" y="180" width="150" height="50" rx="8" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="565" y="200" text-anchor="middle" dominant-baseline="central">Local UI</text>
|
|
||||||
<text class="ts" x="565" y="218" text-anchor="middle" dominant-baseline="central">LEDs + display</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<line x1="200" y1="205" x2="250" y2="205" class="arr" marker-start="url(#arrow)" marker-end="url(#arrow)" stroke="#666"/>
|
|
||||||
<line x1="440" y1="205" x2="490" y2="205" class="arr" marker-end="url(#arrow)" stroke="#666"/>
|
|
||||||
|
|
||||||
<path d="M345,230 L345,260 L180,260 L180,270" fill="none" stroke="#bbb" stroke-width="0.8" marker-end="url(#arrow)"/>
|
|
||||||
<path d="M345,230 L345,260 L505,260 L505,270" fill="none" stroke="#bbb" stroke-width="0.8" marker-end="url(#arrow)"/>
|
|
||||||
|
|
||||||
<!-- Self-tests region -->
|
|
||||||
<rect x="30" y="270" width="300" height="150" rx="8" class="box-dashed"/>
|
|
||||||
<text class="label-section" x="56" y="287">self-tests</text>
|
|
||||||
|
|
||||||
<g class="c-gray"><rect x="46" y="295" width="76" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="84" y="312" text-anchor="middle" dominant-baseline="central">SDRAM</text>
|
|
||||||
<text class="ts" x="84" y="328" text-anchor="middle" dominant-baseline="central">32 MB</text></g>
|
|
||||||
<g class="c-gray"><rect x="132" y="295" width="76" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="170" y="312" text-anchor="middle" dominant-baseline="central">QSPI</text>
|
|
||||||
<text class="ts" x="170" y="328" text-anchor="middle" dominant-baseline="central">W25Q128</text></g>
|
|
||||||
<g class="c-gray"><rect x="218" y="295" width="76" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="256" y="312" text-anchor="middle" dominant-baseline="central">uSD</text>
|
|
||||||
<text class="ts" x="256" y="328" text-anchor="middle" dominant-baseline="central">SDIO</text></g>
|
|
||||||
<g class="c-gray"><rect x="46" y="349" width="76" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="84" y="366" text-anchor="middle" dominant-baseline="central">RTC</text>
|
|
||||||
<text class="ts" x="84" y="382" text-anchor="middle" dominant-baseline="central">BM8563</text></g>
|
|
||||||
<g class="c-gray"><rect x="132" y="349" width="76" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="170" y="366" text-anchor="middle" dominant-baseline="central">Display</text>
|
|
||||||
<text class="ts" x="170" y="382" text-anchor="middle" dominant-baseline="central">operator ✓</text></g>
|
|
||||||
<g class="c-gray"><rect x="218" y="349" width="76" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="256" y="366" text-anchor="middle" dominant-baseline="central">IR</text>
|
|
||||||
<text class="ts" x="256" y="382" text-anchor="middle" dominant-baseline="central">idle GPIO</text></g>
|
|
||||||
|
|
||||||
<!-- HIL tests region -->
|
|
||||||
<rect x="350" y="270" width="290" height="150" rx="8" class="box-dashed"/>
|
|
||||||
<text class="label-section" x="376" y="287">HIL tests · Firefly AIO-3588Q</text>
|
|
||||||
|
|
||||||
<g class="c-teal"><rect x="366" y="295" width="120" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="426" y="312" text-anchor="middle" dominant-baseline="central">CAN</text>
|
|
||||||
<text class="ts" x="426" y="328" text-anchor="middle" dominant-baseline="central">frame exchange</text></g>
|
|
||||||
<g class="c-teal"><rect x="496" y="295" width="120" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="556" y="312" text-anchor="middle" dominant-baseline="central">UART TTL</text>
|
|
||||||
<text class="ts" x="556" y="328" text-anchor="middle" dominant-baseline="central">echo pattern</text></g>
|
|
||||||
<g class="c-teal"><rect x="366" y="349" width="120" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="426" y="366" text-anchor="middle" dominant-baseline="central">UART ISO</text>
|
|
||||||
<text class="ts" x="426" y="382" text-anchor="middle" dominant-baseline="central">+24V RX only</text></g>
|
|
||||||
<g class="c-teal"><rect x="496" y="349" width="120" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="556" y="366" text-anchor="middle" dominant-baseline="central">Opto-in</text>
|
|
||||||
<text class="ts" x="556" y="382" text-anchor="middle" dominant-baseline="central">+24V all ch.</text></g>
|
|
||||||
|
|
||||||
<!-- HAL / BSP -->
|
|
||||||
<line x1="340" y1="420" x2="340" y2="448" class="arr" marker-end="url(#arrow)" stroke="#aaa"/>
|
|
||||||
<g class="c-gray">
|
|
||||||
<rect x="30" y="448" width="620" height="40" rx="8" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="340" y="468" text-anchor="middle" dominant-baseline="central">HAL / BSP</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════════════
|
|
||||||
DIAGRAM 2: Test runner flow
|
|
||||||
═══════════════════════════════════════════════════════ -->
|
|
||||||
|
|
||||||
<text x="340" y="540" text-anchor="middle" class="th" style="font-size:16px;fill:#1a1a1a;">firmware_test · test runner flow</text>
|
|
||||||
|
|
||||||
<!-- Boot -->
|
|
||||||
<g class="c-gray">
|
|
||||||
<rect x="240" y="558" width="200" height="44" rx="8" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="340" y="576" text-anchor="middle" dominant-baseline="central">Boot</text>
|
|
||||||
<text class="ts" x="340" y="592" text-anchor="middle" dominant-baseline="central">clock, USB CDC, HAL init</text>
|
|
||||||
</g>
|
|
||||||
<line x1="340" y1="602" x2="340" y2="630" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
|
||||||
|
|
||||||
<!-- Wait for host -->
|
|
||||||
<g class="c-purple">
|
|
||||||
<rect x="240" y="630" width="200" height="44" rx="8" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="340" y="648" text-anchor="middle" dominant-baseline="central">Wait for host</text>
|
|
||||||
<text class="ts" x="340" y="664" text-anchor="middle" dominant-baseline="central">USB connect or timeout</text>
|
|
||||||
</g>
|
|
||||||
<line x1="340" y1="674" x2="340" y2="698" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
|
||||||
|
|
||||||
<!-- Self-tests block -->
|
|
||||||
<rect x="30" y="698" width="620" height="168" rx="10" class="box-dashed"/>
|
|
||||||
<text class="label-section" x="56" y="714">self-tests</text>
|
|
||||||
|
|
||||||
<g class="c-red">
|
|
||||||
<rect x="46" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="101" y="740" text-anchor="middle" dominant-baseline="central">SDRAM</text>
|
|
||||||
<text class="ts" x="101" y="756" text-anchor="middle" dominant-baseline="central">critical</text>
|
|
||||||
<text class="ts" x="101" y="768" text-anchor="middle" dominant-baseline="central">write/read pattern</text>
|
|
||||||
</g>
|
|
||||||
<g class="c-red">
|
|
||||||
<rect x="166" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="221" y="740" text-anchor="middle" dominant-baseline="central">QSPI Flash</text>
|
|
||||||
<text class="ts" x="221" y="756" text-anchor="middle" dominant-baseline="central">critical</text>
|
|
||||||
<text class="ts" x="221" y="768" text-anchor="middle" dominant-baseline="central">JEDEC ID + R/W</text>
|
|
||||||
</g>
|
|
||||||
<g class="c-red">
|
|
||||||
<rect x="286" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="341" y="740" text-anchor="middle" dominant-baseline="central">uSD</text>
|
|
||||||
<text class="ts" x="341" y="756" text-anchor="middle" dominant-baseline="central">critical</text>
|
|
||||||
<text class="ts" x="341" y="768" text-anchor="middle" dominant-baseline="central">mount + R/W</text>
|
|
||||||
</g>
|
|
||||||
<g class="c-gray">
|
|
||||||
<rect x="406" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="461" y="740" text-anchor="middle" dominant-baseline="central">RTC</text>
|
|
||||||
<text class="ts" x="461" y="756" text-anchor="middle" dominant-baseline="central">I2C presence</text>
|
|
||||||
<text class="ts" x="461" y="768" text-anchor="middle" dominant-baseline="central">set / get</text>
|
|
||||||
</g>
|
|
||||||
<g class="c-amber">
|
|
||||||
<rect x="526" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="581" y="740" text-anchor="middle" dominant-baseline="central">Display</text>
|
|
||||||
<text class="ts" x="581" y="756" text-anchor="middle" dominant-baseline="central">R/G/B/W fill</text>
|
|
||||||
<text class="ts" x="581" y="768" text-anchor="middle" dominant-baseline="central">btn confirm</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<line x1="101" y1="774" x2="101" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
|
||||||
<line x1="221" y1="774" x2="221" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
|
||||||
<line x1="341" y1="774" x2="341" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
|
||||||
<line x1="461" y1="774" x2="461" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
|
||||||
<line x1="581" y1="774" x2="581" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
|
||||||
|
|
||||||
<!-- Check critical failures -->
|
|
||||||
<g class="c-gray">
|
|
||||||
<rect x="46" y="794" width="590" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="341" y="812" text-anchor="middle" dominant-baseline="central">Check critical failures</text>
|
|
||||||
<text class="ts" x="341" y="828" text-anchor="middle" dominant-baseline="central">SDRAM || QSPI || uSD FAIL → abort HIL, report immediately</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<line x1="340" y1="866" x2="340" y2="890" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
|
||||||
|
|
||||||
<!-- HIL block -->
|
|
||||||
<rect x="30" y="890" width="620" height="78" rx="10" class="box-dashed"/>
|
|
||||||
<text class="label-section" x="56" y="906">HIL tests · requires Firefly</text>
|
|
||||||
|
|
||||||
<g class="c-teal">
|
|
||||||
<rect x="46" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="96" y="930" text-anchor="middle" dominant-baseline="central">CAN</text>
|
|
||||||
<text class="ts" x="96" y="946" text-anchor="middle" dominant-baseline="central">full-duplex</text>
|
|
||||||
</g>
|
|
||||||
<g class="c-teal">
|
|
||||||
<rect x="156" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="206" y="930" text-anchor="middle" dominant-baseline="central">UART TTL</text>
|
|
||||||
<text class="ts" x="206" y="946" text-anchor="middle" dominant-baseline="central">echo</text>
|
|
||||||
</g>
|
|
||||||
<g class="c-teal">
|
|
||||||
<rect x="266" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="316" y="930" text-anchor="middle" dominant-baseline="central">UART ISO</text>
|
|
||||||
<text class="ts" x="316" y="946" text-anchor="middle" dominant-baseline="central">RX only</text>
|
|
||||||
</g>
|
|
||||||
<g class="c-teal">
|
|
||||||
<rect x="376" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="426" y="930" text-anchor="middle" dominant-baseline="central">Opto-in</text>
|
|
||||||
<text class="ts" x="426" y="946" text-anchor="middle" dominant-baseline="central">all channels</text>
|
|
||||||
</g>
|
|
||||||
<g class="c-gray">
|
|
||||||
<rect x="486" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="536" y="930" text-anchor="middle" dominant-baseline="central">IR burst</text>
|
|
||||||
<text class="ts" x="536" y="946" text-anchor="middle" dominant-baseline="central">38 kHz RX</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<line x1="340" y1="968" x2="340" y2="996" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
|
||||||
|
|
||||||
<!-- Summary -->
|
|
||||||
<g class="c-teal">
|
|
||||||
<rect x="160" y="996" width="360" height="50" rx="8" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="340" y="1016" text-anchor="middle" dominant-baseline="central">Summary report</text>
|
|
||||||
<text class="ts" x="340" y="1034" text-anchor="middle" dominant-baseline="central">JSON · pass/fail/skip per test · overall</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<line x1="340" y1="1046" x2="340" y2="1074" class="arr" marker-end="url(#arrow)" stroke="#aaa" stroke-dasharray="4 3"/>
|
|
||||||
|
|
||||||
<!-- Provisioning -->
|
|
||||||
<g class="c-purple">
|
|
||||||
<rect x="160" y="1074" width="360" height="50" rx="8" stroke-width="0.5"/>
|
|
||||||
<text class="th" x="340" y="1094" text-anchor="middle" dominant-baseline="central">Provisioning</text>
|
|
||||||
<text class="ts" x="340" y="1112" text-anchor="middle" dominant-baseline="central">OCOTP chip_uid → host DB · only on pass</text>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<text class="ts" x="536" y="1050" fill="#aaa">only if overall == pass</text>
|
|
||||||
|
|
||||||
<!-- Legend -->
|
|
||||||
<rect x="30" y="1140" width="260" height="30" rx="6" fill="none" stroke="#eee" stroke-width="0.5"/>
|
|
||||||
<rect x="40" y="1151" width="12" height="8" rx="2" fill="#FCEBEB" stroke="#A32D2D" stroke-width="0.5"/>
|
|
||||||
<text class="ts" x="58" y="1159">critical test</text>
|
|
||||||
<rect x="130" y="1151" width="12" height="8" rx="2" fill="#E1F5EE" stroke="#0F6E56" stroke-width="0.5"/>
|
|
||||||
<text class="ts" x="148" y="1159">HIL test</text>
|
|
||||||
<rect x="200" y="1151" width="12" height="8" rx="2" fill="#FAEEDA" stroke="#854F0B" stroke-width="0.5"/>
|
|
||||||
<text class="ts" x="218" y="1159">operator</text>
|
|
||||||
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 15 KiB |
|
|
@ -1,51 +0,0 @@
|
||||||
|
|
||||||
#include "board.h"
|
|
||||||
#include "bsp/led.h"
|
|
||||||
#include "bsp/tick.h"
|
|
||||||
#include "bsp/uart_host.h"
|
|
||||||
#include "bsp/usb_cdc.h"
|
|
||||||
#include "log/log.h"
|
|
||||||
#include "port/log_uart.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
const uint16_t DELAY_MS = 100;
|
|
||||||
const uint32_t UART_BAUDRATE = 115200;
|
|
||||||
board_hw_init();
|
|
||||||
|
|
||||||
bsp_led_init();
|
|
||||||
bsp_tick_init();
|
|
||||||
bsp_uart_host_init(UART_BAUDRATE);
|
|
||||||
log_uart_init();
|
|
||||||
|
|
||||||
LOG_I("BOOT", "firmware_test started, tick=%lu", (unsigned long) bsp_tick_get_ms());
|
|
||||||
|
|
||||||
if (bsp_usb_cdc_init() == BSP_OK)
|
|
||||||
{
|
|
||||||
LOG_I("BOOT", "USB CDC ACM initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
bsp_led_on(LED_APP);
|
|
||||||
bool is_connection_established = false;
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
if (bsp_usb_cdc_is_ready() == true && is_connection_established == false)
|
|
||||||
{
|
|
||||||
is_connection_established = true;
|
|
||||||
LOG_I("BOOT", "USB CDC ACM ready");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_connection_established == true)
|
|
||||||
{
|
|
||||||
const char *msg = "Hello from TFT Board\r\n";
|
|
||||||
bsp_usb_cdc_write((const uint8_t *) msg, strlen(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
bsp_usb_cdc_poll();
|
|
||||||
bsp_led_toggle(LED_HEARTBEAT);
|
|
||||||
bsp_delay(DELAY_MS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
191
firmware/test/src/cli.c
Normal file
191
firmware/test/src/cli.c
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
/**
|
||||||
|
* @file cli.c
|
||||||
|
* @brief Реализация CLI для firmware_test.
|
||||||
|
*
|
||||||
|
* Транспорт: USB CDC ACM через bsp_usb_cdc — единственный канал.
|
||||||
|
* Парсинг JSON минималистичный: strstr по полю "cmd".
|
||||||
|
* Полноценный JSON-парсер (cJSON) не используется намеренно —
|
||||||
|
* схема фиксирована, единственное входящее поле — "cmd".
|
||||||
|
*
|
||||||
|
* Добавление новой команды:
|
||||||
|
* 1. Объявить static void cmd_foo(void); выше таблицы.
|
||||||
|
* 2. Добавить { "FOO", cmd_foo } в k_cmds[].
|
||||||
|
* 3. Реализовать обработчик ниже раздела "Command handlers".
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "cli.h"
|
||||||
|
|
||||||
|
#include "bsp/usb_cdc.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* ── Forward declarations ──────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static void cmd_ping(void);
|
||||||
|
|
||||||
|
/* ── Command table ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
typedef void (*cli_handler_t)(void);
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
const char *name;
|
||||||
|
cli_handler_t handler;
|
||||||
|
} cli_cmd_t;
|
||||||
|
|
||||||
|
static const cli_cmd_t k_cmds[] = {
|
||||||
|
{ "PING", cmd_ping },
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CLI_CMD_COUNT (sizeof(k_cmds) / sizeof(k_cmds[0]))
|
||||||
|
|
||||||
|
/* ── RX line buffer ────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static uint8_t g_s_line_buf[CLI_LINE_BUF_SIZE];
|
||||||
|
static size_t g_s_line_len = 0U;
|
||||||
|
|
||||||
|
/* ── Internal helpers ──────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Извлечь значение поля "cmd" из JSON-строки.
|
||||||
|
*
|
||||||
|
* Ищет паттерн ` "cmd":"<ASCII без кавычек и обратных слэшей>"\n`. Без рекурсии и динамической памяти.
|
||||||
|
*
|
||||||
|
* @param[in] line NULL-terminated входная строка.
|
||||||
|
* @param[out] out Буфер для записи значения.
|
||||||
|
* @param[in] out_size Размер out (включая место под '\0').
|
||||||
|
* @return true если поле найдено и значение помещается в out.
|
||||||
|
*/
|
||||||
|
static bool parse_cmd_field(const char *p_line, char *p_out, size_t out_size)
|
||||||
|
{
|
||||||
|
const char *key = strstr(p_line, "\"cmd\"");
|
||||||
|
if (key == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *colon = strchr(key + 5U, ':');
|
||||||
|
if (colon == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *open_q = strchr(colon + 1U, '"');
|
||||||
|
if (open_q == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
open_q++; /* skip the opening quote */
|
||||||
|
|
||||||
|
const char *close_q = strchr(open_q, '"');
|
||||||
|
if (close_q == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = (size_t) (close_q - open_q);
|
||||||
|
if (len == 0U || len >= out_size)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(p_out, open_q, len);
|
||||||
|
p_out[len] = '\0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Найти и вызвать обработчик команды.
|
||||||
|
*
|
||||||
|
* Если команда не найдена — отправить UNKNOWN_CMD.
|
||||||
|
*/
|
||||||
|
static void dispatch(const char *p_cmd_name)
|
||||||
|
{
|
||||||
|
for (size_t i = 0U; i < CLI_CMD_COUNT; i++)
|
||||||
|
{
|
||||||
|
if (strcmp(k_cmds[i].name, p_cmd_name) == 0)
|
||||||
|
{
|
||||||
|
k_cmds[i].handler();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cli_send("{\"ok\":false,\"error\":\"UNKNOWN_CMD\"}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Обработать одну накопленную строку (без завершающего '\n').
|
||||||
|
*/
|
||||||
|
static void process_line(const char *p_line)
|
||||||
|
{
|
||||||
|
|
||||||
|
const uint8_t MAX_CMD_LEN = 32U;
|
||||||
|
char cmd_name[MAX_CMD_LEN];
|
||||||
|
|
||||||
|
if (!parse_cmd_field(p_line, cmd_name, sizeof(cmd_name)))
|
||||||
|
{
|
||||||
|
cli_send("{\"ok\":false,\"error\":\"PARSE_ERR\"}\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(cmd_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Public API ────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
void cli_init(void)
|
||||||
|
{
|
||||||
|
g_s_line_len = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cli_send(const char *p_resp)
|
||||||
|
{
|
||||||
|
bsp_usb_cdc_write((const uint8_t *) p_resp, strlen(p_resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
void cli_process(void)
|
||||||
|
{
|
||||||
|
uint8_t chunk[CLI_LINE_BUF_SIZE];
|
||||||
|
|
||||||
|
size_t nbytes = bsp_usb_cdc_read(chunk, sizeof(chunk));
|
||||||
|
|
||||||
|
for (size_t byte_idx = 0U; byte_idx < nbytes; byte_idx++)
|
||||||
|
{
|
||||||
|
uint8_t byte = chunk[byte_idx];
|
||||||
|
|
||||||
|
if (g_s_line_len >= (CLI_LINE_BUF_SIZE - 1U))
|
||||||
|
{
|
||||||
|
g_s_line_len = 0U;
|
||||||
|
cli_send("{\"ok\":false,\"error\":\"LINE_TOO_LONG\"}\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte == (uint8_t) '\n')
|
||||||
|
{
|
||||||
|
/* Отбросить '\r' если терминал шлёт CR+LF */
|
||||||
|
if (g_s_line_len > 0U && g_s_line_buf[g_s_line_len - 1U] == (uint8_t) '\r')
|
||||||
|
{
|
||||||
|
g_s_line_len--;
|
||||||
|
}
|
||||||
|
g_s_line_buf[g_s_line_len] = '\0';
|
||||||
|
if (g_s_line_len > 0U)
|
||||||
|
{
|
||||||
|
process_line((const char *) g_s_line_buf);
|
||||||
|
}
|
||||||
|
g_s_line_len = 0U;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_s_line_buf[g_s_line_len] = byte;
|
||||||
|
g_s_line_len++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Command handlers ──────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static void cmd_ping(void)
|
||||||
|
{
|
||||||
|
cli_send("{\"ok\":true,\"result\":\"PONG\"}\n");
|
||||||
|
}
|
||||||
50
firmware/test/src/cli.h
Normal file
50
firmware/test/src/cli.h
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* @file cli.h
|
||||||
|
* @brief Command-line interface для firmware_test.
|
||||||
|
*
|
||||||
|
* Единственный транспорт — USB CDC ACM (bsp_usb_cdc).
|
||||||
|
* Протокол — JSON-lines: каждая строка завершается '\n'.
|
||||||
|
* Запрос: {"cmd":"NAME"}\n
|
||||||
|
* Ответ: {"ok":true,...}\n или {"ok":false,"error":"CODE"}\n
|
||||||
|
*
|
||||||
|
* @note Архитектурное ограничение: этот модуль жёстко связан с
|
||||||
|
* bsp_usb_cdc как единственным IO-каналом. Замена транспорта
|
||||||
|
* не предусмотрена — firmware_test работает только через USB CDC.
|
||||||
|
* Для HIL ELF-прошивок используется отдельный канал связи (UART + bsp_uart_host).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CLI_H_
|
||||||
|
#define CLI_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/** @brief Максимальная длина входящей JSON-строки включая завершающий '\n'. */
|
||||||
|
#define CLI_LINE_BUF_SIZE 128U
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Инициализировать CLI.
|
||||||
|
*
|
||||||
|
* Сбрасывает внутренний буфер строки.
|
||||||
|
* Вызывать после bsp_usb_cdc_init() и до первого cli_process().
|
||||||
|
*/
|
||||||
|
void cli_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Отправить готовую JSON-строку через USB CDC.
|
||||||
|
*
|
||||||
|
* @param[in] resp NUL-terminated строка, завершённая '\n'.
|
||||||
|
*
|
||||||
|
* @note Неблокирующий вызов. Если TX занят — запись теряется.
|
||||||
|
* Для firmware_test это приемлемо: хост повторит запрос.
|
||||||
|
*/
|
||||||
|
void cli_send(const char *resp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Обработать входящие байты и диспатчить команду при получении '\n'.
|
||||||
|
*
|
||||||
|
* Вызывать в главном цикле после bsp_usb_cdc_poll().
|
||||||
|
* Неблокирующий: если данных нет — возвращается немедленно.
|
||||||
|
*/
|
||||||
|
void cli_process(void);
|
||||||
|
|
||||||
|
#endif /* CLI_H_ */
|
||||||
61
firmware/test/src/main.c
Normal file
61
firmware/test/src/main.c
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file main.c
|
||||||
|
* @brief firmware_test — точка входа.
|
||||||
|
*
|
||||||
|
* Bare-metal входной контроль платы.
|
||||||
|
* Единственный канал хост↔таргет: USB CDC ACM (J2).
|
||||||
|
* Протокол: JSON-lines через cli.c.
|
||||||
|
*
|
||||||
|
* Последовательность старта:
|
||||||
|
* 1. board_hw_init() — тактирование, MPU, кэш, пины
|
||||||
|
* 2. bsp_tick_init() — SysTick 1 мс
|
||||||
|
* 3. bsp_led_init() — оба LED выключены
|
||||||
|
* 4. bsp_usb_cdc_init() — PHY + стек + NVIC
|
||||||
|
* 5. Ожидание CDC ready — LED_HEARTBEAT мигает
|
||||||
|
* 6. cli_init() — сброс буфера
|
||||||
|
* 7. READY → хост — JSON сигнал готовности
|
||||||
|
* 8. Главный цикл — poll + cli_process
|
||||||
|
*/
|
||||||
|
#include "board.h"
|
||||||
|
#include "bsp/led.h"
|
||||||
|
#include "bsp/tick.h"
|
||||||
|
#include "bsp/usb_cdc.h"
|
||||||
|
#include "cli.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
const uint32_t CONNECT_BLINK_MS = 200U;
|
||||||
|
const uint32_t ERROR_BLINK_MS = 50;
|
||||||
|
board_hw_init();
|
||||||
|
|
||||||
|
bsp_led_init();
|
||||||
|
bsp_tick_init();
|
||||||
|
|
||||||
|
if (bsp_usb_cdc_init() != BSP_OK)
|
||||||
|
{
|
||||||
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
|
bsp_delay(ERROR_BLINK_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ожидать подключения хоста. LED_HEARTBEAT мигает — прошивка жива. */
|
||||||
|
while (!bsp_usb_cdc_is_ready())
|
||||||
|
{
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
|
bsp_delay(CONNECT_BLINK_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_led_on(LED_APP);
|
||||||
|
|
||||||
|
cli_init();
|
||||||
|
cli_send("{\"ok\":true,\"result\":\"READY\"}\n");
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
cli_process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -158,3 +158,18 @@ add_host_test(
|
||||||
${CMAKE_SOURCE_DIR}/bsp/common/include
|
${CMAKE_SOURCE_DIR}/bsp/common/include
|
||||||
MOCKS
|
MOCKS
|
||||||
${BSP_MOCKS_DIR})
|
${BSP_MOCKS_DIR})
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Тест логики cli
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
add_host_test(
|
||||||
|
NAME
|
||||||
|
test_cli
|
||||||
|
SOURCES
|
||||||
|
cli/test_cli.c
|
||||||
|
INCLUDES
|
||||||
|
${CMAKE_SOURCE_DIR}/firmware/test/src/
|
||||||
|
${CMAKE_SOURCE_DIR}/bsp/common/include
|
||||||
|
${CMAKE_SOURCE_DIR}/bsp/usb_cdc/include
|
||||||
|
MOCKS
|
||||||
|
${BSP_MOCKS_DIR})
|
||||||
|
|
|
||||||
201
tests/host/cli/test_cli.c
Normal file
201
tests/host/cli/test_cli.c
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
/**
|
||||||
|
* @file test_cli.c
|
||||||
|
* @brief Host unit-тест для firmware_test/src/cli.c
|
||||||
|
*
|
||||||
|
* Тестирует: parse_cmd_field (через cli_process), dispatch, обработку
|
||||||
|
* граничных случаев буфера. Без железа, без fff — stub-ы для
|
||||||
|
* bsp_usb_cdc_read/write определены прямо здесь.
|
||||||
|
*
|
||||||
|
* Добавление теста для новой команды:
|
||||||
|
* 1. stub_rx_feed() — подать строку в RX буфер.
|
||||||
|
* 2. cli_process() — запустить.
|
||||||
|
* 3. TEST_ASSERT_EQUAL_STRING() — проверить TX буфер.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bsp/status.h"
|
||||||
|
#include "bsp/usb_cdc.h"
|
||||||
|
#include "unity.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* ── Stub: bsp_usb_cdc ─────────────────────────────────────────────────
|
||||||
|
*
|
||||||
|
* RX: тест кладёт байты через stub_rx_feed().
|
||||||
|
* cli_process() читает их по одному через bsp_usb_cdc_read().
|
||||||
|
*
|
||||||
|
* TX: cli_send() пишет через bsp_usb_cdc_write() в s_tx_buf.
|
||||||
|
* тест проверяет s_tx_buf через stub_tx_get().
|
||||||
|
*
|
||||||
|
* ────────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static uint8_t s_rx_buf[256];
|
||||||
|
static size_t s_rx_len = 0U;
|
||||||
|
static size_t s_rx_pos = 0U;
|
||||||
|
|
||||||
|
static char s_tx_buf[512];
|
||||||
|
static size_t s_tx_len = 0U;
|
||||||
|
|
||||||
|
/** @brief Заполнить RX буфер данными для теста. */
|
||||||
|
static void stub_rx_feed(const char *data)
|
||||||
|
{
|
||||||
|
size_t len = strlen(data);
|
||||||
|
memcpy(s_rx_buf, data, len);
|
||||||
|
s_rx_len = len;
|
||||||
|
s_rx_pos = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Получить TX буфер как C-строку. */
|
||||||
|
static const char *stub_tx_get(void)
|
||||||
|
{
|
||||||
|
s_tx_buf[s_tx_len] = '\0';
|
||||||
|
return s_tx_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Сбросить оба буфера. */
|
||||||
|
static void stub_reset(void)
|
||||||
|
{
|
||||||
|
s_rx_len = 0U;
|
||||||
|
s_rx_pos = 0U;
|
||||||
|
s_tx_len = 0U;
|
||||||
|
memset(s_tx_buf, 0, sizeof(s_tx_buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bsp_usb_cdc stubs */
|
||||||
|
|
||||||
|
size_t bsp_usb_cdc_read(uint8_t *buf, size_t max_len)
|
||||||
|
{
|
||||||
|
size_t available = s_rx_len - s_rx_pos;
|
||||||
|
size_t to_copy = (available < max_len) ? available : max_len;
|
||||||
|
memcpy(buf, s_rx_buf + s_rx_pos, to_copy);
|
||||||
|
s_rx_pos += to_copy;
|
||||||
|
return to_copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_status_t bsp_usb_cdc_write(const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
if ((s_tx_len + len) < sizeof(s_tx_buf))
|
||||||
|
{
|
||||||
|
memcpy(s_tx_buf + s_tx_len, data, len);
|
||||||
|
s_tx_len += len;
|
||||||
|
}
|
||||||
|
return BSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Остальные символы bsp_usb_cdc — заглушки, cli.c не вызывает их */
|
||||||
|
bsp_status_t bsp_usb_cdc_init(void)
|
||||||
|
{
|
||||||
|
return BSP_OK;
|
||||||
|
}
|
||||||
|
bool bsp_usb_cdc_is_ready(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool bsp_usb_cdc_write_ready(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void bsp_usb_cdc_poll(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Включаем тестируемый модуль ПОСЛЕ stub-ов ─────────────────────── */
|
||||||
|
|
||||||
|
#include "cli.c" /* NOLINT(bugprone-suspicious-include) */
|
||||||
|
|
||||||
|
/* ── Фикстуры ──────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
void setUp(void)
|
||||||
|
{
|
||||||
|
stub_reset();
|
||||||
|
cli_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Тесты ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
void test_ping_returns_pong(void)
|
||||||
|
{
|
||||||
|
stub_rx_feed("{\"cmd\":\"PING\"}\n");
|
||||||
|
cli_process();
|
||||||
|
TEST_ASSERT_EQUAL_STRING("{\"ok\":true,\"result\":\"PONG\"}\n", stub_tx_get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_unknown_command(void)
|
||||||
|
{
|
||||||
|
stub_rx_feed("{\"cmd\":\"FOOBAR\"}\n");
|
||||||
|
cli_process();
|
||||||
|
TEST_ASSERT_EQUAL_STRING("{\"ok\":false,\"error\":\"UNKNOWN_CMD\"}\n", stub_tx_get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_missing_cmd_field(void)
|
||||||
|
{
|
||||||
|
stub_rx_feed("{\"foo\":\"bar\"}\n");
|
||||||
|
cli_process();
|
||||||
|
TEST_ASSERT_EQUAL_STRING("{\"ok\":false,\"error\":\"PARSE_ERR\"}\n", stub_tx_get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_empty_line_no_response(void)
|
||||||
|
{
|
||||||
|
/* Пустая строка '\n' — буфер пуст, dispatch не вызывается */
|
||||||
|
stub_rx_feed("\n");
|
||||||
|
cli_process();
|
||||||
|
TEST_ASSERT_EQUAL_STRING("", stub_tx_get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_line_too_long(void)
|
||||||
|
{
|
||||||
|
/* 127 байт + '\n' = ровно граница CLI_LINE_BUF_SIZE */
|
||||||
|
char long_line[CLI_LINE_BUF_SIZE + 2U];
|
||||||
|
memset(long_line, 'A', CLI_LINE_BUF_SIZE);
|
||||||
|
long_line[CLI_LINE_BUF_SIZE] = '\n';
|
||||||
|
long_line[CLI_LINE_BUF_SIZE + 1U] = '\0';
|
||||||
|
|
||||||
|
stub_rx_feed(long_line);
|
||||||
|
cli_process();
|
||||||
|
TEST_ASSERT_EQUAL_STRING("{\"ok\":false,\"error\":\"LINE_TOO_LONG\"}\n", stub_tx_get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_two_commands_in_sequence(void)
|
||||||
|
{
|
||||||
|
/* Два PING подряд — оба должны дать PONG */
|
||||||
|
stub_rx_feed("{\"cmd\":\"PING\"}\n{\"cmd\":\"PING\"}\n");
|
||||||
|
cli_process();
|
||||||
|
TEST_ASSERT_EQUAL_STRING("{\"ok\":true,\"result\":\"PONG\"}\n"
|
||||||
|
"{\"ok\":true,\"result\":\"PONG\"}\n",
|
||||||
|
/* Нет: первый вызов — первый PONG. Сбросим и проверим второй. */
|
||||||
|
stub_tx_get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_cmd_field_with_spaces(void)
|
||||||
|
{
|
||||||
|
/* Пробелы вокруг ':' — реальные клиенты могут так форматировать */
|
||||||
|
stub_rx_feed("{\"cmd\" : \"PING\"}\n");
|
||||||
|
cli_process();
|
||||||
|
TEST_ASSERT_EQUAL_STRING("{\"ok\":true,\"result\":\"PONG\"}\n", stub_tx_get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_partial_input_no_response_until_newline(void)
|
||||||
|
{
|
||||||
|
/* Подать строку без '\n' — ответа быть не должно */
|
||||||
|
stub_rx_feed("{\"cmd\":\"PING\"}");
|
||||||
|
cli_process();
|
||||||
|
TEST_ASSERT_EQUAL_STRING("", stub_tx_get());
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_ping_returns_pong);
|
||||||
|
RUN_TEST(test_unknown_command);
|
||||||
|
RUN_TEST(test_missing_cmd_field);
|
||||||
|
RUN_TEST(test_empty_line_no_response);
|
||||||
|
RUN_TEST(test_line_too_long);
|
||||||
|
RUN_TEST(test_two_commands_in_sequence);
|
||||||
|
RUN_TEST(test_cmd_field_with_spaces);
|
||||||
|
RUN_TEST(test_partial_input_no_response_until_newline);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue