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

11 KiB
Raw Permalink Blame History

Форматы данных и логов

Документ описывает все форматы данных системы: байт GPIO, протокол FIFO-пайпа, бинарные и текстовые логи, пути хранения и правила очистки.

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

Содержание

Байт GPIO

Единица данных системы — один байт, снятый с 8-битной GPIO-шины. Разбор — в internal/logger/parser.go (ParseGPIO):

бит:   7 6 | 5 4 3 2 1 0
       └─┬─┘ └────┬─────┘
    strength    count
     (03)      (063)
Поле Биты Диапазон Смысл
count (в API — signal) 05 063 Количество обнаружений
strength (в API — amplitude) 67 03 Сила сигнала

Текстовые имена уровней силы (GetStrengthName):

Значение Имя
0 Слабый
1 Средний
2 Сильный
3 Максимальный

Порог тревоги: при count > 10 (IsHighCount) генерируется событие ОБНАРУЖЕНО_<count>_ОБЪЕКТОВ_СИЛА_<strength> и строка «ВНИМАНИЕ» в human-логе (анти-спам: не чаще одного алерта на одинаковый count за -alert-cooldown секунд).

Пример: байт 0x8C = 1000 1100 → strength = 2 («Сильный»), count = 12 → сработает алерт.

Протокол FIFO-пайпа

Связь C-программы захвата с Go-сервером — именованный канал (по умолчанию /tmp/gpio_pipe, флаг -pipe).

  • Поток сырых байт без кадрирования: каждый байт — одно измерение в формате байта GPIO. Никаких заголовков, разделителей и контрольных сумм.
  • Сервер читает блоками до 4096 байт (internal/pipe/reader.go).
  • Метка времени присваивается на стороне сервера в момент чтения (time.Now().UnixMicro()).
  • При отсутствии/обрыве пайпа сервер переподключается сам: проверка существования каждые 2 с, повторное открытие через 1 с; смены состояния фиксируются событиями PIPE_* (см. словарь событий).

Бинарные логи (.bin)

Основной архив данных. Запись — internal/logger/data_logger.go, чтение — HandleLogData в cmd/server/main.go.

Формат файла: конкатенация записей по 9 байт, без заголовка и футера:

┌────────────────────────────────┬───────────┐
│ timestamp: uint64 LE, 8 байт   │ value: 1б │
└────────────────────────────────┴───────────┘
  • timestamp — микросекунды Unix (UnixMicro), little-endian;
  • value — сырой байт GPIO.

Буферизация записи: сэмплы копятся в буфере 64 КБ и сбрасываются на диск раз в 1 секунду либо при заполнении буфера; после каждого сброса вызывается fsync (durability при отключении питания).

Именование: gpio-YYYY-MM-DD-HH.bin, время локальное, один файл на час (см. Ротация). Пример: gpio-2026-07-17-12.bin — данные за 12:0012:59 17 июля 2026.

Чтение на другой платформе: каждая запись — <Q + B в терминах Python struct:

import struct
with open("gpio-2026-07-17-12.bin", "rb") as f:
    while chunk := f.read(9):
        if len(chunk) < 9:
            break
        ts_us, value = struct.unpack("<QB", chunk)
        strength, count = (value >> 6) & 0x3, value & 0x3F

Человекочитаемый GPIO-лог

Файл gpio_human.log (см. пути), пишет 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 сразу в два файла:

  1. events.log — JSON-строки (одна на событие):
{"ts": 1789034096, "time": "2026-07-17 12:34:56", "event": "ПЛАНОВАЯ_РОТАЦИЯ"}
  1. 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
ОБНАРУЖЕНО_<N>_ОБЪЕКТОВ_СИЛА_<M> reader.go Алерт превышения (count > 10)
ОЧИСТКАОГОВ_УДАЛЕНО_<N>_ФАЙЛОВ_<M>_MB retention.go Retention удалил файлы
ОШИБКАОЛУЧЕНИЯ_ДИРЕКТОРИИ retention.go Retention не смог получить каталог данных

События смены состояния пайпа пишутся только при изменении состояния (дедупликация в PipeReader), тишина — при каждой проверке watchdog (раз в -watchdog-interval минут), пока тишина длится.

Пути хранения

Все пути вычисляет 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: RotatingLogger держит текущий DataLogger и раз в -rotation-check-interval минут (по умолчанию 1) сверяет текущий час с часом открытого файла. При смене часа старый файл закрывается (с финальным flush) и открывается новый gpio-YYYY-MM-DD-HH.bin; пишется событие ПЛАНОВАЯ_РОТАЦИЯ.

Retention (очистка)

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.