23

I've got a Python program which is reading data from a serial port via the PySerial module. The two conditions I need to keep in mind are: I don't know how much data will arrive, and I don't know when to expect data.

Based on this I have came up with the follow code snippets:

#Code from main loop, spawning thread and waiting for data
s = serial.Serial(5, timeout=5)  # Open COM5, 5 second timeout
s.baudrate = 19200

#Code from thread reading serial data
while 1:
  tdata = s.read(500)    # Read 500 characters or 5 seconds

  if(tdata.__len__() > 0):        #If we got data
    if(self.flag_got_data is 0):  #If it's the first data we recieved, store it
      self.data = tdata        
    else:                         #if it's not the first, append the data
      self.data += tdata
      self.flag_got_data = 1

So this code will loop forever getting data off the serial port. We'll get up to 500 characters store the data, then alert the main loop by setting a flag. If no data is present we'll just go back to sleep and wait.

The code is working, but I don't like the 5s timeout. I need it because I don't know how much data to expect, but I don't like that it's waking up every 5 seconds even when no data is present.

Is there any way to check when data becomes available before doing the read? I'm thinking something like the select command in Linux.

Note: I found the inWaiting() method, but really that seems it just change my "sleep" to a poll, so that's not what I want here. I just want to sleep until data comes in, then go get it.

brasofilo
  • 23,940
  • 15
  • 86
  • 168
Mike
  • 40,613
  • 26
  • 100
  • 171

3 Answers3

22

Ok, I actually got something together that I like for this. Using a combination of read() with no timeout and the inWaiting() method:

#Modified code from main loop: 
s = serial.Serial(5)

#Modified code from thread reading the serial port
while 1:
  tdata = s.read()           # Wait forever for anything
  time.sleep(1)              # Sleep (or inWaiting() doesn't give the correct value)
  data_left = s.inWaiting()  # Get the number of characters ready to be read
  tdata += s.read(data_left) # Do the read and combine it with the first character

  ... #Rest of the code

This seems to give the results I wanted, I guess this type of functionality doesn't exist as a single method in Python

Mike
  • 40,613
  • 26
  • 100
  • 171
  • 2
    `time.sleep(1)` seems like a pretty ugly hack that adds a bunch of delay to getting your data out. – TJD Oct 22 '12 at 19:28
  • @TJD - No argument there, seems it works without the sleep as well, but the `data_left` becomes very unreliable (a string of 19 characters loops as 3, then 10, then 6 "banks" came in). So I'm still not happy with it, but it's closer to what I was thinking about. Sleep until data, then read data, then go back to sleep. – Mike Oct 22 '12 at 19:32
  • I don't see how that is "unreliable". That's exactly how the bytes are arriving as you are reading them. You might want to use `interCharTimeout`, which lets you detect when the receiver has stopped seeing any new bytes. – TJD Oct 22 '12 at 20:20
14

You can set timeout = None, then the read call will block until the requested number of bytes are there. If you want to wait until data arrives, just do a read(1) with timeout None. If you want to check data without blocking, do a read(1) with timeout zero, and check if it returns any data.

(see documentation https://pyserial.readthedocs.io/en/latest/)

Pierre Thibault
  • 1,555
  • 2
  • 16
  • 19
TJD
  • 11,349
  • 1
  • 22
  • 32
  • 1
    But I also think there is absolutely nothing wrong with using a 5s timeout and having it wakeup and immediately go back to read. It's very simple and will have no measurable overhead. – TJD Oct 22 '12 at 19:11
  • Thanks for the info, I did see the read documentation just couldn't figure out how to make it do what I wanted. I agree having a loop with a timeout isn't terribly costly on a desktop machine, but (coming from an embedded background) it feels so wrong to me to poll – Mike Oct 22 '12 at 19:21
  • Better solution in my opinion (at least for my needs). Thanks for sharing. – Marcel Mar 21 '16 at 17:13
2
def cmd(cmd,serial):
    out='';prev='101001011'
    serial.flushInput();serial.flushOutput()
    serial.write(cmd+'\r');
    while True:
        out+= str(serial.read(1))
        if prev == out: return out
        prev=out
    return out

call it like this:

cmd('ATZ',serial.Serial('/dev/ttyUSB0', timeout=1, baudrate=115000))