12 KiB
Спецификация HTTP API
Документ описывает HTTP API сервера gpio-monitor-server. Источник истины — cmd/server/main.go.
Аудитория: разработчики и интеграторы.
Содержание
- Общие сведения
- GET /api/health
- GET /api/latest
- GET /api/history
- GET /api/stream
- GET /api/cam
- GET /api/log/files
- GET /api/log/data
- GET /api/log/events
- Известные особенности
Общие сведения
- Адрес: порт задаётся флагом
-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 (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
Последний байт и короткая история.
Ответ при наличии данных:
{
"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— массив целых (0–255), до 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— биты 6–7 (0–3);signal— биты 0–5 (0–63);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:
/api/streamпроверяет доступность камеры по захардкоженномуlocalhost:1984, игнорируя-camera-url.- В
main.goесть неиспользуемая функцияhandleCamProxy(двойникhandleCamProxyWithURLс захардкоженным URL) — роутер её не вызывает. /api/latestпри пустом буфере возвращает200с{"error": "no data"}, а не код ошибки./api/log/filesпри отсутствии файлов возвращаетnullвместо пустого массива.- CORS открыт для всех источников (
*) — рассчитано на доверенную локальную сеть.