The user interface is an essential part of any device. A device must communicate with the user through switches, lights, or screens. Devices are usually connected to a PC with a USB cable; software drivers must be installed and special software is necessary to interact with the user. Some devices are built based on regular PC hardware and have their own screens to display data and control functions. In both cases, the software is typically written using complicated languages such as C++ and developers need specialized skills to develop standalone or embedded applications. Such applications are also OS dependent and can be quite sophisticated when hardware components, the display for example, are upgraded.
In this article we will explore methods to build GUI for embedded devices. Initially, we will discuss the possibility of creating headless devices with PC connectivity. Moving on, we’ll consider implementing UI for onboard display using the same technologies.
Electron
Electron offers one of the simplest ways to create an interface for a device. This framework makes it possible to build desktop applications using popular web technologies. Any JS developer could easily build a desktop application with Electron. In fact, many popular applications are based on this framework – Skype, Slack, and Visual Studio Code as well as many other popular desktop applications. Electron supports ARM architecture and can be used with embedded devices. Let’s create an example using Electron and run it on our raspberry pi.
What will the application do? It will control the LED on the board like any other “hello world” application. We’ll implement a simple backend using Node.js. Notably, this code was written using Atom, an open-source editor that is also based on Electron. The code is:
const http = require('http');
const fs = require('fs');
const requestListener = function (req, res) {
switch (req.method) {
case "POST":
state = fs.readFileSync('/sys/class/leds/led1/brightness').toString().trim();
fs.writeFileSync('/sys/class/leds/led1/brightness', (state == '0') ? '1' : '0');
default:
res.setHeader("Content-Type", "text/html");
res.writeHead(200);
res.end(`
<html>
<body>
<h1>Hello, world! I am an embedded web app</h1>
<form action="" method="post">
<button>Click me</button></form>
</body>
</html>
`);
}
}
fs.writeFileSync('/sys/class/leds/led1/trigger', 'none');
const server = http.createServer(requestListener);
server.listen(8080);
This code using Node.js and built in an http server is quite simple. It has a request handler that performs two functions. It returns a simple static HTML page on a GET request and a toggle on board LED on a POST request. The LED control is implemented using sysfs. It reads LED state and switches it. The web page displays a button and sends a post request when the user clicks the button.
Now save this code to ‘backend.js’ file.
Next, we need some code that runs our backend and displays the browser window. This is even simpler:
const { fork } = require('child_process');
const backend_process = fork(__dirname + '/backend.js')
const { app, BrowserWindow } = require('electron');
process.on('exit', backend_process.kill)
app.on('ready', function() {
var win = new BrowserWindow({
show: false,
});
win.maximize();
win.loadFile('index.html');
win.setFullScreen(true)
win.loadURL('http://127.0.0.1:8080')
win.show();
});
The code forks the process and runs our ‘backend.js’ in another process. It also kills the backend process on exit. Then it creates a browser window, which is pointed to the backend. This file is saved as ‘main.js’.
Let us assume that Node.js is already installed on raspberry pi; it’s easy to find instructions for that installation on the Internet. Now we need a simple ‘package.json’ file that describes the Node.js project:
{
"main": "main.js",
"dependencies": {
"electron": "^12.0.6"
},
"scripts": {
"start": "electron --no-sandbox ."
}
}
Now open the terminal and type:
npm install
to install Electron and then we can run the app:
sudo npm run
We need ‘sudo’ in order to have access to sysfs files. Once the command was typed and run, we see this screen:
The app is ready. If you click on the button, the red LED (that typically shows activity) on the side of the Raspberry Pi board triggers state. The simplest embedded Electron app is ready! Of course, the code is not perfect, and the LED control can be implemented using a Node.js library, while the HTML page could be more beautiful and not inserted into the js file. The app itself can be packed as an executable. While this may seem elementary, the point is to illustrate the simplest case.
Kiosk mode
Is it possible to make the process even easier? Let’s take just the ‘backend.js’ file from the Electron app above. Now start it with:
sudo node backend.js
Then, run the Firefox web browser in kiosk mode from another terminal. Firefox can be installed with:
sudo apt install firefox-esr
command in the official Raspberry Pi Raspian operating system:
firefox --kiosk --private-window http://127.0.0.1:8080
This command runs the Firefox browser in kiosk mode; in full screen mode all controls are hidden. Therefore, only HTML from the specified URL is visible. And that is the same page that was on the Electron app example and it behaves in the same way, by controlling the red LED. In some cases it can be a better solution than Electron, particularly if you only need to display a remote web page from the Internet or a local network.
mDNS
mDNS is the multicast domain name service. It allows you to have a domain name for local devices, just like domain names on the Internet. Raspberry Pi Raspbian OS already has a preinstalled service for that. Just connect your Raspberry Pi to the local network (with Wi-Fi or an Ethernet cable) and open this link in a browser:
http://raspberrypi.local:8080/
Of course, the Electron app or just ‘backend.js’ will be running.
That’s the same interface we saw on board itself! If the button is clicked there, the LED changes its state. Does your device really need a screen or desktop app? Do you really want to spend resources on device drivers and developing desktop applications? There are several popular operating systems, such as Windows, OSX, and Linux. Would you do it for each of these? There are also mobile devices, which all have web browsers. All modern end user devices support mDNS.
Instead of a USB interface with a custom HID profile, OS dependent drivers, and user software, it’s possible to use a universal network adapter USB device or Wi-Fi and access all software right from the device with web technologies. This would work on any end user desktops as well as on mobile devices. To update software and device firmware, it’s possible to add a firmware upgrade page and all the components: firmware and software will be always compatible.
Conclusion
Almost all home routers and access points have this type of web interface, which works well over time. Home routers have network capability by design, which is why adding a web interface was an obvious and straightforward solution. Today even wireless network adapters are inexpensive. Even a cost-efficient microcontroller like ESP8266 could handle a web interface. It’s time to use this technology for wearables and even in devices such as lab equipment.
What about real time tasks? It is totally not an issue. Except the most obvious solution to use additional real time microcontrollers next to Linux CPU, it is possible to use DMA in order to do actions in real time. One example is PyCNC, which controls stepper motors drivers in real time without additional microcontrollers.
It is slow! Of course, it is not a native application. But does the router web interface slow? Slack? Skype? Atom editor? These three apps and many others are Electron based apps. It is the same web technology. And they are not slow, are they?
Can you use such devices with mobile devices? Of course, you can! It is even possible to build PWA (progressive web application) inside a tiny microcontroller, which will behave like a mobile app, and there is no need to publish this app in any mobile stores.
And of course, your codebase and web app can move regardless of future changes in hardware, almost always the web app will continue to work with your device’s mode line. It could be the same app on an embedded monitor of your device and the same web interface for a headless version of your device.
We hope this article was useful for you and it would help you to build cross platform devices for your users and it would save you development time and cost. We also would be happy to see more devices based on web technologies.
By Nikolay Khabarov, Embedded and IoT Expert at DataArt.