- 45.1k
- 4
- 42
- 81
// Return whether the payload of this command is of string type.
bool command_payload_is_string(uint8_t command);
// Return the expected packet length for the provided command,
// excluding header and tail.
int packet_length(uint8_t command);
// Attempt to read a packet, but do not block.
// Return a pointer to a static buffer holding the packet,
// or nullptr if no complete packet was received this time.
uint8_t *read_packet() {
const uint8_t expected_header = 0xaa;
const uint8_t expected_tail[4] = {0xcc, 0x33, 0xc3, 0x3c};
// Return immediately unless we have a byte to process.
if (Serial.available() == 0) return nullptr;
uint8_t data = Serial.read();
static uint8_t buffer[BUFFER_SIZE];
static int bytes_received, bytes_expected;
static enum {
EXPECTING_HEADER, EXPECTING_COMMAND,
EXPECTING_STRING, EXPECTING_PAYLOAD, EXPECTING_TAIL
} state = EXPECTING_HEADER;
switch (state) {
case EXPECTING_HEADER:
if (data = expected_header)
state = EXPECTING_COMMAND;
break;
case EXPECTING_COMMAND:
buffer[0] = data; // command byte
bytes_received = 1;
if (command_payload_is_string(data)) {
state = EXPECTING_STRING;
// bytes_expected is not relevant in this case.
} else {
state = EXPECTING_PAYLOAD;
bytes_expected = packet_length(data);
}
break;
case EXPECTING_STRING:
if (data == '0円') { // end of string
buffer[bytes_received++] = '0円';
state = EXPECTING_TAIL;
bytes_expected = 4;
bytes_received = 1;0;
} else if (bytes_received < BUFFER_SIZE - 1) {
buffer[bytes_received++] = data;
} else {
// Do nothing. As the string is too long
// to fit in the buffer, this byte is discarded.
}
break;
case EXPECTING_PAYLOAD:
buffer[bytes_received++] = data;
if (bytes_received >= bytes_expected) {
state = EXPECTING_TAIL;
bytes_expected = 4;
bytes_received = 0;
}
break;
case EXPECTING_TAIL:
// If we don't get the expected tail,
// give up and wait for a new header.
if (data != expected_tail[bytes_received++]) {
state = EXPECTING_HEADER;
break;
}
// If the tail is all right, return the buffer
// and get ready for the next packet.
if (bytes_received >= bytes_expected) {
state = EXPECTING_HEADER;
return buffer;
}
}
return nullptr;
}
Edit: According to your comment, some commands have a payload which
is a string of unknown length, but seemingly NUL-terminated . I
edited the code, and added an extra EXPECTING_STRING
state to handle
this special case. It is up to you to implement the boolean function
command_payload_is_string()
.
// Return the expected packet length for the provided command,
// excluding header and tail.
int packet_length(uint8_t command);
// Attempt to read a packet, but do not block.
// Return a pointer to a static buffer holding the packet,
// or nullptr if no complete packet was received this time.
uint8_t *read_packet() {
const uint8_t expected_header = 0xaa;
const uint8_t expected_tail[4] = {0xcc, 0x33, 0xc3, 0x3c};
// Return immediately unless we have a byte to process.
if (Serial.available() == 0) return nullptr;
uint8_t data = Serial.read();
static uint8_t buffer[BUFFER_SIZE];
static int bytes_received, bytes_expected;
static enum {
EXPECTING_HEADER, EXPECTING_COMMAND,
EXPECTING_PAYLOAD, EXPECTING_TAIL
} state = EXPECTING_HEADER;
switch (state) {
case EXPECTING_HEADER:
if (data = expected_header)
state = EXPECTING_COMMAND;
break;
case EXPECTING_COMMAND:
buffer[0] = data; // command byte
state = EXPECTING_PAYLOAD;
bytes_expected = packet_length(data);
bytes_received = 1;
break;
case EXPECTING_PAYLOAD:
buffer[bytes_received++] = data;
if (bytes_received >= bytes_expected) {
state = EXPECTING_TAIL;
bytes_expected = 4;
bytes_received = 0;
}
break;
case EXPECTING_TAIL:
// If we don't get the expected tail,
// give up and wait for a new header.
if (data != expected_tail[bytes_received++]) {
state = EXPECTING_HEADER;
break;
}
// If the tail is all right, return the buffer
// and get ready for the next packet.
if (bytes_received >= bytes_expected) {
state = EXPECTING_HEADER;
return buffer;
}
}
return nullptr;
}
// Return whether the payload of this command is of string type.
bool command_payload_is_string(uint8_t command);
// Return the expected packet length for the provided command,
// excluding header and tail.
int packet_length(uint8_t command);
// Attempt to read a packet, but do not block.
// Return a pointer to a static buffer holding the packet,
// or nullptr if no complete packet was received this time.
uint8_t *read_packet() {
const uint8_t expected_header = 0xaa;
const uint8_t expected_tail[4] = {0xcc, 0x33, 0xc3, 0x3c};
// Return immediately unless we have a byte to process.
if (Serial.available() == 0) return nullptr;
uint8_t data = Serial.read();
static uint8_t buffer[BUFFER_SIZE];
static int bytes_received, bytes_expected;
static enum {
EXPECTING_HEADER, EXPECTING_COMMAND,
EXPECTING_STRING, EXPECTING_PAYLOAD, EXPECTING_TAIL
} state = EXPECTING_HEADER;
switch (state) {
case EXPECTING_HEADER:
if (data = expected_header)
state = EXPECTING_COMMAND;
break;
case EXPECTING_COMMAND:
buffer[0] = data; // command byte
bytes_received = 1;
if (command_payload_is_string(data)) {
state = EXPECTING_STRING;
// bytes_expected is not relevant in this case.
} else {
state = EXPECTING_PAYLOAD;
bytes_expected = packet_length(data);
}
break;
case EXPECTING_STRING:
if (data == '0円') { // end of string
buffer[bytes_received++] = '0円';
state = EXPECTING_TAIL;
bytes_expected = 4;
bytes_received = 0;
} else if (bytes_received < BUFFER_SIZE - 1) {
buffer[bytes_received++] = data;
} else {
// Do nothing. As the string is too long
// to fit in the buffer, this byte is discarded.
}
break;
case EXPECTING_PAYLOAD:
buffer[bytes_received++] = data;
if (bytes_received >= bytes_expected) {
state = EXPECTING_TAIL;
bytes_expected = 4;
bytes_received = 0;
}
break;
case EXPECTING_TAIL:
// If we don't get the expected tail,
// give up and wait for a new header.
if (data != expected_tail[bytes_received++]) {
state = EXPECTING_HEADER;
break;
}
// If the tail is all right, return the buffer
// and get ready for the next packet.
if (bytes_received >= bytes_expected) {
state = EXPECTING_HEADER;
return buffer;
}
}
return nullptr;
}
Edit: According to your comment, some commands have a payload which
is a string of unknown length, but seemingly NUL-terminated . I
edited the code, and added an extra EXPECTING_STRING
state to handle
this special case. It is up to you to implement the boolean function
command_payload_is_string()
.
The short answer to your question is "yes, there is a way". Multiple ways actually. You could write a blocking function, which follows more or less the logic of the example you show, blocking while waiting for each new byte. Or you could write a non blocking function, which always return immediately and, either gives you the complete packet, or tells you that no full packet has been received so far.
The non-blocking way may be a bit more complicated, but it is more useful, because you program can do other stuff, and be responsive, while waiting for a packet. Here is my take at it. It is based on a finite state machine:
// Return the expected packet length for the provided command,
// excluding header and tail.
int packet_length(uint8_t command);
// Attempt to read a packet, but do not block.
// Return a pointer to a static buffer holding the packet,
// or nullptr if no complete packet was received this time.
uint8_t *read_packet() {
const uint8_t expected_header = 0xaa;
const uint8_t expected_tail[4] = {0xcc, 0x33, 0xc3, 0x3c};
// Return immediately unless we have a byte to process.
if (Serial.available() == 0) return nullptr;
uint8_t data = Serial.read();
static uint8_t buffer[BUFFER_SIZE];
static int bytes_received, bytes_expected;
static enum {
EXPECTING_HEADER, EXPECTING_COMMAND,
EXPECTING_PAYLOAD, EXPECTING_TAIL
} state = EXPECTING_HEADER;
switch (state) {
case EXPECTING_HEADER:
if (data = expected_header)
state = EXPECTING_COMMAND;
break;
case EXPECTING_COMMAND:
buffer[0] = data; // command byte
state = EXPECTING_PAYLOAD;
bytes_expected = packet_length(data);
bytes_received = 1;
break;
case EXPECTING_PAYLOAD:
buffer[bytes_received++] = data;
if (bytes_received >= bytes_expected) {
state = EXPECTING_TAIL;
bytes_expected = 4;
bytes_received = 0;
}
break;
case EXPECTING_TAIL:
// If we don't get the expected tail,
// give up and wait for a new header.
if (data != expected_tail[bytes_received++]) {
state = EXPECTING_HEADER;
break;
}
// If the tail is all right, return the buffer
// and get ready for the next packet.
if (bytes_received >= bytes_expected) {
state = EXPECTING_HEADER;
return buffer;
}
}
return nullptr;
}
It will be up to you to implement packet_length()
. See jstola's
comment on why it is needed.
You would use read_packet()
like this:
void loop() {
uint8_t *packet = read_packet();
if (packet)
parse_and_handle(packet);
// the rest of the program, never blocked by read_packet()...
}