Skip to content

Commit c25f8d6

Browse files
committed
modules: Add piping for decoding incoming CBOR shadow data
Add piping for decoding incoming CBOR shadow data
1 parent b1c169e commit c25f8d6

14 files changed

+159
-42
lines changed

app/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_subdirectory(src/modules/cloud)
2121
add_subdirectory(src/modules/location)
2222
add_subdirectory(src/modules/fota)
2323
add_subdirectory(src/modules/button)
24+
add_subdirectory(src/cbor)
2425

2526
# Optional modules
2627
add_subdirectory_ifdef(CONFIG_APP_POWER src/modules/power)

app/prj.conf

+3
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,6 @@ CONFIG_TASK_WDT_MIN_TIMEOUT=10000
210210
# Device power management
211211
CONFIG_PM_DEVICE=y
212212
CONFIG_PM_DEVICE_SHELL=y
213+
214+
# JSON library
215+
CONFIG_JSON_LIBRARY=y

app/src/Kconfig.main

-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ config APP_MODULE_THREAD_STACK_SIZE
1616
int "Thread stack size"
1717
default 3200
1818

19-
config APP_MODULE_RECV_BUFFER_SIZE
20-
int "Receive buffer size"
21-
default 1024
22-
2319
config APP_REQUEST_NETWORK_QUALITY
2420
bool "Request network quality"
2521
help

app/src/cbor/CMakeLists.txt

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cbor_helper.c)
8+
target_include_directories(app PRIVATE .)

app/src/cbor/cbor_helper.c

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include <zephyr/logging/log.h>
2+
#include <zcbor_decode.h>
3+
4+
LOG_MODULE_DECLARE(main, CONFIG_APP_LOG_LEVEL);
5+
6+
int get_update_interval_from_cbor_response(const uint8_t *cbor,
7+
size_t len,
8+
uint64_t *interval_sec)
9+
{
10+
interval_sec = 60;
11+
return 0;
12+
}

app/src/cbor/cbor_helper.h

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <zephyr/types.h>
2+
3+
int get_update_interval_from_cbor_response(const uint8_t *cbor,
4+
size_t len,
5+
uint64_t *interval_sec);

app/src/cbor/device_shadow.cddl

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
; Define the basic structure of the JSON object
2+
3+
; The 'any' type is used in this schema to allow forward compatibility.
4+
; They are inserted into maps that may contain additional fields in the future.
5+
; This prevents cbor decoding from failing in the field when the cloud side has new fields.
6+
app-object = {
7+
? "nrfcloud_mqtt_topic_prefix": tstr,
8+
? "pairing": pairing-type,
9+
? "lwm2m": lwm2m-map,
10+
* tstr => any
11+
}
12+
13+
; Define the pairing object with nested topics
14+
pairing-type = {
15+
"state": tstr,
16+
"topics": {
17+
"d2c": tstr,
18+
"c2d": tstr,
19+
* tstr => any
20+
},
21+
* any => any
22+
}
23+
24+
lwm2m-map = {
25+
? "14240:1.0": led,
26+
? "14301:1.0": config,
27+
* tstr => any
28+
}
29+
30+
led = {
31+
"0": led_inner_object,
32+
* tstr => any
33+
}
34+
35+
led_inner_object = {
36+
? "0": int .size 4,
37+
? "1": int .size 4,
38+
? "2": int .size 4,
39+
"99": int .size 8,
40+
* tstr => any
41+
}
42+
43+
config = {
44+
"0": config_inner_object,
45+
* tstr => any
46+
}
47+
48+
config_inner_object = {
49+
? "0": int .size 8,
50+
? "1": bool,
51+
"99": int .size 8,
52+
* tstr => any
53+
}

app/src/main.c

+16-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "cloud_module.h"
1717
#include "fota.h"
1818
#include "location.h"
19+
#include "cbor_helper.h"
1920

2021
#if defined(CONFIG_APP_LED)
2122
#include "led.h"
@@ -35,15 +36,14 @@
3536
/* Register log module */
3637
LOG_MODULE_REGISTER(main, CONFIG_APP_LOG_LEVEL);
3738

38-
#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_shadow_response), \
39-
MAX(sizeof(struct cloud_payload), \
39+
#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_msg), \
4040
/* Button channel payload size */ \
4141
MAX(sizeof(uint8_t), \
4242
/* Timer channel payload size */ \
4343
MAX(sizeof(int), \
4444
MAX(sizeof(enum fota_msg_type), \
4545
MAX(sizeof(enum location_msg_type), \
46-
MAX(sizeof(struct network_msg), POWER_MSG_SIZE))))))))
46+
MAX(sizeof(struct network_msg), POWER_MSG_SIZE)))))))
4747

4848
/* Register subscriber */
4949
ZBUS_MSG_SUBSCRIBER_DEFINE(main_subscriber);
@@ -232,6 +232,7 @@ static const struct smf_state states[] = {
232232
};
233233

234234
/* Static helper function */
235+
235236
static void task_wdt_callback(int channel_id, void *user_data)
236237
{
237238
LOG_ERR("Watchdog expired, Channel: %d, Thread: %s",
@@ -441,6 +442,7 @@ static void triggering_entry(void *o)
441442

442443
static void triggering_run(void *o)
443444
{
445+
int err;
444446
struct main_state *state_object = o;
445447

446448
if (state_object->chan == &CLOUD_CHAN) {
@@ -454,13 +456,19 @@ static void triggering_run(void *o)
454456
}
455457

456458
if (msg.type == CLOUD_SHADOW_RESPONSE) {
457-
/* Missing: Parse the interval received in the shadow response,
458-
* write to state object and schedule new interval
459-
*/
459+
err = get_update_interval_from_cbor_response(msg.response.buffer,
460+
msg.response.buffer_data_len,
461+
&state_object->interval_sec);
462+
if (err) {
463+
LOG_ERR("json_parse, error: %d", err);
464+
SEND_FATAL_ERROR();
465+
return;
466+
}
460467

461-
int err = k_work_reschedule(&trigger_work,
462-
K_SECONDS(state_object->interval_sec));
468+
LOG_WRN("Received new interval: %lld seconds", state_object->interval_sec);
463469

470+
err = k_work_reschedule(&trigger_work,
471+
K_SECONDS(state_object->interval_sec));
464472
if (err < 0) {
465473
LOG_ERR("k_work_reschedule, error: %d", err);
466474
SEND_FATAL_ERROR();

app/src/modules/cloud/cloud_module.c

+49-12
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,9 @@
1111
#include <zephyr/task_wdt/task_wdt.h>
1212
#include <net/nrf_cloud.h>
1313
#include <net/nrf_cloud_coap.h>
14+
#include <nrf_cloud_coap_transport.h>
1415
#include <app_version.h>
1516

16-
#if defined(CONFIG_MEMFAULT)
17-
#include <memfault/core/trace_event.h>
18-
#endif /* CONFIG_MEMFAULT */
19-
2017
#include "cloud_module.h"
2118
#include "app_common.h"
2219
#include "network.h"
@@ -47,7 +44,7 @@ LOG_MODULE_REGISTER(cloud, CONFIG_APP_CLOUD_LOG_LEVEL);
4744
#define ENV_MSG_SIZE 0
4845
#endif /* CONFIG_APP_ENVIRONMENTAL) */
4946

50-
#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_payload), \
47+
#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_msg), \
5148
MAX(sizeof(struct network_msg), \
5249
MAX(BAT_MSG_SIZE, ENV_MSG_SIZE))))
5350

@@ -467,13 +464,19 @@ static void state_connected_exit(void *o)
467464
static void shadow_get(bool delta_only)
468465
{
469466
int err;
470-
uint8_t recv_buf[CONFIG_APP_MODULE_RECV_BUFFER_SIZE] = { 0 };
471-
size_t recv_buf_len = sizeof(recv_buf);
467+
struct cloud_msg msg = {
468+
.type = CLOUD_SHADOW_RESPONSE,
469+
.response = {
470+
.buffer_data_len = sizeof(msg.response.buffer),
471+
},
472+
};
472473

473474
LOG_DBG("Requesting device shadow from the device");
474475

475-
err = nrf_cloud_coap_shadow_get(recv_buf, &recv_buf_len, delta_only,
476-
COAP_CONTENT_FORMAT_APP_JSON);
476+
err = nrf_cloud_coap_shadow_get(msg.response.buffer,
477+
&msg.response.buffer_data_len,
478+
delta_only,
479+
COAP_CONTENT_FORMAT_APP_CBOR);
477480
if (err == -EACCES) {
478481
LOG_WRN("Not connected, error: %d", err);
479482
return;
@@ -486,12 +489,46 @@ static void shadow_get(bool delta_only)
486489
} else if (err > 0) {
487490
LOG_WRN("Cloud error: %d", err);
488491
return;
492+
} else if (err == -E2BIG) {
493+
LOG_WRN("The provided buffer is not large enough, error: %d", err);
494+
return;
489495
} else if (err) {
490496
LOG_ERR("Failed to request shadow delta: %d", err);
491497
return;
492498
}
493499

494-
/* No further processing of shadow is implemented */
500+
if (msg.response.buffer_data_len == 0) {
501+
LOG_DBG("No shadow delta changes available");
502+
return;
503+
}
504+
505+
/* Workaroud: Sometimes nrf_cloud_coap_shadow_get() returns 0 even though obtaining
506+
* the shadow failed. Ignore the payload if the first 10 bytes are zero.
507+
*/
508+
if (!memcmp(msg.response.buffer, "\0\0\0\0\0\0\0\0\0\0", 10)) {
509+
LOG_WRN("Returned buffeør is empty, ignore");
510+
return;
511+
}
512+
513+
err = zbus_chan_pub(&CLOUD_CHAN, &msg, K_SECONDS(1));
514+
if (err) {
515+
LOG_ERR("zbus_chan_pub, error: %d", err);
516+
SEND_FATAL_ERROR();
517+
return;
518+
}
519+
520+
/* Clear the shadow delta by reporting the same data back to the shadow reported state */
521+
err = nrf_cloud_coap_patch("state/reported", NULL,
522+
msg.response.buffer,
523+
msg.response.buffer_data_len,
524+
COAP_CONTENT_FORMAT_APP_CBOR,
525+
true,
526+
NULL,
527+
NULL);
528+
if (err) {
529+
LOG_ERR("Failed to patch the device shadow, error: %d", err);
530+
return;
531+
}
495532
}
496533

497534
static void state_connected_ready_entry(void *o)
@@ -641,9 +678,9 @@ static void state_connected_ready_run(void *o)
641678
#endif /* CONFIG_APP_ENVIRONMENTAL */
642679

643680
if (state_object->chan == &PAYLOAD_CHAN) {
644-
const struct cloud_payload *payload = MSG_TO_PAYLOAD(state_object->msg_buf);
681+
const struct cloud_msg msg = MSG_TO_CLOUD_MSG(state_object->msg_buf);
645682

646-
err = nrf_cloud_coap_json_message_send(payload->buffer, false, confirmable);
683+
err = nrf_cloud_coap_json_message_send(msg.payload.buffer, false, confirmable);
647684
if (err == -ENETUNREACH) {
648685
LOG_WRN("Network is unreachable, error: %d", err);
649686
return;

app/src/modules/cloud/cloud_module.h

+5-4
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ ZBUS_CHAN_DECLARE(
2222

2323
struct cloud_payload {
2424
uint8_t buffer[CONFIG_APP_CLOUD_PAYLOAD_BUFFER_MAX_SIZE];
25-
size_t buffer_len;
25+
/* Length of the data stored inside the buffer */
26+
size_t buffer_data_len;
2627
};
2728

2829
struct cloud_shadow_response {
2930
uint8_t buffer[CONFIG_APP_CLOUD_SHADOW_RESPONSE_BUFFER_MAX_SIZE];
30-
size_t buffer_len;
31+
/* Length of the data stored inside the buffer */
32+
size_t buffer_data_len;
3133
};
3234

3335
enum cloud_msg_type {
@@ -43,11 +45,10 @@ enum cloud_msg_type {
4345
struct cloud_msg {
4446
enum cloud_msg_type type;
4547
struct cloud_payload payload;
48+
struct cloud_shadow_response response;
4649
};
4750

4851
#define MSG_TO_CLOUD_MSG(_msg) (*(const struct cloud_msg *)_msg)
49-
#define MSG_TO_PAYLOAD(_msg) ((struct cloud_payload *)_msg)
50-
#define MSG_TO_SHADOW_RESPONSE(_msg) (*(const struct cloud_shadow_response *)_msg)
5152

5253
#ifdef __cplusplus
5354
}

app/src/modules/cloud/cloud_module_shell.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ static int cmd_publish(const struct shell *sh, size_t argc, char **argv)
4747
return 1;
4848
}
4949

50-
payload.buffer_len = err;
50+
payload.buffer_data_len = err;
5151

5252
(void)shell_print(sh, "Sending on payload channel: %s (%d bytes)",
53-
payload.buffer, payload.buffer_len);
53+
payload.buffer, payload.buffer_data_len);
5454

5555
err = zbus_chan_pub(&PAYLOAD_CHAN, &payload, K_SECONDS(1));
5656
if (err) {

tests/module/cloud/CMakeLists.txt

-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,5 @@ target_compile_definitions(app PRIVATE
4545
-DCONFIG_LTE_LC_CONN_EVAL_MODULE=1
4646
-DCONFIG_LTE_LC_EDRX_MODULE=1
4747
-DCONFIG_LTE_LC_PSM_MODULE=1
48-
-DCONFIG_APP_MODULE_RECV_BUFFER_SIZE=1024
4948
-DCOAP_CONTENT_FORMAT_APP_JSON=50
5049
)

tests/module/main/CMakeLists.txt

-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ target_compile_definitions(app PRIVATE
6060
-DCONFIG_LTE_LC_CONN_EVAL_MODULE=1
6161
-DCONFIG_LTE_LC_EDRX_MODULE=1
6262
-DCONFIG_LTE_LC_PSM_MODULE=1
63-
-DCONFIG_APP_MODULE_RECV_BUFFER_SIZE=1024
6463
-DCOAP_CONTENT_FORMAT_APP_JSON=50
6564
-DCONFIG_APP_POWER=1
6665
-DCONFIG_APP_ENVIRONMENTAL=1

tests/module/main/src/main.c

+5-10
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,17 @@ static void send_location_search_done(void)
141141
TEST_ASSERT_EQUAL(0, err);
142142
}
143143

144-
/*
145144
static void send_config(uint64_t interval)
146145
{
147-
const struct configuration config = {
148-
.update_interval_present = true,
149-
.update_interval = interval,
146+
const struct cloud_msg msg = {
147+
.type = CLOUD_SHADOW_RESPONSE,
148+
.response.buffer_len = "sometinhg"
150149
};
151150

152-
int err = zbus_chan_pub(&CONFIG_CHAN, &config, K_SECONDS(1));
151+
int err = zbus_chan_pub(&CLOUD_CHAN, &shadow_response, K_SECONDS(1));
153152

154153
TEST_ASSERT_EQUAL(0, err);
155154
}
156-
*/
157155

158156
static void send_cloud_disconnected(void)
159157
{
@@ -214,7 +212,6 @@ void test_button_press_on_disconnected(void)
214212
check_no_events(7200);
215213
}
216214

217-
/*
218215
void test_trigger_interval_change_in_connected(void)
219216
{
220217
send_cloud_connected_ready_to_send();
@@ -230,9 +227,8 @@ void test_trigger_interval_change_in_connected(void)
230227
send_cloud_disconnected();
231228
check_no_events(WEEK_IN_SECONDS);
232229
}
233-
*/
234230

235-
/*
231+
236232
void test_trigger_disconnect_and_connect_when_triggering(void)
237233
{
238234
send_cloud_connected_ready_to_send();
@@ -255,7 +251,6 @@ void test_trigger_disconnect_and_connect_when_triggering(void)
255251
send_cloud_disconnected();
256252
check_no_events(WEEK_IN_SECONDS);
257253
}
258-
*/
259254

260255
/* This is required to be added to each test. That is because unity's
261256
* main may return nonzero, while zephyr's main currently must

0 commit comments

Comments
 (0)