Files
go-service/docs/api.md
2026-07-17 15:57:05 +03:00

12 KiB
Raw Blame History

Спецификация HTTP API

Документ описывает HTTP API сервера gpio-monitor-server. Источник истины — cmd/server/main.go.

Аудитория: разработчики и интеграторы.

Содержание

Общие сведения

  • Адрес: порт задаётся флагом -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).

Расшифровка полей value/amplitude/signal — в data-formats.md.

GET /api/health

Статус сервера, соединения с pipe и сводная статистика. Основной эндпоинт для опроса дашбордом.

Пример ответа:

{
  "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

Последний байт и короткая история.

Ответ при наличии данных:

{
  "latest": 66,
  "history": [64, 65, 66, 66, 65, 64, 66, 67, 66, 66]
}
  • history — до 10 последних байт, от старых к новым (последний элемент = latest).
  • Если буфер пуст, возвращается 200 с телом {"error": "no data"} (не HTTP-ошибка).

GET /api/history

История для графика.

{
  "bytes": [64, 65, 66, "...", 66]
}
  • bytes — массив целых (0255), до 300 последних байт, от старых к новым. Если данных меньше — вернётся сколько есть.

GET /api/stream

Статус видеопотока камеры.

{
  "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.

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 из каталога данных (см. пути хранения).

[
  {
    "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

{
  "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).
  • 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).

GET /api/log/events

Системные события из events_human.log с пагинацией.

Параметры: page (по умолчанию 1), page_size (по умолчанию 100) — семантика как у /api/log/data.

{
  "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.

Ошибки: 500, если файл событий недоступен.

Известные особенности

Зафиксированы также в 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 открыт для всех источников (*) — рассчитано на доверенную локальную сеть.