📜 ⬆️ ⬇️

Managing the robot at Arduino from the application on Node.js

Last time, we looked at how to make your mini-terminal with a “question-answer” mode on a robot with Arduina with the library babbler_h. Today we'll see how to use the same library to control the robot from a desktop JavaScript application + Node.js.

To change the data with the robot, in the client part in JavaScript + Node.js, we use the library Babbler.js, specially written for this occasion. Babbler.js uses the node-serialport standard library for working with the serial port, but builds on top of it some additional conveniences.

Library features

- The library allows you to connect to the device, send commands to him, get answers.
- The library itself maintains the connection, hides inside all the technical nuances: it monitors the breaks, notifies about all changes in the connection status, allows to disconnect and reconnect.
- Commands are added to the send queue, sent to the device one by one.
- The library monitors each packet with a command from the moment it is added to the queue until a response is received or an error occurs; generates public events that may be useful for displaying device status or debugging.
- The user code will always receive a notification about the completion of the team’s life path: a response from the device or an error message.
- The library handles any possible exceptions that may occur with the command on the device path, and generates the appropriate error messages. For example, you can add a command to the send queue, and then pull out the connection cord: the user code will receive a command execution error message (the connection is broken before sending the command to the robot / the connection is broken after sending the command to the robot), after which the application reconnects to the device (if the of course, it will be connected again by wire) and will continue to work.
- The library is tolerant of incorrect device behavior: the robot can forget to send answers, send answers at the wrong time, send incorrect answers, or even pour debugging junk into the communication channel (serial port). In the best case, the library will ignore incorrect packets, waiting for the right one, at worst, it will send a message to the user code indicating that the robot did not execute the command (that is, no response was received).
- The device is considered connected after two conditions are met: the communication channel is open, the device has sent the correct “ok” response to the ping command.
')
Additional restrictions on the firmware of the robot:

- The robot must accept commands and send responses in JSON format with support for client command identifiers.
- The firmware of the robot must necessarily include the ping command (without it the connection will not be established).
- The device should send a response to the received command no later than 5 seconds , otherwise the client code will consider the command not executed (will receive an error BBLR_ERROR_REPLY_TIMEOUT).
- It may be a situation where the robot, on command, must perform some kind of continuous action that can last more than 5 seconds (go from point A to point B), and then inform the control panel that the action has been completed. In this case, two commands should be entered in the robot's firmware: “ start the process of performing the action ” (returns instantly with the code “ok”) and “ get the status of the execution of the launched action ” (“in process” / “ready”). The console will start the process of performing the action on the first command, and then periodically check its status, sending the second command time after time.

Main links:

- Library for the robot: babbler_h
- Library for Node.js: babbler-js
- Examples for babbler-js: babbler-js-demo

Protocol


The robot must accept commands and send responses in JSON format. The data packet is a JSON string containing the command or response. Data packets are separated by a line break.

The robot must accept commands in the following JSON format:

{"cmd": "help", "id": "34", "params":["--list"]} 

here:
- cmd - command name, string
- params - command parameters, array of strings
- id - client command identifier, string (optional)

The command name and parameters are clear. The client identifier is an arbitrary value, generated by the client and sent along with the command, the robot sends it with the answer. The command identifier will allow the client to easily determine which of the sent commands the answer came to. The uniqueness of the value is ensured on the client side, the robot simply copies the received value in response and no longer analyzes it.

The answer should be packaged in the following JSON format:

 {"cmd": "help", "id": "34", "reply": "help ping ledon ledoff"} 

here:
- cmd - source command, string
- id - client command identifier (initial value is copied), string
- reply - the answer (the result of the command), string

Perhaps, in new versions, the params value appears in the response with a copy of the original command parameters. Maybe this is not a very efficient use of resources, but an additional convenience for debugging.

Firmware for robot


About the installation of the library babbler_h for Arduino and the features of its application I recommend to look in the previous article . Here at once I give an example of a sketch that can receive commands and send responses in JSON format. The functions required for working with JSON are implemented in the babbler_json module.

We look code

Let's take as an example an example with two user commands ledon and ledoff for blinking lights _2_babbler_custom_cmd.ino and make it accept requests and send responses in JSON format. Compared to the original command line option, there are exactly two differences:

