pyserial - How to read the last line sent from a serial device

100,689

Solution 1

Perhaps I'm misunderstanding your question, but as it's a serial line, you'll have to read everything sent from the Arduino sequentially - it'll be buffered up in the Arduino until you read it.

If you want to have a status display which shows the latest thing sent - use a thread which incorporates the code in your question (minus the sleep), and keep the last complete line read as the latest line from the Arduino.

Update: mtasic's example code is quite good, but if the Arduino has sent a partial line when inWaiting() is called, you'll get a truncated line. Instead, what you want to do is to put the last complete line into last_received, and keep the partial line in buffer so that it can be appended to the next time round the loop. Something like this:

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]

Regarding use of readline(): Here's what the Pyserial documentation has to say (slightly edited for clarity and with a mention to readlines()):

Be careful when using "readline". Do specify a timeout when opening the serial port, otherwise it could block forever if no newline character is received. Also note that "readlines()" only works with a timeout. It depends on having a timeout and interprets that as EOF (end of file).

which seems quite reasonable to me!

Solution 2

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()

Solution 3

You can use ser.flushInput() to flush out all serial data that is currently in the buffer.

After clearing out the old data, you can user ser.readline() to get the most recent data from the serial device.

I think its a bit simpler than the other proposed solutions on here. Worked for me, hope it's suitable for you.

Solution 4

These solutions will hog the CPU while waiting for characters.

You should do at least one blocking call to 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

...and do the split thing as before.

Solution 5

This method allows you to separately control the timeout for gathering all the data for each line, and a different timeout for waiting on additional lines.

# get the last line from serial port
lines = serial_com()
lines[-1]              

def serial_com():
    '''Serial communications: get a response'''

    # open serial port
    try:
        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():
            pass
        if not serial_port.inWaiting():
            break 

    #close the serial port
    serial_port.close()   
    return lines
Share:
100,689
Greg
Author by

Greg

I'm an avid programmer, web developer and electronics enthusiast. Here's my gift to Python hackers. And you can see everything I'm up to here.

Updated on April 24, 2020

Comments

  • Greg
    Greg about 4 years

    I have an Arduino connected to my computer running a loop, sending a value over the serial port back to the computer every 100 ms.

    I want to make a Python script that will read from the serial port only every few seconds, so I want it to just see the last thing sent from the Arduino.

    How do you do this in Pyserial?

    Here's the code I tried which does't work. It reads the lines sequentially.

    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?
    
  • JosefAssad
    JosefAssad almost 15 years
    Well that reads in the sum total of what's in the receive buffer. My impression is the asker is delimiting what the arduino is sending by newlines so it probably won't match the receive buffer size.
  • Greg
    Greg almost 15 years
    So last_received will always have what I need? Is there a way to do it with readline?
  • Vinay Sajip
    Vinay Sajip almost 15 years
    See my updated answer, mtasic's code looks good apart from what I think is one little glitch.
  • Vinay Sajip
    Vinay Sajip almost 15 years
    Your update is almost right. It sets a blank line if the buffer ends in a newline. See my further answer update.
  • mtasic85
    mtasic85 almost 15 years
    thanks so much for pointing it out, actually it is your answer ;)
  • andrew
    andrew almost 15 years
    The code is good, just a quick comment. interCharTimeout is missing from older versions of python. You can omit that line and it should work fine on Python 2.5 or older.
  • Timmay
    Timmay almost 7 years
    Thanks for this. I could not find any other answer that would actually return all lines from a serial response.
  • drc
    drc about 4 years
    Please note that python3 will return bytes not a string from ser.read(), so you will need to adjust your buffer to be a bytearray() and your check in the split and if condition to be b'\n' or b'\r\n'. Finally, byte arrays must be decoded to string via byte_buffer.decode('utf-8')