Перейти к содержанию

API — Система

NTP, backup/restore конфигурации, OTA-обновление прошивки.

Источник: src/API/System/, src/API/ota.cpp.


NTP (синхронизация времени)

GET /api/system/time

Текущее состояние NTP-синхронизации.

Роль: USER (allowPublicRead).

curl http://awdc.local/api/system/time

Ответ 200 OK:

{
  "synced": true,
  "epoch": 1715603421,
  "iso": "2026-05-13T14:30:21",
  "ntp_server": "pool.ntp.org",
  "timezone_offset": 10800
}

Поле Описание
synced epoch > 2024-01-01 (грубая эвристика валидного NTP)
epoch Unix timestamp, 0 если не synced
iso Локальное ISO-8601 (с учётом timezone_offset)
ntp_server Текущий сервер из NVS
timezone_offset Смещение от UTC в секундах

POST /api/system/time/config

Роль: ADMIN.

Тело (оба поля опциональны):

{
  "ntp_server": "pool.ntp.org",
  "timezone_offset": 10800
}

Поле Тип Default Описание
ntp_server string текущее значение Хост NTP
timezone_offset int текущее значение Секунд от UTC (10800 = UTC+3)

Ответ 200 OK:

{"success": true, "message": "NTP config saved"}

Эффект: ntpInit() перечитывает NVS, инициирует пересинхронизацию.


Identity (device_name)

device_name — master alias устройства. Из него выводятся:

  • mDNS hostname<device_name>.local
  • AP SSID<device_name>_Setup (если пользователь не задал собственный SSID)

Хранится в NVS ui:dev_name. Default — awdc-<последние 4 hex от MAC>. См. nvs-reference.mdui namespace.

GET /api/system/identity

Возвращает текущее имя и все derived-значения.

Роль: USER.

curl -u admin:admin123 http://awdc-3f2c.local/api/system/identity

Ответ 200 OK:

{
  "device_name": "awdc-3f2c",
  "mdns_hostname": "awdc-3f2c.local",
  "ap_ssid": "awdc-3f2c_Setup",
  "ap_ssid_default": "awdc-3f2c_Setup",
  "ap_ssid_overridden": false
}
Поле Описание
device_name Текущее значение из NVS, или дефолт от MAC если пусто
mdns_hostname <device_name>.local
ap_ssid Текущий SSID точки доступа (derived или explicit)
ap_ssid_default Каким бы стал SSID при ssid_expl=false
ap_ssid_overridden true если пользователь задал кастомный SSID явно

POST /api/ui/dev_name

Сменить device_name.

Роль: ADMIN.

Валидация:

  • ^[a-z0-9-]{2,16}$ (lower-case, цифры, дефис)
  • Не начинается/кончается дефисом (RFC mDNS)
  • Не в reserved-list: awdc, admin, root, device, setup, localhost
  • Пустая строка = сброс на default awdc-<MAC4>
curl -u admin:admin123 -X POST \
     -H 'Content-Type: application/json' \
     -d '{"name":"lab-box1"}' \
     http://awdc-3f2c.local/api/ui/dev_name

Ответ 200 OK:

{
  "success": true,
  "device_name": "lab-box1",
  "mdns_hostname": "lab-box1.local",
  "ap_ssid": "lab-box1_Setup",
  "ap_ssid_changed": true
}

Эффекты:

  • Live-restart MDNS — lab-box1.local начнёт резолвиться через ~1с
  • Если ap-config:ssid_expl == false — AP SSID обновляется и передёргивается AP (клиенты отвалятся, нужно переподключиться к новому SSID)
  • ap_ssid_changed: true в ответе подсказывает UI что нужно показать предупреждение «переподключиться»

Ответ 400 (валидация):

{"success": false, "error": "Имя зарезервировано"}

Возможные сообщения: Длина 2..16 символов, Только латиница в нижнем регистре, цифры и дефис, Не может начинаться или кончаться дефисом, Имя зарезервировано.


