Интерфейсы для встраиваемых устройств на современных Web-технологиях
Автор: Николай Хабаров, Principal IoT/IoMT Architect в DataArt
Начнем с того, что интерфейс необходим любому устройству. Ведь пользователю придется с ним взаимодействовать — значит, нужны какие-то кнопки, рычажки, лампочки или дисплей. Некоторые устройства подключаются через USB-кабель к компьютеру, на который нужно установить драйверы и специальное ПО для управления. Некоторые устройства, построенные на обычном железе для ПК, снабжены собственными дисплеями для демонстрации данных и контрольными панелями. В обоих случаях софт для них обычно пишут на сложных языках программирования вроде C++, а создание автономных или встраеваемых приложений для них требует от разработчика специальных навыков. Программное обеспечение для них, как правило, зависимо от операционной системы, и попытка апгрейда любого компонента устройства (например, замена дисплея на более совершенную модель) часто оборачивается серьезной проблемой.
В статье я постараюсь разобраться, существует ли более удобное решение для создания графических интерфейсов встраиваемых устройств. В первой части обсудим, как сделать UI для устройств с дисплеем. Затем рассмотрим разработку совместимых с ПК девайсов без собственного UI, используя те же самые технологии.
Пользовательский интерфейс из моей библиотеки GUI на GitHub
Electron
Одно из простейших решений для создания интерфейса для вашего устройства — фреймворк Electron. Он позволяет строить десктопные приложения на основе популярных веб-технологий, причем с помощью Electron с этой задачей справится любой JavaScript-разработчик. На базе этого фреймворка построены многие широко известные приложения: Skype, Slack, Atom, Visual Studio Code и т. д. Electron поддерживает архитектуру ARM и полностью подходит для работы со встраеваемыми устройствами. Давайте рассмотрим пример: построим приложение и запустим его на нашем Raspberry Pi.
Что нашему приложению предстоит делать? Конечно, управлять встроенным светодиодом, как и положено всякой программе "hello, world!". Развернем простой бэкенд на Node.js (кстати, я писал код с помощью Atom, опенсорсного редактора на базе того же Electron). Вот что у меня получилось:
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);
Код довольно прост, написан в среде Node.js, использует встроенный HTTP-север. У него есть обработчик запросов, который выполняет два действия: возвращает простую статическую HTML-страницу по GET запросу и переключает встроенный светодиод по POST запросу. Управление диодом осуществляется с помощью sysfs: сначала считываем состояние, а затем включаем или выключаем его. Веб-страница отображает кнопки и отправляет POST запрос при нажатии.
Сохраняем этот код в файле 'backend.js'.
Теперь нужно написать что-то, что будет запускать наш бэкенд и отображать окно браузера. Это еще проще:
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.setFullScreen(true)
win.loadURL('http://127.0.0.1:8080')
win.show();
});
Этот код делает fork процесса и запускает наш 'backend.js' в отдельном процессе. Также он завершает оба процесс после выхода. Затем он создает окно браузера, отображающее данные с нашего бэкенда. Этот файл сохраняется под именем 'main.js'.
Предположим, что на Raspberry Pi уже установлена Node.js (в интернете достаточно пособий, как это сделать). Кроме исходных кодов, нам понадобится файл 'package.json', описывающий проект на Node.js:
{
"main": "main.js",
"dependencies": {
"electron": "^12.0.6"
},
"scripts": {
"start": "electron --no-sandbox ."
}
}
Теперь мы можем открыть терминал и написать:
npm install
Эта команда установит все зависимости, а чтобы запустить само приложение, нужно набрать:
sudo npm run
Обратите внимание, что в случае нашего демо-приложения, нужно запускать команду с привилегиями суперпользователя через sudo. Это необходимо, т. к. мы обращаемся напрямую к sysfs.
После запуска приложения на экране увидим примерно такое изображение:
Вот и все, мы закончили. Если вы нажмете на кнопку на экране, красный светодиод сбоку платы Raspberry PI, который обычно служит индикатором активности, изменит состояние. Простейшее встроенное приложение на базе Electron готово к работе. Конечно, наш код получился несовершенным, управление диодом можно было наладить с помощью какой-нибудь Node.js-библиотеки (кстати, для той же Raspberry Pi их очень много в npm), а HTML-страница могла бы выглядеть посимпатичнее, да и сохранить бы ее в отдельный файл. Само приложение можно упаковать в виде исполняемого файла. Но я вполне уверен, что с этим-то вы и сами справитесь, потому решил сосредоточиться именно на самом простом варианте.
Kiosk Mode
Можно ли еще проще? Конечно, можно. Возьмем файл 'backend.js' из приложения, которое мы делали с помощью Electron. Просто запустим его:
sudo node backend.js
Теперь запускайте браузер Firefox в режиме Kiosk с другого терминала. Кстати, Firefox можно установить так, он доступен в пакетах:
sudo apt install firefox-esr
Так выглядит команда в Raspian, официальной операционной системе Raspberry Pi:
firefox --kiosk --private-window http://127.0.0.1:8080
Эта команда запускает Firefox в режиме Kiosk, в этом режиме окно браузера развернуто на весь экран и все элементы управления скрыты. Т. е. видимой оказывается только HTML-страница с указанным URL — речь о той же странице, которая показана на иллюстрации в предыдущем разделе. В этом случае она и ведет себя точно так же: контролирует красный диод. В некоторых случаях это решение может быть предпочтительнее Electron, особенно, если вам нужно просто отобразить страницу из интернета или локальной сети.
mDNS-адрес в веб-браузере из встроенного ПО микроконтроллера esp8266
mDNS — протокол резолвинга доменных имен, который позволяет присваивать их локальным устройством, как если бы это были домены в интернете. В операционную систему Raspbian для Raspberry Pi такой сервис встроен по умолчанию. Подсоедините Raspberry Pi к локальной сети (через Wi-Fi или Ethernet-кабель) и откройте в браузере на компьютере следующую ссылку:
http://raspberrypi.local:8080/
Конечно, при этом ваше Electron-приложение или просто 'backend.js' должны быть запущены.
Подождите, но ведь мы уже видели такой интерфейс? Верно, и если кликнуть по кнопке, включится или выключится светодиод. Теперь давайте подумаем, действительно ли вашему устройству нужен экран или десктопное приложение? Вы готовы потратить ресурсы на разработку этого приложения и драйверов? Будете ли вы создавать их для каждой из популярных операционных систем: Windows, OSX, Linux? Не забудьте еще и о мобильных девайсах. Правда, на них тоже установлены браузеры, а все современные устройства, предназначенные для конечных пользователей, поддерживают mDNS.
Достойной заменой любому десктопному приложению, обменивающемуся данными с устройствами по кабелю, может служить универсальный сетевой USB-адаптер или Wi-Fi-модуль внутри самого устройства. А весь необходимый софт можно запускать прямо на устройстве с помощью веб-технологий. При этом обновление прошивки и пользовательского приложения становится единым действием, т. к. оба приложения фактически упакованы одно в другое.
Заключение
Почему же мы до сих пор не наблюдаем таких устройств? На самом деле, они есть: такие интерфейсы есть почти у всех роутеров и точек доступа, и они давным-давно отлично работают. Подключение к сети заложено в саму идею подобных девайсов, поэтому создание веб-интерфейсов для них оказалось очевидным решением. Сейчас, когда адаптеры, в том числе беспроводные, стали очень дешевыми, мы без особенных затрат и усилий можем применить эту технологию к носимым устройствам и даже некоторому стационарному оборудованию. Цена обычной SoC не превышает нескольких долларов, а с описанной задачей справится и ESP8266, который своих денег точно стоит.
Как насчет задач, которые необходимо решать в реальном времени? И это не проблема. Помимо самого очевидного пути с установкой дополнительных микроконтроллеров, для действий в реальном времени можно использовать DMA. Вот вам работающий пример — PyCNC, который безо всяких дополнительных микронтроллеров управляет драйверами шаговых электродвигателей.
Но веб-приложение работает медленно! Конечно, это не нативное приложение. Но такая ли низкая скорость у интерфейса вашего роутера? У Slack, Skype или редактора Atom? Три последних примера — такие же приложения на базе Electron, и медленными их не назовешь.
Можно ли использовать такие устройства с мобильными девайсами? Конечно. Внутри крошечного микроконтроллера можно построить даже PWA (progressive web application), которое будет вести себя, как мобильное приложение.
Наконец, ваш исходный код веб-приложение можно спокойно перенести, если в будущем вы решите сменить железо на более современное. Приложение почти всегда продолжает работать и после этого. Это может быть то же приложение на встроенном мониторе вашего устройства или тот же веб-интерфейс для версии без собственного UI.
Надеюсь, эта статья пригодится вам и поможет разрабатывать кроссплатформенные устройства быстрее и с меньшими затратами. Лично я был бы рад видеть вокруг больше девайсов с управлением, основанным на веб-технологиях.