Skip to content

Commit

Permalink
Merge pull request #11 from LibreSolar/updates-spec-v0.4
Browse files Browse the repository at this point in the history
Updates for Specification v0.4
  • Loading branch information
martinjaeger authored Aug 3, 2021
2 parents 0e695f7 + 4896214 commit 9dad7cf
Show file tree
Hide file tree
Showing 18 changed files with 1,331 additions and 804 deletions.
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,20 @@ The implementation is tested vs. newlib-nano and the default newlib provided by

An example program is implemented in `src/main.cpp`, which provides a shell to access the data via ThingSet protocol if run on a computer.

Most important is the setup of the data node tree in `test/test_data.h`.
The sample can be compiled via PlatformIO:

Assuming the data is stored in a static array `data_nodes` as in the example, a ThingSet object is created by:
pio run -e native-std

Afterwards, run the binary with:

.pio/build/native-std/program

Most important is the setup of the data object tree in `test/test_data.c`.

Assuming the data is stored in a static array `data_objects` as in the example, a ThingSet object is created by:

```C++
ThingSet ts(data_nodes, sizeof(data_nodes)/sizeof(DataNode));
ThingSet ts(data_objects, sizeof(data_objects)/sizeof(ThingSetDataObject));
```
Afterwards, it can be used with any communication interface using the `process` function:
Expand Down Expand Up @@ -57,10 +65,10 @@ The following ThingSet functions are fully implemented:
- PATCH request (first byte '=')
- POST request (first byte '!' or '+')
- DELETE request (first byte '-')
- Execution of functions via callbacks to certain paths or via executable nodes
- Authentication via callback to 'auth' node
- Execution of functions via callbacks to certain paths or via executable objects
- Authentication via callback to 'auth' object
- Sending of publication messages (# {...})
- Setup of publication channels (enable/disable, configure data nodes to be published, change interval)
- Setup of publication channels (enable/disable, configure data objects to be published, change interval)

In order to reduce code size, verbose status messages can be turned off using the TS_VERBOSE_STATUS_MESSAGES = 0 in ts_config.h.

Expand Down
3 changes: 3 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ build_flags =
-D NATIVE_BUILD
-pthread
-Wall
-Wno-deprecated-declarations

# include src directory (otherwise unit-tests will only include lib directory)
test_build_project_src = true
Expand All @@ -33,6 +34,7 @@ upload_protocol = stlink

# include src directory (otherwise unit-tests will only include lib directory)
test_build_project_src = true
lib_ignore = linenoise

[env:device-newlib-nano]
framework = mbed
Expand All @@ -51,3 +53,4 @@ extra_scripts = linker_flags_newlib-nano.py

# include src directory (otherwise unit-tests will only include lib directory)
test_build_project_src = true
lib_ignore = linenoise
49 changes: 26 additions & 23 deletions src/cbor.c
Original file line number Diff line number Diff line change
Expand Up @@ -451,40 +451,43 @@ int cbor_deserialize_bool(const uint8_t *data, bool *value)
}

int cbor_deserialize_string(const uint8_t *data, char *str, uint16_t buf_size)
{
char *str_start;
uint16_t str_len;

int ret = cbor_deserialize_string_zero_copy(data, &str_start, &str_len);

if (ret > 0 && str_len < buf_size) {
strncpy(str, str_start, str_len);
str[str_len] = '\0';
return ret;
}
return 0;
}

