Reputation: 13
I need to scan varied incoming messages from a serial stream to check if they contain this string:
"Everything: Received: switchX yy(y)"
where X = 1 to 9 and yy(y) is "on" or "off". i.e. "Everything: Received: switch4 on" or "Everything: Received: switch2 off" etc.
I am using the following code on an ATMega328 to do the check and pass the relevant variables to the transmit() function:
valid_data = sscanf(serial_buffer, "Everything: Received: switch%u %s", &switch_number, command);
if(valid_data == 2)
{
if(strcmp(command, "on") == 0)
{
transmit(switch_number, 1);
}
if(strcmp(command, "off") == 0)
{
transmit(switch_number, 0);
}
}
The check is triggered when the serial_buffer input ISR detects "\n". '\0' is appended to the serial stream to terminate the string.
It works and I'm not pushed for space/processing power but I just wondered if this is the best way to achieve the required result?
Upvotes: 1
Views: 89
Reputation: 180191
It works and I'm not pushed for space/processing power but I just wondered if this is the best way to achieve the required result?
It's unclear on which criteria you want us to judge, since neither speed nor memory usage is a pressing concern, but in the absence of pressure from those considerations I personally rate code simplicity and clarity as the most important criteria for source code, other than correctness, of course.
From that perspective, a solution based on sscanf()
is good, especially with a comparatively simple format string such as you in fact have. The pattern for the lines you want to match is pretty clear in the format string, and the following logic is clear and simple, too. As a bonus, it also should produce small code, since a library function does most of the work, and it is reasonable to hope that the implementation has put some effort into optimizing that function for good performance, so it's probably a win even on those criteria with which you were not too concerned.
There are, however, some possible correctness issues:
sscanf()
does not match whitespace literally. A run of one or more whitespace characters in the format string matches any run of zero or more whitespace characters in the input.sscanf()
skips leading whitespace before most fields, and before %u
fields in particular.sscanf()
will ignore anything in the input string after the last field matchAll of those issues can be dealt with, if needed. Here, for instance, is an alternative that handles all of them except the amount of whitespace between words (but including avoiding whitspace between "switch" and the digit):
unsigned char switch_number;
int nchars = 0;
int valid_data = sscanf(serial_buffer, "Everything: Received: switch%c %n",
&switch_number, &nchars);
if (valid_data >= 1 && switch_number - (unsigned) '1' < 9) {
char *command = serial_buffer + nchars;
if (strcmp(command, "on") == 0) {
transmit(switch_number - '0', 1);
} else if (strcmp(command, "off") == 0) {
transmit(switch_number - '0', 0);
} // else not a match
} // else not a match
Key differences from yours include
the switch number is read via a %c
directive, which reads a single character without skipping leading whitespace. The validation condition switch_number - (unsigned) '1' < 9
ensures that the character read is between '1' and '9'. It makes use of the fact that unsigned arithmetic wraps around.
instead of reading the command into a separate buffer, the length of the leading substring is captured via a %n
directive. This allows testing the whole tail against "on" and "off", thereby removing the need of an extra buffer, and enabling you to reject lines with trailing words.
If you want to check that all the whitespace, too, exactly matches, then the %n
can help with that as well. For example,
if (nchars == 30 && serial_buffer[11] == ' ' && serial_buffer[21] == ' '
serial_buffer[29] == ' ') // it's OK
Upvotes: 2