Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ build_flags =
-D TS_64BIT_TYPES_SUPPORT=1
-D TS_DECFRAC_TYPE_SUPPORT=1
-D TS_BYTE_STRING_TYPE_SUPPORT=1
-D TS_NESTED_JSON=1

# include src directory (otherwise unit-tests will only include lib directory)
test_build_project_src = true
Expand Down
8 changes: 7 additions & 1 deletion src/thingset.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,17 @@ int ts_process(struct ts_context *ts, const uint8_t *request, size_t request_len
}
}

void ts_set_authentication(struct ts_context *ts, uint16_t flags)
void ts_set_authentication(struct ts_context *ts, uint8_t flags)
{
ts->_auth_flags = flags;
}

void ts_set_update_callback(struct ts_context *ts, const uint16_t subsets, void (*update_cb)(void))
{
ts->_update_subsets = subsets;
ts->update_cb = update_cb;
}

struct ts_data_object *ts_get_object_by_name(struct ts_context *ts, const char *name,
size_t len, int32_t parent)
{
Expand Down
69 changes: 50 additions & 19 deletions src/thingset.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,15 +341,15 @@ static inline void *ts_array_to_void(struct ts_array_info *ptr) { return (void *
#define TS_ROLE_EXP (1U << 1) // expert user
#define TS_ROLE_MKR (1U << 2) // maker

#define TS_READ_MASK 0x00FF // read flags stored in 4 least-significant bits
#define TS_WRITE_MASK 0xFF00 // write flags stored in 4 most-significant bits
#define TS_READ_MASK 0x0F // read flags stored in 4 least-significant bits
#define TS_WRITE_MASK 0xF0 // write flags stored in 4 most-significant bits

#define TS_USR_MASK (TS_ROLE_USR << 8 | TS_ROLE_USR)
#define TS_EXP_MASK (TS_ROLE_EXP << 8 | TS_ROLE_EXP)
#define TS_MKR_MASK (TS_ROLE_MKR << 8 | TS_ROLE_MKR)
#define TS_USR_MASK (TS_ROLE_USR << 4 | TS_ROLE_USR)
#define TS_EXP_MASK (TS_ROLE_EXP << 4 | TS_ROLE_EXP)
#define TS_MKR_MASK (TS_ROLE_MKR << 4 | TS_ROLE_MKR)

#define TS_READ(roles) ((roles) & TS_READ_MASK)
#define TS_WRITE(roles) (((roles) << 8) & TS_WRITE_MASK)
#define TS_WRITE(roles) (((roles) << 4) & TS_WRITE_MASK)
#define TS_READ_WRITE(roles) (TS_READ(roles) | TS_WRITE(roles))

#define TS_USR_R TS_READ(TS_ROLE_USR)
Expand Down Expand Up @@ -401,24 +401,30 @@ struct ts_data_object {
/**
* One of TS_TYPE_INT32, _FLOAT, ...
*/
const uint8_t type;
const uint32_t type : 4;

/**
* Exponent (10^exponent = factor to convert to SI unit) for decimal fraction type,
* decimal digits to use for printing of floats in JSON strings or
* length of string buffer for string type
* Variable storing different detail information depending on th data type
*
* - FLOAT32: Decimal digits (precision) to use for printing in JSON strings.
*
* - DECFRAC: Exponent (10^exponent = factor to convert to internal unit). Example: If
* a voltage measurement is internally stored as an integer in mV, use exponent -3 to
* convert to the SI base unit V as exposed via ThingSet.
*
* - STRING or BYTES: Size of the internal buffer in bytes.
*/
const int16_t detail;
const int32_t detail : 12;

/**
* Flags to define read/write access
*/
const uint16_t access;
const uint32_t access : 8;

/**
* Flags to assign data item to different data item subsets (e.g. for publication messages)
*/
uint16_t subsets;
uint32_t subsets : 8;

};

Expand Down Expand Up @@ -479,7 +485,18 @@ struct ts_context {
/**
* Stores current authentication status (authentication as "normal" user as default)
*/
uint16_t _auth_flags;
uint8_t _auth_flags;

/**
* Stores current authentication status (authentication as "normal" user as default)
*/
uint8_t _update_subsets;

/**
* Callback to be called from patch function if a value belonging to _update_subsets
* was changed
*/
void (*update_cb)(void);
};

/**
Expand Down Expand Up @@ -529,7 +546,16 @@ void ts_dump_json(struct ts_context *ts, ts_object_id_t obj_id, int level);
* @param ts Pointer to ThingSet context.
* @param flags Flags to define authentication level (1 = access allowed)
*/
void ts_set_authentication(struct ts_context *ts, uint16_t flags);
void ts_set_authentication(struct ts_context *ts, uint8_t flags);

/**
* Configures a callback for notification if data belonging to specified subset(s) was updated.
*
* @param ts Pointer to ThingSet context.
* @param subsets Flags to select which subset(s) of data items should be considered
* @param update_cb Callback to be called after an update.
*/
void ts_set_update_callback(struct ts_context *ts, const uint16_t subsets, void (*update_cb)(void));

/**
* Retrieve data in JSON format for given subset(s).
Expand Down Expand Up @@ -676,7 +702,7 @@ int ts_bin_pub_can(struct ts_context *ts, int *start_pos, uint16_t subset, uint8
*
* @returns ThingSet status code
*/
int ts_bin_import(struct ts_context *ts, uint8_t *data, size_t len, uint16_t auth_flags,
int ts_bin_import(struct ts_context *ts, uint8_t *data, size_t len, uint8_t auth_flags,
uint16_t subsets);

/**
Expand Down Expand Up @@ -760,11 +786,16 @@ class ThingSet
ts_dump_json(&ts, obj_id, level);
};

inline void set_authentication(uint16_t flags)
inline void set_authentication(uint8_t flags)
{
ts_set_authentication(&ts, flags);
};

inline void set_update_callback(const uint16_t subsets, void (*update_cb)(void))
{
ts_set_update_callback(&ts, subsets, update_cb);
};

inline int txt_export(char *buf, size_t size, const uint16_t subsets)
{
return ts_txt_export(&ts, buf, size, subsets);
Expand All @@ -790,7 +821,7 @@ class ThingSet
return ts_bin_export(&ts, buf, size, subsets);
};

inline int bin_import(uint8_t *buf, size_t size, uint16_t auth_flags, const uint16_t subsets)
inline int bin_import(uint8_t *buf, size_t size, uint8_t auth_flags, const uint16_t subsets)
{
return ts_bin_import(&ts, buf, size, auth_flags, subsets);
};
Expand Down Expand Up @@ -880,7 +911,7 @@ class ThingSet
*
* @returns ThingSet status code
*/
inline int bin_sub(uint8_t *cbor_data, size_t len, uint16_t auth_flags, uint16_t subsets)
inline int bin_sub(uint8_t *cbor_data, size_t len, uint8_t auth_flags, uint16_t subsets)
__attribute__((deprecated))
{
return ts_bin_import(&ts, cbor_data + 1, len - 1, auth_flags, subsets);
Expand Down
11 changes: 9 additions & 2 deletions src/thingset_bin.c
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ int ts_bin_fetch(struct ts_context *ts, const struct ts_data_object *parent, uin
}
}

int ts_bin_import(struct ts_context *ts, uint8_t *data, size_t len, uint16_t auth_flags,
int ts_bin_import(struct ts_context *ts, uint8_t *data, size_t len, uint8_t auth_flags,
uint16_t subsets)
{
uint8_t resp_tmp[1] = {}; // only one character as response expected
Expand All @@ -358,10 +358,11 @@ int ts_bin_import(struct ts_context *ts, uint8_t *data, size_t len, uint16_t aut
}

int ts_bin_patch(struct ts_context *ts, const struct ts_data_object *parent,
unsigned int pos_payload, uint16_t auth_flags, uint16_t subsets)
unsigned int pos_payload, uint8_t auth_flags, uint16_t subsets)
{
unsigned int pos_req = pos_payload;
uint16_t num_elements, element = 0;
bool updated = false;

if ((ts->req[pos_req] & CBOR_TYPE_MASK) != CBOR_MAP) {
return ts_bin_response(ts, TS_STATUS_BAD_REQUEST);
Expand Down Expand Up @@ -403,6 +404,9 @@ int ts_bin_patch(struct ts_context *ts, const struct ts_data_object *parent,
else {
// actually deserialize the data and update object
num_bytes = cbor_deserialize_data_obj(&ts->req[pos_req], object);
if (ts->_update_subsets & object->subsets) {
updated = true;
}
}
}
else {
Expand All @@ -425,6 +429,9 @@ int ts_bin_patch(struct ts_context *ts, const struct ts_data_object *parent,
}

if (element == num_elements) {
if (updated && ts->update_cb != NULL) {
ts->update_cb();
}
return ts_bin_response(ts, TS_STATUS_CHANGED);
} else {
return ts_bin_response(ts, TS_STATUS_BAD_REQUEST);
Expand Down
2 changes: 1 addition & 1 deletion src/thingset_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ int ts_txt_patch(struct ts_context *ts, const struct ts_data_object *parent);
* @param subsets Bitset to specifiy data item subsets to be considered, 0 to ignore
*/
int ts_bin_patch(struct ts_context *ts, const struct ts_data_object *parent,
unsigned int pos_payload, uint16_t auth_flags, uint16_t subsets);
unsigned int pos_payload, uint8_t auth_flags, uint16_t subsets);

/**
* POST request to append data.
Expand Down
84 changes: 81 additions & 3 deletions src/thingset_txt.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,21 @@ int ts_json_serialize_value(struct ts_context *ts, char *buf, size_t size,
pos = snprintf(&buf[pos], size - pos, "[");
for (unsigned int i = 0; i < ts->num_objects; i++) {
if (ts->data_objects[i].subsets & (uint16_t)object->detail) {
#if TS_NESTED_JSON
if (ts->data_objects[i].parent == 0) {
pos += snprintf(&buf[pos], size - pos, "\"%s\",", ts->data_objects[i].name);
}
else {
struct ts_data_object *parent_obj =
ts_get_object_by_id(ts, ts->data_objects[i].parent);
if (parent_obj != NULL) {
pos += snprintf(&buf[pos], size - pos, "\"%s/%s\",",
parent_obj->name, ts->data_objects[i].name);
}
}
#else
pos += snprintf(&buf[pos], size - pos, "\"%s\",", ts->data_objects[i].name);
#endif
}
}
if (pos > 1) {
Expand Down Expand Up @@ -502,6 +516,7 @@ int ts_json_deserialize_value(struct ts_context *ts, char *buf, size_t len, jsmn
int ts_txt_patch(struct ts_context *ts, const struct ts_data_object *parent)
{
int tok = 0; // current token
bool updated = false;

// buffer for data object value (largest negative 64bit integer has 20 digits)
char value_buf[21];
Expand Down Expand Up @@ -598,6 +613,14 @@ int ts_txt_patch(struct ts_context *ts, const struct ts_data_object *parent)

tok += ts_json_deserialize_value(ts, &ts->json_str[ts->tokens[tok].start], value_len,
ts->tokens[tok].type, object);

if (ts->_update_subsets & object->subsets) {
updated = true;
}
}

if (updated && ts->update_cb != NULL) {
ts->update_cb();
}

return ts_txt_response(ts, TS_STATUS_CHANGED);
Expand Down Expand Up @@ -683,10 +706,15 @@ int ts_txt_create(struct ts_context *ts, const struct ts_data_object *object)
}
else if (object->type == TS_T_SUBSET) {
if (ts->tokens[0].type == JSMN_STRING) {
struct ts_data_object *del_object = ts_get_object_by_name(ts, ts->json_str +
#if TS_NESTED_JSON
struct ts_data_object *add_object = ts_get_object_by_path(ts, ts->json_str +
ts->tokens[0].start, ts->tokens[0].end - ts->tokens[0].start);
#else
struct ts_data_object *add_object = ts_get_object_by_name(ts, ts->json_str +
ts->tokens[0].start, ts->tokens[0].end - ts->tokens[0].start, -1);
if (del_object != NULL) {
del_object->subsets |= (uint16_t)object->detail;
#endif
if (add_object != NULL) {
add_object->subsets |= (uint16_t)object->detail;
return ts_txt_response(ts, TS_STATUS_CREATED);
}
return ts_txt_response(ts, TS_STATUS_NOT_FOUND);
Expand All @@ -708,8 +736,13 @@ int ts_txt_delete(struct ts_context *ts, const struct ts_data_object *object)
}
else if (object->type == TS_T_SUBSET) {
if (ts->tokens[0].type == JSMN_STRING) {
#if TS_NESTED_JSON
struct ts_data_object *del_object = ts_get_object_by_path(ts, ts->json_str +
ts->tokens[0].start, ts->tokens[0].end - ts->tokens[0].start);
#else
struct ts_data_object *del_object = ts_get_object_by_name(ts, ts->json_str +
ts->tokens[0].start, ts->tokens[0].end - ts->tokens[0].start, -1);
#endif
if (del_object != NULL) {
del_object->subsets &= ~((uint16_t)object->detail);
return ts_txt_response(ts, TS_STATUS_DELETED);
Expand Down Expand Up @@ -769,6 +802,49 @@ int ts_txt_exec(struct ts_context *ts, const struct ts_data_object *object)
return ts_txt_response(ts, TS_STATUS_VALID);
}

#if TS_NESTED_JSON

/* currently only supporting nesting of depth 1 */
int ts_txt_export(struct ts_context *ts, char *buf, size_t buf_size, uint16_t subsets)
{
unsigned int len = 1;
buf[0] = '{';
uint16_t prev_parent = 0;
unsigned int depth = 0;

for (unsigned int i = 0; i < ts->num_objects; i++) {
if (ts->data_objects[i].subsets & subsets) {
const uint16_t parent_id = ts->data_objects[i].parent;
if (prev_parent != parent_id) {
if (prev_parent != 0) {
// close object of previous parent
buf[len-1] = '}'; // overwrite comma
buf[len++] = ',';
}
struct ts_data_object *parent = ts_get_object_by_id(ts, parent_id);
len += snprintf(&buf[len], buf_size - len, "\"%s\":{", parent->name);
prev_parent = parent_id;
depth = 1;
}
len += ts_json_serialize_name_value(ts, &buf[len], buf_size - len,
&ts->data_objects[i]);
}
if (len >= buf_size - 1 - depth) {
return 0;
}
}

buf[len-1] = '}'; // overwrite comma

if (depth == 1) {
buf[len++] = '}';
}

return len;
}

#else

int ts_txt_export(struct ts_context *ts, char *buf, size_t buf_size, uint16_t subsets)
{
unsigned int len = 1;
Expand All @@ -789,6 +865,8 @@ int ts_txt_export(struct ts_context *ts, char *buf, size_t buf_size, uint16_t su
return len;
}

#endif /* TS_NESTED_JSON */

int ts_txt_statement(struct ts_context *ts, char *buf, size_t buf_size,
struct ts_data_object *object)
{
Expand Down
20 changes: 20 additions & 0 deletions src/ts_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,24 @@
#define TS_BYTE_STRING_TYPE_SUPPORT CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT
#endif

/*
* The ThingSet specification v0.5 introduces a different data layout compared to previous
* versions where the data is grouped by entities of the device (like battery, actuator)
* instead of the data type (e.g. measurement, configuration). The data type is described by
* a single character prefix in the item name.
* The new grouping allows to have same item names in different groups, so for unambigous
* description of the data the nested structure has to be maintained in statements.
*
* With nested JSON enabled, requesting names for IDs will also return the entire path (as
* a JSON pointer) instead of just the data item name.
*
* This option is introduced to maintain compatibility with legacy firmware and will be
* enabled by default in the future.
*/
#if !defined(TS_NESTED_JSON) && !defined(CONFIG_THINGSET_NESTED_JSON)
#define TS_NESTED_JSON 0 // default: no nested JSON for legacy code
#elif !defined(TS_NESTED_JSON)
#define TS_NESTED_JSON CONFIG_THINGSET_NESTED_JSON
#endif

#endif /* TS_CONFIG_H_ */
Loading