JavaScript / Node.js desktop control console for Arduin robot

  • Tutorial
Today we are making a desktop application with a graphical interface for controlling a robot on Arduin via a serial port. JavaScript in Electron platform with ReactJS + MaterialUI widgets.

image


Now the control panel for your CNC machine is no more difficult to do than write a website.

Previously:

- Part 1: A console in a robot on Arduino
- Part 2: Controlling a robot on Arduino from an application on Node.js

Main links

- Library for a robot: babbler_h
- Library for Node.js: babbler-js
- Babbler widgets for Node.js + ReactJS + MaterialUI: babbler-js-material-ui
- Application examples for babbler-js: babbler-js-demo

Tool Links

- Serial Port in Node.js node-serialport: github.com/EmergingTechnologyAdvisors/node-serialport
- Platform Electron: electron.atom.io
- ReactJS: facebook.github.io/react
- Widgets (components) of MaterialUI for ReactJS: www.material-ui.com/#

Additional links

- NWJS platform: nwjs.io
- Other widgets for React:
github.com/facebook/react / wiki / Complementary-Tools # ui-components

Fast start


1. sewn in Arduino sketch babbler_json_io.ino of previous history

This firmware communicates in JSON format, can flash bulb contains 4 teams: the ping , help , LEDON , LEDoff

2. Swing the control panel:

git clone https://github.com/1i7/babbler-js-demo.git
cd babbler-js-demo/babbler-serial-react
npm install

3. Start the control panel:

./babbler-serial.sh

4. Press the buttons, blink the light:

image

Project structure


The task of the project is to launch the Babbler.js library to communicate with the robot in the Electron environment (launching JavaScript applications in a separate window, based on Google Chrome code), connect ReactJS with MaterialUI widgets for the graphical interface. In general, it’s not more complicated than “Hello World” for the listed projects, but in the process of collecting all these libraries in one application, several problems and nuances were identified, therefore, as a template for new projects, I recommend taking the sources of this example as a basis.

Prerequisites: have node.js, npm and (preferably) git installed on the computer.

Once again, download the sources and go to the babbler-js-demo / babbler-serial-react project :

git clone https://github.com/1i7/babbler-js-demo.git
cd babbler-js-demo/babbler-serial-react

Project files

- package.json - project for npm (Node package manager): project settings, list of dependencies.

Thanks to it, we can install all the dependencies listed in package.json (including the Electron platform), with one command inside the project:

npm install

- main.js - the main file for the Electron application (taken from some "hello world" for the electron).

Content speaks for itself. The only interesting place is the command that opens the panel with development tools when the application starts:

    // Открываем DevTools.
    mainWindow.webContents.openDevTools();

I recommend leaving this call when developing the application (open manually through the menu: View → Toggle Developer Tools) and delete / comment on the release.

- index.html - the contents of the main screen of the Electron application.

Because for the formation of the graphical interface we use React, all that should be inside the body is a div element with id = "app-content"


We will not edit this file, the main code will be further.

- react-app-ui.js - the main application file, here the main widget tree for the main screen (sent to index.html in the div with id = "app-content"), all user code, we only correct it.

- babbler-serial.sh - application startup script

#!/bin/sh
./node_modules/electron-prebuilt/dist/electron .

Additional nuances

For a note

- The problem with node-serialport and Electron

The node-serialport library did not want to work on the latest versions of the Electron platform (despite the fact that everything was fine on the "bare" Node.js).

Without going into details, I note that the problem can be circumvented by rolling back to the old version of Electron 1.1.3 or (as they say in one of the discussions) rebuild it from the source.

sudo npm i -g electron-prebuilt@1.1.3

The same problem is observed in the NWJS platform (an alternative to Electron), obviously something was broken in the Chrome engine on which they are all based.

A working version of Electron is indicated in the project dependencies in package.json, so everything is ok with the demo project.

Messages in project bug trackers:

Node-serialport
Electron
NWJS

Perhaps in one of the following releases the problem will be fixed, in which case it will be possible to switch Electron to a more recent version.

- Babel, JSX and ES6 syntax

ReactJS application and components use special JSX syntax - this is an HTML-like XML to describe the structure of the application control tree directly in JavaScript code. Also inside the application, we will use the extended JavaScript ES6 syntax in some places (this is a set of all kinds of syntactic language constructs that have not yet been included in the JavaScript standard or have entered into it not so long ago, therefore they have not yet been implemented even in the latest versions of browsers). At first, I wanted to exclude ES6 constructs (all of them can be replaced with analogues from the “classic” JavaScript) so as not to make unnecessary configurations in the project. But then he gave up, because many examples on the Internet for ReactJS (and especially MaterialUI) are written using ES6 syntax, and in that case, I would have to convert all of them to the old JavaScript syntax.

