# Pre-release: ready for merge
This commit is contained in:
parent
bfbaf86aed
commit
ae0beda299
12 changed files with 496 additions and 855 deletions
|
|
@ -15,7 +15,8 @@ FLASHLOADER_VID=15a2
|
||||||
FLASHLOADER_PID=0073
|
FLASHLOADER_PID=0073
|
||||||
SERVICE_CDC_VID=1996
|
SERVICE_CDC_VID=1996
|
||||||
SERVICE_CDC_PID=00ad
|
SERVICE_CDC_PID=00ad
|
||||||
|
SERVICE_M5_VID=303a
|
||||||
|
SERVICE_M5_PID=4001
|
||||||
# --- Paths ---
|
# --- Paths ---
|
||||||
# BUILD_DIR и TOOLS_DIR задаются абсолютно в корневом justfile
|
# BUILD_DIR и TOOLS_DIR задаются абсолютно в корневом justfile
|
||||||
# через justfile_directory(), здесь можно переопределить если нужно
|
# через justfile_directory(), здесь можно переопределить если нужно
|
||||||
|
|
@ -61,4 +62,4 @@ HIL_USB_CDC_BAUD=115200
|
||||||
HIL_USB_CDC_TIMEOUT=5.0
|
HIL_USB_CDC_TIMEOUT=5.0
|
||||||
|
|
||||||
# Тип сборки firmware_test для TUI (Debug | Release)
|
# Тип сборки firmware_test для TUI (Debug | Release)
|
||||||
FIRMWARE_BUILD_TYPE=Release
|
FIRMWARE_BUILD_TYPE=Debug
|
||||||
261
.github/workflows/release.yml
vendored
Normal file
261
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,261 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
# Раздельные теги (FIRST_RELEASE_PLAN.md, Шаг 2.1): firmware_test и
|
||||||
|
# service-tui версионируются и релизятся независимо друг от друга.
|
||||||
|
# firmware-vX.Y.Z → publish-firmware (только firmware_test_hab.bin, Debug)
|
||||||
|
# tui-vX.Y.Z → publish-tui (service-tui-vX.Y.Z-{macos,windows}.zip)
|
||||||
|
# HAB firmware_test, вшиваемый в TUI-бандл, всегда собирается заново из
|
||||||
|
# текущего HEAD тега tui-v*, а не берётся из отдельного firmware-релиза —
|
||||||
|
# так проще и не тянет зависимость на чужой GitHub Release.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "tui-v*"
|
||||||
|
- "firmware-v*"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_type:
|
||||||
|
description: "Тестовый прогон без публикации (job publish-* не запускается вне push тега)"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- tui
|
||||||
|
- firmware
|
||||||
|
default: tui
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# firmware — собирает HAB Debug firmware_test. Нужна как для standalone
|
||||||
|
# firmware-релиза, так и для вшивания в TUI-бандл — выполняется всегда.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
firmware:
|
||||||
|
name: Build firmware_test HAB (Debug)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 90
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
|
- name: Build devcontainer image with cache
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: .devcontainer/Dockerfile
|
||||||
|
tags: tft-devcontainer-ci:latest
|
||||||
|
load: true
|
||||||
|
cache-from: type=gha,scope=tft-devcontainer
|
||||||
|
cache-to: type=gha,mode=max,scope=tft-devcontainer
|
||||||
|
|
||||||
|
- name: Sync host Python tools inside container
|
||||||
|
run: |
|
||||||
|
docker run --rm --user root -v "$GITHUB_WORKSPACE":/workspace -w /workspace \
|
||||||
|
tft-devcontainer-ci:latest bash -lc 'cd tools/host && uv sync'
|
||||||
|
|
||||||
|
- name: Проверить, что тег совпадает с версией в CMakeLists.txt
|
||||||
|
if: startsWith(github.ref, 'refs/tags/firmware-v')
|
||||||
|
run: |
|
||||||
|
tag_version="${GITHUB_REF_NAME#firmware-v}"
|
||||||
|
file_version=$(grep -m1 -oE 'VERSION [0-9]+\.[0-9]+\.[0-9]+' firmware/test/CMakeLists.txt | awk '{print $2}')
|
||||||
|
if [[ "$tag_version" != "$file_version" ]]; then
|
||||||
|
echo "::error::Тег ${GITHUB_REF_NAME} (версия ${tag_version}) не совпадает с VERSION в firmware/test/CMakeLists.txt (${file_version})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ✅ Версия тега совпадает с CMakeLists.txt: ${file_version}"
|
||||||
|
|
||||||
|
- name: Собрать firmware_test HAB (Debug)
|
||||||
|
run: |
|
||||||
|
docker run --rm --user root -v "$GITHUB_WORKSPACE":/workspace -w /workspace \
|
||||||
|
tft-devcontainer-ci:latest bash -lc 'just build::hab-firmware-test-debug'
|
||||||
|
|
||||||
|
- name: Upload firmware_test_hab.bin
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: firmware-hab-debug
|
||||||
|
path: build/Debug/firmware_test_hab.bin
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# publish-firmware — только на тег firmware-v*, отдельный standalone-релиз
|
||||||
|
# HAB-образа (для tools/host/flash_usb.py и ручной прошивки, не через TUI).
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
publish-firmware:
|
||||||
|
name: Publish firmware release
|
||||||
|
needs: firmware
|
||||||
|
if: startsWith(github.ref, 'refs/tags/firmware-v')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download firmware_test_hab.bin
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: firmware-hab-debug
|
||||||
|
path: release-assets/
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
gh release create "${GITHUB_REF_NAME}" \
|
||||||
|
release-assets/firmware_test_hab.bin \
|
||||||
|
--title "firmware_test ${GITHUB_REF_NAME#firmware-v}" \
|
||||||
|
--generate-notes
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# service-tui-{macos,windows} — упаковка PyInstaller-бандла с вшитым HAB
|
||||||
|
# из job firmware. Запускается на тег tui-v* и на workflow_dispatch с
|
||||||
|
# release_type=tui (для тестового прогона без публикации).
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
service-tui-macos:
|
||||||
|
name: Package service-tui (macOS)
|
||||||
|
needs: firmware
|
||||||
|
if: startsWith(github.ref, 'refs/tags/tui-v') || (github.event_name == 'workflow_dispatch' && github.event.inputs.release_type == 'tui')
|
||||||
|
runs-on: macos-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v8.3.2 # без плавающего major-тега начиная с v8 (upstream security policy) — пин точной версии, обновлять периодически
|
||||||
|
|
||||||
|
- name: Install just
|
||||||
|
uses: extractions/setup-just@v4
|
||||||
|
|
||||||
|
- name: Download firmware_test_hab.bin
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: firmware-hab-debug
|
||||||
|
path: build/Debug/
|
||||||
|
|
||||||
|
- name: Проверить, что тег совпадает с версией в pyproject.toml
|
||||||
|
if: startsWith(github.ref, 'refs/tags/tui-v')
|
||||||
|
run: |
|
||||||
|
tag_version="${GITHUB_REF_NAME#tui-v}"
|
||||||
|
file_version=$(grep -m1 '^version' tools/service_tui/pyproject.toml | sed -E 's/.*"(.+)".*/\1/')
|
||||||
|
if [[ "$tag_version" != "$file_version" ]]; then
|
||||||
|
echo "::error::Тег ${GITHUB_REF_NAME} (версия ${tag_version}) не совпадает с version в tools/service_tui/pyproject.toml (${file_version})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ✅ Версия тега совпадает с pyproject.toml: ${file_version}"
|
||||||
|
|
||||||
|
- name: Собрать зависимости и упаковать бандл
|
||||||
|
run: |
|
||||||
|
just host::service-setup
|
||||||
|
just host::package-tui
|
||||||
|
|
||||||
|
- name: Заархивировать бандл
|
||||||
|
working-directory: tools/service_tui/dist
|
||||||
|
run: |
|
||||||
|
name=$(ls -d service-tui-v*-macos)
|
||||||
|
zip -r "${name}.zip" "${name}"
|
||||||
|
|
||||||
|
- name: Upload bundle
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: service-tui-macos
|
||||||
|
path: tools/service_tui/dist/service-tui-v*-macos.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
|
service-tui-windows:
|
||||||
|
name: Package service-tui (Windows)
|
||||||
|
needs: firmware
|
||||||
|
if: startsWith(github.ref, 'refs/tags/tui-v') || (github.event_name == 'workflow_dispatch' && github.event.inputs.release_type == 'tui')
|
||||||
|
runs-on: windows-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v8.3.2 # без плавающего major-тега начиная с v8 (upstream security policy) — пин точной версии, обновлять периодически
|
||||||
|
|
||||||
|
- name: Install just
|
||||||
|
uses: extractions/setup-just@v4
|
||||||
|
|
||||||
|
- name: Download firmware_test_hab.bin
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: firmware-hab-debug
|
||||||
|
path: build/Debug/
|
||||||
|
|
||||||
|
- name: Проверить, что тег совпадает с версией в pyproject.toml
|
||||||
|
if: startsWith(github.ref, 'refs/tags/tui-v')
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
tag_version="${GITHUB_REF_NAME#tui-v}"
|
||||||
|
file_version=$(grep -m1 '^version' tools/service_tui/pyproject.toml | sed -E 's/.*"(.+)".*/\1/')
|
||||||
|
if [[ "$tag_version" != "$file_version" ]]; then
|
||||||
|
echo "::error::Тег ${GITHUB_REF_NAME} (версия ${tag_version}) не совпадает с version в tools/service_tui/pyproject.toml (${file_version})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ✅ Версия тега совпадает с pyproject.toml: ${file_version}"
|
||||||
|
|
||||||
|
- name: Собрать зависимости и упаковать бандл
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
just host::service-setup
|
||||||
|
just host::package-tui
|
||||||
|
|
||||||
|
- name: Заархивировать бандл
|
||||||
|
working-directory: tools/service_tui/dist
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$name = (Get-ChildItem -Directory -Filter "service-tui-v*-windows").Name
|
||||||
|
Compress-Archive -Path $name -DestinationPath "$name.zip"
|
||||||
|
|
||||||
|
- name: Upload bundle
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: service-tui-windows
|
||||||
|
path: tools/service_tui/dist/service-tui-v*-windows.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# publish-tui — только на тег tui-v*, релиз с обоими zip-бандлами.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
publish-tui:
|
||||||
|
name: Publish service-tui release
|
||||||
|
needs: [service-tui-macos, service-tui-windows]
|
||||||
|
if: startsWith(github.ref, 'refs/tags/tui-v')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download bundles
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: service-tui-*
|
||||||
|
path: release-assets/
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
gh release create "${GITHUB_REF_NAME}" \
|
||||||
|
release-assets/*.zip \
|
||||||
|
--title "service-tui ${GITHUB_REF_NAME#tui-v}" \
|
||||||
|
--generate-notes
|
||||||
|
|
@ -191,11 +191,6 @@
|
||||||
|
|
||||||
### Известные ограничения
|
### Известные ограничения
|
||||||
|
|
||||||
- `service_tui.spec` не включает `datas` для `spsdk`/`dcd/*.bin`/
|
|
||||||
`pyproject.toml`, хотя уже собранные alpha-бандлы их содержат — спек
|
|
||||||
нужно синхронизировать перед следующей сборкой релиза.
|
|
||||||
- `pyusb` в `pyproject.toml` — мёртвая зависимость (Р7 перевёл детект на
|
|
||||||
`spsdk`/`serial.tools.list_ports`), кандидат на удаление.
|
|
||||||
- Auto-config Flashloader для W25Q256/512 не проверялся напрямую — решили
|
- Auto-config Flashloader для W25Q256/512 не проверялся напрямую — решили
|
||||||
не полагаться на него вообще, FCB для кастомных бинарей всегда пишется
|
не полагаться на него вообще, FCB для кастомных бинарей всегда пишется
|
||||||
явно.
|
явно.
|
||||||
|
|
|
||||||
|
|
@ -1,226 +0,0 @@
|
||||||
# План первого релиза в GitHub
|
|
||||||
|
|
||||||
> Цель: в GitHub Releases появляется тег с прикреплёнными бинарниками —
|
|
||||||
> `firmware_test` (HAB-образ) и `service-tui` (standalone-бандлы под macOS и
|
|
||||||
> Windows). Этот документ — практический чек-лист «как довести до кнопки
|
|
||||||
> Publish», а не повторение инженерных фаз `RELEASE_ROADMAP.md`.
|
|
||||||
|
|
||||||
## Как связаны два артефакта
|
|
||||||
|
|
||||||
`firmware_test` (C, i.MX RT1052, версия `0.1.2` из
|
|
||||||
`firmware/test/CMakeLists.txt`) и `service-tui` (Python/Textual, версия
|
|
||||||
`0.2.0` из `tools/service_tui/pyproject.toml`) — независимо версионируемые
|
|
||||||
проекты, но релиз одного без другого бесполезен сервисному инженеру:
|
|
||||||
`service-tui` — это инструмент, которым он *прошивает* плату диагностической
|
|
||||||
прошивкой, и HAB-образ `firmware_test` кладётся внутрь бандла TUI как
|
|
||||||
`firmware/<Type>/firmware_test_hab.bin` (см. `just host::package-tui`,
|
|
||||||
`tools/service_tui/docs/DEV_ARCH.md` §14). Поэтому релиз собирается как один
|
|
||||||
комплект, даже если версии независимые.
|
|
||||||
|
|
||||||
Известное ограничение (задокументировано в `README.md`/`DEV_ARCH.md`):
|
|
||||||
Release-сборка `firmware_test` нестабильна (проблема с FCB/clock), поэтому
|
|
||||||
в бандл TUI кладётся **Debug**-образ (`FIRMWARE_BUILD_TYPE=Debug`). В релиз
|
|
||||||
GitHub имеет смысл положить оба HAB-образа отдельными assets (Debug — как
|
|
||||||
основной для TUI, Release — с пометкой «experimental», для тех, кто прошивает
|
|
||||||
через `tools/host/flash_usb.py` вручную), либо только Debug — см. открытый
|
|
||||||
вопрос в шаге 1.5.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Текущее состояние (снимок на момент написания плана)
|
|
||||||
|
|
||||||
| Область | Состояние |
|
|
||||||
| --- | --- |
|
|
||||||
| `firmware_test` | Собирается, HAB-образ генерируется (`just build::hab-firmware-test-{debug,release}`), Release нестабилен |
|
|
||||||
| `service-tui` | v0.2.0, PyInstaller onedir, alpha-бандлы уже вручную собраны и прогнаны на живом железе macOS+Windows (коммиты `c694258`/`bfe4dd6`) |
|
|
||||||
| `service_tui.spec` | **Устарел относительно того, чем реально собраны протестированные alpha-бандлы** — не содержит `datas` для `spsdk`, `dcd/*.bin`, `pyproject.toml` (задокументировано в `DEV_ARCH.md` §14 и `CHANGELOG.md`「Известные ограничения」) |
|
|
||||||
| `tools/service_tui/dist/service-tui-v0.2.0-{macos,windows}/` | Закоммичены в git (906 файлов, ~96 МБ суммарно) и **устарели относительно HEAD** — собраны до коммитов `2dbe3e6`/`22c4077`/`31e3237` (фиксы моков тестов, рефакторинг докстрингов) |
|
|
||||||
| CI (`.github/workflows/ci.yml`) | Только `build`+`test` в devcontainer на `ubuntu-latest`; не собирает `service-tui`, нет macOS/Windows раннеров, нет release-пайплайна, нет тегов в репозитории |
|
|
||||||
| `just/ci.just` | Есть рецепт `release` (→ `just build::hab-all-release`) — только firmware, ничего про упаковку TUI или публикацию на GitHub |
|
|
||||||
| Ветки | `feature-tui-monolith` на 15 коммитов впереди `dev`, ещё не смёржена; в репозитории также есть `main` — политика, какая ветка режет релизы, явно не зафиксирована |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Шаг 1 — Закрыть блокирующие долги перед тегом
|
|
||||||
|
|
||||||
Без этого CI-сборка (шаг 3) не будет соответствовать тому, что уже
|
|
||||||
провалидировано на железе, — а «релиз, который не воспроизводим из
|
|
||||||
исходников» хуже отсутствия релиза.
|
|
||||||
|
|
||||||
1. **Актуализировать `tools/service_tui/service_tui.spec`** — добавить
|
|
||||||
`collect_data_files("spsdk")` (+ `SPSDK_DATA_FOLDER` если понадобится),
|
|
||||||
`collect_dynamic_libs("libusbsio")`, `datas` для
|
|
||||||
`tools/host/dcd/{dcd.bin,w25q128_fdcb.bin,w25q512_fdcb.bin,ivt_flashloader.bin}`
|
|
||||||
и `pyproject.toml`. Ориентир — реальное содержимое уже собранных
|
|
||||||
alpha-бандлов в `dist/` (их можно инспектировать перед удалением из git,
|
|
||||||
см. следующий пункт).
|
|
||||||
2. **Убрать `tools/service_tui/dist/` из git**: `git rm -r --cached
|
|
||||||
tools/service_tui/dist` + добавить `tools/service_tui/dist/` в
|
|
||||||
`.gitignore`. Собранные бандлы — это build-артефакты, их место в GitHub
|
|
||||||
Release assets или CI-артефактах, не в истории репозитория.
|
|
||||||
3. **(Дёшево, но не блокирует)** Убрать мёртвую зависимость `pyusb` из
|
|
||||||
`tools/service_tui/pyproject.toml` — детект давно переведён на
|
|
||||||
`spsdk`/`serial.tools.list_ports` (Р7), ни один модуль `app/` её не
|
|
||||||
импортирует.
|
|
||||||
4. **Пересобрать бандлы локально** с исправленным spec из актуального HEAD
|
|
||||||
(`just host::package-tui` на macOS и на Windows) и повторить хотя бы
|
|
||||||
дымовой прогон чек-листа Гейта 5 из `RELEASE_ROADMAP.md` (детект SDP →
|
|
||||||
прошивка `firmware_test` → диагностика → выход). Полный деструктивный
|
|
||||||
чек-лист (обрыв USB и т.п.) уже пройден на предыдущей сборке — здесь
|
|
||||||
цель убедиться, что исправленный spec не сломал состав бандла, а не
|
|
||||||
повторять всё с нуля.
|
|
||||||
5. **Открытый вопрос:** класть ли в релиз Release-сборку `firmware_test`
|
|
||||||
вообще (сейчас нестабильна) — по умолчанию план предполагает **только
|
|
||||||
Debug**-образ как основной asset, Release не публикуется до починки
|
|
||||||
FCB/clock-проблемы. Требует подтверждения.
|
|
||||||
|
|
||||||
→ **Решено:** только Debug.
|
|
||||||
6. **(Добавилось по ходу, не было в исходном плане)** Помимо самого
|
|
||||||
`datas`/`binaries` в spec, ужесточили сборку двумя хардфейлами вместо
|
|
||||||
тихих warning'ов:
|
|
||||||
- `service_tui.spec` теперь падает с `FileNotFoundError`, если в
|
|
||||||
`tools/host/dcd/` нет хотя бы одного из
|
|
||||||
`dcd.bin`/`ivt_flashloader.bin`/`w25q128_fdcb.bin`/`w25q512_fdcb.bin`;
|
|
||||||
- `just host::package-tui` падает с `exit 1`, если не нашёлся Debug
|
|
||||||
`*_hab.bin` в `build/Debug` (Release остаётся необязательным).
|
|
||||||
Заодно поправлен баг расположения `custom_binaries/` — recipe создавал
|
|
||||||
пустую декоративную папку рядом с `service_tui/`, а не внутри неё, хотя
|
|
||||||
`flasher._resolve_custom_binaries_dir()` смотрит именно внутрь (рядом с
|
|
||||||
исполняемым файлом); теперь `custom_binaries/` создаётся в правильном
|
|
||||||
месте и сразу наполняется `TFT_BOOTLOADER_NEW.bin`/`TFT_BOOTLOADER_OLD.bin`
|
|
||||||
из `tools/service_tui/custom_binaries/`.
|
|
||||||
|
|
||||||
**TODO (отложено, не забыть перед шагом 2):** актуализировать
|
|
||||||
`tools/service_tui/README.md` и `tools/service_tui/docs/DEV_ARCH.md` §14 —
|
|
||||||
они всё ещё описывают старое поведение (в частности, блок «Расхождение
|
|
||||||
spec/факт» в DEV_ARCH.md §14 уже неактуален, spec восстановлен и
|
|
||||||
ужесточён). Сознательно отложено до ручной валидации сборки на
|
|
||||||
macOS/Windows (пункт 4) — чтобы задокументировать то, что реально
|
|
||||||
проверено на железе, а не то, что должно было бы работать.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Шаг 2 — Версия и тег
|
|
||||||
|
|
||||||
1. **Схема тега** — `firmware_test` (0.1.2) и `service-tui` (0.2.0)
|
|
||||||
версионируются независимо. Предлагается: тег вида `vX.Y.Z` = версия
|
|
||||||
`service-tui` (это главный продукт релиза для сервисного инженера),
|
|
||||||
версия `firmware_test` указывается в описании релиза отдельной строкой.
|
|
||||||
Альтернатива — раздельные теги (`tui-v0.2.0` + `firmware-v0.1.2`), если
|
|
||||||
в будущем оба проекта должны релизиться независимо друг от друга.
|
|
||||||
**Требует подтверждения**, план ниже считает первый вариант.
|
|
||||||
2. **Ветка релиза** — в репозитории есть и `dev`, и `main`, при этом
|
|
||||||
`main` в `git log` не встречается в истории `feature-tui-monolith`/`dev`
|
|
||||||
(нужно свериться отдельно, если `main` уже используется под что-то
|
|
||||||
другое). Рекомендация: смёржить `feature-tui-monolith → dev`, затем
|
|
||||||
`dev → main`, тег ставить на `main` — так `main` остаётся точкой,
|
|
||||||
соответствующей опубликованным релизам, а `dev` — интеграционной веткой.
|
|
||||||
**Требует подтверждения**, если у проекта другая договорённость про
|
|
||||||
`main`.
|
|
||||||
3. **`CHANGELOG.md`** — закрыть секцию `[Не выпущено] — service-tui: ...`
|
|
||||||
→ `[YYYY-MM-DD] — v0.2.0`, вычеркнуть из «Известные ограничения» то, что
|
|
||||||
закрывается шагом 1 (spec-расхождение, `pyusb`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Шаг 3 — CI: собрать релизные бинарники автоматически
|
|
||||||
|
|
||||||
Текущий `.github/workflows/ci.yml` собирает только `firmware_test` на
|
|
||||||
`ubuntu-latest` внутри devcontainer — этого недостаточно для
|
|
||||||
кросс-платформенной упаковки `service-tui`. Нужен отдельный workflow,
|
|
||||||
не смешанный с обычным PR-циклом (см. `just/ci_workflow.md`, «Шаг 3 —
|
|
||||||
выделить release workflow»).
|
|
||||||
|
|
||||||
Новый `.github/workflows/release.yml`, триггер — тег `v*` (плюс
|
|
||||||
`workflow_dispatch` для тестового прогона без публикации):
|
|
||||||
|
|
||||||
| Job | Раннер | Что делает |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `firmware` | `ubuntu-latest` (тот же devcontainer-подход, что в `ci.yml`) | `just ci::release` → `hab-all-release` (по факту нужен только `firmware_test`, Debug+Release); выгрузить `firmware_test_hab.bin` (оба типа) как артефакт |
|
|
||||||
| `service-tui-macos` | `macos-latest` | скачать firmware-артефакт из job `firmware`; `uv sync` в `tools/service_tui`; `just host::package-tui`; заархивировать `dist/service-tui-vX.Y.Z-macos/` |
|
|
||||||
| `service-tui-windows` | `windows-latest` | то же самое, PowerShell-совместимые команды (`just`/`uv` доступны на Windows) |
|
|
||||||
| `publish-release` | `ubuntu-latest`, `needs: [firmware, service-tui-macos, service-tui-windows]` | скачать все артефакты, создать GitHub Release через `gh release create` / `softprops/action-gh-release@v2`, прикрепить `firmware_test_hab.bin` (Debug, + Release с пометкой experimental, если решение по шагу 1.5 — «класть оба»), `service-tui-vX.Y.Z-macos.zip`, `service-tui-vX.Y.Z-windows.zip` |
|
|
||||||
|
|
||||||
Важные нюансы:
|
|
||||||
|
|
||||||
- Firmware для бандла TUI собирается **один раз** в job `firmware` и
|
|
||||||
передаётся в macOS/Windows job'ы артефактом — пересобирать ARM-прошивку
|
|
||||||
на каждом раннере отдельно избыточно (и на macOS/Windows раннерах нет
|
|
||||||
подготовленного devcontainer/toolchain).
|
|
||||||
- Аппаратные гейты (детект SDP на живой плате, деструктивные сценарии
|
|
||||||
обрыва USB) **CI выполнить не может** — GitHub-hosted раннеры не видят
|
|
||||||
реальное USB-устройство. Это ручной шаг, который уже пройден один раз
|
|
||||||
вручную (коммиты «MacOS tested»/«Windows tested») и должен повторяться
|
|
||||||
вручную перед каждым релизом, пока не поднят self-hosted HIL-раннер
|
|
||||||
(см. `just/ci_workflow.md`, «Шаг 5»). План релиза это не блокирует, но
|
|
||||||
release notes должны явно фиксировать, что сборка прошла ручную проверку
|
|
||||||
на железе, а не только CI.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Шаг 4 — Ручные шаги перед Publish
|
|
||||||
|
|
||||||
1. Скачать `service-tui-vX.Y.Z-{macos,windows}.zip`, собранные именно CI
|
|
||||||
(не локальную сборку из шага 1.4) — прогнать сокращённый чек-лист Гейта
|
|
||||||
5: детект SDP → прошивка `firmware_test` → диагностика на обеих ОС.
|
|
||||||
Цель — убедиться, что CI-сборка не разошлась с уже провалидированной
|
|
||||||
локальной.
|
|
||||||
2. Обновить `tools/service_tui/README.md`/корневой `README.md` — ссылка на
|
|
||||||
релиз/инструкция «откуда скачать сервисному инженеру».
|
|
||||||
|
|
||||||
## Шаг 5 — Публикация
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git tag vX.Y.Z
|
|
||||||
git push origin vX.Y.Z
|
|
||||||
```
|
|
||||||
|
|
||||||
— триггерит `release.yml`. Проверить, что все 3 asset'а прикрепились и
|
|
||||||
release notes корректны (описание можно сгенерировать из секции
|
|
||||||
`CHANGELOG.md` за этот релиз + `gh release create --generate-notes` как
|
|
||||||
дополнение). Если Гейт 6 (`RELEASE_ROADMAP.md`) закрыт не полностью
|
|
||||||
(например, POST-1 сознательно отложен — это нормально, он и заявлен как
|
|
||||||
пост-релизный) — релиз всё равно можно публиковать как обычный, а не
|
|
||||||
pre-release, POST-1 не блокирует v1 по замыслу roadmap.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Шаг 6 — Развитие CI после первого релиза (не блокирует, но логично заложить сразу)
|
|
||||||
|
|
||||||
Эти пункты уже зафиксированы в `just/ci_workflow.md` («Рекомендуемые
|
|
||||||
следующие шаги»), возвращаемся сюда после первого релиза:
|
|
||||||
|
|
||||||
- `lint` job (`just ci::lint` сейчас заглушка) — `clang-format --dry-run
|
|
||||||
--Werror` + `clang-tidy`.
|
|
||||||
- Coverage (`just ci::_coverage` уже есть, но не подключён в workflow).
|
|
||||||
- Публикация devcontainer image в GHCR — сократит время `firmware` job в
|
|
||||||
`release.yml` и обычном `ci.yml` (сейчас образ пересобирается в каждой
|
|
||||||
job, даже с layer-кэшем).
|
|
||||||
- Self-hosted HIL lane — единственный способ когда-нибудь автоматизировать
|
|
||||||
то, что сейчас в шаге 4 делается руками.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Сводная последовательность
|
|
||||||
|
|
||||||
```
|
|
||||||
Шаг 1 (spec + dist из git + пересборка)
|
|
||||||
│
|
|
||||||
Шаг 2 (тег/ветка/CHANGELOG — решения по открытым вопросам)
|
|
||||||
│
|
|
||||||
Шаг 3 (release.yml: firmware → macOS/Windows package → publish)
|
|
||||||
│
|
|
||||||
Шаг 4 (ручная проверка CI-бинарников на железе)
|
|
||||||
│
|
|
||||||
Шаг 5 (git tag → publish)
|
|
||||||
│
|
|
||||||
Шаг 6 (lint/coverage/GHCR/HIL — после релиза, не блокирует)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Открытые вопросы, требующие решения пользователя
|
|
||||||
|
|
||||||
| # | Вопрос | Где всплывает |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 1 | Класть ли Release-сборку `firmware_test` в релиз (сейчас нестабильна) | Шаг 1.5 |
|
|
||||||
| 2 | Схема тега — один `vX.Y.Z` (=версия TUI) или раздельные теги firmware/TUI | Шаг 2.1 |
|
|
||||||
| 3 | Тег ставится на `main` (после `dev → main`) или сразу на `dev`/на самой feature-ветке | Шаг 2.2 |
|
|
||||||
27
README.md
27
README.md
|
|
@ -11,9 +11,9 @@
|
||||||
|
|
||||||
| Проект | Путь | Описание |
|
| Проект | Путь | Описание |
|
||||||
| ------------------- | ---------------------- | -------------------------------------------------------------------------------------- |
|
| ------------------- | ---------------------- | -------------------------------------------------------------------------------------- |
|
||||||
| Тестовая прошивка (✅ реализована) | `firmware/test/` | Входной контроль платы: CAN, UART, SDRAM, QSPI, SDIO, RGB, оптовходы, LED, кнопки, MQS |
|
| Тестовая прошивка (реализована) | `firmware/test/` | Входной контроль платы: CAN, UART, SDRAM, QSPI, SDIO, RGB, оптовходы, LED, кнопки, MQS |
|
||||||
| Загрузчик (⏳ запланирован) | `firmware/bootloader/` | A/B обновление через uSD. Обновляется только через USB ROM + blhost / SWD |
|
| Загрузчик (запланирован) | `firmware/bootloader/` | A/B обновление через uSD. Обновляется только через USB ROM + blhost / SWD |
|
||||||
| Production прошивка (⏳ запланирован) | `firmware/tft_app/` | Приложение с реализацией логики лифтового индикатора. Обновляется загрузчиком |
|
| Production прошивка (запланирован) | `firmware/tft_app/` | Приложение с реализацией логики лифтового индикатора. Обновляется загрузчиком |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -21,9 +21,9 @@
|
||||||
|
|
||||||
| Инструмент | Путь | Назначение |
|
| Инструмент | Путь | Назначение |
|
||||||
| ------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------- |
|
| ------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||||
| Сервисный TUI | `tools/service_tui/` | Диагностика и прошивка готовых плат сервисным инженером (Textual, standalone-бинарь). [README](tools/service_tui/README.md) |
|
| Сервисный TUI | `tools/service_tui/` | Диагностика и прошивка готовых плат сервисным инженером [README](tools/service_tui/README.md) |
|
||||||
| Прошивка (dev-CLI) | `tools/host/` | USB SDP / SWD прошивка при разработке (`sdphost`/`blhost`/`nxpimage`/`pyOCD`). [README](tools/host/README.md) |
|
| Прошивка (dev-CLI) | `tools/host/` | USB SDP / SWD прошивка при разработке [README](tools/host/README.md) |
|
||||||
| HIL-тесты | `tools/hil/` | pytest-окружение аппаратных тестов (pyOCD + M5StampPLC). [README](tools/hil/README.md) |
|
| HIL-тесты | `tools/hil/` | pytest-окружение аппаратных тестов [README](tools/hil/README.md) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -68,21 +68,6 @@ just host::debug-server # GDB-сервер для отладки
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Зависимости
|
|
||||||
|
|
||||||
| | Подход |
|
|
||||||
| --------------------------------------------- | -------------------- |
|
|
||||||
| NXP MCUXpresso SDK, FreeRTOS, FatFS, LittleFS | vendored |
|
|
||||||
| Unity, fff, SEGGER RTT | vendored |
|
|
||||||
| pyOCD, pyserial, pytest, mpremote | `tools/hil/uv.lock` |
|
|
||||||
| spsdk (nxpimage, blhost, sdphost — dev-CLI) | `tools/host/uv.lock` |
|
|
||||||
| spsdk (McuBoot/SDP/HabImage — прямой Python API), Textual | `tools/service_tui/uv.lock` |
|
|
||||||
|
|
||||||
Всё что не меняется — vendored. Сборка работает после `git clone` без интернета
|
|
||||||
(кроме Python-зависимостей).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Быстрый старт
|
## Быстрый старт
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
189
docs/CI_WORKFLOW.md
Normal file
189
docs/CI_WORKFLOW.md
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
# CI/CD: устройство GitHub Actions workflow
|
||||||
|
|
||||||
|
> Проект: TFT Firmware (MIMXRT1052CVJ5B)
|
||||||
|
> Документ описывает схему и принцип работы двух GitHub Actions workflow в
|
||||||
|
> репозитории: `ci.yml` (обычный PR/push-цикл) и `release.yml` (публикация
|
||||||
|
> релизных бинарников по тегу). Для истории решений и roadmap развития
|
||||||
|
> `ci.yml` — см. `just/ci_workflow.md`; этот документ — техническая справка
|
||||||
|
> «как оно работает сейчас», а не хронология.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Два workflow, два разных назначения
|
||||||
|
|
||||||
|
| | `ci.yml` | `release.yml` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Когда запускается | `push` в `dev`/`main`, любой `pull_request`, `workflow_dispatch` | `push` тега `tui-v*` / `firmware-v*`, `workflow_dispatch` |
|
||||||
|
| Что проверяет | Собирается ли проект и проходят ли host-тесты | Собираются ли и публикуются ли релизные бинарники |
|
||||||
|
| Публикует что-то наружу? | Нет — только артефакты прогона (для отладки) | Да — GitHub Release с реальными asset'ами (только по тегу) |
|
||||||
|
| Раннеры | `ubuntu-latest` (оба job'а) | `ubuntu-latest` + `macos-latest` + `windows-latest` |
|
||||||
|
|
||||||
|
Они намеренно не смешаны в один файл (см. `just/ci_workflow.md`, «Шаг 3 —
|
||||||
|
выделить release workflow»): PR-цикл должен оставаться быстрым и не зависеть
|
||||||
|
от кросс-платформенной упаковки `service-tui`, а релизная публикация не
|
||||||
|
должна гонять host-тесты повторно на каждый push в PR.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. `ci.yml` — обычный PR/push-цикл
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
trigger["push (dev/main)\nPR\nworkflow_dispatch"] --> build["build\nсборка в devcontainer"]
|
||||||
|
build -->|"artifact: build-tree"| test["test\nhost-тесты в devcontainer"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Оба job'а выполняются на `ubuntu-latest`, внутри одного и того же
|
||||||
|
devcontainer-образа (`.devcontainer/Dockerfile`) — то же окружение, что и у
|
||||||
|
разработчика локально (ARM toolchain, cmake, ninja, `just`, `uv`), не
|
||||||
|
отдельно собранное под раннер. Образ пересобирается в каждом job'е, но
|
||||||
|
кэшируется через `docker/build-push-action@v7` (`cache-from`/`cache-to:
|
||||||
|
type=gha, scope=tft-devcontainer`) — повторные прогоны переиспользуют слои,
|
||||||
|
не пересобирают с нуля.
|
||||||
|
|
||||||
|
- **`build`** — checkout, поднять devcontainer, `tools/host && uv sync`,
|
||||||
|
`just ci::build` (→ `just build::build-all-release`, все три firmware-
|
||||||
|
проекта), выгрузить `build/` как артефакт `build-tree`.
|
||||||
|
- **`test`** — зависит от `build` (`needs: build`), скачивает `build-tree`,
|
||||||
|
поднимает тот же образ (тот же кэш), `just ci::test` (→
|
||||||
|
`just build::test-host-release`, Unity/fff host-тесты), выгружает логи +
|
||||||
|
`build/` как `test-artifacts`.
|
||||||
|
|
||||||
|
Команды внутри контейнера запускаются `docker run --user root -v
|
||||||
|
"$GITHUB_WORKSPACE":/workspace -w /workspace tft-devcontainer-ci:latest
|
||||||
|
bash -lc '...'` — `--user root` обязателен, иначе non-root пользователь в
|
||||||
|
контейнере не может писать в bind-mounted `$GITHUB_WORKSPACE` (ломало
|
||||||
|
`uv sync`/создание `.venv`).
|
||||||
|
|
||||||
|
Чего `ci.yml` **не делает**: lint (заглушка в `just ci::lint`), coverage,
|
||||||
|
сборку/упаковку `service-tui`, HIL-тесты (нужно физическое железо —
|
||||||
|
самостоятельная задача для self-hosted раннера). См. `just/ci_workflow.md`
|
||||||
|
для планов по каждому из этих пунктов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. `release.yml` — публикация релиза
|
||||||
|
|
||||||
|
### 3.1 Схема тегов
|
||||||
|
|
||||||
|
Firmware (`firmware_test`) и `service-tui` версионируются и релизятся
|
||||||
|
**независимо** (`FIRST_RELEASE_PLAN.md`, Шаг 2.1) — два разных паттерна
|
||||||
|
тега запускают два разных сценария внутри одного workflow-файла:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
push_fw["push tag\nfirmware-vX.Y.Z"] --> firmware
|
||||||
|
push_tui["push tag\ntui-vX.Y.Z"] --> firmware
|
||||||
|
dispatch["workflow_dispatch\n(release_type: tui | firmware)"] --> firmware
|
||||||
|
|
||||||
|
firmware["firmware\n(ubuntu-latest, devcontainer)\njust build::hab-firmware-test-debug"]
|
||||||
|
|
||||||
|
firmware -->|"тег firmware-v*"| publishFw["publish-firmware\ngh release create\n(HAB Debug)"]
|
||||||
|
|
||||||
|
firmware -->|"тег tui-v* ИЛИ\ndispatch(tui)"| macos["service-tui-macos\njust host::package-tui"]
|
||||||
|
firmware -->|"тег tui-v* ИЛИ\ndispatch(tui)"| windows["service-tui-windows\njust host::package-tui"]
|
||||||
|
|
||||||
|
macos -->|"тег tui-v*"| publishTui["publish-tui\ngh release create\n(2× .zip)"]
|
||||||
|
windows -->|"тег tui-v*"| publishTui
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключевое архитектурное решение: **HAB-образ `firmware_test`, который
|
||||||
|
вшивается внутрь TUI-бандла, всегда собирается заново из текущего HEAD**
|
||||||
|
джобой `firmware` — а не скачивается из последнего опубликованного
|
||||||
|
`firmware-v*` релиза. Поэтому job `firmware` выполняется **при любом
|
||||||
|
триггере**, без условия — она нужна и для отдельного firmware-релиза, и
|
||||||
|
как зависимость для упаковки TUI.
|
||||||
|
|
||||||
|
### 3.2 Триггеры
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["tui-v*", "firmware-v*"]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_type: {type: choice, options: [tui, firmware], default: tui}
|
||||||
|
```
|
||||||
|
|
||||||
|
`workflow_dispatch` — «сухой прогон» без публикации: собирает всё
|
||||||
|
(включая упаковку TUI под выбранный `release_type`), выгружает скачиваемые
|
||||||
|
артефакты, но **не** создаёт GitHub Release — джобы `publish-*` гейтятся
|
||||||
|
условием `if: startsWith(github.ref, 'refs/tags/...')`, которое на ручном
|
||||||
|
запуске всегда ложно (`github.ref` в этом случае — ветка, не тег).
|
||||||
|
|
||||||
|
Важный нюанс реализации: условие для `service-tui-macos`/`-windows`
|
||||||
|
проверяет `release_type` через `github.event.inputs.release_type`, не
|
||||||
|
через голый контекст `inputs.release_type` — последний рассчитан прежде
|
||||||
|
всего на reusable workflows (`workflow_call`) и не даёт предсказуемого
|
||||||
|
результата в job-level `if:` для прямого `workflow_dispatch`. На первом
|
||||||
|
реальном прогоне (`inputs.release_type`) джобы `service-tui-*` молча
|
||||||
|
скипались даже при выбранном `release_type=tui` — потребовалась замена на
|
||||||
|
`github.event.inputs.*`, если снова понадобится ссылаться на inputs в
|
||||||
|
job-level `if:`, использовать именно эту форму.
|
||||||
|
|
||||||
|
### 3.3 Джобы
|
||||||
|
|
||||||
|
| Job | Раннер | Когда выполняется | Что делает |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `firmware` | `ubuntu-latest`, devcontainer (тот же подход и кэш, что в `ci.yml`) | всегда | сверяет тег `firmware-v*` с `VERSION` в `firmware/test/CMakeLists.txt` (если применимо); `just build::hab-firmware-test-debug`; артефакт `firmware-hab-debug` |
|
||||||
|
| `publish-firmware` | `ubuntu-latest` | только push тега `firmware-v*` | скачивает `firmware-hab-debug`; `gh release create firmware-vX.Y.Z firmware_test_hab.bin` — standalone-релиз для `tools/host/flash_usb.py`, без TUI |
|
||||||
|
| `service-tui-macos` / `service-tui-windows` | `macos-latest` / `windows-latest` | push тега `tui-v*` ИЛИ `workflow_dispatch` с `release_type=tui` | `astral-sh/setup-uv` + `extractions/setup-just` (на раннерах нет `uv`/`just` из коробки); скачивает `firmware-hab-debug` в `build/Debug/`; сверяет тег `tui-v*` с `version` в `pyproject.toml` (если применимо); `just host::service-setup` + `just host::package-tui`; архивирует `dist/service-tui-vX.Y.Z-<os>/` в zip (`zip -r` на macOS, `Compress-Archive` на Windows); артефакт `service-tui-macos`/`service-tui-windows` |
|
||||||
|
| `publish-tui` | `ubuntu-latest`, `needs: [service-tui-macos, service-tui-windows]` | только push тега `tui-v*` | скачивает оба zip; `gh release create tui-vX.Y.Z *.zip` |
|
||||||
|
|
||||||
|
**Только Debug HAB** идёт в релиз (`FIRST_RELEASE_PLAN.md`, Шаг 1.5) —
|
||||||
|
Release-сборка `firmware_test` нестабильна (FCB/clock), поэтому `firmware`
|
||||||
|
джоба собирает только `hab-firmware-test-debug`, не полный
|
||||||
|
`hab-all-release`.
|
||||||
|
|
||||||
|
**Сверка версии тег↔файл** — маленький, но важный guard в обеих ветках
|
||||||
|
(`firmware`/`service-tui-*`): если версия в теге не совпадает с версией в
|
||||||
|
`CMakeLists.txt`/`pyproject.toml`, job падает с понятной ошибкой вместо
|
||||||
|
того, чтобы молча опубликовать релиз с несовпадающим номером версии внутри
|
||||||
|
файлов (забытый version bump перед тегом).
|
||||||
|
|
||||||
|
**Windows-раннер и bash** — корневой `Justfile` требует `bash` (`set shell
|
||||||
|
:= ["bash", ...]`); `windows-latest` образ GitHub Actions включает Git for
|
||||||
|
Windows (даёт `bash.exe` в PATH из коробки) — дополнительной настройки
|
||||||
|
shell не требуется, `just`-рецепты выполняются так же, как и локально под
|
||||||
|
Git Bash на Windows.
|
||||||
|
|
||||||
|
### 3.4 Как проверить без публикации
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh workflow run release.yml --ref dev -f release_type=tui
|
||||||
|
gh workflow run release.yml --ref dev -f release_type=firmware
|
||||||
|
```
|
||||||
|
|
||||||
|
или через веб-интерфейс: Actions → **Release** → **Run workflow** → выбрать
|
||||||
|
branch и `release_type`. Джобы `publish-*` в этом сценарии показываются как
|
||||||
|
**Skipped**, не **Failed** — это ожидаемое поведение, не баг.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Общее между `ci.yml` и `release.yml`
|
||||||
|
|
||||||
|
- **Один и тот же devcontainer-подход** для всего, что требует ARM
|
||||||
|
toolchain (сборка firmware) — job `firmware` в `release.yml` использует
|
||||||
|
дословно тот же паттерн `docker/build-push-action@v7` +
|
||||||
|
`docker run --user root ...`, что и `build`/`test` в `ci.yml`.
|
||||||
|
- **Общий GHA layer-кэш** — оба workflow используют `scope:
|
||||||
|
tft-devcontainer` в `cache-from`/`cache-to`, поэтому кэш переиспользуется
|
||||||
|
между обычными PR-прогонами и релизными сборками, а не живёт отдельно.
|
||||||
|
- **`workflow_dispatch` есть у обоих** — в `ci.yml` это просто способ
|
||||||
|
перезапустить pipeline вручную без нового коммита; в `release.yml` — это
|
||||||
|
единственный способ протестировать сборку без реальной публикации.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Чего пока нет
|
||||||
|
|
||||||
|
- **Self-hosted HIL-раннер.** Ни один из двух workflow не может
|
||||||
|
задетектировать SDP на живой плате или прогнать деструктивные сценарии
|
||||||
|
(обрыв USB) — GitHub-hosted раннеры не видят реальное железо. Это ручной
|
||||||
|
шаг перед каждым релизом (`FIRST_RELEASE_PLAN.md`, Шаг 4), пока не
|
||||||
|
поднят self-hosted lane (`just/ci_workflow.md`, «Шаг 5»).
|
||||||
|
- **`lint`/`coverage`** не подключены ни в `ci.yml`, ни в `release.yml` —
|
||||||
|
см. `just/ci_workflow.md`, «Рекомендуемые следующие шаги» (Шаги 1–2).
|
||||||
|
- **Публикация devcontainer image в GHCR** — образ пересобирается в каждой
|
||||||
|
job'е каждого workflow (пусть и с layer-кэшем); заранее опубликованный
|
||||||
|
образ сократил бы время старта ещё сильнее (`just/ci_workflow.md`, «Шаг 4»).
|
||||||
|
|
@ -221,6 +221,10 @@ Production/Custom этот шаг не нужен).
|
||||||
перед готовностью отвечать). WaitingScreen в это время показывает
|
перед готовностью отвечать). WaitingScreen в это время показывает
|
||||||
«Плата найдена, подключаемся...» — это штатное поведение, не зависание.
|
«Плата найдена, подключаемся...» — это штатное поведение, не зависание.
|
||||||
См. `docs/DEV_ARCH.md`, §10.
|
См. `docs/DEV_ARCH.md`, §10.
|
||||||
|
- **macOS-бандл не подписан Apple Developer ID и не нотаризован** — при
|
||||||
|
первом запуске Gatekeeper блокирует каждый файл бандла по отдельности.
|
||||||
|
Обход — `xattr -cr` на распакованную папку, см. «Запуск» → «macOS:
|
||||||
|
первый запуск» выше.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -285,6 +289,27 @@ spsdk Python API (`app/flash_backend.py`), без вызова `tools/host/` н
|
||||||
Python/uv на машине сервисника. Структура бандла и резолв путей во frozen —
|
Python/uv на машине сервисника. Структура бандла и резолв путей во frozen —
|
||||||
см. [DEV_ARCH.md](docs/DEV_ARCH.md), §14.
|
см. [DEV_ARCH.md](docs/DEV_ARCH.md), §14.
|
||||||
|
|
||||||
|
#### macOS: первый запуск («Apple could not verify...» на каждый файл)
|
||||||
|
|
||||||
|
Бандл не подписан Apple Developer ID и не нотаризован — Gatekeeper при
|
||||||
|
первом запуске выдаёт отдельное предупреждение «не удалось проверить
|
||||||
|
разработчика» **на каждый** файл бандла (исполняемый файл + каждая `.dylib`
|
||||||
|
внутри `_internal/`, включая `libusbsio`), а не одно общее окно. Причина —
|
||||||
|
карантинный атрибут (`com.apple.quarantine`), который macOS проставляет на
|
||||||
|
файлы, распакованные из скачанного архива; через «Открыть» в Finder его
|
||||||
|
пришлось бы снимать по одному диалогу на файл.
|
||||||
|
|
||||||
|
Снять его сразу со всего бандла одной командой в терминале (один раз,
|
||||||
|
после распаковки zip):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xattr -cr service-tui-vX.Y.Z-macos/
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого бандл запускается без единого диалога. Это стандартный путь
|
||||||
|
для несигнированных бинарников, распространяемых вне App Store/сайта
|
||||||
|
разработчика — не баг конкретной сборки.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
|
|
@ -293,7 +318,6 @@ Python/uv на машине сервисника. Структура бандл
|
||||||
| --------------- | ------- | -------------------------------------------------------- |
|
| --------------- | ------- | -------------------------------------------------------- |
|
||||||
| `textual` | ≥ 0.80 | TUI фреймворк |
|
| `textual` | ≥ 0.80 | TUI фреймворк |
|
||||||
| `pyserial` | ≥ 3.5 | USB CDC ACM + M5 Serial |
|
| `pyserial` | ≥ 3.5 | USB CDC ACM + M5 Serial |
|
||||||
| `pyusb` | ≥ 1.0 | не используется в коде (детект SDP/CDC идёт через `spsdk`) — исторический остаток, кандидат на удаление из `pyproject.toml` |
|
|
||||||
| `spsdk` | 3.7.0 | прошивка: SDP/McuBoot (Flash, chip erase) + HabImage (сборка кастомных HAB), нативный HID (libusbsio) — без Zadig |
|
| `spsdk` | 3.7.0 | прошивка: SDP/McuBoot (Flash, chip erase) + HabImage (сборка кастомных HAB), нативный HID (libusbsio) — без Zadig |
|
||||||
| `python-dotenv` | ≥ 1.0 | загрузка `.env` |
|
| `python-dotenv` | ≥ 1.0 | загрузка `.env` |
|
||||||
| `pyinstaller` | ≥ 6.0 | сборка standalone-бандла (`just host::package-tui`) |
|
| `pyinstaller` | ≥ 6.0 | сборка standalone-бандла (`just host::package-tui`) |
|
||||||
|
|
|
||||||
|
|
@ -721,17 +721,18 @@ service-tui-vX.Y.Z-<os>/
|
||||||
означает, что `libusb-1.0.*`/Zadig в бандле **не нужны** ни на Windows, ни
|
означает, что `libusb-1.0.*`/Zadig в бандле **не нужны** ни на Windows, ни
|
||||||
на macOS — детект BootROM SDP и Flashloader работает из коробки.
|
на macOS — детект BootROM SDP и Flashloader работает из коробки.
|
||||||
|
|
||||||
> **Расхождение spec/факт:** закоммиченный `service_tui.spec` объявляет в
|
`service_tui.spec` актуализирован и ужесточён (FIRST_RELEASE_PLAN.md, Шаг 1):
|
||||||
> `datas` только `('../shared', 'shared')` — без `dcd/*.bin`,
|
`collect_data_files("spsdk")`, `collect_dynamic_libs("libusbsio")`, `datas`
|
||||||
> `pyproject.toml` или `spsdk`-данных. Тем не менее уже собранные релизные
|
для `tools/host/dcd/*.bin` (→ `data/` внутри `_internal`) и `pyproject.toml`.
|
||||||
> бандлы в `tools/service_tui/dist/service-tui-v0.2.0-{macos,windows}/`
|
Сборка падает с `FileNotFoundError` уже на этапе генерации спека, если в
|
||||||
> фактически содержат `_internal/data/{dcd.bin,*_fdcb.bin,ivt_flashloader.bin}`,
|
`tools/host/dcd/` не хватает хотя бы одного из обязательных блобов —
|
||||||
> `_internal/pyproject.toml` и `_internal/spsdk/` — то есть сборки, тестировавшиеся
|
несоответствие spec/факт, из-за которого ранее собранные и протестированные
|
||||||
> на железе (Фаза 5, гейт по macOS/Windows), были собраны с более полным
|
на железе бандлы расходились с тем, что в репозитории, больше не может
|
||||||
> набором `datas`, чем то, что сейчас лежит в репозитории. `service_tui.spec`
|
проскочить незамеченным. `dist/` больше не коммитится в git (см.
|
||||||
> нужно актуализировать (`collect_data_files("spsdk")`, `tools/host/dcd/*.bin`
|
`.gitignore`) — собранные бандлы это build-артефакты, не история репозитория.
|
||||||
> → `data/`, `pyproject.toml`) до следующей сборки релиза — см. «Известные
|
Пересобрано и провалидировано на живом железе macOS + Windows после
|
||||||
> открытые вопросы».
|
актуализации spec (Гейт 5: детект SDP → прошивка `firmware_test` →
|
||||||
|
диагностика → выход) — см. `FIRST_RELEASE_PLAN.md`, Шаг 1.4.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -769,13 +770,6 @@ service-tui-vX.Y.Z-<os>/
|
||||||
`app/flash.py` (комментарий `_check_sdp_present`) и `flasher.py`
|
`app/flash.py` (комментарий `_check_sdp_present`) и `flasher.py`
|
||||||
(docstring модуля упоминает Фазу 2 буквально, что нормально как история
|
(docstring модуля упоминает Фазу 2 буквально, что нормально как история
|
||||||
провенанса, но стоит перепроверить при следующей правке этих файлов).
|
провенанса, но стоит перепроверить при следующей правке этих файлов).
|
||||||
- **`service_tui.spec` не актуализирован под реальные релизные сборки** —
|
|
||||||
см. §14. Нужно добавить `datas` (`dcd/*.bin`, `pyproject.toml`,
|
|
||||||
`collect_data_files("spsdk")`) до следующей упаковки релиза.
|
|
||||||
- **`pyusb` в `pyproject.toml` — мёртвая зависимость.** Р7 перевёл детект
|
|
||||||
SDP/CDC на `spsdk`/`serial.tools.list_ports`; ни один модуль `app/` больше
|
|
||||||
не импортирует `usb`/`pyusb`. Кандидат на удаление при следующей
|
|
||||||
grep-зачистке (Фаза 6).
|
|
||||||
- **Release-сборка firmware нестабильна** (медленное мигание — подозрение на
|
- **Release-сборка firmware нестабильна** (медленное мигание — подозрение на
|
||||||
проблему с FCB/clock конфигурацией в Release HAB-образе) — TUI временно
|
проблему с FCB/clock конфигурацией в Release HAB-образе) — TUI временно
|
||||||
форсирует Debug через `FIRMWARE_BUILD_TYPE`.
|
форсирует Debug через `FIRMWARE_BUILD_TYPE`.
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
# service-tui — Единый мастер-план разработки и релиза (Master Plan v1.0)
|
|
||||||
|
|
||||||
> **Статус документа:** Консолидированный рабочий документ на основе планов миграции на монолит (V4), USB-кроссплатформенности, верификации прошивки и дорожной карты выпуска версии v1.0.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Контекст проекта и целевая архитектура
|
|
||||||
|
|
||||||
**Цель прошивки (`firmware_test`):** Полная диагностика платы **MIMXRT1052CVJ5B** на сервисе и производстве (обработка возвратов по рекламации). Прошивка запускается напрямую через BootROM (USB SDP) без предварительной записи загрузчика в Flash.
|
|
||||||
|
|
||||||
**Архитектура стенда:**
|
|
||||||
|
|
||||||
* **Основной канал:** Хост подключается к плате через один кабель USB CDC ACM (интерфейс `firmware_test`). UART/LPUART в сервисе сознательно не используются.
|
|
||||||
* **HIL-стенд (опционально):** Автоматические HIL-тесты управляются через модуль **M5StampPLC** (реле RLY1–4 + CAN-трансивер).
|
|
||||||
* **Оркестрация:** TUI-приложение на Textual одновременно координирует работу `firmware_test` (через USB CDC) и M5 (через Serial JSON-lines).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Закрытые архитектурные решения (Не пересматривать)
|
|
||||||
|
|
||||||
### Транспорт и парсинг протокола
|
|
||||||
|
|
||||||
* **Единый канал:** USB CDC ACM — единственный интерфейс рантайма. Строковый парсинг без использования тяжелого cJSON (через поиск `strstr` по ключевым полям `"type"` / `"cmd"`).
|
|
||||||
* **Разделение тестов:** Тесты делятся на автономные (`requires_hil=false`) и стендовые (`requires_hil=true`). Если M5StampPLC не обнаружен на шине, TUI автоматически делает HIL-тесты недоступными для выбора (окрашивает в серый цвет) и исключает их из группового запуска.
|
|
||||||
* **Динамический реестр (`list_tests`):** Плата сама отдает список поддерживаемых тестов с метаданными. TUI строит интерфейс динамически и не хардкодит список тестов внутри себя. Порядок выполнения при `run_selected` всегда определяется реестром таргета, а не порядком ID в запросе хоста.
|
|
||||||
|
|
||||||
### Аппаратная интеграция и особенности NXP BSP
|
|
||||||
|
|
||||||
* **SDRAM и DCD:** Контроллер SEMC инициализируется блоком DCD до передачи управления в `main()`. Функция `bsp_sdram_init()` выполняет исключительно верификацию стабильности памяти.
|
|
||||||
* **QSPI Flash в ITCM:** Функции работы с QSPI выполняются из быстрой памяти ITCM с использованием макросов `AT_QUICKACCESS_SECTION_CODE` и инициализации через `__STARTUP_INITIALIZE_RAMFUNCTION`.
|
|
||||||
* **W25Q256/512:** Используются выделенные 4-байтные опкоды чтения/записи/стирания без перевода чипа в глобальный 4-байтный режим (команда `0xB7`).
|
|
||||||
* **MQS Аудио:** Периферия требует стерео PCM16 буфера (SAI3 + eDMA канал 0). На плате выведен только правый канал (`MQS_RIGHT`), поэтому буфер формируется как стерео с идентичными данными L и R.
|
|
||||||
* **Порядок включения MQS:** Сначала запускается усилитель LM4875M (`bsp_mqs_amp_init()`), далее выдерживается пауза 300 мс для заряда конденсаторов C103/C105, и только потом инициализируется ядро MQS. Нарушение порядка приводит к громкому щелчку или отсутствию звука. Использование `bsp_mqs_play()` реализовано асинхронно с поллингом USB CDC во избежание голодания интерфейса.
|
|
||||||
* **PWMChannelEnable (SDK ≥ 2.13):** Флаг `pwmchannelenable` в структуре `pwm_signal_param_t` обязательно выставляется в `true`, иначе функция `PWM_SetupPwm()` не откроет выход `OUTEN`, что маскируется отладчиком и воспроизводится только при «холодном» сбросе платы.
|
|
||||||
* **ERRATA 50235 (FlexCAN + USB):** Макрос `FLEXCAN_Init()` вызывает `assert` на тактирование LPUART1 (`CCM_CCGR5_CG12`). Если после инициализации USB CDC этот гейт закрыт — плата уходит в `HardFault`. Решение: принудительный вызов `CLOCK_EnableClock(kCLOCK_Lpuart1)` перед инициализацией CAN.
|
|
||||||
* **Синхронное чтение оптовходов:** Внедрена функция `bsp_opto_force_read()` для прямого чтения состояния пинов без дебаунса, что устраняет race condition, возникающий из-за дребезга контактов реле на HIL-стенде.
|
|
||||||
|
|
||||||
### Монолитная архитектура TUI (V4) и кроссплатформенность
|
|
||||||
|
|
||||||
* **Отказ от Nuitka и venv:** Упаковка приложения выполняется исключительно штатными средствами PyInstaller в один самодостаточный каталог (`onedir`), без развертывания виртуального окружения Python на целевой машине инженера.
|
|
||||||
* **In-process spsdk:** Вся работа с BootROM SDP и McuBoot переведена на прямое использование Python API пакета `spsdk==3.7.0`. Скрипт `flash_usb.py` полностью исключен из production-цепочки и оставлен разработчикам как инструмент автоматизации в `Justfile`.
|
|
||||||
* **Нативный USB-детект без Zadig:** Устройства в режимах SDP (`1FC9:0130`) и Flashloader (`15A2:0073`) определяются как HID-устройства методами `SdpUSBInterface.scan()` и `MbootUSBInterface.scan()`. Обмен идет через системную библиотеку `libusbsio`, что полностью устраняет необходимость использования утилиты **Zadig** (WinUSB) на ОС Windows.
|
|
||||||
* **Резолв Serial-портов:** Осуществляется рантайм-поиск по VID:PID через `serial.tools.list_ports`. Явные пути к портам в `.env` используются только в качестве оверрайда (escape hatch). Платформа macOS использует неблокирующие callout-устройства `/dev/cu.usbmodem*`.
|
|
||||||
* **«Липкий» выбор (`FlashPreset`):** Выбор оператора (файл прошивки, тип памяти платы, тумблер DCD) кэшируется в контексте процесса. При прошивке партии одинаковых плат настройки подставляются автоматически, оператору достаточно нажать «Загрузить».
|
|
||||||
* **Каркас AppFrame:** Все экраны оборачиваются в фиксированный CSS-контейнер `AppFrame` (макс. размер `112x35`). Это гарантирует визуальную консистентность интерфейса и устраняет критический краш Textual 8.x при mouse drag.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Итоговая матрица тестов прошивки
|
|
||||||
|
|
||||||
| ID | Название теста | Critical | Требует HIL | Тип выполнения | Драйвер BSP |
|
|
||||||
| :-------- | :----------------- | :------: | :---------: | :--------------------------------- | :--------------- |
|
|
||||||
| `sdram` | SDRAM 32 MB | ✅ | ❌ | Автономный (self) | `bsp_sdram` |
|
|
||||||
| `qspi` | QSPI Flash W25Qxx | ✅ | ❌ | Автономный (self) | `bsp_qspi_flash` |
|
|
||||||
| `usd` | microSD (SDIO) | ❌ | ❌ | Интерактивный | `bsp_sd` + FatFS |
|
|
||||||
| `display` | TFT Display RGB888 | ❌ | ❌ | Интерактивный (4 цвета, 2 ротации) | `bsp_display` |
|
|
||||||
| `buttons` | Test Buttons 1/2 | ❌ | ❌ | Интерактивный (физический клик) | `bsp_button` |
|
|
||||||
| `mqs` | MQS Audio Out | ❌ | ❌ | Интерактивный (тон ~4 сек) | `bsp_mqs` |
|
|
||||||
| `can` | CAN loopback | ❌ | ✅ | Стендовый HIL (M5 CAN RX/TX) | `bsp_can` |
|
|
||||||
| `opto` | Оптовходы IN1/2+RS | ❌ | ✅ | Стендовый HIL (M5 реле 2, 3, 4) | `bsp_opto` |
|
|
||||||
|
|
||||||
*Примечание: Тесты `uart_ttl` и `uart_iso` удалены из реестра как избыточные для сервисного применения.*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Дорожная карта до релиза v1.0 (Текущие фазы)
|
|
||||||
|
|
||||||
### Фаза 4a — Добор типизации обрыва USB
|
|
||||||
|
|
||||||
* **Цель:** Перехват всех физических отключений кабеля во время операций и вывод унифицированного сообщения *«Соединение с платой потеряно»* вместо необработанных исключений.
|
|
||||||
* **Реализация:**
|
|
||||||
1. Расширить блоки `try-except` в `flash_backend.py` (`load_flashloader`, `flash`, `erase_chip`), обрабатывая кортеж исключений `(SPSDKConnectionError, SPSDKTimeoutError)`.
|
|
||||||
2. Внедрить «Вариант B» для долгих операций (например, `flash_erase_all`), которые при таймауте возвращают `False` без генерации исключения: в случае `ok == False` вызывать мгновенный `detect_sdp()`. Если устройство исчезло с шины — поднимать `ConnectionLostError(connection_lost=True)`. Любой сбой самой проверки трактовать как обрыв связи.
|
|
||||||
* **Критерий успеха (Гейт):** Выдергивание кабеля во время стирания или записи вызывает корректную реакцию экрана `FlashScreen`, кнопки разблокируются, лог информирует об обрыве, а повторное подключение позволяет продолжить работу без перезапуска TUI.
|
|
||||||
|
|
||||||
### Фаза 4b — Оптимизация логирования и троттлинг
|
|
||||||
|
|
||||||
* **Цель:** Исключение избыточных HID-дампов из основного лога и разгрузка текстового виджета Textual.
|
|
||||||
* **Реализация:**
|
|
||||||
1. В `main.py` установить глобальный уровень логов `INFO`. Сторонние логгеры (`spsdk`, `libusbsio`, подмодули протоколов bulk) принудительно перевести в режим `WARNING`. Полный дамп активировать только при передаче переменной окружения `SERVICE_LOG_LEVEL=DEBUG`.
|
|
||||||
2. Внедрить шаг троттлинга в `flash.py::_on_progress`. Значения в графический прогресс-бар отправлять без задержек (для плавности), а текстовые записи фазы `write` отправлять в виджет `#flash-log` только при пересечении шага в **10%** (0%, 10%, 20%... 100%).
|
|
||||||
* **Критерий успеха (Гейт):** Лог одной сессии прошивки сокращается со ~135 строк до ~10. Отсутствует визуальное замедление интерфейса.
|
|
||||||
|
|
||||||
### Фаза 5 — Упаковка через PyInstaller и полировка UI
|
|
||||||
|
|
||||||
* **Цель:** Создание бинарного дистрибутива под целевые ОС (Windows, macOS).
|
|
||||||
* **Реализация:**
|
|
||||||
1. Написать конфигурационный файл `service_tui.spec`. Использовать директиву `collect_dynamic_libs("libusbsio")` для копирования нативных библиотек HID-транспорта под текущую ОС. Библиотеки `pyusb` и `libusb-1.0` исключить из сборки.
|
|
||||||
2. Настроить секцию `datas` для переноса файлов `tools/host/dcd/` (`dcd.bin`, `*_fdcb.bin`, `ivt_flashloader.bin`) во внутреннюю папку бандла `data/`. Двухрежимный резолвер путей в коде должен прозрачно переключаться на `sys._MEIPASS` при `getattr(sys, 'frozen', False)`.
|
|
||||||
3. Скопировать стабильный отладочный образ `firmware_test_hab.bin` (Debug) в директорию `firmware/` на одном уровне с исполняемым файлом, обеспечивая возможность его замены инженерами без пересборки бандла.
|
|
||||||
4. Добавить кнопку «✕ Выйти из приложения» на стартовый экран `WaitingScreen` с привязкой к методу `self.app.exit()`.
|
|
||||||
* **Критерий успеха (Гейт):** На чистой машине (без установленного Python, uv и привязанных через Zadig драйверов) дистрибутив запускается, успешно определяет плату в SDP-режиме, загружает Flashloader, выполняет Chip Erase и зашивает диагностическую прошивку.
|
|
||||||
|
|
||||||
### Фаза 6 — Документация, CHANGELOG и выпуск релиза
|
|
||||||
|
|
||||||
* **Цель:** Финализация кодовой базы и документации.
|
|
||||||
* **Реализация:**
|
|
||||||
1. Выполнить тотальную зачистку (`grep-cleanup`) исходного кода. Удалить отладочные комментарии, неактуальные упоминания вызовов через `subprocess` и `uv run`. Актуализировать docstrings модулей `flash_backend.py` и `flasher.py`.
|
|
||||||
2. Сформировать финальный `CHANGELOG.md`, зафиксировав переход на монолит V4, нативную обработку ошибок USB-шины и отказ от Zadig.
|
|
||||||
3. Исключить инструкции по настройке Zadig из руководств `README.md` и `HOW_TO_FLASH.md`. Добавить описание ограничений первой версии (строго одна плата на стенде одновременно).
|
|
||||||
4. Создать релизный тег в Git, соответствующий текущему значению версии в `pyproject.toml`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Бэклог и пост-релизные задачи (Версии v1.1+)
|
|
||||||
|
|
||||||
Задачи, согласованные к реализации, но вынесенные за рамки стабильного релиза v1.0:
|
|
||||||
|
|
||||||
1. **Этап 7 — Provisioning платы:**
|
|
||||||
* Считывание уникального аппаратного идентификатора кристалла `OCOTP_UNIQUE_ID` средствами SDK-модуля `fsl_ocotp`.
|
|
||||||
* Передача события формата `{"type":"provision_ready","chip_uid":"..."}` на хост по завершении матрицы тестов.
|
|
||||||
* Ожидание подтверждения `provision_ack` от TUI и последующая фиксация статуса успешного прохождения в первом секторе Flash-памяти за пределами исполняемой зоны XIP (проработка логики защиты от повторной перезаписи).
|
|
||||||
2. **Экспорт результатов (POST-1):** Создание обработчика для автоматической выгрузки результатов диагностики в структурированный JSON-файл с жесткой привязкой к считанному UID микроконтроллера.
|
|
||||||
3. **UID буфер обмена:** Реализация механизма копирования или выделения UID контроллера напрямую с экрана терминала `DiagScreen`.
|
|
||||||
4. **Циклический прогон тестов:** Добавление тумблера «Циклический режим» для непрерывного фонового тестирования неинтерактивных узлов платы (SDRAM, QSPI Flash, CAN, Opto) с целью выявления плавающих аппаратных дефектов и температурной нестабильности элементов.
|
|
||||||
|
|
@ -1,467 +0,0 @@
|
||||||
# service-tui — Roadmap до релиза v1 (фазы 4a → 4b → 5 → 6)
|
|
||||||
|
|
||||||
> Продолжение `MONOLITH_APP_PLAN.md` (V4). Фазы 0–3 закрыты, Фаза 4
|
|
||||||
> закрыта частично — деструктивные гейты на железе вскрыли пробел в
|
|
||||||
> типизации обрыва USB (см. Фаза 4a). Этот документ — дорожная карта
|
|
||||||
> оставшегося пути до релиза.
|
|
||||||
>
|
|
||||||
> Ветка: `feature-tui-monolith` от `dev`. Каждая фаза = коммит(ы) с
|
|
||||||
> зелёным гейтом; откат любой фазы не ломает предыдущие.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Статус на входе
|
|
||||||
|
|
||||||
| Фаза | Статус |
|
|
||||||
| --- | --- |
|
|
||||||
| 0 — Спайк / де-риск | ✅ Закрыта (⚠В1/⚠В2/Р7/О1 сняты, golden-тест byte-exact) |
|
|
||||||
| 1 — Backend-модуль | ✅ Закрыта (`flash_backend.py`, `usb_ports.py`, 41 тест) |
|
|
||||||
| 2 — Пересадка `Flasher` | ✅ Закрыта (async-мост, гейт на железе) |
|
|
||||||
| 3 — Custom HAB in-process | ✅ Закрыта (де-факто вместе с фазой 1, подтверждена на железе) |
|
|
||||||
| 4 — Обработка отвала USB | ⚠️ **Частично** — see 4a |
|
|
||||||
| 4a — Добор типизации обрыва | ⏳ **Следующая** |
|
|
||||||
| 4b — Сокращение логов | ⏳ |
|
|
||||||
| 5 — Упаковка PyInstaller | ⏳ |
|
|
||||||
| 6 — Документация / релиз | ⏳ |
|
|
||||||
|
|
||||||
### Почему Фаза 4 не закрыта
|
|
||||||
|
|
||||||
Деструктивные гейты на железе (macOS) показали: **выдёргивание USB
|
|
||||||
проявляется тремя разными способами**, а код Фазы 4 корректно
|
|
||||||
типизирует только один.
|
|
||||||
|
|
||||||
| Проявление в spsdk | Что реально прилетает | Фаза 4 (сейчас) | Пользователь видит |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| write-фаза, обрыв при записи | `SPSDKConnectionError` | обёрнут только вокруг `with SDP`/`with McuBoot`, но реальный путь `write_memory` прошёл мимо | «Непредвиденная ошибка» (safety net) |
|
|
||||||
| read-фаза после write | `SPSDKTimeoutError` (потомок `SPSDKError`, **не** `SPSDKConnectionError`) | не ловится обёрткой обрыва | «Непредвиденная ошибка» (safety net) |
|
|
||||||
| chip erase по таймауту | `flash_erase_all()` вернул `False` (без исключения) | ветка `if not ok: raise FlashBackendError(...)` | «flash_erase_all вернул False» |
|
|
||||||
|
|
||||||
План Фазы 4 буквально требовал «`McuBootConnectionError`/**таймауты** →
|
|
||||||
`FlashBackendError(connection_lost=True)`». Таймаут (`SPSDKTimeoutError`)
|
|
||||||
и `False`-по-таймауту не были покрыты — значит Гейт 4 по букве не пройден.
|
|
||||||
Это добор, а не новая работа сверх плана.
|
|
||||||
|
|
||||||
> **Важно (UX-надёжность уже работает):** safety net (`except Exception`
|
|
||||||
> в `flasher.py`) во всех трёх случаях корректно вернул `ok=False`,
|
|
||||||
> разблокировал кнопки, оставил приложение живым. Проблема
|
|
||||||
> исключительно в *формулировке* сообщения, не в устойчивости.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Принятые решения этого этапа
|
|
||||||
|
|
||||||
| ID | Решение |
|
|
||||||
| --- | --- |
|
|
||||||
| **Р10** | Erase-таймаут (`False` без исключения) переклассифицируется в `ConnectionLostError` **вариантом B**: после `False` выполнить быстрый `detect_sdp()` — если устройство пропало с шины, это обрыв; если на месте — честная ошибка операции. |
|
|
||||||
| **Р11** | Троттлинг `write`-событий в `#flash-log` — каждые **10%** (≈10 строк вместо ~135). Прогресс-бар обновляется на **каждом** событии (плавность не теряется), в лог пишется прореженно. |
|
|
||||||
| **Р12** | Логирование: root/`spsdk`/`libusbsio` понижаются до `WARNING` по умолчанию; полный DEBUG доступен через env-переключатель (диагностика не теряется совсем). |
|
|
||||||
| **О2 (закрыт)** | Состав `firmware/` в релизе — только `firmware_test`; тип сборки (Debug/Release) управляется через `.env` (`FIRMWARE_BUILD_TYPE`), механизм уже реализован в `flasher.py`. |
|
|
||||||
| **POST-1** | Циклический прогон неинтерактивных тестов (SDRAM/NOR/OPTO/CAN) на `DiagScreen` — **отложен на пост-релиз**, вне `MONOLITH_APP_PLAN.md`. Зафиксирован, чтобы не потерять. |
|
|
||||||
| **Предложение 2** | Кнопка «Выйти из приложения» на `WaitingScreen` — принято, включается в Фазу 5 (UI-полировка перед упаковкой). |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Фаза 4a — Добор: корректная типизация обрыва USB
|
|
||||||
|
|
||||||
**Цель:** все три проявления обрыва USB дают пользователю единое
|
|
||||||
понятное сообщение «Соединение с платой потеряно», а не «Непредвиденная
|
|
||||||
ошибка» / «flash_erase_all вернул False».
|
|
||||||
|
|
||||||
### Файлы
|
|
||||||
|
|
||||||
| Файл | Тип правки |
|
|
||||||
| --- | --- |
|
|
||||||
| `app/flash_backend.py` | правки: расширить обёртку обрыва на `SPSDKTimeoutError`; реализовать вариант B для erase |
|
|
||||||
| `tests/test_flash_backend.py` | правки: тесты на `SPSDKTimeoutError`-путь и на erase-переклассификацию |
|
|
||||||
|
|
||||||
### Содержание
|
|
||||||
|
|
||||||
1. **Обёртка обрыва расширяется** с `SPSDKConnectionError` на общий
|
|
||||||
родительский случай, покрывающий и `SPSDKTimeoutError`. Оба —
|
|
||||||
потомки `SPSDKError`, но `SPSDKTimeoutError` **не** наследует
|
|
||||||
`SPSDKConnectionError`, поэтому текущий `except SPSDKConnectionError`
|
|
||||||
его пропускает. Ловим оба явным кортежем
|
|
||||||
`(SPSDKConnectionError, SPSDKTimeoutError)` в трёх местах:
|
|
||||||
`load_flashloader`, `flash` (основная + ram_only ветки), `erase_chip`.
|
|
||||||
|
|
||||||
2. **Вариант B для erase** (Р10): в `erase_chip` (и в `flash`, где
|
|
||||||
команды возвращают `False` по тем же причинам) — при `ok == False`
|
|
||||||
выполнить быстрый `detect_sdp()`; если устройство исчезло с шины →
|
|
||||||
`ConnectionLostError`, иначе → обычный `FlashBackendError` с прежним
|
|
||||||
текстом. Проверка `detect_sdp()` добавляется **только в error-путь**,
|
|
||||||
на happy path не влияет.
|
|
||||||
|
|
||||||
3. **`_format_error_message` в `flasher.py` не трогается** — он уже
|
|
||||||
корректно даёт префикс «Соединение с платой потеряно» для любого
|
|
||||||
`connection_lost=True`. Достаточно, чтобы backend правильно поднял
|
|
||||||
`ConnectionLostError`.
|
|
||||||
|
|
||||||
### Гейт 4a
|
|
||||||
|
|
||||||
- [ ] Юнит-тест: `SPSDKTimeoutError` из `write_memory` → `ConnectionLostError`
|
|
||||||
(мок).
|
|
||||||
- [ ] Юнит-тест: `flash_erase_all` → `False` + `detect_sdp()==False` →
|
|
||||||
`ConnectionLostError`; `False` + `detect_sdp()==True` →
|
|
||||||
обычный `FlashBackendError` (мок).
|
|
||||||
- [ ] Существующие 41 тест зелёные (регрессии нет).
|
|
||||||
- [ ] **Железо (повтор деструктивных сценариев):**
|
|
||||||
- [ ] Выдернуть USB во время `write-memory` → в `#flash-log`
|
|
||||||
«Соединение с платой потеряно», не «Непредвиденная ошибка».
|
|
||||||
- [ ] Выдернуть во время chip erase → то же сообщение.
|
|
||||||
- [ ] Повторная вставка → прошивка успешна (порт не «занят»).
|
|
||||||
- [ ] macOS + Windows.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Фаза 4b — Сокращение логов
|
|
||||||
|
|
||||||
**Цель:** лог-файл прошивки уменьшается на порядок; `#flash-log` в TUI
|
|
||||||
показывает осмысленный прогресс, а не ~135 однотипных строк.
|
|
||||||
|
|
||||||
### Файлы
|
|
||||||
|
|
||||||
| Файл | Тип правки |
|
|
||||||
| --- | --- |
|
|
||||||
| `app/main.py` | правки: уровни логгеров (Р12) + env-переключатель DEBUG |
|
|
||||||
| `app/screens/flash.py` | правки: троттлинг `write`-событий в `_on_progress` (Р11) |
|
|
||||||
|
|
||||||
### Содержание
|
|
||||||
|
|
||||||
1. **Уровни логгеров (Р12):** root по умолчанию `INFO`; `spsdk`,
|
|
||||||
`libusbsio`, `libusbsio.hidapi.dev`, `spsdk.mboot.protocol.bulk_protocol`
|
|
||||||
→ `WARNING` (именно они дают портянки HID-байтов). Полный DEBUG
|
|
||||||
включается через переменную окружения (например
|
|
||||||
`SERVICE_LOG_LEVEL=DEBUG`) — точное имя согласовать, не изобретаю.
|
|
||||||
|
|
||||||
2. **Троттлинг `#flash-log` (Р11):** в `flash.py::_on_progress`
|
|
||||||
прогресс-бар обновляется всегда, а `write_line` в лог для фазы
|
|
||||||
`write` — только при пересечении 10%-границы (0/10/20/…/100).
|
|
||||||
Остальные фазы (`configure`/`erase`/`fcb`/`reset`/`done`/`error`/
|
|
||||||
`hab_build`) логируются как есть — их немного.
|
|
||||||
|
|
||||||
### Гейт 4b
|
|
||||||
|
|
||||||
- [ ] Лог-файл одной прошивки на порядок короче; DEBUG-портянок
|
|
||||||
spsdk/libusbsio нет при дефолтном уровне.
|
|
||||||
- [ ] `SERVICE_LOG_LEVEL=DEBUG` (или согласованное имя) возвращает
|
|
||||||
полный DEBUG — диагностика доступна.
|
|
||||||
- [ ] `#flash-log`: ~10 строк прогресса записи вместо ~135, бар
|
|
||||||
по-прежнему плавный.
|
|
||||||
- [ ] Регрессия: прошивка/erase/диагностика на железе работают.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Фаза 5 — Упаковка PyInstaller + UI-полировка
|
|
||||||
|
|
||||||
**Цель:** один исполняемый бандл на Windows и macOS, проходящий полный
|
|
||||||
полевой цикл на чистой машине без Zadig/сети/Python. Плюс кнопка
|
|
||||||
«Выйти» на `WaitingScreen`.
|
|
||||||
|
|
||||||
### Файлы
|
|
||||||
|
|
||||||
| Файл | Тип правки |
|
|
||||||
| --- | --- |
|
|
||||||
| `tools/service_tui/service_tui.spec` | новый — PyInstaller spec |
|
|
||||||
| `app/screens/waiting.py` | правки: кнопка «Выйти из приложения» (Предложение 2) |
|
|
||||||
| just-рецепт | новый — имя задачи согласовать, **не изобретаю** |
|
|
||||||
| `app/app.tcss` | правки при необходимости — стиль кнопки Quit на Waiting |
|
|
||||||
|
|
||||||
### Содержание spec (из плана V4, §Фаза 5)
|
|
||||||
|
|
||||||
- `collect_data_files("spsdk")` (+ `SPSDK_DATA_FOLDER` при необходимости —
|
|
||||||
документированный NXP механизм для frozen);
|
|
||||||
- **`collect_dynamic_libs("libusbsio")`** — нативный HID-транспорт
|
|
||||||
(следствие Р7); `libusb-1.0.*` в бандле **отсутствует**;
|
|
||||||
- `datas`: `tools/host/dcd/{dcd.bin, w25q128_fdcb.bin, w25q512_fdcb.bin,
|
|
||||||
ivt_flashloader.bin}` → `data/`;
|
|
||||||
- `datas`: `firmware/<Type>/firmware_test_hab.bin` (Type из `.env`, О2);
|
|
||||||
- `datas`: `pyproject.toml` (для `_read_app_version` во frozen);
|
|
||||||
- onedir (не onefile — onefile замедляет старт распаковкой);
|
|
||||||
- резолвер путей backend'а уже готов: frozen → `sys.executable.parent`
|
|
||||||
(`firmware_hab_path`, `_resolve_custom_binaries_dir`).
|
|
||||||
|
|
||||||
Целевая структура бандла:
|
|
||||||
|
|
||||||
```
|
|
||||||
service-tui-vX.Y.Z-<os>/
|
|
||||||
├── service_tui[.exe]
|
|
||||||
├── _internal/
|
|
||||||
│ ├── data/ ← dcd.bin, *_fdcb.bin, ivt_flashloader.bin, spsdk data
|
|
||||||
│ └── ... ← рантайм PyInstaller, libusbsio
|
|
||||||
├── firmware/
|
|
||||||
│ └── <Type>/firmware_test_hab.bin
|
|
||||||
└── custom_binaries/ ← пустая
|
|
||||||
```
|
|
||||||
|
|
||||||
### UI-полировка (Предложение 2)
|
|
||||||
|
|
||||||
Кнопка «✕ Выйти из приложения» на `WaitingScreen`, симметрично
|
|
||||||
`FlashScreen`/`DiagScreen`/`PostFlashScreen` (`self.app.exit()`).
|
|
||||||
|
|
||||||
### Гейт 5 (Windows + macOS)
|
|
||||||
|
|
||||||
- [ ] Чистая Windows, **без Zadig, без сети, без Python/uv**: полный
|
|
||||||
полевой цикл — детект SDP → firmware_test → диагностика →
|
|
||||||
custom (W25Q128 и W25Q512) → chip erase.
|
|
||||||
- [ ] То же на macOS.
|
|
||||||
- [ ] Версия на `WaitingScreen` корректна во frozen.
|
|
||||||
- [ ] Порты резолвятся при перетыкании в другой физический USB-порт
|
|
||||||
(проверка Р8 на обеих ОС).
|
|
||||||
- [ ] Кнопка «Выйти» на `WaitingScreen` работает.
|
|
||||||
- [ ] M5StampPLC (нативный CDC `303A:4001`, драйверы не нужны —
|
|
||||||
подтверждено О1) виден во frozen-бандле.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Фаза 6 — Документация, CHANGELOG, финальная зачистка, релиз
|
|
||||||
|
|
||||||
**Цель:** синхронизировать документацию с реальностью монолита,
|
|
||||||
провести отложенную зачистку комментариев/grep, собрать релизный
|
|
||||||
артефакт из тега.
|
|
||||||
|
|
||||||
### Файлы
|
|
||||||
|
|
||||||
| Файл | Тип правки |
|
|
||||||
| --- | --- |
|
|
||||||
| `app/flash_backend.py`, `app/flasher.py`, `app/screens/flash.py`, `app/models.py` | правки: **отложенная зачистка комментариев** (grep-cleanup Гейта 4 + актуализация docstring-провенансов) |
|
|
||||||
| `CHANGELOG.md` | правки |
|
|
||||||
| `RELEASE_PLAN.md` | правки: закрыть шаг 3 ссылкой на V4/этот roadmap |
|
|
||||||
| `docs/DEV_ARCH.md` | правки: §2 (убрать subprocess из диаграммы), §8.3 (новый конвейер) |
|
|
||||||
| `HOW_TO_FLASH.md` | правки |
|
|
||||||
| `tools/service_tui/README.md` | правки |
|
|
||||||
| `.env.example` | правки: по Р9 (+`SERVICE_M5_VID/PID`, `FIRMWARE_BUILD_TYPE`, `SERVICE_LOG_LEVEL`) |
|
|
||||||
|
|
||||||
### Содержание
|
|
||||||
|
|
||||||
1. **Отложенная зачистка (из Фазы 4, согласовано):** финальный проход
|
|
||||||
по всему коду — актуализировать docstring-провенансы («прямой порт
|
|
||||||
flash_usb.py», «subprocess-версия» и т.п.) под реальность монолита.
|
|
||||||
Цель grep Гейта 4 (`flash_usb\|uv run\|subprocess\|usb.core` пусто
|
|
||||||
в `app/`) — либо достигается, либо остаётся осознанно как
|
|
||||||
документация происхождения (решение по каждому вхождению).
|
|
||||||
2. **CHANGELOG:** монолит (flash_backend, отказ от venv/subprocess),
|
|
||||||
нативный детект без Zadig (Р7), кроссплатформенный резолв портов (Р8),
|
|
||||||
нативная обработка отвала USB, упаковка одним exe.
|
|
||||||
3. **Zadig-инструкция в доки НЕ добавляется** (Р7 отменил план
|
|
||||||
RELEASE_PLAN). M5 — нативный CDC, вендорский драйвер не нужен (О1).
|
|
||||||
4. **Разделение зафиксировать:** `flash_usb.py` — dev-CLI (just-рецепты),
|
|
||||||
`flash_backend.py` — production-TUI; независимые реализации (Р2).
|
|
||||||
5. **Golden-тест HAB** — отметить как обязательный при апгрейде spsdk.
|
|
||||||
6. **Ограничение «одна плата на столе»** (О3) — в README.
|
|
||||||
7. **POST-1** (циклический прогон) — зафиксировать в бэклоге/README как
|
|
||||||
запланированную пост-релизную фичу.
|
|
||||||
8. Тег релиза = версия из `pyproject.toml`.
|
|
||||||
|
|
||||||
### Гейт 6
|
|
||||||
|
|
||||||
- [ ] Документация синхронизирована (железо подтверждено гейтами 4a/5).
|
|
||||||
- [ ] `just host::flash*`, `incoming`, `production` работают как раньше —
|
|
||||||
регрессия dev-пути.
|
|
||||||
- [ ] Релизный артефакт собран из тега; чек-лист Гейта 5 повторён на
|
|
||||||
релизном бинаре.
|
|
||||||
- [ ] POST-1 зафиксирован в бэклоге, не потерян.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Сводная последовательность и зависимости
|
|
||||||
|
|
||||||
```
|
|
||||||
4a ──► 4b ──► 5 ──► 6 ──► RELEASE v1
|
|
||||||
│ │ │ │
|
|
||||||
│ │ │ └── доки, зачистка, тег, регрессия dev-пути
|
|
||||||
│ │ └── PyInstaller (Win+macOS), кнопка Quit на Waiting
|
|
||||||
│ └── уровни логов + троттлинг #flash-log
|
|
||||||
└── типизация обрыва (SPSDKTimeoutError + erase вариант B)
|
|
||||||
|
|
||||||
Блокеры перед фазами:
|
|
||||||
4a: нет — старт сразу
|
|
||||||
4b: нет — после 4a
|
|
||||||
5: О2 закрыт ✅; согласовать имя just-задачи и env-переменной DEBUG
|
|
||||||
6: все гейты 4a/4b/5 зелёные на железе
|
|
||||||
```
|
|
||||||
|
|
||||||
## Открытые мелочи (согласовать по ходу, не блокируют старт 4a)
|
|
||||||
|
|
||||||
| Вопрос | Когда нужен | Предложение |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Имя env-переменной уровня лога | Фаза 4b | `SERVICE_LOG_LEVEL` (в стиле существующих `SERVICE_*`) |
|
|
||||||
| Имя just-задачи упаковки | Фаза 5 | согласовать по `Justfile`, не изобретаю |
|
|
||||||
| Формат имени релизного каталога | Фаза 5 | `service-tui-vX.Y.Z-<os>` (из плана) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Риски этого этапа
|
|
||||||
|
|
||||||
| Риск | Фаза | Митигация |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `detect_sdp()` в error-пути erase сам упадёт/подвиснет (шина уже нестабильна) | 4a | обернуть проверку в try/except, при любой ошибке — считать «устройство пропало» (обрыв); проверка уже в error-пути, хуже не сделает |
|
|
||||||
| Троттлинг скроет полезную деталь при отладке | 4b | полный DEBUG остаётся через env-переключатель |
|
|
||||||
| PyInstaller не соберёт нативные libusbsio / data spsdk | 5 | документированный NXP путь (`collect_dynamic_libs`, `SPSDK_DATA_FOLDER`); риск на CI, не в поле |
|
|
||||||
| frozen-резолв путей разойдётся с onedir-структурой | 5 | резолвер уже написан и покрыт тестом `test_firmware_hab_path_frozen` |
|
|
||||||
| Регрессия dev-пути (`just host::flash*`) после зачистки | 6 | `flash_usb.py` не трогался ни в одной фазе (Р2); гейт 6 это проверяет |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Приложение: работа в новом треде
|
|
||||||
|
|
||||||
Этот roadmap рассчитан на продолжение в **новом чате без контекста**
|
|
||||||
предыдущего. Ниже — всё, что нужно передать вместе с этим файлом, чтобы
|
|
||||||
новый тред стартовал без потерь.
|
|
||||||
|
|
||||||
## A. Какой набор правил к чему применяется
|
|
||||||
|
|
||||||
Проектные правила «Role & Hardware Context» (senior embedded C, i.MX
|
|
||||||
RT1052, LVGL, SDK HAL, C11, Doxygen, `.clang-tidy`/`.clang-format`,
|
|
||||||
CMake) написаны под **C/прошивочную** часть монорепо (`firmware_test`).
|
|
||||||
|
|
||||||
**Вся работа этого roadmap (4a→4b→5→6) — Python/spsdk/Textual** в
|
|
||||||
`tools/service_tui`. Поэтому:
|
|
||||||
|
|
||||||
| Правило | Применимо к Python-работе roadmap? |
|
|
||||||
| --- | --- |
|
|
||||||
| Unified diffs, не полные переписывания | ✅ Да |
|
|
||||||
| «Какой файл / какая функция затронуты» — первым | ✅ Да |
|
|
||||||
| ASK при неоднозначности/противоречии | ✅ Да |
|
|
||||||
| Не изобретать just-таски / пути / структуру | ✅ Да |
|
|
||||||
| Проверять существующие файлы перед правкой | ✅ Да |
|
|
||||||
| No malloc/free в драйверах и ISR | ❌ C-специфично |
|
|
||||||
| NXP SDK HAL вместо raw-регистров | ❌ C-специфично |
|
|
||||||
| Doxygen на public API | ❌ (Python — docstrings, уже используются) |
|
|
||||||
| `.clang-tidy`/`.clang-format` | ❌ (Python — стиль проекта: type hints, `from __future__ import annotations`) |
|
|
||||||
| CMake target_compile_options | ❌ Неприменимо |
|
|
||||||
|
|
||||||
Когда/если roadmap коснётся C-части — C-правила снова в силе.
|
|
||||||
|
|
||||||
## B. Первый вопрос на старте нового треда (не потерять)
|
|
||||||
|
|
||||||
**Фаза 4a, вариант B (Р10):** `detect_sdp()` в error-пути `erase_chip`
|
|
||||||
предлагается обернуть в `try/except`, и **любую ошибку самой проверки**
|
|
||||||
(не только «устройство отсутствует») трактовать как обрыв — потому что
|
|
||||||
проверка и так выполняется только после уже случившегося сбоя, шина
|
|
||||||
нестабильна, и «не смог проверить» практически всегда означает «платы
|
|
||||||
нет». Требуется явное подтверждение этой трактовки перед написанием
|
|
||||||
кода Фазы 4a. (Альтернатива: ошибка самой проверки → обычный
|
|
||||||
`FlashBackendError`.)
|
|
||||||
|
|
||||||
## C. Файлы, которые нужно предоставить — по фазам
|
|
||||||
|
|
||||||
Пути относительно `tools/service_tui/`, если не указано иное. Пометка
|
|
||||||
**[есть в этом треде]** — файл уже фигурировал и его актуальная версия
|
|
||||||
известна; в новом треде его всё равно нужно приложить заново.
|
|
||||||
|
|
||||||
### Фаза 4a — типизация обрыва
|
|
||||||
|
|
||||||
| Файл | Зачем |
|
|
||||||
| --- | --- |
|
|
||||||
| `app/flash_backend.py` **[правится]** | основной файл фазы — обёртки обрыва + вариант B |
|
|
||||||
| `tests/test_flash_backend.py` **[правится]** | новые тесты на `SPSDKTimeoutError` и erase-переклассификацию |
|
|
||||||
| `app/flasher.py` | контекст: `_format_error_message` / `_run_flash_op` — убедиться, что `connection_lost` доходит до UI (не факт что правится) |
|
|
||||||
| `app/models.py` | контекст: `FlashProgress` |
|
|
||||||
|
|
||||||
### Фаза 4b — логи
|
|
||||||
|
|
||||||
| Файл | Зачем |
|
|
||||||
| --- | --- |
|
|
||||||
| `app/main.py` **[правится]** | уровни логгеров + env-переключатель DEBUG (Р12) |
|
|
||||||
| `app/screens/flash.py` **[правится]** | троттлинг `#flash-log` в `_on_progress` (Р11) |
|
|
||||||
| `.env` / `.env.example` (`tools/service_tui/`) | согласовать имя `SERVICE_LOG_LEVEL` с существующими переменными |
|
|
||||||
|
|
||||||
### Фаза 5 — упаковка PyInstaller + UI
|
|
||||||
|
|
||||||
| Файл | Зачем |
|
|
||||||
| --- | --- |
|
|
||||||
| `pyproject.toml` (`tools/service_tui/`) | зависимости, версия, `requires-python` — база для spec |
|
|
||||||
| `Justfile` + все `*.just` (корневой и подключаемые: `build.just`, `ci.just`, `host.just`) | **согласовать имя задачи упаковки, НЕ изобретать** — критично по правилу проекта |
|
|
||||||
| `app/main.py` | entry point для PyInstaller |
|
|
||||||
| `app/app.py` | `CSS_PATH="app.tcss"` — как резолвится во frozen |
|
|
||||||
| `app/app.tcss` | data-файл для бандла; правки под кнопку Quit |
|
|
||||||
| `app/screens/waiting.py` **[правится]** | кнопка «Выйти» (Предложение 2) |
|
|
||||||
| `app/flasher.py`, `app/flash_backend.py` | frozen-резолв путей (`firmware_hab_path`, `_resolve_custom_binaries_dir`) — проверить против структуры бандла |
|
|
||||||
| дерево `tools/host/dcd/` (список файлов) | что кладём в `datas` (`dcd.bin`, `*_fdcb.bin`, `ivt_flashloader.bin`) |
|
|
||||||
| `project_tree.txt` или `ls -R tools/service_tui` | реальная структура пакета `app/` для spec |
|
|
||||||
| существующий `.spec`, если уже есть | не изобретать заново |
|
|
||||||
|
|
||||||
### Фаза 6 — документация и релиз
|
|
||||||
|
|
||||||
| Файл | Зачем |
|
|
||||||
| --- | --- |
|
|
||||||
| `CHANGELOG.md` | дописать секцию монолита |
|
|
||||||
| `RELEASE_PLAN.md` | закрыть шаг 3 ссылкой на этот roadmap |
|
|
||||||
| `docs/DEV_ARCH.md` | §2 (диаграмма без subprocess), §8.3 (новый конвейер) |
|
|
||||||
| `HOW_TO_FLASH.md` | актуализировать под TUI-backend |
|
|
||||||
| `tools/service_tui/README.md` | ограничение О3, POST-1, разделение dev-CLI / production-TUI |
|
|
||||||
| `.env.example` | по Р9 (+`SERVICE_M5_VID/PID`, `FIRMWARE_BUILD_TYPE`, `SERVICE_LOG_LEVEL`) |
|
|
||||||
| `app/flash_backend.py`, `app/flasher.py`, `app/screens/flash.py`, `app/models.py` | финальная зачистка комментариев (grep-cleanup Гейта 4) |
|
|
||||||
| `tools/host/flash_usb.py` | сверка при зачистке — что dev-CLI и правда не тронут (Р2) |
|
|
||||||
|
|
||||||
## D. Полный список актуальных файлов монолита (снимок на входе)
|
|
||||||
|
|
||||||
Чтобы в новом треде можно было приложить всё разом, если удобнее не
|
|
||||||
дробить по фазам. Актуальные (пост-Фаза-4) версии:
|
|
||||||
|
|
||||||
```
|
|
||||||
tools/service_tui/
|
|
||||||
├── pyproject.toml
|
|
||||||
├── app/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── app.py
|
|
||||||
│ ├── app.tcss
|
|
||||||
│ ├── main.py (точка входа — фактически в tools/service_tui/main.py, см. pyproject scripts)
|
|
||||||
│ ├── models.py
|
|
||||||
│ ├── flasher.py ← Фаза 2/4, актуальная версия
|
|
||||||
│ ├── flash_backend.py ← Фаза 1/4, актуальная версия (41 тест)
|
|
||||||
│ ├── usb_ports.py ← Фаза 1
|
|
||||||
│ ├── firmware_client.py
|
|
||||||
│ ├── m5_client.py
|
|
||||||
│ ├── orchestrator.py
|
|
||||||
│ ├── boot_art.py
|
|
||||||
│ ├── widgets.py (или widgets/)
|
|
||||||
│ └── screens/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── waiting.py
|
|
||||||
│ ├── flash.py ← Фаза 4 (правлены комментарии)
|
|
||||||
│ ├── post_flash.py
|
|
||||||
│ ├── connection_watcher.py
|
|
||||||
│ └── diag/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── confirm_panel.py
|
|
||||||
│ ├── results.py
|
|
||||||
│ └── test_list.py
|
|
||||||
├── tests/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ └── test_flash_backend.py ← 41 тест
|
|
||||||
├── spike/ (Фаза 0, в релиз не идёт)
|
|
||||||
│ ├── spike_hab.py
|
|
||||||
│ ├── spike_flash.py
|
|
||||||
│ └── spike_readback.py (диагностика Гейта 3, на будущее)
|
|
||||||
└── custom_binaries/ (пустая, для оператора)
|
|
||||||
|
|
||||||
tools/host/ (dev-CLI, Р2 — НЕ трогается)
|
|
||||||
├── flash_usb.py
|
|
||||||
└── dcd/
|
|
||||||
├── ivt_flashloader.bin
|
|
||||||
├── dcd.bin
|
|
||||||
├── w25q128_fdcb.bin
|
|
||||||
└── w25q512_fdcb.bin
|
|
||||||
```
|
|
||||||
|
|
||||||
> Примечание: `main.py` в `pyproject.toml` прописан как
|
|
||||||
> `service-tui = "main:main"` — точка входа лежит в
|
|
||||||
> `tools/service_tui/main.py` (не в `app/`), а `app/app.py` содержит
|
|
||||||
> `ServiceApp`. Уточнить фактическое расположение при старте Фазы 4b/5.
|
|
||||||
|
|
||||||
## E. Что уже решено и не пересматривается (сводка для нового треда)
|
|
||||||
|
|
||||||
- **Р1–Р9** — см. `MONOLITH_APP_PLAN.md` (приложить его тоже).
|
|
||||||
- **Р10** — erase-таймаут → вариант B (detect_sdp после False).
|
|
||||||
- **Р11** — троттлинг лога 10%.
|
|
||||||
- **Р12** — уровни логов + env DEBUG.
|
|
||||||
- **О1** — M5 = нативный CDC `303A:4001`, драйверы не нужны.
|
|
||||||
- **О2** — `firmware/` = только firmware_test, тип сборки через `.env`.
|
|
||||||
- **О3** — одна плата на столе, ограничение v1.
|
|
||||||
- **POST-1** — циклический прогон тестов, после релиза.
|
|
||||||
- Публичный API `Flasher` заморожен; `flash.py`/`waiting.py`/`app.py`
|
|
||||||
меняются только там, где явно указано в roadmap.
|
|
||||||
- `flash_usb.py` (dev-CLI) не трогается ни в одной фазе.
|
|
||||||
- Порядок ревью: один файл за раз, полные файлы для новых/целиком
|
|
||||||
переписываемых, unified diff для точечных правок.
|
|
||||||
Loading…
Reference in a new issue