-
-
Save TennesseeJed/980dbc68aa2e0002399e35c81ef8c0eb to your computer and use it in GitHub Desktop.
Ultra naive but functional parsing example for Arduino. See https://arduino.stackexchange.com/questions/61122/how-to-parse-multi-line-serial-data-with-an-unknown-number-of-lines
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <string.h> | |
/* Example valid packets: | |
G: 5.79kg\r\n | |
G:6.23kg\r\n | |
Invalid: | |
X: foo\r\n | |
G:---32kg\r\n | |
*/ | |
// Type of data | |
typedef enum { | |
NoType, | |
NO, | |
G, | |
T, | |
N, | |
} Type; | |
// type of unit | |
typedef enum { | |
NoUnit, | |
uKg, // kilogram | |
uG, // gram | |
} Unit; | |
// unions are handy | |
typedef union { | |
char str[5]; | |
float dval; | |
} Value; | |
// the data model | |
typedef struct { | |
Type type; | |
Value value; | |
Unit unit; | |
byte valid; | |
// metadata | |
byte expectUnit; // 0 if a unit should be specified, false otherwise. | |
} Packet; | |
// Sets pPacket->type to the type found in psz. Type is delimited by a colon (:) | |
// Returns 0 (null) if a known Type wasn't found. | |
// Otherwise a ptr to the data past colon delimiter. | |
char* processType(Packet* pPacket, const char* psz) { | |
// find the colon (:) delimiter and return what it leads to. | |
byte len = 0; | |
char* ptr = strchr((char*)psz, ':'); | |
char* pResult = 0; | |
if (ptr) { | |
// found the colon. | |
char buffer[4]; | |
byte len = (ptr - psz) / sizeof(char); // pointer math to find length. | |
strncpy(buffer, psz, len); | |
// null term buff | |
buffer[len] = 0; | |
// assume succcess; default will handle fail. | |
pResult = (char*)psz + len + 1; // +1 to skip colon character. | |
switch (buffer[0]) { | |
case 'G': | |
pPacket->type = Type::G; | |
pPacket->expectUnit = 1; // unit should be given. | |
break; | |
default: | |
pPacket->type = Type::NoType; | |
pResult = 0; // fail, didn't find a known type. | |
} | |
} | |
return pResult; | |
} | |
// Sets pPacket->value.dval | |
// Returns 0 (null) if a value wasn't found. | |
// Otherwise a ptr past the last character of the decimal value. | |
char* processFloat(Packet* pPacket, const char*psz) { | |
const char *float_chars = ".0123456789"; | |
char* ptr = (char*)psz; | |
if (ptr) { | |
// scan ptr until we don't find a match. | |
byte index = 0; | |
while (strchr(float_chars, (char)ptr[index])) | |
index++; | |
if (index) { | |
// get the float using a temporary buffer. | |
// dangerous! assuming this will not overrun! | |
char buf[10]; | |
strncpy(buf, ptr, index); | |
// null term buff | |
buf[index] = 0; | |
pPacket->value.dval = atof(buf); | |
// advance ptr past the decimal characters for return value. | |
ptr += index; | |
} | |
else ptr = 0; // fail | |
} | |
return ptr; | |
} | |
// Sets pPacket->unit to the unit given in psz. | |
// Returns 0 if no unit was found, non-zero otherwise. | |
byte processUnit(Packet* pPacket, const char* psz) { | |
pPacket->unit = Unit::NoUnit; | |
if (psz) { | |
switch ((char)*psz) { | |
case 'k': | |
pPacket->unit = Unit::uKg; | |
break; | |
case 'g': | |
pPacket->unit = Unit::uG; | |
break; | |
default: | |
break; // unit initialized to NoUnit. | |
} | |
} | |
return pPacket->unit != Unit::NoUnit; | |
} | |
// Sets pPacket->value according to the packet Type. | |
// Returns null (0) if type not supported or value not found, | |
// otherwise a pointer to the remaining characters following the value handled. | |
char* processValue(Packet* pPacket, const char* psz) { | |
char* ptr = 0; | |
// value is construed by Type | |
switch (pPacket->type) { | |
case Type::G: | |
ptr = processFloat(pPacket, psz); | |
break; | |
// | |
// add your additonal Type implementations... | |
// | |
default: | |
// invalid, set psz to null. | |
ptr = 0; | |
} | |
return ptr; | |
} | |
// Returns a ptr to the first non-space character in psz. | |
// If no spaces found, ptr = psz. | |
// Ideally this should check any whitespace e.g. \t\r\n etc | |
// This function is unsafe because it assumes psz is null-terminated. | |
char* trimLeadingSpaces(const char* psz) { | |
char *ptr = (char*)psz; | |
if (ptr) | |
while (*ptr == ' ') ptr++; | |
return ptr; | |
} | |
// pszBuffer should be a string of characters delimited by \r\n. | |
// pPacket is a pointer to a Packet which is filled by this function. | |
// returns 1 if packet is valid, 0 otherwise. | |
// This function does NOT check array boundary overruns! | |
byte readPacket(Packet* pPacket, const char* pszBuffer) { | |
byte isvalid = 0; | |
if (pszBuffer && pPacket) { | |
char tmp[10]; | |
char* psz = (char*)pszBuffer; | |
char* pszTmp = 0; | |
byte len = 0; | |
// initialize packet | |
pPacket->type = Type::NoType; | |
pPacket->unit = Unit::NoUnit; | |
pPacket->valid = 0; | |
pPacket->expectUnit = 0; | |
// get the Type | |
psz = processType(pPacket, psz); | |
if (psz) { | |
// skip any space characters now. | |
psz = trimLeadingSpaces(psz); | |
psz = processValue(pPacket, psz); | |
// if Type handled, check units and finish. | |
if (psz) { | |
if (pPacket->expectUnit) | |
pPacket->valid = processUnit(pPacket, psz); | |
else | |
pPacket->valid = 1; | |
} | |
} // Type not found | |
isvalid = pPacket->valid; | |
} | |
return isvalid; | |
} | |
char buffer[41]; | |
byte index; | |
void resetBuffer() { | |
index = 0; | |
buffer[0] = 0; | |
} | |
void setup(void) { | |
Serial.begin(19200); | |
resetBuffer(); | |
Serial.println("Enter a packet with a CRLF delimiter."); | |
} | |
void loop() { | |
while (Serial.available()) { | |
buffer[index++] = Serial.read(); | |
if (index == sizeof(buffer) - sizeof(char)) { | |
Serial.println("Buffer overrun! resetting."); | |
resetBuffer(); | |
} | |
buffer[index] = 0; | |
char *ptr = strstr(buffer, "\r\n"); | |
if (ptr) { // ok got an packet delimiter. | |
Packet packet; | |
if (readPacket(&packet, buffer)) { | |
// process packet | |
Serial.println("Valid packet:"); | |
Serial.print("type: "); Serial.println(packet.type); | |
switch (packet.type) { | |
case Type::G: | |
Serial.print("value: "); Serial.println(packet.value.dval); | |
Serial.print("units: "); Serial.println(packet.unit); | |
break; | |
} | |
} else { | |
Serial.println("Invalid packet!"); | |
} | |
Serial.println(); | |
resetBuffer(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment