From e06f0ae0c61d1420b648cb7ec8f3fbd2a887dcd5 Mon Sep 17 00:00:00 2001 From: Maxim Date: Fri, 1 May 2026 09:58:02 +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=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B8=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 105 +++++++++++++++++++-- scripts/imi_wire.py | 177 +++++++++++++++++++++++++++++++++++ scripts/monitor-gpio.service | 16 ++++ scripts/monitor-gpio.socket | 12 +++ 4 files changed, 300 insertions(+), 10 deletions(-) create mode 100755 scripts/imi_wire.py create mode 100644 scripts/monitor-gpio.service create mode 100644 scripts/monitor-gpio.socket diff --git a/README.md b/README.md index 6b5d2ac..5c071fd 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,101 @@ -# GPIO Monitor Server +# GPIO Monitor Dashboard for Raspberry Pi -Сервер для мониторинга GPIO на Raspberry Pi. +Реалтайм мониторинг параллельной GPIO-шины Raspberry Pi через C + Go + Web Dashboard. -## Структура проекта -- `cmd/server/` - точка входа приложения -- `internal/` - внутренняя логика -- `web/` - веб-интерфейс -- `scripts/` - вспомогательные скрипты +Проект читает входящие сигналы с GPIO-пинов Raspberry Pi по прерыванию, передаёт данные через FIFO pipe в Go-сервер и отображает всё в браузере в реальном времени. + +--- + +# Возможности + +✅ Захват данных по GPIO interrupt +✅ Работа через WiringPi +✅ FIFO pipe между C и Go +✅ Высокоскоростной RingBuffer +✅ HTTP API +✅ Web Dashboard +✅ Работа через Wi-Fi Access Point Raspberry Pi +✅ Подходит для ПК и мобильных устройств + +--- + +# Архитектура + +```text +GPIO BUS (8 bit) + ↓ +gpio-interrupt.c + ↓ stdout +systemd socket FIFO (/tmp/gpio_pipe) + ↓ +Go Server + ↓ +HTTP API (:8080) + ↓ +Dashboard (Browser) + +# Структура проекта + + +. +├── cmd/server/main.go # Go HTTP сервер +├── internal/adapter/ # RingBuffer +├── web/ +│ ├── index.html +│ ├── dashboard.html +│ ├── css/style.css +│ ├── js/app.js +│ └── favicon.png +├── scripts/ +├── start-server.sh +└── README.md + +# GPIO pin + +WR/STROBE = GPIO27 + +DATA BUS: +D0 = GPIO21 +D1 = GPIO7 +D2 = GPIO6 +D3 = GPIO5 +D4 = GPIO25 +D5 = GPIO24 +D6 = GPIO23 +D7 = GPIO22 + +# Установка + +sudo apt update +sudo apt install golang git build-essential + +# Сборка -## Сборка go build -o gpio-monitor-server ./cmd/server -## Запуск -./gpio-monitor-server +# Запуск сервера +./start-server.sh + +sudo systemctl start monitor-gpio.service + +# Dashboard + +Открывать на http://192.168.77.1:8080 + +# API + +GET /api/health + +GET /api/history + +# Wi-Fi режим Raspberry Pi + +SSID: fix_me +IP: 192.168.77.1 + +Подключение: ssh user@192.168.77.1 + +# License + +MIT diff --git a/scripts/imi_wire.py b/scripts/imi_wire.py new file mode 100755 index 0000000..1eb496b --- /dev/null +++ b/scripts/imi_wire.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +""" +Имитатор параллельной шины для gpio-interrupt (Raspberry Pi 4) +""" + +import gpiod +import time +import argparse +import sys +import signal +from gpiod.line import Direction, Value + + +class ParallelBusImitator: + """Имитатор 7-битной параллельной шины данных""" + + # Временные константы (секунды) + DATA_SETUP_TIME = 0.000005 + STROBE_WIDTH = 0.000005 + HOLD_TIME = 0.000005 + + WR_PIN = 12 # BCM12 + + DATA_PINS_MAPPING = [ + (21, 18, "D0 (LSB)"), + (7, 27, "D1"), + (6, 22, "D2"), + (5, 23, "D3"), + (25, 17, "D4"), + (24, 20, "D5"), + (23, 21, "D6"), + (22, None, "D7 (не подключён)"), + ] + + def __init__(self, use_mask=True, chip_path='/dev/gpiochip0'): + self.use_mask = use_mask + self.counter = 0 + self.active_pins = [] + + for wpi, bcm, desc in self.DATA_PINS_MAPPING: + if bcm is not None: + self.active_pins.append((wpi, bcm, desc)) + + self.data_pins_bcm = [bcm for _, bcm, _ in self.active_pins] + self.all_pins = self.data_pins_bcm + [self.WR_PIN] + + try: + self.chip = gpiod.Chip(chip_path) + self.lines = self.chip.request_lines( + { + p: gpiod.LineSettings( + direction=Direction.OUTPUT, + output_value=Value.INACTIVE + ) + for p in self.all_pins + }, + consumer="par-bus-imitator" + ) + except PermissionError: + print("Нет прав доступа к GPIO. Используйте sudo.") + sys.exit(1) + except Exception as e: + print(f"Ошибка GPIO: {e}") + sys.exit(1) + + bit_width = 4 if use_mask else len(self.data_pins_bcm) + + print(f"✓ Имитатор шины ({bit_width}-бит режим)") + print("Нажмите Ctrl+C для остановки\n") + + def send_byte(self, value): + """Отправка одного байта""" + if self.use_mask: + value &= 0x0F + else: + value &= 0x7F + + out = {} + + for bit_pos, bcm_pin in enumerate(self.data_pins_bcm): + bit = (value >> bit_pos) & 1 + out[bcm_pin] = Value.ACTIVE if bit else Value.INACTIVE + + self.lines.set_values(out) + time.sleep(self.DATA_SETUP_TIME) + + self.lines.set_value(self.WR_PIN, Value.ACTIVE) + time.sleep(self.STROBE_WIDTH) + self.lines.set_value(self.WR_PIN, Value.INACTIVE) + time.sleep(self.HOLD_TIME) + + self.counter += 1 + self.show_status(value) + + def show_status(self, value): + """Красивый статус без спама""" + bits = 4 if self.use_mask else 7 + bin_val = format(value, f'0{bits}b') + + msg = ( + f"\r▶ Передача: " + f"HEX=0x{value:02X} | " + f"BIN={bin_val} | " + f"Пакетов: {self.counter}" + ) + + print(msg, end='', flush=True) + + def send_sequence(self, values, delay=0.2): + for val in values: + self.send_byte(val) + time.sleep(delay) + + def cleanup(self): + if hasattr(self, 'lines'): + self.lines.release() + if hasattr(self, 'chip'): + self.chip.close() + + +def handle_signal(signum, frame): + print("\nЗавершение работы...") + sys.exit(0) + + +def main(): + signal.signal(signal.SIGINT, handle_signal) + signal.signal(signal.SIGTERM, handle_signal) + + parser = argparse.ArgumentParser( + description='Имитатор параллельной шины' + ) + + parser.add_argument('--no-mask', action='store_true', + help='7-битный режим') + parser.add_argument('--test', + choices=['default', 'counter', 'pattern'], + default='default') + parser.add_argument('--delay', type=float, default=0.2) + parser.add_argument('--single', type=str) + parser.add_argument('--chip', default='/dev/gpiochip0') + + args = parser.parse_args() + + imitator = ParallelBusImitator( + use_mask=not args.no_mask, + chip_path=args.chip + ) + + try: + if args.single: + val = int(args.single, 16) + imitator.send_byte(val) + print() + return + + if args.test == 'default': + test_data = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40] + elif args.test == 'counter': + test_data = list(range(128)) + else: + test_data = [0x00, 0x01, 0x03, 0x07, 0x0F, 0x55, 0x2A] + + while True: + imitator.send_sequence(test_data, args.delay) + + except KeyboardInterrupt: + print("\nОстановка...") + except Exception as e: + print(f"\nОшибка: {e}") + finally: + imitator.cleanup() + print("✓ GPIO освобождены") + + +if __name__ == "__main__": + main() diff --git a/scripts/monitor-gpio.service b/scripts/monitor-gpio.service new file mode 100644 index 0000000..accb83e --- /dev/null +++ b/scripts/monitor-gpio.service @@ -0,0 +1,16 @@ +[Unit] +Description=GPIO Interrupt Capture Service +Requires=monitor-gpio.socket +After=monitor-gpio.socket + +[Service] +Type=simple +User=root +WorkingDirectory=/home/user/WiringPi/examples + +ExecStart=/home/user/WiringPi/examples/gpio-interrupt - +StandardOutput=socket +StandardError=journal + +Restart=always +RestartSec=2 diff --git a/scripts/monitor-gpio.socket b/scripts/monitor-gpio.socket new file mode 100644 index 0000000..a91a3a6 --- /dev/null +++ b/scripts/monitor-gpio.socket @@ -0,0 +1,12 @@ +[Unit] +Description=GPIO FIFO Pipe + +[Socket] +ListenFIFO=/tmp/gpio_pipe +SocketMode=0666 +SocketUser=user +SocketGroup=user +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target