In order to use non-standard syntax on the old JavaScript engine, they use a special tool - Babel. He is able to convert custom designs on the fly to their counterparts in regular JavaScript, if the project has the correct settings. Here crutches and a garden begin. In the project template, all the necessary settings are already set, therefore I will not analyze in details, I will list the main points:

- package.json should contain a block with Babel settings:

"babel": { "presets": ["es2015", "react", "stage-1"] }

- Similar settings should be specified in the .babelrc file if you import widgets from directories outside the current directory into the project (for example: babbler-js-meterial-ui / src / .babelrc ).

- To enable the conversion of Babel in script blocks (type = "text / babel") in HTML files (we have index.html), you need to import the browser.min.js script in the same file (local copy in the project: babbler- serial-react / script / browser.min.js , so as not to depend on the Internet).

- To enable the conversion of Babel in separate js files, you need to load the 'babel-register' module, and download the js files themselves through require ('./ react-app-ui.js'); (see all the same index.html).

Maybe after some time, ES6 innovations will migrate to the main JavaScript branch in the Google Chrome version (and from there to Electron) and some of these props can be thrown out as unnecessary.

- For MaterialUI widgets to work in index.html, you need to load the 'react-tap-event-plugin' module and execute injectTapEventPlugin ()

We look at source codes


All useful user code is located in one file - babbler-js-demo / babbler-serial-react / react-app-ui.js

Here we create our component - a robot control panel with bulbs, as well as form a control tree for the main screen .

Preliminary preparations

Base React Objects:

var React = require('react');
var ReactDOM = require('react-dom');

MaterialUI widgets - buttons, icons, tabs, panels:

// виджеты 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 widgets from the babbler-js-material-ui project - interaction with the device:

- BabblerConnectionPanel - connection panel: select devices from the drop-down list, connect / disconnect buttons (depending on connection status)
- BabblerConnectionStatusIcon - device connection status icon: disconnected, connect, connect
- BabblerConnectionErrorSnackbar - a pop-up panel at the bottom of the screen notifying of a disconnection and other connection errors
- BabblerDataFlow - full log in real time: adding a command to the queue, exchanging data with the device, etc.
- BabblerDebugPanel (not yet defined in the library, but inside the test project) - debug panel: sending commands to the device manually, help, ping buttons, log with 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 to communicate with the device:

// Babbler.js
import BabblerDevice from 'babbler-js';

Button Style:

const btnStyle = {
  margin: 12
};

Finally, the most interesting part is communication with the robot, the control panel of the bulb

The panel is a regular React component: the “Turn on the light bulb” button, the “Turn off the light bulb” button, and the light bulb status icon.

You should know about React components:

- The React component works as a state machine (state machine).
- The current state of the component is determined by two groups of values: the static properties of this.props and the dynamic states of this.state .
- Static properties of this.props : passed through the parameters of the component tag when adding it to the screen.
- The dynamic states of this.state : change during the execution of the application, set at the right time using this.setState .
- State changes throughthis.setState redraws the component.
- The component is redrawn in the render function , the appearance depends on the values ​​of this.props and this.state.
- The appearance of the component inside render is defined through the React JSX syntax.

In our case:

- The BabblerDevice object enters the component through the static parameter this.props.babblerDevice .
- The babblerDevice events change the dynamic states of the component (if connected, we make all buttons active, if not connected, we make inactive).
- The “Turn on the light” and “Turn off the light” buttons send the ledon and ledoff commandsdevice via babblerDevice.
- In the case of a positive response, “ok” change the picture of the status of the bulb by writing the value of the property 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 (
            
{(this.state.ledOn ? "sentiment_very_satisfied" : "sentiment_very_dissatisfied")}
); }, 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) ); } });

Application Home Screen

Create a BabblerDevice device to connect to the robot:

// Устройство Babbler, подключенное к последовательному порту
var babblerDevice1 = new BabblerDevice();

The final layout of the main screen of the application is the ReactJS JSX syntax (HTML-like XML inside JavaScript code). We draw a tree of controls, send to index.html in a div with id = 'app-content'.

Here we have a panel for connecting to the device - a bar at the top, communication blocks with the robot - inside the tabs.

// Контент приложения
ReactDOM.render(
    
, document.getElementById('app-content') );

We launch


./babbler-serial.sh

Choose a device:

image

Connect:

image

Wait:

image

Turn on the light:

image

Turn off the light:

image

We look at the log:

image

Team helmet in manual mode:

image

image


Also popular now: