User interfaces (UIs) are an essential part of modern software applications, providing an interactive way for users to interact with the underlying functionality of the application. Qt Creator, a popular integrated development environment (IDE) for developing Qt applications, provides a graphical interface for designing UIs using a drag-and-drop approach. Once the UI is designed, we can implement it in Python using the PySide2 library, which is a set of Python bindings for the Qt application framework. HMI is Human Machine Interface – The hardware or software through which an operator interacts with a controller. In this blog, we will explore how to design HMI on Brainy Pi using Python i.e. UI with Qt Creator and implement it in using PySide2.
Index
Pre-requisites
HMI Top-level Design
QT Creator Overview
Designing the HMI in Depth
Coding
Run the ready to use code
TLDR
Pre-requisites for HMI on Brainy Pi
Installing Qt creator
sudo apt update && sudo apt install -y qtbase5-dev qtcreator
Installing PySide2 (python for QT5)
sudo apt update && sudo apt-get install -y python3-pyside2.qt3dcore python3-pyside2.qt3dinput python3-pyside2.qt3dlogic python3-pyside2.qt3drender python3-pyside2.qtcharts python3-pyside2.qtconcurrent python3-pyside2.qtcore python3-pyside2.qtgui python3-pyside2.qthelp python3-pyside2.qtlocation python3-pyside2.qtmultimedia python3-pyside2.qtmultimediawidgets python3-pyside2.qtnetwork python3-pyside2.qtopengl python3-pyside2.qtpositioning python3-pyside2.qtprintsupport python3-pyside2.qtqml python3-pyside2.qtquick python3-pyside2.qtquickwidgets python3-pyside2.qtscript python3-pyside2.qtscripttools python3-pyside2.qtsensors python3-pyside2.qtsql python3-pyside2.qtsvg python3-pyside2.qttest python3-pyside2.qttexttospeech python3-pyside2.qtuitools python3-pyside2.qtwebchannel python3-pyside2.qtwebsockets python3-pyside2.qtwidgets python3-pyside2.qtx11extras python3-pyside2.qtxml python3-pyside2.qtxmlpatterns
Installing pyqtgraph (library for creating graphs)
pip3 install pyqtgraph
HMI Top-level Design
PushButton which will toggle a GPIO on or off and show the GPIO value.
2 LCD display, 1 Displays Temperature, 1 Displays Pressure
2 Sliders
1 Slider for setting Temperature
1 Slider for setting Pressure
Dial for fine tuning the temperature setting.
2 Graphs
1 Graph which displays live CPU temperature of the system.
1 Graph which displays a random value.
Finally the HMI would look something like this.
QT Creator Overview
Designing a UI with Qt Creator involves the following steps:
Open QT Creator
Create a New Qt Project: Launch Qt Creator and create a new Qt project by selecting “File” > “New File or Project” from the menu bar.
Choose “Application (QT for Python)” as the project template, then choose the “QT for Python – Window (UI file)” as the template and click “Next”.
To Configure the Project: Enter a Class name for the project, and then click “Next”.
Finally, click “Finish” to create the project.
Design the UI:
Once we create a project, Qt Creator will open the main window so click on the
form.ui
to open the Designer windowThe main window consists of a central widget, where the UI can be designed using various UI elements such as buttons, labels, text boxes, etc., which can be dragged and dropped onto the canvas and hence it becomes easy for developers to create UI using QT.
The properties of each UI element can be customized using the “Properties” pane on the right side of the window.
We will see the designing the HMI in depth in next section.
Save the UI: After making changes to the UI, save by selecting “File” > “Save” from the menu bar.
Designing the HMI on Brainy Pi in Depth
Since, we got basic understand of QT Creator lets move on to designing HMI for our application. Double click on the “form.ui” file in QT Creator, to open up the QT Designer window, this is where we will be designing the UI.
1. Button and Labels
Search for PushButton, Drag and drop it into the canvas.
So now double Click on the PushButton to change the text inside the PushButton, to
OFF
.Search for Label, Drag and drop it into the canvas, just above the PushButton.
So now double Click on the Label to change the text inside the label.
2. Sliders, Dials and LCD
Search for Vertical Slider, Drag and drop it into the canvas. Make it 2 vertical sliders.
Then search for Dial, Drag and drop it into the canvas.
Then search for LCD Number, Drag and drop 2 LCD into the canvas.
Label all the components that you dropped into the canvas and the end result should look like this
3. Graphs
Search for Widget, Drag and drop 2 widgets into the canvas.
Then resize both the widget as per your liking.
Note Down the Width and the Height of the widgets from the Properties section.
Coding
Implementing the UI designed with Qt Creator in Python using PySide2, will the split into these parts
1. Structure of the code
import sys
from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QSlider, QDial
from PySide2.QtCore import Qt
class hmi(QWidget):
def __init__(self):
super(hmi, self).__init__()
self.load_ui()
def load_ui(self):
loader = QUiLoader()
path = os.path.join(os.path.dirname(__file__), "form.ui")
ui_file = QFile(path)
ui_file.open(QFile.ReadOnly)
self.ui = loader.load(ui_file, self)
ui_file.close()
# .....
# More code will follow here
# Create QApplication instance
app = QApplication(sys.argv)
# Create main window
window = hmi()
window.show()
# Start event loop
sys.exit(app.exec_())
Class Definition: The
hmi
class is defined, which inherits from theQWidget
class, the base class for all GUI objects in PySide2. Thehmi
class represents the main window of the GUI and contains methods for initializing GUI widgets, handling signals and slots, and updating GUI elements.GUI Layout: The
load_ui
method loads the GUI layout from an external.ui
file using theQUiLoader
class. The.ui
file contains the graphical design of the GUI created using Qt Creator.Application Definition: We used
QApplication
class to create application, and usedhmi
class to create new window and this window is the primary window of the application.
2. Button and Labels
We will create a
init_ui
function to initialize the UI.def init_ui(self): """ Intialize the QT UI """ # Set Window Title self.setWindowTitle("HMI Display")
We will also create a
init_gpio
function to initialize the GPIO which will be toggled by the button.def init_gpio(self): """ Intialize GPIO Function initializes GPIO for UI usage later on in the code """ GPIO.setmode(GPIO.BOARD) GPIO.setup(self.LED_GPIO_PIN, GPIO.OUT)
Then we will create a function
gpio_toggle
which will toggle GPIO on or off on button press@Slot() def gpio_toggle(self): """Toggle GPIO on or off Toggles GPIO on or off based on the current state of the GPIO. If on the function will turn it off. If off the function will turn it on. """ if self.LED_value: GPIO.output(self.LED_GPIO_PIN, GPIO.LOW) self.LED_value = False self.ui.pushButton.setText("OFF") self.ui.label.setText("LED is OFF") else: GPIO.output(self.LED_GPIO_PIN, GPIO.HIGH) self.LED_value = True self.ui.pushButton.setText("ON") self.ui.label.setText("LED is ON")
The @Slot() decorator indicates that this method is a slot, which can be connected to signals emitted by GUI widgets.
The method first checks the current state of the LED, which is stored in a boolean variable self.LED_value.
If the LED is currently ON (self.LED_value is True), the method turns it OFF by setting the GPIO pin to GPIO.LOW, updating the button text to “OFF”, and changing a label text to “LED is OFF”.
If the LED is currently OFF (self.LED_value is False), the method turns it ON by setting the GPIO pin to GPIO.HIGH, updating the button text to “ON”, and changing the label text to “LED is ON”.
Connect the
gpio_toggle
function to button click using the button click signaldef init_ui(self): """ Intialize the QT UI """ # Set Window Title self.setWindowTitle("HMI Display") # Set the slot for the Pushbutton click self.ui.pushButton.clicked.connect(self.gpio_toggle)
3. Sliders, Dials and LCD
Lets create a function for updating the LCD Display when the slider is moved.
@Slot() def slider1Moved(self, value): """Update the LCD value when the slider1 moves """ self.ui.lcdNumber.display((value/10)%20 + 0.01) @Slot() def slider2Moved(self, value): """Update the LCD value when the slider2 moves """ self.TEMP_value = value + 0.01 self.ui.lcdNumber_2.display(((value)) + 0.01)
slider1Moved(self, value): This method is a slot that is connected to a slider widget in the GUI. It is triggered when the slider is moved, and it updates the value displayed on an LCD widget. The value is calculated by taking the slider value (value) divided by 10, then taking the modulus of 20, and adding 0.01. The result is then displayed on the LCD widget using the display() method.
slider2Moved(self, value): Similar to slider1Moved, this method is a slot connected to a second slider widget. It updates the value displayed on a different LCD widget. We calculate this value by adding 0.01 to the slider value, and then displayed on the LCD widget using the display() method.
Let’s create a function for updating the LCD Display when the Dial is moved, the code is similar to the slider
@Slot() def dialMoved(self, value): """Update the LCD value when the dial moves """ self.ui.lcdNumber_2.display(self.TEMP_value + (value/100))
dialMoved(self, value): This method is a slot connected to a dial widget. It updates the value displayed on the same LCD widget as slider2Moved. The value is calculated by adding the dial value divided by 100 to a variable self.TEMP_value, and then displayed on the LCD widget using the display() method.
Now Let’s connect all the above functions to their respective UI elements, so that when the user interacts with those UI elements, those function get triggered.
def init_ui(self): """ Intialize the QT UI """ # Set Window Title self.setWindowTitle("HMI Display") # Set the slot for the Pushbutton click self.ui.pushButton.clicked.connect(self.gpio_toggle) # Set the slot for the Vertical sliders moved self.ui.verticalSlider.sliderMoved.connect(self.slider1Moved) self.ui.verticalSlider_2.sliderMoved.connect(self.slider2Moved) # Set the slot for the Dial moved self.ui.dial.sliderMoved.connect(self.dialMoved)
Hence, this is how
init_ui
function connects the UI elements and functions.
4. Graphs
To create 2 Graphs, we need these functions for each graph
Initialize function – Which initializes the Graph
Timer function – Which triggers the update function after set time.
Update Function – Which updates the Graph values
Now that we understood what these functions are let’s create the Initialize function
def initialize_graph1(self): """Initialize the Graph1 """ pen = pg.mkPen(color=(0, 255, 0), width=2) self.ui.graphWidget1 = pg.PlotWidget(self.ui.widget) # Set Background colour to white self.ui.graphWidget1.setBackground('w') # Set Graph Title self.ui.graphWidget1.setTitle("CPU Temperature", color="b", size="20pt") # Set Graph Axis Labels self.ui.graphWidget1.setLabel('left', "Temperature (C)") self.ui.graphWidget1.setLabel('bottom', "Time (s)") # Set Graph size self.ui.graphWidget1.setFixedSize(461, 201) # Set Initial Graph points self.graph1x = list(range(100)) # 100 time points self.graph1y = [0 for _ in range(100)] # 100 data points self.graph1data_line = self.ui.graphWidget1.plot(self.graph1x, self.graph1y, pen=pen) def initialize_graph2(self): """Initialize the Graph2 """ pen = pg.mkPen(color=(255, 0, 0), width=2) self.ui.graphWidget2 = pg.PlotWidget(self.ui.widget_2) # Set Background colour to white self.ui.graphWidget2.setBackground('w') # Set Graph Title self.ui.graphWidget2.setTitle("Random Value - Pressure", color="b", size="20pt") # Set Graph Axis Labels self.ui.graphWidget2.setLabel('left', "Pressure (ba)") self.ui.graphWidget2.setLabel('bottom', "Hour (H)") # Set Graph size self.ui.graphWidget2.setFixedSize(461, 201) # Set Initial Graph points self.graph2x = list(range(100)) # 100 time points self.graph2y = [randint(0, 100) for _ in range(100)] # 100 data points self.graph2data_line = self.ui.graphWidget2.plot(self.graph2x, self.graph2y, pen=pen)
Both these functions:
Creates a PlotWidget object from pyqtgraph library to create a graph in the GUI.
Sets properties of the graph such as background color, title, axis labels, and size.
Creates lists to store x and y values of the graph and fills these list with initial values
Creates a plot on the graph using the plot() method with the specified pen.
Let’s create and update function
def update_graph1_data(self): """Update the Graph1 """ self.graph1x = self.graph1x[1:] # Remove the first x element. self.graph1x.append(self.graph1x[-1] + 1) # Add a new value 1 higher than the last. self.graph1y = self.graph1y[1:] # Remove the first temperature = self.get_cpu_temperature() self.graph1y.append(temperature) # Add a new temperature value. self.graph1data_line.setData(self.graph1x, self.graph1y) # Update the data. def update_graph2_data(self): """Update the Graph2 """ self.graph2x = self.graph2x[1:] # Remove the first x element. self.graph2x.append(self.graph2x[-1] + 1) # Add a new value 1 higher than the last. self.graph2y = self.graph2y[1:] # Remove the first self.graph2y.append(randint(0, 100)) # Add a new random value. self.graph2data_line.setData(self.graph2x, self.graph2y) # Update the data. def get_cpu_temperature(self): """Helper function which gets the live system CPU Temperature value """ file1 = open("/sys/devices/virtual/thermal/thermal_zone0/temp", "r") CPU_temp = str(file1.readline()) file1.close() CPU_temp = float(int(CPU_temp)/1000) return CPU_temp
update_graph1_data(self): Updates the data of Graph1 by removing the oldest data point, adding a new data point for CPU temperature obtained from get_cpu_temperature() function, and updating the plot with the new data using setData() method.
update_graph2_data(self): Updates the data of Graph2 by removing the oldest data point, adding a new random data point, and updating the plot with the new data using setData() method.
get_cpu_temperature(self): Helper function that reads the live system CPU temperature from a file, converts it to Celsius, and returns the temperature as a float value.
Let’s move on to create the Timer functions
def initialize_timer_for_graph1(self, updateTime, updateFunction): """Initiallize the timer for graph Args: 1. updateTime - Time interval in ms for updating the graph value 2. updateFunction - The update data function for the graph """ self.timer1 = QTimer() self.timer1.setInterval(updateTime) self.timer1.timeout.connect(updateFunction) self.timer1.start() def initialize_timer_for_graph2(self, updateTime, updateFunction): """Initiallize the timer for graph Args: 1. updateTime - Time interval in ms for updating the graph value 2. updateFunction - The update data function for the graph """ self.timer2 = QTimer() self.timer2.setInterval(updateTime) self.timer2.timeout.connect(updateFunction) self.timer2.start()
initialize_timer_for_graph1(self, updateTime, updateFunction): Initializes a timer for Graph1 with the given time interval updateTime in milliseconds. The timer is connected to the updateFunction which is responsible for updating the data of Graph1. The timer is started using start() method.
initialize_timer_for_graph2(self, updateTime, updateFunction): Initializes a timer for Graph2 with the given time interval updateTime in milliseconds. The timer is connected to the updateFunction which is responsible for updating the data of Graph2. The timer is started using start() method.
Now that we explored all the factors lets put it all together
def init_ui(self): """ Intialize the QT UI """ # Set Window Title self.setWindowTitle("HMI Display") # Set the slot for the Pushbutton click self.ui.pushButton.clicked.connect(self.gpio_toggle) # Set the slot for the Vertical sliders moved self.ui.verticalSlider.sliderMoved.connect(self.slider1Moved) self.ui.verticalSlider_2.sliderMoved.connect(self.slider2Moved) # Set the slot for the Dial moved self.ui.dial.sliderMoved.connect(self.dialMoved) # Initialize the Graph self.initialize_graph1() self.initialize_graph2() # Set the timers for graph self.initialize_timer_for_graph1(100, self.update_graph1_data) self.initialize_timer_for_graph2(500, self.update_graph2_data)
This is how we initialize the graphs and set the timer in the
init_ui
function.Now the code should be ready
Your code should look something like this – https://github.com/brainypi/brainypi-hmi-example.git
Run the ready to use code
Open Terminal and Navigate to your project folder
cd /path/to/project/folder # For example # cd /home/pi/hmi
Then run the program
python3 main.py
TLDR;
Because there are so many steps above in order to create an application so here is easy way to run the HMI on brainy pi, follow these steps.
sudo apt update && sudo apt install -y qtbase5-dev qtcreator
sudo apt update && sudo apt-get install -y python3-pyside2.qt3dcore python3-pyside2.qt3dinput python3-pyside2.qt3dlogic python3-pyside2.qt3drender python3-pyside2.qtcharts python3-pyside2.qtconcurrent python3-pyside2.qtcore python3-pyside2.qtgui python3-pyside2.qthelp python3-pyside2.qtlocation python3-pyside2.qtmultimedia python3-pyside2.qtmultimediawidgets python3-pyside2.qtnetwork python3-pyside2.qtopengl python3-pyside2.qtpositioning python3-pyside2.qtprintsupport python3-pyside2.qtqml python3-pyside2.qtquick python3-pyside2.qtquickwidgets python3-pyside2.qtscript python3-pyside2.qtscripttools python3-pyside2.qtsensors python3-pyside2.qtsql python3-pyside2.qtsvg python3-pyside2.qttest python3-pyside2.qttexttospeech python3-pyside2.qtuitools python3-pyside2.qtwebchannel python3-pyside2.qtwebsockets python3-pyside2.qtwidgets python3-pyside2.qtx11extras python3-pyside2.qtxml python3-pyside2.qtxmlpatterns
pip3 install pyqtgraph
git clone https://github.com/brainypi/brainypi-hmi-example.git
cd brainypi-hmi-example
python3 main.py