Backup / restore

GET /api/system/backup

Скачать всю конфигурацию в зашифрованном JSON. Секретные поля (пароли, токены) шифруются AES-256-GCM ключом, производным от admin-пароля.

Роль: ADMIN. Использует admin-пароль из текущей Basic Auth как ключ.

curl -u admin:admin123 \
     -o awdc-backup.json \
     http://awdc.local/api/system/backup

Ответ 200 OK: JSON примерно такого вида (сильно сокращённо):

{
  "version": 1,
  "fw_version": "0.12.0",
  "data": {
    "ap-config":    { "ssid": "AWDC_Setup", "channel": 1, ... },
    "sta-config":   { "ssid": "MyHome", "useStaticIP": false, ... },
    "dosator":      { "prop_0": 22, "window_ms_0": 2000, ... },
    "hmi":          { "active_ch": 0, "hmi_bright": 7 },
    "int_telegram": { "chat_id": "...", "interval_s": 30, ... },
    "int_smtp":     { "host": "smtp.gmail.com", "port": 465, ... }
  },
  "_secrets": "BASE64(IV || TAG || AES-GCM(plaintext_secrets_json))"
}

_secrets — зашифрованный блок, содержащий пароли/токены из namespace'ов auth, ap-config, sta-config, int_*.

Полный перечень шифруемых ключей — см. kSecrets[] в src/nvs_backup.cpp.

Замечания:

  • Файл бэкапа можно открыть в любом текстовом редакторе и посмотреть plaintext-конфиг. Секреты — только с admin-паролем.
  • Если admin-пароль сменился — старые бэкапы расшифровать не получится.
  • См. подробности: admin/backup-restore.md.

POST /api/system/restore

Восстановить конфигурацию из JSON-бэкапа.

Роль: ADMIN. Admin-пароль из Basic Auth используется как ключ для расшифровки _secrets.

curl -u admin:admin123 -X POST \
     -H "Content-Type: application/json" \
     --data-binary @awdc-backup.json \
     http://awdc.local/api/system/restore

Лимит тела: 8192 байт (8 КБ). Если backup больше — нужно резать.

Ответ 200 OK:

{"success":true,"message":"Restored. Reboot to apply."}

После успеха обязательно перезагрузить устройство, чтобы менеджеры (WiFi, интеграции) перечитали свои настройки.

Ошибки:

Код Тело
400 {"success":false,"error":"Body required"}
400 {"success":false,"error":"Invalid JSON"}
400 {"success":false,"error":"<reason from nvsRestore>"}
401/403 стандарт
413 {"success":false,"error":"Body too large"} (>8KB)

Возможные причины восстановления-fail: - Несовпадение версии бэкапа (version != 1). - Невалидная подпись _secrets — admin-пароль не тот.


Factory reset

Полный сброс к заводским настройкам через UI/API. Стирает user-config (STA/интеграции/dosator-config/UI), сохраняет factory creds и AP-настройки (чтобы устройство сразу было доступно), не трогает EEPROM-наработку (отдельный I²C-чип).

POST /api/system/factory-reset

Роль: ADMIN.

