# Спецификация HTTP API Документ описывает HTTP API сервера `gpio-monitor-server`. Источник истины — [cmd/server/main.go](../cmd/server/main.go). **Аудитория:** разработчики и интеграторы. ## Содержание - [Общие сведения](#общие-сведения) - [GET /api/health](#get-apihealth) - [GET /api/latest](#get-apilatest) - [GET /api/history](#get-apihistory) - [GET /api/stream](#get-apistream) - [GET /api/cam](#get-apicam) - [GET /api/log/files](#get-apilogfiles) - [GET /api/log/data](#get-apilogdata) - [GET /api/log/events](#get-apilogevents) - [Известные особенности](#известные-особенности) ## Общие сведения - **Адрес:** порт задаётся флагом `-port` (по умолчанию `:8080`). - **Метод:** все эндпоинты — только `GET` (плюс `OPTIONS` для CORS preflight). - **Формат ответов:** JSON (`Content-Type: application/json`), кроме `/api/cam` (MJPEG-поток). - **CORS:** на всех эндпоинтах, кроме `/api/cam`, стоит обёртка `cors()`: `Access-Control-Allow-Origin: *`, `Access-Control-Allow-Methods: GET, OPTIONS`. Запрос `OPTIONS` возвращает `200` без тела. `/api/cam` выставляет `Access-Control-Allow-Origin: *` самостоятельно, но `OPTIONS` не обрабатывает. - **Ошибки:** возвращаются как `text/plain` с русскоязычным сообщением и кодом `400`/`404`/`500`/`503`. Неизвестный путь под `/api/` → `404 not found`. - **Статика:** все пути вне `/api/` обслуживаются встроенным веб-дашбордом (`go:embed`, см. [architecture.md](architecture.md)). Расшифровка полей `value`/`amplitude`/`signal` — в [data-formats.md](data-formats.md). ## GET /api/health Статус сервера, соединения с pipe и сводная статистика. Основной эндпоинт для опроса дашбордом. Пример ответа: ```json { "status": "На связи", "uptime_sec": 12345.67, "last_data_ms": 42, "pipe_alive": true, "has_data": true, "latest": 66, "stats": { "size": 10240, "filled": 10240, "last_write": "2026-07-17T12:34:56.789012345+03:00", "total_bytes": 1234567, "total_bits": 9876536, "bytes_per_sec": 100.5, "bits_per_sec": 804.0 }, "server_time": "17.07.2026 12:34", "sound_level": 27 } ``` | Поле | Тип | Описание | |---|---|---| | `status` | string | `"На связи"`, если последняя запись в буфер была менее 15 секунд назад, иначе `"Нет связи"` | | `uptime_sec` | float | Время работы сервера в секундах | | `last_data_ms` | int | Миллисекунд с момента последней записи данных | | `pipe_alive` | bool | Была ли запись за последние 3 секунды | | `has_data` | bool | Есть ли хоть один байт в кольцевом буфере | | `latest` | int (0–255) | Последний сырой байт GPIO; `0`, если данных ещё не было | | `stats.size` | int | Ёмкость кольцевого буфера (флаг `-buffer-size`) | | `stats.filled` | int | Сколько ячеек буфера заполнено | | `stats.last_write` | string (RFC 3339) | Время последней записи | | `stats.total_bytes` / `total_bits` | int | Всего принято байт/бит с момента запуска | | `stats.bytes_per_sec` / `bits_per_sec` | float | Текущая скорость приёма | | `server_time` | string | Серверное время в формате `ДД.ММ.ГГГГ ЧЧ:ММ` (два пробела между датой и временем) | | `sound_level` | int (0–100) | Уровень звука с микрофона; `0`, если аудиомонитор не запущен | ## GET /api/latest Последний байт и короткая история. Ответ при наличии данных: ```json { "latest": 66, "history": [64, 65, 66, 66, 65, 64, 66, 67, 66, 66] } ``` - `history` — до 10 последних байт, **от старых к новым** (последний элемент = `latest`). - Если буфер пуст, возвращается `200` с телом `{"error": "no data"}` (не HTTP-ошибка). ## GET /api/history История для графика. ```json { "bytes": [64, 65, 66, "...", 66] } ``` - `bytes` — массив целых (0–255), до **300** последних байт, от старых к новым. Если данных меньше — вернётся сколько есть. ## GET /api/stream Статус видеопотока камеры. ```json { "cam": "/api/cam", "available": true, "source": "http://192.168.1.10:1984/api/stream.mjpeg?src=cam_mjpeg" } ``` | Поле | Описание | |---|---| | `cam` | Путь к прокси-эндпоинту MJPEG (всегда `/api/cam`) | | `available` | Доступен ли поток: сервер делает запрос `http://localhost:1984/api/streams?src=cam_mjpeg` и проверяет код 200 | | `source` | Прямой URL потока go2rtc, построенный из хоста запроса (`r.Host`) и порта 1984 | ⚠️ Проверка `available` захардкожена на `localhost:1984` и **не учитывает флаг `-camera-url`** (main.go:460). См. [tech-debt.md](tech-debt.md). ## GET /api/cam Прокси MJPEG-потока камеры. Сервер запрашивает URL из флага `-camera-url` (по умолчанию `http://127.0.0.1:1984/api/stream.mjpeg?src=cam_mjpeg`) и ретранслирует поток клиенту с исходными заголовками, сбрасывая буфер после каждого чтения (chunk 32 КБ). Ошибки: | Код | Тело | Когда | |---|---|---| | `503` | `камера недоступна` | go2rtc не отвечает | | `500` | `поток не поддерживается` | ResponseWriter не поддерживает Flush | ## GET /api/log/files Список бинарных лог-файлов `gpio-*.bin` из каталога данных (см. [пути хранения](data-formats.md#пути-хранения)). ```json [ { "name": "gpio-2026-07-17-12.bin", "path": "/var/log/gpio-monitoring/data/gpio-2026-07-17-12.bin", "size": 34567, "mod_time": "2026-07-17T12:59:59+03:00", "time": "17.07.2026 12:00", "date": "2026-07-17", "hour": 12, "is_active": false } ] ``` - Массив отсортирован по `mod_time`, новые первыми. - `time`/`date`/`hour` вычисляются из имени файла; если имя не соответствует шаблону `gpio-YYYY-MM-DD-HH.bin` — из `mod_time`. - `is_active` в текущей реализации всегда `false` (зарезервировано). - ⚠️ Если файлов нет, возвращается `null`, а не `[]` (сериализация nil-слайса). ## GET /api/log/data Чтение сэмплов из конкретного `.bin`-файла с пагинацией. **Параметры запроса:** | Параметр | Обязательный | По умолчанию | Описание | |---|---|---|---| | `file` | да | — | Имя файла (например `gpio-2026-07-17-12.bin`); путь очищается через `filepath.Base` — path traversal невозможен | | `page` | нет | 1 | Номер страницы (значения < 1 игнорируются; больше максимума — прижимается к последней) | | `page_size` | нет | 100 | Размер страницы | Пример: `GET /api/log/data?file=gpio-2026-07-17-12.bin&page=1&page_size=50` ```json { "filename": "gpio-2026-07-17-12.bin", "total": 3600, "page": 1, "page_size": 50, "total_pages": 72, "has_previous": false, "has_next": true, "start_index": 3551, "end_index": 3600, "samples": [ { "ts": 1789034096123456, "time": "17.07.2026 12:34:56", "value": 66, "amplitude": 1, "signal": 2, "strength_name": "Средний" } ], "stats": { "total_points": 3600, "amplitude_over_0": 120, "signal_over_10": 15 }, "order": "newest_first" } ``` - **Порядок `newest_first`:** страница 1 содержит самые новые сэмплы; внутри страницы сэмплы также идут от новых к старым. - `ts` — метка времени в **микросекундах** Unix; `time` — она же в формате `ДД.ММ.ГГГГ ЧЧ:ММ:СС`. - `value` — сырой байт; `amplitude` — биты 6–7 (0–3); `signal` — биты 0–5 (0–63); `strength_name` — текстовое имя уровня (см. [data-formats.md](data-formats.md#байт-gpio)). - `stats` считается по **всему файлу**, а не по странице: `amplitude_over_0` — сэмплы с амплитудой > 0, `signal_over_10` — с сигналом > 10. - `start_index`/`end_index` — 1-based диапазон в хронологическом порядке файла. - Для пустого файла возвращается `total: 0` и пустой массив `samples`. Ошибки: `400 отсутствует параметр file`, `404 файл не найден`, `500` при ошибке чтения. ⚠️ Файл целиком читается в память до пагинации — учитывайте при больших `.bin`-файлах ([tech-debt.md](tech-debt.md)). ## GET /api/log/events Системные события из `events_human.log` с пагинацией. **Параметры:** `page` (по умолчанию 1), `page_size` (по умолчанию 100) — семантика как у `/api/log/data`. ```json { "events": [ { "time": "2026-07-17 12:00:00.001", "event": "ПЛАНОВАЯ_РОТАЦИЯ" }, { "time": "2026-07-17 11:59:12.512", "event": "ОБНАРУЖЕНО_12_ОБЪЕКТОВ_СИЛА_2" } ], "total": 254, "page": 1, "page_size": 100, "total_pages": 3, "has_previous": false, "has_next": true, "returned": 100, "start_index": 155, "end_index": 254, "order": "newest_first" } ``` - Парсятся только строки вида `[время] EVENT: имя`; прочие строки учитываются в `total`, но не попадают в `events` (поэтому `returned` может быть меньше размера страницы). - Словарь имён событий — в [data-formats.md](data-formats.md#события-системы). Ошибки: `500`, если файл событий недоступен. ## Известные особенности Зафиксированы также в [tech-debt.md](tech-debt.md): 1. `/api/stream` проверяет доступность камеры по захардкоженному `localhost:1984`, игнорируя `-camera-url`. 2. В `main.go` есть неиспользуемая функция `handleCamProxy` (двойник `handleCamProxyWithURL` с захардкоженным URL) — роутер её не вызывает. 3. `/api/latest` при пустом буфере возвращает `200` с `{"error": "no data"}`, а не код ошибки. 4. `/api/log/files` при отсутствии файлов возвращает `null` вместо пустого массива. 5. CORS открыт для всех источников (`*`) — рассчитано на доверенную локальную сеть.