python - 如何从串口读取完整的数据?

python - How to read data fully from serial port?

我正在尝试读取写入串行端口的 JSON 字符串,使用以下基于 PySerial 库的代码:

while True:
    if serial_port.in_waiting > 0:
        buffer = serial_port.readline()
        print('buffer=', buffer)
        ascii = buffer.decode('ascii')
        print('ascii=', ascii)

我尝试嗅探端口上的数据,并确保数据完全写入,没有任何分散:

jpnevulator --ascii --tty "/dev/ttyACM1" --read
7B 22 30 22 3A 31 7D                            {"0":1}
7B 22 30 22 3A 32 7D                            {"0":2}
7B 22 30 22 3A 33 7D                            {"0":3}
7B 22 30 22 3A 34 7D                            {"0":4}
7B 22 30 22 3A 35 7D                            {"0":5}
7B 22 30 22 3A 36 7D                            {"0":6}

但是使用的代码导致读取数据分散,因此显示如下结果:

buffer= b'{"0'
ascii= {"0
buffer= b'":1}'
ascii= ":1}
buffer= b'{"'
ascii= {"
buffer= b'0":2'
ascii= 0":2
buffer= b'}'
ascii= }

此外,当我使用 read() 而不是 readline() 时,我得到了相同的行为:

buffer= b'{'
data_str= {
buffer= b'"'
data_str= "
buffer= b'3'
data_str= 3
buffer= b'"'
data_str= "
buffer= b':'
data_str= :
buffer= b'1'
data_str= 1
buffer= b'}'
data_str= }

我什至尝试使用使用相同库的 another code,但遇到了同样的问题。

我不确定为什么会遇到这种行为。

我会尝试解决这个问题。 :) 您的循环等待任何输入可用 serial_port.in_waiting > 0。因此,您看到的行为。一旦可以获取任何内容,读取就会开始。 PySerial 似乎没有任何额外的抽象可以让你知道最后一个准备好的字节是一个 ASCII 花括号字符(我真的只是浏览了文档)。您始终可以在 Python 脚本中应用 read as stuff 的通用解决方案,并从中理解它。

先问一个问题。您的输入示例表明您将处理同样大小的字符串 / JSON?我们真的应该那么幸运吗?如果是这样,您可以等到那个或更多字节可用,然后将所需的大小读入 buffer.

否则您的代码会发生变化:

buffer = bytes()  # .read() returns bytes right?
while True:
    if serial_port.in_waiting > 0:
        buffer += serial_port.read(serial_port.in_waiting)
        try:
            complete = buffer[:buffer.index(b'}')+1]  # get up to '}'
            buffer = buffer[buffer.index(b'}')+1:]  # leave the rest in buffer
        except ValueError:
            continue  # Go back and keep reading
        print('buffer=', complete)
        ascii = buffer.decode('ascii')
        print('ascii=', ascii)

注意 1:我假设 serial_port.in_waiting 理论上可以在 if 和 read 之间改变,但我也假设未读字节只保留在缓冲区中,我们没问题。

注意 2:这种方法有点天真,没有考虑到您还可以阅读两段 JSON 代码。

注意 3:如果是这种情况,它也不考虑 JSON 中的嵌套映射。

希望它仍然有用。底线。除非处理固定大小的输入或通过任何其他方式让 pySerial 根据需要为您提供内容分块,否则您必须读取内容并在脚本中处理它。


更新:在评论中反映讨论。

你的问题确实是你只是在串口上查看字节(流)。在那个级别,对传递的数据没有任何有用的理解。您需要更高级别(应用程序或介于两者之间的层)理解传入的内容。换句话说,解析封装传输数据的协议。

事实上,如果我们知道表示 JSON 的字符串(字节串)是通过的(作为协议的服务器,通往 encapsulate/represent 数据(结构)的方式),那么可以工作,但重组需要在原始串行通信之上进行。我们的应用程序(或库/模块)可以读取原始串行数据,理解它并将它们提供给更高级别。

这是数据通信中的常见问题:什么时候收到完整的帧?

如果你能控制双方,我建议你在协议中添加额外的框架。否则,如果您收到了完整的 JSON 对象,则需要逐个字符地检查。

一个非常简单的协议是ndjson,它只需要在帧之间添加'\n',并且不要在有效负载中插入换行符。如果 JSON 字符串中有实际的换行符,如果您使用 JSON 库,它们将自动转义。

示例作者:

# Writing ndjson frames
frames = [
    {"0": "1"},
    {"0": "2"},
]
for frame in frames:
    port.write(json.dumps(frame, separators=(',', ':')))  # No extra whitespace or newlines
    port.write('\n')  # ndjson protocol separator

可以使用 pyserial.readline().

读取 ndjson 框架流

请注意,您需要指定超时才能退出程序,否则 readline() 将永远阻塞。

示例 reader 假设 port 打开超时:

while True:
    data = port.readline()  # Blocks until a complete frame is received or timeout
    if data:
        d = json.loads(data)
        print("Received object: %r" % d)
    else:
        if should_exit:
            break