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 app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_subdirectory(src/modules/cloud)
add_subdirectory(src/modules/location)
add_subdirectory(src/modules/fota)
add_subdirectory(src/modules/button)
add_subdirectory(src/cbor)

# Optional modules
add_subdirectory_ifdef(CONFIG_APP_POWER src/modules/power)
Expand Down
2 changes: 1 addition & 1 deletion app/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ CONFIG_NRF_MODEM_LIB=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_SHELL=y
CONFIG_SHELL_WILDCARD=n
CONFIG_SHELL_STACK_SIZE=1560
CONFIG_SHELL_STACK_SIZE=4096
CONFIG_AT_SHELL=y

# nRF Cloud
Expand Down
4 changes: 0 additions & 4 deletions app/src/Kconfig.main
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ config APP_MODULE_THREAD_STACK_SIZE
int "Thread stack size"
default 3200

config APP_MODULE_RECV_BUFFER_SIZE
int "Receive buffer size"
default 1024

config APP_REQUEST_NETWORK_QUALITY
bool "Request network quality"
help
Expand Down
8 changes: 8 additions & 0 deletions app/src/cbor/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cbor_helper.c)
target_include_directories(app PRIVATE .)
17 changes: 17 additions & 0 deletions app/src/cbor/cbor_helper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include <zephyr/logging/log.h>

LOG_MODULE_DECLARE(main, CONFIG_APP_LOG_LEVEL);

int get_update_interval_from_cbor_response(const uint8_t *cbor,
size_t len,
uint64_t *interval_sec)
{
*interval_sec = 1800;
return 0;
}
11 changes: 11 additions & 0 deletions app/src/cbor/cbor_helper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include <zephyr/types.h>

int get_update_interval_from_cbor_response(const uint8_t *cbor,
size_t len,
uint64_t *interval_sec);
53 changes: 53 additions & 0 deletions app/src/cbor/device_shadow.cddl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
; Define the basic structure of the JSON object

; The 'any' type is used in this schema to allow forward compatibility.
; They are inserted into maps that may contain additional fields in the future.
; This prevents cbor decoding from failing in the field when the cloud side has new fields.
app-object = {
? "nrfcloud_mqtt_topic_prefix": tstr,
? "pairing": pairing-type,
? "lwm2m": lwm2m-map,
* tstr => any
}

; Define the pairing object with nested topics
pairing-type = {
"state": tstr,
"topics": {
"d2c": tstr,
"c2d": tstr,
* tstr => any
},
* any => any
}

lwm2m-map = {
? "14240:1.0": led,
? "14301:1.0": config,
* tstr => any
}

led = {
"0": led_inner_object,
* tstr => any
}

led_inner_object = {
? "0": int .size 4,
? "1": int .size 4,
? "2": int .size 4,
"99": int .size 8,
* tstr => any
}

config = {
"0": config_inner_object,
* tstr => any
}

config_inner_object = {
? "0": int .size 8,
? "1": bool,
"99": int .size 8,
* tstr => any
}
24 changes: 16 additions & 8 deletions app/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "cloud_module.h"
#include "fota.h"
#include "location.h"
#include "cbor_helper.h"

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

#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_shadow_response), \
MAX(sizeof(struct cloud_payload), \
#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_msg), \
/* Button channel payload size */ \
MAX(sizeof(uint8_t), \
/* Timer channel payload size */ \
MAX(sizeof(int), \
MAX(sizeof(enum fota_msg_type), \
MAX(sizeof(enum location_msg_type), \
MAX(sizeof(struct network_msg), POWER_MSG_SIZE))))))))
MAX(sizeof(struct network_msg), POWER_MSG_SIZE)))))))

/* Register subscriber */
ZBUS_MSG_SUBSCRIBER_DEFINE(main_subscriber);
Expand Down Expand Up @@ -232,6 +232,7 @@ static const struct smf_state states[] = {
};

/* Static helper function */

