Skip to content

Commit d96d758

Browse files
committed
modules: Add piping for decoding incoming CBOR shadow data
Add piping for decoding incoming CBOR shadow data Fix use of json payload channel Signed-off-by: Simen S. Røstad <simen.rostad@nordicsemi.no>
1 parent b1c169e commit d96d758

14 files changed

+214
-77
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/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

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#include <zephyr/logging/log.h>
8+
#include <zcbor_decode.h>
9+
10+
LOG_MODULE_DECLARE(main, CONFIG_APP_LOG_LEVEL);
11+
12+
int get_update_interval_from_cbor_response(const uint8_t *cbor,
13+
size_t len,
14+
uint64_t *interval_sec)
15+
{
16+
*interval_sec = 60;
17+
return 0;
18+
}

app/src/cbor/cbor_helper.h

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#include <zephyr/types.h>
8+
9+
int get_update_interval_from_cbor_response(const uint8_t *cbor,
10+
size_t len,
11+
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

+61-29
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@
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>
15+
#include <zephyr/net/coap.h>
1416
#include <app_version.h>
1517

16-
#if defined(CONFIG_MEMFAULT)
17-
#include <memfault/core/trace_event.h>
18-
#endif /* CONFIG_MEMFAULT */
19-
2018
#include "cloud_module.h"
2119
#include "app_common.h"
2220
#include "network.h"
@@ -47,7 +45,7 @@ LOG_MODULE_REGISTER(cloud, CONFIG_APP_CLOUD_LOG_LEVEL);
4745
#define ENV_MSG_SIZE 0
4846
#endif /* CONFIG_APP_ENVIRONMENTAL) */
4947

50-
#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_payload), \
48+
#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_msg), \
5149
MAX(sizeof(struct network_msg), \
5250
MAX(BAT_MSG_SIZE, ENV_MSG_SIZE))))
5351

@@ -59,7 +57,6 @@ BUILD_ASSERT(CONFIG_APP_CLOUD_WATCHDOG_TIMEOUT_SECONDS >
5957
ZBUS_MSG_SUBSCRIBER_DEFINE(cloud);
6058

6159
/* Observe channels */
62-
ZBUS_CHAN_ADD_OBS(PAYLOAD_CHAN, cloud, 0);
6360
ZBUS_CHAN_ADD_OBS(NETWORK_CHAN, cloud, 0);
6461
ZBUS_CHAN_ADD_OBS(CLOUD_CHAN, cloud, 0);
6562

@@ -73,14 +70,6 @@ ZBUS_CHAN_ADD_OBS(POWER_CHAN, cloud, 0);
7370

7471
/* Define channels provided by this module */
7572

76-
ZBUS_CHAN_DEFINE(PAYLOAD_CHAN,
77-
struct cloud_payload,
78-
NULL,
79-
NULL,
80-
ZBUS_OBSERVERS_EMPTY,
81-
ZBUS_MSG_INIT(0)
82-
);
83-
8473
ZBUS_CHAN_DEFINE(CLOUD_CHAN,
8574
struct cloud_msg,
8675
NULL,
@@ -467,13 +456,19 @@ static void state_connected_exit(void *o)
467456
static void shadow_get(bool delta_only)
468457
{
469458
int err;
470-
uint8_t recv_buf[CONFIG_APP_MODULE_RECV_BUFFER_SIZE] = { 0 };
471-
size_t recv_buf_len = sizeof(recv_buf);
459+
struct cloud_msg msg = {
460+
.type = CLOUD_SHADOW_RESPONSE,
461+
.response = {
462+
.buffer_data_len = sizeof(msg.response.buffer),
463+
},
464+
};
472465

473466
LOG_DBG("Requesting device shadow from the device");
474467

475-
err = nrf_cloud_coap_shadow_get(recv_buf, &recv_buf_len, delta_only,
476-
COAP_CONTENT_FORMAT_APP_JSON);
468+
err = nrf_cloud_coap_shadow_get(msg.response.buffer,
469+
&msg.response.buffer_data_len,
470+
delta_only,
471+
COAP_CONTENT_FORMAT_APP_CBOR);
477472
if (err == -EACCES) {
478473
LOG_WRN("Not connected, error: %d", err);
479474
return;
@@ -486,12 +481,46 @@ static void shadow_get(bool delta_only)
486481
} else if (err > 0) {
487482
LOG_WRN("Cloud error: %d", err);
488483
return;
484+
} else if (err == -E2BIG) {
485+
LOG_WRN("The provided buffer is not large enough, error: %d", err);
486+
return;
489487
} else if (err) {
490488
LOG_ERR("Failed to request shadow delta: %d", err);
491489
return;
492490
}
493491

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

497526
static void state_connected_ready_entry(void *o)
@@ -640,17 +669,20 @@ static void state_connected_ready_run(void *o)
640669
}
641670
#endif /* CONFIG_APP_ENVIRONMENTAL */
642671

643-
if (state_object->chan == &PAYLOAD_CHAN) {
644-
const struct cloud_payload *payload = MSG_TO_PAYLOAD(state_object->msg_buf);
672+
if (state_object->chan == &CLOUD_CHAN) {
673+
const struct cloud_msg msg = MSG_TO_CLOUD_MSG(state_object->msg_buf);
645674

646-
err = nrf_cloud_coap_json_message_send(payload->buffer, false, confirmable);
647-
if (err == -ENETUNREACH) {
648-
LOG_WRN("Network is unreachable, error: %d", err);
649-
return;
650-
} else if (err) {
651-
LOG_ERR("nrf_cloud_coap_sensor_send, error: %d", err);
652-
SEND_FATAL_ERROR();
653-
return;
675+
if (msg.type == CLOUD_PAYLOAD_JSON) {
676+
err = nrf_cloud_coap_json_message_send(msg.payload.buffer,
677+
false, confirmable);
678+
if (err == -ENETUNREACH) {
679+
LOG_WRN("Network is unreachable, error: %d", err);
680+
return;
681+
} else if (err) {
682+
LOG_ERR("nrf_cloud_coap_sensor_send, error: %d", err);
683+
SEND_FATAL_ERROR();
684+
return;
685+
}
654686
}
655687
}
656688

app/src/modules/cloud/cloud_module.h

+6-6
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,19 @@ extern "C" {
1616

1717
/* Channels provided by this module */
1818
ZBUS_CHAN_DECLARE(
19-
CLOUD_CHAN,
20-
PAYLOAD_CHAN
19+
CLOUD_CHAN
2120
);
2221

2322
struct cloud_payload {
2423
uint8_t buffer[CONFIG_APP_CLOUD_PAYLOAD_BUFFER_MAX_SIZE];
25-
size_t buffer_len;
24+
/* Length of the data stored inside the buffer */
25+
size_t buffer_data_len;
2626
};
2727

2828
struct cloud_shadow_response {
2929
uint8_t buffer[CONFIG_APP_CLOUD_SHADOW_RESPONSE_BUFFER_MAX_SIZE];
30-
size_t buffer_len;
30+
/* Length of the data stored inside the buffer */
31+
size_t buffer_data_len;
3132
};
3233

3334
enum cloud_msg_type {
@@ -43,11 +44,10 @@ enum cloud_msg_type {
4344
struct cloud_msg {
4445
enum cloud_msg_type type;
4546
struct cloud_payload payload;
47+
struct cloud_shadow_response response;
4648
};
4749

4850
#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)
5151

5252
#ifdef __cplusplus
5353
}

0 commit comments

Comments
 (0)