pyserial - 如何读取从一个串行设备发送的最后一行信息

22 人关注

我有一个连接到我的电脑的Arduino,运行一个循环,每隔100毫秒通过串行端口向电脑发送一个值。

我想做一个Python脚本,每隔几秒钟才从串口读取一次,所以我想让它只看到Arduino发出的最后一件事。

你如何在Pyserial中做到这一点?

这是我试过的代码,它不起作用。它是按顺序读行的。

import serial
import time
ser = serial.Serial('com4',9600,timeout=1)
while 1:
    time.sleep(10)
    print ser.readline() #How do I get the most recent line sent from the device?
    
python
serial-port
arduino
pyserial
Greg
Greg
发布于 2009-07-08
10 个回答
Vinay Sajip
Vinay Sajip
发布于 2018-07-12
已采纳
0 人赞同

也许我误解了你的问题,但由于它是一条串行线,你必须按顺序读取从Arduino发出的所有信息 - 它将在Arduino中被缓冲起来,直到你读取它。

如果你想有一个状态显示,显示最新发送的东西--使用一个包含你问题中的代码的线程(减去睡眠),并保持最后读到的完整行作为Arduino的最新行。

更新。 mtasic 的示例代码相当好,但如果在调用 inWaiting() 时Arduino已经发送了部分行,你会得到一个截断的行。相反,你要做的是把最后的 complete 行到 last_received 中,并将部分行保留在 buffer 中,以便它可以被追加到下一次的循环中。就像这样。

def receiving(ser):
    global last_received
    buffer_string = ''
    while True:
        buffer_string = buffer_string + ser.read(ser.inWaiting())
        if '\n' in buffer_string:
            lines = buffer_string.split('\n') # Guaranteed to have at least 2 entries
            last_received = lines[-2]
            #If the Arduino sends lots of empty lines, you'll lose the
            #last filled line, so you could make the above statement conditional
            #like so: if lines[-2]: last_received = lines[-2]
            buffer_string = lines[-1]

关于readline()的使用。以下是Pyserial文档中的内容(为清晰起见略加编辑,并提到了readlines())。

使用 "readline "时要小心。做到 指定一个超时时间,当打开 时指定一个超时,否则,如果没有换行字符,它可能会永远阻塞 如果没有收到换行符 收到换行字符,它可能会永远阻塞。还要注意,"readlines()" 只有在超时的情况下才能工作。它 依赖于有一个超时并 将其解释为EOF(文件结束)。

这在我看来是很合理的!

mtasic85
mtasic85
发布于 2018-07-12
0 人赞同
from serial import *
from threading import Thread
last_received = ''
def receiving(ser):
    global last_received
    buffer = ''
    while True:
        # last_received = ser.readline()
        buffer += ser.read(ser.inWaiting())
        if '\n' in buffer:
            last_received, buffer = buffer.split('\n')[-2:]
if __name__ ==  '__main__':
    ser = Serial(
        port=None,
        baudrate=9600,
        bytesize=EIGHTBITS,
        parity=PARITY_NONE,
        stopbits=STOPBITS_ONE,
        timeout=0.1,
        xonxoff=0,
        rtscts=0,
        interCharTimeout=None
    Thread(target=receiving, args=(ser,)).start()
    
好吧,那是读出了接收缓冲区中的总和。我的印象是提问者用换行来限定arduino发送的内容,所以它可能不符合接收缓冲区的大小。
Greg
那么last_received将永远有我需要的东西? 有办法用readline来做吗?
请看我更新的答案, mtasic 的代码看起来不错,除了我认为的一个小故障。
你的更新几乎是正确的。如果缓冲区以换行结束,它就会设置一个空行。请看我进一步的答案更新。
非常感谢你指出这一点,实际上这是你的答案;)
user3524946
user3524946
发布于 2018-07-12
0 人赞同

你可以使用 ser.flushInput() 来冲掉当前在缓冲区中的所有串行数据。

在清除旧数据后,你可以用ser.readline()从串行设备中获得最新的数据。

我认为这比这里提出的其他解决方案要简单一些。对我来说是有效的,希望它适合你。

Rufus
Rufus
发布于 2018-07-12
0 人赞同

