Multiple serial connections in separate dedicated threads with Tkinter using serial ReaderThread - python

I want to create and maintain multiple non-blocking serial connections with some peripherals over UART. Truthfully, this is an expansion of this question about tkinter and multithreading
Domarm suggests the following as a solution to the original question of creating a new thread to handle receiving serial data without blocking the main script. (In the code below I left out the Raw data reader class for simplicity here).
import tkinter as tk
from serial import Serial
from serial.threaded import ReaderThread, Protocol, LineReader
class SerialReaderProtocolLine(LineReader):
tk_listener = None
TERMINATOR = b'\n\r'
def connection_made(self, transport):
"""Called when reader thread is started"""
if self.tk_listener is None:
raise Exception("tk_listener must be set before connecting to the socket!")
super().connection_made(transport)
print("Connected, ready to receive data...")
def handle_line(self, line):
"""New line waiting to be processed"""
# Execute our callback in tk
self.tk_listener.after(0, self.tk_listener.on_data, line)
class MainFrame(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.listbox = tk.Listbox(self)
self.listbox.pack()
self.pack()
def on_data(self, data):
print("Called from tk Thread:", data)
self.listbox.insert(tk.END, data)
if __name__ == '__main__':
app = tk.Tk()
main_frame = MainFrame()
# Set listener to our reader
SerialReaderProtocolLine.tk_listener = main_frame
# Initiate serial port
serial_port = Serial("/dev/ttyUSB0")
# Initiate ReaderThread
reader = ReaderThread(serial_port, SerialReaderProtocolLine)
# Start reader
reader.start()
app.mainloop()
This solution works well for a single connection, but what if I want to expand this so I can manage multiple connections at once?
Since this code structure is using the SerialReaderProtocolLine class variable tk_listener, I am unsure of how to go about making this modular so that I can create multiple ReaderThreads with each their own listeners.
Here is an example of me trying to move the class variable tk_listener into a constructor to allow for the creation of a new instance of SerialReaderProtocolLine, which I then try to pass into a new ReaderThread. From that, I get a type error, "TypeError: 'SerialReaderProtocolLine' object is not callable". This error is thrown when I try to pass an instance of SerialReaderProtocolLine to ReaderThread.
import tkinter as tk
from serial import Serial
from serial.threaded import ReaderThread, LineReader
class SerialReaderProtocolLine(LineReader):
def __init__(self, listener, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tk_listener = listener
self.TERMINATOR = b'\n\r'
def connection_made(self, transport):
"""Called when reader thread is started"""
if self.tk_listener is None:
raise Exception("tk_listener must be set before connecting to the socket!")
super().connection_made(transport)
print("Connected, ready to receive data...")
def handle_line(self, line):
"""New line waiting to be processed"""
# Execute our callback in tk
self.tk_listener.after(0, self.tk_listener.on_data, line)
class MainFrame(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.listbox = tk.Listbox(self)
self.listbox.pack()
self.pack()
def on_data(self, data):
print("Called from tk Thread:", data)
self.listbox.insert(tk.END, data)
if __name__ == '__main__':
app = tk.Tk()
main_frame1 = MainFrame()
main_frame2 = MainFrame()
# Set listener to our reader
reader_listener1 = SerialReaderProtocolLine(main_frame1)
reader_listener2 = SerialReaderProtocolLine(main_frame2)
# Initiate serial port
serial_port1 = Serial("/dev/ttyUSB0")
serial_port2 = Serial("/dev/ttyUSB1")
# Initiate ReaderThread
reader1 = ReaderThread(serial_port1, reader_listener1)
reader2 = ReaderThread(serial_port2, reader_listener2)
# Start reader
reader1.start()
reader2.start()
app.mainloop()
So because it looks like ReaderThread is trying to create a new instance of SerialProtocolLine, I now try a different approach where I pass the SerialProtocolLine class twice to two different ReaderThread constructors. I reassign the tk_listener class variable inbetween ReaderThread instantiation calls...and this bluescreens my computer upon the first receipt of data!
if __name__ == '__main__':
app = tk.Tk()
main_frame0 = MainFrame()
main_frame1 = MainFrame()
# Set listener to our reader
SerialReaderProtocolLine.tk_listener = main_frame0
serial_port0 = Serial("/dev/ttyUSB0")
reader0 = ReaderThread(serial_port0, SerialReaderProtocolLine)
# Initiate serial port
SerialReaderProtocolLine.tk_listener = main_frame1
serial_port1 = Serial("/dev/ttyUSB1")
reader1 = ReaderThread(serial_port1, SerialReaderProtocolLine)
# Start reader
reader0.start()
reader1.start()
app.mainloop()
Any suggestions on how to go about making this modular?

You can pass the instance of MainFrame to SerialReaderProtocolLine class and use functools.partial to pass this extra argument in ReaderThread(...).
Below is the updated code (note that my platform is Windows, so the com ports used are "COM1" and "COM3", change them to the com ports in your platform):
from functools import partial
import tkinter as tk
from serial import Serial
from serial.threaded import ReaderThread, LineReader
class SerialReaderProtocolLine(LineReader):
def __init__(self, tk_listener):
super().__init__()
self.tk_listener = tk_listener
def connection_made(self, transport):
super().connection_made(transport)
self.handle_line(f"Connected {self.tk_listener}, ready to receive data ...")
def handle_line(self, line):
self.tk_listener.after(1, self.tk_listener.on_data, line)
class MainFrame(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.listbox = tk.Listbox(self, width=60)
self.listbox.pack()
#self.pack() # it is not recommended to pack itself, let the parent do it
def on_data(self, data):
print("Called from tk Thread:", data)
self.listbox.insert(tk.END, data)
if __name__ == "__main__":
app = tk.Tk()
main_frame1 = MainFrame(app, name="listener1")
main_frame2 = MainFrame(app, name="listener2")
main_frame1.pack(side="left")
main_frame2.pack(side="left")
serial_port1 = Serial("COM1") # change to your required port name
serial_port2 = Serial("COM3") # change to your required port name
reader1 = ReaderThread(serial_port1, partial(SerialReaderProtocolLine, main_frame1))
reader2 = ReaderThread(serial_port2, partial(SerialReaderProtocolLine, main_frame2))
reader1.start()
reader2.start()
app.mainloop()
The result:

Related

Python serial read byte

I have an interface which has button for the read serial line.
class MainWindow(QMainWindow):
def __init__(self,parent = None):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
########################################################################
# APPLY JSON STYLESHEET
########################################################################
# self = QMainWindow class
# self.ui = Ui_MainWindow / user interface class
loadJsonStyle(self, self.ui)
########################################################################
QSizeGrip(self.ui.size_grip)
self.connection = SerialConnection()
self.Rs232 = RS232_Data()
self.ui.propertyListBtn.clicked.connect(self.get_property_list)
self.show()
def get_property_list(self):
self.Rs232.read(46,0,self.propertyDataArray) #Read Request
self.getAllByte = self.connection.serial_read_byte(236)
print(list(self.getAllByte))
# self.connection.connection_close()
#self.connection.connection_start()
This is the SerialConnection class:
import time
import serial
# serialPort = serial.Serial(port = "COM4", baudrate=115200,
# bytesize=8, timeout=2, stopbits=serial.STOPBITS_ONE)
import serial.tools.list_ports
class SerialConnection():
def __init__(self):
portList = serial.tools.list_ports.comports()
for p in portList:
if p.pid == 12345:
print("Confirmed")
self.getPortName = p.device
print(self.getPortName)
self.serialPort = serial.Serial(port=self.getPortName, baudrate=230400, bytesize=8, timeout=5,
stopbits=serial.STOPBITS_ONE)
break
else:
## Show dialog
print("There is no device on the line")
def connection_start(self):
try:
self.serialPort.open()
except Exception:
print("Port is already open")
def connection_close(self):
self.serialPort.close()
def serial_write(self, data):
self.data = data
self.serialPort.write(self.data)
def serial_read_string(self):
readData = self.serialPort.readline()
return readData
def serial_read_byte(self,size):
readData = self.serialPort.read(size)
return readData
RS232 Class:
class RS232_Data():
def __init__(self):
self.startOfTheFrame = "0x2621"
self.endOfTheFrame = "0x0D0A"
self.startOfTheFrameL = 0x26
self.startOfTheFrameH = 0x21
self.endOfTheFrameL = 0x0D
self.endOfTheFrameH = 0x0A
self.connection = SerialConnection()
def read(self,length,type,propertyData = []):
self.datagram = []
self.datagram.extend([startOfTheFrameL,startOfTheFrameH])
self.connection.serial_write(self.dataGram)
When I press the button only one time. I get correct value. But If I press second-three-third.. time I get wrong values.
But if I add below lines:
self.connection.connection_close()
self.connection.connection_start()
end of the get_property_list function, I get correct value everytime.
Is there any other way to do this? Do I have to close and open the connection every time?

How do I refer to a socket as a class variable that I can use later?

In my case I have a texting app that has a receiver class that accepts incoming connections, and it has a pyqt signal connected to the main app class that will add messages to the screen upon being received. I want to be able to have two of these running and connect to eachother on different ports and send messages back and forth. This also means I want it to be able to run and listen for connections before itself connecting back.
I thought I'd create a connecter class, with the socket as a class variable, and then later instantiate that within the main app and link the send button pyqt signal to a send function that sends over that socket. It doesn't seem to like this. I get the error "socket.socket(socket.AF_INET, socket.SOCK_STREAM) as self.s" invalid syntax.
This is my code:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import socket
import numpy as np
from collections import deque, namedtuple
from threading import Thread
from time import sleep
HOST = '127.0.0.1'
inPORT = int(input('Input port to receive from: '))
outPORT = int(input('Input port to send to: '))
class Receiver(QThread):
received = pyqtSignal(object)
def __init__(self):
# self.outPORT = input('Input port to connect to: ')
super().__init__()
def run(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, inPORT))
s.listen()
print('Listening...')
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)
self.received.emit(data)
class Connecter():
def __init__(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as self.c
self.c.connect((HOST, outPORT))
class ChatApp(QMainWindow):
def __init__(self):
super().__init__()
self.text_area = QTextEdit()
self.text_area.setReadOnly(True)
self.message_line = QLineEdit()
self.message_line.setFocus()
self.button = QPushButton('Send')
outerLayout = QVBoxLayout()
outerLayout.addWidget(self.text_area)
bottomLayout = QHBoxLayout()
bottomLayout.addWidget(self.message_line)
bottomLayout.addWidget(self.button)
outerLayout.addLayout(bottomLayout)
widget = QWidget()
widget.setLayout(outerLayout)
self.setWindowTitle("Chat App")
self.setWindowIcon(QIcon('ChatIcon.png'))
self.setCentralWidget(widget)
self.name = 'frd'
receiver = Receiver()
receiver.received.connect(self.addMessages)
self.threads = [receiver]
connecter = Connecter()
self.button.clicked.connect(self.sendMessage)
self.message_line.returnPressed.connect(self.sendMessage)
receiver.start()
# def askName(self):
# name, ask = QInputDialog.getText(self, 'input dialog', 'Enter username: ')
# return name
def sendMessage(self):
message = self.message_line.text()
connecter.c.sendall(message)
# headerArr = np.array([len(message)], dtype=np.uint16)
# header = headerArr.tobytes
def addMessages(self, message):
self.text_area.append(message.decode('utf-8'))
app = QApplication([])
myApp = ChatApp()
myApp.show()
app.exec()
The point of with is it creates something, then closes it again after the code inside the with is done. That doesn't apply here. It's not what you want the computer to do.
You can assign the socket to the variable like so:
self.c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
and when you are done with the socket, close it like so:
self.c.close()

How to open serial port from a thread in Python

For a few weeks I'm stuck with, how to open a serial COM port from a thread,
to be able to write/read it from another thread. In my example, when I write to the port from another thread, there is access denied.
When I open the port from Main GUI, it works fine, I can write it from the thread, but I need to give a user possibility to choose the COM number.
This is my code, if anybody could take a look and help, it would be great...
class Connect(QThread):
connectResult = QtCore.pyqtSignal(str)
position1 = QtCore.pyqtSignal(str)
actuPosResult = QtCore.pyqtSignal(str)
def __init__(self, myvar, parent=None):
QThread.__init__(self, parent)
self.myvar = str(myvar) # value from spinbox
def run(self):
self.pserial = serial.Serial()
try:
COMnumber= self.myvar
self.pserial = serial.Serial('COM'+COMnumber, 115200,timeout=None)
r='COM '+COMnumber+' connected.'
self.pserial.write("c".encode('ascii')+"\n".encode('ascii'))
incomingByte = self.pserial.read()
decodedByte = incomingByte.decode("utf-8")
if decodedByte == ('c'):
r='Atmega On-Line'
self.connectResult.emit(r)
pos1='---'
self.position1.emit(pos1)
else :
r=' No answer from Atmega.'
self.connectResult.emit(r)
def stop(self):
self.terminate()
class ReadingEncoder(QThread):
actuPosResult = QtCore.pyqtSignal(str)
def __init__(self, mojazmienna, parent=None):
QThread.__init__(self, parent)
self.mojazmienna = str(mojazmienna)
def run(self):
Try:
self.pserial = serial.Serial()
self.pserial = serial.Serial('COM3', 115200,timeout=1)
self.pserial.write("p".encode('ascii')+"\n".encode('ascii'))
incomingByte = self.pserial.read()
decodedByte = incomingByte.decode("utf-8")
actualPos = ''
if decodedByte == ('a'):
while decodedByte != ('\n'):
incomingByte = self.pserial.read()
decodedByte = incomingByte.decode("utf-8")
actualPos = actualPos + decodedByte
pos= actualPos.rstrip('\n')# pozycja w formacie string
print(pos)
self.actuPosResult.emit(pos)
except (EOFError, OSError, IOError, ValueError, RuntimeError, BrokenPipeError, InterruptedError, TimeoutError):
print('Thread readingEncoder error')
self.pserial.close()
You just open the serial port and start the thread.
import atexit
class SerialPort(QThread):
connectResult = QtCore.pyqtSignal(str)
position1 = QtCore.pyqtSignal(str)
actuPosResult = QtCore.pyqtSignal(str)
def __init__(self, port=None, baud=115200, timeout=1):
super().__init__()
self.ser = serial.Serial()
self.ser.port = port
self.ser.baudrate = baud
self.ser.timeout = timeout
self.running = False
atexit.register(self.ser.close) # Make sure the serial port closes when you quit the program.
def set_port(port_num):
self.ser.port = "COM"+str(port_num)
def start(self, *args, **kwargs):
self.running = True
self.ser.open()
super().start()
self.ser.write("c\n".encode("ascii"))
def run(self):
while self.running:
try:
incomingByte = self.ser.read()
decodedByte = incomingByte.decode("utf-8")
if decodedByte == ('c'):
r='Atmega On-Line'
self.connectResult.emit(r)
pos1='---'
self.position1.emit(pos1)
else:
r=' No answer from Atmega.'
self.connectResult.emit(r)
except:
pass
# time.sleep(0.01) # You may want to sleep or use readline
You use this class by having a button call the start method.
serial = SerialPort("COM3")
btn = QtGui.QPushButton("Connect")
btn.clicked.connect(serial.start)
You can typically only open a serial port once unless you know that you have both ends available to you.
The serial port exists on the main thread and exists the entire time. You don't need to continuously open and close the serial port. All of the reading happens in the thread. To write just call serial.write(b"c\n"). You shouldn't need the writing to happen in the thread.
To have the user select the com port just use a QLineEdit.
myserial = QtGui.QLineEdit("3")
myserial.textChanged.connect(serial.set_port)
You're trying to open the port multiple times.
Instead of doing that, you have a couple of alternatives;
Create a Serial object once, and pass it to the thread that has to work with it.
Provide the name of the serial port to use to the the thread that has to work with it, and let it open the port.
In both cases make sure that your thread closes the port properly when it exits!

Combine threads

I'm making a program that reads a UDP socket then decodes this information by STEIM1 function, I want it to plot that information on PyQwt but when I try to upload the information to graph the error (Core Dumped) appears. I searched for information on the web but I would like some support, especially for the part of the graph.
#!/usr/bin/env python
import random, sys
from PyQt4 import QtGui, QtCore
import socket
import threading
import Queue
from datetime import datetime, date, time
import milibreria
from PyQt4.Qwt5 import *
host = '0.0.0.0'
port = 32004
buffer = 1024
my_queue = Queue.Queue()####################################################
my_queue1= Queue.Queue()
class readFromUDPSocket(threading.Thread):
def __init__(self, my_queue):
threading.Thread.__init__(self)
self.setDaemon(True)
self.my_queue = my_queue
def run(self):
while True:
buffer1,addr = socketUDP.recvfrom(buffer)
self.my_queue.put(buffer1)
print 'UDP received'
class process(threading.Thread):
def __init__(self, my_queue,my_queue1):
threading.Thread.__init__(self)
self.setDaemon(True)
self.my_queue = my_queue
self.my_queue1 = my_queue1
self.alive = threading.Event()
self.alive.set()
def run(self):
while True:
buffer3 = self.my_queue.get()
le=len(buffer3)
#today = datetime.now()
#timestamp = today.strftime("%A, %B %d, %Y %H:%M:%S")
#print 'Dato recibido el:', timestamp
DTj,le=milibreria.STEIM1(buffer3)
self.my_queue1.put(DTj)
class Plot(threading.Thread):
def __init__(self, my_queue1):
threading.Thread.__init__(self)
self.setDaemon(True)
self.my_queue1 = my_queue1
self.alive = threading.Event()
self.alive.set()
def run(self):
while True:
Datos = self.my_queue1.get()
print Datos,len(Datos)
milibreria.plotmat(Datos)
if __name__ == '__main__':
# Create socket (IPv4 protocol, datagram (UDP)) and bind to address
socketUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
socketUDP.bind((host, port))
# Instantiate & start threads
myServer = readFromUDPSocket(my_queue)
#mySerial = readFromSerial(my_queue)
myDisplay = process(my_queue,my_queue1)
myPlot = Plot(my_queue1)
myServer.start()
myDisplay.start()
#mySerial.start()
myPlot.start()
#plotThread = threading.Thread(target=main)
#plotThread.start()
while 1:
pass
UDPSock.close()
Your application is crashing because you are plotting from a thread. You cannot interact with a PyQt GUI from within a thread. Interaction with the GUI (this includes PyQwt) must be done in the main thread only.
Refactoring your code to remove the plotting thread is probably beyond the scope of a stack overflow answer. However, if you run into a specific problem when removing your plotting thread, posting a new question (with details on that problem) on stack overflow is encouraged.

Terminal with Threads using PyQt

I'm trying to build a PyQt app which (among other things) has the ability via a QTextEdit Box to function like a serial terminal program (HyperTerminal, TeraTerm, etc.) I've read through a few examples from the PySerial page and I think I've managed to get the receive data thread working properly but maybe not as efficiently as possible.
My problem is how do I take the last typed character in the QTextEdit box and send that out the serial connection? I've tried using the textChanged signal that QTextEdit emits, but that then sends everything that I type AND that it receives. I've tried setting up an eventFilter in my main GUI class, but I can't figure out how to get that over to the serial function in another file. Do I want to have a separate thread that listens for a signal emitted from the eventFilter? How do I do that? Is there a more elegant way to do this?
I'm sure I've just managed to overthink this and the solution is simple, but I'm somewhat struggling with it. I'll attach the relevant code snippets (not a full code set) and perhaps somebody can point me in the right direction. If anybody also thinks that the threading that I'm doing could be done in a more efficient manner, then please relay that to me as well!
Thanks for any help that anybody can provide!
Main File:
import sys
from PyQt4 import QtGui
from MainGUI import TestGUI
from SerialClasses import *
from SerialMiniterm import *
class StartMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(StartMainWindow, self).__init__(parent)
self.ui = TestGUI()
self.ui.setupUi(self)
self.ui.serialTextEditBox.installEventFilter(self)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.KeyPress and source is self.ui.serialTextEditBox):
# print some debug statements to console
if (event.key() == QtCore.Qt.Key_Tab):
print ('Tab pressed')
print ('key pressed: %s' % event.text())
print ('code pressed: %d' % event.key())
# do i emit a signal here? how do i catch it in thread?
self.emit(QtCore.SIGNAL('transmitSerialData(QString)'), event.key())
return True
return QtGui.QTextEdit.eventFilter(self, source, event)
def serialConnectCallback(self):
self.miniterm = SerialMiniterm(self.ui, self.SerialSettings)
self.miniterm.start()
temp = self.SerialSettings.Port + 1
self.ui.serialLabel.setText("<font color = green>Serial Terminal Connected on COM%d" % temp)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setStyle("Cleanlooks")
myapp = StartMainWindow()
myapp.show()
sys.exit(app.exec_())
SerialMiniterm.py:
import serial
from PyQt4 import QtGui, QtCore
def character(b):
return b
class SerialMiniterm(object):
def __init__(self, ui, SerialSettings):
self.SerialSettings = SerialSettings
self.ui = ui
self.serial = serial.Serial(self.SerialSettings.Port, self.SerialSettings.BaudRate, parity=self.SerialSettings.Parity, rtscts=self.SerialSettings.RTS_CTS, xonxoff=self.SerialSettings.Xon_Xoff, timeout=1)
self.repr_mode = self.SerialSettings.RxMode
self.convert_outgoing = self.SerialSettings.NewlineMode
self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing]
self.dtr_state = True
self.rts_state = True
self.break_state = False
def _start_reader(self):
"""Start reader thread"""
self._reader_alive = True
self.receiver_thread = ReaderThread(self.alive, self._reader_alive, self.repr_mode, self.convert_outgoing, self.serial)
self.receiver_thread.connect(self.receiver_thread, QtCore.SIGNAL("updateSerialTextBox(QString)"), self.updateTextBox)
self.receiver_thread.start()
def _stop_reader(self):
"""Stop reader thread only, wait for clean exit of thread"""
self._reader_alive = False
self.receiver_thread.join()
def updateTextBox(self, q):
self.ui.serialTextEditBox.insertPlainText(q)
self.ui.serialTextEditBox.moveCursor(QtGui.QTextCursor.End)
#print "got here with value %s..." % q
def start(self):
self.alive = True
self._start_reader()
# how do i handle transmitter thread?
def stop(self):
self.alive = False
def join(self, transmit_only=False):
self.transmitter_thread.join()
if not transmit_only:
self.receiver_thread.join()
class ReaderThread(QtCore.QThread):
def __init__(self, alive, _reader_alive, repr_mode, convert_outgoing, serial, parent=None):
QtCore.QThread.__init__(self, parent)
self.alive = alive
self._reader_alive = _reader_alive
self.repr_mode = repr_mode
self.convert_outgoing = convert_outgoing
self.serial = serial
def __del__(self):
self.wait()
def run(self):
"""loop and copy serial->console"""
while self.alive and self._reader_alive:
data = self.serial.read(self.serial.inWaiting())
if data: #check if not timeout
q = data
self.emit(QtCore.SIGNAL('updateSerialTextBox(QString)'), q)
Something like this?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Terminal(QtGui.QPlainTextEdit):
def keyPressEvent(self, event):
print event.text()
return QtGui.QPlainTextEdit.keyPressEvent(self, event)
term = Terminal()
term.show()

Categories

Resources