The Arduino Parser library implements functions to interpret the information contained in a char Array or in a String in a simple way. It provides functions to read numbers, substrings, search for a character, as well as various functions to navigate or search along the buffer.
It is designed to work with an array received by a communication medium (for example, serial port), but the Parser does not perform the reception or modify the data. In this way, it is possible to use it with any char array, regardless of its origin.
Arduino Parser is designed to work together with the AsyncSerial library, which performs data reception by the serial port in a non-blocking way.
Usage Instructions
The Arduino Parser library works on a char array buffer, which is provided externally to the object. The Parser does not receive or modify the buffer at any time, so it is safe to use it on any array. The buffer and its length are received in the Parser constructor. However, it is possible to change it at any time with the Init() functions.
Parser provides functions to perform data reading (bool, char, numbers, strings). These functions return the read object and advance the cursor to the current element CurrentIndex if the reading has been successful.
On the other hand, all reading functions accept a callback function, which triggers if the reading has been successful. It is possible to use lambda functions (but not mandatory) to achieve more compact code.
This usage is more advanced, but it allows to discriminate if the reading has been really valid (for example, reading an integer returns 0, even if it has not been successful, while the callback function only triggers if the reading is really correct).
On the other hand, we have the Compare(…) functions that compare the current point of the buffer with a String. These functions are useful, for example, to discriminate the command received on a serial port. The Compare(…) functions need the length of the compared string. If, by design, most of your commands have the same length, you can use the DEFAULT_COMMAND_LENGTH attribute and not provide it in those Compare(…) that have that length.
For their part, the Search(…) functions search the rest of the buffer for a character. We can use them, for example, to divide an array separated by commas with a variable number of elements. The IfCurrentIs(…), IfCurrentIsNot(…), DoUntil(…), and DoWhile(…) functions allow to easily implement conditionals and loops on the buffer. Additionally, we have the displacement functions in the buffer Skip(…), SkipWhile(…), SkipUntil(…), JumpAfter(…), JumpTo(…).
Finally, we have a series of static functions that compare a char, and are designed to be provided to any of the previous methods. Thus, for example, it is possible to advance in the buffer until a digit is found or to move while a separator is found.
All these functions together give us great potential to interpret almost any frame that you have as communication in your project. Consult the examples to see some possible combinations. (There are examples with/without callback functions and with/without lambda functions).
Constructor
The Arduino Parser object is instantiated through one of its constructors
Parser(size_t default_command_length = 5); //Empty
Parser(String& buffer, size_t default_command_length = 5); //From String
Parser(char* buffer, size_t bufferLength, size_t default_command_length = 5); //From char array
Parser(byte* buffer, size_t bufferLength, size_t default_command_length = 5); //From byte array
But we can change the buffer using the Init(…) functions
void Init(char* buffer, size_t bufferLength, size_t default_command_length = 5);
void Init(byte* buffer, size_t bufferLength, size_t default_command_length = 5);
void Init(String& buffer, size_t default_command_length = 5);
Using Arduino Parser
To interpret and read the buffer, we have the Read_(…) functions that return the read value, and advance the cursor if the reading has been successful. Additionally, they accept a callback function that receives the read value as a parameter, and triggers only if the reading has been correct.
bool Read_Bool(ParserCallbackBool callback = nullptr);
char Read_Char(ParserCallbackChar callback = nullptr);
byte Read_Byte(ParserCallbackByte callback = nullptr);
int8_t Read_Int8(ParserCallbackInt8 callback = nullptr);
int16_t Read_Int16(ParserCallbackInt16 callback = nullptr);
int32_t Read_Int32(ParserCallbackInt32 callback = nullptr);
uint8_t Read_Uint8(ParserCallbackUint8 callback = nullptr);
uint16_t Read_Uint16(ParserCallbackUint16 callback = nullptr);
uint32_t Read_Uint32(ParserCallbackUint32 callback = nullptr);
float Read_Float(ParserCallbackFloat callback = nullptr);
float Read_UnsignedFloat(ParserCallbackFloat callback = nullptr);
The string reading functions require a valid separator to end the reading. Similarly, they accept a callback function that triggers if the reading has been successful.
On the other hand, the optional endIfNotFound parameter indicates if, in case the separator is not found, the string is considered valid until the end of the buffer. By default it is true since it is useful, for example, to divide strings separated by commas (where the last data does not have a comma at the end)
size_t Read_CharArray(char separator, ParserCallbackCharArray callback = nullptr);
size_t Read_CharArray(ParserCriterion criterion, ParserCallbackCharArray callback = nullptr);
size_t Read_CharArray(char separator, bool endIfNotFound, ParserCallbackCharArray callback = nullptr);
size_t Read_CharArray(ParserCriterion criterion, bool endIfNotFound, ParserCallbackCharArray callback = nullptr);
String Read_String(char separator, ParserCallbackString callback = nullptr);
String Read_String(ParserCriterion criterion, ParserCallbackString callback = nullptr);
String Read_String(char separator, bool endIfNotFound, ParserCallbackString callback = nullptr);
String Read_String(ParserCriterion criterion, bool endIfNotFound, ParserCallbackString callback = nullptr);
The Compare(…) methods are one of the main ones of the Parser, and allow to compare the current position of the buffer with a text string. It is necessary to provide the length of the compared string. In case of being omitted, the DEFAULT_COMMAND_LENGTH field will be used, which is useful if most of the compared commands have the same length.
// Compare methods
bool Compare(char token, ParserCallback callback = nullptr);
bool Compare(char token[], ParserCallback callback = nullptr);
bool Compare(char token[], size_t max_length, ParserCallback callback = nullptr);
bool Compare(String token, ParserCallback callback = nullptr);
bool Compare(ParserCriterion comparision, ParserCallback callback = nullptr);
The Search(…) functions search for a text string in the rest of the buffer, and return whether the occurrence has been found or not. They do not modify the current position in the buffer, but serve to determine if we have to perform one type of reading or another.
// Search methods
bool Search(char token, ParserCallback callback = nullptr);
bool Search(char token[], ParserCallback callback = nullptr);
bool Search(char token[], size_t max_length, ParserCallback callback = nullptr);
bool Search(String token, ParserCallback callback = nullptr);
bool Search(ParserCriterion criterion, ParserCallback callback = nullptr);
The Loop and Ifs functions allow to perform an action in the buffer based on a condition. In most cases they will be used together with the Search(…) functions, but any other condition is possible.
// Loop-if methods
bool IfCurrentIs(char token, ParserCallback yesCallback = nullptr, ParserCallback noCallback = nullptr);
bool IfCurrentIs(ParserCriterion criterion, ParserCallback yesCallback = nullptr, ParserCallback noCallback = nullptr);
bool IfCurrentIsNot(char token, ParserCallback yesCallback = nullptr, ParserCallback noCallback = nullptr);
bool IfCurrentIsNot(ParserCriterion criterion, ParserCallback yesCallback = nullptr, ParserCallback noCallback = nullptr);
void DoUntil(ParserCondition condition, ParserCallback callback = nullptr, ParserCallback finally = nullptr);
void DoWhile(ParserCondition condition, ParserCallback callback = nullptr, ParserCallback finally = nullptr);
The Skip(…) methods allow to ignore certain elements of the buffer and advance the cursor.
// Skip methods
void Skip(size_t num_items); //advance N elements
void SkipWhile(char item); //advance while the condition is met
void SkipWhile(ParserCriterion comparision);
void SkipUntil(char item); //advance until the condition is met
void SkipUntil(ParserCriterion comparision);
The Jump(…) methods allow to advance until a condition is met in the buffer.
// Jump methods
void JumpAfter(char item); //jump after the element that meets the condition
void JumpAfter(ParserCriterion comparision);
void JumpTo(char item); //jump to the element that meets the condition
void JumpTo(ParserCriterion comparision);
The static comparison functions allow to group sets of characters. They are used in all the previous functions, to achieve more complex conditions than just comparing with a character.
// Comparision static methods
static bool IsPrintable(byte item); // valid Ascii character
static bool IsDigit(byte item); // 0 - 9
static bool IsNotDigit(byte Item); // Not a digit
static bool IsNumeric(byte item); // Digit or , or . or -
static bool IsLowercasaLetter(byte item); // a - z
static bool IsUpperCaseLetter(byte item); // A - Z
static bool IsLetter(byte item); // Lowercase or uppercase letter
static bool IsNotLetter(byte item); // Not a letter
static bool IsAlfaNumeric(byte Item); // Number or letter
static bool IsSeparator(byte item); // . , ; _ - = # ?
static bool IsSymbol(byte item); // Printable non-alphanumeric
static bool IsNewLine(byte item); // \n
static bool IsNewLine(byte item); // \r
static bool IsSeparatorOrNewLine(byte item); // Separator or \n
Examples
The Parser library includes the following examples to illustrate its use.
- Simple: Simple example that shows the use of some functions.
#include "ParserLib.h"
char demo[] = "AA-123#CC;;276.56__-,;#__abcd-16";
int demoLength = strlen(demo);
Parser parser((byte*)demo, demoLength);
void setup()
{
Serial.begin(9600);
while (!Serial) { ; }
Serial.println("Starting Demo");
}
void loop()
{
Serial.println("--- Demo loop ---");
// Demo Prints AA 123 CC 276.56 -16
Serial.println(parser.Read_String(-)); // Read string until - (AA)
parser.Skip(1); //Ignore -
Serial.println(parser.Read_Int16()); // Read INT16 (123)
parser.Skip(1); //Ignore #
Serial.println(parser.Read_String(;)); // Read string until ; (CC)
parser.SkipWhile(;); // Ignore ;;
Serial.println(parser.Read_Float()); //Read Float (276.56)
parser.SkipWhile(Parser::IsSeparator); // Ignore separators __-,;#_
parser.JumpTo(Parser::IsNumeric); //Jump to next number (ignore abcd)
Serial.println(parser.Read_Int32()); //Read Int32 (-16)
parser.Reset();
delay(2500);
}
- CommandRecieve: Shows a simple usage to receive orders and commands, with and without lambda functions.
#include "ParserLib.h"
char demo[] = "CMD2";
int demoLength = strlen(demo);
Parser parser((byte*)demo, demoLength);
void setup()
{
Serial.begin(9600);
while (!Serial) { ; }
Serial.println("Starting Demo");
}
void loop()
{
Serial.println("--- Demo loop ---");
parser.Compare("CMD1", 4, []() { Serial.println("Command1 Recieved"); });
parser.Compare("CMD2", 4, []() { Command(2); });
parser.Compare("CMD3", 4, Command3);
parser.Reset();
delay(2500);
}
void Command3()
{
Command(3);
}
void Command(int num)
{
Serial.print("Command");
Serial.print(num);
Serial.println(" recieved");
}
- Led: Shows a possible use to interpret a frame that turns a Led on or off by receiving ON or OFF.
#include "ParserLib.h"
char demo[] = "LED#1-ON";
int demoLength = strlen(demo);
Parser parser((byte*)demo, demoLength);
void setup()
{
Serial.begin(9600);
while (!Serial) { ; }
Serial.println("Starting Demo");
}
void loop()
{
Serial.println("--- Demo loop ---");
parser.Compare("LED", 3,
[]() { Serial.print("Recieved LED N:");
parser.SkipWhile(Parser::IsSeparator);
Serial.print(parser.Read_Uint8());
parser.SkipWhile(Parser::IsSeparator);
parser.Compare("ON", 2, []() {Serial.println(" Action: TURN_ON"); });
parser.Compare("OFF", 3, []() {Serial.println(" ACTION: TURN_OFF"); });
});
parser.Reset();
delay(2500);
}
- Servo: Shows a possible use to interpret a frame that would move a servo to an angle.
#include "ParserLib.h"
char demo[] = "SERVO12_185.67";
int demoLength = strlen(demo);
Parser parser((byte*)demo, demoLength);
void setup()
{
Serial.begin(9600);
while (!Serial) { ; }
Serial.println("Starting Demo");
}
void loop()
{
Serial.println("--- Demo loop ---");
parser.Compare("SERVO", 5,
[]() { Serial.print("Recieved SERVO N:");
uint8_t servo = parser.Read_Uint8();
Serial.print(servo);
parser.SkipWhile(Parser::IsSeparator);
Serial.print(" Angle:");
Serial.println(parser.Read_Float());
});
parser.Reset();
delay(2500);
}
- SerialExample: Shows a possible usage together with reception by Serial port. (the reception could be improved, but it is a simple example).
#include "ParserLib.h"
Parser parser;
void setup()
{
Serial.begin(9600);
while (!Serial) { ; }
Serial.println("Starting Demo");
Serial.setTimeout(10);
}
void loop()
{
if (Serial.available())
{
// Try writing some numbers and separators (, . ; # = are valid separators)
String reader = Serial.readStringUntil(\n);
if (reader.length() > 0)
{
parser.Init(reader);
parser.DoWhile(
[]()-> bool { return parser.Search(Parser::IsSeparator); },
[]() { Serial.println(parser.Read_Int8()); parser.SkipWhile(Parser::IsSeparator); },
[]() { Serial.println(parser.Read_Int8()); }
);
parser.Reset();
}
}
}
- CommaSeparated: Shows a possible use to interpret numbers separated by commas, using and not using success callbacks.
#include "ParserLib.h"
char demo[] = "12,13,14";
int demoLength = strlen(demo);
Parser parser((byte*)demo, demoLength);
void setup()
{
Serial.begin(9600);
while (!Serial) { ; }
Serial.println("Starting Demo");
Serial.setTimeout(2);
}
void loop()
{
Serial.println("--- Demo loop ---");
// Example without using success Callback
Serial.println(" - Example 1 -");
parser.DoWhile(
[]()-> bool { return parser.Search(,); },
[]() { Serial.println(parser.Read_Uint16()); parser.SkipWhile(,); },
[]() { Serial.println(parser.Read_Uint16()); }
);
parser.Reset();
// Example using success Callback
Serial.println(" - Example 2 -");
parser.DoWhile(
[]()-> bool { return parser.Search(,); },
[]() { parser.Read_Uint16([](uint16_t data) {Serial.println(data); parser.SkipWhile(,); }); },
[]() { parser.Read_Uint16([](uint16_t data) {Serial.println(data); }); }
);
parser.Reset();
delay(2500);
}
- SplitText: Shows a possible example to split a text by different types of separator.
#include "ParserLib.h"
char demo[] = "AA-BB#CC;DD";
int demoLength = strlen(demo);
Parser parser((byte*)demo, demoLength);
void setup()
{
Serial.begin(9600);
while (!Serial) { ; }
Serial.println("Starting Demo");
}
void loop()
{
Serial.println("--- Demo loop ---");
// Example without using success Callback
Serial.println(" - Example 1 -");
parser.DoWhile(
// Condition
[]()-> bool { return parser.Search(Parser::IsSeparator); },
// Callback
[]() { Serial.print("Recieved:");
Serial.println(parser.Read_String(Parser::IsSeparator));
parser.SkipWhile(Parser::IsSeparator); },
//Finally
[]() { Serial.print("Finally:");
Serial.println(parser.Read_String(Parser::IsSeparator)); }
);
parser.Reset();
// Example using success Callback
Serial.println(" - Example 2 -");
parser.DoWhile(
// Condition
[]()-> bool { return parser.Search(Parser::IsSeparator); },
// Callback
[]() { parser.Read_String(Parser::IsSeparator,
[](String& data) { Serial.print("Recieved:");
Serial.println(data); });
parser.SkipWhile(Parser::IsSeparator); },
//Finally
[]() { parser.Read_String(Parser::IsSeparator,
[](String& data) { Serial.print("Finally:");
Serial.println(data); }); }
);
parser.Reset();
delay(2500);
}
Installation
- Download the latest version from GitHub
- Unzip the file
- Copy to your libraries folder (usually My Documents\Arduino\libraries)
- Restart the Arduino IDE