Skip to content

Commit

Permalink
modules: Handle desired configurations
Browse files Browse the repository at this point in the history
Handle desired configurations set in the shadow:
 - Add cddl schema for configurations set in the shadow
 - Add configuration channel that interested modules can subscribe to
 - Enable PWM and set RGB values according to the shadow configurations
 - Update trigger module to only use system workqueue, this is ok
   since we are not blocking the system workqueue for long periods of
   time and its simplifies the module quite a bit.
 - The trigger will only run as long as we are connected.

Missing:
 - Adopting gnss enable. This depends on GNSS being sent internally
   from the location module.

Signed-off-by: Simen S. Røstad <[email protected]>
  • Loading branch information
simensrostad committed Jun 4, 2024
1 parent 7300a84 commit e3e12a1
Show file tree
Hide file tree
Showing 16 changed files with 264 additions and 118 deletions.
7 changes: 2 additions & 5 deletions app/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

CONFIG_NCS_SAMPLES_DEFAULTS=y
CONFIG_RESET_ON_FATAL_ERROR=y
CONFIG_DK_LIBRARY=y
CONFIG_PWM=y

# Heap and stacks
# Extended AT host/monitor stack/heap sizes since some nrf_cloud credentials are longer than 1024 bytes.
Expand Down Expand Up @@ -105,11 +107,6 @@ CONFIG_NRF_CLOUD_COAP_SEC_TAG=4242
#Required if device is provisioned to nRF Cloud using attestation token
#CONFIG_NRF_CLOUD_CLIENT_ID_SRC_INTERNAL_UUID=y

CONFIG_SIMPLE_CONFIG=y
CONFIG_DK_LIBRARY=y

CONFIG_SIMPLE_CONFIG_LOG_LEVEL_DBG=y

# needed for processing floats
CONFIG_PICOLIBC=y
CONFIG_FPU=y
Expand Down
8 changes: 4 additions & 4 deletions app/src/common/message_channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ ZBUS_CHAN_DEFINE(FATAL_ERROR_CHAN,
ZBUS_MSG_INIT(0)
);

ZBUS_CHAN_DEFINE(LED_CHAN,
int,
ZBUS_CHAN_DEFINE(CONFIG_CHAN,
struct configuration,
NULL,
NULL,
ZBUS_OBSERVERS(led),
ZBUS_OBSERVERS(led, trigger),
ZBUS_MSG_INIT(0)
);

ZBUS_CHAN_DEFINE(CLOUD_CHAN,
enum cloud_status,
NULL,
NULL,
ZBUS_OBSERVERS(fota, app, location),
ZBUS_OBSERVERS(fota, app, location, trigger),
CLOUD_DISCONNECTED
);
11 changes: 10 additions & 1 deletion app/src/common/message_channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,23 @@ enum cloud_status {
CLOUD_DISCONNECTED,
};

struct configuration {
int led_red;
int led_green;
int led_blue;
bool gnss;
uint64_t update_interval;
};

ZBUS_CHAN_DECLARE(
TRIGGER_CHAN,
PAYLOAD_CHAN,
NETWORK_CHAN,
FATAL_ERROR_CHAN,
LED_CHAN,
CLOUD_CHAN,
FOTA_ONGOING_CHAN
FOTA_ONGOING_CHAN,
CONFIG_CHAN
);

#ifdef __cplusplus
Expand Down
27 changes: 27 additions & 0 deletions app/src/modules/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,30 @@
#

target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app.c)

# generate encoder code using zcbor
set(zcbor_command
zcbor code # Invoke code generation
--cddl ${ZEPHYR_BASE}/subsys/net/lib/lwm2m/lwm2m_senml_cbor.cddl
--cddl ${CMAKE_CURRENT_SOURCE_DIR}/app_object.cddl
--decode # Generate decoding functions
--short-names # Attempt to make generated symbol names shorter (at the risk of collision)
-t app-object # Create a public API for decoding the "app_object" type from the cddl file
--output-cmake ${CMAKE_CURRENT_BINARY_DIR}/app_object.cmake # The generated cmake file will be placed here
)
execute_process(COMMAND ${zcbor_command} COMMAND_ERROR_IS_FATAL ANY)

