diff --git a/scripts/imi_wire.py b/scripts/imi_wire.py index 1eb496b..51206c2 100644 --- a/scripts/imi_wire.py +++ b/scripts/imi_wire.py @@ -1,177 +1,191 @@ -#!/usr/bin/env python3 -""" -Имитатор параллельной шины для gpio-interrupt (Raspberry Pi 4) -""" - -import gpiod -import time -import argparse import sys +import time +import ast import signal -from gpiod.line import Direction, Value +import os + +OUT_BUFFER_LEN = 32 +RECONNECT_DELAY = 0.5 +REPEAT_COUNT = 50 # повтор +DEFAULT_SEND_DELAY = 0.1 # по умолчанию (10 байт/с) + +stop_flag = False -class ParallelBusImitator: - """Имитатор 7-битной параллельной шины данных""" +# ================= SIGNALS ================= - # Временные константы (секунды) - DATA_SETUP_TIME = 0.000005 - STROBE_WIDTH = 0.000005 - HOLD_TIME = 0.000005 +def handle_sigint(signum, frame): + global stop_flag + stop_flag = True - 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 (не подключён)"), - ] +signal.signal(signal.SIGINT, handle_sigint) +signal.signal(signal.SIGTERM, handle_sigint) - 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)) +# ================= UI ================= - self.data_pins_bcm = [bcm for _, bcm, _ in self.active_pins] - self.all_pins = self.data_pins_bcm + [self.WR_PIN] +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: - 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) + 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"Ошибка GPIO: {e}") - sys.exit(1) + print(f"[emu] Ошибка чтения: {e}", file=sys.stderr) + time.sleep(1) - bit_width = 4 if use_mask else len(self.data_pins_bcm) - print(f"✓ Имитатор шины ({bit_width}-бит режим)") - print("Нажмите Ctrl+C для остановки\n") +# ================= PIPE ================= - def send_byte(self, value): - """Отправка одного байта""" - if self.use_mask: - value &= 0x0F +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 /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: - value &= 0x7F + log_file = open(log_path, "a") - 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() + return array_path, pipe_path, send_delay, log_file -def handle_signal(signum, frame): - print("\nЗавершение работы...") - sys.exit(0) - +# ================= MAIN ================= def main(): - signal.signal(signal.SIGINT, handle_signal) - signal.signal(signal.SIGTERM, handle_signal) + array_path, pipe_path, send_delay, log_file = parse_args() - parser = argparse.ArgumentParser( - description='Имитатор параллельной шины' - ) + data = load_array(array_path) - 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') + log_buffer = [] + total_bytes = 0 + packet_id = 0 + i = 0 + repeat = 0 - args = parser.parse_args() + pipe = None - imitator = ParallelBusImitator( - use_mask=not args.no_mask, - chip_path=args.chip - ) + while not stop_flag: - try: - if args.single: - val = int(args.single, 16) - imitator.send_byte(val) - print() - return + if pipe is None: + pipe = open_pipe(pipe_path) + if pipe is None: + break - 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] + try: + byte = data[i] - while True: - imitator.send_sequence(test_data, args.delay) + pipe.write(bytes([byte])) - except KeyboardInterrupt: - print("\nОстановка...") - except Exception as e: - print(f"\nОшибка: {e}") - finally: - imitator.cleanup() - print("✓ GPIO освобождены") + 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() + main() \ No newline at end of file