Parser - mit strtok_r und ohne

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.

Du könntest dir mal numeric_limits anschauen.

Ich bin mir gerade nicht sicher, ob strncpy() irgendwelche Garantienen bezüglich überlappenen Buffern macht. Vielleicht besser memmove() an der Stelle einsetzen.

Hast du einen Link / Anhang vergessen, oder bin ich blind oder verstehe ich da was falsch ?

Klicke im Post mal auf die kleinen schwarzen Dreiecke/Pfeile

Hallo,

strncpy habe ich in der ersten Version verwendet. Es kopiert maximal n Zeichen oder der Null-Terminator wird eher gelesen. Eine Tücke gibts jedoch mit dem Null-Terminator, deswegen sollte man den immer nochmal von Hand im Ziel setzen. strlcpy wäre wohl besser wie gerade gelesen. Wurde in der neueren Version alles durch strtok_r ersetzt,

#include <limits>
Das ist leider ein anderes Thema. Kann ich verwenden. Die Frage ist wer kann das noch aus dem Forum verwenden? Ich kenne nur combie von dem ich das sicher weiß. Dunkelziffer unbekannt. Dafür benötigt man die AvrStdCpp und möglichst eine aktuellere Toolchain. Deswegen stehen die TypeTraits usw. im Sketch.

Das bestätige ich!
Die 32Bit und 64 Bit User haben das allerdings auch alle.

Aber Punkt ist ja wohl, dass es auch flächendeckend auf AVRs laufen soll.

PS:
Deine Parser Geschichte habe ich bisher erst überflogen. Das reicht noch nicht, um mir sowas wie eine Beurteilung erlauben zu dürfen.

Hallo,

Die 32Bit und 64 Bit User haben das allerdings auch alle.

Das vergesse ich immer wieder, bin zu sehr auf AVR fokussiert.

Aber Punkt ist ja wohl, dass es auch flächendeckend auf AVRs laufen soll.

Richtig, ich wollte es der breiten Masse zeigen. Spezialversionen für einen selbst sind immer möglich. Kann ja auch nur immer als Basis/Einstieg dienen. Das Protokoll UserData macht ja sicherlich jeder anders. float fehlt auch noch. Wobei UserData nur zum "groben" einlesen dienen soll für das maximal Mögliche. Alle Feinheiten wie zum Bsp. Wertebereichslimitierungen sollte man in commandProcessing() einbauen. Wenn ich dabei an Tommys RGB Eingabe denke könnte man dort und nicht vorher die Wertebereichtslimiterung einbauen. Wenn der Parser rein dem RGB dienen soll, dann kann man UserData anpassen. Dann wären jedoch nach aktueller Programmierung und Philosophie alle Eingabe außerhalb Byte gnadenlos ungültig. Gedacht ist das so, dass man alle erforderlichen Befehlsdefinitionen in commandProcessing() erstellt.

Was mir gerade durch die Birne sackt ist, dass man vielleicht erst das Kommando auswertet und dann je nach Anforderung das entsprechende Protokoll verwendet. Dann könnte man verschiedene Member von/in UserData anlegen und dann entsprechend als Parameter übergeben zur weiteren Verarbeitung. Ich merke gerade es gibt noch viele fast zu viele Ideen. Ich lasse das erstmal sacken und wende den Parser erstmal selbst an. :wink: Der ist quasi als Nebenprodukt entstanden damit ich Duty und Frequenz bequem über die Tastatur eingeben kann für mein Liebling dem Timer.

Kritiken werden gesammelt und durchdacht. Ich mache aber keine Versprechen alles sofort umzusetzen. Das muss alles überlegt sein und ich schreibe ja nicht nur am Parser. Es dauert so lange wie es dauert. :wink: Ich gehe davon aus es wird ein ewiger Prozess.

Ich habe erst mal nur grob drüber geschaut. Ich muss gleich zu einem Seminar "Käse selbst herstellen". Ich will heute den Grundstock für einen Hartkäse mit Knoblauch legen.

Vorschlag: Ich würde den Delimitter in splitString mit als Parameter übergeben. Das erhöht die Flexibilität.

