Linux环境下Python pySerial中USB重连ttyUSB端口切换的解决方法
这确实是Linux下USB转串口设备常见的痛点——系统会按设备插入顺序分配ttyUSB*编号,只要旧的设备节点没被正确释放(比如程序还持有连接),重新插拔就会用下一个编号。你的翻转切换方案能工作,但确实不够灵活,尤其是如果有多个USB串口设备时容易出错。下面给你几个更优雅的解决方案,按推荐程度排序:
1. 给USB串口设备绑定固定别名(最推荐,一劳永逸)
从系统层面解决问题,给你的USB转串口适配器分配一个固定的、有意义的设备名(比如/dev/my_serial),不管插拔多少次都不会变。
操作步骤:
- 插入你的USB适配器,运行命令查看设备的唯一标识:
你会得到类似udevadm info --attribute-walk --name=/dev/ttyUSB0 | grep -E "(idVendor|idProduct)"ATTRS{idVendor}=="1a86"和ATTRS{idProduct}=="7523"的输出(这是CH340芯片的典型值,你的设备可能不同)。 - 创建udev规则文件:
sudo nano /etc/udev/rules.d/99-usb-serial.rules - 写入以下规则(替换成你刚才拿到的
idVendor和idProduct值):SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="my_serial" - 重载udev规则让其生效:
sudo udevadm control --reload-rules && sudo udevadm trigger
之后你的设备会一直有一个别名/dev/my_serial,程序里直接用这个路径即可,完全不用管ttyUSB0/ttyUSB1的问题。对应的Python代码示例:
import serial import time FIXED_PORT = "/dev/my_serial" BAUD_RATE = 9600 def get_serial_connection(): try: ser = serial.Serial(FIXED_PORT, BAUD_RATE, timeout=1) print(f"成功连接到 {FIXED_PORT}") return ser except serial.SerialException as e: print(f"连接失败: {str(e)}") return None ser = get_serial_connection() while True: if ser and ser.is_open: try: # 这里写你的串口业务逻辑 received = ser.readline() if received: print(f"收到数据: {received.decode().strip()}") except serial.SerialException: print("连接断开,尝试重连...") ser.close() ser = None else: ser = get_serial_connection() time.sleep(1)
2. 程序中动态扫描并匹配目标设备
如果不想修改系统配置,可以在程序里主动扫描所有可用串口,通过设备的描述、厂商ID等信息匹配你的目标适配器,而不是硬编码端口编号。
需要用到pyserial的list_ports工具(如果没装的话,先跑pip install pyserial):
import serial from serial.tools import list_ports import time BAUD_RATE = 9600 def find_target_serial_port(): # 扫描所有可用的串口设备 all_ports = list_ports.comports() for port in all_ports: # 可以根据设备描述、厂商ID、产品ID来匹配 # 比如我的设备描述是"USB Serial Device",你可以改成你设备的描述 if "USB Serial Device" in port.description: print(f"找到目标设备: {port.device}") return port.device # 没找到返回None print("未找到目标串口设备") return None def connect_to_port(port): try: ser = serial.Serial(port, BAUD_RATE, timeout=1) return ser except serial.SerialException as e: print(f"连接到 {port} 失败: {str(e)}") return None ser = None target_port = find_target_serial_port() while True: if not ser or not ser.is_open: if not target_port: target_port = find_target_serial_port() else: ser = connect_to_port(target_port) if not ser: # 连接失败,重新扫描端口 target_port = find_target_serial_port() else: try: # 串口操作逻辑 data = ser.readline() if data: print(f"收到: {data.decode().strip()}") except serial.SerialException: print("连接中断,关闭旧连接并重连") ser.close() ser = None target_port = None time.sleep(1)
3. 优化你的翻转切换逻辑(临时过渡方案)
如果暂时想用你原来的思路,一定要确保在连接失败时关闭旧的串口对象,避免占用端口导致系统分配新编号:
import serial import time available_ports = ["/dev/ttyUSB0", "/dev/ttyUSB1"] current_port_index = 0 ser = None def attempt_connection(port): global ser # 先关闭可能存在的旧连接 if ser and ser.is_open: try: ser.close() except Exception: pass try: ser = serial.Serial(port, 9600, timeout=1) print(f"已连接到 {port}") return True except serial.SerialException as e: print(f"连接 {port} 失败: {str(e)}") return False # 初始尝试连接 attempt_connection(available_ports[current_port_index]) while True: if ser and ser.is_open: try: # 你的串口操作 data = ser.readline() if data: print(f"收到数据: {data.decode().strip()}") except serial.SerialException: print("连接出错,准备切换端口") ser.close() ser = None current_port_index = (current_port_index + 1) % len(available_ports) else: # 尝试连接当前端口,失败则切换 if not attempt_connection(available_ports[current_port_index]): current_port_index = (current_port_index + 1) % len(available_ports) time.sleep(1)
总结一下,最推荐的是第一种方案,从系统层面绑定固定别名,彻底解决端口编号变动的问题;第二种方案适合不能修改系统配置的场景,动态识别设备也很灵活;第三种只是优化了你原来的思路,作为临时过渡可以用,但长期来看还是前两种更可靠。
内容的提问来源于stack exchange,提问作者secarica
相关产品推荐
相关产品推荐

