Files
Button/docs/rest-api.md
T
GrigoandClaude Opus 5.5 ec84133417 rest api: accept Basic Auth with the panel password for writes
Any login, password = web/settings password (or master password). 401
carries WWW-Authenticate so browsers/Postman prompt. X-Api-Key still
works for existing integrations. Docs warn that over plain HTTP the
password travels in every request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 10:58:05 +00:00

321 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REST API ButtonTask `/api/v1`
Базовый адрес — тот же, что у веб-конфигуратора (по умолчанию порт 8080). Отдельного процесса нет.
Чтение открыто. Запись требует авторизацию, подходит любой из двух способов:
- **Basic Auth с паролем веб-панели** (основной способ). Логин любой, например `admin`; пароль тот же, что для входа в конфигуратор и настройки на экране (подходит и мастер-пароль). Сменили пароль в панели — меняется и пароль для API.
```sh
curl -u admin:ПАРОЛЬ -X POST http://<ip>:8080/api/v1/buttons/<id>/press
```
- **Заголовок `X-Api-Key`** (оставлен для уже настроенных интеграций). Ключ лежит в `settings.apiKey` и показывается на вкладке «Безопасность» конфигуратора.
Устройство работает по HTTP, поэтому пароль в Basic Auth передаётся по сети открытым текстом в каждом запросе (так же, как при входе в веб-панель). Кто перехватит трафик, получит пароль администратора, а с ним и OTA, то есть root на устройстве. Ключ API при утечке даёт меньше: только методы записи API. В доверенной сети это приемлемо; если интеграция ходит через чужую сеть, лучше ключ.
Без авторизации или с неверными данными — HTTP 401 с заголовком `WWW-Authenticate: Basic`:
```json
{ "ok": false, "error": "unauthorized" }
```
Тело ошибок всегда `{"ok": false, "error": "<код>"}`.
Калибровка в файле хранится как `min` / `max`. В API это `empty_mm` (0%) и `full_mm` (100%). Уровень: `(raw_mm - empty_mm) / (full_mm - empty_mm) * 100`, зажим 0…100, один знак после запятой. Нет границ или они равны — `level_pct` равен `null`.
Байт питания — десятые вольта. `voltage_mv = байт * 100`. Байт `255` — нет данных, тогда `voltage_mv` и `charge_pct` равны `null`. `charge_pct` — оценка остатка двух дешёвых AA по кривой разряда.
`stale_timeout_s` только для чтения: `report_interval_min * 60 * 3`. Для 30 минут это 5400.
## GET /api/v1/health
Авторизация не требуется.
Сводка коллектора, его аптайм, метрики хоста и счётчики датчиков. `ok` и `status: "healthy"` — файл статуса коллектора свежий (не старше 15 секунд). Иначе `status: "degraded"`, HTTP всё равно 200.
```json
{
"ok": true,
"status": "healthy",
"uptime_s": 86400.1,
"version": "1.2.3",
"ts": 1730000000.0,
"collector": {
"available": true,
"sensors_live": 2,
"sensors_stale": 1,
"sensors_total": 3
},
"system": {
"cpu_percent": 12.5,
"memory_used_percent": 41.0,
"load_1m": 0.42
}
}
```
`uptime_s` — секунды с старта процесса коллектора. Если метки старта нет, поле `null`.
## GET /api/v1/sensors
Авторизация не требуется.
Интервал отчёта, таймаут устаревания и карточки известных датчиков. Пустых слотов нет.
```json
{
"report_interval_min": 30,
"stale_timeout_s": 5400,
"sensors": [
{
"mac": "AA:BB:CC:DD:EE:FF",
"name": "Резервуар_1",
"raw_mm": 420,
"level_pct": 63.5,
"voltage_mv": 2800,
"charge_pct": 72,
"age_s": 42,
"state": "live",
"rssi": -67,
"last_seen": 1730000000.0
}
]
}
```
`raw_mm` равен `null`, если датчик не видит цель. `state` — `live` или `stale`.
## GET /api/v1/sensors/{mac}
Авторизация не требуется.
Карточка одного датчика, тот же объект, что элемент `sensors` выше. Неизвестный адрес или кривой MAC — HTTP 404 `sensor_not_found`.
## GET /api/v1/sensors/{mac}/history
Авторизация не требуется.
Параметры:
- `limit` — от 1 до 1000, по умолчанию 100. Иначе HTTP 400 `invalid_limit`.
- `since` — необязательный Unix timestamp, остаются точки с `ts >= since`. Нечисло — HTTP 400 `invalid_since`.
Кольцо на диске `run/sensors-history.json`, не больше 1000 точек на датчик. Ответ от новых к старым. Проценты считаются по текущей калибровке, а не по той, что была в момент замера.
Неизвестный датчик — HTTP 404 `sensor_not_found`. Известный датчик без точек — пустой `entries`.
```json
{
"mac": "AA:BB:CC:DD:EE:FF",
"entries": [
{
"ts": 1730000000.0,
"raw_mm": 420,
"level_pct": 63.5,
"voltage_mv": 2800,
"charge_pct": 72,
"rssi": -67
}
]
}
```
## PUT /api/v1/sensors/{mac}/name
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
```json
{ "name": "Резервуар_1" }
```
Пустая строка сбрасывает имя. `name` не строка — HTTP 400 `invalid_name`. Кривой MAC — HTTP 400 `invalid_mac`.
```json
{ "ok": true, "mac": "AA:BB:CC:DD:EE:FF", "name": "Резервуар_1" }
```
## GET /api/v1/settings
Авторизация не требуется.
```json
{
"report_interval_min": 30,
"stale_timeout_s": 5400
}
```
## PUT /api/v1/settings
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
Принимается только `report_interval_min`, целое от 1 до 65535. Иначе HTTP 400 `invalid_interval`. `stale_timeout_s` записать нельзя.
Ответ — та же пара полей, что у GET.
## GET /api/v1/sensors/{mac}/calibration
Авторизация не требуется.
```json
{ "empty_mm": 1200, "full_mm": 200 }
```
Границ ещё нет — оба поля `null`. Кривой MAC — HTTP 404 `sensor_not_found`.
## PUT /api/v1/sensors/{mac}/calibration
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
```json
{ "empty_mm": 1200, "full_mm": 200 }
```
Оба значения — целые миллиметры от 0 до 65535 и не равны друг другу. Иначе HTTP 400 `invalid_calibration`. Кривой MAC — HTTP 400 `invalid_mac`.
`empty_mm` — показание 0%, `full_mm` — показание 100%. Ответ повторяет записанные границы.
## GET /api/v1/buttons
Авторизация не требуется.
Только идентификатор и подпись. URL и остальные поля кнопки не отдаются.
```json
[
{ "id": "example-1", "label": "Пример" }
]
```
## GET /api/v1/buttons/{id}
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
Полная карточка кнопки: подпись, иконка, действие, тексты ответа, удержание и цвет. Неизвестный id — HTTP 404 `button_not_found`.
```json
{
"id": "example-1",
"label": "Пример",
"iconPath": "cleaner1.png",
"action": {
"type": "http_get",
"url": "http://localhost/btn?bid=01",
"headers": {},
"body": ""
},
"feedback": {
"successText": "Отправлено",
"errorText": "Ошибка",
"pendingText": "...",
"fadeMs": 5000
},
"trigger": { "mode": "hold", "holdMs": 800 },
"color": "#7b007b"
}
```
## POST /api/v1/buttons
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
Создаёт кнопку. Идентификатор назначается сам. Поля те же, что при правке, плюс плоские имена действия: `actionType` (`http_get` или `http_post`), `url`, `headers`, `body`, `timeoutMs` (1000…600000), `successText`, `errorText`, `pendingText`, `fadeMs`, `triggerMode` (`click` или `hold`), `holdMs`, `color`, `iconPath`, `latchReset`, `responseCheck`. Некорректное тело — HTTP 400 `invalid_button` и поле `message` с причиной. Ответ HTTP 201 — созданная карточка.
```json
{
"label": "Пример",
"actionType": "http_get",
"url": "http://localhost/btn?bid=01",
"triggerMode": "hold",
"holdMs": 800,
"color": "#7b007b"
}
```
## PUT /api/v1/buttons/{id}
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
Меняет только переданные поля. Имена полей те же, что у создания. Неизвестный id — HTTP 404 `button_not_found`. Некорректное поле — HTTP 400 `invalid_button`. Ответ — карточка после записи. Экран подхватывает конфиг сам.
## DELETE /api/v1/buttons/{id}
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
Удаляет кнопку. Неизвестный id — HTTP 404 `button_not_found`.
```json
{ "ok": true }
```
## POST /api/v1/buttons/reorder
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
Порядок кнопок на экране. В `order` — идентификаторы спереди назад. Пропущенные остаются в конце в прежнем порядке. `order` не массив строк — HTTP 400 `invalid_order`.
```json
{ "order": ["example-1", "cbd6791cdcb74a58861554b30239c65e"] }
```
Ответ:
```json
{ "ok": true, "order": ["example-1", "cbd6791cdcb74a58861554b30239c65e"] }
```
## GET /api/v1/background
Авторизация не требуется.
```json
{
"type": "gradient",
"color1": "#00007b",
"color2": "#7b007b",
"animated": true,
"imagePath": ""
}
```
`type`: `gradient`, `solid` или `image`.
## PUT /api/v1/background
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
Меняет только переданные поля. Цвет — `#rrggbb`. `animated` — логическое. Иначе HTTP 400 `invalid_background`. Ответ — фон после записи.
```json
{
"type": "gradient",
"color1": "#00007b",
"color2": "#7b007b",
"animated": true
}
```
## POST /api/v1/buttons/{id}/press
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
То же действие, что нажатие на экране, включая защёлку. Тело не нужно.
Ответ HTTP 202 значит, что команда принята экраном, а не что внешний HTTP-запрос кнопки уже завершился:
```json
{ "ok": true, "id": "example-1" }
```
Неизвестный id — HTTP 404 `button_not_found`. Повторная команда, пока экран не забрал предыдущую, заменяет её.
## POST /api/v1/system/reboot
Нужна авторизация (Basic или `X-Api-Key`, см. начало).
Перезагрузка устройства тем же путём, что кнопка в конфигураторе. Ответ уходит до того, как машина погаснет.
```json
{ "ok": true }
```