Дорабтка c HackRF

This commit is contained in:
Maxim
2026-05-20 11:30:45 +03:00
parent f6416ca106
commit 941a7cf6e6
5 changed files with 419 additions and 158 deletions

1
scripts/array.txt Normal file
View File

@@ -0,0 +1 @@
[0xE0, 0x01, 0x4C, 0x88]

15
scripts/emulatohackrf.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Эмулятор GPIO -> stdout (1 мс на байт)
python3 /home/user/temp/golang/scripts/emulator.py \
array.txt \
- \
0.001 \
| pv | # Передача через HackRF
/home/user/hackrf-liquid-dsp/hackrf-frame64-tx \
-x 40 \
-a 0 \
-d a32868dc347c6e47 \
-s 2000000 \
-b 1750000 \
-f 800000000

230
scripts/emulator.py Normal file
View File

@@ -0,0 +1,230 @@
import sys
import time
import ast
import signal
import os
OUT_BUFFER_LEN = 32
RECONNECT_DELAY = 0.5
REPEAT_COUNT = 100 # повтор
DEFAULT_SEND_DELAY = 0.1 # по умолчанию (10 байт/с)
stop_flag = False
# ================= SIGNALS =================
def handle_sigint(signum, frame):
global stop_flag
stop_flag = True
signal.signal(signal.SIGINT, handle_sigint)
signal.signal(signal.SIGTERM, handle_sigint)
# ================= UI =================
last_status = ""
def status(msg):
global last_status
if msg != last_status:
sys.stderr.write("\r" + msg + " " * 20)
sys.stderr.flush()
last_status = msg
def status_clear():
sys.stderr.write("\r" + " " * 80 + "\r")
sys.stderr.flush()
# ================= DATA =================
def load_array(path):
while True:
try:
with open(path, "r") as f:
content = f.read().strip()
data = ast.literal_eval(content)
result = []
for x in data:
if isinstance(x, str):
x = int(x, 0)
result.append(x & 0xFF)
print(f"[emu] Загружено {len(result)} байт", file=sys.stderr)
return result
except Exception as e:
print(f"[emu] Ошибка чтения: {e}", file=sys.stderr)
time.sleep(1)
# ================= PIPE =================
def open_pipe(path):
while not stop_flag:
try:
fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK)
status("[emu] pipe: подключен")
return os.fdopen(fd, "wb", buffering=0)
except OSError:
status("[emu] pipe: нет читателя")
time.sleep(RECONNECT_DELAY)
return None
# ================= ARGS PARSING =================
def parse_args():
"""
Разбирает аргументы командной строки.
Использование:
python3 emulator.py array.txt /tmp/gpio_pipe [задержка] [logfile|-]
Где:
задержка — float, секунд между байтами (по умолчанию DEFAULT_SEND_DELAY)
logfile — путь к файлу лога или "-" для stderr
"""
if len(sys.argv) < 3:
print("Использование: python emulator.py array.txt <pipe_path|-> [задержка] [logfile|-]", file=sys.stderr)
sys.exit(1)
array_path = sys.argv[1]
pipe_path = sys.argv[2]
send_delay = DEFAULT_SEND_DELAY
log_file = None
arg_offset = 0
if len(sys.argv) > 3:
try:
send_delay = float(sys.argv[3])
arg_offset = 1
print(f"[emu] Задержка: {send_delay} с (~{1/send_delay:.0f} байт/с)", file=sys.stderr)
except ValueError:
arg_offset = 0
log_idx = 3 + arg_offset
if len(sys.argv) > log_idx:
log_path = sys.argv[log_idx]
if log_path == "-":
log_file = sys.stderr
else:
log_file = open(log_path, "a")
return array_path, pipe_path, send_delay, log_file
# ================= MAIN =================
def main():
array_path, pipe_path, send_delay, log_file = parse_args()
data = load_array(array_path)
log_buffer = []
total_bytes = 0
packet_id = 0
i = 0
repeat = 0
# ===== STDOUT MODE =====
if pipe_path == "-":
pipe = sys.stdout.buffer # бинарный stdout
print(f"[emu] Режим stdout, {len(data)} байт в цикле", file=sys.stderr)
try:
while not stop_flag:
byte = data[i]
pipe.write(bytes([byte]))
pipe.flush()
log_buffer.append(byte)
total_bytes += 1
if len(log_buffer) >= OUT_BUFFER_LEN and log_file:
packet_id += 1
log_file.write(f"--- Пакет #{packet_id} ({OUT_BUFFER_LEN} байт) ---\n")
for idx, b in enumerate(log_buffer):
log_file.write(f" [{idx:02d}] = 0x{b:02x} ({b:3d})\n")
log_file.flush()
log_buffer.clear()
repeat += 1
if repeat >= REPEAT_COUNT:
repeat = 0
i += 1
if i >= len(data):
i = 0
time.sleep(send_delay)
except KeyboardInterrupt:
pass
except BrokenPipeError:
pass # нормально для pipe
finally:
status_clear()
print("[emu] завершён", file=sys.stderr)
return
# ===== FIFO MODE =====
pipe = None
while not stop_flag:
if pipe is None:
pipe = open_pipe(pipe_path)
if pipe is None:
break
try:
byte = data[i]
pipe.write(bytes([byte]))
log_buffer.append(byte)
total_bytes += 1
if len(log_buffer) >= OUT_BUFFER_LEN and log_file:
packet_id += 1
log_file.write(f"--- Пакет #{packet_id} ({OUT_BUFFER_LEN} байт) ---\n")
for idx, b in enumerate(log_buffer):
log_file.write(f" [{idx:02d}] = 0x{b:02x} ({b:3d})\n")
log_file.flush()
log_buffer.clear()
repeat += 1
if repeat >= REPEAT_COUNT:
repeat = 0
i += 1
if i >= len(data):
i = 0
time.sleep(send_delay)
except BrokenPipeError:
pipe.close()
pipe = None
status("[emu] pipe: отключён")
except Exception as e:
status(f"[emu] ошибка: {e}")
time.sleep(1)
status_clear()
print("[emu] завершён", file=sys.stderr)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,29 @@
[Unit]
Description=GPIO Monitor Server
After=network.target
Wants=network.target
[Service]
Type=simple
User=user
Group=user
WorkingDirectory=/home/user/temp/golang
# Запуск сервера
ExecStart=/home/user/temp/golang/build/gpio-monitor-server -pipe /tmp/gpio_pipe
# Перезапуск при падении
Restart=on-failure
RestartSec=10s
# Лимиты и безопасность
LimitNOFILE=4096
NoNewPrivileges=yes
# Логирование
StandardOutput=journal
StandardError=journal
SyslogIdentifier=gpio-monitor
[Install]
WantedBy=multi-user.target