int cbor_deserialize_string_zero_copy(const uint8_t *data, char **str_start, uint16_t *str_len)
{
uint8_t type = data[0] & CBOR_TYPE_MASK;
uint8_t info = data[0] & CBOR_INFO_MASK;
uint16_t len;

//printf("deserialize string: \"%s\", len = %d, max_len = %d\n", (char*)&data[1], len, buf_size);

if (!str || type != CBOR_TEXT)
if (!data || !str_start || !str_len || type != CBOR_TEXT) {
return 0;
}

if (info <= CBOR_NUM_MAX) {
len = info;
if (len < buf_size) {
strncpy(str, (const char*)&data[1], len);
str[len] = '\0';
//printf("deserialize string: \"%s\", len = %d, max_len = %d\n", (char*)&data[1], len, buf_size);
return len + 1;
}
*str_len = info;
*str_start = (char *)&data[1];
return *str_len + 1;
}
else if (info == CBOR_UINT8_FOLLOWS) {
len = data[1];
if (len < buf_size) {
strncpy(str, (const char*)&data[2], len);
str[len] = '\0';
return len + 2;
}
*str_len = data[1];
*str_start = (char *)&data[2];
return *str_len + 2;
}
else if (info == CBOR_UINT16_FOLLOWS) {
len = data[1] << 8 | data[2];
if (len < buf_size) {
strncpy(str, (const char*)&data[3], len);
str[len] = '\0';
return len + 3;
}
*str_len = data[1] << 8 | data[2];
*str_start = (char*)&data[3];
return *str_len + 3;
}
return 0; // longer string not supported
}
Expand Down
11 changes: 11 additions & 0 deletions src/cbor.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,17 @@ int cbor_deserialize_bool(const uint8_t *data, bool *value);
*/
int cbor_deserialize_string(const uint8_t *data, char *str, uint16_t buf_size);

/**
* Deserialize string with zero-copy
*
* @param data Buffer containing CBOR data with matching type
* @param str_start Pointer to store start of string
* @param str_len Pointer to store length of string in the buffer EXCLUDING null-termination
*
* @returns Number of bytes read from data buffer or 0 in case of error
*/
int cbor_deserialize_string_zero_copy(const uint8_t *data, char **str_start, uint16_t *str_len);

/**
* Deserialize bytes
*
Expand Down
12 changes: 6 additions & 6 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include "../test/test_data.c"


ThingSet thing(data_nodes, sizeof(data_nodes)/sizeof(DataNode));
ThingSet thing(data_objects, sizeof(data_objects)/sizeof(ThingSetDataObject));

//
// Setup functions used in test data included.
Expand Down Expand Up @@ -64,23 +64,23 @@ void pub_thread()
char pub_msg[1000];

while (1) {
if (pub_serial_enable) {
thing.txt_pub(pub_msg, sizeof(pub_msg), PUB_SER);
if (pub_report_enable) {
thing.txt_statement(pub_msg, sizeof(pub_msg), "report");
printf("%s\r\n", pub_msg);
}
std::this_thread::sleep_for(std::chrono::milliseconds(pub_serial_interval));
std::this_thread::sleep_for(std::chrono::milliseconds(pub_report_interval));
}
}

int main()
{
uint8_t resp_buf[1000];

printf("\n----------------- Data node tree ---------------------\n");
printf("\n----------------- Data object tree ---------------------\n\n");

thing.dump_json();

printf("\n----------------- ThingSet shell ---------------------\n");
printf("\n----------------- ThingSet shell ---------------------\n\n");

linenoiseHistoryLoad(".thingset-shell-history.txt"); /* Load the history at startup */

Expand Down
77 changes: 22 additions & 55 deletions src/thingset.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,56 +13,23 @@
#include <stdio.h>


static void _check_id_duplicates(const struct ts_data_node *data, size_t num)
static void _check_id_duplicates(const struct ts_data_object *data, size_t num)
{
for (unsigned int i = 0; i < num; i++) {
for (unsigned int j = i + 1; j < num; j++) {
if (data[i].id == data[j].id) {
LOG_ERR("ThingSet error: Duplicate data node ID 0x%X.\n", data[i].id);
LOG_ERR("ThingSet error: Duplicate data object ID 0x%X.\n", data[i].id);
}
}
}
}

