Auton Loops (without blocking everything)

Hello again!

I’m still on my journey to modify AVRGUI, and right now the thing that’s stumping me is non - blocking loops. I want to constantly check for an input type not listed under QtGui (or at least, I can’t find it) but if I do a while loop to check, it blocks all the code after it from running. Is there a way for me to asynchronously run a loop to check for input?

If it helps, the input I’m trying to grab is from an Arduino.

This is a tough topic, and the bane of every GUI programmer’s existence. What you need to do here, is start the loop in a new thread, and then communicate back to the main GUI thread with QtSignals. This is how all the MQTT functionality works in the GUI: AVR-GUI/mqtt.py at main · bellflight/AVR-GUI · GitHub
The MQTT client is started in a separate thread, and Signals are used to communicate with all the other tabs that consume data.

Hmm. Is it possible to have an event with a trigger that checks for data from the serial port? Like instead of checking for a MouseEvent, check for serial.

Or just have it check on a timer. As long as it’s receiving data and not preventing everything else from running it’ll work.

Right now my only solution is to hijack the MouseEvent trigger and just constantly wiggle my mouse to force updates.

You should be able to. I was going to make a proof of concept for you, but I admit, I forgot. I’ll try to get to it tonight

Here’s an example I whipped up. An inifite loop is started to pick cards from a deck of cards forever, at random intervals. The GUI remains interactive throughout.

import random
import sys
import time

from PySide6 import QtCore, QtWidgets


class DataGenerator:
    def __init__(self) -> None:
        self._suits = ["hearts", "diamonds", "spades", "clubs"]
        self._values = [
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "10",
            "jack",
            "queen",
            "king",
            "ace",
        ]

        self._first = True

    def pick_card(self) -> tuple[str, str]:
        """
        Pick a new card randomly after a random time interval.
        """

        if self._first:
            # first time, have no delay
            self._first = False
        else:
            time.sleep(random.randint(1, 5))

        return (random.choice(self._values), random.choice(self._suits))


class DataGeneratorThread(QtCore.QThread):
    new_data = QtCore.Signal(tuple)
    """
    Create a signal for when new data is available.
    """

    def __init__(self, parent: QtCore.QObject | None = ...) -> None:
        super().__init__(parent)
        self.generator = DataGenerator()

    def run(self) -> None:
        """
        Infinitely pick cards and emit signals
        """
        while True:
            self.new_data.emit(self.generator.pick_card())


class MainWindow(QtWidgets.QWidget):
    def __init__(self) -> None:
        super().__init__()

        layout = QtWidgets.QHBoxLayout()

        label = QtWidgets.QLabel("Current Card:")
        layout.addWidget(label)

        self.display = QtWidgets.QLineEdit()
        self.display.setReadOnly(True)
        layout.addWidget(self.display)

        test_button = QtWidgets.QPushButton("Interactivity test button")
        layout.addWidget(test_button)

        self.setLayout(layout)

        data_generator_thread = DataGeneratorThread(self)
        data_generator_thread.new_data.connect(self.update_data)
        data_generator_thread.start()

    def update_data(self, data: tuple[str, str]) -> None:
        """
        Handle data updates
        """
        self.display.setText(f"{data[0]} of {data[1]}")


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()

    app.exec()

image

It’s not great, and violates some best practices like not cleaning up threads, but it should be a good basic example to show the concept.