# Include the cmake file generated by zcbor. It adds the
# generated code and the necessary zcbor C code files.
include(${CMAKE_CURRENT_BINARY_DIR}/app_object.cmake)

# Ensure that the cmake reconfiguration is triggerred everytime the cddl file changes.
# This ensures that the codec generation is triggered.
set_property(
DIRECTORY
PROPERTY
CMAKE_CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/app_object.cddl
)

zephyr_link_libraries(app_object)
target_link_libraries(app_object PRIVATE zephyr_interface)
2 changes: 1 addition & 1 deletion app/src/modules/app/Kconfig.app
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ menu "App"

config APP_MODULE_THREAD_STACK_SIZE
int "Thread stack size"
default 4096
default 8192

config APP_MODULE_MESSAGE_QUEUE_SIZE
int "Message queue size"
Expand Down
117 changes: 75 additions & 42 deletions app/src/modules/app/app.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/zbus/zbus.h>
#include <simple_config/simple_config.h>
#include <zephyr/task_wdt/task_wdt.h>
#include <net/nrf_cloud_coap.h>
#include <nrf_cloud_coap_transport.h>

#include "message_channel.h"
#include "app_object_decode.h" /* change this to config object and add led object here */

/* Register log module */
LOG_MODULE_REGISTER(app, CONFIG_APP_LOG_LEVEL);
Expand All @@ -21,45 +23,86 @@ ZBUS_SUBSCRIBER_DEFINE(app, CONFIG_APP_MODULE_MESSAGE_QUEUE_SIZE);
BUILD_ASSERT(CONFIG_APP_MODULE_WATCHDOG_TIMEOUT_SECONDS > CONFIG_APP_MODULE_EXEC_TIME_SECONDS_MAX,
"Watchdog timeout must be greater than maximum execution time");

