Hallo,
ich stell meinen Parser vor. Einmal die "ältere" Handgedengelte Version und einmal womit mit strtok_r vieles davon ersetzt wurde und zeitgleich weiter ausgebaut wurde. Die Handgedengelte Variante benötigt zwischen Buchstaben und Ziffern und andersrum nicht zwingend ein Leerzeichen. Hier werden alle Leerzeichen die davorstehen können entfernt, oder eben auch nicht.
Die aktuelle Variante ist soweit ausgebaut das der Datentyp vom Integerzielwert egal ist und dennoch alle Limitierungen überprüft und eingehalten werden. Ich habe mich nach langen hin und her entschieden alle Fehleingaben, egal in welcher Form, als ungültig zu erklären. Es sind eine Menge Sicherheitsabfragen eingebaut, möglicherweise etwas ausgeartet.
Man kann den Typ von value beliebig zwischen 8 ... 32 Bit ändern und dessen Elemente.
struct UserData {
char cmd [10 + 1]; // 10 Zeichen + Null-Terminator
int32_t value[2];
};
UserData user;
Der Grund sowas zu schreiben lag darin das ich es satt hatte für jede kleine Änderungen im Code neu flashen zu müssen.
Handgedengelte Version
/*
Doc_Arduino - german Arduino Forum
IDE 1.8.19
avr-gcc 11.3.0
Arduino Nano Every with MegaCoreX Package
03.11.2022
Parser: in Entwicklung, funktioniert
uint8_t Wert, höchster Wert 255 = Zeichenlänge = 3
uint16_t Wert, höchster Wert 65'535 = Zeichenlänge = 5
uint32_t Wert, höchster Wert 4'294'967'295 = Zeichenlänge = 10
uint64_t Wert, höchster Wert 18'446'744'073'709'551'615 = Zeichenlänge = 20
int8_t Range, -128 ......................... 127
int16_t Range, -32'768 ...................... 32'767
int32_t Range, -2'147'483'648 ............... 2'147'483'647
int64_t Range, -9'223'372'036'854'775'808 ... 9'223'372'036'854'775'807
Example: abcdefghij -2147483648 -2147483647'\0' >> 34 Zeichen + Null-Terminator = 35
>>> user.command Buffer mit voller Länge
>>> user.buffer entfernt
>>> Leerzeichenentfernung in Funktion ausgegliedert
*/
#include <Streaming.h>
Stream &cout {Serial};
constexpr bool DEBUG {true};
constexpr uint8_t BUFFER_SIZE {34};
struct ReceiveData {
char buffer [BUFFER_SIZE + 1]; // + Null-Terminator
size_t index {0};
};
ReceiveData rx;
struct UserData {
char cmd [BUFFER_SIZE + 1]; // + Null-Terminator
int32_t value[2];
};
UserData user;
// Prototypen //
/*
void removedLeftAligned (auto &data, const uint8_t COUNTREMOVE);
void findCommandInString (auto &data, auto &cmd)
void findIntegerInString (auto &data, auto &integer)
bool readSerial (Stream &stream, char* const buffer, const uint8_t LENGTH);
*/
// --------------------------------------------------------
void setup (void)
{
Serial.begin(250000);
cout.println(F("\nuC Reset ### ###"));
if (DEBUG) {
cout << F("rx.buffer size ") << BUFFER_SIZE << endl;
}
if (DEBUG) {
cout.println();
}
}
void loop (void)
{
if (readSerial(cout, rx, BUFFER_SIZE))
{
if (DEBUG) { cout << F("eingelesen: ") << rx.buffer << endl; }
findCommandInString(rx.buffer, user.cmd);
cout << F("user.cmd: ") << user.cmd << endl;
for (auto &v : user.value)
{
findIntegerInString (rx.buffer, v);
cout << F("user.value[?] ") <<v << endl;
}
cout.println();
commandProcessing (cout, user);
}
}
// ****** Funktionen ****************************** //
/* Input: buffer/array per Referenz, Anzahl der zu entfernenden Zeichen
Return: per Referenzen
Description: Entfernt Linksbündig eine bestimmte Zeichenanzahl */
void removedLeftAligned (auto &data, const uint8_t COUNTREMOVE)
{
const uint8_t CURRENTLENGTH = strlen(data);
if (DEBUG) {
cout << F("\nremovedLeftAligned()") << endl;
if (DEBUG) { cout << F("buffer: ") << data << endl; }
cout << COUNTREMOVE << F(" Zeichen linksbündig entfernt.") << endl;
cout << F("Kopiere die Anzahl der restlichen ") << (CURRENTLENGTH-COUNTREMOVE) << F(" Zeichen.") << endl;
}
// wenn 0 spart Arbeit und Zeit
if (COUNTREMOVE) {
strncpy(data, data + COUNTREMOVE, CURRENTLENGTH-COUNTREMOVE);
data[CURRENTLENGTH-COUNTREMOVE] = '\0';
if (DEBUG) { cout << F("buffer: ") << data << endl; }
}
}
/* Input: buffer/array per Referenz, command per Referenz
Return: per Referenzen
Description: sucht in 'data' nach Alpha Character, entfernt davor und danach Leerzeichen,
und übergibt die Character an 'cmd' */
void findCommandInString (auto &data, auto &cmd)
{
if (DEBUG) {
cout << F("\nfindCommandInString()") << endl;
cout << F("buffer: ") << data << endl;
const uint8_t CURRENTLENGTH = strlen(data);
cout << F("CURRENTLENGTH: ") << CURRENTLENGTH << endl;
}
// determine the number of spaces at the beginning of each call
uint8_t countSpaces {0};
for (auto &c : data) {
if ( (c == ' ') && (c != '\0') ) { countSpaces++; }
else { break; }
}
removedLeftAligned (data, countSpaces);
// find letter only
uint8_t countCharacter {0};
for (auto &c : data) {
if ( (isalpha(c)) && (c != '\0') ) {
cmd[countCharacter] = c;
countCharacter++;
if (DEBUG) { cout << F("letter: ") << c << F(" countCharacter: ") << countCharacter << endl; }
}
else { break; }
}
cmd[countCharacter] = '\0';
if (DEBUG) {
cout << F("countSpaces: ") << countSpaces << endl;
cout << F("strlen(cmd): ") << strlen(cmd) << endl;
}
removedLeftAligned (data, countCharacter);
}
/* Input: buffer/array per Referenz, Integer Variable per Referenz
Return: per Referenzen
Description: sucht in 'data' nach Integer/Digit, entfernt davor und danach Leerzeichen und
übergibt den ermittelten Integerwert an 'integer' */
void findIntegerInString (auto &data, auto &integer)
{
//constexpr uint8_t MAXDIGIT {3}; // für 8Bit Datentyp
//constexpr uint8_t MAXDIGIT {5}; // für 16Bit Datentyp
constexpr uint8_t MAXDIGIT {10}; // für 32Bit Datentyp
//constexpr uint8_t MAXDIGIT {20}; // für 64Bit Datentyp
if (DEBUG) {
cout << F("\nfindIntegerInString()") << endl;
cout << F("buffer: ") << data << endl;
const uint8_t CURRENTLENGTH = strlen(data);
cout << F("CURRENTLENGTH: ") << CURRENTLENGTH << endl;
}
// determine the number of spaces at the beginning of each call
uint8_t countSpaces {0};
for (auto &c : data) {
if ( (c == ' ') && (c != '\0') ) { countSpaces++; }
else { break; }
}
removedLeftAligned (data, countSpaces);
// detect minus sign only
uint8_t minusSign {0};
if (data[0] == '-') { minusSign = 1; }
// find digit only
uint8_t countDigit {0};
char digitBuffer [MAXDIGIT+2]; // '-' und Null-Terminator
for (auto &c : data) {
if ( ((isdigit(c)) || (c == '-')) && (c != '\0') && (countDigit < (MAXDIGIT+minusSign)) ) {
digitBuffer[countDigit] = c;
countDigit++;
if (DEBUG) {
if (c == '-') { cout << F("countDigit: ") << countDigit << F(" sign: -") << endl; }
else { cout << F("countDigit: ") << countDigit << F(" digit: ") << c-48 << endl; }
}
}
else {
digitBuffer[countDigit] = '\0';
break;
}
}
//integer = strtoul(digitBuffer, nullptr, 10); // irgendwie nicht perfekt und erzeugt Warnung
integer = atol(digitBuffer);
if (DEBUG) {
const uint8_t DIGITLENGTH = strlen(digitBuffer);
cout << F("DIGITLENGTH: ") << DIGITLENGTH << endl;
}
removedLeftAligned (data, countDigit);
}
void commandProcessing (Stream &out, auto &data)
{
const uint8_t CMDLENGTH = strlen(data.cmd);
out << F("cmd: ") << data.cmd << endl;
out << F("Länge: ") << CMDLENGTH << endl;
// ****** CHECK & 5 ************
if ( (CMDLENGTH == 5) && (strstr(data.cmd, "check") == data.cmd) ) {
out.println(F("--- check --- ok"));
out << F("value[0]: ") << data.value[0] << F(" value[1]: ") << data.value[1] << endl;
}
// ****** FLAECHE & 7 ************
else if ( (CMDLENGTH == 7) && (strstr(data.cmd, "flaeche") == data.cmd) ) {
out.println(F("--- flaeche --- ok"));
out << F("Fläche: ") << data.value[0] * data.value[1] << endl;
}
else { out.println(F("Befehl unbekannt.")); }
out.println();
}
bool readSerial (Stream &stream, auto &data, const uint8_t LENGTH)
{
bool status {false};
char c = stream.read();
if (c == '\r' || c == '\n') { // wenn CR oder LF eingelesen
data.buffer[ data.index] = '\0'; // Terminierung
if (DEBUG) { stream << F("readSerial: ") << data.index << F(" numbers of character") << endl; }
data.index = 0;
status = true; // Character fertig eingelesen
}
else if ( (c >= 32) && ( data.index < LENGTH) ) { // wenn noch Platz im Buffer ist
data.buffer[data.index++] = c; // Zeichen speichern und Index inkrementieren
}
return status;
}
mit strtok_r und typeTraits
/*
Doc_Arduino - german Arduino Forum
IDE 1.8.19
getestet mit avr-gcc 11.3.0 & 12.2.0
Arduino Nano Every with MegaCoreX Package
04.11.2022
Parser: in Entwicklung
Status: funktioniert
>>> Handgedengeltes durch strtok_r ersetzt
>>> um TypeTraits ergänzt für
- automatische Digitermittung des Integerdatentyps
- ob signed oder unsigned
- Wertebereichprüfung des verwendeten user.value Datentyps
>>> erfordert deswegen C++14 oder höher Einstellung in platform.local.txt
uint8_t Wert, max. 255 = Zeichenlänge = 3
uint16_t Wert, max. 65'535 = Zeichenlänge = 5
uint32_t Wert, max. 4'294'967'295 = Zeichenlänge = 10
uint64_t Wert, max. 18'446'744'073'709'551'615 = Zeichenlänge = 20 >>> rechnen ja, aber Print/Serial kann es nicht ausgeben
int8_t Range, -128 ......................... 127
int16_t Range, -32'768 ...................... 32'767
int32_t Range, -2'147'483'648 ............... 2'147'483'647
int64_t Range, -9'223'372'036'854'775'808 ... 9'223'372'036'854'775'807 >>> rechnen ja, aber Print/Serial kann es nicht ausgeben
Alle Eingaben müssen durch Leerzeichen getrennt werden. Ansonsten delimiter in Zeile 174 ändern bzw. ergänzen.
Protokollaufbau: Es sind 10 Zeichen für ein Kommando und zwei signed 32Bit Werte für die Eingabe vorgesehen.
Kann man im struct Zeile 49 jederzeit ändern.
max. Format: abcdefghij -2147483648 -2147483647'\0' >> 34 Zeichen + Null-Terminator = 35
Example: check -2147483648 2147483647
Example: flaeche 100 4
*/
#include <Streaming.h>
Stream &cout {Serial};
constexpr bool DEBUG {false};
constexpr uint8_t BUFFER_SIZE {34};
struct ReceiveData {
char buffer [BUFFER_SIZE + 1]; // + Null-Terminator
size_t index {0};
};
ReceiveData rx;
struct UserData {
char cmd [10 + 1]; // 10 Zeichen + Null-Terminator
int32_t value[2];
};
UserData user;
/* Prototypen:
void splitString (auto &string, auto &structure);
void transferTokenToCommand (const auto &token, auto &cmd);
uint8_t transferTokenToInteger (const auto &token, auto &integer, const uint8_t MAXDIGIT, const bool TYPSIGNED, const auto &rangeMin, const auto &rangeMax);
void commandProcessing (Stream &out, const auto &data);
bool readSerial (Stream &stream, auto &data, const uint8_t LENGTH); */
// --------------------------------------------------------------------- //
template<class T, T v>
struct integral_constant
{
static constexpr T value {v};
typedef T value_type;
typedef integral_constant type;
constexpr value_type operator()() const noexcept { return value; } // since C++14
};
typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;
template<class T, class U> struct is_same : false_type {};
template<class T> struct is_same<T, T> : true_type {};
template<class T> struct is_signed : integral_constant<bool, (T(-1) < T(0))> {};
template<class T> struct is_unsigned : integral_constant<bool, (T(-1) > T(0))> {};
template<class T> constexpr bool is_Bool (const T var) {
(void) var;
return (is_same<T, bool>::value);
}
template<class T> constexpr bool is_8Bit (const T var) {
(void) var;
return (is_same<T, int8_t>::value) || (is_same<T, uint8_t>::value) || (is_same<T, char>::value) || (is_same<T, unsigned char>::value);
}
template<class T> constexpr bool is_16Bit (const T var) {
(void) var;
return (is_same<T, int16_t>::value) || (is_same<T, uint16_t>::value);
}
template<class T> constexpr bool is_32Bit (const T var) {
(void) var;
return (is_same<T, int32_t>::value) || (is_same<T, uint32_t>::value);
}
template<class T> constexpr bool is_64Bit (const T var) {
(void) var;
return (is_same<T, int64_t>::value) || (is_same<T, uint64_t>::value);
}
template<class T> constexpr size_t howManyBytes (const T var) {
return sizeof(var);
}
template<class T> constexpr uint8_t howManyDigit (const T integer) {
size_t digit {0};
if (is_8Bit(integer)) digit = 3;
if (is_16Bit(integer)) digit = 5;
if (is_32Bit(integer)) digit = 10;
// if (is_64Bit(integer)) digit = 20; // vorhanden aber Print/Serial kann es nicht ausgeben
return digit;
}
template<class T> constexpr bool isTypeSigned (const T var) {
bool state {false};
if ( is_signed<decltype(var)>::value ) { state = true; }
return state;
}
template<class T> constexpr int64_t typeRangeMax (const T var) {
(void) var;
int64_t rangeMax {0};
if ( is_same<T, int8_t>::value ) { rangeMax = 127; }
if ( is_same<T, uint8_t>::value ) { rangeMax = 255; }
if ( is_same<T, int16_t>::value ) { rangeMax = 32767; }
if ( is_same<T, uint16_t>::value ) { rangeMax = 65535; }
if ( is_same<T, int32_t>::value ) { rangeMax = 2147483647; }
if ( is_same<T, uint32_t>::value ) { rangeMax = 4294967295; }
return rangeMax;
}
template<class T> constexpr int64_t typeRangeMin (const T var) {
(void) var;
int64_t rangeMin {0};
if ( is_same<T, int32_t>::value ) { rangeMin = -2147483648; }
if ( is_same<T, int16_t>::value ) { rangeMin = -32768; }
if ( is_same<T, int8_t>::value ) { rangeMin = -128; }
if ( is_same<T, uint8_t>::value ) { rangeMin = 0; }
if ( is_same<T, uint16_t>::value ) { rangeMin = 0; }
if ( is_same<T, uint32_t>::value ) { rangeMin = 0; }
return rangeMin;
}
void setup (void)
{
Serial.begin(250000);
cout.println(F("\nuC Reset ### ###"));
if (DEBUG) { cout << F("rx.buffer size ") << BUFFER_SIZE << endl << endl; }
}
void loop (void)
{
if (readSerial(cout, rx, BUFFER_SIZE))
{
if (DEBUG) { cout << F("eingelesen: ") << rx.buffer << endl; }
const size_t error = splitString(rx.buffer, user.value);
if (0 == error) { commandProcessing (cout, user); }
else { cout.println(F("Fehleingabe.")); }
if (DEBUG) { cout << F("error: ") << error << endl; }
}
}
// ****** Funktionen ****************************** //
/* Input: buffer/array per Referenz, Ziel struct per Referenz
Return: per Referenzen
Description: ersetzt die delimiter durch '\0' und
zerlegt den buffer in Token und übergibt diese anderen Funktionen */
uint8_t splitString (auto &string, auto &structure)
{
const char delimiter[] = " ";
char *rest {nullptr};
char *token {nullptr};
uint8_t error {0};
if (DEBUG) { cout << F("zerlege: ") << string << endl; }
// Initialisierungsaufruf von strtok_r
token = strtok_r(string, delimiter, &rest);
error = transferTokenToCommand(token, user.cmd);
if (DEBUG) { cout << F("user.cmd: ") << user.cmd << endl; }
for (auto &v : structure)
{
// ab hier jeder weitere strtok_r Aufruf mit 'NULL'
token = strtok_r(NULL, delimiter, &rest);
error += transferTokenToInteger (token, v, howManyDigit(v), isTypeSigned(v), typeRangeMin(v), typeRangeMax(v));
if (DEBUG) { cout << F("user.value[?] ") << v << endl << endl; }
}
return error;
}
/* Input: Token per const Referenz, Kommando per Referenz
Return: Fehleranzeige und Kommando per Referenz
Description: überprüft die Länge vom Token, wenn diese die maximal Länge von Kommando nicht überschreitet
wird der Token dem Kommando übergeben */
uint8_t transferTokenToCommand (const auto &token, auto &cmd)
{
const uint8_t TOKENLENGTH = strlen(token);
const uint8_t CMDCAPACITY = sizeof(cmd);
if (DEBUG) {
cout << F("\ntransferTokenToCommand()") << endl;
cout << F("TOKENLENGTH: ") << TOKENLENGTH << endl;
cout << F("CMDCAPACITY: ") << CMDCAPACITY << endl;
}
uint8_t error {0};
// Alternative, sorgt für "Befehl unbekannt" wenn zu lang
if (TOKENLENGTH < CMDCAPACITY) {
memcpy(cmd, token, CMDCAPACITY);
}
else {
cmd[0] = '\0';
error = 1;
}
if (DEBUG) { cout << F("data: ") << token << F(" cmd: ") << cmd <<endl; }
return error;
}
/* Input: Token per const Referenz, Integer per Referenz, Anzahl der zulässigen Integer Digits, ob Typ signed/unsigned, Typ Max, Typ Min
Return: Fehleranzeige und Integer per Referenz
Description: Ermittelt im token ob '-' Zeichen am Anfang steht, addiert zur max. Digitanzahl des Typs und überprüft damit die zulässige Tokenlänge.
Danach wird in Abhängigkeit ob Ziel Datentyp signed/unsigned ist entsprechend in temporären int64_t Integer gewandelt.
Dann erfolgt nochmal eine Prüfung ob irgendwelche Überschreitungen erkannt werden. Wenn ja wird ein Error Code zurückgegeben.
War die Überprüfung erfolgreich erfogt die endgültige Wertzuweisung an die Ziel Integer Variable. */
uint8_t transferTokenToInteger (const auto &token, auto &integer, const uint8_t MAXDIGIT, const bool TYPSIGNED, const auto &rangeMin, const auto &rangeMax)
{
const uint8_t TOKENLENGTH = strlen(token);
uint8_t error {0};
if (DEBUG) {
cout << F("\ntransferTokenToInteger()") << endl;
cout << F("token: ") << token << endl;
cout << F("TOKENLENGTH: ") << TOKENLENGTH << endl;
cout << F("MAXDIGIT: ") << MAXDIGIT << endl;
cout << F("isTypeSigned: ") << TYPSIGNED << endl;
}
[[maybe_unused]] uint8_t minusSign {0};
int64_t tempInteger {0};
if (token[0] == '-') { minusSign = 1; }
if (TOKENLENGTH <= MAXDIGIT+minusSign) {
if (TYPSIGNED) { tempInteger = strtol (token, NULL, 10 ); }
else { tempInteger = strtoul(token, NULL, 10 ); }
}
if ( (TOKENLENGTH > MAXDIGIT+minusSign) || (tempInteger < rangeMin) || (tempInteger > rangeMax) ) {
error = 2;
integer = 0;
}
else { integer = tempInteger; }
if (DEBUG) {
cout << F("minusSign: ") << minusSign << endl;
cout << F("token: ") << token << F(" integer: ") << integer <<endl;
}
return error;
}
/* Input: Serial per Referenz, Ziel struct per Referenz
Return: nichts bzw. nicht vorgehen
Description: In dieser Funktion werden alle benötigten Kommandos erstellt die zur Anwendung kommen sollen.
Das Kommando wird auf Übereinstimmung und Länge überprüft,
wenn beides passt wird die weitere Verarbeitung ausgeführt */
void commandProcessing (Stream &out, const auto &data)
{
const uint8_t CMDLENGTH = strlen(data.cmd);
out << F("cmd: ") << data.cmd << F(" (") << CMDLENGTH << F(" Zeichen)") <<endl;
// ****** CHECK & 5 ************
if ( (CMDLENGTH == 5) && (strstr(data.cmd, "check") == data.cmd) ) {
out.println(F("--- check --- ok"));
out << F("value[0]: ") << data.value[0] << F(" value[1]: ") << data.value[1] << endl;
}
// ****** FLAECHE & 7 ************
else if ( (CMDLENGTH == 7) && (strstr(data.cmd, "flaeche") == data.cmd) ) {
out.println(F("--- flaeche --- ok"));
out << F("Fläche: ") << data.value[0] * data.value[1] << endl;
}
out.println();
}
bool readSerial (Stream &stream, auto &data, const uint8_t LENGTH)
{
bool status {false};
char c = stream.read();
if (c == '\r' || c == '\n') { // wenn CR oder LF eingelesen
data.buffer[ data.index] = '\0'; // Terminierung
if (DEBUG) { stream << F("readSerial: ") << data.index << F(" numbers of character") << endl; }
data.index = 0;
status = true; // Character fertig eingelesen
}
else if ( (c >= 32) && ( data.index < LENGTH) ) { // wenn noch Platz im Buffer ist
data.buffer[data.index++] = c; // Zeichen speichern und Index inkrementieren
}
return status;
}
Falls sich das wer zu Gemüte führt und irgendwas feststellt was nicht so schön ist würde ich bitten sich zu melden. Für die zweite Variante sollte C++14 oder auch 17 in der platform.local.txt eingeschalten sein. Das sollte das bei euch funktionieren.