By Greg


2009-07-07 17:16:43 8 Comments

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?

10 comments

@bdoubleu 2018-07-12 10:40:03

Here's an example using a wrapper that allows you to read the most recent line without 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)

s = serial.Serial('/dev/ttyS0')
device = ReadLine(s)
while True:
    print(device.readline())

@Vinay Sajip 2009-07-07 17:28:01

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!

@fja0568 2013-03-22 13:56:34

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

@Timmay 2017-08-07 20:35:32

Thanks for this. I could not find any other answer that would actually return all lines from a serial response.

@LXSoft 2014-11-04 18:35:33

Too much complications

What is the reason to split the bytes object by newline or by other array manipulations? I write the simplest method, which will solve your problem:

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 = ''

@user3524946 2014-04-11 19:35:53

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.

@Srinath 2011-06-09 22:59:22

Using .inWaiting() inside an infinite loop may be problematic. It may hog up the entire CPU depending on the implementation. Instead, I would recommend using a specific size of data to be read. So in this case the following should be done for example:

ser.read(1024)

@Rufus 2010-10-22 18:53:23

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.

@Crazy Joe Malloy 2009-07-17 18:40:17

Slight modification to mtasic & Vinay Sajip's code:

While I found this code quite helpful to me for a similar application, I needed all the lines coming back from a serial device that would send information periodically.

I opted to pop the first element off the top, record it, and then rejoin the remaining elements as the new buffer and continue from there.

I realize that this is not what Greg was asking for, but I thought it was worth sharing as a side note.

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)

@mtasic85 2009-07-07 17:27:56

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

@JosefAssad 2009-07-07 17:32:53

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 2009-07-07 17:34:17

So last_received will always have what I need? Is there a way to do it with readline?

@Vinay Sajip 2009-07-07 17:53:57

See my updated answer, mtasic's code looks good apart from what I think is one little glitch.

@Vinay Sajip 2009-07-07 18:06:23

Your update is almost right. It sets a blank line if the buffer ends in a newline. See my further answer update.

@mtasic85 2009-07-07 18:08:50

thanks so much for pointing it out, actually it is your answer ;)

@Great Turtle 2009-07-07 19:30:53

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.

@drc 2020-03-13 22:46:04

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

@quamrana 2009-07-07 17:44:05

You will need a loop to read everything sent, with the last call to readline() blocking until the timeout. So:

def readLastLine(ser):
    last_data=''
    while True:
        data=ser.readline()
        if data!='':
            last_data=data
        else:
            return last_data

Related Questions

Sponsored Content

10 Answered Questions

22 Answered Questions

[SOLVED] How do you read from stdin?

  • 2009-09-20 05:48:07
  • tehryan
  • 1708529 View
  • 1514 Score
  • 22 Answer
  • Tags:   python stdin

28 Answered Questions

[SOLVED] How to read a file line-by-line into a list?

2 Answered Questions

[SOLVED] PySerial. Unable to make any input during serial reading

1 Answered Questions

[SOLVED] How to read and write from a COM Port using PySerial?

  • 2017-05-18 20:02:06
  • Neil Dey
  • 58754 View
  • 9 Score
  • 1 Answer
  • Tags:   python pyserial

6 Answered Questions

[SOLVED] How to check if device is connected Pyserial

5 Answered Questions

[SOLVED] PySerial [Error 5] Access is Denied

1 Answered Questions

[SOLVED] Pyserial using default system serial configuration

Sponsored Content