int config_cb(const char *key, const struct simple_config_val *val)
static void shadow_get(bool get_desired)
{
int err;

if (val->type == SIMPLE_CONFIG_VAL_STRING) {
LOG_DBG("\"%s\" = %s", key, val->val._str);
} else if (val->type == SIMPLE_CONFIG_VAL_BOOL) {
LOG_DBG("\"%s\" = %s", key, val->val._bool ? "true" : "false");
} else if (val->type == SIMPLE_CONFIG_VAL_DOUBLE) {
LOG_DBG("\"%s\" = %f", key, val->val._double);
} else {
return -EINVAL;
struct app_object app_object = { 0 };
uint8_t buf_cbor[512] = { 0 };
uint8_t buf_json[512] = { 0 };
size_t buf_cbor_len = sizeof(buf_cbor);
size_t buf_json_len = sizeof(buf_json);
size_t not_used;

/* Request shadow delta, request only changes by setting delta parameter to true. */
err = nrf_cloud_coap_shadow_get(buf_json, &buf_json_len, !get_desired,
COAP_CONTENT_FORMAT_APP_JSON);
if (err == -EACCES) {
LOG_WRN("Not connected");
return;
} else if (err) {
LOG_ERR("Failed to request shadow delta: %d", err);
SEND_FATAL_ERROR();
return;
}

if (strcmp(key, "led_blue") == 0) {
int status = val->val._bool;

err = zbus_chan_pub(&LED_CHAN, &status, K_NO_WAIT);
if (err) {
LOG_ERR("zbus_chan_pub, error:%d", err);
SEND_FATAL_ERROR();
return err;
}
if (buf_json_len == 0 || strlen(buf_json) == 0) {
LOG_WRN("No shadow delta changes available");
return;
}

return 0;
/* Shadow available, fetch CBOR encoded desired section. */
err = nrf_cloud_coap_shadow_get(buf_cbor, &buf_cbor_len, false,
COAP_CONTENT_FORMAT_APP_CBOR);
if (err == -EACCES) {
LOG_WRN("Not connected");
return;
} else if (err) {
LOG_ERR("Failed to request shadow delta: %d", err);
SEND_FATAL_ERROR();
return;
}

return -EINVAL;
}
err = cbor_decode_app_object(buf_cbor, buf_cbor_len, &app_object, &not_used);
if (err) {
LOG_ERR("Failed to decode app object, error: %d", err);
SEND_FATAL_ERROR();
return;
}

static void init_app_settings(void)
{
struct simple_config_val val = {.type = SIMPLE_CONFIG_VAL_BOOL, .val._bool = true};
int err = simple_config_set("blue_led", &val);
struct configuration configuration = {
.led_red = app_object.lwm2m._1424010._0._0,
.led_green = app_object.lwm2m._1424010._0._1,
.led_blue = app_object.lwm2m._1424010._0._2,
.gnss = app_object.lwm2m._1430110._0._1,
.update_interval = app_object.lwm2m._1430110._0._0,
};

LOG_DBG("LED object (1424010) values received from cloud:");
LOG_DBG("R: %d", app_object.lwm2m._1424010._0._0);
LOG_DBG("G: %d", app_object.lwm2m._1424010._0._1);
LOG_DBG("B: %d", app_object.lwm2m._1424010._0._2);
LOG_DBG("Timestamp: %lld", app_object.lwm2m._1424010._0._99);

LOG_DBG("Application configuration object (1430110) values received from cloud:");
LOG_DBG("Update interval: %lld", app_object.lwm2m._1430110._0._0);
LOG_DBG("GNSS: %d", app_object.lwm2m._1430110._0._1);
LOG_DBG("Timestamp: %lld", app_object.lwm2m._1430110._0._99);

/* Distribute configuration */
err = zbus_chan_pub(&CONFIG_CHAN, &configuration, K_SECONDS(1));
if (err) {
LOG_ERR("simple_config_set, error: %d", err);
LOG_ERR("zbus_chan_pub, error: %d", err);
SEND_FATAL_ERROR();
return;
}

/* Send the received configuration back to the reported shadow section. */
err = nrf_cloud_coap_shadow_state_update(buf_json);
if (err < 0) {
LOG_ERR("Failed to send PATCH request: %d", err);
} else if (err > 0) {
LOG_ERR("Error from server: %d", err);
}
}

static void task_wdt_callback(int channel_id, void *user_data)
Expand All @@ -84,12 +127,6 @@ static void app_task(void)

task_wdt_id = task_wdt_add(wdt_timeout_ms, task_wdt_callback, (void *)k_current_get());

/* Set up callback for runtime config changes from cloud */
simple_config_set_callback(config_cb);

/* Set initial settting values (can be skipped if cloud initializes shadow) */
init_app_settings();

while (true) {
err = task_wdt_feed(task_wdt_id);
if (err) {
Expand Down Expand Up @@ -118,19 +155,15 @@ static void app_task(void)
}

if (cloud_status == CLOUD_CONNECTED) {
LOG_INF("Cloud connected");
LOG_INF("Getting latest device configuration from cloud");
LOG_DBG("Cloud connected");
LOG_DBG("Getting latest device configuration from device shadow");

simple_config_update();
} else {
LOG_INF("Cloud disconnected");
shadow_get(true);
}
}

if ((&TRIGGER_CHAN == chan) && (cloud_status == CLOUD_CONNECTED)) {
LOG_INF("Getting latest device configuration from cloud");

simple_config_update();
shadow_get(false);
}
}
}
Expand Down
41 changes: 41 additions & 0 deletions app/src/modules/app/app_object.cddl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
; Define the basic structure of the JSON object
app-object = {
"nrfcloud_mqtt_topic_prefix": tstr,
"pairing": pairing-type,
"lwm2m": lwm2m-map
}

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