1. Connect the library babbler_json.h in the title:

 #include "babbler_json.h" 

2. Replace the handle_input_simple handler with handle_input_json in babbler_serial_set_input_handler in the presets in setup .

  babbler_serial_set_input_handler(handle_input_json); 

instead

  babbler_serial_set_input_handler(handle_input_simple); 

There are no more differences, including (and first of all) in the code of user commands, in general.

File → Examples → babbler_h → babbler_json_io.ino

 #include "babbler.h" #include "babbler_cmd_core.h" #include "babbler_simple.h" #include "babbler_json.h" #include "babbler_serial.h" //         #define SERIAL_READ_BUFFER_SIZE 128 #define SERIAL_WRITE_BUFFER_SIZE 512 //         . // +1       char serial_read_buffer[SERIAL_READ_BUFFER_SIZE+1]; char serial_write_buffer[SERIAL_WRITE_BUFFER_SIZE]; #define LED_PIN 13 /**   ledon ( ) */ int cmd_ledon(char* reply_buffer, int reply_buf_size, int argc=0, char *argv[]=NULL) { digitalWrite(LED_PIN, HIGH); //   strcpy(reply_buffer, REPLY_OK); return strlen(reply_buffer); } /**   ledoff ( ) */ int cmd_ledoff(char* reply_buffer, int reply_buf_size, int argc=0, char *argv[]=NULL) { digitalWrite(LED_PIN, LOW); //   strcpy(reply_buffer, REPLY_OK); return strlen(reply_buffer); } babbler_cmd_t CMD_LEDON = { /*   */ "ledon", /*       */ &cmd_ledon }; babbler_man_t MAN_LEDON = { /*   */ "ledon", /*   */ "turn led ON", /*  */ "SYNOPSIS\n" " ledon\n" "DESCRIPTION\n" "Turn led ON." }; babbler_cmd_t CMD_LEDOFF = { /*   */ "ledoff", /*       */ &cmd_ledoff }; babbler_man_t MAN_LEDOFF = { /*   */ "ledoff", /*   */ "turn led OFF", /*  */ "SYNOPSIS\n" " ledoff\n" "DESCRIPTION\n" "Turn led OFF." }; /**   */ extern const babbler_cmd_t BABBLER_COMMANDS[] = { //   babbler_cmd_core.h CMD_HELP, CMD_PING, //   CMD_LEDON, CMD_LEDOFF }; /**    */ extern const int BABBLER_COMMANDS_COUNT = sizeof(BABBLER_COMMANDS)/sizeof(babbler_cmd_t); /**     */ extern const babbler_man_t BABBLER_MANUALS[] = { //   babbler_cmd_core.h // commands from babbler_cmd.core.h MAN_HELP, MAN_PING, //   // custom commands MAN_LEDON, MAN_LEDOFF }; /**      */ extern const int BABBLER_MANUALS_COUNT = sizeof(BABBLER_MANUALS)/sizeof(babbler_man_t); void setup() { Serial.begin(9600); Serial.println("Starting babbler-powered device with JSON i/o," " type {\"cmd\": \"help\", \"id\": \"34\", \"params\":[]} for list of commands"); //       // {"cmd": "help", "id": "34", "params":[]} babbler_serial_set_packet_filter(packet_filter_newline); babbler_serial_set_input_handler(handle_input_json); //babbler_serial_setup( // serial_read_buffer, SERIAL_READ_BUFFER_SIZE, // serial_write_buffer, SERIAL_WRITE_BUFFER_SIZE, // 9600); babbler_serial_setup( serial_read_buffer, SERIAL_READ_BUFFER_SIZE, serial_write_buffer, SERIAL_WRITE_BUFFER_SIZE, BABBLER_SERIAL_SKIP_PORT_INIT); pinMode(LED_PIN, OUTPUT); } void loop() { //     ,    babbler_serial_tasks(); } 

For a quick test in the Arduino environment, you can open all the same Tools → Port Monitor and send a command to the robot:

 {"cmd": "help", "id": "34", "params":["--list"]} 

The answer will be:

 {"cmd": "help", "id": "34", "reply": "help ping ledon ledoff"} 

Of course, manually typing lines in the JSON format is not very convenient, but for a JavaScript application, this communication channel will be as native.

Configuring the client side on Node.js


