Добавить протокол Altera Logic и общие клиенты прошивки

This commit is contained in:
2026-09-19 07:12:09 +03:00
parent def3eb08f3
commit 80ba17d77d
38 changed files with 3271 additions and 10 deletions

View File

@@ -0,0 +1,178 @@
# База прошивок как в SETGUI
Это общий сервис **базы файлов прошивок**: получить список опубликованных
образов, выбрать изделие/версию/транспорт, скачать файл, проверить SHA-256 и
передать его загрузчику. Также можно опубликовать новый образ в эту же базу.
Модуль не зависит от версии самого приложения и от `c/firmware-info`.
## Состав
| Файл | Ответственность |
|---|---|
| [`firmware_database.py`](../../python/setprotocol/firmware_database.py) | Готовый HTTPS-клиент, чтение базы, кэш скачанных образов, публикация в Gitea |
| [`firmware_catalog.py`](../../python/setprotocol/firmware_catalog.py) | Общий формат записей базы, используемый SETGUI |
| [`firmware_publish.py`](../../python/setprotocol/firmware_publish.py) | Метаданные образа, SHA-256, объединение записей каталога |
| [`firmware_db.py`](firmware_db.py) | Самостоятельный консольный запуск без SETGUI |
Требования: Python 3.10+ и стандартная библиотека. Qt, Windows Credential
Manager, установленный SETGUI и библиотеки МК не требуются. Это сервис и CLI;
готовое Qt-окно остаётся в SETGUI, а другой GUI подключает сервис по API ниже.
## Как устроена база
В текущей конфигурации SETGUI файлы находятся в Gitea Releases репозитория
`setcorp/SETRD12-Releases` на `https://git.rd12.ru`. Список файлов лежит в
`firmware.releases` файла `update.json` ветки `main`. Запись содержит изделие,
версию, транспорт, адрес образа, имя файла, SHA-256, описание и необязательный
адрес приложения во Flash. Это файловый каталог; SQL-сервер не нужен.
Новый сервис использует тот же JSON-контракт, поэтому выпуски видны клиентам
SETGUI, поддерживающим указанный транспорт. В текущем SETGUI транспорт `tms`
фильтруется; общий сервис его сохраняет. Публикация не добавляет новый
загрузочный протокол в приложение или МК.
```text
публикация .hex/.bin → Gitea release asset → скачать и проверить SHA-256
↓
обновить firmware.releases
↓
GUI: «Обновить каталог» → выбрать запись → «Скачать и выбрать» → загрузчик МК
```
Для своего сервера задайте `server`, `owner`, `repository`, `branch` и
`manifest_path`. Репозиторий, ветка и JSON-файл должны уже существовать.
Для пустой базы достаточно файла `{"firmware":{"catalogVersion":0,"releases":[]}}`.
Если `update.json` общий с обновлениями приложения, его другие разделы
сохраняются при добавлении прошивки.
## Консоль: проверить, опубликовать, посмотреть, скачать
Из корня `templates` в PowerShell (параметры сервера стоят перед командой):
```powershell
$db = @('--server', 'https://git.rd12.ru', '--owner', 'setcorp', '--repo', 'SETRD12-Releases')
# Без сети: проверить файл и сформировать запись каталога.
python tools/firmware-publish/firmware_db.py @db preflight --file C:/build/device.hex --product MY_DEVICE --version 1.2.3 --version-code 0x010203 --transport can --base-address 0x08003000
# Публикация образа в базу (изменяет сервер).
python tools/firmware-publish/firmware_db.py @db publish --file C:/build/device.hex --product MY_DEVICE --version 1.2.3 --version-code 0x010203 --transport can --base-address 0x08003000
# Получить список записей и скачать конкретную.
python tools/firmware-publish/firmware_db.py @db list --product MY_DEVICE
python tools/firmware-publish/firmware_db.py @db download --product MY_DEVICE --version-code 0x010203 --transport can --cache C:/firmware-cache
```
`MY_DEVICE`, версия, транспорт и адрес — примеры, замените их данными изделия.
SCI8 для TMS публикуйте как `.bin` с `--transport tms`, без `--base-address`.
Инструмент не конвертирует `.axf/.out` и не проверяет пригодность образа для МК.
CLI читает логин и пароль/токен из переменных окружения `FIRMWARE_DB_LOGIN` и
`FIRMWARE_DB_PASSWORD`. Задавайте их средствами своего секрет-хранилища/CI,
а не в файлах проекта и не в аргументах командной строки. Для интерактивного
ввода в PowerShell можно использовать:
```powershell
$fwCredential = Get-Credential -Message 'Gitea: логин и пароль либо токен вместо пароля'
$env:FIRMWARE_DB_LOGIN = $fwCredential.UserName
$env:FIRMWARE_DB_PASSWORD = $fwCredential.GetNetworkCredential().Password
# Выполнить нужные команды; после работы убрать переменные из текущего процесса.
Remove-Item Env:FIRMWARE_DB_LOGIN, Env:FIRMWARE_DB_PASSWORD
```
Публичный каталог можно читать без входа. Публикация через CLI требует обе
переменные. Сервис не извлекает сохранённый пароль SETGUI автоматически.
Команда `publish` явно разрешает запись и не запрашивает дополнительного
подтверждения; `preflight` ничего не загружает. Старый BAT и конфигурации
`firmware-release.cmd` продолжают использовать прежний путь через SETGUI.
## Портирование в другое Python-приложение
Подключите `templates/python` в import path приложения; переносите зависимость
целиком, не одну копию файла. Сеть и кэш реализованы в сервисе, UI и хранение
учётных данных задаёт приложение:
```python
from pathlib import Path
from setprotocol.firmware_database import (
Credentials, FirmwareDatabase, GiteaRepository, GiteaFirmwarePublisher,
)
from setprotocol.firmware_publish import FirmwarePublication
repo = GiteaRepository('https://git.rd12.ru', 'setcorp', 'SETRD12-Releases')
# login и password получает ваше приложение из формы/хранилища.
credentials = Credentials(login, password)
database = FirmwareDatabase(repo.manifest_url, Path('firmware'), credentials=credentials)
# «Обновить каталог»: результат заполнит список в вашем UI.
releases = database.read_catalog(product='MY_DEVICE', transport='can')
# После выбора пользователем строки списка:
selected = releases[selected_index]
path = database.download(selected, progress=lambda percent: print(percent))
# Передайте path, selected.transport и selected.base_address своему загрузчику.
# Публикация нового образа: отдельное действие оператора.
publisher = GiteaFirmwarePublisher(repo, credentials)
publication = FirmwarePublication(
Path('build/device.hex'), 'MY_DEVICE', '1.2.3', 0x010203, 'can', 0x08003000,
'Описание выпуска',
)
preview = publisher.preflight(publication) # можно показать перед выпуском
entry = publisher.publish(publication)
```
Для публичного каталога передайте `credentials=None`. Для чтения каталога,
расположенного вне стандартного Gitea URL, передайте его HTTPS-адрес прямо в
`FirmwareDatabase`; `GiteaRepository` нужен сетевому публикатору.
В Qt выполняйте `read_catalog`, `download`, `publish` в worker/QThread,
передавайте прогресс сигналами в главный поток. По аналогии с SETGUI:
1. Кнопка «Обновить каталог» вызывает `read_catalog`, заполняет combo/table.
2. В строке показываются `product`, `version`, `transport`; в деталях —
`file_name`, `notes`, `base_address`.
3. «Скачать и выбрать» вызывает `download`, возвращает проверенный путь.
4. Сигнал в основное окно передаёт путь и выбранную `FirmwareRelease`;
окно выставляет файл и транспорт на вкладке прошивки.
Скачивание не запускает прошивку устройства автоматически. Для C++/Android
можно использовать CLI как отдельный процесс на ПК или реализовать клиент
этого JSON-контракта на языке платформы; Python-модуль не является C-библиотекой.
## Поведение и ошибки
- Кэш: `<cache>/<sha256>/<fileName>`. Одинаковые имена разных образов не
конфликтуют. Проверенный кэш используется повторно; незавершённые временные
файлы удаляются. Результат появляется только после совпадения SHA-256.
- Только HTTPS. Учётные данные отправляются лишь исходному origin (схема,
хост, порт); на сторонний CDN и при смене origin в redirect не передаются.
Redirect для запросов записи отклоняется.
- Публикация использует asset с SHA-256 в имени: новые байты не заменяют файл,
на который ещё ссылается старый каталог. `fileName` в каталоге остаётся
исходным именем. В этом деталь реализации отличается от старого публикатора
SETGUI, но формат каталога совместим.
- Каталог меняется только после обратного скачивания образа. Запись проверяет
ревизию файла (`sha`). При конфликте операция завершается ошибкой: повторите
выпуск после проверки причины. Загруженный asset может остаться без записи
каталога; повторный запуск использует его и снова проверит скачивание.
- Повтор той же записи не увеличивает `catalogVersion`. Обновление одного
`product + versionCode + transport` заменяет запись. Полный rollback и
автоматическая очистка старых assets не выполняются.
- Текущий upload держит образ в памяти (лимит 128 MiB), чтобы параллельная
пересборка не подменила байты между вычислением хэша и отправкой.
## Проверка
Из `templates`:
```powershell
$env:PYTHONPATH = "$PWD/python"
python -m unittest discover -s python/tests -p test_firmware_database.py -v
python -m unittest discover -s python/tests -p test_firmware_publish.py -v
```
Тесты используют сервер в памяти: каталог → публикация → скачивание, повторный
выпуск, сохранение других разделов, ошибки SHA-256 и конфликты ревизии,
изоляцию авторизации при redirect. Проверка реальной Gitea и загрузка на МК
в эти тесты не входят. Перед производственным подключением проверьте выпуск
в тестовом репозитории вашей Gitea и чтение каталога целевым GUI.

