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

14 KiB
Raw Permalink Blame History

Архитектура системы

Описание устройства GPIO Monitor: компоненты, поток данных, конкурентность, топологии развёртывания.

Аудитория: разработчики.

Содержание

Общая схема

GPIO-шина (8 бит)
      │ прерывание по стробу WR
      ▼
gpio-interrupt (C + WiringPi)          ← отдельная программа, вне этого репозитория
      │ stdout → socket (systemd)
      ▼
FIFO pipe /tmp/gpio_pipe               ← создаёт systemd (monitor-gpio.socket)
      │ сырые байты
      ▼
┌──────────────────────── Go-сервер (gpio-monitor-server) ────────────────────────┐
│  PipeReader (internal/pipe)                                                     │
│    ├─► RingBuffer (internal/adapter)        — RAM, для UI                       │
│    ├─► RotatingLogger → DataLogger          — .bin-архив на диске               │
│    ├─► HumanLogger                          — gpio_human.log                    │
│    ├─► EventLogger (алерты count > 10)      — events.log / events_human.log     │
│    └─► Monitor (watchdog тишины)                                                │
│                                                                                 │
│  Retention (internal/logger)                — фоновая очистка .bin              │
│  audio.Monitor (internal/audio)             — уровень звука через arecord       │
│                                                                                 │
│  HTTP :8080                                                                     │
│    ├─ /api/*        — JSON API (см. api.md)                                     │
│    ├─ /api/cam      — прокси MJPEG с go2rtc (:1984)                             │
│    └─ /*            — встроенный веб-дашборд (go:embed)                         │
└─────────────────────────────────────────────────────────────────────────────────┘
      ▲
      │ HTTP-опрос (~1 раз/с)
   Браузер (dashboard.html / logs.html, TypeScript — см. frontend.md)

Форматы данных на каждом участке — в data-formats.md, API — в api.md.

Захват GPIO и доставка данных

Захват выполняет C-программа gpio-interrupt (WiringPi), которая по прерыванию строба читает биты шины и пишет байты в stdout. Её исходников в этом репозитории нет — она живёт на устройстве в /home/user/WiringPi/examples (см. tech-debt.md).

Доставка построена на systemd socket-activation (scripts/monitor-gpio.socket, scripts/monitor-gpio.service):

  1. monitor-gpio.socket создаёт FIFO /tmp/gpio_pipe (ListenFIFO, права 0666, RemoveOnStop=yes);
  2. monitor-gpio.service запускает gpio-interrupt со StandardOutput=socket — stdout программы направляется прямо в FIFO;
  3. Go-сервер открывает FIFO на чтение (флаг -pipe).

Такая схема развязывает жизненные циклы: капчер и сервер можно перезапускать независимо, pipe создаёт и убирает systemd.

Компоненты Go-сервера

PipeReader — internal/pipe/reader.go

Единственный «производитель» данных. В отдельной горутине: следит за существованием FIFO (проверка каждые 2 с), открывает его, читает блоками до 4096 байт и раздаёт каждый байт потребителям (см. схему). Ведёт машину состояний unknown → found / not_found / error / disconnected и пишет события PIPE_* только при смене состояния — защита от спама в лог при флапающем соединении. При обрыве чтения переподключается через 1 с.

Дополнительно дедуплицирует human-лог (пишет при изменении count/strength или раз в -human-log-interval с) и алерты (-alert-cooldown на одинаковый count).

RingBuffer — internal/adapter/buffer.go

Кольцевой буфер последних N байт (флаг -buffer-size, по умолчанию 10240) — источник данных для /api/latest, /api/history, /api/health. Потокобезопасен: данные под sync.RWMutex, счётчики (lastWrite, totalBytes) — атомики.

Скорость приёма считается двумя механизмами, оба пишут в одни и те же атомики currentBPS/currentBPSBits: периодический пересчёт по дельте счётчика (не чаще раза в 250 мс) и скользящее окно 1 с по временным меткам последних байт. Второй перетирает первый; есть и публичный GetWindowSpeed(), который API не использует. Это избыточность — кандидат на упрощение (tech-debt.md).

Логгеры — internal/logger/

  • RotatingLogger (rotation.go) — фасад над DataLogger: держит файл текущего часа, по таймеру (-rotation-check-interval) проверяет смену часа и переоткрывает файл.
  • DataLogger (data_logger.go) — бинарная запись сэмплов (9 байт) с буфером 64 КБ, flush раз в 1 с или при заполнении, fsync после каждого сброса.
  • HumanLogger (human_logger.go) — построчная запись в gpio_human.log с немедленным Sync.
  • EventLogger (event_logger.go) — системные события, двойная запись: JSON (events.log) + текст (events_human.log).
  • Monitor (monitor.go) — watchdog: PipeReader отмечает каждую запись (RecordWrite), фоновый цикл раз в -watchdog-interval минут сравнивает тишину с порогами и пишет ТИШИНА_5МИН/ТИШИНА_10МИН.
  • Retention (retention.go) — фоновая FIFO-очистка .bin-файлов по возрасту и суммарному размеру, минимум 2 файла всегда сохраняются; устойчива к скачку системных часов (подробности в data-formats.md).

Аудиомонитор — internal/audio/monitor.go

Запускает arecord (ALSA: raw, 1 канал, 16 кГц, S16_LE), автоматически выбирая USB-аудиоустройство по выводу arecord -l (fallback — первое устройство захвата, затем default). Читает PCM блоками 3200 байт (100 мс), считает RMS и публикует уровень 0100 (RMS/32768 × 100 × 4, с отсечкой). При падении arecord перезапускает захват через 5 с. Уровень отдаётся в /api/health полем sound_level. Недоступность микрофона не мешает запуску сервера.

HTTP-слой — cmd/server/main.go

Стандартный net/http без внешних роутеров: /api/ обслуживает switch по пути (8 эндпоинтов, все под CORS-обёрткой, кроме /api/cam), остальное — http.FileServer поверх встроенных веб-файлов. Прокси камеры ретранслирует MJPEG-поток из go2rtc с flush после каждого чанка 32 КБ.

Порядок инициализации

Последовательность в main() (важна: каждый следующий компонент получает уже готовые предыдущие):

  1. Разбор CLI-флагов → logger.Config;
  2. EventLogger (без него сервер не стартует) → событие СЕРВЕР_ЗАПУЩЕН;
  3. HumanLogger;
  4. Monitor (watchdog) — сразу запускает фоновый цикл;
  5. RotatingLogger — открывает файл текущего часа, запускает цикл ротации;
  6. Retention — если -retention-hours > 0 или -retention-mb > 0; первая очистка через 1 мин;
  7. RingBuffer;
  8. audio.Monitor — ошибка запуска не фатальна;
  9. PipeReader.Start — горутина чтения FIFO;
  10. Регистрация HTTP-хендлеров и ListenAndServe (блокируется навсегда).

Ошибка инициализации любого логгера (шаги 25) — log.Fatal, сервер не запускается.

Конкурентность

Горутина Кто запускает Что делает
Чтение FIFO PipeReader.Start Цикл открытия/чтения пайпа, раздача байт
Flush-цикл DataLogger NewDataLogger (пересоздаётся при ротации) Сброс буфера раз в 1 с
Цикл ротации NewRotatingLogger Проверка смены часа
Watchdog NewMonitor Проверка тишины
Цикл retention Retention.StartWithInterval Периодическая очистка + разовая через 1 мин
Чтение PCM audio.Monitor.startCapture RMS-расчёт уровня звука
cmd.Wait arecord startCapture Сбор зомби-процесса
HTTP-хендлеры net/http По горутине на запрос

Синхронизация: у каждого компонента свой мьютекс (RingBuffer.mu+speedMu+recentMu, DataLogger.mu, RotatingLogger.mu, EventLogger.mu, HumanLogger.mu, Monitor.mu, audio.Monitor.mu); межкомпонентных блокировок нет — данные передаются вызовами методов, владение файлами не разделяется. Завершение — через context (PipeReader, audio) и каналы (DataLogger.closeCh, Retention.stopCh).

Встраивание фронтенда

//go:embed web/dist web/css web/fonts web/*.html web/*.png
var webFiles embed.FS

Скомпилированный дашборд вшивается в бинарник на этапе go build — сервер разворачивается одним файлом. Следствие: go build падает, если web/dist не существует, поэтому фронтенд всегда собирается первым (make build = build-frontend + build-backend). Подробнее — development.md и frontend.md.

Топологии развёртывания

Режим определяется наличием каталога /opt/gpio-monitoring (internal/logger/paths.go):

Пакетный режим (deb) Режим разработки
Бинарник /usr/bin/gpio-monitor-server ./build/gpio-monitor-server или go run
Запуск systemd gpio-monitor-server.service, пользователь gpio-monitor вручную
Логи /var/log/gpio-monitoring ~/.local/share/gpio-monitoring/logs
Веб-файлы вшиты в бинарник (копия в /opt/gpio-monitoring/web) вшиты в бинарник
Источник данных реальный gpio-interrupt через systemd socket эмулятор scripts/emulator.py + mkfifo

Внешние сервисы в обоих режимах: go2rtc (порт 1984, конфиг scripts/go2rtc.yaml) для MJPEG-камеры и ALSA/arecord для микрофона. Установка и настройка — в operations.md.

Подпроект SDR/ (OFDM-радиолинк на PlutoSDR) — автономный C-проект со своей сборкой и README; с Go-сервером не связан: канал HackRF/PlutoSDR используется как альтернативный транспорт байтов GPIO-шины (см. scripts/emulatohackrf.sh).