#!/usr/bin/env python3 import argparse import sys import time import serial def read_response(port: serial.Serial, timeout_s: float) -> list[str]: deadline = time.monotonic() + timeout_s lines: list[str] = [] while time.monotonic() < deadline: raw = port.readline() if not raw: continue line = raw.decode(errors="replace").strip() if not line: continue lines.append(line) if line in {"OK", "ERROR"}: break return lines def send_at(port: serial.Serial, command: str, timeout_s: float) -> tuple[bool, list[str]]: port.write((command + "\r").encode()) port.flush() lines = read_response(port, timeout_s) ok = any(line == "OK" for line in lines) err = any(line == "ERROR" for line in lines) return ok and not err, lines def send_and_log(port: serial.Serial, command: str, timeout_s: float) -> tuple[bool, list[str]]: ok, lines = send_at(port, command, timeout_s) print(f"> {command}") for line in lines: print(f"< {line}") return ok, lines def main() -> int: parser = argparse.ArgumentParser(description="Control an AT modem and send DTMF tones") parser.add_argument("device", help="Serial device path, e.g. /dev/ttyACM0") parser.add_argument("--baud", type=int, default=115200, help="Serial baudrate (default: 115200)") parser.add_argument("--timeout", type=float, default=2.0, help="AT command timeout seconds") args = parser.parse_args() try: with serial.Serial(args.device, args.baud, timeout=0.2, write_timeout=1.0) as port: port.reset_input_buffer() port.reset_output_buffer() # Inicialise for cmd in ("AT", "ATZ0", "ATE1", "ATX0"): ok, lines = send_and_log(port, cmd, args.timeout) if not ok: print(f"Modem did not accept {cmd}", file=sys.stderr) return 1 send_and_log(port, "ATS6=0", args.timeout) # Set wait for dial tone time to 0 send_and_log(port, "ATS11=2", args.timeout) # set tone length to 1 ms send_and_log(port, "ATH1", args.timeout) while True: send_and_log(port, "ATDT0123456789;", args.timeout) send_and_log(port, "ATH0", args.timeout) except serial.SerialException as exc: print(f"Serial error: {exc}", file=sys.stderr) return 1 print("Done") return 0 if __name__ == "__main__": raise SystemExit(main())