So I have a communication system between two microcontrollers and I'm sending data between them, i.e. sensor data from one µC to the other one, and commands back. The sensor data is taken from a struct and put into a frame, which looks like this (the identifier "SENSORFRAME" is not constant; it depends on what's in the frame):
sprintf(message, "\:\\SENSORFRAME\:%.2f;%.2f;%.2f;%d;%d;%u\r\r",
input.temperature, input.current, input.voltage,
input.dutycycle,input.lightsensor, input.message);
Resulting in a Frame like this:
:\SENSORFRAME:-12.40;1.42;0.53;500;1200;8\r\r
Or for the commandframes:
sprintf(message, "\:\\COMMANDFRAME\:%u;%.5f\r\r", input.command, input.data);
Resulting in a Frame like this:
:\COMMANDFRAME:3;1.42\r\r
When the bytestream arrives at one microcontroller it is written into a simple ring buffer until it is processed.
Now my two questions are, firstly, what is the best way to identify a frame in a bytestream, i.e. everything between ":\" and "\r\r", and secondly how to parse it efficiently back into the struct — some combination of strtok
(";
" and ":
"), and atoi
/atof
?
If you do not process the type of frame until you've read the double carriage return (CR) at the end and you then insert a null byte after the double CR, then you can extract the frame type information using sscanf()
or other parsing techniques.
char frame_type[32];
char colon[2];
int offset;
if (sscanf(ring_buffer, ":\\%31[^:]%[:]%n", frame_type, colon, &offset) != 2)
…deal with malformatted frame…
Now you have the frame type in frame_type
as a string. The slightly odd %[:]
only matches a colon; what's crucial, though, is that it gets counted as a successful conversion, and the %n
conversion will be valid. If it was changed to:
if (sscanf(":\\%31[^:]:%n", frame_type, &offset) != 1)
…deal with malformatted frame…
you would not know whether the trailing colon was matched or not, and you wouldn't know whether offset
held a valid value (the offset into the string where the preceding character, the colon, was found).
You have at least two frame types; how many are there in total (at the moment, and could there be in the future)? At one level, it doesn't matter. You could use a series of string comparisons against the possible frame types, or you could use a hash of the frame type string and compare that against the set of valid hash values, or you could devise another mechanism.
Once you know which frame type you have, you know what format string to use to read the rest of the data. Since you read up to a double CR, you know that the ending contains that — you don't need to validate it again.
For example, for the sensor frame, you might use:
if (sscanf(ring_buffer + offset, "%.2f;%.2f;%.2f;%d;%d;%u",
&input.temperature, &input.current, &input.voltage,
&input.dutycycle, &input.lightsensor, &input.message) != 6)
…deal with malformatted sensor frame…
Or, for the command frame, you might use:
if (sscanf(ring_buffer + offset, "%u;%.5f\r\r", &input.command, &input.data) != 2)
…deal with malformatted command frame…
The only complicating factor is that you're using a ring buffer. That could mean that your sensor frame is split so that the first 5 bytes are at the end of the ring buffer and the remainder at the start of the buffer. Frankly, if you can afford the space and copying, converting the ring buffer to a regular (linear?) buffer will be easiest. If that is absolutely not an option, then you are probably stuck with not using sscanf()
at all; you will either need to write your own variant of sscanf()
that can be told about the shape of the ring buffer and work with that, or you will have to work character at a time.
Perhaps your custom function is:
int rbscanf(const char *rb_base, int rb_len, int rb_off, const char *format, ...);
The ring buffer starts at rb_base
and is rb_len
bytes long in total; the data starts at &rb_base[rb_off]
. You might need to specify the buffer length and the data length (rb_len
and rb_nbytes
). You might already have a structure describing the ring buffer, in which case, you could pass (a pointer to) that to the function.
Alternatively, if you process the data before you've read the entire frame, then you can validate as you read the bytes. You'll still need to accumulate strings and numbers for conversion. You'll probably use strtol()
and strtod()
rather than atoi()
and atof()
; you will need to know about errors, including trailing unconverted characters, which the atoi()
and atof()
functions cannot tell you about. Care is required with the strtoX()
functions, but they are effective.