How to structure this project?
-
Hi all,
I am looking for a bit of help structuring/(de)coupling/... the different parts of my program so that it is structured more cleanly.
I'm sorry if this is an overly long question for something that's possibly very intuitive for most of you. I'm trying to properly explain what I'm after.
I realise I might be (likely am?) overcomplicating things...
What am I doing/trying to do
My software is used to do basic control of a LED processor (driving a LED screen) over a serial port. Think
- setting brightness
- selecting input sources
- starting test patterns
I have a working version of my software but it was one of the first things I did in PyQt so I ended up putting almost everything together. I have the
MainWindow
and a controller class that also has everything related to serial ports. I did not use signals/slots. Everything was messy but it works. I just don't think it's a good solution and wouldn't scale well.As an excercise, I am now trying to redo the project to disconnect separate parts. It makes sense to separate in to:
- the UI (
MainWindow
) - the serial part listing available ports, managing status, ... (
SerialStatus
) - the controller issuing commands and requesting data back (
LEDController
)
Where I'm stuck
I can't figure out how to do this somewhat cleanly.
I have the serial class with signals/slots connected to the UI
Direction Function MainWindow
->SerialStatus
Request to check if the serial port is still connected/ok and the list of available ports is still correct MainWindow
<-SerialStatus
The list of available serial ports has changed (with a list of ports) MainWindow
->SerialStatus
Change connection (including which port and connect/disconnect) MainWindow
<-SerialStatus
The connection status has changed (connected or disconnected plus which port) MainWindow
<-SerialStatus
Error message or conditions This works for basic functionality and I think I got this covered.
I can make a similar list for communication between the
MainWindow
and theLEDController
classes. But then, how do I connect betweenSerialStatus
andLEDController
?The
LEDController
class needs to be able to interface with theSerialStatus
class that has the opened port stored as the attributeSerialStatus.port
Specific example
If I press a button to change a test pattern, I need the GUI to talk to the LED controller class which needs to send commands to the serial port.
How could I do this? I could have a signal/slot for Rx/Tx request from the
LEDController
to theMainWindow
which then has a signal/slot for Rx/Tx to theSerialStatus
, but that seems awkward. Should I pass theSerialStatus
class as an argument to theLEDController
class and have that do all the communication directly? This would meanLEDController
andSerialStatus
are coupled together.Once again, I understand this might seem like very basic and intuitive.
-
-
@DieterV said in How to structure this project?:
SerialStatus class that has the opened port stored as the attribute SerialStatus.port
Calling a class which does the serial port communication SerialStatus sounds wrong. It is going to do more than just checking the status, right?
Connecting SerialStatus and LEDController can be done the same way you connected LEDController with your UI. I guess you create LEDController and SerialStatus instaces in MainWindow, right? So, what is the problem connect both there?
-
@jsulm said in How to structure this project?:
@DieterV said in How to structure this project?:
SerialStatus class that has the opened port stored as the attribute SerialStatus.port
Calling a class which does the serial port communication SerialStatus sounds wrong. It is going to do more than just checking the status, right?
Correct. I'd better rename that
SerialHandler
. Thanks.Connecting SerialStatus and LEDController can be done the same way you connected LEDController with your UI. I guess you create LEDController and SerialStatus instaces in MainWindow, right? So, what is the problem connect both there?
If I understand correctly, you're saying to have the
LedController
signal each outgoing message to the UI which connects this to a slot in theSerialHandler
and vice versa.Hadn't thought about the fact that I could connect signals in
SerialHandler
to slots inLEDController
and the other way around.That way, you do end up with a lot of connections through the
MainWindow
class (if you start having bigger programs), but I presume that's not really an issue.I'll whip up some pseudo code throughout the day and post it to verify I understand correctly.
-
@DieterV said in How to structure this project?:
If I understand correctly, you're saying to have the LedController signal each outgoing message to the UI which connects this to a slot in the SerialHandler and vice versa.
No. What I mean is: in a place where you have access to both instances (SerialHandler and LedController) you can connect their signals and slots, so that they can connect directly with each other. No need to go through UI.
-
@jsulm said in How to structure this project?:
I guess you create LEDController and SerialStatus instaces in MainWindow, right? So, what is the problem connect both there?
Well, yeah. I create the
LEDController
andSerialHandler
as attributes in theMainWindow
. That is what I meant with "connecting them in the GUI". But I'll post some code to make sure I'm correctly understanding. -
Below the (non functional, just to make sure I understand correctly) code.
Variable names are extremely verbose, just to make them clear.
@jsulm is this what you mean?from PySide6 import QtCore as qtc from PySide6 import QtWidgets as qtw from .ui_sources.mainwindow import Ui_MainWindow class MainWindow(Ui_MainWindow, qtw.QMainWindow): def __init__(self, *args, obj=None, **kwargs): super().__init__() self.setupUi(self) self.led_controller = LEDController() self.serial_handler = SerialHandler() self.btn_green = qtw.QPushButton("Green") self._setup_signals() def _setup_signals(self): self.led_controller.sgn_command_available.connect(self.serial_handler.send_cmd) self.btn_green.clicked.connect(self.led_controller.pattern_green) class SerialHandler: def __init__(self, *args): self.port = self._create_and_open_port(*args) # Open serial port @qtc.pyqtSlot() def send_cmd(self, cmd: bytearray): self.port.write(cmd) class LEDController: sgn_cmd_ready_for_tx = qtc.Signal(bytearray) def __init__(self): ... @qtc.pyqtSlot() def pattern_green(self) -> bytearray: #Would generate command for a green pattern here generated_cmd = bytearray([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]) self.sgn_cmd_ready_for_tx.emit(generated_cmd) ``
-
-
That is great. Thank you very much for your time and help.
Would you have any (open source) pyqt projects you would recommend to look at and learn from?
I've got a couple of good books but they usually have small examples focused on specific topics, not complete projects. I'm currently mostly looking at how https://github.com/dietervansteenwegen/serial-tool is set up and try to understand and copy (if it makes sense) that.
-