πŸ“œ ⬆️ ⬇️

Console in the robot on Arduin

Send the robot on Arduin a few bytes via Wi-Fi, bluetooth, serial port or any other communication channel as a command, and then take a few bytes as an answer is not difficult: just download the sketch with the hello world data exchange example and insert several lines of your own code that will perform the desired actions.

However, with the development of the project, the area of ​​the auxiliary code, which is responsible for communication with the outside world, expands: there is a logic that separates one data packet from another, a forest of checks grows, what team has come, what parameters does it have, how to do it correctly, what to do, if the data packet is not correct, if the data did not arrive completely, if they do not fit in their allocated memory buffers, and so on. The code serving the auxiliary logic is intertwined with the main code that performs interesting and useful work. Replacing one communication channel with another (for example, adding a Wi-Fi connection to a serial port) without processing the accumulated code base becomes quite problematic.

For a long time, I dragged such code from one project to another, new improvements and fixes had to be updated in all projects in parallel. It is tiring, fraught with the emergence of new errors and not always possible. Finally, it is time to bring all this logic into a separate library.
')
The initial task: to simplify the process of creating firmware for robots that will work in the "question-answer" mode. The main sketch should contain a useful code (which, in fact, the robot should do) and a minimum number of auxiliary structures. All auxiliary transport and protocol blocks should be puppeted into the library and moved beyond the engineer’s attention.

As a side effect, we got a kind of command line working inside Arduina, if you connect to it via the serial port monitor and send commands manually:

image


Library features


- Work in the question-answer mode
- The maximum sizes of the incoming command and response are limited by the size of the buffers (set in the settings in the sketch)
- Communication channels (serial port, Wi-Fi, bluetooth) are interchangeable, implemented as separate submodules
- No strict requirements for protocol details (built on top of communication modules)
- New commands are added as separate functions (subroutines) and are registered in the system by a unique name.
- Mechanisms for the transfer of information about exceptional situations to the client

Architecturally, the library is divided into 3 levels:

- Modules of communication channels (implemented work through the serial port; wifi and bluetooth in the medium-term plans): set up and maintain the connection, isolate packets from the input data stream, send a response.
- Module for registering and executing commands: registering a function (subroutine) as a command, searching a command by name, executing a command.
- Auxiliary container protocols: formats for receiving commands and packaging responses (a simple command line β€” the command name and parameters are separated by spaces; the command and answers are in JSON format).

Serial communication channel: babbler_serial
Command module (command registration and execution API): babbler_h
Simple command line module (input format): babbler_simple
JSON module (input and response format): babbler_json

The modules are relatively independent of each other: you can use only the communication channel module to exchange raw data and build your own protocol with it, you can connect other implementations of communication channels to the command module, you can not use the JSON module at all or put the module implementation in its place working with XML packages and so on.

Further examples.

Library installation


Github Project: babbler_h

git clone https://github.com/1i7/babbler_h.git 

Or download the next release in the archive

then put the subdirectories babbler_h , babbler_serial , babbler_json in the directory to the libraries Arduino $ HOME / Arduino / libraries, should be:

 $HOME/Arduino/libraries/babbler_h $HOME/Arduino/libraries/babbler_serial $HOME/Arduino/libraries_babbler_json 

Everything.

Launch the Arduino development environment, in the File β†’ Examples β†’ babbler_h menu , the following examples will appear:

_1_babbler_hello : simple firmware: setting up a communication channel, registering commands (built-in commands: ping and help)
_2_babbler_custom_cmd : add your own commands (turn on / off the light bulb)
_3_babbler_cmd_params : commands with parameters (transport for pin_mode / digital_write)
_4_babbler_cmd_devino : a set of commands for getting information about the device
_5_babbler_custom_handler : own input data handler (same as _1_babbler_hello, only the inside is outside)
_6_babbler_reply_json : I / O is packed with JSON
_7_babbler_reply_xml : input by string, response in XML
babbler_basic_io : raw Q & A over serial port without command module infrastructure

