aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--confui/confui.pro11
-rw-r--r--confui/makefile6
-rw-r--r--confui/mesh_connector.cpp8
-rw-r--r--confui/mesh_connector.h6
-rw-r--r--shared/bin.c93
-rw-r--r--shared/bin.h65
-rw-r--r--shared/consts.h10
-rw-r--r--shared/protocol.c30
-rw-r--r--shared/protocol.h163
-rw-r--r--shared/protocol.md51
-rw-r--r--shared/serial_parse.c61
-rw-r--r--shared/serial_parse.h17
12 files changed, 511 insertions, 10 deletions
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 <string>
#include <vector>
+#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 <stdlib.h>
+#include <string.h>
+
+#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 <stdint.h>
+
+#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 <stdint.h>
+#include <stdlib.h>
+
+#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`|<u>ping</u>
+|`0x02`|[EXPT](#expt)|yes|`r --> c`|<u>ex</u>ce<u>pt</u>ion
+
+### 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 <string.h>
+
+#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(&current_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 <stdint.h>
+#include <stdbool.h>
+
+#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);