View File

@@ -0,0 +1,164 @@
# Подключение публикации прошивки к новому проекту
**Дополнение:** полная независимая библиотека базы прошивок теперь реализована
в `python/setprotocol/firmware_database.py`, с CLI `firmware_db.py`.
Чтение, скачивание, публикация и перенос GUI описаны в [`DATABASE.md`](DATABASE.md).
Ниже сохранено описание интеграции через прежний BAT и SETGUI.
## Что уже реализовано
По локальным исходникам на 19 сентября 2026 года общий механизм уже есть.
Создавать вторую библиотеку для той же схемы SETGUI не требуется:
| Компонент | Назначение | Зависимость |
|---|---|---|
| `c/firmware-info` | Версия реально запущенного образа, 12 слов / 24 байта | C-config проекта, без сети |
| `python/setprotocol/firmware_publish.py` | Проверка параметров файла, SHA-256, release tag, запись и обновление каталога | Python stdlib, `firmware_catalog.py` |
| `python/setprotocol/firmware_catalog.py` | Модель и чтение `firmware.releases` | Python stdlib |
| `tools/firmware-publish/PUBLISH_FIRMWARE.bat` | Конфигурация IDE, транспорты, проверка Git, запуск выпуска | Windows, checkout SETGUI и его `.venv` |
| `SETGUI/scripts/publish_firmware.py` | Загрузка в Gitea, скачивание и проверка SHA-256, запись и повторное чтение `update.json` | Общие Python-модули, API и учётные данные SETGUI |
Модуль `firmware_publish.py` сам не выполняет HTTP-запросы. Старый общий BAT
использует сетевой публикатор SETGUI. Для автономного CI без SETGUI теперь
используйте `GiteaFirmwarePublisher` из `firmware_database.py` или CLI
`firmware_db.py`; одного `firmware_publish.py` для загрузки недостаточно.
```text
проект МК → сборка .hex/.bin → общий BAT → SETGUI/scripts/publish_firmware.py
↓
setprotocol.firmware_publish
↓
Gitea release asset → SHA-256 → update.json
проект МК → firmware-info → обработчик версии → транспорт → клиент
```
Эти две цепочки не синхронизируют версии автоматически. Bootloader и запись
Flash — отдельные компоненты (`rs485-boot`, `protocan-boot`, `set-protocol`);
публикация файла не добавляет поддержку обновления в устройство.
## Фактические подключения в этой рабочей папке
- `KONOR_ds18b20/src/main.c`: `app_send_firmware_info()` и команда `0x03`.
`mdk/SETGUI_DS18B20.uvprojx` подключает оба C-файла через `..\lib\c`, config
через `..\inc`, generated через `mdk/Generated`, генератор — Before Build.
- В текущем checkout `KONOR_ds18b20/lib/c` является Windows junction на общий
`newProject/templates/c`. Это локальная связь; её нельзя считать переносимым
путём для другого компьютера. `.gitmodules` при этом декларирует `templates`
в `lib`: перед переносом нужно сверить реальную раскладку, а не копировать
пути из Keil вслепую. Корневой `firmware-release.cmd` у KONOR при проверке
отсутствует.
- SETGUI импортирует модули публикации из `SETGUI/third_party/templates/python`.
Изменение соседнего `newProject/templates/python` само по себе эту копию не
обновляет. Версию зависимости в SETGUI обновляют отдельно.
- `SETGUI/src/gui_desktop/firmware_update.py` читает каталог через общий parser.
Поиск по текущему `SETGUI/src` не обнаружил обработчика `FIRMWARE_INFO` /
`firmware_info`: упаковка совместимого ответа ещё не означает, что эта версия
клиента запрашивает и показывает расширенный контракт KONOR.
- В исходниках сетевого публикатора задан Gitea `https://git.rd12.ru`, владелец
`setcorp`, репозиторий `SETRD12-Releases`, ветка `main`, файл `update.json`.
Это настройки кода, а не результат проверки доступности сервера.
## 1. Выбрать раскладку
Для нового проекта используйте явное расположение зависимости:
```text
workspace/
SETGUI/
PUBLISH_FIRMWARE.bat
.venv/Scripts/python.exe
MyFirmware/
firmware-release.cmd
lib/templates/tools/firmware-publish/PUBLISH_FIRMWARE.bat
mdk/build/application.hex
```
Подключите `templates` принятой в проекте системой зависимостей. Скопируйте
только конфигурацию из `examples/keil-firmware-release.cmd` или
`examples/ccs12-firmware-release.cmd` в корень прошивки. Общий BAT оставьте
в зависимости.
## 2. Заполнить firmware-release.cmd
Пример для приложения STM32 с bootloader; адрес `0x08003000` является примером
и должен совпасть с linker script и настройками загрузчика вашего устройства:
```bat
@echo off
set "FW_PROJECT_ROOT=%~dp0"
set "FW_SETGUI_ROOT=%~dp0..\SETGUI"
set "FW_PRODUCT=MY_DEVICE"
set "FW_VERSION=1.2.3"
set "FW_VERSION_CODE=0x00010203"
set "FW_TRANSPORTS=can rs485"
set "FW_BASE_ADDRESS=0x08003000"
set "FW_IMAGE=mdk\build\application.hex"
set "FW_NOTES=Firmware 1.2.3"
```
`FW_PRODUCT` должен соответствовать идентификатору изделия, с которым клиент
выбирает прошивку. Сверьте `FW_VERSION` с C-config; при упаковке версии в три
8-битных поля используйте части 0–255. Укажите только реально реализованные
транспорты. Parser допускает `can`, `rs485`, `stm32`, `tms`; для их применения
нужна соответствующая поддержка клиента и загрузчика.
Все транспорты одного запуска используют **один и тот же файл**. Если образы
для CAN и RS-485 отличаются, заведите разные конфигурации с разными именами
файлов и запускайте их отдельно. Одинаковое имя asset в одном release tag
нельзя использовать для разных образов.
Для CCS 12 создайте SCI8 boot `.bin` через C2000 Hex Utility (`--binary --boot
--sci8`), задайте `FW_TRANSPORTS=tms` и очистите адрес:
`set "FW_BASE_ADDRESS="`. `.out` и `.axf` публикатор не преобразует.
## 3. Проверить и выпустить
Подготовьте окружение SETGUI по документации этого проекта. Учётные данные
Gitea сохраняются через окно SETGUI «Версия и обновление»; в `.cmd` они не нужны.
Из корня MyFirmware в cmd.exe:
```bat
call "lib\templates\tools\firmware-publish\PUBLISH_FIRMWARE.bat" --config "firmware-release.cmd" --preflight
```
Для post-build в `mdk` используйте пути `..\lib\templates\...` и
`--config "..\firmware-release.cmd"`. В post-build оставляйте `--preflight`.
Preflight проверяет существование, расширение, размер, метаданные, SHA-256 и
схему записи каталога. Он **не разбирает содержимое Intel HEX/SCI8**, не сверяет
версию с C-config, адреса с linker script и файл с текущими исходниками.
Успех preflight не заменяет Rebuild и загрузку на тестовое устройство.
После проверки конкретного Release-образа и фиксации его исходников:
```bat
call "lib\templates\tools\firmware-publish\PUBLISH_FIRMWARE.bat" --config "firmware-release.cmd" --publish
```
Скрипт запросит подтверждение. Успех означает загрузку asset, проверку скачанных
байтов, обновление каталога и его повторное чтение. Несколько транспортов
публикуются последовательно, общей транзакции для всего запуска нет. Откройте
базу прошивок SETGUI, обновите каталог и проверьте загрузку на тестовое устройство.
## 4. Другой сервер или собственный инструмент
`FW_SETGUI_ROOT` выбирает checkout SETGUI, а не адрес Gitea. Переменная
`SETGUI_UPDATE_MANIFEST_URL` меняет URL чтения каталога, но не перенастраивает
`WEB_ROOT`, `OWNER`, `RELEASE_REPO` сетевого публикатора. Для другого сервера
нужно согласованно перенести сетевой адаптер, адреса чтения и авторизацию.
Для собственного Python-инструмента добавьте `templates/python` в import path
и используйте `FirmwarePublication`, `sha256_file`, `firmware_release_tag`,
`firmware_release_entry`, `update_firmware_manifest` из
`setprotocol.firmware_publish`. Вызывайте `publication.validate()` до создания
записи. Сетевой адаптер должен обеспечить тот же порядок, что SETGUI:
1. Загрузить образ в release и скачать его обратно, сверить SHA-256.
2. Прочитать актуальный manifest, объединить через `update_firmware_manifest`.
3. Записать manifest с проверкой исходной ревизии, чтобы не затереть чужое
параллельное изменение; конфликт требует повторного чтения и объединения.
4. Перечитать каталог через `parse_firmware_catalog` и проверить запись выпуска.
Не переносите пароли из профиля SETGUI в исходники или конфигурации проекта.

