1

I have an Arduino board that puts out a string like this "287, 612, 109, 1134" every second. My code tries to read this string and convert it to wind speed and wind direction. Initially it works fine but eventually on one of the readines it reads only the first number in the string which causes an error. How can I have the code get the full string and not just the first number?

import serial, time, csv, decimal

#for Mac
port = '/dev/cu.usbserial-A401316V'

ser = serial.Serial(port , baudrate=57600, bytesize=8, parity=serial.PARITY_NONE, stopbits=1, timeout=2)

while True:

    time.sleep(2)

    data = ser.readline( eol="\r\n").strip()

    data = data.split(',')

    if len(data) > 0:

        if data[0] != '2501':

            s = float( 1000 / float(data[0]) )

            print  'wind speed %.1f' %(round(float((2.5 * s)), 1))
            print 'wind direction', round((int(data[1]) - (50))  * .39, 0)
            print 'software version', data[2], '\n'             
        else:   
            print 'wind speed is zero'
            print 'wind direction', round((int(data[1]) - (50))  * .39, 0)
            print 'software version', data[2]

ser.close()
2
  • Are you sure that data read from serial port is valid (is setup of serial port on the uC-board equal to PC config)? Commented May 6, 2014 at 15:39
  • Yes the fourth number is a checksum and it validates correctly. The script runs fine for an hour or more until ser.readline only returns the first number in the string. Commented May 6, 2014 at 19:05

1 Answer 1

4

Without seeing more of the error data from the serial port, I can say that the most common reason you would see single numbers on lines (or even blank lines) is due to the non-synchronization between the Arduino program sending data to the port and your program trying to read that data. I'd wager if you print data after your data=ser.readline() and comment out the rest of the processing, you'd find lines like some of the following in the output:

252, 236, 218, 1136
251, 202, 215
2, 1353
199, 303, 200, 1000
259, 231, 245, 993
28
4, 144, 142, 1112
245, 199, 143, 1403

251, 19
2, 187, 1639
246, 235, 356, 1323

The reason for the data split across lines, or even blank lines, is that the program is trying to read data from the serial connection during or between writes. When this happens, the readline() gets what's available (even if partially/not written) and throws a \n on the end of what it found.

The fix for this is to ensure that the Arduino has finished sending data before we read, and that there is data there for us to read. A good way to handle this is with a generator that only yields lines to the main loop when they are complete (e.g. they end with a \r\n signifying the end of a write to the port).

def get_data():
    ibuffer = ""  # Buffer for raw data waiting to be processed
    while True:
        time.sleep(.1)  # Best between .1 and 1
        data = ser.read(4096)  # May need to adjust read size
        ibuffer += data  # Concat data sets that were possibly split during reading
        if '\r\n' in ibuffer:  # If complete data set
            line, ibuffer = ibuffer.split('\r\n', 1)  # Split off first completed set
            yield line.strip('\r\n')  # Sanitize and Yield data

The yield makes this function a generator you can invoke to grab the next complete set of data while keeping any thing that was split by the read() in a buffer to await the next read() where the pieces of the data set can be concatenated. Think of yield like a return, but rather than passing a value and leaving the function/loop, it passes the value and waits for next() to be called where it will pick up where it left off for the next pass through the loop. With this function, the rest of your program will look something like this:

import serial, time, csv, decimal

port = '/dev/cu.usbserial-A401316V'
ser = serial.Serial(port , baudrate=57600, bytesize=8, parity=serial.PARITY_NONE, stopbits=1, timeout=2)

def get_data():
    """
    The above function here
    """

ser_data = get_data()
while True:
    data = ser_data.next().replace(' ','').split(',')

    if len(data) > 0:
        """
        data processing down here

        """
ser.close()

Depending on your set up, you may want to throw a conditional in get_data() to break, if the serial connection is lost for example, or a try/except around the data declaration.

It's worth noting that one thing I did change aside from the aforementioned is your ser.readline(eol) to a byte sized ser.read(4096). PySerial's readline() with eol is actually deprecated with the latest versions of Python.

Hopefully this helps; or if you have more problems, hopefully it at least gives you some ideas to get on the right track.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks @MikeRixWolfe for your help! I am getting the following error because I need to use 2.5; Traceback (most recent call last): File "windspeed_.py", line 19, in <module> data = next(ser_data).replace(' ','').split(',') NameError: name 'next' is not defined
Oh, sorry, for Python 2.5 that line would be ser_data.next().replace(' ','').split(',')
Good to hear! Be sure to mark the answer as correct

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.