In the previous tutorial, we discussed the object-oriented features of Python. These features are of great importance in organizing our code and structuring our applications. Now, let us talk about designing graphic interfaces in Python.
Embedded applications developed on microcontrollers usually have LEDs, character LCDs, or small graphic LCDs as display devices. These displays cannot have many fancy interfaces. A single-board computer like Raspberry Pi can be configured like a desktop system, for example, we set up our Raspberry Pi Linux desktop. With such a setup, it is possible to have rich interfaces and large applications running for similar embedded circuits.
While microcontrollers always have the advantage of execution speed, single-board computers, despite lagging in speed benchmarks, have lots of other things to offer, all credits to sitting operating system and easy access to high-level language features. Rich graphics interfaces, access to databases, data visualization, 3D graphics, networking, and data mining are just some of the HLL features that can be utilized in an embedded project with SBCs.
For example, by interfacing a sensor with a microcontroller, we can only collect sensor data and implement some immediate actions based on the instantly collected data. If the same sensor is interfaced with SBC, it is possible to record and maintain a large amount of sensor data in databases, perform analytics, visualize data, perform data mining, share data as well as its results to other devices on a network and implement immediate as well as long-term actions.
A microcontroller-based device being running a firmware-level code cannot perform all such complex tasks on its own. At most, it can be configured as an IoT device by connecting it to a network, and the collected physical data can then be manipulated on a cloud platform. That is the reason single-board computers have a distinct role in the embedded domain.
Secondly, you get a real feel of hardware-software co-design in embedded systems when you work on SBCs. The microcontroller-based applications still keep the focus on hardware. With single-board computers, you get a chance to shift the focus of an embedded application to software aspects. SBCs allow creating high-level embedded software that truly utilizes the power of a hardware component. Ultimately, it is the software running on the hardware that makes the most sense.
Interfacing and controlling LEDs is the ‘Hello World’ of embedded systems. From this very first activity onwards, you will realize how much creative and powerful it can be with single-board computers. The following is a screenshot of GUI designed for our very first LED driver recipe. Take a minute to inspect this GUI and try guessing the level of control we can exert on a hardware component with SBC.
In all of our Raspberry Pi recipes, we will frequently be using GUI programming and multithreading. When we use sensors, we will include data visualization, database programming, and data mining to our embedded software. Similarly, when we are controlling actuators, building robots, or utilizing sophisticated hardware modules like camera, microphone, etc., we can include relevant software features like image processing, audio and multimedia programming, 3D graphics, game programming, etc. Anyways GUI programming and multithreading will remain essential software components of all our recipes. So, let us discuss GUI programming in Python.
Python GUI programming
Python is a powerful very-high-level-language (VHLL). It does not just allow designing rich graphic interfaces; it also provides a myriad of data visualization techniques with data mining capability. This can be a distinct advantage in sophisticated embedded applications. While using Raspberry Pi in embedded applications, we will be treating embedded circuits as ‘Physical Data Sources’ (when interfaced to sensors) and ‘Sophisticated Controllers’ (when interfaced to actuators). The graphic interfaces designed on Python will allow interacting with embedded circuits, visualize sensor data, manipulate data for analytics and decision making, and provide additional interactivity that might not be typically possible with microcontrollers.
There are several options in Python to develop graphical user interfaces. The most popular one includes the following:
Tkinter – It is a standard Python interface to Tk GUI toolkit. It is the standard de facto GUI in Python. Tkinter is free and already included within Python installation on Linux, Microsoft Windows, and Mac OS X. For using it in an application, the Tkinter package must be imported.
WxPython – It is again an open-source, cross-platform GUI toolkit for Python. Its main advantage is its native widgets (OS-specific native widgets).
PyQT – It is Python binding for cross-platform GUI toolkit Qt. It is probably the most powerful and more substantial GUI for Python. It is best suitable for large applications. It comes available under GPL and commercial licenses. So, it can be a bit complicated to use it for commercial applications.
PySide – PySide is similar to PyQT in functionality. Though, it is covered under the LGPL license. So, it is much liberal for use in commercial applications.
There are many other GUI packages for Python. Before using any package in an application, it is important to learn about its features, portability, development tools, licensing, and limitations. For large applications, PyQT and PySide are the best options right now with PySide having a much liberal license. There are also WYSIWYG editors available for PyQT and PySide that make designing interfaces really quick and handy. Tkinter is best for basic applications.
We will be using Tkinter for designing GUI for our Raspberry Pi recipes. Tkinter has a small footprint and has a relatively fast execution in comparison to other packages. Layout management is also quite easy on Tkinter. These features make Tkinter a better option for embedded UI. Though we will have to write code for each widget and handling events could be tricky sometimes. A little more effort will be worth to speed up our small embedded apps.
Multi-threading embedded tasks
In embedded applications, we usually need to keep repeating some tasks all the time. Like, we repeatedly need to fetch data from a sensor all the time or repeatedly display some messages on a display device, etc. Desktop applications generally do not involve such tasks. You will never want code for a desktop application to run an infinite loop that never ends.
In our Raspberry Pi recipes, we will want to implement those infinite loops typical to embedded applications. If we run these loops from within our GUI app, our interface will freeze, and we will not be able to input any other command. So, for every embedded task, we will be creating a separate thread. The GUI will keep running as a different process on Linux. We can pass commands to kill these threads with a small coding trick. So, we can stop the tasks whenever we wish or require. With threads, we can treat our Raspberry Pi recipes just like microcontroller-based embedded applications but with much efficiency.
With multi-threading, we will also get the advantage to simultaneously and independently handle multiple hardware components. Multi-Threading is not possible on microcontrollers, so this is just another proof a single-board computer running an embedded OS.
Tkinter is a standard GUI library for Python. It provides an object-oriented interface to the Tk GUI toolkit. Any graphic user interface (GUI) consists of a main window in which different graphical elements like labels, text boxes, buttons, radio buttons, select buttons, canvas, etc. are laid out. These graphical elements, including the main window and other windows are called widgets. User actions like clicking a button, focusing on a textbox, selecting a radio button, etc. are called events. In response to an event, it is possible to open another window, which is then called the child window to its parent window.
For creating a user interface with Tkinter, first of all, it is required to import the Tkinter module. The Tkinter module can be imported in Python code like any other module. It should be noted that the name of the module is ‘Tkinter’ in Python 2 and ‘tkinter’ in Python 3. The following are valid statements for importing Tkinter module in Python 3:
from tkinter import *
import tkinter as tk
In the third example above, ‘tk’ is reference defined for the Tk class. This reference can be any identifier. After importing the tkinter module, one needs first to create a GUI application main window. The main window can be created by creating an instance object of the Tk class. For the main window (which is not a child of any other window), the Tk object is created without any arguments. The following are valid statements for creating the main window
root = Tk()
root = tkinter.Tk()
root = tk.Tk() # tk is reference to Tk() class in the import statement
For creating a child window, the instance object with Toplevel() method must be created, which must be passed the parent window (object) as an argument. The following is a valid statement for creating a child window:
LED_window = Toplevel(root)
All window widgets, including the main window supports several methods. Of all methods, the main loop() method is the most important. Until the mainloop() method is called on a window widget, it does not appear on the screen. The mainloop() method starts an infinite loop to run the GUI window, wait for an event to occur, and process the event as long as the window is not closed. The mainloop() method must be the last statement to be called on a window widget. Any other methods called on the widget after mainloop() will not be executed and will return an error. The following are valid statements to start the main window in Tkinter:
root = Tk()
The following are valid statements to start a child window:
LED_window = Toplevel(root)
A window can be destroyed by calling destroy() method. The following is a valid statement to destroy a window (named window):
The style and behavior of a window can be changed by calling different methods. For example, for setting the title of the window, the title() method is there. The following is a valid statement to set the title of a window (named window):
window.title(“Raspberry Pi Embedded Electronics Control App”)
To set the size and position of the window on the screen, the geometry() method can be used. The geometry method can take four optional arguments as follows:
The following is a valid statement for setting the size and position of a window (named window):
All the four arguments – width of the window, the height of the window, horizontal position, and vertical position accept integer values in pixels. If the horizontal position has a leading plus sign, the left edge of the window is that many pixels from the left edge of the screen. If the horizontal position has a leading minus sign, the right edge of the window is that many pixels from the right edge of the screen. Similarly, if the vertical position has a leading plus sign, top edge of the window is that many pixels from the top edge of the screen, and if the vertical position has a leading minus sign, the bottom edge of the window is that many pixels from the bottom edge of the screen.
The minimum and maximum size of a window can also be set using the minsize() and maxsize() methods. Both methods take the width and height of the window in pixels as arguments. The following is a valid statement for setting minimum size of a window (named window) –
A window is by default resizable, and the user can change the size of the window by dragging its edges. The resizing behavior of a window can be changed using the resizable() method. The method takes two Boolean arguments to set the width and height of the window resizable to True or False. Tkinter module allows creating static interfaces. When the user resizes a window, its layout can get clumsy due to the static nature of the interface. So, it is recommended to set the resize ability of a window to False as follow –
A window is automatically resized according to child widgets contained in it. If the maximum size is set for a window, some of the child widgets may not be visible. So, it is recommended never to set the maximum size of a window.
A window can have several states like normal (displayed on the screen in its default size and position), zoomed (displayed covering entire screen), icon (minimized to the taskbar), or withdrawn (window closed). By default, the window appears in its ‘normal’ state. If it is set to resizable both horizontally and vertically, it can be zoomed to full-screen size by clicking on the maximize button. It can be zoomed from within the code by setting its state to ‘zoomed’ as follows:
A window can be minimized to the icon by clicking on the minimize button. It can be minimized to an icon from within the code by using iconify() method or setting its state to ‘icon’ as follows:
A window can be restored to its normal state by clicking its icon from the taskbar. It can be restored to a normal state from within the code by using deiconify() method or setting its state to ‘normal’ as follow –
We will be using ttk themed widgets in our GUI windows. The ttk is a separate module that provides access to Tk themed widgets. It allows separating the code implementing the appearance of the widgets, and the code implementing the behavior of the widgets. So we will need to import ttk separately along with tkinter. The following are valid statements for importing ttk:
from ttk import *
from tkinter import ttk
The following are valid statements to start a blank main window:
from tkinter import *
from tkinter import ttk
root = Tk()
root.title(“RPi Embedded Electronics Control APP”)
This is a blank GUI main window containing no other widgets. Note that the mainloop() method is called in the last statement on the main window.
In the next tutorial, we will learn about tkinter and ttk widgets, and will add a menu to our ‘Raspberry Pi Embedded Electronics Control App’. We will keep adding GUI frames to our app for every embedded electronics component (display devices, sensors, modules, motors, and communication interfaces) that we will control from our app.
Stay connected. You are about to witness a multi-threading approach to embedded applications on Raspberry Pi. We will be controlling embedded circuits through a desktop GUI app while implementing the embedded operations of the microcontroller-way on a single-board computer.