diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | shared/backlog.c | 47 | ||||
-rw-r--r-- | shared/backlog.h | 59 | ||||
-rw-r--r-- | shared/bin.c | 11 | ||||
-rw-r--r-- | shared/bin.h | 13 | ||||
-rw-r--r-- | shared/makefile | 24 | ||||
-rw-r--r-- | shared/protocol.c | 136 | ||||
-rw-r--r-- | shared/protocol.h | 150 | ||||
-rw-r--r-- | shared/protocol.md | 54 | ||||
-rw-r--r-- | shared/test.c | 82 | ||||
-rw-r--r-- | shared/testcmd | 1 | ||||
-rw-r--r-- | shared/util.h | 4 | ||||
-rw-r--r-- | stm32f091/backlog.c | 41 | ||||
-rw-r--r-- | stm32f091/backlog.h | 4 |
14 files changed, 612 insertions, 15 deletions
@@ -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(); |