How to: CBOR
CHESTER with Cloud v2 uses CBOR to encode and decode transmitted data. Thanks to CBOR, you can describe how the transmitted data will look like using the YAML file, then in your C code, you use the keys from this YAML file.
In the initial session messages, Cloud sends codec hash in the session down message. CHESTER compares the hash with its own codec hash and if needed, it uploads decoder with decoder up message and optionally uploads encoder in encoder up message.
To use CBOR with CHESTER you need to:
- Create a
codec\cbor-decoder.yaml
file in your application folder that describes the JSON attributes. - Optionally create a
codec\cbor-encoder.yaml
file for downlink commands (see CHESTER Control code) - Header file
src/app_codec.h
is automatically generated whenwest build
is called. - Use these definitions in
app_cbor.c
and add the needed data.
YAML file have a header, then in schema
you define a strictly hiearchical structure.
In src/app_codec.h
there are generated #defines with names like CODEC_KEY_E_
where E
stands for Encoder (viewed from CHESTER perspective).
In you inherit items to deeper structure, then in header file you will see __
double underscore.
For example CODEC_KEY_E_NETWORK__MESSAGE__VERSION
for YAML example below.
version: 2
type: decoder
name: com.hardwario.chester.app.clime
schema:
- message:
- version:
- sequence:
- timestamp:
...
You can see more practical examples in CHESTER SDK in the catalog applications chester/applications/*
folder.
Naming cbor-decoder.yaml
and cbor-encoder.yaml
is took from the Cloud perspective. So CHESTER is encoding data using decoder file because cloud is using this YAML file for decoding.
YAML
In the YAML you define the name of the keys that will later be used in decoded JSON. However, the YAML file can define other things:
- Modificators
- Enumerators
- Time series data, periodical (TSP) or with time offset (TSO)
Modificators
Modificators are:
$add
$sub
$mul
$div
$fpp
- floating point decimal places in JSON$key
- rename the JSON key
The example below creates a key with name temperature
. In CHESTER you need to multiply value by 100, then in HARDWARIO Cloud it is automatically divided by 100, and in the final JSON, the number has two decimal places.
- temperature:
- $div: 100
- $fpp: 2
The CHESTER C code will look like this:
zcbor_uint32_put(zs, CODEC_KEY_E_TEMPERATURE);
zcbor_int32_put(zs, g_app_data.therm_temperature * 100.f);
Output JSON will be:
"temperature": 21.75
Enumerators
Define string values and send them efficiently as an integer.
- backup_state:
- $enum:
- inactive
- active
The CHESTER C code will look like this:
zcbor_uint32_put(zs, CODEC_KEY_E_BACKUP_STATE);
zcbor_uint32_put(zs, g_app_data.backup.line_present ? 1 : 0);
Output JSON will be:
"backup_state": "active"
Time Series Period
Time Series Period (TSP) efficiently encodes value or multiple values with the timestamps. In the CHESTER, you only send reference timestamp, period and values. The decoder automatically adds an absolute timestamp to each value.
- measurements_val:
- $tsp:
- avg:
- mdn:
The CHESTER C code will look like this:
zcbor_uint32_put(zs, CODEC_KEY_E_MEASUREMENTS_VAL);
{
zcbor_list_start_encode(zs, ZCBOR_VALUE_IS_INDEFINITE_LENGTH);
zcbor_uint64_put(zs, g_app_data.counter.timestamp); // unix timestamp 1679321760
zcbor_uint32_put(zs, g_app_config.counter_interval_aggreg); // 30 seconds
for (int i = 0; i < g_app_data.counter.measurement_count; i++) {
zcbor_uint64_put(zs, g_app_data.counter.measurements[i].avg);
zcbor_uint64_put(zs, g_app_data.counter.measurements[i].mdn);
}
zcbor_list_end_encode(zs, ZCBOR_VALUE_IS_INDEFINITE_LENGTH);
}
Output JSON will be:
measurements_val:
[
{
"timestamp": 1679321760,
"avg": 123,
"mdn": 456
},
{
"timestamp": 1679321790,
"avg": 123,
"mdn": 456
},
...
]
Notice that in the JSON the timestamp for each sample is absolute. It is computed from the reference timestamp 1679321760 of the first sample; then next sample has a timestamp bigger by 30 seconds 1679321790.
Time Series Offset
Time Series Offset (TSO) is similar to the previous Time Series Period (TSP). However, the time between samples is not periodic, and every sample has its own offset relative to the previous one.
This code also shows how you can combine modificators inside the TSO.
- trigger_events:
- $key: "events"
- $tso:
- type:
- $enum:
- deactivated
- activated
The CHESTER C code will look like this:
int64_t timestamp_abs = g_app_data.trigger.events[0].timestamp;
zcbor_uint32_put(zs, CODEC_KEY_E_TRIGGER_EVENTS);
{
zcbor_list_start_encode(zs, ZCBOR_VALUE_IS_INDEFINITE_LENGTH);
/* TSO absolute timestamp */
zcbor_int64_put(zs, timestamp_abs);
for (int i = 0; i < g_app_data.trigger.event_count; i++) {
/* TSO offset timestamp */
zcbor_int64_put(zs, g_app_data.trigger.events[i].timestamp - timestamp_abs);
zcbor_uint32_put(zs, g_app_data.trigger.events[i].is_active ? 1 : 0);
}
zcbor_list_end_encode(zs, ZCBOR_VALUE_IS_INDEFINITE_LENGTH);
}
Output JSON will be:
events:
[
{
"timestamp": 1679321760,
"event": "activated"
},
{
"timestamp": 1679321765,
"event": "deactivated"
},
{
"timestamp": 1679321780,
"event": "activated"
},
...
]