Arduino SerialPort GUI built with Electron + Vue
Have you ever wanted to control or configure an Arduino board using a user-friendly graphical interface? In this blog post, I will explain how to build a GUI with Electron (a popular framework for building cross-platform desktop applications) and Vue. With this application, you’ll be able to list Serial Devices and easily send data to an Arduino with a click of a button.
Preview
For your knowledge
The Electron framework lets you write cross-platform desktop applications using JavaScript, HTML and CSS. It is based on Node.js and Chromium and is used by Atom, GitHub Desktop, Visual Studio Code, WordPress Desktop, Eclipse Theia and many other apps. You can also use community-supported tooling to generate platform-specific tooling like Apple Disk Image (.dmg) on macOS, Windows Installer (.msi) on Windows, or RPM Package Manager (.rpm) on Linux.
If you are not familiar with Electron, I recommend you to read the Quick Start guide from Electron before continuing with this tutorial.
Project
You can find the code for this project on my Github.
Getting started
If you want to use my project as a base for another project, you can start by cloning it and installing all Node.js (install if you don’t have) packages:
git clone https://github.com/ddavidmelo/electron-arduino.git
cd electron-arduino
npm install
To run the application use the command:
npm run electron:serve
If you want to learn on your own and start a project from scratch, you’ll need to have Node.js and Vue already installed. To begin, create a new Vue project:
vue create electron-arduino [Select Vue3]
In the project folder (cd /electron-arduino
) add the following plugins:
vue add quasar (Vue popular framework)
vue add electron-builder@alpha (Electron)
vue add router (for mapping URL routes to specific components)
vue add vuex (centralized store for all the components in an application)
npm install --save serialport (to communicate via a COM port)
To run the application use the command:
npm run electron:serve
You should see an output similar to the picture below:
Arduino serial commands
For this project I will use an Arduino to receive AT commands from the GUI interface. For that, I will use Arduino Nano 33 BLE Sense. You can find the code in here.
Supported commands [?=1 Turn ON, ?=0 Turn OFF
]:
AT+LED=LEDR:?
AT+LED=LEDG:?
AT+LED=LEDB:?
AT+LED=LED_BUILTIN:?
AT+LED=LED_PWR:?
Other devices can be supported, you only need to program them to accept these commands.
Electron
Configuration
Electron builder can be configured by adding a build section to the vue.config.js file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
electronBuilder: {
nodeIntegration: true,
externals: ['serialport'],
customFileProtocol: './',
builderOptions: {
appId: 'test.com',
win: {
icon: 'public/icon.png',
target: "portable"
},
linux: {
icon: 'public/icon.icns',
}
},
}
...
Remove menubar
Electron by default adds a menu bar on top of the window. To remove that you need to set autoHideMenuBar to true.
1
2
3
4
5
6
7
8
9
...
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
autoHideMenuBar: true,
icon: './public/favicon.ico',
})
}
...
Vue device list
In the DeviceList Vue component is where I list the devices that are connected. The method listSerialPorts() sets the serialports data variable with a list of available serial ports. The method uses the SerialPort library to retrieve a list of ports and filters the ports based on their productId and vendorId properties. It only retrieves devices with valid productId and vendorId. The noDevices data variable is used to show a legend saying that are no devices connected.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
methods: {
async listSerialPorts() {
await SerialPort.list().then((ports, err) => {
if (err != null || err == undefined) {
let devices = [];
for (const device of ports) {
if (device.productId != undefined && device.vendorId != undefined) {
devices.push(device);
}
}
if(devices.length == 0) {
this.noDevices = true;
} else {
this.noDevices = false;
}
this.serialports = devices;
console.log('ports: ', ports);
}
})
}
}
...
Vue set device
After selecting a device. Is necessary to store the selected port. For that, the method setDevice sets device port and navigates to a new route.
1
2
3
4
5
6
7
8
...
setDevice(port) {
this.$store.commit('setPort', port); // Vue store
this.$router.push({
name: "device"
});
}
...
this.$store.commit('setPort', port);
I forgot to mention earlier, the port object format is the same as the default_port template object.
Vuex implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
const default_port = {
locationId: "",
manufacturer: "",
path: "_",
pnpId: "",
productId: "",
serialNumber: "",
vendorId: ""
}
export default createStore({
state: {
port: default_port,
},
getters: {
port(state) {
return state.port;
},
},
mutations: {
setPort(state, port) {
state.port = port
},
}
})
Vue serial port functions
After selecting the device port, it is necessary to open the port and proceed with the methods for sending commands and check if the port is still open.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
...
const serialports = [];
methods: {
writeCommand(command) {
serialport.write(command, function (err) {
if (err) {
return console.log('Error on write: ', err.message)
}
console.log(command)
})
},
checkPortStatus() {
const parser = new ReadlineParser()
serialport.pipe(parser)
if (serialport.isOpen) {
this.portClosed = false;
} else {
this.portClosed = true;
}
}
},
mounted() {
console.log("open serial: ", this.$store.state.port.path);
serialport = new SerialPort({ path: this.$store.state.port.path, baudRate: 9600 })
this.timer = setInterval(() => {
this.checkPortStatus()
}, 500)
},
unmounted() {
clearInterval(this.timer)
serialport.close()
console.log("close serial: ", this.$store.state.port.path);
}
...
Build
In order to build the project for distribution your package.json should look something like this:
1
2
3
4
5
6
7
8
9
...
"name": "electron-arduino",
"version": "0.1.0",
"private": true,
"homepage": ".",
"maintainer": "electron-arduino",
"email": "electron-arduino@electron.com",
"author": "electron-arduino, Inc <electron-arduino@electron.com>",
...
To build the Electron application for both Linux and Windows, you need to run the following command:
npm run electron:build -- --linux deb --win nsis
In the dist_electron folder you will find a Debian package (.deb) and a portable app (.exe) for Windows.
Note: To install the .deb package you need to run this command
sudo dpkg -i <debfilename>