- PVSM.RU - https://www.pvsm.ru -
Сегодня делаем настольное приложение с графическим интерфейсом для управления роботом на Ардуине через последовательный порт. На языке JavaScript на платформе Electron с виджетами ReactJS+MaterialUI.
Теперь пульт управления для своего станочка с ЧПУ сделать не сложнее, чем написать сайтик.
ранее
— часть 1: Консолька в роботе на Ардуине [1]
— часть 2: Управление роботом на Ардуино из приложения на Node.js [2]
Главные ссылки
— Библиотека для робота: babbler_h [3]
— Библиотека для Node.js: babbler-js [4]
— Виджеты Babbler для Node.js+ReactJS+MaterialUI: babbler-js-material-ui [5]
— Примеры приложений для babbler-js: babbler-js-demo [6]
Ссылки на инструменты
— Последовательный порт в Node.js node-serialport: github.com/EmergingTechnologyAdvisors/node-serialport [7]
— Платформа Electron: electron.atom.io [8]
— ReactJS: facebook.github.io/react [9]
— Виджеты (компоненты) MaterialUI для ReactJS: www.material-ui.com/# [10]
Дополнительные ссылки
— Платформа NWJS: nwjs.io [11]
— Другие виджеты для React:
github.com/facebook/react/wiki/Complementary-Tools#ui-components [12]
1. Прошивайте в Ардуино скетч babbler_json_io.ino [13] из предыдущей истории [2]
Эта прошивка обменивается данными в формате JSON, умеет мигать лампочкой, содержит 4 команды: ping, help, ledon, ledoff
2. Качайте пульт управления
git clone https://github.com/1i7/babbler-js-demo.git
cd babbler-js-demo/babbler-serial-react
npm install
3. Запускайте пульт управления
./babbler-serial.sh
4. Нажимайте на кнопочки, мигайте лампочкой
Задача проекта — запустить библитеку Babbler.js для общения с роботом в окружении Electron (запускалка приложений JavaScript в отдельном окне, основана на коде Google Chrome), для графического интерфейса подключить ReactJS с виджетами MaterialUI. В общем, все не сложнее «Здравствуй мира» для перечисленных проектов, но в процессе собирания всех этих библиотек в одном приложении было выявлено несколько проблем и нюансов, поэтому в качестве шаблона новых проектов рекомендую брать за основу исходники этого примера.
Предварительные требования: иметь на компьютере установленными node.js, npm и (желательно) git.
еще раз, качаем исходники и идем в проект babbler-js-demo/babbler-serial-react [14]
git clone https://github.com/1i7/babbler-js-demo.git
cd babbler-js-demo/babbler-serial-react
Файлы проекта
— package.json [15] — проект для npm (Node package manager): настройки проекта, список зависимостей.
Благодаря ему мы можем установить все зависимости, перечисленные в package.json (включая платформу Electron), одной командой внутри проекта
npm install
— main.js [16] — главный файл для приложения Electron (взят из какого-то «здравствуй мир» для электрона).
Содержимое говорит само за себя. Единственное интересное место — команда, открывающая панель с инструментами разработки при старте приложения:
// Открываем DevTools.
mainWindow.webContents.openDevTools();
Рекомендую оставлять этот вызов при разработке приложения (открыть вручную через меню: View → Toggle Developer Tools) и удалять/комментировать при релизе.
— index.html [17] — содержимое главного экрана приложения Electron.
Т.к. для формирования графического интерфейса мы используем React, всё, что должно быть внутри body, — элемент div с id=«app-content»
<body>
<div id="app-content"></div>
</body>
Этот файл мы править не будем, главный код будет дальше.
— react-app-ui.js [18] — главный файл приложения, здесь главное дерево виджетов для главного экрана (отправляется в index.html в div с id=«app-content»), весь пользовательский код, правим только его.
— babbler-serial.sh [19] — скрипт-запускалка приложения
#!/bin/sh
./node_modules/electron-prebuilt/dist/electron .
Дополнительные нюансы
для заметки
— Проблема с node-serialport и Electron
Библиотека node-serialport не захотела работать на последних версиях платформы Electron (при том, что на «голом» Node.js всё было прекрасно).
Не вдаваясь в подробности, отмечу, что проблему можно обойти, откатившись на старую версию Electron 1.1.3 или (как пишут в одном из обсуждений) пересобрать его из исходников.
sudo npm i -g electron-prebuilt@1.1.3
Эта же проблема наблюдается в платформе NWJS (альтернатива Electron), очевидно, что-то поломали в движке Хрома, на котором они все основаны.
Работающая версия Electron указана в зависимостях проекта в package.json, поэтому с демо-проектом всё ок.
Сообщения в баг-трекерах проектов:
Node-serialport: github.com/EmergingTechnologyAdvisors/node-serialport/issues/838 [20]
Electron: github.com/electron/electron/issues/6074 [21]
NWJS: github.com/nwjs/nw.js/issues/5035 [22]
Возможно, в одном из следующих релизов проблема будет исправлена, в таком случае можно будет переключить Electron на более свежую версию.
— Babel, синтаксис JSX и ES6
Приложение и компоненты ReactJS используют специальный синтаксис JSX — это HTML-подобный XML для описания структуры дерева элементов управления приложения прямо в коде JavaScript. Так же внутри приложения мы будем местами использовать расширенный синтаксис JavaScript ES6 (это набор всевозможных синтаксических конструкций языка, которые еще не вошли в стандарт JavaScript или вошли в него не так давно, поэтому пока не реализованы даже в самых свежих версиях браузеров). Сначала я хотел исключить конструкции ES6 (их все можно заменить на аналоги из «классического» JavaScript), чтобы не городить лишних конфигураций в проекте. Но потом сдался, т.к. многие примеры в интернете для ReactJS (и, в особенности, для MaterialUI) написаны с использованием синтаксиса ES6, и, в таком случае, мне бы пришлось всех их конвертировать в старый синтаксис JavaScript.
Для того, чтобы использовать нестандартный синтаксис на старом движке JavaScript, используют специальный инструмент — Babel. Он умеет на лету конвертировать нестандартные конструкции в их аналоги на обычном JavaScript, если в проекте задать правильные настройки. Здесь начинаются костыли и огород. В шаблоне проекта все необходимые настройки уже заданы, поэтому в подробности разбирать не буду, перечислю основные пункты:
— package.json должен содержать блок с настройками Babel:
"babel": { "presets": ["es2015", "react", "stage-1"] }
— Аналогичные настройки нужно указать в файле .babelrc, если в проект импортируете виджеты из каталогов за пределами текущего каталога (например: babbler-js-meterial-ui/src/.babelrc [23])
— Чтобы включить конвертацию Babel в блоках script (type=«text/babel») в HTML-файлах (у нас — index.html), в этом же файле нужно импортировать скрипт browser.min.js [24] (локальная копия в проекте: babbler-serial-react/script/browser.min.js [25], чтобы не зависеть от интернета)
— Чтобы включить конвертацию Babel в отдельных js-файлах, нужно загрузить модуль 'babel-register', а сами js-файлы загружать через require('./react-app-ui.js'); (см всё тот же index.html).
Может быть через какое-то время новации ES6 перекочуют в основную ветку JavaScript в варианте Гугл Хрома (а оттуда — в Электрон) и часть этих подпорок можно будет выкинуть за ненадобностью.
— Для работы виджетов MaterialUI в index.html необходимо загрузить модуль 'react-tap-event-plugin' и выполнить injectTapEventPlugin()
Весь полезный пользовательский код расположен в одном файле — babbler-js-demo/babbler-serial-react/react-app-ui.js [26]
Здесь мы создаем свой компонент — панель управления роботом с лампочками, а так же формируем дерево элементов управления для главного экрана.
Базовые объекты Реакта
var React = require('react');
var ReactDOM = require('react-dom');
Виджеты MaterialUI — кнопки, иконки, табы, панельки
// виджеты MaterialUI
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import Paper from 'material-ui/Paper';
import {Tabs, Tab} from 'material-ui/Tabs';
import Divider from 'material-ui/Divider';
import RaisedButton from 'material-ui/RaisedButton';
import FontIcon from 'material-ui/FontIcon';
import {red200, green200} from 'material-ui/styles/colors';
import Subheader from 'material-ui/Subheader';
Виджеты Babbler из проекта babbler-js-material-ui [5] — взаимодействие с устройством:
— BabblerConnectionPanel — панель подключения: выбор устройств из выпадающего списка, кнопки подключиться/отключиться (в зависимости от статуса подключения)
— BabblerConnectionStatusIcon — иконка статуса подключения к устройству: отключены, подключаемся, подключены
— BabblerConnectionErrorSnackbar — всплывающая внизу экрана панелька, извещающая о разрыве соединения и других ошибках подключения
— BabblerDataFlow — полный лог в реальном времени: добавление команды в очередь, обмен данными с устройством и т.п.
— BabblerDebugPanel (пока определен не в библиотеке, а внутри тестового проекта) — панель отладки: отправка команд устройству вручную, кнопки help, ping, лог с BabblerDataFlow
// виджеты Babbler MaterialUI
import BabblerConnectionStatusIcon from 'babbler-js-material-ui/lib/BabblerConnectionStatusIcon';
import BabblerConnectionErrorSnackbar from 'babbler-js-material-ui/lib/BabblerConnectionErrorSnackbar';
import BabblerConnectionPanel from 'babbler-js-material-ui/lib/BabblerConnectionPanel';
import BabblerDataFlow from 'babbler-js-material-ui/lib/BabblerDataFlow';
import BabblerDebugPanel from './widgets/BabblerDebugPanel';
Babbler.js для связи с устройством
// Babbler.js
import BabblerDevice from 'babbler-js';
Стиль для кнопочек
const btnStyle = {
margin: 12
};
Панель — обычный компонент React: кнопка «Включить лампочку», кнопка «Выключить лампочку», иконка статуса лампочки.
про компоненты React следует знать
— Компонент React работает как машина состояний (стейт-машина).
— Текущее состояние компонента определяют две группы значений: статические свойства this.props и динамические состояния this.state.
— Статические свойства this.props: передаются через параметры тега компонента при добавлении его на экран.
— Динамические состояния this.state: меняются в процессе выполнения приложения, устанавливаются в нужный момент при помощи this.setState.
— Изменения состояний через this.setState приводит к перерисовке компонента.
— Перерисовка компонента происходит в функции render, внешний вид зависит от значений this.props и this.state.
— Внешний вид компонента внутри render определяется через синтаксис React JSX.
в нашем случае
— Объект BabblerDevice попадает в компонент через статический параметр this.props.babblerDevice.
— События babblerDevice меняют динамические состояния компонента (если подключены, делаем все кнопки активными, если не подключены — делаем неактивными).
— Кнопки «Включить лампочку» и «Выключить лампочку» отправляют команды ledon и ledoff устройству через babblerDevice.
— В случае получения положительного ответа «ok» меняют картинку статуса лампочки через запись значения свойства this.state.ledOn (true/false).
// Управление лампочкой
var BabblerLedControlPnl = React.createClass({
// http://www.material-ui.com/#/components/raised-button
// http://www.material-ui.com/#/components/subheader
getInitialState: function() {
return {
deviceStatus: this.props.babblerDevice.deviceStatus(),
ledOn: false
};
},
componentDidMount: function() {
// слушаем статус устройства
this.deviceStatusListener = function(status) {
this.setState({deviceStatus: status});
}.bind(this);
this.props.babblerDevice.on(BabblerDevice.Event.STATUS, this.deviceStatusListener);
},
componentWillUnmount: function() {
// почистим слушателей
this.props.babblerDevice.removeListener(BabblerDevice.Event.STATUS, this.deviceStatusListener);
},
render: function() {
var connected = this.state.deviceStatus === BabblerDevice.Status.CONNECTED ? true : false;
return (
<div style={{textAlign: "center"}}>
<div>
<RaisedButton label="Включить лампочку" onClick={this.cmdLedon} disabled={!connected} style={btnStyle} />
<RaisedButton label="Выключить лампочку" onClick={this.cmdLedoff} disabled={!connected} style={btnStyle} />
</div>
<FontIcon
className="material-icons"
style={{fontSize: 160, marginTop: 40}}
color={(this.state.ledOn ? green200 : red200)}
>{(this.state.ledOn ? "sentiment_very_satisfied" : "sentiment_very_dissatisfied")}</FontIcon>
</div>
);
},
cmdLedon: function() {
this.props.babblerDevice.sendCmd("ledon", [],
// onReply
function(cmd, params, reply) {
if(reply == 'ok') {
this.setState({ledOn: true});
}
}.bind(this),
// onError
function(cmd, params, err) {
console.log(cmd + (params.length > 0 ? " " + params : "") + ": " + err);
}.bind(this)
);
},
cmdLedoff: function() {
this.props.babblerDevice.sendCmd("ledoff", [],
// onReply
function(cmd, params, reply) {
if(reply == 'ok') {
this.setState({ledOn: false});
}
}.bind(this),
// onError
function(cmd, params, err) {
console.log(cmd + (params.length > 0 ? " " + params : "") + ": " + err);
}.bind(this)
);
}
});
Создаем устройство BabblerDevice для подключения к роботу
// Устройство Babbler, подключенное к последовательному порту
var babblerDevice1 = new BabblerDevice();
Финальная верстка главного экрана приложения — синтаксис ReactJS JSX (HTML-подобный XML внутри кода JavaScript). Рисуем дерево элементов управления, отправляем в index.html в div с id='app-content'.
Здесь у нас панель подключения к устройству — полоска наверху, блоки общения с роботом — внутри табов.
// Контент приложения
ReactDOM.render(
<MuiThemeProvider muiTheme={getMuiTheme()}>
<div>
<Paper>
<BabblerConnectionPanel babblerDevice={babblerDevice1}/>
<BabblerConnectionStatusIcon
babblerDevice={babblerDevice1}
iconSize={50}
style={{position: "absolute", right: 0, marginRight: 14, marginTop: 5}} />
</Paper>
<Divider style={{marginTop: 20, marginBottom: 20}}/>
<Tabs>
<Tab label="Лампочки" >
<BabblerLedControlPnl babblerDevice={babblerDevice1}/>
</Tab>
<Tab label="Отладка" >
<BabblerDebugPanel babblerDevice={babblerDevice1}/>
</Tab>
<Tab label="Лог" >
<BabblerDataFlow
babblerDevice={babblerDevice1}
reverseOrder={true}
maxItems={10000}
timestamp={true}
// filter={{ err: false, data: false }}
// filter={{ data: {queue: false} }}
// filter={{ err: {in: false, out: false, queue: false}, data: {in: false, out: false, queue: false} }}
style={{margin: 20}}/>
</Tab>
</Tabs>
<BabblerConnectionErrorSnackbar babblerDevice={babblerDevice1}/>
</div>
</MuiThemeProvider>,
document.getElementById('app-content')
);
./babbler-serial.sh
выбираем устройство
подключаемся
ждем
включаем лампочку
выключаем лампочку
смотрим лог
шлем команды в ручном режиме
Автор: sadr0b0t
Источник [27]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/214435
Ссылки в тексте:
[1] Консолька в роботе на Ардуине: https://habrahabr.ru/post/315084/
[2] Управление роботом на Ардуино из приложения на Node.js: https://habrahabr.ru/post/315480/
[3] babbler_h: https://github.com/1i7/babbler_h
[4] babbler-js: https://github.com/1i7/babbler-js
[5] babbler-js-material-ui: https://github.com/1i7/babbler-js-material-ui
[6] babbler-js-demo: https://github.com/1i7/babbler-js-demo
[7] github.com/EmergingTechnologyAdvisors/node-serialport: https://github.com/EmergingTechnologyAdvisors/node-serialport
[8] electron.atom.io: http://electron.atom.io/
[9] facebook.github.io/react: https://facebook.github.io/react/
[10] www.material-ui.com/#: http://www.material-ui.com/#/
[11] nwjs.io: http://nwjs.io/
[12] github.com/facebook/react/wiki/Complementary-Tools#ui-components: https://github.com/facebook/react/wiki/Complementary-Tools#ui-components
[13] babbler_json_io.ino: https://github.com/1i7/babbler_h/blob/master/babbler_h/examples/babbler_json_io/babbler_json_io.ino
[14] babbler-serial-react: https://github.com/1i7/babbler-js-demo/tree/master/babbler-serial-react
[15] package.json: https://github.com/1i7/babbler-js-demo/blob/master/babbler-serial-react/package.json
[16] main.js: https://github.com/1i7/babbler-js-demo/blob/master/babbler-serial-react/main.js
[17] index.html: https://github.com/1i7/babbler-js-demo/blob/master/babbler-serial-react/index.html
[18] react-app-ui.js: https://github.com/1i7/babbler-js-demo/blob/master/babbler-serial-react/react-app-ui.js
[19] babbler-serial.sh: https://github.com/1i7/babbler-js-demo/blob/master/babbler-serial-react/babbler-serial.sh
[20] github.com/EmergingTechnologyAdvisors/node-serialport/issues/838: https://github.com/EmergingTechnologyAdvisors/node-serialport/issues/838
[21] github.com/electron/electron/issues/6074: https://github.com/electron/electron/issues/6074
[22] github.com/nwjs/nw.js/issues/5035: https://github.com/nwjs/nw.js/issues/5035
[23] .babelrc: https://github.com/1i7/babbler-js-material-ui/blob/master/src/.babelrc
[24] browser.min.js: https://unpkg.com/babel-core@5.8.38/browser.min.js
[25] browser.min.js: https://github.com/1i7/babbler-js-demo/tree/master/babbler-serial-react/script
[26] react-app-ui.js: https://github.com/1i7/babbler-js-demo/tree/master/babbler-serial-react/react-app-ui.js
[27] Источник: https://habrahabr.ru/post/316194/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.