From ae3970ad5e1b3495726b70d5653c2424fccfba07 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Sun, 11 Dec 2022 16:13:03 +0100 Subject: add serial parser from robotrun to project --- confui/confui.pro | 11 +++- confui/makefile | 6 -- confui/mesh_connector.cpp | 8 +++ confui/mesh_connector.h | 6 +- shared/bin.c | 93 ++++++++++++++++++++++++++ shared/bin.h | 65 ++++++++++++++++++ shared/consts.h | 10 +++ shared/protocol.c | 30 +++++++++ shared/protocol.h | 163 ++++++++++++++++++++++++++++++++++++++++++++++ shared/protocol.md | 51 +++++++++++++++ shared/serial_parse.c | 61 +++++++++++++++++ shared/serial_parse.h | 17 +++++ 12 files changed, 511 insertions(+), 10 deletions(-) create mode 100644 shared/bin.c create mode 100644 shared/bin.h create mode 100644 shared/protocol.c create mode 100644 shared/protocol.h create mode 100644 shared/protocol.md create mode 100644 shared/serial_parse.c create mode 100644 shared/serial_parse.h diff --git a/confui/confui.pro b/confui/confui.pro index 2992f70..7dcd66c 100644 --- a/confui/confui.pro +++ b/confui/confui.pro @@ -9,7 +9,10 @@ SOURCES += \ ui_node.cpp \ ui_automation.cpp \ ui_scroll_container.cpp \ - serial.cpp + serial.cpp \ + ../shared/bin.c \ + ../shared/protocol.c \ + ../shared/serial_parse.c HEADERS += \ mainwindow.h \ @@ -19,7 +22,11 @@ HEADERS += \ ui_node.h \ ui_automation.h \ ui_scroll_container.h \ - serial.h + serial.h \ + ../shared/bin.h \ + ../shared/protocol.h \ + ../shared/serial_parse.h \ + ../shared/consts.h CONFIG += c++17 CONFIG += force_debug_info diff --git a/confui/makefile b/confui/makefile index 930daed..e1faed6 100644 --- a/confui/makefile +++ b/confui/makefile @@ -7,12 +7,6 @@ endif include confui.mk -OBJECTS += $(patsubst %.c,%.o, $(wildcard ../shared/*.c)) -confui: $(OBJECTS) - -../shared/%.o: ../shared/%.c - $(CC) -c $(CFLAGS) -w $< -o $@ - FMT_FILES := $(DIST) FMT_FILES := $(filter-out .%,$(FMT_FILES)) # filter hidden files FMT_FILES := $(filter-out /%,$(FMT_FILES)) # filter files outside working directory diff --git a/confui/mesh_connector.cpp b/confui/mesh_connector.cpp index 5b65d31..fd9c5fc 100644 --- a/confui/mesh_connector.cpp +++ b/confui/mesh_connector.cpp @@ -185,6 +185,14 @@ string CDMeshConnector::cd_mac_to_string(cd_mac_addr_t mac) { return ret; } +string CDMeshConnector::cd_uuid_to_string(cd_uuid_t uuid) { + char *addr = nullptr; + asprintf(&addr, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", uuid[15], uuid[14], uuid[13], uuid[12], uuid[11], uuid[10], uuid[9], uuid[8], uuid[7], uuid[6], uuid[5], uuid[4], uuid[3], uuid[2], uuid[1], uuid[0]); + string ret = addr; + free(addr); + return ret; +} + cd_s_automation *CDMeshConnector::get_link(cd_link_t id) { return _links[id]; } cd_s_node *CDMeshConnector::get_node(cd_uid_t id) { return _nodes[id]; } diff --git a/confui/mesh_connector.h b/confui/mesh_connector.h index 176778f..43f78d5 100644 --- a/confui/mesh_connector.h +++ b/confui/mesh_connector.h @@ -5,6 +5,8 @@ #include #include +#include "../shared/protocol.h" + using std::array; using std::map; using std::size_t; @@ -15,8 +17,6 @@ using std::vector; typedef uint32_t cd_uid_t; /** @brief link/automation id type */ typedef uint32_t cd_link_t; -/** @brief node mac address type */ -typedef uint8_t cd_mac_addr_t[6]; /** @brief automation types/actions */ enum cd_e_automation_type { @@ -157,6 +157,8 @@ public: // conversion functions /** @brief convert `cd_mac_addr_t` to `std::string` for printing/GUI */ static string cd_mac_to_string(cd_mac_addr_t mac); + /** @brief convert `cd_uuid_t` to `std::string` for printing/GUI */ + static string cd_uuid_to_string(cd_uuid_t uuid); }; /** @brief global pointer to mesh connector, initialized in CDMainWindow */ diff --git a/shared/bin.c b/shared/bin.c new file mode 100644 index 0000000..fdceb30 --- /dev/null +++ b/shared/bin.c @@ -0,0 +1,93 @@ +#include +#include + +#include "bin.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CD_ENDIAN_LITTLE (1) +#define CD_ENDIAN_BIG (0) + +#define _SHIFT_0B (8 * 0) +#define _SHIFT_1B (8 * 1) +#define _SHIFT_2B (8 * 2) +#define _SHIFT_3B (8 * 3) +#define _BYTE_0 ((uint32_t)0xff << (_SHIFT_0B)) +#define _BYTE_1 ((uint32_t)0xff << (_SHIFT_1B)) +#define _BYTE_2 ((uint32_t)0xff << (_SHIFT_2B)) +#define _BYTE_3 ((uint32_t)0xff << (_SHIFT_3B)) + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshift-count-overflow" +cd_s_bin *cd_bin_from_uint8_t(uint8_t data) { + size_t size = 1; + cd_s_bin *ret = malloc(sizeof(cd_s_bin) + sizeof(uint8_t) * size); + ret->bytes = size; + ret->data[0] = data; + return ret; +} + +cd_s_bin *cd_bin_from_uint16_t(uint16_t data) { + size_t size = 2; + cd_s_bin *ret = malloc(sizeof(cd_s_bin) + sizeof(uint8_t) * size); + data = cd_bin_hton16(data); + ret->bytes = size; + ret->data[0] = (data & _BYTE_1) >> _SHIFT_1B; + ret->data[1] = (data & _BYTE_0) >> _SHIFT_0B; + return ret; +} + +cd_s_bin *cd_bin_from_uint32_t(uint32_t data) { + size_t size = 4; + cd_s_bin *ret = malloc(sizeof(cd_s_bin) + sizeof(uint8_t) * size); + data = cd_bin_hton32(data); + ret->bytes = size; + ret->data[0] = (data & _BYTE_3) >> _SHIFT_3B; + ret->data[1] = (data & _BYTE_2) >> _SHIFT_2B; + ret->data[2] = (data & _BYTE_1) >> _SHIFT_1B; + ret->data[3] = (data & _BYTE_0) >> _SHIFT_0B; + return ret; +} + +uint32_t cd_bin_hton32(uint32_t h32) { + if (g_cd_endianness == CD_ENDIAN_BIG) return h32; + return ((h32 & _BYTE_0) << _SHIFT_3B) | ((h32 & _BYTE_1) << _SHIFT_1B) | + ((h32 & _BYTE_2) >> _SHIFT_1B) | ((h32 & _BYTE_3) >> _SHIFT_3B); +} +#pragma GCC diagnostic pop + +uint16_t cd_bin_hton16(uint16_t h16) { + if (g_cd_endianness == CD_ENDIAN_BIG) return h16; + return ((h16 & _BYTE_0) << _SHIFT_1B) | ((h16 & _BYTE_1) >> _SHIFT_1B); +} + +uint32_t cd_bin_ntoh32(uint32_t n32) { return cd_bin_hton32(n32); } +uint16_t cd_bin_ntoh16(uint16_t n16) { return cd_bin_hton16(n16); } + +cd_s_bin *cd_bin_s_alloc(uint16_t bytes, uint8_t *data) { + cd_s_bin *temp = malloc(sizeof(cd_s_bin) + sizeof(uint8_t) * bytes); + temp->bytes = bytes; + memcpy(&temp->data, data, bytes); + return temp; +} + +cd_s_bin *cd_bin_s_cat(cd_s_bin *a, cd_s_bin *b) { + uint8_t data[a->bytes + b->bytes]; + memcpy(data, a->data, a->bytes); + memcpy(data + a->bytes, b->data, b->bytes); + cd_s_bin *c = cd_bin_s_alloc(a->bytes + b->bytes, data); + free(a); + free(b); + return c; +} + +void cd_bin_repl_hton32(uint32_t *h32) { *h32 = cd_bin_hton32(*h32); } +void cd_bin_repl_hton16(uint16_t *h16) { *h16 = cd_bin_hton16(*h16); } +void cd_bin_repl_ntoh32(uint32_t *h32) { *h32 = cd_bin_ntoh32(*h32); } +void cd_bin_repl_ntoh16(uint16_t *h16) { *h16 = cd_bin_ntoh16(*h16); } + +#ifdef __cplusplus +} +#endif diff --git a/shared/bin.h b/shared/bin.h new file mode 100644 index 0000000..35d2bc4 --- /dev/null +++ b/shared/bin.h @@ -0,0 +1,65 @@ +#pragma once + +/** @file bin.h */ + +/** + * helper file for binary data + * + * - fix endianness with functions inspired by UNIX arpa/inet.h + * - convert uint16_t and uint32_t to cd_s_bin + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern uint8_t g_cd_endianness; + +/** cast `in.data` to `type out` */ +#define CD_CAST_BIN(type, in, out) type *out = (type *)&in->data; +#define CD_CREATE_MSG_BIN(type, normal, bin) CD_CREATE_MSG_SIZE_BIN(type, sizeof(type), normal, bin) +/** @brief */ +#define CD_CREATE_MSG_SIZE_BIN(type, size, normal, bin) \ + cd_s_bin *bin = malloc(sizeof(cd_s_bin) + size); \ + bin->bytes = size; \ + type *normal = (type *)&bin->data; + +/** @brief hold binary data with fixed size */ +typedef struct { + uint16_t bytes; /** @brief data size */ + uint8_t data[]; /** @brief data */ +} cd_s_bin; + +/** @brief allocate new cd_s_bin struct and fill with `*data` for `bytes` bytes */ +cd_s_bin *cd_bin_s_alloc(uint16_t bytes, uint8_t *data); +/** @brief concatenate 2 cd_s_bin structs, deallocates `a` and `b` */ +cd_s_bin *cd_bin_s_cat(cd_s_bin *a, cd_s_bin *b); + +cd_s_bin *cd_bin_from_uint8_t(uint8_t data); +cd_s_bin *cd_bin_from_uint16_t(uint16_t data); +cd_s_bin *cd_bin_from_uint32_t(uint32_t data); + +/** @brief convert 32-bit value from host endian to network (big-endian) */ +uint32_t cd_bin_hton32(uint32_t h32); +/** @brief convert 16-bit value from host endian to network (big-endian) */ +uint16_t cd_bin_hton16(uint16_t h16); +/** @brief convert 32-bit value from network (big-endian) to host endian */ +uint32_t cd_bin_ntoh32(uint32_t n32); +/** @brief convert 16-bit value from network (big-endian) to host endian */ +uint16_t cd_bin_ntoh16(uint16_t n16); + +/** @brief replace 32-bit value from host endian to network (big-endian) */ +void cd_bin_repl_hton32(uint32_t *h32); +/** @brief replace 16-bit value from host endian to network (big-endian) */ +void cd_bin_repl_hton16(uint16_t *h16); +/** @brief replace 32-bit value from network (big-endian) to host endian */ +void cd_bin_repl_ntoh32(uint32_t *n32); +/** @brief replace 16-bit value from network (big-endian) to host endian */ +void cd_bin_repl_ntoh16(uint16_t *n16); + +#ifdef __cplusplus +} +#endif + diff --git a/shared/consts.h b/shared/consts.h index 6f70f09..57426bf 100644 --- a/shared/consts.h +++ b/shared/consts.h @@ -1 +1,11 @@ #pragma once + +/** @file consts.h */ + +/** @brief size of input (receive) buffer (in bytes) */ +#define CD_SERIAL_READ_BUFFER_SIZE 255 +/** @brief size of the error handling buffer (in errors, not bytes) */ +#define CD_ERROR_BUFFER_SIZE 16 +/** @brief size of the serial communication buffer (in messages, not bytes) */ +#define CD_SERCOMM_BUFFER_SIZE 16 + diff --git a/shared/protocol.c b/shared/protocol.c new file mode 100644 index 0000000..474398a --- /dev/null +++ b/shared/protocol.c @@ -0,0 +1,30 @@ +#include "protocol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +size_t cd_cmd_sizeof(uint8_t data[CD_SERIAL_READ_BUFFER_SIZE], uint8_t data_length) { + cd_cmd_opcode_t opcode = data[0]; + if (CD_CMD_HANDLERS_SIZE[opcode] > 0) return CD_CMD_HANDLERS_SIZE[opcode]; + + cd_s_bin *copy = cd_bin_s_alloc(data_length, data); + size_t length = (*CD_CMD_HANDLERS_SIZEOF[opcode])(copy); + + free(copy); + + return length; +} + +#define CD_DYN_MEMBER_SIZEOF(struct_t, length_byte, trailing_type) \ + sizeof(struct_t) + \ + (data->bytes > length_byte ? (sizeof(trailing_type) * data->data[length_byte]) : 0) + +size_t cd_cmd_response_sizeof(cd_s_bin* data) { + (void) data; // unused variable TODO: implement this + return 0; +} + +#ifdef __cplusplus +} +#endif diff --git a/shared/protocol.h b/shared/protocol.h new file mode 100644 index 0000000..e2b3b1c --- /dev/null +++ b/shared/protocol.h @@ -0,0 +1,163 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file protocol.h */ + +#define CD_SERIAL_START_BYTE 0xff + +#include +#include + +#include "bin.h" +#include "consts.h" + +/** @brief mac address (00:11:22:33:44:55) */ +typedef uint8_t cd_mac_addr_t[6]; +/** @brief uuid (ffeeddcc-bbaa-9988-7766-554433221100) */ +typedef uint8_t cd_uuid_t[16]; + +/** @brief command opcode (identifies message type) */ +typedef uint8_t cd_cmd_opcode_t; +/** @brief command id (identifies messages uniquely) */ +typedef uint16_t cd_cmd_id_t; +/** @brief smallest boolean type */ +typedef uint8_t cd_cmd_bool_t; + +/** @brief cmd handler function signature */ +typedef void (cd_cmd_handler)(cd_s_bin *data); + +#pragma pack(push, 1) + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ +} cd_s_cmd_ping; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ + cd_cmd_bool_t all; /** @brief get all known nodes */ + cd_uuid_t uuid; /** @brief node uuid to get details from, ignored if `all` = `true` */ +} cd_s_cmd_get_node; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ + cd_cmd_bool_t on; /** @brief new led status */ + cd_uuid_t uuid; /** @brief node uuid to set */ +} cd_s_cmd_post_led; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ +} cd_s_cmd_post_link; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief message id */ + cd_uuid_t uuid; /** @brief node uuid */ + cd_cmd_bool_t join; /** @brief `true` to join network, `false` to leave network */ +} cd_s_cmd_post_net; + +typedef struct { +} cd_s_cmd_response_ping; + +/** @brief protocol node */ +typedef struct { + cd_uuid_t uuid; /** @brief node network uuid */ + cd_mac_addr_t address; /** @brief node bluetooth mac address */ + uint8_t name_len; /** @brief name length in bytes */ + cd_cmd_bool_t light_on; /** @brief state of light on node */ + cd_cmd_bool_t provisioned; /** @brief whether the node is provisioned into the network */ + const char name[]; /** @brief user-friendly node name */ +} cd_s_cmd_node; + +typedef struct { + uint16_t node_count; + cd_s_cmd_node nodes[]; +} cd_s_cmd_response_get_node; + +typedef struct { +} cd_s_cmd_response_post_led; + +typedef struct { +} cd_s_cmd_response_post_link; + +typedef struct { +} cd_s_cmd_response_post_net; + +typedef struct { + cd_cmd_opcode_t opcode; /** @brief cmd opcode */ + cd_cmd_id_t id; /** @brief response message id */ + cd_cmd_bool_t success; /** @brief `true` if some error occurred */ + cd_cmd_id_t response_id; /** @brief original message id */ + cd_cmd_opcode_t response_type; /** @brief response type, used to cast type of `response_info` */ + uint16_t response_size; /** @brief size of remaining response */ + uint8_t response_info[]; /** @brief (CAST) remaining response struct, not read if `response_size`=`0` */ +} cd_s_cmd_response; + +#pragma pack(pop) + +/** @brief global handler for complete messages */ +void cd_cmd_handle(uint8_t data[CD_SERIAL_READ_BUFFER_SIZE], uint8_t length); +/** @brief calculate message length for any message */ +size_t cd_cmd_sizeof(uint8_t data[CD_SERIAL_READ_BUFFER_SIZE], uint8_t length); + +/** @brief calculate dynamic size of response message */ +size_t cd_cmd_response_sizeof(cd_s_bin* data); + +// down here is the garbage manual duplicate code section for constants + +#define CD_CMD_COUNT 6 +typedef enum { + CD_CMD_PING = 0x00, /** @brief send ping */ + CD_CMD_GET_NODE = 0x01, /** @brief get node(s) */ + CD_CMD_POST_LED = 0x02, /** @brief set led */ + CD_CMD_POST_LINK = 0x03, /** @brief set/remove link */ + CD_CMD_POST_NET = 0x04, /** @brief (un)register node with network */ + CD_CMD_RESPONSE = 0x05, /** @brief response message */ +} cd_e_scmds; + +cd_cmd_handler cd_cmd_ping, + cd_cmd_get_node, + cd_cmd_post_led, + cd_cmd_post_link, + cd_cmd_post_net, + cd_cmd_response; + +/** @brief constant message sizes, 0 for dynamic size */ +static const size_t CD_CMD_HANDLERS_SIZE[CD_CMD_COUNT] = { + [CD_CMD_PING] = sizeof(cd_s_cmd_ping), + [CD_CMD_GET_NODE] = sizeof(cd_s_cmd_get_node), + [CD_CMD_POST_LED] = sizeof(cd_s_cmd_post_led), + [CD_CMD_POST_LINK] = sizeof(cd_s_cmd_post_link), + [CD_CMD_POST_NET] = sizeof(cd_s_cmd_post_net), + [CD_CMD_RESPONSE] = 0, +}; + +/** @brief constant message sizes, 0 for dynamic size */ +static size_t (* const CD_CMD_HANDLERS_SIZEOF[CD_CMD_COUNT])(cd_s_bin*) = { + [CD_CMD_PING] = NULL, + [CD_CMD_GET_NODE] = NULL, + [CD_CMD_POST_LED] = NULL, + [CD_CMD_POST_LINK] = NULL, + [CD_CMD_POST_NET] = NULL, + [CD_CMD_RESPONSE] = &cd_cmd_response_sizeof, +}; + +/** @brief stores message handlers in array with opcode as index */ +static cd_cmd_handler* const CD_CMD_HANDLERS[CD_CMD_COUNT] = { + [CD_CMD_PING] = &cd_cmd_ping, + [CD_CMD_GET_NODE] = &cd_cmd_get_node, + [CD_CMD_POST_LED] = &cd_cmd_post_led, + [CD_CMD_POST_LINK] = &cd_cmd_post_link, + [CD_CMD_POST_NET] = &cd_cmd_post_net, + [CD_CMD_RESPONSE] = &cd_cmd_response, +}; + +#ifdef __cplusplus +} +#endif diff --git a/shared/protocol.md b/shared/protocol.md new file mode 100644 index 0000000..62caded --- /dev/null +++ b/shared/protocol.md @@ -0,0 +1,51 @@ +# protocol specs + +## commands + +each command consists of a start byte, opcode, and a payload. each opcode +defines logic to handle payload length, so certain commands might expect a +fixed-length payload, a variable-length payload, or none at all. the start byte +is `0xff`, and because most data sent is in binary format, if the data contains +an `0xff` byte, it will be escaped by replacing it with two `0xff` bytes. this +is converted to a single `0xff` on the receiving end, so these duplicated bytes +and the starting byte don't count towards message length. + +opcodes are picked sequentially, and are stored as enum constants inside +shared/protocol.h for code readability. + +|code|name|implemented|directions|full name| +|--:|---|:-:|:-:|---| +|`0x00`|[PING](#ping)|yes|`r <=> c`|ping +|`0x02`|[EXPT](#expt)|yes|`r --> c`|exception + +### PING + +#### ping (`r <=> c`) (2 bytes) + +|type|description| +|-:|-| +|`uint8_t`|opcode (`0x00 + 0` or `0x00 + 1`)| +|`uint8_t`|ping id| + +**ping** sends back an identical message either way with the **same** direction +bit. _ping id_ is a random 8-bit value that identifies the ping message. this +is the only command that makes either the robot or client send a message with +an opcode not matching the respective sender. the direction bit indicates which +device initiated the ping message. + +### EXPT + +#### exception (`r --> c`) (3+ bytes) + +|type|description| +|-:|-| +|`uint8_t`|opcode (`0x02 + 1`)| +|`uint8_t`|error code| +|`uint8_t`|length| +|`uint8_t[length]`|message contents| + +the **exception** instruction is used by the robot to send errors, warnings, +and other messages back to the client. an error can also optionally contain a +message between 0 and 255 characters long. message length is sent before the +message, and can be 0 in case of no message. + diff --git a/shared/serial_parse.c b/shared/serial_parse.c new file mode 100644 index 0000000..de48f60 --- /dev/null +++ b/shared/serial_parse.c @@ -0,0 +1,61 @@ +#include + +#include "consts.h" +#include "serial_parse.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool cd_serial_parse(uint8_t byte) { + static uint8_t current_message[CD_SERIAL_READ_BUFFER_SIZE] = {0}; + static uint8_t current_message_index = 0; + static uint8_t complete_message_length = 2; + + static bool attentive = false; + static bool listening = false; + + if (byte == CD_SERIAL_START_BYTE) { + attentive = !attentive; + if (attentive && listening) return CD_SERIAL_READ_SUCCESS; + } else if (attentive) { + attentive = false; + listening = !listening; + if (!listening) return CD_SERIAL_READ_FAILURE; + } + + if (!listening) return CD_SERIAL_READ_SUCCESS; + current_message[current_message_index++] = byte; + + complete_message_length = cd_cmd_sizeof(current_message, current_message_index); + + if (current_message_index == complete_message_length) { + cd_cmd_handle(current_message, current_message_index); + + memset(¤t_message, 0, CD_SERIAL_READ_BUFFER_SIZE); + current_message_index = 0; + complete_message_length = 1; + attentive = false; + listening = false; + return CD_SERIAL_READ_SUCCESS; + } + + return CD_SERIAL_READ_SUCCESS; +} + +void cd_cmd_handle(uint8_t data[CD_SERIAL_READ_BUFFER_SIZE], uint8_t data_length) { + cd_s_bin *copy = cd_bin_s_alloc(data_length, data); + + if (data[0] >= CD_CMD_COUNT) return; + cd_cmd_handler* handler = CD_CMD_HANDLERS[data[0]]; + + if (handler == NULL) return; + (*handler)(copy); + + free(copy); +} + +#ifdef __cplusplus +} +#endif + diff --git a/shared/serial_parse.h b/shared/serial_parse.h new file mode 100644 index 0000000..1efedc1 --- /dev/null +++ b/shared/serial_parse.h @@ -0,0 +1,17 @@ +#pragma once + +/** @file serial_parse.h */ + +#include +#include + +#include "protocol.h" + +#define CD_SERIAL_READ_SUCCESS true +#define CD_SERIAL_READ_FAILURE false + +/** + * parse serial data byte by byte + * @return true if read success, false if read fails + */ +bool cd_serial_parse(uint8_t byte); -- cgit v1.2.3