Simple example: echo via serial port


Without using the infrastructure of working with teams.

File β†’ Examples β†’ babbler_h β†’ babbler_basic_io.ino

We only need the babbler_serial module:

 #include "babbler_serial.h" 

Buffers for receiving incoming data and sending a response. An incoming packet (command and parameters) must fit completely into the serial_read_buffer buffer (plus one byte is reserved for one terminating zero). The answer must fit completely into the serial_write_buffer buffer.

 //         #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]; 

Input data handler function: accepts data in the input_buffer buffer, decides what to do with it, writes the response to the reply_buffer buffer, returns the number of bytes written to the response buffer. Here is all user code.

 int handle_input(char* input_buffer, int input_len, char* reply_buffer, int reply_buf_size) { //      , //       input_buffer[input_len] = 0; // -    -     if(reply_buf_size > input_len + 10) sprintf(reply_buffer, "you say: %s\n", input_buffer); else sprintf(reply_buffer, "you are too verbose, dear\n"); return strlen(reply_buffer); } 

Serial Port Communication Module Presets:

- babbler_serial_setup : passing buffers for incoming commands and outgoing responses,
- packet_filter_newline : filter for new packets - packets are separated by a line break
- babbler_serial_set_input_handler : pointer to the input data handler function in the user code (our handle_input )

 void setup() { Serial.begin(9600); Serial.println("Starting babbler-powered device, type something to have a talk"); babbler_serial_set_packet_filter(packet_filter_newline); babbler_serial_set_input_handler(handle_input); //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); } 

We place the babbler_serial_tasks into the main loop: we constantly monitor the serial port and wait for the input data. The babbler_serial_tasks call is not blocking, after which any other logic can be placed.

 void loop() { //     ,    babbler_serial_tasks(); } 

Flash, open Tools β†’ Port Monitor , enter messages, get the answers:

image

A simple example: working with teams


The next simple example is working with teams. We register two embedded commands in the firmware (defined in the babbler_cmd_core.h module):

- help (get a list of commands, see help on the selected command) and
- ping (check if the device is alive).

Ping command:
 ping 

Returns "ok"

Help command:
Display a list of commands with a brief description.
 help 

List the commands
 help --list 

Display detailed help on the team
 help _ 


File β†’ Examples β†’ babbler_h β†’ _1_babbler_hello.ino

Here is the infrastructure for registering, searching and executing commands by name:

 #include "babbler.h" 

Here, the analysis of the incoming command line: the line is divided into elements by spaces, the first element is the name of the command, all the rest are parameters.

 #include "babbler_simple.h" 

Here are the command definitions: help and ping

 #include "babbler_cmd_core.h" 

Serial communication module:

 #include "babbler_serial.h" 

Buffers for input and output, everything is unchanged.

 //         #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]; 

Register commands - add the CMD_HELP and CMD_PING structures (they are defined in babbler_cmd_core.h) to the global BABBLER_COMMANDS array. Along the way, we fix the number of registered commands BABBLER_COMMANDS_COUNT - the number of elements in the BABBLER_COMMANDS array (in C, it is impossible to know the size of the array, thus defined, dynamically in the place where we need it).

 /**   */ extern const babbler_cmd_t BABBLER_COMMANDS[] = { //   babbler_cmd_core.h CMD_HELP, CMD_PING }; /**    */ extern const int BABBLER_COMMANDS_COUNT = sizeof(BABBLER_COMMANDS)/sizeof(babbler_cmd_t); 

Using the same scheme, we register human-readable manuals for registered commands in the BABBLER_MANUALS array β€” they are displayed by the help command (you can define an empty array with no elements if you want to save memory, but then the help command will not work).

 /**     */ extern const babbler_man_t BABBLER_MANUALS[] = { //   babbler_cmd_core.h MAN_HELP, MAN_PING }; /**      */ extern const int BABBLER_MANUALS_COUNT = sizeof(BABBLER_MANUALS)/sizeof(babbler_man_t); 

Configure the module:

- babbler_serial_set_packet_filter and babbler_serial_setup - everything, as before
- in babbler_serial_set_input_handler we send a pointer to the handle_input_simple function (from babbler_simple.h, instead of our own handle_input ) - it does all the necessary work: parses the input string by spaces, separates the command name from the parameters, executes the command, writes the answer.

 void setup() { Serial.begin(9600); Serial.println("Starting babbler-powered device, type help for list of commands"); babbler_serial_set_packet_filter(packet_filter_newline); babbler_serial_set_input_handler(handle_input_simple); //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); } 

The main loop is unchanged:

 void loop() { //     ,    babbler_serial_tasks(); } 

Flash, open Tools β†’ Port Monitor , enter commands, get the answers:

]help --list
 help ping 

]ping
 ok 

]help
 Commands: help list available commands or show detailed help on selected command ping check if device is available 

]help ping
 ping - manual NAME ping - check if device is available SYNOPSIS ping DESCRIPTION Check if device is available, returns "ok" if device is ok 

]help help
 help - manual NAME help - list available commands or show detailed help on selected command SYNOPSIS help help [cmd_name] help --list DESCRIPTION List available commands or show detailed help on selected command. Running help with no options would list commands with short description. OPTIONS cmd_name - command name to show detailed help for --list - list all available commands separated by space 

Adding custom commands


And finally, adding your own team so that it can be easily called by name. For example, add two commands:

- ledon (turn on the bulb) and
- ledoff (turn off the light bulb)

to turn on and off the LED connected to the selected leg of the microcontroller.

Here everything is unchanged:

 #include "babbler.h" #include "babbler_simple.h" #include "babbler_cmd_core.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]; 

LED foot number:

 #define LED_PIN 13 

And here is a useful code right away - for each command, a function with parameters must be defined:

- reply_buffer - buffer for recording the reply
- reply_buf_size - reply_buffer buffer size (the answer should fit in it, otherwise report an error)
- argc - the number of arguments (parameters) command
- argv - the values ​​of the command arguments (the first argument is always the name of the command, all by analogy with the usual main)

Option for ledon :

 /**   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); } 

The babbler_cmd_t structure for registering a command: command name and pointer to its function:

 babbler_cmd_t CMD_LEDON = { /*   */ "ledon", /*       */ &cmd_ledon }; 

Team Guide - babbler_man_t structure: team name, short description, detailed description.

 babbler_man_t MAN_LEDON = { /*   */ "ledon", /*   */ "turn led ON", /*  */ "SYNOPSIS\n" " ledon\n" "DESCRIPTION\n" "Turn led ON." }; 

All the same for ledoff :

 /**   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_LEDOFF = { /*   */ "ledoff", /*       */ &cmd_ledoff }; babbler_man_t MAN_LEDOFF = { /*   */ "ledoff", /*   */ "turn led OFF", /*  */ "SYNOPSIS\n" " ledoff\n" "DESCRIPTION\n" "Turn led OFF." }; 

We register new CMD_LEDON and CMD_LEDOFF together with the familiar CMD_HELP and CMD_PING , similar to the manual.

 /**   */ 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 MAN_HELP, MAN_PING, //   MAN_LEDON, MAN_LEDOFF }; /**      */ extern const int BABBLER_MANUALS_COUNT = sizeof(BABBLER_MANUALS)/sizeof(babbler_man_t); 

The setup and main cycle unchanged.

 void setup() { Serial.begin(9600); Serial.println("Starting babbler-powered device, type help for list of commands"); babbler_serial_set_packet_filter(packet_filter_newline); babbler_serial_set_input_handler(handle_input_simple); //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(); } 

Flash, open Tools β†’ Port Monitor , enter commands, watch the light bulb:

image

Live with a piece of iron:



β†’ An example of a command with parameters for independent work.

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


All Articles