From 4180353b658e57a165c690783f8b550cf1cab3b9 Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 17 Jun 2026 09:53:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 372 +++++++++++++++++++++++++++++---- cmd/server/main.go | 220 ++++++++++++++++++++ cmd/server/web/README.md | 4 + cmd/server/web/dashboard.html | 17 +- cmd/server/web/js/camera.ts | 26 +-- cmd/server/web/logs.html | 373 ++++++++++++++++++++++++++++++++++ 6 files changed, 936 insertions(+), 76 deletions(-) create mode 100644 cmd/server/web/logs.html diff --git a/README.md b/README.md index 5c071fd..f8ef1c5 100644 --- a/README.md +++ b/README.md @@ -2,56 +2,125 @@ Реалтайм мониторинг параллельной GPIO-шины Raspberry Pi через C + Go + Web Dashboard. -Проект читает входящие сигналы с GPIO-пинов Raspberry Pi по прерыванию, передаёт данные через FIFO pipe в Go-сервер и отображает всё в браузере в реальном времени. +Проект читает входящие сигналы с GPIO-пинов Raspberry Pi по прерыванию, передаёт данные через FIFO pipe в Go-сервер и отображает всё в браузере в реальном времени с возможностью просмотра исторических данных. --- -# Возможности +## Возможности -✅ Захват данных по GPIO interrupt -✅ Работа через WiringPi -✅ FIFO pipe между C и Go -✅ Высокоскоростной RingBuffer -✅ HTTP API -✅ Web Dashboard -✅ Работа через Wi-Fi Access Point Raspberry Pi -✅ Подходит для ПК и мобильных устройств +✅ Захват данных по GPIO interrupt (C + WiringPi) +✅ FIFO pipe, а также stdin/stdout между C + WiringPi и Go Service +✅ Реализация кольцевого буфера чтения +✅ HTTP API + Web Dashboard +✅ **Автоматическая ротация** лог-файлов +✅ **Retention** (автоматическая очистка старых логов) +✅ **Watchdog** мониторинг активности +✅ **MJPEG камера** через go2rtc +✅ TypeScript фронтенд +✅ Работа через Wi-Fi Access Point --- -# Архитектура +## Архитектура ```text GPIO BUS (8 bit) ↓ -gpio-interrupt.c +gpio-interrupt.c (C + WiringPi) ↓ stdout -systemd socket FIFO (/tmp/gpio_pipe) +FIFO pipe (/tmp/gpio_pipe) ↓ -Go Server +Go Server (gpio-monitor-server) + ├── RingBuffer (RAM) + ├── Data Logger (бинарные .bin файлы) + ├── Human Logger (текстовые логи) + ├── Event Logger (события системы) + ├── Watchdog (мониторинг) + └── Retention (очистка старых логов) ↓ HTTP API (:8080) + ├── /api/health - статус сервера + ├── /api/latest - последние данные + ├── /api/history - история (последние 100) + ├── /api/stream - статус камеры + ├── /api/cam - MJPEG поток + ├── /api/log/files - список лог-файлов + ├── /api/log/data - данные из лог-файла + └── /api/log/events - события системы ↓ -Dashboard (Browser) +Web Dashboard (Embedded) + ├── index.html + ├── dashboard.html (главная) + ├── logs.html (просмотр логов) + ├── css/style.css + ├── js/*.ts (TypeScript исходники) + └── dist/*.js (скомпилированный JS) +``` -# Структура проекта +--- +## Структура проекта +``` . -├── cmd/server/main.go # Go HTTP сервер -├── internal/adapter/ # RingBuffer -├── web/ -│ ├── index.html -│ ├── dashboard.html -│ ├── css/style.css -│ ├── js/app.js -│ └── favicon.png +├── build/ +│ └── gpio-monitor-server # Скомпилированный бинарник +├── build.sh # Скрипт сборки (Go + TypeScript) +├── cmd/server/ +│ ├── main.go # Go HTTP сервер +│ └── web/ # Веб-интерфейс +│ ├── css/style.css +│ ├── dist/ # Скомпилированный JS +│ │ ├── app.js +│ │ ├── chart.js +│ │ ├── data.js +│ │ └── ... +│ ├── js/ # TypeScript исходники +│ │ ├── app.ts +│ │ ├── chart.ts +│ │ ├── data.ts +│ │ └── ... +│ ├── dashboard.html +│ ├── index.html +│ ├── logs.html +│ ├── favicon.png +│ ├── package.json +│ ├── tsconfig.json +│ └── README.md +├── internal/ +│ ├── adapter/buffer.go # RingBuffer +│ ├── logger/ # Система логирования +│ │ ├── config.go +│ │ ├── data_logger.go # Бинарные логи +│ │ ├── event_logger.go # События +│ │ ├── human_logger.go # Человекочитаемые логи +│ │ ├── monitor.go # Watchdog +│ │ ├── parser.go # Парсер данных GPIO +│ │ ├── paths.go # Пути к файлам +│ │ ├── retention.go # Очистка старых логов +│ │ └── rotation.go # Ротация файлов +│ └── pipe/reader.go # Чтение из FIFO pipe ├── scripts/ +│ ├── monitor-gpio.service # Systemd сервис +│ ├── monitor-gpio.socket # Systemd socket +│ ├── go2rtc # Бинарник go2rtc +│ ├── go2rtc.yaml # Конфиг go2rtc +│ ├── view_logs.sh # Скрипт просмотра логов +│ ├── emulator.py # Эмулятор GPIO +│ └── imi_wire.py # IMI Wire эмулятор +├── SDR/ # SDR компоненты +│ ├── common.c/h +│ ├── reciever.c +│ ├── transmitter.c +│ └── makefile +├── go.mod ├── start-server.sh └── README.md +``` -# GPIO pin +## GPIO пины +``` WR/STROBE = GPIO27 DATA BUS: @@ -63,39 +132,262 @@ D4 = GPIO25 D5 = GPIO24 D6 = GPIO23 D7 = GPIO22 +``` -# Установка +--- +## Установка + +### 1. Установка зависимостей + +```bash sudo apt update -sudo apt install golang git build-essential +sudo apt install -y golang git build-essential wiringpi nodejs npm +``` -# Сборка +### 2. Клонирование и сборка -go build -o gpio-monitor-server ./cmd/server +```bash +git clone +cd ~/work/golang -# Запуск сервера +# Сборка TypeScript + Go +./build.sh +``` -./start-server.sh +### 3. Настройка systemd сервиса +```bash +# Копировать сервисные файлы +sudo cp scripts/monitor-gpio.service /etc/systemd/system/ +sudo cp scripts/monitor-gpio.socket /etc/systemd/system/ + +# Перезагрузить systemd +sudo systemctl daemon-reload + +# Включить автозапуск +sudo systemctl enable monitor-gpio.socket +sudo systemctl enable monitor-gpio.service + +# Запустить сервис sudo systemctl start monitor-gpio.service +``` -# Dashboard +### 4. Проверка статуса -Открывать на http://192.168.77.1:8080 +```bash +sudo systemctl status monitor-gpio.service +journalctl -u monitor-gpio.service -f +``` -# API +--- +## Настройка firewall (nftables) + +**Важно:** На Raspberry Pi OS Bookworm по умолчанию активен nftables, который блокирует все входящие порты, кроме SSH (22). + +### Разрешить порт 8080: + +```bash +# Добавить правило для порта 8080 +sudo nft add rule inet filter input tcp dport 8080 accept + +# Проверить правила +sudo nft list ruleset + +# Сохранить правила (для сохранения после перезагрузки) +sudo nft list ruleset | sudo tee /etc/nftables.conf > /dev/null +sudo systemctl enable nftables +sudo systemctl restart nftables +``` + +### Для доступа только с локальной сети: + +```bash +# Разрешить только с сети 10.1.1.0/24 +sudo nft add rule inet filter input ip saddr 10.1.1.0/24 tcp dport 8080 accept + +# Или только с конкретного IP +sudo nft add rule inet filter input ip saddr 192.168.1.100 tcp dport 8080 accept +``` + +### Для доступа через Wi-Fi AP: + +```bash +# Разрешить с Wi-Fi сети (192.168.77.0/24) +sudo nft add rule inet filter input iifname "wlan0" tcp dport 8080 accept +``` + +--- + +## Запуск + +### Ручной запуск + +```bash +./build/gpio-monitor-server \ + -pipe /tmp/gpio_pipe \ + -retention-hours=72 \ + -retention-mb=2000 \ + -retention-interval=60 \ + -rotation-check-interval=1 \ + -buffer-size=10240 \ + -human-log-interval=5 +``` + +### Параметры командной строки + +| Параметр | По умолчанию | Описание | +|----------|-------------|----------| +| `-pipe` | `/tmp/gpio_pipe` | Путь к FIFO pipe | +| `-port` | `:8080` | Порт веб-сервера | +| `-retention-hours` | `48` | Часы хранения логов (0 = отключено) | +| `-retention-mb` | `5000` | Максимальный размер логов в MB | +| `-retention-interval` | `15` | Интервал проверки retention (мин) | +| `-rotation-check-interval` | `1` | Интервал проверки ротации (мин) | +| `-buffer-size` | `10240` | Размер кольцевого буфера | +| `-human-log-interval` | `5` | Интервал записи human-логов (сек) | +| `-alert-cooldown` | `10` | Задержка между алертами (сек) | +| `-camera-url` | `http://127.0.0.1:1984/api/stream.mjpeg?src=cam_mjpeg` | URL MJPEG камеры | + +--- + +## Dashboard + +Открывайте в браузере: + +- **Через Ethernet:** `http://10.1.1.33:8080` (или ваш IP) +- **Через Wi-Fi AP:** `http://192.168.77.1:8080` +- **Локально:** `http://localhost:8080` + +### Страницы + +- `http://:8080/` - Перенаправление на dashboard +- `http://:8080/dashboard.html` - Главная панель мониторинга +- `http://:8080/logs.html` - Просмотр исторических логов + +--- + +## API + +### Основные эндпоинты + +```bash GET /api/health +# Статус сервера, uptime, статистика + +GET /api/latest +# Последние данные и история (10 записей) GET /api/history +# Последние 100 записей -# Wi-Fi режим Raspberry Pi +GET /api/stream +# Статус камеры -SSID: fix_me -IP: 192.168.77.1 +GET /api/cam +# MJPEG поток камеры (прокси на go2rtc) +``` -Подключение: ssh user@192.168.77.1 +### Работа с логами -# License +```bash +GET /api/log/files +# Список доступных лог-файлов -MIT +GET /api/log/data?file=gpio-2026-06-17-11.bin +# Данные из конкретного бинарного лога + +GET /api/log/events?limit=100 +# Последние события системы +``` + +--- + +## Диагностика проблем + +### Если Dashboard не открывается: + +1. **Проверьте, что сервер запущен:** + ```bash + ps aux | grep gpio-monitor-server + sudo systemctl status monitor-gpio.service + ``` + +2. **Проверьте firewall:** + ```bash + sudo nft list ruleset | grep 8080 + ``` + +3. **Проверьте, на каком интерфейсе слушает сервер:** + ```bash + sudo ss -tlnp | grep 8080 + # Должно быть *:8080 или 0.0.0.0:8080 + ``` + +4. **Проверьте маршрутизацию:** + ```bash + ip route show + ``` + +5. **Посмотрите логи:** + ```bash + sudo journalctl -u monitor-gpio.service -f + tail -f /home/user/logs/gpio-events.log + tail -f /home/user/logs/gpio-human.log + ``` + +6. **Проверьте наличие pipe:** + ```bash + ls -la /tmp/gpio_pipe + ``` + +--- + +## Wi-Fi режим Raspberry Pi + +SSID: `fix_me` +IP: `192.168.77.1` + +Подключение: `ssh user@192.168.77.1` + +--- + + +### Запуск Go сервера в режиме разработки: + +```bash +go run cmd/server/main.go \ + -pipe /tmp/gpio_pipe \ + -retention-hours=1 \ + -human-log-interval=1 +``` + +### Эмуляция GPIO сигналов: + +```bash +python3 scripts/emulator.py +``` + +### Просмотр логов: + +```bash +./scripts/view_logs.sh +``` + +--- + +## Камера (go2rtc) + +Для работы камеры установите go2rtc: + +```bash +# Запуск go2rtc +./scripts/go2rtc -config scripts/go2rtc.yaml +``` + +Камера будет доступна по адресу: +- MJPEG поток: `http://:1984/api/stream.mjpeg?src=cam_mjpeg` +- Встроенный интерфейс: `http://:1984` + +--- diff --git a/cmd/server/main.go b/cmd/server/main.go index 2b040b3..eb6f502 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -3,6 +3,7 @@ package main import ( "context" "embed" + "encoding/binary" "encoding/json" "flag" "fmt" @@ -10,6 +11,10 @@ import ( "io/fs" "log" "net/http" + "os" + "path/filepath" + "sort" + "strconv" "strings" "time" @@ -33,6 +38,215 @@ func writeJSON(w http.ResponseWriter, v any) { json.NewEncoder(w).Encode(v) } +// HandleLogFiles - список доступных лог-файлов +func (a *API) HandleLogFiles(w http.ResponseWriter, r *http.Request) { + dataDir, err := logger.GetDataLogsDir() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + files, err := filepath.Glob(filepath.Join(dataDir, "gpio-*.bin")) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + type LogFileInfo struct { + Name string `json:"name"` + Path string `json:"path"` + Size int64 `json:"size"` + ModTime time.Time `json:"mod_time"` + Time string `json:"time"` + Date string `json:"date"` + Hour int `json:"hour"` + IsActive bool `json:"is_active"` + } + + var result []LogFileInfo + for _, file := range files { + info, err := os.Stat(file) + if err != nil { + continue + } + + // Парсим имя файла: gpio-2026-06-16-11.bin + base := filepath.Base(file) + parts := strings.Split(strings.TrimSuffix(base, ".bin"), "-") + + var fileTime time.Time + if len(parts) == 5 { + year, _ := strconv.Atoi(parts[1]) + month, _ := strconv.Atoi(parts[2]) + day, _ := strconv.Atoi(parts[3]) + hour, _ := strconv.Atoi(parts[4]) + fileTime = time.Date(year, time.Month(month), day, hour, 0, 0, 0, time.Local) + } else { + fileTime = info.ModTime() + } + + result = append(result, LogFileInfo{ + Name: base, + Path: file, + Size: info.Size(), + ModTime: info.ModTime(), + Time: fileTime.Format("02.01.2006 15:00"), + Date: fileTime.Format("2006-01-02"), + Hour: fileTime.Hour(), + IsActive: false, + }) + } + + // Сортируем по времени (новые сверху) + sort.Slice(result, func(i, j int) bool { + return result[i].ModTime.After(result[j].ModTime) + }) + + writeJSON(w, result) +} + +// HandleLogData - чтение данных из конкретного bin файла +func (a *API) HandleLogData(w http.ResponseWriter, r *http.Request) { + filename := r.URL.Query().Get("file") + if filename == "" { + http.Error(w, "missing file parameter", 400) + return + } + + // Защита от path traversal + filename = filepath.Base(filename) + dataDir, err := logger.GetDataLogsDir() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + filePath := filepath.Join(dataDir, filename) + if _, err := os.Stat(filePath); os.IsNotExist(err) { + http.Error(w, "file not found", 404) + return + } + + // Читаем бинарный файл + f, err := os.Open(filePath) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + defer f.Close() + + type Sample struct { + Timestamp int64 `json:"ts"` + Time string `json:"time"` + Value byte `json:"value"` + Count byte `json:"count"` + Strength byte `json:"strength"` + StrengthName string `json:"strength_name"` + } + + var samples []Sample + buf := make([]byte, 9) // 8 байт timestamp + 1 байт value + + for { + n, err := f.Read(buf) + if err == io.EOF { + break + } + if err != nil { + http.Error(w, err.Error(), 500) + return + } + if n < 9 { + continue + } + + ts := int64(binary.LittleEndian.Uint64(buf[0:8])) + value := buf[8] + + data := logger.ParseGPIO(value) + + samples = append(samples, Sample{ + Timestamp: ts, + Time: time.UnixMicro(ts).Format("2006-01-02 15:04:05.000"), + Value: value, + Count: data.Count, + Strength: data.Strength, + StrengthName: logger.GetStrengthName(data.Strength), + }) + } + + // Ограничиваем количество точек для производительности + if len(samples) > 10000 { + // Берем каждую N-ю точку + step := len(samples) / 10000 + var filtered []Sample + for i := 0; i < len(samples); i += step { + filtered = append(filtered, samples[i]) + } + samples = filtered + } + + writeJSON(w, map[string]any{ + "filename": filename, + "total": len(samples), + "samples": samples, + }) +} + +// HandleLogEvents - чтение событий +func (a *API) HandleLogEvents(w http.ResponseWriter, r *http.Request) { + limit := 100 + if l := r.URL.Query().Get("limit"); l != "" { + if parsed, err := strconv.Atoi(l); err == nil { + limit = parsed + } + } + + eventsPath, err := logger.GetEventHumanLogPath() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + content, err := os.ReadFile(eventsPath) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + lines := strings.Split(string(content), "\n") + // Берем последние N строк + if len(lines) > limit+1 { + lines = lines[len(lines)-limit-1:] + } + + // Парсим строки для JSON + type Event struct { + Time string `json:"time"` + Event string `json:"event"` + } + + var events []Event + for _, line := range lines { + if line == "" { + continue + } + // Формат: [2026-06-16 11:23:45.123] EVENT: СЕРВЕР_ЗАПУЩЕН + parts := strings.SplitN(line, "] EVENT: ", 2) + if len(parts) == 2 { + events = append(events, Event{ + Time: strings.TrimPrefix(parts[0], "["), + Event: parts[1], + }) + } + } + + writeJSON(w, map[string]any{ + "events": events, + "total": len(events), + }) +} + func (a *API) HandleHealth(w http.ResponseWriter, r *http.Request) { latest, ok := a.buf.GetLatest() idle := time.Since(a.buf.LastWriteTime()) @@ -299,6 +513,12 @@ func main() { case "/cam": // Передаем URL камеры из конфигурации handleCamProxyWithURL(w, r, *cameraURL) + case "/log/files": + cors(api.HandleLogFiles)(w, r) + case "/log/data": + cors(api.HandleLogData)(w, r) + case "/log/events": + cors(api.HandleLogEvents)(w, r) default: http.Error(w, "not found", 404) } diff --git a/cmd/server/web/README.md b/cmd/server/web/README.md index 033b725..d24dcf6 100644 --- a/cmd/server/web/README.md +++ b/cmd/server/web/README.md @@ -31,3 +31,7 @@ #### Установка TypeScript и типов Node.js ```npm install -D typescript @types/node``` + +```bash +chmod +x node_modules/.bin/tsc +``` \ No newline at end of file diff --git a/cmd/server/web/dashboard.html b/cmd/server/web/dashboard.html index 67780b7..86381e2 100644 --- a/cmd/server/web/dashboard.html +++ b/cmd/server/web/dashboard.html @@ -13,11 +13,11 @@

GPIO МОНИТОРИНГ В РЕАЛЬНОМ ВРЕМЕНИ

-
--:--:--
- +
+ 📋 Журнал +
--:--:--
+ +
@@ -63,11 +63,6 @@
-
-

ГРАФИК КАНАЛА 1

- -
-

▶ ПРИНЯТЫЕ ДАННЫЕ

@@ -82,7 +77,7 @@
-
+