- The babbler.js library on github .
- Examples babbler-js-demo

To manually configure a new project - install the babbler-js package:

 npm install babbler-js 

or for a finished project with examples we perform:

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

A simple example : connect to the device, execute ping and help --list commands .

babbler-js-demo / babbler-basic / babbler-basic.js

 var BabblerDevice = require('babbler-js'); var babbler = new BabblerDevice(); babbler.on('connected', function() { console.log("connected"); console.log("send cmd: ping"); babbler.sendCmd("ping", [], // onReply function(cmd, params, reply) { console.log("got reply on '" + cmd + " " + params + "': " + reply); }, // onError function(cmd, params, err) { console.log("fail with '" + cmd + " " + params + "': " + err); } ); console.log("send cmd: help --list"); babbler.sendCmd("help", ["--list"], // onReply function(cmd, params, reply) { console.log("got reply on '" + cmd + " " + params + "': " + reply); }, // onError function(cmd, params, err) { console.log("fail with '" + cmd + " " + params + "': " + err); } ); }); babbler.on('disconnected', function(error) { console.log("disconnected" + (error != undefined ? ": " + error : "")); }); babbler.connect("/dev/ttyUSB0"); //babbler.connect("/dev/ttyUSB0", {baudRate: 9600}); 

run:

 node babbler-basic.js 

in the terminal we observe:

 connected send cmd: ping send cmd: help --list got reply on 'ping ': ok got reply on 'help --list': help ping ledon ledoff 

We pull out the USB cable with the robot, the program writes the last message and ends:

 disconnected: Device unplugged 

The example is slightly more interesting :

- the program connects to the device and starts to turn on ( leodon command) and turn off ( ledoff command) a light bulb every 2 seconds;
- in case of disconnecting the device, the program tries to reconnect every 3 seconds until it connects, then the light starts flashing again.

babbler-basic / babbler-basic-blink.js
 var BabblerDevice = require('babbler-js'); var babbler = new BabblerDevice(); var blinkIntervalId; babbler.on('connected', function() { console.log("connected"); //    2  var ledstatus = "off"; blinkIntervalId = setInterval(function() { if(ledstatus === "on") { console.log("send cmd: ledoff"); babbler.sendCmd("ledoff", [], // onReply function(cmd, params, reply) { console.log("got reply on '" + cmd + " " + params + "': " + reply); ledstatus = "off"; }, // onError function(cmd, params, err) { console.log("fail with '" + cmd + " " + params + "': " + err); } ); } else { // ledstatus === "off" console.log("send cmd: ledon"); babbler.sendCmd("ledon", [], // onReply function(cmd, params, reply) { console.log("got reply on '" + cmd + " " + params + "': " + reply); ledstatus = "on"; }, // onError function(cmd, params, err) { console.log("fail with '" + cmd + " " + params + "': " + err); } ); } }, 3000); }); babbler.on('connecting', function() { console.log("connecting..."); }); babbler.on('disconnected', function(error) { console.log("disconnected" + (error != undefined ? ": " + error : "")); //  ,    clearInterval(blinkIntervalId); //     3  setTimeout(function() { babbler.connect("/dev/ttyUSB0"); }, 3000); }); babbler.connect("/dev/ttyUSB0"); //babbler.connect("/dev/ttyUSB0", {baudRate: 9600}); 

Run:

 node babbler-basic-blink.js 

Watch the flashing light:

 connecting... connected send cmd: ledon got reply on 'ledon ': ok send cmd: ledoff got reply on 'ledoff ': ok send cmd: ledon got reply on 'ledon ': ok send cmd: ledoff got reply on 'ledoff ': ok send cmd: ledon got reply on 'ledon ': ok disconnected: Device unplugged connecting... disconnected: Error: Error: No such file or directory, cannot open /dev/ttyUSB0 connecting... disconnected: Error: Error: No such file or directory, cannot open /dev/ttyUSB0 connecting... connected send cmd: ledon got reply on 'ledon ': ok send cmd: ledoff got reply on 'ledoff ': ok send cmd: ledon got reply on 'ledon ': ok disconnected: Device unplugged 

In the process, you can pull out the USB cable leading to the robot, and then plug it back.

Source: https://habr.com/ru/post/315480/


All Articles