问题
I have an MCU connected to the computer through a serial interface. The MCU might send data at regular intervals or very seldom depending on the type of sensor connected to it. So I want to have a python function that gets called whenever there is data incoming from the serial port instead of polling all the time.
After reading a lot of similar questions (Small Example for pyserial using Threading. PySerial/Arduino, PySerial/interrupt mode, Python Serial listener, and so on), I came to the conclusion that the solution to this is threading. So I came up with 3 different codes that work:
First:
import time
import serial
import threading
ser = serial.Serial("/dev/ttyUSB0", 19200)
datos = ""
class SerialReaderThread(threading.Thread):
'''
The class with the method that reads the serial port in the backgroud.
'''
def __init__(self):
super().__init__()
self._stop_event = threading.Event()
def run(self):
'''
The method that actually gets data from the port
'''
global ser, datos
while not self.stopped():
datos = ser.readline().decode('ascii').strip()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
serial_thread = SerialReaderThread()
serial_thread.start()
i = 0
while i < 5:
if datos != "":
print(datos)
datos = ""
i += 1
serial_thread.stop()
while serial_thread.isAlive():
pass
print("Thread stopped.")
ser.close()
Second:
import serial
import threading
import time
ser = serial.Serial("/dev/ttyUSB0", 19200)
read = True
datos = ""
def serialEvent():
global ser, read, datos
while read is True:
datos = ser.read_until().decode('ascii').strip()
return
t = threading.Thread(target=serialEvent)
t.start()
i = 0
while i < 5:
if datos != "":
print(datos)
datos = ""
i += 1
read = False
t.join()
while t.isAlive():
pass
print("Thread stopped.")
ser.close()
Third:
import serial
import concurrent
ser = serial.Serial("/dev/ttyUSB0", 19200)
datos = ""
readData = True
def serialReadEvent():
global ser, readData, datos
while readData is True:
datos = ser.read_until().decode('ascii').strip()
return
executor = concurrent.futures.ThreadPoolExecutor()
serialData = executor.submit( serialReadEvent )
i = 0
while i < 5:
if datos != "":
print(datos)
datos = ""
i += 1
readData = False
while serialData.running():
pass
print('Thread stopped.')
ser.close()
Question 1: is any of those codes better than the others?
Question 2: is using a global variable the best way to pass data between the thread and the main process?
I've also read that the PySerial API provides a way to work with threads, but I don't understand the documentation.
Question 3: can anybody give me an example of a reading thread using PySerial API?
Finally, I've read that Qt Serial Port also provides a way to process incoming data in a thread (example).
Question 4: is this the best way to solve my problem if I'm going to have a GUI written in PyQt5 as well?
回答1:
You got to the correct conclusion that the solution to this is Threading. But I would recommend to make maximum use of APIs of the framework/library you are using.
So in light of what you are trying to achieve, there is an API in pySerial in_waiting which return the number of bytes in receive buffer.
Making use of this, you can fire up a thread which will continuously monitor the underlying receive buffer and only read when your desired number of bytes are present in the buffer.
To share the received data between the Serial Read Thread and main, the better way to do is to make use of queues. Based on FIFO principle, your Serial Read Thread will be the one responsible for the enqueue operation only and main will be responsible for dequeue operation only. There won't be a case of data duplication etc. In main, you can decide how many bytes must be in this queue and only then you can dequeue.
This should work well with your GUI written in PyQt5.
来源:https://stackoverflow.com/questions/62836625/python-serial-port-event