Skip to content

Instantly share code, notes, and snippets.

@TennesseeJed
Last active January 30, 2019 17:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TennesseeJed/980dbc68aa2e0002399e35c81ef8c0eb to your computer and use it in GitHub Desktop.
Save TennesseeJed/980dbc68aa2e0002399e35c81ef8c0eb to your computer and use it in GitHub Desktop.
#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