/*
* Counts the number of elements in an an array of node IDs by looking for the first non-zero
* elements starting from the back.
*
* Currently only supporting uint16_t (node_id_t) arrays as we need the size of each element
* to iterate through the array.
*/
static void _count_array_elements(const struct ts_data_node *data, size_t num)
{
for (unsigned int i = 0; i < num; i++) {
if (data[i].type == TS_T_ARRAY) {
struct ts_array_info *arr = (struct ts_array_info *)data[i].data;
if (arr->num_elements == TS_AUTODETECT_ARRLEN) {
arr->num_elements = 0; // set to safe default
if (arr->type == TS_T_NODE_ID) {
for (int elem = arr->max_elements - 1; elem >= 0; elem--) {
if (((ts_node_id_t *)arr->ptr)[elem] != 0) {
arr->num_elements = elem + 1;
LOG_DBG("%s num elements: %d\n", data[i].name, arr->num_elements);
break;
}
}
}
else {
LOG_ERR("Autodetecting array length of node 0x%X not possible.\n", data[i].id);
}
}
}
}
}

int ts_init(struct ts_context *ts, struct ts_data_node *data, size_t num)
int ts_init(struct ts_context *ts, struct ts_data_object *data, size_t num)
{
_check_id_duplicates(data, num);

_count_array_elements(data, num);

ts->data_nodes = data;
ts->num_nodes = num;
ts->data_objects = data;
ts->num_objects = num;
ts->_auth_flags = TS_USR_MASK;

return 0;
Expand Down Expand Up @@ -99,34 +66,34 @@ void ts_set_authentication(struct ts_context *ts, uint16_t flags)
ts->_auth_flags = flags;
}

struct ts_data_node *ts_get_node_by_name(struct ts_context *ts, const char *name, size_t len, int32_t parent)
struct ts_data_object *ts_get_object_by_name(struct ts_context *ts, const char *name, size_t len, int32_t parent)
{
for (unsigned int i = 0; i < ts->num_nodes; i++) {
if (parent != -1 && ts->data_nodes[i].parent != parent) {
for (unsigned int i = 0; i < ts->num_objects; i++) {
if (parent != -1 && ts->data_objects[i].parent != parent) {
continue;
}
else if (strncmp(ts->data_nodes[i].name, name, len) == 0
&& strlen(ts->data_nodes[i].name) == len) // otherwise e.g. foo and fooBar would be recognized as equal
else if (strncmp(ts->data_objects[i].name, name, len) == 0
&& strlen(ts->data_objects[i].name) == len) // otherwise e.g. foo and fooBar would be recognized as equal
{
return &(ts->data_nodes[i]);
return &(ts->data_objects[i]);
}
}
return NULL;
}

struct ts_data_node *ts_get_node_by_id(struct ts_context *ts, ts_node_id_t id)
struct ts_data_object *ts_get_object_by_id(struct ts_context *ts, ts_object_id_t id)
{
for (unsigned int i = 0; i < ts->num_nodes; i++) {
if (ts->data_nodes[i].id == id) {
return &(ts->data_nodes[i]);
for (unsigned int i = 0; i < ts->num_objects; i++) {
if (ts->data_objects[i].id == id) {
return &(ts->data_objects[i]);
}
}
return NULL;
}

struct ts_data_node *ts_get_node_by_path(struct ts_context *ts, const char *path, size_t len)
struct ts_data_object *ts_get_object_by_path(struct ts_context *ts, const char *path, size_t len)
{
struct ts_data_node *node;
struct ts_data_object *object;
const char *start = path;
const char *end;
uint16_t parent = 0;
Expand All @@ -136,17 +103,17 @@ struct ts_data_node *ts_get_node_by_path(struct ts_context *ts, const char *path
end = strchr(start, '/');
if (end == NULL || end >= path + len) {
// we are at the end of the path
return ts_get_node_by_name(ts, start, path + len - start, parent);
return ts_get_object_by_name(ts, start, path + len - start, parent);
}
else if (end == path + len - 1) {
// path ends with slash
return ts_get_node_by_name(ts, start, end - start, parent);
return ts_get_object_by_name(ts, start, end - start, parent);
}
else {
// go further down the path
node = ts_get_node_by_name(ts, start, end - start, parent);
if (node) {
parent = node->id;
object = ts_get_object_by_name(ts, start, end - start, parent);
if (object) {
parent = object->id;
start = end + 1;
}
else {
Expand Down
Loading

0 comments on commit 9dad7cf

Please sign in to comment.