aboutsummaryrefslogtreecommitdiff
path: root/shared
diff options
context:
space:
mode:
authorlonkaars <loek@pipeframe.xyz>2022-10-13 15:59:14 +0200
committerlonkaars <loek@pipeframe.xyz>2022-10-13 15:59:14 +0200
commit385b47211ea8674f97f014537d694bb2efbd6ab9 (patch)
treebad26f85d854ae55b4e97680de96e07e9ff7ea8d /shared
parent1032c63bae663d8ed8736020b60a4ba8744062ee (diff)
WIP protocol implementation
Diffstat (limited to 'shared')
-rw-r--r--shared/bin.h12
-rwxr-xr-xshared/mainbin0 -> 21456 bytes
-rw-r--r--shared/makefile24
-rw-r--r--shared/protocol.c72
-rw-r--r--shared/protocol.h85
-rw-r--r--shared/protocol.md54
-rw-r--r--shared/test.c36
-rw-r--r--shared/testcmd1
8 files changed, 284 insertions, 0 deletions
diff --git a/shared/bin.h b/shared/bin.h
new file mode 100644
index 0000000..c7405be
--- /dev/null
+++ b/shared/bin.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <stdint.h>
+
+typedef struct {
+ uint16_t bytes;
+ uint8_t data[];
+} ws_s_bin;
+
+/** allocate new ws_s_bin struct and fill with `*data` for `bytes` bytes */
+ws_s_bin *ws_bin_s_alloc(uint16_t bytes, uint8_t *data);
+
diff --git a/shared/main b/shared/main
new file mode 100755
index 0000000..7d7b7fe
--- /dev/null
+++ b/shared/main
Binary files differ
diff --git a/shared/makefile b/shared/makefile
new file mode 100644
index 0000000..4d538da
--- /dev/null
+++ b/shared/makefile
@@ -0,0 +1,24 @@
+CC = gcc
+LD = gcc
+RM = rm -f
+CFLAGS =
+LFLAGS =
+TARGET = main
+
+SRCS := $(wildcard *.c)
+OBJS := $(patsubst %.c,%.o, $(SRCS))
+
+all: main
+
+%.o: %.c
+ $(CC) -c $(CFLAGS) $< -o $@
+
+$(TARGET): $(OBJS)
+ $(LD) $^ $(LFLAGS) -o $@
+
+clean:
+ $(RM) $(TARGET) $(OBJS)
+
+compile_commands: clean
+ compiledb make
+
diff --git a/shared/protocol.c b/shared/protocol.c
new file mode 100644
index 0000000..85b320f
--- /dev/null
+++ b/shared/protocol.c
@@ -0,0 +1,72 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "protocol.h"
+
+void ws_protocol_parse_byte(ws_s_protocol_parser_state* state, char input) {
+ switch(input) {
+ case WS_PROTOCOL_C_NEWLINE: {
+ if (!state->valid) return;
+ break;
+ }
+
+ case WS_PROTOCOL_C_SPACE: {
+ if (!state->valid) return;
+ printf("argument delimiter\n");
+ return;
+ }
+
+ case WS_PROTOCOL_C_NULL: {
+ state->valid = false;
+ return;
+ }
+
+ default: {
+ if (!state->valid) return;
+ printf("recv byte 0x%02x, (\"%c\")\n", input, input);
+ state->cmd[state->cmd_len++] = input;
+ state->args_len[state->arg_len] += 1;
+ if (state->cmd_len == WS_PROTOCOL_CMD_BUFFER_LEN) state->valid = false;
+ return;
+ }
+ }
+
+ printf("command done!\n");
+}
+
+void ws_protocol_parse_bytes(ws_s_protocol_parser_state* state, char* input, unsigned int length) {
+ for (unsigned int i = 0; i < length; i++) ws_protocol_parse_byte(state, input[i]);
+}
+
+ws_s_protocol_parser_state* ws_protocol_parser_alloc() {
+ ws_s_protocol_parser_state* parser_state = malloc(sizeof(ws_s_protocol_parser_state) + sizeof(uint16_t) * WS_PROTOCOL_CMD_MAX_ARGUMENTS);
+ parser_state->cmd = malloc(sizeof(char) * WS_PROTOCOL_CMD_BUFFER_LEN);
+ parser_state->valid = true;
+ parser_state->cmd_len = 0;
+ parser_state->arg_len = 0;
+ parser_state->target = NULL;
+ return parser_state;
+}
+
+void ws_protocol_cmd_init(ws_s_protocol_parser_state* state) {
+ state->target = malloc(sizeof(ws_s_protocol_parsed_cmd) + sizeof(char*) * state->arg_len);
+ for (unsigned int i = 0; i < state->arg_len; i++)
+ state->target->argv[i] = malloc(sizeof(char) * state->args_len[i]);
+ state->target->argc = state->arg_len;
+}
+
+void ws_protocol_parser_free(ws_s_protocol_parser_state* state) {
+ if (state == NULL) return;
+ if (state->target != NULL) ws_protocol_cmd_free(state->target);
+ free(state->cmd);
+ free(state);
+ state = NULL;
+ return;
+}
+void ws_protocol_cmd_free(ws_s_protocol_parsed_cmd* cmd) {
+ for (unsigned int i = 0; i < cmd->argc; i++)
+ free(cmd->argv[i]);
+ free(cmd);
+ cmd = NULL;
+ return;
+}
diff --git a/shared/protocol.h b/shared/protocol.h
new file mode 100644
index 0000000..5cf7ac6
--- /dev/null
+++ b/shared/protocol.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "bin.h"
+
+#define WS_PROTOCOL_CMD_MAX_ARGUMENTS (1)
+#define WS_PROTOCOL_CMD_BUFFER_LEN (40)
+
+#define WS_PROTOCOL_CMD_AMOUNT (1)
+
+#define WS_PROTOCOL_C_NEWLINE (0x0a)
+#define WS_PROTOCOL_C_SPACE (0x20)
+#define WS_PROTOCOL_C_NULL (0x00)
+
+typedef struct {
+ int argc;
+ char* argv[];
+} ws_s_protocol_parsed_cmd;
+
+typedef struct {
+ ws_s_protocol_parsed_cmd* target;
+ bool valid;
+ char* cmd;
+ uint16_t cmd_len;
+ uint16_t arg_len;
+ uint16_t args_len[];
+} ws_s_protocol_parser_state;
+
+//TODO: document
+ws_s_protocol_parser_state* ws_protocol_parser_alloc();
+void ws_protocol_parser_free(ws_s_protocol_parser_state* state);
+void ws_protocol_cmd_init(ws_s_protocol_parser_state* state);
+void ws_protocol_cmd_free(ws_s_protocol_parsed_cmd* cmd);
+
+/**
+ * @brief parse incoming data byte by byte until a finished command is detected
+ *
+ * @remark [server]
+ *
+ * @param state parser state object, each incoming request should have it's own parser state
+ * @param input input byte
+ */
+void ws_protocol_parse_byte(ws_s_protocol_parser_state* state, char input);
+/**
+ * @brief parse incoming data chunk
+ *
+ * @remark [server]
+ *
+ * @param state parser state object, each incoming request should have it's own parser state
+ * @param input input byte array
+ * @param length input byte array length
+ */
+void ws_protocol_parse_bytes(ws_s_protocol_parser_state* state, char* input, unsigned int length);
+
+/**
+ * @brief create a `last-records` request command
+ * @remark [client]
+ * @return ws_s_bin containing the command string
+ */
+ws_s_bin* ws_protocol_req_last_records(unsigned int record_amount);
+
+/**
+ * @brief `last-records` response handler
+ *
+ * @remark [server]
+ *
+ * gets fired when the weather station receives a complete `last-records`
+ * command, and returns the response string
+ *
+ * @param parsed_cmd complete parsed command from ws_protocol_parse_*
+ *
+ * @return ws_s_bin containing response string
+ */
+ws_s_bin* ws_protocol_res_last_records(ws_s_protocol_parsed_cmd* parsed_cmd);
+
+typedef enum {
+ WS_PROTOCOL_CMD_LAST_RECORDS = 0,
+} ws_e_protocol_cmd;
+
+static ws_s_bin* (*g_ws_protocol_res_handlers[WS_PROTOCOL_CMD_AMOUNT])(ws_s_protocol_parsed_cmd* parsed_cmd) = {
+ [WS_PROTOCOL_CMD_LAST_RECORDS] = &ws_protocol_res_last_records
+};
+
diff --git a/shared/protocol.md b/shared/protocol.md
new file mode 100644
index 0000000..bafec4d
--- /dev/null
+++ b/shared/protocol.md
@@ -0,0 +1,54 @@
+# Protocol spec
+
+This is a brief overview of the protocol specifications that the weather
+station uses to send and receive data between the weather station and qt
+client. This protocol is text-based, and used over a TCP connection. This
+document will only go into detail about the data sent over this connection, not
+requirements about the connection itself.
+
+The protocol is only used in a request-response fashion, so all commands are
+assumed to be sent by the qt client, and responded to by the weather station.
+
+Functions for generating commands and parsing incoming data are provided by the
+protocol.c and protocol.h files. See [code
+implementation](#code-implementation) section for more details about naming
+conventions.
+
+- LF for newline instead of CRLF
+- Commands are single-line
+- Spaces used for separating command arguments
+- Commands with malformed data are discarded and return error
+- Response consist of `ok` or `error`, a comma, and the byte length of the
+ remaining response (if any)
+- Numbers are sent as hexadecimal
+
+## Commands
+
+### `last-records <n>`
+
+Returns the last `n` records in csv format. The first line has the csv table
+header, with the fields `id`, `temperature`, `humidity`, and
+`atmospheric_pressure`. The rest of the response consists of 1 record per line.
+When `n` is 0, or no records exist yet, the csv header is still returned, but
+without any records.
+
+## Example transaction
+
+In the following example, newlines are indicated by `<0a>`, request by lines
+starting with `<`, and response by lines starting with `>`.
+
+```
+< last-records 5<0a>
+> ok,115<0a>
+> id,temperature,humidity,atmospheric_pressure<0a>
+> 10dc,2f,c5,7f<0a>
+> 10dd,30,c6,7f<0a>
+> 10de,31,c7,7f<0a>
+> 10df,35,ca,7e<0a>
+> 10e0,34,c9,7e<0a>
+```
+
+## Code implementation
+
+
+
diff --git a/shared/test.c b/shared/test.c
new file mode 100644
index 0000000..788dc94
--- /dev/null
+++ b/shared/test.c
@@ -0,0 +1,36 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <termios.h>
+#include <fcntl.h>
+
+#include "protocol.h"
+
+ws_s_bin* ws_protocol_res_last_records(ws_s_protocol_parsed_cmd* parsed_cmd) {
+ return NULL;
+}
+
+int main() {
+ // disable echo and enable raw mode
+ fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
+ struct termios term;
+ tcgetattr(STDIN_FILENO, &term);
+ term.c_lflag &= ~(ECHO | ICANON);
+ term.c_cc[VTIME] = 0;
+ term.c_cc[VMIN] = 1;
+ tcsetattr(STDIN_FILENO, 0, &term);
+
+ ws_s_protocol_parser_state* parser1 = ws_protocol_parser_alloc();
+
+ for(;;) {
+ fflush(stdout);
+
+ char byte;
+ while(read(STDIN_FILENO, &byte, 1) > 0)
+ ws_protocol_parse_byte(parser1, byte);
+ }
+
+ ws_protocol_parser_free(parser1);
+
+ return 0;
+}
diff --git a/shared/testcmd b/shared/testcmd
new file mode 100644
index 0000000..17f8842
--- /dev/null
+++ b/shared/testcmd
@@ -0,0 +1 @@
+last-records 5