Доработка документации

This commit is contained in:
Maxim
2026-07-17 15:57:05 +03:00
parent 5b403d7ee4
commit 9fa3d172a8
22 changed files with 1371 additions and 481 deletions

251
docs/api.md Normal file
View File

@@ -0,0 +1,251 @@
# Спецификация 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 (0255) | Последний сырой байт 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 (0100) | Уровень звука с микрофона; `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` — массив целых (0255), до **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` биты 67 (03); `signal` биты 05 (063); `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 открыт для всех источников (`*`) — рассчитано на доверенную локальную сеть.