23

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?
Seanny123
  • 8,776
  • 13
  • 68
  • 124
Greg
  • 45,306
  • 89
  • 231
  • 297

10 Answers10

29

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!

Seanny123
  • 8,776
  • 13
  • 68
  • 124
Vinay Sajip
  • 95,872
  • 14
  • 179
  • 191
11
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()
mtasic85
  • 3,905
  • 2
  • 19
  • 28
  • 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. – JosefAssad Jul 07 '09 at 17:32
  • So last_received will always have what I need? Is there a way to do it with readline? – Greg Jul 07 '09 at 17:34
  • See my updated answer, `mtasic`'s code looks good apart from what I think is one little glitch. – Vinay Sajip Jul 07 '09 at 17:53
  • Your update is almost right. It sets a blank line if the buffer ends in a newline. See my further answer update. – Vinay Sajip Jul 07 '09 at 18:06
  • thanks so much for pointing it out, actually it is your answer ;) – mtasic85 Jul 07 '09 at 18:08
  • 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. – Great Turtle Jul 07 '09 at 19:30
  • 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')` – drc Mar 13 '20 at 22:46
11

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.

Cezar
  • 55,636
  • 19
  • 86
  • 87
user3524946
  • 111
  • 1
  • 2
  • yeah for some reason .flush() doesnt work for this purpose but flushInput works like a charm. Im sure theres an explanation in the docs or something – Brett Young Dec 19 '22 at 21:38
7

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.

Rufus
  • 71
  • 1
  • 2
3

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
fja0568
  • 53
  • 4
  • Thanks for this. I could not find any other answer that would actually return all lines from a serial response. – Timmay Aug 07 '17 at 20:35
2

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
quamrana
  • 37,849
  • 12
  • 53
  • 71
2

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)
Crazy Joe Malloy
  • 834
  • 6
  • 14
2

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 = ''
LXSoft
  • 587
  • 5
  • 25
2

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)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Srinath
  • 1,215
  • 2
  • 9
  • 9
1

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())
bdoubleu
  • 5,568
  • 2
  • 20
  • 53