Двойной password gate: UI сначала вызывает POST /api/auth/verify с admin-паролем для re-confirm, затем этот endpoint с тем же паролем в теле. Сделано чтобы случайный/проксированный POST не убил production (защита от auto-fill или одного скомпрометированного Basic Auth header'а).

# 1. Re-confirm пароля (UI делает автоматически)
curl -u admin:admin123 -X POST \
     -H 'Content-Type: application/json' \
     -d '{"password":"admin123"}' \
     http://awdc-3f2c.local/api/auth/verify

# 2. Сам factory reset
curl -u admin:admin123 -X POST \
     -H 'Content-Type: application/json' \
     -d '{"password":"admin123"}' \
     http://awdc-3f2c.local/api/system/factory-reset

Ответ 200 OK:

{
  "success": true,
  "reboot": true,
  "wiped_namespaces": [
    "sta-config", "int_smtp", "int_imap", "int_telegram", "int_maxbot",
    "ntp-config", "ota", "ota-test", "net-mgr", "wifi", "ui",
    "dosator", "selftest_v"
  ]
}

Через ~500мс устройство перезагружается. После reboot:

  • admin/admin123, duty/duty123 (factory creds)
  • AP активна, SSID сохранён (ap-config не трогается)
  • device_name сбросился → mDNS вернулся к awdc-<MAC4>.local
  • STA не настроен — устройство в AP-only setup-режиме
  • Все интеграции выключены (enabled=false)

НЕ стирается:

  • auth — factory creds сохраняются (иначе нет доступа)
  • ap-config — AP должен остаться доступен сразу после reset
  • eeprom_* — наработка дозаторов (отдельная микросхема I²C M24C16)
  • eeprom_mig — версия миграции EEPROM

Полный список и обоснование — nvs-reference.md → раздел Factory reset.

Ошибки:

Код Тело
400 {"success":false,"error":"Body required"}
400 {"success":false,"error":"Invalid JSON"}
401 Basic Auth не прошла
403 {"success":false,"error":"Invalid password"} (re-confirm)

Howto-гайд: admin/factory-reset.md.


OTA — обновление прошивки

Process: 1. POST /api/ota/prepare — отправить манифест (метаданные прошивки). 2. POST /api/ota/upload — стрим тела .bin, чанками. 3. POST /api/ota/apply — применить → перезагрузка → новая прошивка загружается из второго OTA-слота. Если boot-validation падает — автоматический rollback.

Все эндпоинты — ADMIN.

Подробности процесса и rollback-политики — admin/ota-update.md, а также doc/dev/adr/adr-003-high-risk-review.md.


GET /api/ota/status

Текущее состояние OTA-сессии.

curl -u admin:admin123 http://awdc.local/api/ota/status

Ответ 200 OK (idle):

{
  "state": "IDLE",
  "uploaded_bytes": 0,
  "total_bytes": 0,
  "pending_version": "",
  "last_error": ""
}

Ответ 200 OK (uploading):

{
  "state": "UPLOADING",
  "uploaded_bytes": 524288,
  "total_bytes": 1843200,
  "pending_version": "0.13.0",
  "expected_sha256": "abcd1234...",
  "computed_sha256": "abcd1234..."
}

Полное описание полей и значений state — см. OtaManager::getStatusJson() в src/ota/OtaManager.cpp.


POST /api/ota/prepare

Передать манифест прошивки (fw_version, expected_sha256, total_bytes, build-metadata). Менеджер валидирует совместимость (чип-модель, EEPROM-схема и т.д.) и резервирует второй OTA-слот.

Лимит манифеста: 2048 байт.

Тело (JSON, обязательные поля проверяются OtaManager):

{
  "fw_version": "0.13.0",
  "build_date": "2026-05-14T10:00:00Z",
  "total_bytes": 1843200,
  "sha256": "abcd1234...",
  "product_id": "AWDC",
  "board_id": "v1",
  "hw_rev": 1,
  "expected_eeprom_schema": 2
}

Ответ 200 OK: {"success": true}

Ошибки:

Код Тело
400 {"error":"Body required"}
400 {"error":"Invalid manifest JSON"}
400 {"success":false,"error":"<message from OtaManager::prepare>"}
413 {"error":"Payload too large"} (>2KB)

Причины fail prepare: несовместимая HW_REV, мигратор EEPROM не готов к новой схеме, прошивка уже в процессе и т.д.


POST /api/ota/upload

Стримить бинарь прошивки. Тело — raw bytes (НЕ JSON). Может приходить чанками — менеджер собирает.

Заголовки: - Authorization: Basic <admin> — обязателен (проверяется на первом чанке). - Content-Length — должен соответствовать total_bytes из манифеста.

curl -u admin:admin123 -X POST \
     -H "Content-Type: application/octet-stream" \
     --data-binary @firmware.bin \
     http://awdc.local/api/ota/upload

Ответ 200 OK (по завершению): {"success": true}

Ошибки:

Код Когда
401 Нет/неверная авторизация (на первом чанке)
500 Memory allocation failed, или err из uploadChunk()

Прогресс можно отслеживать через GET /api/ota/status параллельно.


POST /api/ota/apply

Применить загруженную прошивку: переключить OTA-селектор и esp_restart(). Устройство перезагрузится через 500 мс.

curl -u admin:admin123 -X POST \
     http://awdc.local/api/ota/apply

Ответ 200 OK: {"success":true,"reboot":true}

После этого соединение терминируется (устройство уходит в reboot). По возврату в STA — новая прошивка работает в одном из app0/app1 (см. /api/diag/systemota_partition).

Ошибки: 400 если нет успешно загруженного образа в UPLOADING_DONE.


POST /api/ota/cancel

Отменить текущую сессию: освободить slot, обнулить состояние.

curl -u admin:admin123 -X POST \
     http://awdc.local/api/ota/cancel

Ответ 200 OK: {"success": true}


POST /api/ota/rollback

Manual rollback на предыдущую (last-good) партицию. Авто-rollback срабатывает только при MAX_BOOT_ATTEMPTS фейлов validation подряд — этот endpoint даёт явный путь когда новая прошивка стартует и проходит auto-validation, но оператор всё равно хочет вернуться.

Доступность: проверяется в /api/ota/status через can_rollback: true + last_good_version: "<X.Y.Z>". Условия: есть валидная last-good партиция, она отличается от running, и сейчас НЕТ активной OTA-сессии (если есть — отмените через /api/ota/cancel сначала).

curl -u admin:admin123 -X POST http://awdc.local/api/ota/rollback

Ответ 200 OK:

{
  "success": true,
  "reboot": true,
  "target_version": "0.14.0"
}

Через ~500мс устройство перезагружается на last-good партицию. После reboot текущая версия — та, что в target_version.

Текущая прошивка остаётся в OTA-слоте — обратный rollback (если будет нужен) переключит обратно.

Ошибки:

Код Тело
400 {"success":false,"error":"Rollback недоступен: нет валидной..."}
400 {"success":false,"error":"OTA уже идёт — отмените..."}

GET /api/ota/result

Результат последней OTA-сессии (для UI после reboot — показать «обновление успешно/откатилось»).

curl -u admin:admin123 http://awdc.local/api/ota/result

Ответ 200 OK (успешный апдейт):

{
  "success": true,
  "applied_version": "0.13.0",
  "previous_version": "0.12.0",
  "boot_attempts": 1,
  "ota_partition": "app1"
}

Ответ (был rollback):

{
  "success": false,
  "error": "Boot validation failed",
  "rolled_back_to": "0.12.0",
  "boot_attempts": 3,
  "ota_partition": "app0"
}

Полная схема — см. OtaManager::getResultJson().


OTA pull-mode (сервер обновлений)

Помимо классического upload-flow выше, устройство умеет само тянуть прошивку с https-сервера. Используется в Web UI (карточка OTA → секция «Прошивка с сервера обновлений») и в OTA через MaxBot/Telegram.

URL'ы внутри собираются как <host>/firmware/<channel>/<sha>.bin и /firmware/<channel>/index.json. Канал хранится в NVS-ключе ota:channel (см. nvs-reference.md).

GET /api/ota/host

Текущая конфигурация puller'а.

curl -u admin:admin123 http://awdc.local/api/ota/host

Ответ 200 OK:

{
  "host_url":   "https://awdc.nikolaev.world/",
  "channel":    "stable",
  "pulling":    false,
  "pull_sha":   "",
  "pull_error": ""
}

POST /api/ota/host

Установить URL хоста.

curl -u admin:admin123 -X POST \
     -H 'Content-Type: application/json' \
     -d '{"url":"https://awdc.nikolaev.world/"}' \
     http://awdc.local/api/ota/host

Ответ 200 OK: {"success": true}

POST /api/ota/channel

Переключить канал (stable / dev). Устройство НЕ перешивается — только меняется URL, по которому будут искаться будущие обновления.

curl -u admin:admin123 -X POST \
     -H 'Content-Type: application/json' \
     -d '{"channel":"dev"}' \
     http://awdc.local/api/ota/channel

Ответ 200 OK: { "success": true, "channel": "dev" }

Ошибки:

Код Тело
400 {"error":"Body required"}
400 {"error":"Invalid JSON"}
400 {"success":false,"error":"Unknown channel ..."}

POST /api/ota/check

Спросить сервер «есть ли что-то новее?». Делает синхронный TLS-GET <host>/firmware/<channel>/index.json и сравнивает с текущим FW_VERSION + BUILD_GIT_SHA.

curl -u admin:admin123 -X POST http://awdc.local/api/ota/check

Ответ 200 OK (нашёл новее):

{
  "ok": true,
  "configured": true,
  "host": "https://awdc.nikolaev.world/",
  "channel": "stable",
  "running_version": "0.14.0",
  "current_sha": "92d3b57",
  "newer_available": true,
  "latest": {
    "sha": "92d3b57",
    "fw_version": "0.14.1",
    "channel": "stable",
    "bin_size": 1331696,
    "built_at": "2026-05-28T16:30:00Z"
  }
}

newer_available — true если sha с хоста отличается от того, что скомпилирован в прошивку, ИЛИ версия выше.

POST /api/ota/pull

Запустить async pull выбранного билда. Возвращается сразу, прогресс смотреть через GET /api/ota/status (как при ручном upload).

curl -u admin:admin123 -X POST \
     -H 'Content-Type: application/json' \
     -d '{"sha":"92d3b57"}' \
     http://awdc.local/api/ota/pull

Ответ 202 Accepted: { "success": true, "started": true }

После завершения pull'а state переходит в UPLOAD_DONE — дальше обычный POST /api/ota/apply для перезагрузки на новую прошивку.


Перезагрузка устройства

Нет отдельного /api/system/restart. Перезагрузка происходит:

  • через POST /api/ota/apply — после успешного OTA;
  • через UART/USB-консоль (esp_restart() руками);
  • через power-cycle.

Если нужна программная перезагрузка без OTA — это потенциальное расширение API. Сейчас отсутствует by design: исключает случайные перезагрузки в production.


Настройки web-интерфейса

GET /api/ui/config

Текущие настройки web-UI. Сейчас одна — таймаут автоматического разлогина по неактивности.

Роль: USER+ (нужно всем авторизованным клиентам).

curl -u duty:duty123 http://awdc.local/api/ui/config

Ответ 200 OK:

{ "idle_timeout_min": 10 }

  • idle_timeout_min (int) — таймаут неактивности в минутах; 0 — автоматический разлогин отключён.

POST /api/ui/config

Изменить настройки web-UI.

Роль: ADMIN.

curl -u admin:admin123 -X POST \
     -H 'Content-Type: application/json' -d '{"idle_timeout_min": 15}' \
     http://awdc.local/api/ui/config
  • idle_timeout_min (int, обязателен) — 0..120 минут; 0 отключает.

Ответ 200 OK: { "success": true, "idle_timeout_min": 15 }

Замечания

  • Хранится в NVS (namespace ui, ключ idle_min), переживает перезагрузку.
  • Idle-logout — клиентская логика: web-UI отслеживает активность и при простое дольше порога очищает credentials и показывает окно входа. Это UX, а не серверная граница — credentials остаются валидны на сервере.