readline() 是串口通信中一个非常常用的函数,它的作用是从串口缓冲区中读取一行数据,直到遇到换行符(\n)为止。
准备工作:安装 pyserial 库
你需要确保已经安装了 pyserial 库,如果尚未安装,可以通过 pip 进行安装:
pip install pyserial
readline() 的基本用法
readline() 的核心逻辑是:不断读取数据,直到遇到一个换行符 \n,然后将之前读取的所有数据作为一个字符串返回。
下面是一个最简单的示例代码:
import serial
# --- 配置串口参数 ---
# 'COM3' 是 Windows 下的串口名
# '/dev/ttyUSB0' 是 Linux 下的串口名
# '/dev/tty.serialname' 是 macOS 下的串口名
SERIAL_PORT = 'COM3'
BAUD_RATE = 9600 # 波特率,必须与设备端设置一致
try:
# 1. 打开串口连接
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
print(f"成功打开串口 {ser.name}")
# 2. 写入数据(可选,用于触发设备发送数据)
# ser.write(b'SEND_DATA\n') # 注意:通常需要发送换行符
# 3. 读取一行数据
# readline() 会阻塞,直到读取到换行符或超时
line = ser.readline()
# 4. 处理读取到的数据
# readline() 返回的是 bytes 类型,需要解码为字符串
if line:
decoded_line = line.decode('utf-8').strip() # .strip() 用于去除两端的空白字符和换行符
print(f"接收到数据: {decoded_line}")
except serial.SerialException as e:
print(f"无法打开串口 {SERIAL_PORT}: {e}")
except Exception as e:
print(f"发生错误: {e}")
finally:
# 5. 关闭串口连接
if 'ser' in locals() and ser.is_open:
ser.close()
print("串口已关闭")
关键参数详解:timeout (超时)
timeout 是使用 readline() 时最重要的参数,它定义了 readline() 函数等待数据的最大时间。
-
timeout = None(默认值): 阻塞模式readline()会一直等待,直到读取到换行符。- 如果设备从不发送换行符,程序将永远卡在这里,无法执行后续代码。
- 适用于确定数据一定会以换行符结尾的场景。
-
timeout = 0: 非阻塞模式readline()会立即返回。- 如果缓冲区中没有数据或没有完整的行(没有换行符),它会返回一个空的
bytes对象 (b'')。 - 适用于需要不停轮询串口,而不希望程序被卡住的场景。
-
timeout = x(x > 0, 1): 超时模式readline()会等待最多x秒。- 如果在
x秒内读取到了换行符,则立即返回数据。 x秒后仍未读取到换行符,则返回已读取到的所有数据(即使没有换行符),或者如果什么都没读到,则返回空的bytes对象。- 这是最常用、最推荐的模式,因为它既能让程序等待数据,又能防止程序因设备故障或无数据而永久卡死。
readline() 的常见陷阱与问题
陷阱 1:换行符不匹配
这是最常见的问题。readline() 依赖换行符 \n 来识别一行的结束。
-
问题: 你的设备发送的数据格式是
Hello, World!,没有末尾的\n。 -
结果:
ser.readline()会一直等待,直到超时,如果设置了timeout=1,它将在 1 秒后返回b'Hello, World!'(没有换行符)。timeout=None,它会永久阻塞。 -
解决方案:
- 修改设备端: 让设备在发送数据后自动加上换行符
\n,这是最佳方案。 - 修改 Python 端: 使用
ser.read_until(expected=terminator),它可以自定义结束符。ser.read_until(b'\n')和ser.readline()在默认情况下行为一致。 - 不依赖换行符: 如果确实无法添加换行符,可以使用
ser.read(size)来读取固定长度的数据,但这不够灵活。
- 修改设备端: 让设备在发送数据后自动加上换行符
陷阱 2:数据不完整(缓冲区问题)
串口通信是异步的,数据可能分多次到达。
- 场景: 设备发送
12345\n,但数据分两包到达:12和345\n。 - 结果:
readline()会先读取到12,发现没有\n,于是继续等待。345\n到达,readline()将它们拼接成12345\n并返回,这个过程对用户是透明的,通常没有问题。
陷阱 3:数据丢失
- 场景:
readline()正在等待数据时,缓冲区里已经有了一些旧数据(OLD_DATA\n),此时新数据NEW_DATA\n到达,但readline()立即读取了缓冲区里的OLD_DATA\n。 - 结果: 程序处理了旧数据,而新数据可能被下一个
readline()读取,或者被后续的数据覆盖,这通常需要通过协议设计(如数据帧头)来解决。
更稳健的读取方式:read_until()
pyserial 提供了 read_until() 方法,它比 readline() 更灵活,可以指定自定义的结束符。
import serial
ser = serial.Serial('COM3', 9600, timeout=1)
# 假设你的设备使用回车符 '\r' 作为行尾
line = ser.read_until(b'\r')
print(f"接收到数据: {line.decode('utf-8').strip()}")
ser.close()
read_until() 同样受 timeout 参数影响,并且可以指定一个 max_length 参数来防止内存溢出攻击(如果对方发送一个超长的“行”)。
最佳实践总结
- 始终设置
timeout: 强烈建议设置一个合理的超时时间(如timeout=1),避免程序无响应。 - 统一换行符: 与设备开发者确认,确保数据包的结尾有明确的、一致的结束符(最好是
\n)。 - 处理返回值:
readline()返回的是bytes类型,记得使用.decode('utf-8')或其他编码(如'ascii')将其转换为字符串。 - 清理数据: 使用
.strip()去除字符串两端可能存在的\n,\r或空格。 - 使用
try...finally或with语句: 确保串口在使用完毕后一定会被关闭,防止资源泄漏。
使用 with 语句是更 Pythonic 的方式,它能自动处理资源的关闭:
import serial
SERIAL_PORT = 'COM3'
BAUD_RATE = 9600
try:
with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) as ser:
print(f"成功打开串口 {ser.name}")
while True: # 进入一个循环来持续读取数据
line = ser.readline()
if line:
print(f"接收到数据: {line.decode('utf-8').strip()}")
# 可以在这里添加其他逻辑,比如检查某个条件来退出循环
except serial.SerialException as e:
print(f"无法打开串口 {SERIAL_PORT}: {e}") 