API — Система¶
NTP, backup/restore конфигурации, OTA-обновление прошивки.
Источник: src/API/System/, src/API/ota.cpp.
NTP (синхронизация времени)¶
GET /api/system/time¶
Текущее состояние NTP-синхронизации.
Роль: USER (allowPublicRead).
Ответ 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.
Тело (оба поля опциональны):
| Поле | Тип | Default | Описание |
|---|---|---|---|
ntp_server |
string | текущее значение | Хост NTP |
timezone_offset |
int | текущее значение | Секунд от UTC (10800 = UTC+3) |
Ответ 200 OK:
Эффект: 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.md → ui namespace.
GET /api/system/identity¶
Возвращает текущее имя и все derived-значения.
Роль: USER.
Ответ 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 (валидация):
Возможные сообщения: Длина 2..16 символов,
Только латиница в нижнем регистре, цифры и дефис,
Не может начинаться или кончаться дефисом, Имя зарезервировано.
Backup / restore¶
GET /api/system/backup¶
Скачать всю конфигурацию в зашифрованном JSON. Секретные поля (пароли, токены) шифруются AES-256-GCM ключом, производным от admin-пароля.
Роль: ADMIN. Использует admin-пароль из текущей Basic Auth как ключ.
Ответ 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:
После успеха обязательно перезагрузить устройство, чтобы менеджеры (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 должен остаться доступен сразу после reseteeprom_*— наработка дозаторов (отдельная микросхема 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-сессии.
Ответ 200 OK (idle):
Ответ 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 мс.
Ответ 200 OK: {"success":true,"reboot":true}
После этого соединение терминируется (устройство уходит в reboot).
По возврату в STA — новая прошивка работает в одном из app0/app1
(см. /api/diag/system → ota_partition).
Ошибки: 400 если нет успешно загруженного образа в UPLOADING_DONE.
POST /api/ota/cancel¶
Отменить текущую сессию: освободить slot, обнулить состояние.
Ответ 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 сначала).
Ответ 200 OK:
Через ~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 — показать «обновление успешно/откатилось»).
Ответ 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'а.
Ответ 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.
Ответ 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+ (нужно всем авторизованным клиентам).
Ответ 200 OK:
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 остаются валидны на сервере.