View File

@@ -1,191 +1,177 @@
import sys #!/usr/bin/env python3
"""
Имитатор параллельной шины для gpio-interrupt (Raspberry Pi 4)
"""
import gpiod
import time import time
import ast import argparse
import sys
import signal import signal
import os from gpiod.line import Direction, Value
OUT_BUFFER_LEN = 32
RECONNECT_DELAY = 0.5
REPEAT_COUNT = 50 # повтор
DEFAULT_SEND_DELAY = 0.1 # по умолчанию (10 байт/с)
stop_flag = False
# ================= SIGNALS ================= class ParallelBusImitator:
"""Имитатор 7-битной параллельной шины данных"""
def handle_sigint(signum, frame): # Временные константы (секунды)
global stop_flag DATA_SETUP_TIME = 0.000005
stop_flag = True STROBE_WIDTH = 0.000005
HOLD_TIME = 0.000005
WR_PIN = 12 # BCM12
signal.signal(signal.SIGINT, handle_sigint) DATA_PINS_MAPPING = [
signal.signal(signal.SIGTERM, handle_sigint) (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 = []
# ================= UI ================= for wpi, bcm, desc in self.DATA_PINS_MAPPING:
if bcm is not None:
self.active_pins.append((wpi, bcm, desc))
last_status = "" self.data_pins_bcm = [bcm for _, bcm, _ in self.active_pins]
self.all_pins = self.data_pins_bcm + [self.WR_PIN]
def status(msg):
global last_status
if msg != last_status:
sys.stderr.write("\r" + msg + " " * 20)
sys.stderr.flush()
last_status = msg
def status_clear():
sys.stderr.write("\r" + " " * 80 + "\r")
sys.stderr.flush()
# ================= DATA =================
def load_array(path):
while True:
try: try:
with open(path, "r") as f: self.chip = gpiod.Chip(chip_path)
content = f.read().strip() self.lines = self.chip.request_lines(
{
data = ast.literal_eval(content) p: gpiod.LineSettings(
direction=Direction.OUTPUT,
result = [] output_value=Value.INACTIVE
for x in data: )
if isinstance(x, str): for p in self.all_pins
x = int(x, 0) },
result.append(x & 0xFF) consumer="par-bus-imitator"
)
print(f"[emu] Загружено {len(result)} байт", file=sys.stderr) except PermissionError:
return result print("Нет прав доступа к GPIO. Используйте sudo.")
sys.exit(1)
except Exception as e: except Exception as e:
print(f"[emu] Ошибка чтения: {e}", file=sys.stderr) print(f"Ошибка GPIO: {e}")
time.sleep(1) sys.exit(1)
bit_width = 4 if use_mask else len(self.data_pins_bcm)
# ================= PIPE ================= print(f"✓ Имитатор шины ({bit_width}-бит режим)")
print("Нажмите Ctrl+C для остановки\n")
def open_pipe(path): def send_byte(self, value):
while not stop_flag: """Отправка одного байта"""
try: if self.use_mask:
fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK) value &= 0x0F
status("[emu] pipe: подключен")
return os.fdopen(fd, "wb", buffering=0)
except OSError:
status("[emu] pipe: нет читателя")
time.sleep(RECONNECT_DELAY)
return None
# ================= ARGS PARSING =================
def parse_args():
"""
Разбирает аргументы командной строки.
Использование:
python3 emulator.py array.txt /tmp/gpio_pipe [задержка] [logfile|-]
Где:
задержка — float, секунд между байтами (по умолчанию DEFAULT_SEND_DELAY)
logfile — путь к файлу лога или "-" для stderr
"""
if len(sys.argv) < 3:
print("Использование: python emulator.py array.txt /tmp/gpio_pipe [задержка] [logfile|-]", file=sys.stderr)
sys.exit(1)
array_path = sys.argv[1]
pipe_path = sys.argv[2]
send_delay = DEFAULT_SEND_DELAY
log_file = None
arg_offset = 0
if len(sys.argv) > 3:
try:
send_delay = float(sys.argv[3])
arg_offset = 1 # сдвигаемся, значит logfile будет 4-м аргументом
print(f"[emu] Задержка: {send_delay} с (~{1/send_delay:.0f} байт/с)", file=sys.stderr)
except ValueError:
arg_offset = 0
# Лог-файл
log_idx = 3 + arg_offset
if len(sys.argv) > log_idx:
log_path = sys.argv[log_idx]
if log_path == "-":
log_file = sys.stderr
else: else:
log_file = open(log_path, "a") value &= 0x7F
return array_path, pipe_path, send_delay, log_file 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()
# ================= MAIN ================= def handle_signal(signum, frame):
print("\nЗавершение работы...")
sys.exit(0)
def main(): def main():
array_path, pipe_path, send_delay, log_file = parse_args() signal.signal(signal.SIGINT, handle_signal)
signal.signal(signal.SIGTERM, handle_signal)
data = load_array(array_path) parser = argparse.ArgumentParser(
description='Имитатор параллельной шины'
)
log_buffer = [] parser.add_argument('--no-mask', action='store_true',
total_bytes = 0 help='7-битный режим')
packet_id = 0 parser.add_argument('--test',
i = 0 choices=['default', 'counter', 'pattern'],
repeat = 0 default='default')
parser.add_argument('--delay', type=float, default=0.2)
parser.add_argument('--single', type=str)
parser.add_argument('--chip', default='/dev/gpiochip0')
pipe = None args = parser.parse_args()
while not stop_flag: imitator = ParallelBusImitator(
use_mask=not args.no_mask,
chip_path=args.chip
)
if pipe is None: try:
pipe = open_pipe(pipe_path) if args.single:
if pipe is None: val = int(args.single, 16)
break imitator.send_byte(val)
print()
return
try: if args.test == 'default':
byte = data[i] 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]
pipe.write(bytes([byte])) while True:
imitator.send_sequence(test_data, args.delay)
log_buffer.append(byte) except KeyboardInterrupt:
total_bytes += 1 print("\nОстановка...")
except Exception as e:
if len(log_buffer) >= OUT_BUFFER_LEN and log_file: print(f"\nОшибка: {e}")
packet_id += 1 finally:
imitator.cleanup()
log_file.write(f"--- Пакет #{packet_id} ({OUT_BUFFER_LEN} байт) ---\n") print("✓ GPIO освобождены")
for idx, b in enumerate(log_buffer):
log_file.write(f" [{idx:02d}] = 0x{b:02x} ({b:3d})\n")
log_file.flush()
log_buffer.clear()
repeat += 1
if repeat >= REPEAT_COUNT:
repeat = 0
i += 1
if i >= len(data):
i = 0
time.sleep(send_delay)
except BrokenPipeError:
pipe.close()
pipe = None
status("[emu] pipe: отключён")
except Exception as e:
status(f"[emu] ошибка: {e}")
time.sleep(1)
status_clear()
print("[emu] завершён", file=sys.stderr)
if __name__ == "__main__": if __name__ == "__main__":
main() main()