aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--shared/backlog.c47
-rw-r--r--shared/backlog.h59
-rw-r--r--shared/bin.c11
-rw-r--r--shared/bin.h13
-rw-r--r--shared/makefile24
-rw-r--r--shared/protocol.c136
-rw-r--r--shared/protocol.h150
-rw-r--r--shared/protocol.md54
-rw-r--r--shared/test.c82
-rw-r--r--shared/testcmd1
-rw-r--r--shared/util.h4
-rw-r--r--stm32f091/backlog.c41
-rw-r--r--stm32f091/backlog.h4
14 files changed, 612 insertions, 15 deletions
diff --git a/.gitignore b/.gitignore
index 1c68c8a..aba4463 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,5 +7,6 @@ stm32f091/main.bin
client/makefile
client/client
client/moc_*
+shared/main
.qmake.stash
.vscode/.cortex-debug.registers.state.json
diff --git a/shared/backlog.c b/shared/backlog.c
new file mode 100644
index 0000000..926ccad
--- /dev/null
+++ b/shared/backlog.c
@@ -0,0 +1,47 @@
+#include <stdlib.h>
+
+#include "backlog.h"
+
+ws_s_backlog_database* g_ws_backlog_database = NULL;
+
+void ws_backlog_alloc(uint16_t record_amt) {
+ g_ws_backlog_database = malloc(sizeof(ws_s_backlog_database) + sizeof(ws_s_backlog_record) * record_amt);
+ g_ws_backlog_database->buffer_size = record_amt;
+ g_ws_backlog_database->buffer_start = 0;
+ g_ws_backlog_database->buffer_end = 0;
+}
+
+void ws_backlog_add_record(ws_s_backlog_record record) {
+ static uint16_t id = 0;
+
+ g_ws_backlog_database->records[g_ws_backlog_database->buffer_end].id = id++;
+ g_ws_backlog_database->records[g_ws_backlog_database->buffer_end].sens_atm_pressure = record.sens_atm_pressure;
+ g_ws_backlog_database->records[g_ws_backlog_database->buffer_end].sens_humidity = record.sens_humidity;
+ g_ws_backlog_database->records[g_ws_backlog_database->buffer_end].sens_temperature = record.sens_temperature;
+
+ // shift buffer start/end
+ g_ws_backlog_database->buffer_end = (g_ws_backlog_database->buffer_end + 1) % g_ws_backlog_database->buffer_size;
+ if (g_ws_backlog_database->buffer_end == g_ws_backlog_database->buffer_start)
+ g_ws_backlog_database->buffer_start = (g_ws_backlog_database->buffer_start + 1) % g_ws_backlog_database->buffer_size;
+}
+
+ws_s_backlog_record* ws_backlog_get_record(uint16_t record_index) {
+ return &g_ws_backlog_database->records[record_index];
+}
+
+ws_s_backlog_record* ws_backlog_get_last_record(uint16_t record_offset) {
+ return ws_backlog_get_record((g_ws_backlog_database->buffer_end - record_offset - 1) % g_ws_backlog_database->buffer_size);
+}
+
+static uint16_t mod(uint16_t a, uint16_t b) {
+ uint16_t m = a % b;
+ return m < 0 ? (b < 0) ? m - b : m + b : m;
+}
+
+uint16_t ws_backlog_get_record_count() {
+ // add buffer_size to the result of the modulo operation if it's result is negative
+ // (only works when buffer_size is less than 2^15)
+ // this is a consequence of the way in which c handles negative numbers in modulo operations
+ int16_t mod = (g_ws_backlog_database->buffer_end - g_ws_backlog_database->buffer_start) % g_ws_backlog_database->buffer_size;
+ return mod < 0 ? mod + g_ws_backlog_database->buffer_size : mod;
+}
diff --git a/shared/backlog.h b/shared/backlog.h
new file mode 100644
index 0000000..c8ea019
--- /dev/null
+++ b/shared/backlog.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <stdint.h>
+
+/**
+ * @brief allocate backlog buffer and set global backlog pointer
+ * @param record_amt amount of records to keep before overwriting oldest record
+ */
+void ws_backlog_alloc(uint16_t record_amt);
+
+// enable struct packing
+#pragma pack(push, 1)
+
+/** @brief backlog record */
+typedef struct {
+ uint16_t id; /**< unique record identifier, numbered sequentially */
+ uint8_t sens_temperature; /**< temperature reading */
+ uint8_t sens_humidity; /**< humidity reading */
+ uint8_t sens_atm_pressure; /**< atmospheric pressure reading */
+} ws_s_backlog_record;
+
+typedef struct {
+ uint16_t buffer_size; /**< buffer size (amount of records) */
+ uint16_t buffer_start; /** first record index */
+ uint16_t buffer_end; /** last record index */
+ ws_s_backlog_record records[]; /** record array */
+} ws_s_backlog_database;
+
+// disable struct packing
+#pragma pack(pop)
+
+/** @brief global record backlog database pointer */
+extern ws_s_backlog_database* g_ws_backlog_database;
+
+/**
+ * @brief add record to database
+ *
+ * automatically sets record.id, pushes buffer_end forwards and overwrites the
+ * last record if the buffer is full
+ */
+void ws_backlog_add_record(ws_s_backlog_record record);
+
+/**
+ * there's intentionally no function to retrieve multiple records as an array,
+ * as this would either require
+ * (a) copying the selection which is not possible with the current memory
+ * constraints, or
+ * (b) giving a direct pointer, but this would cause undefined behavior at the
+ * ring buffer seam
+ */
+
+/** @brief get pointer to record with index `record_index` from the database */
+ws_s_backlog_record* ws_backlog_get_record(uint16_t record_index);
+
+/** @brief get pointer to last record with offset `record_offset` from the database */
+ws_s_backlog_record* ws_backlog_get_last_record(uint16_t record_offset);
+
+/** @brief return amount of valid records in database */
+uint16_t ws_backlog_get_record_count();
diff --git a/shared/bin.c b/shared/bin.c
new file mode 100644
index 0000000..def2aa8
--- /dev/null
+++ b/shared/bin.c
@@ -0,0 +1,11 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <memory.h>
+
+#include "bin.h"
+
+ws_s_bin *ws_bin_s_alloc(uint16_t bytes) {
+ ws_s_bin *temp = malloc(sizeof(ws_s_bin) + sizeof(uint8_t) * bytes);
+ temp->bytes = bytes;
+ return temp;
+}
diff --git a/shared/bin.h b/shared/bin.h
new file mode 100644
index 0000000..bfcda0c
--- /dev/null
+++ b/shared/bin.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <stdint.h>
+
+/** @brief binary data container with length */
+typedef struct {
+ uint16_t bytes;
+ uint8_t data[];
+} ws_s_bin;
+
+/** @brief allocate new ws_s_bin struct */
+ws_s_bin *ws_bin_s_alloc(uint16_t bytes);
+
diff --git a/shared/makefile b/shared/makefile
new file mode 100644
index 0000000..2093f00
--- /dev/null
+++ b/shared/makefile
@@ -0,0 +1,24 @@
+CC = gcc
+LD = gcc
+RM = rm -f
+CFLAGS = -g -std=c11
+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..55e6759
--- /dev/null
+++ b/shared/protocol.c
@@ -0,0 +1,136 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "protocol.h"
+
+#define WS_CMD_MAP(parsed_cmd, name, code) \
+ if (strlen(parsed_cmd->argv[0]) == strlen(name) && strncmp(parsed_cmd->argv[0], name, strlen(name)) == 0) return code;
+
+static ws_e_protocol_cmd ws_protocol_get_req_cmd_code(ws_s_protocol_parsed_req_cmd* parsed_cmd) {
+ if (parsed_cmd == NULL) return WS_PROTOCOL_CMD_UNKNOWN; // invalid command
+ WS_CMD_MAP(parsed_cmd, "last-records", WS_PROTOCOL_CMD_LAST_RECORDS);
+
+ return WS_PROTOCOL_CMD_UNKNOWN;
+}
+
+void ws_protocol_parse_req_byte(ws_s_protocol_req_parser_state* state, char input) {
+ switch(input) {
+ case WS_PROTOCOL_C_EOL: {
+ break;
+ }
+
+ case WS_PROTOCOL_C_SPACE: {
+ if (!state->valid) return;
+ state->arg_len++;
+ return;
+ }
+
+ case WS_PROTOCOL_C_NULL: {
+ state->valid = false;
+ return;
+ }
+
+ default: {
+ if (!state->valid) return;
+ 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;
+ }
+ }
+ // arg_len is used as an index while parsing, so add 1 to get length
+ state->arg_len++;
+
+ // parse cmd into argc and argv
+ if (state->valid) ws_protocol_req_cmd_init(state);
+ // create response
+ ws_s_protocol_res* response = ws_protocol_parse_req_finished(state->target);
+
+ // send response
+ char response_first_line[16];
+ sprintf(response_first_line, "%s,%x\n", response->success == WS_PROTOCOL_CMD_RETURN_OK ? "ok" : "error", response->msg->bytes);
+ ws_protocol_send_data(response_first_line, strlen(response_first_line));
+ if (!response->csh) ws_protocol_send_data((char*) response->msg->data, response->msg->bytes);
+ else (*g_ws_protocol_res_handlers[response->cmd_code])(state->target, response, true);
+
+ // free response data containers
+ free(response->msg);
+ free(response);
+
+ // reset parser
+ ws_protocol_req_parser_reset(state);
+
+ return;
+}
+
+ws_s_protocol_res* ws_protocol_parse_req_finished(ws_s_protocol_parsed_req_cmd* parsed_cmd) {
+ ws_s_protocol_res* response = malloc(sizeof(ws_s_protocol_res));
+ response->success = WS_PROTOCOL_CMD_RETURN_ERROR;
+ response->csh = false;
+ response->msg = NULL;
+ response->cmd_code = ws_protocol_get_req_cmd_code(parsed_cmd);
+
+ if (response->cmd_code == WS_PROTOCOL_CMD_UNKNOWN) goto ws_protocol_parse_exit;
+ if (response->cmd_code >= WS_PROTOCOL_CMD_AMOUNT) goto ws_protocol_parse_exit;
+
+ ws_protocol_res_handler_t* ws_protocol_res_handler = g_ws_protocol_res_handlers[response->cmd_code];
+ if (ws_protocol_res_handler == NULL) goto ws_protocol_parse_exit;
+ (*ws_protocol_res_handler)(parsed_cmd, response, false);
+
+ws_protocol_parse_exit:
+
+ if (response->msg == NULL) response->msg = ws_bin_s_alloc(0);
+ return response;
+}
+
+void ws_protocol_parse_req_bytes(ws_s_protocol_req_parser_state* state, char* input, unsigned int length) {
+ for (unsigned int i = 0; i < length; i++) ws_protocol_parse_req_byte(state, input[i]);
+}
+
+ws_s_protocol_req_parser_state* ws_protocol_req_parser_alloc() {
+ ws_s_protocol_req_parser_state* parser_state = malloc(sizeof(ws_s_protocol_req_parser_state) + sizeof(uint16_t) * WS_PROTOCOL_CMD_MAX_ARGUMENTS);
+ parser_state->cmd = malloc(sizeof(char) * WS_PROTOCOL_CMD_BUFFER_LEN);
+ ws_protocol_req_parser_reset(parser_state);
+ return parser_state;
+}
+
+void ws_protocol_req_cmd_init(ws_s_protocol_req_parser_state* state) {
+ state->target = malloc(sizeof(ws_s_protocol_parsed_req_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] + 1));
+
+ state->target->argc = state->arg_len;
+
+ unsigned int head = 0;
+ for (unsigned int i = 0; i < state->arg_len; i++) {
+ strncpy(state->target->argv[i], &state->cmd[head], state->args_len[i]);
+ state->target->argv[i][state->args_len[i]] = 0x00; // terminate argument with null byte
+ head += state->args_len[i];
+ }
+}
+
+void ws_protocol_req_parser_free(ws_s_protocol_req_parser_state* state) {
+ if (state == NULL) return;
+ if (state->target != NULL) ws_protocol_req_cmd_free(state->target);
+ state->target = NULL;
+ free(state->cmd);
+ free(state);
+ return;
+}
+
+void ws_protocol_req_parser_reset(ws_s_protocol_req_parser_state* state) {
+ if (state->target != NULL) ws_protocol_req_cmd_free(state->target);
+ state->target = NULL;
+ state->valid = true;
+ state->cmd_len = 0;
+ state->arg_len = 0;
+ memset(state->args_len, 0, sizeof(uint16_t) * WS_PROTOCOL_CMD_MAX_ARGUMENTS);
+}
+
+void ws_protocol_req_cmd_free(ws_s_protocol_parsed_req_cmd* cmd) {
+ for (unsigned int i = 0; i < cmd->argc; i++)
+ free(cmd->argv[i]);
+ free(cmd);
+ return;
+}
diff --git a/shared/protocol.h b/shared/protocol.h
new file mode 100644
index 0000000..fbe29d6
--- /dev/null
+++ b/shared/protocol.h
@@ -0,0 +1,150 @@
+#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_EOL (0x0a)
+#define WS_PROTOCOL_C_SPACE (0x20)
+#define WS_PROTOCOL_C_NULL (0x00)
+
+/**
+ * @brief parsed request cmd struct, holds arguments similar to argc and argv
+ * provided to `int main()`
+ */
+typedef struct {
+ int argc; /** argument count */
+ char* argv[]; /** argument array, null terminated strings */
+} ws_s_protocol_parsed_req_cmd;
+
+/**
+ * @brief holds parser state variables for `ws_protocol_parse_req_byte` function.
+ * each incoming tcp request should get it's own parser 'instance'
+ */
+typedef struct {
+ ws_s_protocol_parsed_req_cmd* target; /** parsed cmd reference */
+ bool valid; /** command still valid flag */
+ char* cmd; /** raw cmd */
+ uint16_t cmd_len; /** raw cmd string length */
+ uint16_t arg_len; /** amount of arguments */
+ uint16_t args_len[]; /** array of argument lengths */
+} ws_s_protocol_req_parser_state;
+
+/** @brief return values for command handlers */
+typedef enum {
+ WS_PROTOCOL_CMD_RETURN_OK = 0,
+ WS_PROTOCOL_CMD_RETURN_ERROR = 1,
+} ws_e_protocol_cmd_return_value;
+
+/** @brief cmd codes (used to call handlers) */
+typedef enum {
+ WS_PROTOCOL_CMD_UNKNOWN = -1,
+
+ WS_PROTOCOL_CMD_LAST_RECORDS = 0,
+} ws_e_protocol_cmd;
+
+/** @brief request response data struct */
+typedef struct {
+ ws_e_protocol_cmd_return_value success; /** status code for response
+ validity, defaults to
+ WS_PROTOCOL_CMD_RETURN_ERROR */
+ bool csh; /** whether the response handler has logic for a custom send
+ handler, false by default */
+ ws_s_bin* msg; /** pointer to response data, uninitialized by default */
+ ws_e_protocol_cmd cmd_code; /** cmd code */
+} ws_s_protocol_res;
+
+/**
+ * @brief allocate parser struct
+ *
+ * @return pointer to newly allocated struct
+ */
+ws_s_protocol_req_parser_state* ws_protocol_req_parser_alloc();
+/** @brief deallocate parser struct, automatically frees all child pointers */
+void ws_protocol_req_parser_free(ws_s_protocol_req_parser_state* state);
+/** @brief reset parser state to parse a new request */
+void ws_protocol_req_parser_reset(ws_s_protocol_req_parser_state* state);
+/**
+ * @brief initialize ws_s_protocol_parsed_req_cmd struct pointer of
+ * ws_s_protocol_req_parser_state (internal only)
+ */
+void ws_protocol_req_cmd_init(ws_s_protocol_req_parser_state* state);
+/** @brief deallocate ws_s_protocol_parsed_req_cmd struct pointer (internal only) */
+void ws_protocol_req_cmd_free(ws_s_protocol_parsed_req_cmd* cmd);
+
+/**
+ * @brief parse incoming data byte by byte until a finished command is detected
+ *
+ * @param state parser state object, each incoming request should have it's own parser state
+ * @param input input byte
+ */
+void ws_protocol_parse_req_byte(ws_s_protocol_req_parser_state* state, char input);
+/**
+ * @brief parse incoming data chunk
+ *
+ * @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_req_bytes(ws_s_protocol_req_parser_state* state, char* input, unsigned int length);
+/**
+ * @brief handle complete command
+ *
+ * this function gets called when ws_protocol_parse_req_byte(s) has detected a
+ * finished command. this function decides which command handler gets called,
+ * given that argv[0] contains a valid command. command argument parsing is
+ * handled by the command handler function.
+ *
+ * @return response
+ *
+ * @param parsed_cmd cmd parsed into ws_s_protocol_parsed_req_cmd struct
+ */
+ws_s_protocol_res* ws_protocol_parse_req_finished(ws_s_protocol_parsed_req_cmd* parsed_cmd);
+
+/**
+ * @brief create a `last-records` request command
+ * @return ws_s_bin containing the command string
+ */
+ws_s_bin* ws_protocol_req_last_records(unsigned int record_amount);
+
+/**
+ * @brief response handler
+ *
+ * gets fired when the weather station receives a complete command, and returns
+ * a response struct with a success code and an optional message. if
+ * response->csh is set to `true` within the handler, it gets fired a second
+ * time after the response header is sent, but with the `send` parameter set to
+ * `true`. this is so response handlers can send large amounts of data without
+ * allocating large areas of memory.
+ *
+ * @param parsed_cmd complete parsed command from ws_protocol_parse_req_*
+ * @param response response struct with uninitialized pointer to msg
+ * @param send `false` on first run, `true` on second run if `response->csh` was set to true
+ */
+typedef void ws_protocol_res_handler_t(ws_s_protocol_parsed_req_cmd*, ws_s_protocol_res*, bool);
+
+ws_protocol_res_handler_t ws_protocol_res_last_records;
+
+/**
+ * @brief data sender wrapper
+ *
+ * this function should be implemented in the source files of each target
+ * platform, as the send interface will be different on desktop and on the
+ * stm32.
+ *
+ * @param data pointer to data char array
+ * @param length length of data array
+ */
+void ws_protocol_send_data(const char* data, unsigned int length);
+
+/** @brief response handlers, called when a command is parsed */
+static ws_protocol_res_handler_t* g_ws_protocol_res_handlers[WS_PROTOCOL_CMD_AMOUNT] = {
+ [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..287332a
--- /dev/null
+++ b/shared/test.c
@@ -0,0 +1,82 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "protocol.h"
+#include "backlog.h"
+#include "util.h"
+
+void ws_protocol_res_last_records(ws_s_protocol_parsed_req_cmd* parsed_cmd, ws_s_protocol_res* response, bool send) {
+ static unsigned int record_amount = 0;
+ const char* response_header = "id,temperature,humidity,atmospheric_pressure\n";
+ const size_t response_line_size = sizeof("xxxx,xx,xx,xx\n");
+
+ if (!send) {
+ response->success = WS_PROTOCOL_CMD_RETURN_OK;
+ if (sscanf(parsed_cmd->argv[1], "%u", &record_amount) < 1) response->success = WS_PROTOCOL_CMD_RETURN_ERROR;
+ record_amount = WS_MIN(record_amount, ws_backlog_get_record_count());
+ response->csh = true;
+ response->msg = ws_bin_s_alloc(0);
+ response->msg->bytes = strlen(response_header) + response_line_size * record_amount;
+ } else {
+ // example send routine
+ ws_protocol_send_data(response_header, strlen(response_header));
+ char line[response_line_size + 1]; // +1 for null terminator -> sprintf
+ for (unsigned int i = 0; i < record_amount; i++) {
+ ws_s_backlog_record* record = ws_backlog_get_last_record(i);
+ sprintf(line, "%04x,%02x,%02x,%02x\n", record->id, record->sens_temperature, record->sens_humidity, record->sens_atm_pressure);
+ ws_protocol_send_data(line, response_line_size);
+ }
+ }
+}
+
+void ws_protocol_send_data(const char* data, unsigned int length) {
+ printf("%.*s", length, data);
+}
+
+int main() {
+ ws_backlog_alloc(10);
+
+ // 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_req_parser_state* parser1 = ws_protocol_req_parser_alloc();
+
+ ws_backlog_add_record((ws_s_backlog_record) {
+ .sens_temperature = 0x29,
+ .sens_humidity = 0x34,
+ .sens_atm_pressure = 0x69,
+ });
+
+ ws_backlog_add_record((ws_s_backlog_record) {
+ .sens_temperature = 0x00,
+ .sens_humidity = 0x00,
+ .sens_atm_pressure = 0x00,
+ });
+
+ ws_backlog_add_record((ws_s_backlog_record) {
+ .sens_temperature = 0x01,
+ .sens_humidity = 0x01,
+ .sens_atm_pressure = 0x01,
+ });
+
+ fflush(stdout);
+
+ char byte;
+ while(read(STDIN_FILENO, &byte, 1) > 0)
+ ws_protocol_parse_req_byte(parser1, byte);
+
+ ws_protocol_req_parser_free(parser1);
+ parser1 = NULL;
+
+ 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
diff --git a/shared/util.h b/shared/util.h
new file mode 100644
index 0000000..f39ff34
--- /dev/null
+++ b/shared/util.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#define WS_MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define WS_MAX(a, b) (((a) > (b)) ? (a) : (b))
diff --git a/stm32f091/backlog.c b/stm32f091/backlog.c
index 3f21924..926ccad 100644
--- a/stm32f091/backlog.c
+++ b/stm32f091/backlog.c
@@ -2,33 +2,46 @@
#include "backlog.h"
-ws_s_backlog_database* WS_G_BACKLOG_DATABASE = NULL;
+ws_s_backlog_database* g_ws_backlog_database = NULL;
void ws_backlog_alloc(uint16_t record_amt) {
- WS_G_BACKLOG_DATABASE = malloc(sizeof(ws_s_backlog_database) + sizeof(ws_s_backlog_record) * record_amt);
- WS_G_BACKLOG_DATABASE->buffer_size = record_amt;
- WS_G_BACKLOG_DATABASE->buffer_start = 0;
- WS_G_BACKLOG_DATABASE->buffer_end = 0;
+ g_ws_backlog_database = malloc(sizeof(ws_s_backlog_database) + sizeof(ws_s_backlog_record) * record_amt);
+ g_ws_backlog_database->buffer_size = record_amt;
+ g_ws_backlog_database->buffer_start = 0;
+ g_ws_backlog_database->buffer_end = 0;
}
void ws_backlog_add_record(ws_s_backlog_record record) {
static uint16_t id = 0;
- WS_G_BACKLOG_DATABASE->records[WS_G_BACKLOG_DATABASE->buffer_end].id = id++;
- WS_G_BACKLOG_DATABASE->records[WS_G_BACKLOG_DATABASE->buffer_end].sens_atm_pressure = record.sens_atm_pressure;
- WS_G_BACKLOG_DATABASE->records[WS_G_BACKLOG_DATABASE->buffer_end].sens_humidity = record.sens_humidity;
- WS_G_BACKLOG_DATABASE->records[WS_G_BACKLOG_DATABASE->buffer_end].sens_temperature = record.sens_temperature;
+ g_ws_backlog_database->records[g_ws_backlog_database->buffer_end].id = id++;
+ g_ws_backlog_database->records[g_ws_backlog_database->buffer_end].sens_atm_pressure = record.sens_atm_pressure;
+ g_ws_backlog_database->records[g_ws_backlog_database->buffer_end].sens_humidity = record.sens_humidity;
+ g_ws_backlog_database->records[g_ws_backlog_database->buffer_end].sens_temperature = record.sens_temperature;
// shift buffer start/end
- WS_G_BACKLOG_DATABASE->buffer_end = (WS_G_BACKLOG_DATABASE->buffer_end + 1) % WS_G_BACKLOG_DATABASE->buffer_size;
- if (WS_G_BACKLOG_DATABASE->buffer_end == WS_G_BACKLOG_DATABASE->buffer_start)
- WS_G_BACKLOG_DATABASE->buffer_start = (WS_G_BACKLOG_DATABASE->buffer_start + 1) % WS_G_BACKLOG_DATABASE->buffer_size;
+ g_ws_backlog_database->buffer_end = (g_ws_backlog_database->buffer_end + 1) % g_ws_backlog_database->buffer_size;
+ if (g_ws_backlog_database->buffer_end == g_ws_backlog_database->buffer_start)
+ g_ws_backlog_database->buffer_start = (g_ws_backlog_database->buffer_start + 1) % g_ws_backlog_database->buffer_size;
}
ws_s_backlog_record* ws_backlog_get_record(uint16_t record_index) {
- return &WS_G_BACKLOG_DATABASE->records[record_index];
+ return &g_ws_backlog_database->records[record_index];
}
ws_s_backlog_record* ws_backlog_get_last_record(uint16_t record_offset) {
- return ws_backlog_get_record((WS_G_BACKLOG_DATABASE->buffer_end - record_offset - 1) % WS_G_BACKLOG_DATABASE->buffer_size);
+ return ws_backlog_get_record((g_ws_backlog_database->buffer_end - record_offset - 1) % g_ws_backlog_database->buffer_size);
+}
+
+static uint16_t mod(uint16_t a, uint16_t b) {
+ uint16_t m = a % b;
+ return m < 0 ? (b < 0) ? m - b : m + b : m;
+}
+
+uint16_t ws_backlog_get_record_count() {
+ // add buffer_size to the result of the modulo operation if it's result is negative
+ // (only works when buffer_size is less than 2^15)
+ // this is a consequence of the way in which c handles negative numbers in modulo operations
+ int16_t mod = (g_ws_backlog_database->buffer_end - g_ws_backlog_database->buffer_start) % g_ws_backlog_database->buffer_size;
+ return mod < 0 ? mod + g_ws_backlog_database->buffer_size : mod;
}
diff --git a/stm32f091/backlog.h b/stm32f091/backlog.h
index 465b3c0..c8ea019 100644
--- a/stm32f091/backlog.h
+++ b/stm32f091/backlog.h
@@ -30,7 +30,7 @@ typedef struct {
#pragma pack(pop)
/** @brief global record backlog database pointer */
-extern ws_s_backlog_database* WS_G_BACKLOG_DATABASE;
+extern ws_s_backlog_database* g_ws_backlog_database;
/**
* @brief add record to database
@@ -55,3 +55,5 @@ ws_s_backlog_record* ws_backlog_get_record(uint16_t record_index);
/** @brief get pointer to last record with offset `record_offset` from the database */
ws_s_backlog_record* ws_backlog_get_last_record(uint16_t record_offset);
+/** @brief return amount of valid records in database */
+uint16_t ws_backlog_get_record_count();