Gruß Tommy

Hallo,

vom Wikinger Tommy zum Käse Tommy, was für eine Entwicklung. :rofl:
Den Parametergedanken nehme ich dankend mit auf.

Wir haben in Fürth einen der letzten 3 Käse- Affineure Deutschlands. Da gibt es interessante Seminare bei unserem Käse-Toni, z.B. letzte Woche Käse und Portwein, Montag Käse und Hochprozentiges. Man soll das Leben genießen.

Gruß Tommy

Hallo,

sowas macht man viel zu selten - Das Leben genießen.

Hallo,

ich habe ein komisches unverstandenes Problem festgestellt. Nehme ich exakt den gleichen Code und lasse ihn auf einem anderen AVR µC laufen, wird "cmd" von commandProcessing 2x ausgeführt. Also nach fertiger Abarbeitung wird nochmal NULL komplett ausgewertet und angezeigt. Lässt sich schon mit leerer Return Eingabe provozieren. Bin aktuell ratlos.

Auf dem "Entwicklungs" Nano Every ohne Debug nur mit Retun erscheint:

cmd:  (0 Zeichen)

mit AVR DB erscheint

cmd:  (0 Zeichen)

cmd:  (0 Zeichen)

Gleiche Toolchain usw. Kann doch nicht sein ... ?
Kann jemand mit letzerem Sketch mit Debug false mir sagen wie das bei euch reagiert?
1 oder 2 Zeilen Ausgabe? Wäre mal interessant. Danke.

In commandProcessing hast Du die Ausgabe nochmal ohne DEBUG-Einschränkung drin.

Gruß Tommy

Hallo,

genau um diese Ausgabe geht es, genau die darf nur einmal erscheinen. Mit anderem Controller erscheint diese Zeile jedoch 2x. Mit zusammengestrichen Sketch kann ich das weiterhin provozieren. Der Einzigste verbleibende Unterschied ist das Core Package. 'grübel'

TestSketch
#include <Streaming.h>

Stream &cout {Serial};
constexpr uint8_t BUFFER_SIZE {10};

struct ReceiveData {
  char buffer [BUFFER_SIZE + 1]; // + Null-Terminator
  size_t index {0};
};
ReceiveData rx;

void setup (void)
{
  Serial.begin(250000);
  cout.println(F("\nuC Reset ### ###"));
} 

void loop (void)
{
  if (readSerial(cout, rx, BUFFER_SIZE))
  {
    cout << F("eingelesen: ") << rx.buffer << endl;
  }
}

// ****** Funktionen ****************************** //

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
    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;
}

Wenn Du readSerial mehrfach hintereinander aufrufst, ist der Status immer noch true.
Gib true zurück, setze aber vorher den status auf false und data.index wieder auf 0 für die nächste Meldung.

Gruß Tommy

Hallo,

entschuldige aber das stimmt doch überhaupt nicht. So wird readSerial nicht aufgerufen.

Sorry, ich war wohl in dem anderen Quelltext gelandet. Das ist mit den beiden versteckten Teilen etwas verwirrend.

Gruß Tommy

Hallo,

okay. Wegen dem Code einklappen, ich dachte und denke immer noch das es der Übersicht im Thread ungemein nützlich ist. Nicht?

Für den Beitrag evtl. ja. Aber die Gefahr ist halt groß, dass man das falsche erwischt.
Wie machst Du das?

Gruß Tommy

Hallo,

[details=Beschriftung] 
Text, Code, Bilder. Links etc.
[/details]

Was ich nicht hinbekomme ist es "Beschriftung" fett und/oder farbig zu machen. Das macht immer alles kaputt.

Beliebige Farben funktioniert auch nicht mehr, hat einmal funktioniert.

<font color="#1466b8">test</font>
<font color=red>test</font>

Das funktioniert noch einzeln.

[b][color=red]red[/color][/b]

Wegen Thema Ausgabe nochmal. :wink: Ich gehe davon aus das ihr immer nur 1 Zeile zu Gesicht bekommt so wie sich das gehört.