Howto: обновить прошивку (OTA)¶
Контроллер использует двухсекционную OTA: новый образ пишется в
app1 (если активен app0) и наоборот. После апплая bootloader пробует
загрузиться с новой секции. Если boot-validation падает — автоматический
rollback на предыдущую рабочую версию.
1. Подготовить артефакты¶
После pio run в .pio/build/esp32dev/:
firmware.bin— сам бинарь (~1.5 МБ)manifest.json— метаданные (генерируетсяtools/export_artifacts.py)
Манифест содержит fw_version, sha256, total_bytes, product_id,
board_id, hw_rev, expected_eeprom_schema. Подробности — в
src/ota/FirmwareManifest.cpp.
2. Обновление через CLI¶
DEV=http://awdc.local
ADMIN=admin:NEW_ADMIN_PASSWORD
# 1. Передать манифест → менеджер валидирует совместимость
curl -u $ADMIN -X POST \
-H "Content-Type: application/json" \
--data-binary @manifest.json \
$DEV/api/ota/prepare
# 2. Залить бинарь (raw bytes). На медленной сети может занять минуту-две
curl -u $ADMIN -X POST \
-H "Content-Type: application/octet-stream" \
--data-binary @firmware.bin \
$DEV/api/ota/upload
# 3. Применить → устройство перезагрузится через 500 мс
curl -u $ADMIN -X POST $DEV/api/ota/apply
# (соединение оборвётся — нормально)
После reboot подождать 10…20 секунд, потом проверить:
Успех: {"success":true,"applied_version":"...","ota_partition":"app1"}.
Если был rollback — поле success:false и rolled_back_to:"...".
3. Мониторинг прогресса¶
Во время заливки можно опросить статус из другого терминала:
Покажет state, uploaded_bytes / total_bytes, computed_sha256.
4. Отмена¶
Если что-то пошло не так до apply:
После cancel слот освободится, можно начинать заново с prepare.
5. Rollback policy¶
После apply устройство пишет флаг ota_pending=true и пробует
загрузиться. На первом успешном boot:
- Если probe-валидация (NVS, EEPROM, периферия) прошла — фиксируется
как «good», ota_pending=false.
- Иначе — bootloader при следующем reboot откатится на предыдущий
раздел. Счётчик попыток b_att ограничивает loop.
Детали — src/ota/BootValidation.cpp,
ADR в doc/dev/adr/adr-003-*.md.
6. Совместимость¶
Менеджер проверяет в prepare:
- product_id — должен совпадать (AWDC)
- board_id — версия платы
- hw_rev — ревизия железа
- expected_eeprom_schema — для миграции счётчиков
При несовпадении возвращает 400 с описанием.
7. Pull-mode: обновление с сервера обновлений¶
Альтернатива ручному upload — контроллер сам скачивает прошивку с https-хоста. Удобно, когда сборочная машина выкладывает свежие билды автоматически (см. §8). На контроллере:
- Web-UI → Система → Обновление прошивки (OTA). В шапке карточки
видно бейдж
сервер сконфигурирован, если URL уже задан. - Раскрыть подраздел «Прошивка с сервера обновлений».
- В поле URL вписать https-корень хоста (напр.
https://awdc.nikolaev.world/) и «Сохранить». URL пишется в NVSota/host_url. - «Проверить» → контроллер тянет
<host>/firmware/index.json. После успешной проверки рядом появляется зелёная ✓ и блок:На хосте: <fw_version> · <sha> · <size> · <built_at>. - Если есть более новая версия — кнопка «Скачать и подготовить».
Прогресс-бар идёт от того же
/api/ota/status(uploaded/total в байтах + КБ/с + ETA), что и ручной upload. - По завершении state переходит в
UPLOAD_DONE→ общий шаг 3 «Применить» (как при ручном upload).
Эндпоинты (если предпочитаете curl):
curl -u $ADMIN -X POST $DEV/api/ota/host -d '{"url":"https://awdc.nikolaev.world/"}'
curl -u $ADMIN -X POST $DEV/api/ota/check # → JSON с latest
curl -u $ADMIN -X POST $DEV/api/ota/pull -d '{"sha":"<sha>"}' # 202, pull-task в фоне
curl -u $ADMIN $DEV/api/ota/status # прогресс
curl -u $ADMIN -X POST $DEV/api/ota/apply # после UPLOAD_DONE
/api/ota/pull отвечает 202 сразу — pull идёт в отдельной FreeRTOS-таске,
прогресс смотреть через /api/ota/status. pull_error в /api/ota/host
показывает, если что-то упало (TLS handshake, HTTP code != 200, размер
не совпал с manifest).
8. Auto-deploy: выкладка свежей прошивки на сервер обновлений¶
На сборочной машине каждый успешный pio run может автоматически
заливать новые артефакты на хост обновлений. Опт-ин через deploy.env
в корне репо (см. также подробный pipeline в devops.md).
8.1. Подготовить хост¶
Один раз на сервере обновлений:
sudo mkdir -p /var/www/awdc.nikolaev.world/firmware
sudo chown andy:www-data /var/www/awdc.nikolaev.world/firmware
sudo chmod 755 /var/www/awdc.nikolaev.world/firmware
В nginx добавлен location /firmware/ (см.
max-webapp/scripts/nginx.conf.example) — JSON без кэша, .bin/.sha256
с immutable на 30 дней.
8.2. SSH-ключ для логина без пароля¶
deploy-firmware.sh запускается из-под pio run неинтерактивно
(BatchMode=yes) — пароль набрать некуда. Нужен SSH-ключ.
Способ A (рекомендуемый) — ssh-keygen в git-bash, одной командой:
ssh-keygen -t ed25519 -f /c/Users/<you>/.ssh/awdc_deploy -N "" -C "awdc-deploy"
cat /c/Users/<you>/.ssh/awdc_deploy.pub
Содержимое .pub дописать на хосте в ~/.ssh/authorized_keys нужного
юзера (DEPLOY_HOST).
Способ B — PuTTYgen. Подвох: для Ed25519 рабочий пункт меню —
Conversions → Export OpenSSH key (force new file format)
а не соседний «Export OpenSSH key» без скобок — последний для Ed25519 пишет устаревший PEM-контейнер, и современный OpenSSH/libcrypto падает с:
Если уже наступили — откройте .ppk обратно в PuTTYgen и переэкспортируйте
правильным пунктом. Сам публичный ключ из верхнего окошка PuTTYgen
дописать в authorized_keys как в способе A.
8.3. Прописать deploy.env¶
В корне репо скопировать шаблон и заполнить:
Минимум для прошивочного деплоя:
DEPLOY_HOST=andy@awdc.nikolaev.world
SSH_KEY=/c/Users/<you>/.ssh/awdc_deploy
DEPLOY_FIRMWARE_PATH=/var/www/awdc.nikolaev.world/firmware/
DEPLOY_FIRMWARE_CHANNEL=dev
deploy.env в .gitignore — каждая инсталляция своя. Этот же файл
используется для деплоя max-webapp (см. секцию DEPLOY_WEBAPP_* в
devops.md §1.3).
Путь к ключу — в POSIX-стиле (/c/Users/...), не C:\Users\...:
git-bash'ный ssh так понимает Windows-пути.
8.4. Что делает скрипт¶
После успешного pio run PlatformIO дёргает tools/deploy_firmware_hook.py,
он находит git-bash (стандартные пути Git for Windows + fallback через
git --exec-path) и запускает tools/deploy-firmware.sh:
- Берёт последний билд из
build_artifacts/<ver>/AWDC_fw-*.{bin,manifest.json,sha256}. scpвсех трёх файлов наDEPLOY_HOST:DEPLOY_PATH/<sha>.{bin,manifest.json,sha256}.- По ssh на хосте перегенерирует
index.jsonсо списком всех билдов и указателемcurrent = <sha>(только что залитый).
Все ssh/scp идут через SSH_KEY с IdentitiesOnly=yes — никаких других
ключей из агента не пробуется. При падении trap печатает подсказку,
куда смотреть (auth, формат ключа, libcrypto-ошибка).
См. также: api/system.md#ota-обновление-прошивки, memory-map.md.