lwm2m-map = {
"14240:1.0": led,
"14301:1.0": config
}

led = {
"0": lwm2m_inner_object_1
}

lwm2m_inner_object_1 = {
"0": int .size 4,
"1": int .size 4,
"2": int .size 4,
"99": int .size 8
}

config = {
"0": lwm2m_inner_object
}

lwm2m_inner_object = {
"0": int .size 8,
"1": bool,
"99": int .size 8
}
59 changes: 53 additions & 6 deletions app/src/modules/led/led.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,73 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/zbus/zbus.h>
#include <zephyr/drivers/led.h>
#include <zephyr/drivers/pwm.h>
#include <dk_buttons_and_leds.h>

#include "message_channel.h"

/* Register log module */
LOG_MODULE_REGISTER(led, CONFIG_APP_LED_LOG_LEVEL);

#define PWM_LED0_NODE DT_ALIAS(pwm_led0)
#define PWM_LED1_NODE DT_ALIAS(pwm_led1)
#define PWM_LED2_NODE DT_ALIAS(pwm_led2)

#if DT_NODE_HAS_STATUS(PWM_LED0_NODE, okay)
static const struct pwm_dt_spec led0 = PWM_DT_SPEC_GET(PWM_LED0_NODE);
#else
#error "Unsupported board: pwm-led 0 devicetree alias is not defined"
#endif
#if DT_NODE_HAS_STATUS(PWM_LED1_NODE, okay)
static const struct pwm_dt_spec led1 = PWM_DT_SPEC_GET(PWM_LED1_NODE);
#else
#error "Unsupported board: pwm-led 1 devicetree alias is not defined"
#endif
#if DT_NODE_HAS_STATUS(PWM_LED2_NODE, okay)
static const struct pwm_dt_spec led2 = PWM_DT_SPEC_GET(PWM_LED2_NODE);
#else
#error "Unsupported board: pwm-led 2 devicetree alias is not defined"
#endif

#define PWM_PERIOD 20000U
#define LIGHTNESS_MAX UINT8_MAX

void led_callback(const struct zbus_channel *chan)
{
int err;
const int *status;

if (&LED_CHAN == chan) {
/* Get LED status from channel. */
status = zbus_chan_const_msg(chan);
if (&CONFIG_CHAN == chan) {
/* Get LED configuration from channel. */

const struct configuration *config = zbus_chan_const_msg(chan);

LOG_DBG("LED configuration: red:%d, green:%d, blue:%d",
config->led_red, config->led_green, config->led_blue);

/* Red LED */
err = pwm_set_dt(&led0, PWM_USEC(PWM_PERIOD),
PWM_USEC((PWM_PERIOD * config->led_red) / LIGHTNESS_MAX));
if (err) {
LOG_ERR("pwm_set_dt, error:%d", err);
SEND_FATAL_ERROR();
return;
}

/* Blue LED */
err = dk_set_led(DK_LED2, *status);
err = pwm_set_dt(&led1, PWM_USEC(PWM_PERIOD),
PWM_USEC((PWM_PERIOD * config->led_blue) / LIGHTNESS_MAX));
if (err) {
LOG_ERR("pwm_set_dt, error:%d", err);
SEND_FATAL_ERROR();
return;
}

/* Green LED */
err = pwm_set_dt(&led2, PWM_USEC(PWM_PERIOD),
PWM_USEC((PWM_PERIOD * config->led_green) / LIGHTNESS_MAX));
if (err) {
LOG_ERR("dk_set_led, error:%d", err);
LOG_ERR("pwm_set_dt, error:%d", err);
SEND_FATAL_ERROR();
return;
}
Expand Down
Loading

0 comments on commit e3e12a1

Please sign in to comment.