static void task_wdt_callback(int channel_id, void *user_data)
{
LOG_ERR("Watchdog expired, Channel: %d, Thread: %s",
Expand Down Expand Up @@ -441,6 +442,7 @@ static void triggering_entry(void *o)

static void triggering_run(void *o)
{
int err;
struct main_state *state_object = o;

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

if (msg.type == CLOUD_SHADOW_RESPONSE) {
/* Missing: Parse the interval received in the shadow response,
* write to state object and schedule new interval
*/
err = get_update_interval_from_cbor_response(msg.response.buffer,
msg.response.buffer_data_len,
&state_object->interval_sec);
if (err) {
LOG_ERR("json_parse, error: %d", err);
SEND_FATAL_ERROR();
return;
}

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

err = k_work_reschedule(&trigger_work,
K_SECONDS(state_object->interval_sec));
if (err < 0) {
LOG_ERR("k_work_reschedule, error: %d", err);
SEND_FATAL_ERROR();
Expand Down
90 changes: 61 additions & 29 deletions app/src/modules/cloud/cloud_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@
#include <zephyr/task_wdt/task_wdt.h>
#include <net/nrf_cloud.h>
#include <net/nrf_cloud_coap.h>
#include <nrf_cloud_coap_transport.h>
#include <zephyr/net/coap.h>
#include <app_version.h>

#if defined(CONFIG_MEMFAULT)
#include <memfault/core/trace_event.h>
#endif /* CONFIG_MEMFAULT */

#include "cloud_module.h"
#include "app_common.h"
#include "network.h"
Expand Down Expand Up @@ -47,7 +45,7 @@ LOG_MODULE_REGISTER(cloud, CONFIG_APP_CLOUD_LOG_LEVEL);
#define ENV_MSG_SIZE 0
#endif /* CONFIG_APP_ENVIRONMENTAL) */

#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_payload), \
#define MAX_MSG_SIZE (MAX(sizeof(struct cloud_msg), \
MAX(sizeof(struct network_msg), \
MAX(BAT_MSG_SIZE, ENV_MSG_SIZE))))

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

/* Observe channels */
ZBUS_CHAN_ADD_OBS(PAYLOAD_CHAN, cloud, 0);
ZBUS_CHAN_ADD_OBS(NETWORK_CHAN, cloud, 0);
ZBUS_CHAN_ADD_OBS(CLOUD_CHAN, cloud, 0);

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

/* Define channels provided by this module */

ZBUS_CHAN_DEFINE(PAYLOAD_CHAN,
struct cloud_payload,
NULL,
NULL,
ZBUS_OBSERVERS_EMPTY,
ZBUS_MSG_INIT(0)
);

ZBUS_CHAN_DEFINE(CLOUD_CHAN,
struct cloud_msg,
NULL,
Expand Down Expand Up @@ -467,13 +456,19 @@ static void state_connected_exit(void *o)
static void shadow_get(bool delta_only)
{
int err;
uint8_t recv_buf[CONFIG_APP_MODULE_RECV_BUFFER_SIZE] = { 0 };
size_t recv_buf_len = sizeof(recv_buf);
struct cloud_msg msg = {
.type = CLOUD_SHADOW_RESPONSE,
.response = {
.buffer_data_len = sizeof(msg.response.buffer),
},
};

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

err = nrf_cloud_coap_shadow_get(recv_buf, &recv_buf_len, delta_only,
COAP_CONTENT_FORMAT_APP_JSON);
err = nrf_cloud_coap_shadow_get(msg.response.buffer,
&msg.response.buffer_data_len,
delta_only,
COAP_CONTENT_FORMAT_APP_CBOR);
if (err == -EACCES) {
LOG_WRN("Not connected, error: %d", err);
return;
Expand All @@ -486,12 +481,46 @@ static void shadow_get(bool delta_only)
} else if (err > 0) {
LOG_WRN("Cloud error: %d", err);
return;
} else if (err == -E2BIG) {
LOG_WRN("The provided buffer is not large enough, error: %d", err);
return;
} else if (err) {
LOG_ERR("Failed to request shadow delta: %d", err);
return;
}

/* No further processing of shadow is implemented */
if (msg.response.buffer_data_len == 0) {
LOG_DBG("No shadow delta changes available");
return;
}

/* Workaroud: Sometimes nrf_cloud_coap_shadow_get() returns 0 even though obtaining
* the shadow failed. Ignore the payload if the first 10 bytes are zero.
*/
if (!memcmp(msg.response.buffer, "\0\0\0\0\0\0\0\0\0\0", 10)) {
LOG_WRN("Returned buffeør is empty, ignore");
return;
}

err = zbus_chan_pub(&CLOUD_CHAN, &msg, K_SECONDS(1));
if (err) {
LOG_ERR("zbus_chan_pub, error: %d", err);
SEND_FATAL_ERROR();
return;
}

/* Clear the shadow delta by reporting the same data back to the shadow reported state */
err = nrf_cloud_coap_patch("state/reported", NULL,
msg.response.buffer,
msg.response.buffer_data_len,
COAP_CONTENT_FORMAT_APP_CBOR,
true,
NULL,
NULL);
if (err) {
LOG_ERR("Failed to patch the device shadow, error: %d", err);
return;
}
}

static void state_connected_ready_entry(void *o)
Expand Down Expand Up @@ -640,17 +669,20 @@ static void state_connected_ready_run(void *o)
}
#endif /* CONFIG_APP_ENVIRONMENTAL */

if (state_object->chan == &PAYLOAD_CHAN) {
const struct cloud_payload *payload = MSG_TO_PAYLOAD(state_object->msg_buf);
if (state_object->chan == &CLOUD_CHAN) {
const struct cloud_msg msg = MSG_TO_CLOUD_MSG(state_object->msg_buf);

err = nrf_cloud_coap_json_message_send(payload->buffer, false, confirmable);
if (err == -ENETUNREACH) {
LOG_WRN("Network is unreachable, error: %d", err);
return;
} else if (err) {
LOG_ERR("nrf_cloud_coap_sensor_send, error: %d", err);
SEND_FATAL_ERROR();
return;
if (msg.type == CLOUD_PAYLOAD_JSON) {
err = nrf_cloud_coap_json_message_send(msg.payload.buffer,
false, confirmable);
if (err == -ENETUNREACH) {
LOG_WRN("Network is unreachable, error: %d", err);
return;
} else if (err) {
LOG_ERR("nrf_cloud_coap_sensor_send, error: %d", err);
SEND_FATAL_ERROR();
return;
}
}
}

Expand Down
12 changes: 6 additions & 6 deletions app/src/modules/cloud/cloud_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@ extern "C" {

/* Channels provided by this module */
ZBUS_CHAN_DECLARE(
CLOUD_CHAN,
PAYLOAD_CHAN
CLOUD_CHAN
);

struct cloud_payload {
uint8_t buffer[CONFIG_APP_CLOUD_PAYLOAD_BUFFER_MAX_SIZE];
size_t buffer_len;
/* Length of the data stored inside the buffer */
size_t buffer_data_len;
};

struct cloud_shadow_response {
uint8_t buffer[CONFIG_APP_CLOUD_SHADOW_RESPONSE_BUFFER_MAX_SIZE];
size_t buffer_len;
/* Length of the data stored inside the buffer */
size_t buffer_data_len;
};

enum cloud_msg_type {
Expand All @@ -43,11 +44,10 @@ enum cloud_msg_type {
struct cloud_msg {
enum cloud_msg_type type;
struct cloud_payload payload;
struct cloud_shadow_response response;
};

#define MSG_TO_CLOUD_MSG(_msg) (*(const struct cloud_msg *)_msg)
#define MSG_TO_PAYLOAD(_msg) ((struct cloud_payload *)_msg)
#define MSG_TO_SHADOW_RESPONSE(_msg) (*(const struct cloud_shadow_response *)_msg)

#ifdef __cplusplus
}
Expand Down
Loading
Loading