# Форматы данных и логов Документ описывает все форматы данных системы: байт GPIO, протокол FIFO-пайпа, бинарные и текстовые логи, пути хранения и правила очистки. **Аудитория:** разработчики и интеграторы. ## Содержание - [Байт GPIO](#байт-gpio) - [Протокол FIFO-пайпа](#протокол-fifo-пайпа) - [Бинарные логи (.bin)](#бинарные-логи-bin) - [Человекочитаемый GPIO-лог](#человекочитаемый-gpio-лог) - [События системы](#события-системы) - [Пути хранения](#пути-хранения) - [Ротация](#ротация) - [Retention (очистка)](#retention-очистка) ## Байт GPIO Единица данных системы — один байт, снятый с 8-битной GPIO-шины. Разбор — в [internal/logger/parser.go](../internal/logger/parser.go) (`ParseGPIO`): ``` бит: 7 6 | 5 4 3 2 1 0 └─┬─┘ └────┬─────┘ strength count (0–3) (0–63) ``` | Поле | Биты | Диапазон | Смысл | |---|---|---|---| | `count` (в API — `signal`) | 0–5 | 0–63 | Количество обнаружений | | `strength` (в API — `amplitude`) | 6–7 | 0–3 | Сила сигнала | Текстовые имена уровней силы (`GetStrengthName`): | Значение | Имя | |---|---| | 0 | Слабый | | 1 | Средний | | 2 | Сильный | | 3 | Максимальный | **Порог тревоги:** при `count > 10` (`IsHighCount`) генерируется событие `ОБНАРУЖЕНО__ОБЪЕКТОВ_СИЛА_` и строка «ВНИМАНИЕ» в human-логе (анти-спам: не чаще одного алерта на одинаковый `count` за `-alert-cooldown` секунд). Пример: байт `0x8C` = `1000 1100` → strength = 2 («Сильный»), count = 12 → сработает алерт. ## Протокол FIFO-пайпа Связь C-программы захвата с Go-сервером — именованный канал (по умолчанию `/tmp/gpio_pipe`, флаг `-pipe`). - Поток **сырых байт без кадрирования**: каждый байт — одно измерение в формате [байта GPIO](#байт-gpio). Никаких заголовков, разделителей и контрольных сумм. - Сервер читает блоками до 4096 байт ([internal/pipe/reader.go](../internal/pipe/reader.go)). - Метка времени присваивается **на стороне сервера** в момент чтения (`time.Now().UnixMicro()`). - При отсутствии/обрыве пайпа сервер переподключается сам: проверка существования каждые 2 с, повторное открытие через 1 с; смены состояния фиксируются событиями `PIPE_*` (см. [словарь событий](#события-системы)). ## Бинарные логи (.bin) Основной архив данных. Запись — [internal/logger/data_logger.go](../internal/logger/data_logger.go), чтение — `HandleLogData` в [cmd/server/main.go](../cmd/server/main.go). **Формат файла:** конкатенация записей по 9 байт, без заголовка и футера: ``` ┌────────────────────────────────┬───────────┐ │ timestamp: uint64 LE, 8 байт │ value: 1б │ └────────────────────────────────┴───────────┘ ``` - `timestamp` — микросекунды Unix (`UnixMicro`), little-endian; - `value` — сырой [байт GPIO](#байт-gpio). **Буферизация записи:** сэмплы копятся в буфере 64 КБ и сбрасываются на диск раз в 1 секунду либо при заполнении буфера; после каждого сброса вызывается `fsync` (durability при отключении питания). **Именование:** `gpio-YYYY-MM-DD-HH.bin`, время **локальное**, один файл на час (см. [Ротация](#ротация)). Пример: `gpio-2026-07-17-12.bin` — данные за 12:00–12:59 17 июля 2026. **Чтение на другой платформе:** каждая запись — `> 6) & 0x3, value & 0x3F ``` ## Человекочитаемый GPIO-лог Файл `gpio_human.log` (см. [пути](#пути-хранения)), пишет [internal/pipe/reader.go](../internal/pipe/reader.go) через `HumanLogger`. Обычная строка (пишется при изменении `count`/`strength` либо раз в `-human-log-interval` секунд): ``` [2026-07-17 12:34:56.789] Обнаружение: 12 объектов | Сила: Сильный (уровень 2) | Сырое: 0x8C (140) ``` Строка тревоги (при `count > 10`, с учётом анти-спама): ``` [2026-07-17 12:34:56.789] ВНИМАНИЕ: Обнаружено превышение! 12 объектов | Сила: Сильный (уровень 2) ``` ## События системы События пишутся [internal/logger/event_logger.go](../internal/logger/event_logger.go) **сразу в два файла**: 1. `events.log` — JSON-строки (одна на событие): ```json {"ts": 1789034096, "time": "2026-07-17 12:34:56", "event": "ПЛАНОВАЯ_РОТАЦИЯ"} ``` 2. `events_human.log` — текст (этот файл читает `/api/log/events`): ``` [2026-07-17 12:34:56.789] EVENT: ПЛАНОВАЯ_РОТАЦИЯ ``` **Словарь событий** (по вызовам `Event(...)` в коде): | Событие | Источник | Когда | |---|---|---| | `СЕРВЕР_ЗАПУЩЕН` | main.go | Старт сервера | | `HUMAN_ЛОГЕР_ГОТОВ` | main.go | Инициализация HumanLogger | | `DATA_ЛОГЕР_ГОТОВ` | main.go | Инициализация RotatingLogger | | `ОШИБКА_ИНИЦИАЛИЗАЦИИ_DATA_ЛОГЕРА` | main.go | Сбой инициализации (сервер завершается) | | `RETENTION_ГОТОВ` | main.go | Запуск очистки логов | | `ПЛАНОВАЯ_РОТАЦИЯ` | rotation.go | Открыт новый почасовой файл | | `ПЛАНОВАЯ_РОТАЦИЯ_НЕУДАЧНА` | rotation.go | Не удалось открыть новый файл | | `PIPE_НЕ_НАЙДЕН` | reader.go | FIFO-файл отсутствует | | `ОШИБКА_ОТКРЫТИЯ_PIPE` | reader.go | FIFO существует, но не открывается | | `PIPE_ПОДКЛЮЧЕН` | reader.go | Пайп открыт / связь восстановлена | | `PIPE_ОТКЛЮЧЕН` | reader.go | Ошибка чтения из пайпа | | `ТИШИНА_5МИН` | monitor.go | Нет данных дольше порога `-silence-5min` | | `ТИШИНА_10МИН` | monitor.go | Нет данных дольше порога `-silence-10min` | | `ОБНАРУЖЕНО__ОБЪЕКТОВ_СИЛА_` | reader.go | Алерт превышения (`count > 10`) | | `ОЧИСТКА_ЛОГОВ_УДАЛЕНО__ФАЙЛОВ__MB` | retention.go | Retention удалил файлы | | `ОШИБКА_ПОЛУЧЕНИЯ_ДИРЕКТОРИИ` | retention.go | Retention не смог получить каталог данных | События смены состояния пайпа пишутся **только при изменении** состояния (дедупликация в `PipeReader`), тишина — при каждой проверке watchdog (раз в `-watchdog-interval` минут), пока тишина длится. ## Пути хранения Все пути вычисляет [internal/logger/paths.go](../internal/logger/paths.go). Режим определяется по существованию каталога `/opt/gpio-monitoring`: есть — «пакетный» режим (deb-установка), нет — режим разработки (XDG). | Что | Пакетный режим | Режим разработки | |---|---|---| | Данные приложения | `/var/lib/gpio-monitoring` | `~/.local/share/gpio-monitoring` | | Каталог логов | `/var/log/gpio-monitoring` | `~/.local/share/gpio-monitoring/logs` | | Бинарные `.bin` | `/var/log/gpio-monitoring/data/` | `~/.local/share/gpio-monitoring/logs/data/` | | События (JSON) | `.../events.log` | `.../logs/events.log` | | События (текст) | `.../events_human.log` | `.../logs/events_human.log` | | GPIO human-лог | `.../gpio_human.log` | `.../logs/gpio_human.log` | ## Ротация [internal/logger/rotation.go](../internal/logger/rotation.go): `RotatingLogger` держит текущий `DataLogger` и раз в `-rotation-check-interval` минут (по умолчанию 1) сверяет текущий час с часом открытого файла. При смене часа старый файл закрывается (с финальным flush) и открывается новый `gpio-YYYY-MM-DD-HH.bin`; пишется событие `ПЛАНОВАЯ_РОТАЦИЯ`. ## Retention (очистка) [internal/logger/retention.go](../internal/logger/retention.go): фоновая FIFO-очистка каталога `data/`. Проверка раз в `-retention-interval` минут (по умолчанию 15), первая — через 1 минуту после старта. Алгоритм `Cleanup()`: 1. **По возрасту** (`-retention-hours`, 0 = отключено): файлы старше порога удаляются с самого старого края. Файл считается устаревшим, только если он старше порога **и по времени из имени, и по ModTime** — защита от скачка системных часов после NTP-синхронизации на устройстве без RTC. 2. **По размеру** (`-retention-mb`, 0 = отключено): пока суммарный размер `.bin`-файлов превышает лимит, удаляется самый старый файл. 3. В любом случае сохраняются минимум **2 самых свежих файла** (`minKeepFiles`) — защита от полного стирания каталога. Если имя файла не соответствует шаблону `gpio-YYYY-MM-DD-HH.bin`, вместо времени из имени используется ModTime. Поведение закреплено тестами [internal/logger/retention_test.go](../internal/logger/retention_test.go).