# REST API ButtonTask `/api/v1` Базовый адрес — тот же, что у веб-конфигуратора (по умолчанию порт 8080). Отдельного процесса нет. Чтение открыто. Запись требует заголовок `X-Api-Key`. Ключ лежит в `settings.apiKey`, создаётся сам при первом запуске и показывается на вкладке «Безопасность» конфигуратора. Запроса «отдать ключ» в этом API нет: его увидел бы любой в сети. Неверный или пустой ключ — HTTP 401: ```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 Заголовок `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 Заголовок `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 Заголовок `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} Заголовок `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 Заголовок `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} Заголовок `X-Api-Key` обязателен. Меняет только переданные поля. Имена полей те же, что у создания. Неизвестный id — HTTP 404 `button_not_found`. Некорректное поле — HTTP 400 `invalid_button`. Ответ — карточка после записи. Экран подхватывает конфиг сам. ## DELETE /api/v1/buttons/{id} Заголовок `X-Api-Key` обязателен. Удаляет кнопку. Неизвестный id — HTTP 404 `button_not_found`. ```json { "ok": true } ``` ## POST /api/v1/buttons/reorder Заголовок `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 Заголовок `X-Api-Key` обязателен. Меняет только переданные поля. Цвет — `#rrggbb`. `animated` — логическое. Иначе HTTP 400 `invalid_background`. Ответ — фон после записи. ```json { "type": "gradient", "color1": "#00007b", "color2": "#7b007b", "animated": true } ``` ## POST /api/v1/buttons/{id}/press Заголовок `X-Api-Key` обязателен. То же действие, что нажатие на экране, включая защёлку. Тело не нужно. Ответ HTTP 202 значит, что команда принята экраном, а не что внешний HTTP-запрос кнопки уже завершился: ```json { "ok": true, "id": "example-1" } ``` Неизвестный id — HTTP 404 `button_not_found`. Повторная команда, пока экран не забрал предыдущую, заменяет её. ## POST /api/v1/system/reboot Заголовок `X-Api-Key` обязателен. Перезагрузка устройства тем же путём, что кнопка в конфигураторе. Ответ уходит до того, как машина погаснет. ```json { "ok": true } ```