Files
banban/tools/read_usb_log.py
T

128 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Read a USB CDC tty on macOS using only Python's standard library."""
import argparse
import codecs
from datetime import datetime
import glob
import os
from pathlib import Path
import select
import sys
import termios
import time
import tty
def available_ports():
return sorted(set(glob.glob('/dev/cu.usbmodem*') + glob.glob('/dev/cu.usbserial*')))
def open_port(path):
fd = os.open(path, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
try:
saved = termios.tcgetattr(fd)
tty.setraw(fd, termios.TCSANOW)
config = termios.tcgetattr(fd)
config[2] |= termios.CLOCAL | termios.CREAD
config[2] &= ~(termios.PARENB | termios.CSTOPB | termios.CSIZE)
config[2] |= termios.CS8
if hasattr(termios, 'CRTSCTS'):
config[2] &= ~termios.CRTSCTS
config[4] = config[5] = termios.B115200
termios.tcsetattr(fd, termios.TCSANOW, config)
return fd, saved
except BaseException:
os.close(fd)
raise
def read_session(path, output, diag_only=False):
fd, saved = open_port(path)
decoder = codecs.getincrementaldecoder('utf-8')(errors='replace')
pending = ''
def emit(line):
line = line.rstrip('\r')
if not line or (diag_only and '[现场诊断]' not in line and '[USB日志]' not in line):
return
stamped = f'{datetime.now().astimezone().isoformat(timespec="milliseconds")} {line}\n'
output.write(stamped)
output.flush()
sys.stdout.write(stamped)
sys.stdout.flush()
print(f'已连接 {path},UTF-8 / 115200 8N1;Ctrl+C 结束。', file=sys.stderr)
try:
while True:
readable, _, _ = select.select([fd], [], [], 1)
if not readable:
continue
try:
block = os.read(fd, 4096)
except BlockingIOError:
continue
if not block:
raise OSError('USB端口已关闭')
pending += decoder.decode(block)
while '\n' in pending:
line, pending = pending.split('\n', 1)
emit(line)
if len(pending) > 8192:
emit(pending + ' [未换行,分段保存]')
pending = ''
finally:
pending += decoder.decode(b'', final=True)
if pending:
emit(pending)
try:
termios.tcsetattr(fd, termios.TCSANOW, saved)
except termios.error:
pass
os.close(fd)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--list', action='store_true', help='列出 USB 串口,不打开设备')
parser.add_argument('--port', help='例如 /dev/cu.usbmodemXXXX;仅一个USB串口时可省略')
parser.add_argument('--output', type=Path, help='日志路径;默认 usb-logs/banban-时间.log')
parser.add_argument('--diag-only', action='store_true', help='只保存现场诊断与USB状态行')
parser.add_argument('--reconnect', action='store_true', help='拔插后等待同名端口重连;端口改名需重新指定')
args = parser.parse_args()
ports = available_ports()
if args.list:
print('\n'.join(ports) if ports else '未发现 USB 串口;检查数据线和设备枚举。')
return 0
path = args.port
if not path:
if len(ports) != 1:
print('请用 --port 指定设备 CDC 口;先运行 --list 并对比拔插前后的列表。', file=sys.stderr)
for port in ports:
print(port, file=sys.stderr)
return 2
path = ports[0]
destination = args.output or Path('usb-logs') / f'banban-{datetime.now():%Y%m%d-%H%M%S}.log'
destination.parent.mkdir(parents=True, exist_ok=True)
print(f'日志保存到 {destination.resolve()};时间戳是电脑接收时间。', file=sys.stderr)
try:
with destination.open('a', encoding='utf-8') as output:
while True:
try:
read_session(path, output, args.diag_only)
except (OSError, termios.error) as error:
notice = f'[电脑读取] {datetime.now().astimezone().isoformat(timespec="milliseconds")} 连接中断:{error}'
print(notice, file=sys.stderr)
output.write(notice + '\n')
output.flush()
if not args.reconnect:
return 1
print('等待同名USB端口重连……', file=sys.stderr)
time.sleep(1)
except KeyboardInterrupt:
print('\n已停止,日志已保存。', file=sys.stderr)
return 0
if __name__ == '__main__':
raise SystemExit(main())