这些解决方案将在等待字符时占用CPU。

你应该至少对read(1)做一次阻塞性调用。

while True:
    if '\n' in buffer: 
        pass # skip if a line already in buffer
    else:
        buffer += ser.read(1)  # this will block until one more char or timeout
    buffer += ser.read(ser.inWaiting()) # get remaining buffered chars

......和以前一样做分割的事情。

fja0568
fja0568
发布于 2018-07-12
0 人赞同

这种方法允许你分别控制收集每行所有数据的超时,以及等待额外行的不同超时。

# get the last line from serial port
lines = serial_com()
lines[-1]              
def serial_com():
    '''Serial communications: get a response'''
    # open serial port
        serial_port = serial.Serial(com_port, baudrate=115200, timeout=1)
    except serial.SerialException as e:
        print("could not open serial port '{}': {}".format(com_port, e))
    # read response from serial port
    lines = []
    while True:
        line = serial_port.readline()
        lines.append(line.decode('utf-8').rstrip())
        # wait for new data after each line
        timeout = time.time() + 0.1
        while not serial_port.inWaiting() and timeout > time.time():
        if not serial_port.inWaiting():
            break 
    #close the serial port
    serial_port.close()   
    return lines
    
谢谢你的建议。我找不到任何其他答案,可以实际返回串行响应中的所有行。
quamrana
quamrana
发布于 2018-07-12
0 人赞同

你将需要一个循环来读取所有发送的信息,最后一次调用readline()的时候会阻塞,直到超时。 所以。

def readLastLine(ser):
    last_data=''
    while True:
        data=ser.readline()
        if data!='':
            last_data=data
        else:
            return last_data
    
Crazy Joe Malloy
Crazy Joe Malloy
发布于 2018-07-12
0 人赞同

Slight modification to mtasic & Vinay Sajip's code:

虽然我发现这段代码对我的一个类似应用很有帮助,但我需要 所有 the lines coming back from a serial device that would send information periodic所有y.

我选择将第一个元素从顶部弹出,将其记录下来,然后将剩余的元素重新连接起来,作为新的缓冲区,并从那里继续。

我意识到这是 not 格雷格要求的是什么,但我认为这值得作为一个侧重点分享。

def receiving(ser):
    global last_received
    buffer = ''
    while True:
        buffer = buffer + ser.read(ser.inWaiting())
        if '\n' in buffer:
            lines = buffer.split('\n')
            last_received = lines.pop(0)
            buffer = '\n'.join(lines)
    
Srinath
Srinath
发布于 2018-07-12
0 人赞同

在一个无限循环内使用 .inWaiting() 可能会有问题。它可能占用了整个 CPU 取决于实现方式。相反,我建议使用一个特定大小的数据来读取。因此,在这种情况下,例如应该做以下事情。

ser.read(1024)
    
LXSoft
LXSoft
发布于 2018-07-12
0 人赞同

太多的复杂情况

通过换行或其他数组操作来分割字节对象的原因是什么? 我写了一个最简单的方法,它可以解决你的问题。

import serial
s = serial.Serial(31)
s.write(bytes("ATI\r\n", "utf-8"));
while True:
    last = ''
    for byte in s.read(s.inWaiting()): last += chr(byte)
    if len(last) > 0:
        # Do whatever you want with last
        print (bytes(last, "utf-8"))
        last = ''
    
bdoubleu
bdoubleu
发布于 2018-07-12
0 人赞同

下面是一个使用包装器的例子,它允许你在没有100%的CPU的情况下读取最近的一行

class ReadLine:
    pyserial object wrapper for reading line
    source: https://github.com/pyserial/pyserial/issues/216
    def __init__(self, s):
        self.buf = bytearray()
        self.s = s
    def readline(self):
        i = self.buf.find(b"\n")
        if i >= 0:
            r = self.buf[:i + 1]
            self.buf = self.buf[i + 1:]
            return r
        while True:
            i = max(1, min(2048, self.s.in_waiting))
            data = self.s.read(i)
            i = data.find(b"\n")
            if i >= 0:
                r = self.buf + data[:i + 1]
                self.buf[0:] = data[i + 1:]
                return r
            else:
                self.buf.extend(data)