Search code examples
pythongpspyserialuart

Large delay when reading multiple serial devices using pyserial


I'm trying to read data from more than one serial device using pyserial with the idea of syncing everything together. At the end I would like the code to:

- read serial from laser
- read serial from gps
- get a single string with [gps_reading, laser_reading]

The GPS has a refresh rate of up to 5hz The Laser sends values on demand up to around 20 hz

In isolation, they all work fine and I get fast response time. However, when I try to read from more than one I get a delay which increases over time.

The code is as follows:

#!/usr/bin/env python
# -*- coding: utf-8 -*- 
import serial
import time
import gps
import laser

#serial
def serialGeneric(device, baudRate):
    ser = serial.Serial(
    port=device,
    baudrate=baudRate,
    parity=serial.PARITY_NONE,
    stopbits=serial.STOPBITS_ONE,
    bytesize=serial.EIGHTBITS,
    )
    return ser

#Device 1
gpsSerial = serialGeneric("/dev/ttyUSB0",9600)
gps.gps_init(gpsSerial)

#Device 2
laserSerial = serialGeneric("/dev/ttyUSB1",19200)

i = 1
start_time = time.time()

while i<50:
    dis = laser.lrf_getDistance(laserSerial)
    print dis
    pos = gps.gps_getData(gpsSerial)
    print pos

    i+=1


print("--- %s seconds ---" % (time.time() - start_time))

gps and laser functions simply send the appropriate command to request data: i.e.

#!/usr/bin/env python
# -*- coding: utf-8 -*- 
import serial

def lrf_getDistance(ser):
    i = 0
    while i == 0:
        ser.write("d\r\n")
        ser.flush()
        msg = ser.readline()
        try:
            msg = float(msg)
            i == 1
            return msg
        except ValueError:
            pass 

When running the code, if I comment out 'pos = gps.gps_getData(gpsSerial)' and 'print pos' the "laser" device output is almost immediate. After uncommenting the "laser" output is extremely laggy.

In case it's relevant, I'm running the code on a desktop machine.

Can anyone please suggest how can I get rid of the lag?

  • EDIT: I have changed the code to run both function in multiple threads. Following tutorial from tutorialspoint on multithreading in python.

The new code is as follows:

#!/usr/bin/env python
# -*- coding: utf-8 -*- 

import serial
import time
import threading
import gps
import laser

#serial
def serialGeneric(device, baudRate):
    ser = serial.Serial(
    port=device,
    baudrate=baudRate,
    parity=serial.PARITY_NONE,
    stopbits=serial.STOPBITS_ONE,
    bytesize=serial.EIGHTBITS
    #timeout=0
    )
    return ser

#Device 1
gpsSerial = serialGeneric("/dev/ttyUSB0",9600)

#Device 2
laserSerial = serialGeneric("/dev/ttyUSB1",19200)

class myThreadGPS (threading.Thread):
    def __init__(self, ser):
        threading.Thread.__init__(self)
        self.ser = ser
    def run(self):
        print "Starting GPS"
        gps.gps_getDataINF(self.ser)

class myThreadLAS (threading.Thread):
    def __init__(self, ser):
        threading.Thread.__init__(self)
        self.ser = ser
    def run(self):
        print "Starting Laser"
        laser.lrf_getDistanceINF(self.ser)
# Create new threads
thread1 = myThreadGPS(gpsSerial)
thread2 = myThreadLAS(laserSerial)

# Start new Threads
thread1.start()
thread2.start()

As mentioned in the comments, this "solved" the issue at hand. Unfortunately, I still don't really understand why this was needed.


Solution

  • For each thread, there will be the following synchronized resources:

    • An event indicating when the cycle was ended, and the data received
    • A shared variable, where the data to be printed is stored
    • Another event indicating when can be started a new cycle. Each thread has to wait for this flag before beginning the cycle, and will be raised when both threads end their respective tasks.

    I didn't check thoroughly the syntax of the following code, so there may some syntax errors. Basically, the threads synchronize with the main routine when they both read the serial port. When the main routine allows to start a new cycle, they read the ports in parallel once again.

    class myThreadGPS (threading.Thread):
        def __init__(self, ser, start_event, end_event, pos):
            threading.Thread.__init__(self)
            self.ser = ser
            self.start_event = start_event
            self.end_event = end_event
            self.pos = pos
        def run(self):
            self.start_event.wait()
            self.start_event.clear()
            print "Starting GPS"
            self.pos[0] = gps.gps_getDataINF(self.ser)
            self.end_event.set()
    
    class myThreadLAS (threading.Thread):
        def __init__(self, ser, start_event, end_event, dis):
            threading.Thread.__init__(self)
            self.ser = ser
            self.start_event = start_event
            self.end_event = end_event
            self.dis = dis
        def run(self):
            self.start_event.wait()
            self.start_event.clear()
            print "Starting Laser"
            self.dis[0] = laser.lrf_getDistanceINF(self.ser)
            self.end_event.set()
    
    #Declare the used events
    gps_end_event = threading.Event()
    laser_end_event = threading.Event()
    gps_start_event = threading.Event()
    laser_start_event = threading.Event()
    #Initialize shared variables
    pos = [None]
    dis = [None]
    # Create new threads
    thread1 = myThreadGPS(gpsSerial, gps_start_event, gps_end_event, pos)
    thread2 = myThreadLAS(laserSerial, laser_start_event, laser_end_event, dis)
    
    # Start new Threads
    thread1.start()
    thread2.start()
    #Start events initially set to True
    gps_start_event.set()
    laser_start_event.set()
    while True:
        #Wait for both threads to end and reset them.
        gps_end_event.wait()
        gps_end_event.clear()
        laser_end_event.wait()
        laser_end_event.clear()
        #print the shared variables
        print pos[0]
        print dis[0]
        gps_start_event.set()
        laser_start_event.set()