View File

@@ -1,10 +1,20 @@
# Публикация прошивки из Keil и CCS 12
**Самостоятельная база прошивок без SETGUI:**
[`DATABASE.md`](DATABASE.md) — библиотека и CLI для чтения каталога,
скачивания и публикации образов. Ниже описан прежний BAT-путь через SETGUI.
Архитектура, фактические зависимости и пошаговое подключение нового проекта:
[`PORTING.md`](PORTING.md). Общая Python-библиотека подготовки каталога уже
находится в `python/setprotocol/firmware_publish.py`; сетевую загрузку выполняет
SETGUI.
Этот комплект связывает проект прошивки с каталогом SETGUI. IDE по-прежнему
собирает штатный загрузочный файл, а `PUBLISH_FIRMWARE.bat`:
1. читает метаданные выпуска из `firmware-release.cmd`;
2. проверяет наличие и формат `.hex` или `.bin`;
2. проверяет наличие, расширение и размер `.hex` или `.bin` (содержимое
Intel HEX/SCI8 и совместимость с устройством не проверяются);
3. запускает локальную проверку SETGUI (`--preflight`) либо публикацию
(`--publish`);
4. для каждого транспорта загружает образ в Gitea, скачивает его обратно,

View File

@@ -0,0 +1,82 @@
"""Standalone firmware database CLI; requires Python 3.10+, no SETGUI."""
from __future__ import annotations
import argparse
import json
import os
import sys
from dataclasses import asdict
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "python"))
from setprotocol.firmware_database import (
Credentials, FirmwareDatabase, GiteaFirmwarePublisher, GiteaRepository,
)
from setprotocol.firmware_publish import FirmwarePublication, SUPPORTED_TRANSPORTS
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--server", required=True, help="Gitea HTTPS URL")
parser.add_argument("--owner", required=True)
parser.add_argument("--repo", required=True)
parser.add_argument("--branch", default="main")
parser.add_argument("--manifest", default="update.json")
commands = parser.add_subparsers(dest="command", required=True)
catalog = commands.add_parser("list")
catalog.add_argument("--product")
catalog.add_argument("--transport", choices=sorted(SUPPORTED_TRANSPORTS))
download = commands.add_parser("download")
download.add_argument("--product", required=True)
download.add_argument("--version-code", type=lambda v: int(v, 0), required=True)
download.add_argument("--transport", choices=sorted(SUPPORTED_TRANSPORTS), required=True)
download.add_argument("--cache", type=Path, default=Path("firmware"))
for name in ("preflight", "publish"):
command = commands.add_parser(name)
command.add_argument("--file", type=Path, required=True)
command.add_argument("--product", required=True)
command.add_argument("--version", required=True)
command.add_argument("--version-code", type=lambda v: int(v, 0), required=True)
command.add_argument("--transport", choices=sorted(SUPPORTED_TRANSPORTS), required=True)
command.add_argument("--base-address", type=lambda v: int(v, 0))
command.add_argument("--notes", default="")
args = parser.parse_args()
try:
repository = GiteaRepository(args.server, args.owner, args.repo, args.branch, args.manifest)
login = os.environ.get("FIRMWARE_DB_LOGIN")
password = os.environ.get("FIRMWARE_DB_PASSWORD")
credentials = Credentials(login, password) if login and password else None
if args.command in ("list", "download"):
database = FirmwareDatabase(repository.manifest_url,
getattr(args, "cache", Path("firmware")),
credentials=credentials)
rows = database.read_catalog(product=args.product, transport=args.transport)
if args.command == "list":
print(json.dumps([asdict(row) for row in rows], ensure_ascii=False, indent=2))
else:
rows = [row for row in rows if row.version_code == args.version_code]
if len(rows) != 1:
raise ValueError("Expected exactly one matching firmware release")
print(database.download(rows[0]))
else:
publication = FirmwarePublication(args.file.resolve(), args.product, args.version,
args.version_code, args.transport,
args.base_address, args.notes)
publisher = GiteaFirmwarePublisher(repository, credentials)
if args.command == "publish":
if credentials is None:
raise ValueError("Set FIRMWARE_DB_LOGIN and FIRMWARE_DB_PASSWORD for publication")
result = publisher.publish(publication)
else:
result = publisher.preflight(publication)
print(json.dumps(result, ensure_ascii=False, indent=2))
except (OSError, ValueError, RuntimeError) as error:
print(f"Firmware database: {error}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())