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?
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.
For each thread, there will be the following synchronized resources:
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()