Skip to main content
Arduino

Return to Answer

+ handle commands with variable-length string payloads.
Source Link
Edgar Bonet
  • 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().

Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

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()...
}
lang-cpp

AltStyle によって変換されたページ (->オリジナル) /