From 676a6747947d4d989118f0d8a8a3798ea890ff61 Mon Sep 17 00:00:00 2001 From: Maxim Date: Tue, 9 Jun 2026 11:19:32 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0.=20=D0=9F=D0=B0=D1=80=D1=81=D0=B8=D0=BD=D0=B3=20?= =?UTF-8?q?=D0=B2=20=D0=BB=D0=BE=D0=B3=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/logger/parser.go | 40 ++++++++++++++++ internal/pipe/reader.go | 99 ++++++++++++++++++++++++++------------- scripts/view_logs.sh | 45 ++++++++++++++++++ 3 files changed, 151 insertions(+), 33 deletions(-) create mode 100644 internal/logger/parser.go create mode 100644 scripts/view_logs.sh diff --git a/internal/logger/parser.go b/internal/logger/parser.go new file mode 100644 index 0000000..159d14f --- /dev/null +++ b/internal/logger/parser.go @@ -0,0 +1,40 @@ +package logger + +type GPIOData struct { + RawValue byte // сырое значение + Count byte // количество обнаружений (биты 0-5) + Strength byte // сила сигнала (биты 6-7) + IsHighCount bool // превышает ли количество 10 +} + +// ParseGPIO парсит сырой байт на составляющие +func ParseGPIO(raw byte) GPIOData { + // Биты 0-5: количество (0-63) + count := raw & 0x3F // маска 00111111 + + // Биты 6-7: сила (0-3) + strength := (raw >> 6) & 0x03 // сдвиг на 6 бит, маска 00000011 + + return GPIOData{ + RawValue: raw, + Count: count, + Strength: strength, + IsHighCount: count > 10, + } +} + +// GetStrengthName возвращает текстовое описание силы +func GetStrengthName(strength byte) string { + switch strength { + case 0: + return "Слабый" + case 1: + return "Средний" + case 2: + return "Сильный" + case 3: + return "Максимальный" + default: + return "Неизвестно" + } +} \ No newline at end of file diff --git a/internal/pipe/reader.go b/internal/pipe/reader.go index 9c28132..3803aa9 100644 --- a/internal/pipe/reader.go +++ b/internal/pipe/reader.go @@ -16,7 +16,11 @@ type PipeReader struct { humanLogger *logger.HumanLogger monitor *logger.Monitor eventLog *logger.EventLogger - lastAlert map[byte]time.Time // для предотвращения спама алертов + + // Для дедупликации логов + lastData logger.GPIOData + lastLogTime time.Time + lastAlertTime map[byte]time.Time } func NewPipeReader( @@ -27,12 +31,12 @@ func NewPipeReader( eventLog *logger.EventLogger, ) *PipeReader { return &PipeReader{ - buf: buf, - dataLogger: dataLogger, - humanLogger: humanLogger, - monitor: monitor, - eventLog: eventLog, - lastAlert: make(map[byte]time.Time), + buf: buf, + dataLogger: dataLogger, + humanLogger: humanLogger, + monitor: monitor, + eventLog: eventLog, + lastAlertTime: make(map[byte]time.Time), } } @@ -47,7 +51,6 @@ func (pr *PipeReader) Start(ctx context.Context, pipePath string) { default: } - // Проверяем существует ли pipe if _, err := os.Stat(pipePath); os.IsNotExist(err) { if pr.eventLog != nil { pr.eventLog.Event("PIPE_НЕ_НАЙДЕН") @@ -79,33 +82,58 @@ func (pr *PipeReader) Start(ctx context.Context, pipePath string) { break } - // Обрабатываем каждый байт for i := 0; i < n; i++ { - b := buffer[i] + rawByte := buffer[i] now := time.Now() - // 1. В RAM для UI - pr.buf.Write(b) + // Парсим данные + data := logger.ParseGPIO(rawByte) + + // 1. В RAM для UI (сырое значение) + pr.buf.Write(rawByte) // 2. На диск для архива (бинарный) if pr.dataLogger != nil { pr.dataLogger.Write(logger.Sample{ Timestamp: now.UnixMicro(), - Value: b, + Value: rawByte, }) } - // 3. Human-readable лог (только когда есть данные) + // 3. Human-readable лог (при изменении состояния или раз в 5 секунд) if pr.humanLogger != nil { - humanLine := fmt.Sprintf("[%s] Значение GPIO: %d (0x%02X)\n", - now.Format("2006-01-02 15:04:05.000"), - b, b) - pr.humanLogger.Write(humanLine) + shouldLog := false + + // Логируем если изменилось количество или сила + if data.Count != pr.lastData.Count || data.Strength != pr.lastData.Strength { + shouldLog = true + } + + // Или если прошло больше 5 секунд с последнего лога + if now.Sub(pr.lastLogTime) >= 5*time.Second { + shouldLog = true + } + + if shouldLog { + strengthName := logger.GetStrengthName(data.Strength) + humanLine := fmt.Sprintf( + "[%s] Обнаружение: %d объектов | Сила: %s (уровень %d) | Сырое: 0x%02X (%d)\n", + now.Format("2006-01-02 15:04:05.000"), + data.Count, + strengthName, + data.Strength, + rawByte, + rawByte, + ) + pr.humanLogger.Write(humanLine) + pr.lastData = data + pr.lastLogTime = now + } } - // 4. Алерт если значение > 10 - if b > 10 { - pr.handleAlert(b, now) + // 4. Алерт если количество обнаружений > 10 + if data.IsHighCount { + pr.handleAlert(data, now) } // 5. Обновляем watchdog @@ -115,32 +143,37 @@ func (pr *PipeReader) Start(ctx context.Context, pipePath string) { } } - // Пауза перед переподключением time.Sleep(1 * time.Second) } }() } -func (pr *PipeReader) handleAlert(value byte, timestamp time.Time) { - // Anti-spam: не чаще 1 алерта в секунду для одного значения - if last, exists := pr.lastAlert[value]; exists { - if timestamp.Sub(last) < 1*time.Second { +func (pr *PipeReader) handleAlert(data logger.GPIOData, timestamp time.Time) { + // Anti-spam: не чаще 1 алерта в 10 секунд для одинакового количества + key := data.Count + if last, exists := pr.lastAlertTime[key]; exists { + if timestamp.Sub(last) < 10*time.Second { return } } - pr.lastAlert[value] = timestamp + pr.lastAlertTime[key] = timestamp + + strengthName := logger.GetStrengthName(data.Strength) // 1. В event log (JSON) if pr.eventLog != nil { - pr.eventLog.Event(fmt.Sprintf("ВЫСОКОЕ_ЗНАЧЕНИЕ_%d", value)) + pr.eventLog.Event(fmt.Sprintf("ОБНАРУЖЕНО_%d_ОБЪЕКТОВ_СИЛА_%d", data.Count, data.Strength)) } - // 2. В human-readable лог с предупреждением (только на русском) + // 2. В human-readable лог с предупреждением if pr.humanLogger != nil { - alertLine := fmt.Sprintf("[%s] ВНИМАНИЕ: Обнаружено высокое значение! GPIO = %d (>10)\n", - timestamp.Format("2006-01-02 15:04:05.000"), value) + alertLine := fmt.Sprintf( + "[%s] ВНИМАНИЕ: Обнаружено превышение! %d объектов | Сила: %s (уровень %d)\n", + timestamp.Format("2006-01-02 15:04:05.000"), + data.Count, + strengthName, + data.Strength, + ) pr.humanLogger.Write(alertLine) } - - // 3. В консоль больше НЕ пишем (всё уже в логах) } \ No newline at end of file diff --git a/scripts/view_logs.sh b/scripts/view_logs.sh new file mode 100644 index 0000000..a6fb8a6 --- /dev/null +++ b/scripts/view_logs.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +cd /home/user/temp/golang + +echo "=== СТАТИСТИКА ЛОГОВ ===" +echo "" + +if [ -d logs ]; then + echo "Общая статистика:" + echo " Событий (JSON): $(wc -l < logs/events.log 2>/dev/null || echo 0)" + echo " Событий (Human): $(wc -l < logs/events_human.log 2>/dev/null || echo 0)" + echo " Записей GPIO: $(wc -l < logs/gpio_human.log 2>/dev/null || echo 0)" + echo "" + + echo "Последние 5 предупреждений:" + grep "ВНИМАНИЕ" logs/gpio_human.log 2>/dev/null | tail -5 || echo " Нет предупреждений" + echo "" + + echo "Топ-10 по количеству обнаруженных объектов:" + grep "Обнаружение:" logs/gpio_human.log 2>/dev/null | \ + sed -n 's/.* \([0-9]\+\) объектов.*/\1/p' | \ + sort | uniq -c | sort -rn | head -10 | \ + awk '{print " " $2 " объектов: " $1 " раз"}' + echo "" + + echo "Распределение по силе сигнала:" + grep "Обнаружение:" logs/gpio_human.log 2>/dev/null | \ + grep -o "Сила: [А-Яа-я]*" | \ + sort | uniq -c | \ + awk '{print " " $2 " " $3 ": " $1 " раз"}' + echo "" + + echo "💾 Использование диска:" + du -sh logs/ 2>/dev/null + echo "" + + if ls logs/data/*.bin 1> /dev/null 2>&1; then + echo "Файлы данных:" + ls -lh logs/data/*.bin 2>/dev/null | awk '{print " " $9 ": " $5}' + else + echo "Нет файлов данных" + fi +else + echo "Директория logs/ не найдена" +fi \ No newline at end of file