How to reduce time spent by 'readline()' from serial data

user1993416 :

I am trying to make a function to get the gyroscope components X,Y,Z from a sensor.

The function is the following:

def bimu_get_gyroscope_raw():
    #ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=15)
    ser = serial.Serial('/dev/tty.usbserial-00002014', 115200, timeout=15)
    ser_io = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1),  
                               newline = '\r',
                               line_buffering = True)
    try:
        ser.isOpen()
        print('serial is open')
    except:
        print('error_1')
        exit()
    #--------------------
    i = 0
    gyro_dict = dict()
    if (ser.isOpen()):
        ser.flushInput()
        # write the function to get 
        while (i==0):
            try:
                print('serial is open_1')
                line = ser_io.readline()
                print('serial is open_2')
                print(line)
            except serial.SerialException as err:
                print("Error ocurred while reading data: {}".format(err))
            if not line.endswith('\r'):
                print("Attempt to read from serial port timed out ... Exiting.")
                break  # terminate the loop and let the program exit
            if line.startswith('S,'):
                i += 1
                line = line.split(',')
                print(line)
                if len(line)==12: 
                    gyro_dict = {'x':float(line[1]), 'y': float(line[2]), 'z': float(line[3]) }
    else:
        print('Cannot open serial port')
    return gyro_dict

I get the following output:

raw = bimu_get_gyroscope_raw()
print(raw)

serial is open
serial is open_1
-43,-122,-2833,83,65
serial is open_2
serial is open_1
S,2,0,0,-20,19,1014,-146,184,-158,168,99
serial is open_2
['S', '2', '0', '0', '-20', '19', '1014', '-146', '184', '-158', '168', '99\r']
{'z': 0.0, 'y': 0.0, 'x': 2.0}

The problem I have is that between the first time I call the line line = ser_io.readline() it takes about 2.25s with a hand chronometer to write on the screen serial is open_2.
If the function needs to call again ser_io.readline() there is no delay and the lines serial is open_1 and serial is open_2 appears almost at the same time.

I think the first call to readline() does something internally with the port or with the data buffer that once is already done makes that the successive callings to readline() to run much faster.

Is there any way to solve this problem and make a function run fast at all times.

EDIT

I have tested times with time python module and modified the readline part, like this:

     while (i<=5):
        try:
            print('before readline')
            start_time = time.time()
            line = ser_io.readline()
            #print(line)
            print("--- %s seconds ---" % (time.time() - start_time))
            #print(line)
            print('after readline')
        except serial.SerialException as err:
            print("Error ocurred while reading data: {}".format(err))
        if not line.endswith('\r'):
            print("Attempt to read from serial port timed out ... Exiting.")
            break  # terminate the loop and let the program exit
        if line.startswith('S,'):
            i += 1
            line = line.split(',')
            print(line)
            if len(line)==12: 
                gyro_dict = {'x':float(line[1]), 'y': float(line[2]), 'z': float(line[3]) }

with the following result:

    serial is open
before readline
--- 2.1859400272369385 seconds ---
after readline
before readline
--- 5.9604644775390625e-06 seconds ---
after readline
['S', '0', '0', '0', '380', '0', '-902', '-497', '-228', '200', '63', '103\r']
before readline
--- 2.86102294921875e-06 seconds ---
after readline
before readline
--- 3.814697265625e-06 seconds ---
after readline
['S', '-1', '0', '1', '375', '-8', '-918', '-497', '-223', '194', '64', '108\r']
before readline
--- 3.0994415283203125e-06 seconds ---
after readline
['S', '1', '0', '2', '380', '-10', '-909', '-500', '-223', '200', '65', '113\r']
before readline
--- 2.1457672119140625e-06 seconds ---
after readline
before readline
--- 1.9073486328125e-06 seconds ---
after readline
['S', '0', '0', '0', '379', '-1', '-914', '-500', '-220', '197', '66', '69\r']
before readline
--- 2.1457672119140625e-06 seconds ---
after readline
['S', '0', '0', '-1', '374', '-5', '-902', '-500', '-225', '1\r']
before readline
--- 3.0994415283203125e-06 seconds ---
after readline
['S', '1', '1', '1', '376', '-2', '-915', '-500', '-223', '192', '37', '75\r']

The function takes more than two seconds the first iteration, the rest iterations are very fast.

Paul Cornelius :

I have a couple of suggestions for you. I write Windows applications that use a serial port and I use a different approach - I assume the principles would be the same across all OS's. I create and open the port first, at the beginning of the program, and leave it open. It's good practice to close the port before your program exists but that's not really necessary, since the OS will clean up afterwards.

But your code will create and initialize the port each time you call the function. You're not explicitly closing it when you're done; perhaps you can get away with that because the port object gets garbage collected. You are trusting the serial library to close the port properly at the OS level before you try to open it again. In any case, if there is overhead in creating the port object, why not incur it once and be done with it?

You don't need to create a TextIOWrapper at all, let alone a bi-directional one. You're wondering if it's the reason for your performance issues, so why not get rid of it? The serial port library has all the functionality you need: check out the read_until function.

I think you ought to start with a framework something like this. I can't run and test this program, so it's a schematic only. I have stripped out all the error handling code. One small issue is that serial ports operate on bytes and you have to convert that to a string.

ser = serial.Serial('/dev/tty.usbserial-00002014', 115200, timeout=15)
def bimu_get_gyroscope_raw():
    while True:
        ser.flushInput()
        b = ser.read_until('\r')
        s = str(b, encoding='latin1')  # convert to str
        if a.startswith('S,'):
            line = s.split(',')
            if len(line)==12: 
                return dict(x = float(line[1]),
                            y = float(line[2]),
                            z = float(line[3]))

I have made ser a global but you could also pass it to the function as an argument.

Keep in mind how serial ports work on a modern OS. You are never reading the characters directly from the hardware - the OS is doing that for you, and placing the characters in an input buffer. When you "read" from the port you are actually retrieving any characters from the buffer, or waiting for their arrival. What you observe - a long delay followed by a rapid succession of lines of data - could be explained by the gyroscope hardware doing nothing for several seconds, and then producing a burst of data that's more than one line long. I don't know how your gyroscope works so I can't say that this is really the case.

The PySerial implementation is actually a wrapper around a set of operating system calls. The Python overhead is very minimal, and much of it is error-handling code. I am sure you will be able to receive thousands of characters per second using Python - I do it all the time. Three seconds is close to eternity on a modern PC. There must be another explanation for it. Don't think for a moment that Python is your bottleneck.

Timing events by looking at the screen and clicking a stopwatch is clumsy. Look at the Python time package. You could simply print the value of time.time() in each of your print statements and put away your chronometer.

You can test the data gathering part of the implementation independently. Just strip out the logic to parse the data, and stay in the while loop forever. Print the data along with time stamps for each received line. If you have another instrument that talks to a serial port you can isolate the performance of the instrument from the performance of the software.

Finally, what event causes the gyroscope to make a data transmission? Is it one of those instruments that just periodically broadcasts its data, or do you have to send it some command to request the data? If the former and the broadcasts are every three seconds, the mystery is solved; likewise if it's the latter and the latency in the response is three seconds. I can imagine that some such thing might be the case, since the instrument will have to read some sensors and translate the results to a character string. You haven't shown us the whole program or told us how the instruments works, so this is just guesswork.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=5294&siteId=1