diff --git a/README.md b/README.md index c938163d..53eb6d9c 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,12 @@ When using an external debugger, you can flash using this command: ```shell west flash --erase ``` + +### LED pattern + +| LED effect | Color | Meaning | Duration (seconds) | +|----------------|------------|----------------------------------------------|-----------------------------------------------------| +| Blinking | Yellow | Device is (re-)connecting to the LTE network | NA | +| Blinking slow | Blue | Device is actively polling cloud | 10 minutes after last config update or button press | +| Solid | Configured | Device has received a LED configuration | NA | +| Blinking rapid | Red | Fatal error, the device will reboot | NA | diff --git a/app/src/common/message_channel.c b/app/src/common/message_channel.c index 080a3601..c03da5bd 100644 --- a/app/src/common/message_channel.c +++ b/app/src/common/message_channel.c @@ -18,6 +18,14 @@ ZBUS_CHAN_DEFINE(TRIGGER_CHAN, ZBUS_MSG_INIT(0) ); +ZBUS_CHAN_DEFINE(TRIGGER_MODE_CHAN, + enum trigger_mode, + NULL, + NULL, + ZBUS_OBSERVERS_EMPTY, + ZBUS_MSG_INIT(0) +); + ZBUS_CHAN_DEFINE(FOTA_ONGOING_CHAN, bool, NULL, diff --git a/app/src/common/message_channel.h b/app/src/common/message_channel.h index f66b5d85..af560ba8 100644 --- a/app/src/common/message_channel.h +++ b/app/src/common/message_channel.h @@ -59,6 +59,11 @@ enum trigger_type { TRIGGER_DATA_SAMPLE, }; +enum trigger_mode { + TRIGGER_MODE_POLL = 0x1, + TRIGGER_MODE_NORMAL, +}; + enum time_status { TIME_AVAILABLE = 0x1, }; @@ -88,7 +93,8 @@ ZBUS_CHAN_DECLARE( NETWORK_CHAN, PAYLOAD_CHAN, TIME_CHAN, - TRIGGER_CHAN + TRIGGER_CHAN, + TRIGGER_MODE_CHAN ); #ifdef __cplusplus diff --git a/app/src/modules/led/CMakeLists.txt b/app/src/modules/led/CMakeLists.txt index f1697eb8..9d2e579d 100644 --- a/app/src/modules/led/CMakeLists.txt +++ b/app/src/modules/led/CMakeLists.txt @@ -4,4 +4,6 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +target_include_directories(app PRIVATE .) target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/led.c) +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/led_pwm.c) diff --git a/app/src/modules/led/led.c b/app/src/modules/led/led.c index 5149de73..c0405b35 100644 --- a/app/src/modules/led/led.c +++ b/app/src/modules/led/led.c @@ -7,11 +7,11 @@ #include #include #include -#include -#include #include #include "message_channel.h" +#include "led_pwm.h" +#include "led.h" /* Register log module */ LOG_MODULE_REGISTER(led, CONFIG_APP_LED_LOG_LEVEL); @@ -26,33 +26,162 @@ ZBUS_LISTENER_DEFINE(led, led_callback); /* Observe channels */ ZBUS_CHAN_ADD_OBS(ERROR_CHAN, led, 0); ZBUS_CHAN_ADD_OBS(CONFIG_CHAN, led, 0); +ZBUS_CHAN_ADD_OBS(NETWORK_CHAN, led, 0); +ZBUS_CHAN_ADD_OBS(TRIGGER_MODE_CHAN, led, 0); -#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 +/* Forward declarations */ +static void led_pattern_update_work_fn(struct k_work *work); + +/* Definition used to specify LED patterns that should hold forever. */ +#define HOLD_FOREVER -1 + +/* List of LED patterns supported in the UI module. */ +static struct led_pattern { + /* Variable used to construct a linked list of led patterns. */ + sys_snode_t header; + /* LED state. */ + enum led_state led_state; + /* Duration of the LED state. */ + int16_t duration_sec; + + /* RGB values for the LED state. */ + uint8_t red; + uint8_t green; + uint8_t blue; + +} led_pattern_list[LED_PATTERN_COUNT]; + +/* Linked list used to schedule multiple LED pattern transitions. */ +static sys_slist_t pattern_transition_list = SYS_SLIST_STATIC_INIT(&pattern_transition_list); + +/* Delayed work that is used to display and transition to the correct LED pattern depending on the + * internal state of the module. + */ +static K_WORK_DELAYABLE_DEFINE(led_pattern_update_work, led_pattern_update_work_fn); + +/* Function that clears LED pattern transition list. */ +static void transition_list_clear(void) +{ + struct led_pattern *transition = NULL; + struct led_pattern *next_transition = NULL; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&pattern_transition_list, + transition, + next_transition, + header) { + sys_slist_remove(&pattern_transition_list, NULL, &transition->header); + }; +} + +/* Function that appends a LED state and a corresponding duration to the + * LED pattern transition list. If the LED state is set to LED_CONFIGURED, the RGB values are + * also appended to the list and used to set the desired color. + */ +static void transition_list_append(enum led_state led_state, int16_t duration_sec, + uint8_t red, uint8_t green, uint8_t blue) +{ + led_pattern_list[led_state].led_state = led_state; + led_pattern_list[led_state].duration_sec = duration_sec; + + if (led_state == LED_CONFIGURED) { + led_pattern_list[led_state].red = red; + led_pattern_list[led_state].green = green; + led_pattern_list[led_state].blue = blue; + } + + sys_slist_append(&pattern_transition_list, &led_pattern_list[led_state].header); +} + +static void led_pattern_update_work_fn(struct k_work *work) +{ + ARG_UNUSED(work); + + struct led_pattern *next_pattern; + static enum led_state previous_led_state = LED_PATTERN_COUNT; + sys_snode_t *node = sys_slist_get(&pattern_transition_list); + + if (node == NULL) { + LOG_ERR("Cannot find any more LED pattern transitions"); + return; + } + + next_pattern = CONTAINER_OF(node, struct led_pattern, header); + + /* Prevent the same LED led_state from being scheduled twice in a row. */ + if (next_pattern->led_state != previous_led_state) { + + if (next_pattern->led_state == LED_CONFIGURED) { + led_pwm_set_rgb(next_pattern->red, next_pattern->green, next_pattern->blue); + } else { + led_pwm_set_effect(next_pattern->led_state); + } + + previous_led_state = next_pattern->led_state; + } + + /* Even if the LED state is not updated due a match with the previous state a LED pattern + * update is scheduled. This will prolong the pattern until the LED pattern transition + * list is cleared. + */ + if (next_pattern->duration_sec > 0) { + k_work_reschedule(&led_pattern_update_work, K_SECONDS(next_pattern->duration_sec)); + } +} void led_callback(const struct zbus_channel *chan) { - int err; + if (&TRIGGER_MODE_CHAN == chan) { + const enum trigger_mode *mode = zbus_chan_const_msg(chan); + + if (*mode == TRIGGER_MODE_NORMAL) { + LOG_DBG("Trigger mode normal"); + + /* Clear all LED patterns and stop the PWM. + * I think we need to use the LED pwm API directly in some way here + * to ensure that we are disabling the PWM. + */ + transition_list_clear(); + transition_list_append(LED_OFF, HOLD_FOREVER, 0, 0, 0); + + k_work_reschedule(&led_pattern_update_work, K_NO_WAIT); + + } else if (*mode == TRIGGER_MODE_POLL) { + LOG_DBG("Trigger mode poll"); + + transition_list_clear(); + + /* If we enter poll mode and the LED is configured, we set the configured + * color. + */ + if (led_pattern_list[LED_CONFIGURED].red != 0 || + led_pattern_list[LED_CONFIGURED].green != 0 || + led_pattern_list[LED_CONFIGURED].blue != 0) { + + transition_list_append(LED_CONFIGURED, HOLD_FOREVER, + led_pattern_list[LED_CONFIGURED].red, + led_pattern_list[LED_CONFIGURED].green, + led_pattern_list[LED_CONFIGURED].blue); + } else { + transition_list_append(LED_POLL_MODE, HOLD_FOREVER, 0, 0, 0); + } + + k_work_reschedule(&led_pattern_update_work, K_NO_WAIT); + } + } + + if (&NETWORK_CHAN == chan) { + + const enum network_status *status = zbus_chan_const_msg(chan); + + if (*status == NETWORK_DISCONNECTED) { + LOG_DBG("Network disconnected"); + + transition_list_clear(); + transition_list_append(LED_LTE_CONNECTING, HOLD_FOREVER, 0, 0, 0); + + k_work_reschedule(&led_pattern_update_work, K_NO_WAIT); + } + } if (&CONFIG_CHAN == chan) { /* Get LED configuration from channel. */ @@ -67,46 +196,21 @@ void led_callback(const struct zbus_channel *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 = 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; - } + transition_list_clear(); + transition_list_append(LED_CONFIGURED, HOLD_FOREVER, + (uint8_t)config->led_red, + (uint8_t)config->led_green, + (uint8_t)config->led_blue); - /* Green LED */ - err = pwm_set_dt(&led2, PWM_USEC(PWM_PERIOD), - PWM_USEC((PWM_PERIOD * config->led_green) / LIGHTNESS_MAX)); - if (err) { - LOG_ERR("pwm_set_dt, error:%d", err); - SEND_FATAL_ERROR(); - return; - } + k_work_reschedule(&led_pattern_update_work, K_NO_WAIT); } if (&ERROR_CHAN == chan) { - /* Red LED */ - const enum error_type *type = zbus_chan_const_msg(chan); if (*type == ERROR_FATAL) { - err = dk_set_led_on(DK_LED1); - if (err) { - LOG_ERR("dk_set_led_on, error:%d", err); - SEND_FATAL_ERROR(); - return; - } + transition_list_clear(); + transition_list_append(LED_ERROR_SYSTEM_FAULT, HOLD_FOREVER, 0, 0, 0); } } } diff --git a/app/src/modules/led/led.h b/app/src/modules/led/led.h new file mode 100644 index 00000000..d107a78d --- /dev/null +++ b/app/src/modules/led/led.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/**@file + * + * @brief LED module. + * + * Module that handles LED behaviour. + */ + +#ifndef LED_H__ +#define LED_H__ + +#include + +#include "led_effect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define LED_1 1 +#define LED_2 2 +#define LED_3 3 +#define LED_4 4 + +#define LED_ON(x) (x) +#define LED_BLINK(x) ((x) << 8) +#define LED_GET_ON(x) ((x)&0xFF) +#define LED_GET_BLINK(x) (((x) >> 8) & 0xFF) + +#define LED_ON_PERIOD_NORMAL 500 +#define LED_OFF_PERIOD_NORMAL 2000 +#define LED_ON_PERIOD_ERROR 500 +#define LED_OFF_PERIOD_ERROR 500 +#define LED_ON_PERIOD_SHORT 350 +#define LED_OFF_PERIOD_SHORT 350 +#define LED_ON_PERIOD_STROBE 50 +#define LED_OFF_PERIOD_STROBE 50 +#define LED_OFF_PERIOD_LONG 4000 + +#define LED_MAX 50 + +#define LED_COLOR_OFF LED_COLOR(0, 0, 0) +#define LED_COLOR_RED LED_COLOR(LED_MAX, 0, 0) +#define LED_COLOR_GREEN LED_COLOR(0, LED_MAX, 0) +#define LED_COLOR_BLUE LED_COLOR(0, 0, LED_MAX) +#define LED_COLOR_YELLOW LED_COLOR(LED_MAX, LED_MAX, 0) +#define LED_COLOR_CYAN LED_COLOR(0, LED_MAX, LED_MAX) +#define LED_COLOR_PURPLE LED_COLOR(LED_MAX, 0, LED_MAX) +#define LED_COLOR_WHITE LED_COLOR(LED_MAX, LED_MAX, LED_MAX) + +#define LED_LTE_CONNECTING_COLOR LED_COLOR_YELLOW +#define LED_POLL_MODE_COLOR LED_COLOR_BLUE +#define LED_ERROR_SYSTEM_FAULT_COLOR LED_COLOR_RED +#define LED_OFF_COLOR LED_COLOR_OFF + +/**@brief LED state pattern definitions. */ +enum led_state { + LED_LTE_CONNECTING, + LED_POLL_MODE, + LED_ERROR_SYSTEM_FAULT, + LED_CONFIGURED, + LED_OFF, + LED_PATTERN_COUNT, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* LED_H__ */ diff --git a/app/src/modules/led/led/CMakeLists.txt b/app/src/modules/led/led/CMakeLists.txt new file mode 100644 index 00000000..52534a69 --- /dev/null +++ b/app/src/modules/led/led/CMakeLists.txt @@ -0,0 +1,8 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +target_include_directories(app PRIVATE .) +target_sources_ifdef(CONFIG_LED_USE_PWM app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/led_pwm.c) diff --git a/app/src/modules/led/led_effect.h b/app/src/modules/led/led_effect.h new file mode 100644 index 00000000..2372f4ce --- /dev/null +++ b/app/src/modules/led/led_effect.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef LED_EFFECT_H__ +#define LED_EFFECT_H__ + +/** + * @brief LED Effect + * @defgroup led_effect LED Effect + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct led_color { + uint8_t c[3]; +}; + +struct led_effect_step { + struct led_color color; + uint16_t substep_cnt; + uint16_t substep_time; +}; + +struct led_effect { + struct led_effect_step *steps; + uint16_t step_cnt; + bool loop_forever; +}; + +#define LED_COLOR(_r, _g, _b) \ + { \ + .c = { _r, _g, _b } \ + } +#define LED_NOCOLOR() \ + { \ + .c = { 0, 0, 0 } \ + } + +#define LED_EFFECT_LED_ON(_color) \ + { \ + .steps = ((struct led_effect_step[]){ \ + { \ + .color = _color, \ + .substep_cnt = 1, \ + .substep_time = 0, \ + }, \ + }), \ + .step_cnt = 1, .loop_forever = false, \ + } + +#define LED_EFFECT_LED_OFF() LED_EFFECT_LED_ON(LED_NOCOLOR()) + +#define LED_EFFECT_LED_BLINK(_period, _color) \ + { \ + .steps = ((struct led_effect_step[]){ \ + { \ + .color = _color, \ + .substep_cnt = 1, \ + .substep_time = (_period), \ + }, \ + { \ + .color = LED_NOCOLOR(), \ + .substep_cnt = 1, \ + .substep_time = (_period), \ + }, \ + }), \ + .step_cnt = 2, .loop_forever = true, \ + } + +#define _BREATH_SUBSTEPS 15 +#define _BREATH_PAUSE_SUBSTEPS 1 +#define LED_EFFECT_LED_BREATHE(_period, _pause, _color) \ + { \ + .steps = ((struct led_effect_step[]){ \ + { \ + .color = _color, \ + .substep_cnt = _BREATH_SUBSTEPS, \ + .substep_time = \ + ((_period + (_BREATH_SUBSTEPS - 1)) / \ + _BREATH_SUBSTEPS), \ + }, \ + { \ + .color = _color, \ + .substep_cnt = 1, \ + .substep_time = _period, \ + }, \ + { \ + .color = LED_NOCOLOR(), \ + .substep_cnt = _BREATH_SUBSTEPS, \ + .substep_time = \ + ((_period + (_BREATH_SUBSTEPS - 1)) / \ + _BREATH_SUBSTEPS), \ + }, \ + { \ + .color = LED_NOCOLOR(), \ + .substep_cnt = _BREATH_PAUSE_SUBSTEPS, \ + .substep_time = _pause, \ + }, \ + }), \ + .step_cnt = 4, .loop_forever = true, \ + } + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* LED_EFFECT_H_ */ diff --git a/app/src/modules/led/led_pwm.c b/app/src/modules/led/led_pwm.c new file mode 100644 index 00000000..8cd4fe99 --- /dev/null +++ b/app/src/modules/led/led_pwm.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include "led.h" +#include "led_pwm.h" +#include "led_effect.h" + +LOG_MODULE_REGISTER(app_led_pwm, 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 + +struct led { + const struct device *pwm_dev; + + size_t id; + struct led_color color; + const struct led_effect *effect; + uint16_t effect_step; + uint16_t effect_substep; + + struct k_work_delayable work; + struct k_work_sync work_sync; +}; + +static const struct led_effect effect[] = { + [LED_LTE_CONNECTING] = LED_EFFECT_LED_BREATHE( + LED_ON_PERIOD_NORMAL, + LED_OFF_PERIOD_NORMAL, + LED_LTE_CONNECTING_COLOR + ), + [LED_POLL_MODE] = LED_EFFECT_LED_BREATHE( + LED_ON_PERIOD_SHORT, + LED_OFF_PERIOD_LONG, + LED_POLL_MODE_COLOR + ), + [LED_ERROR_SYSTEM_FAULT] = LED_EFFECT_LED_BREATHE( + LED_ON_PERIOD_ERROR, + LED_OFF_PERIOD_ERROR, + LED_ERROR_SYSTEM_FAULT_COLOR + ), + [LED_OFF] = LED_EFFECT_LED_BREATHE( + LED_ON_PERIOD_ERROR, + LED_OFF_PERIOD_ERROR, + LED_OFF_COLOR + ), +}; + +static struct led leds; + +static void pwm_out(const struct led_color *color) +{ + int err; + + /* RED */ + err = pwm_set_dt(&led0, PWM_USEC(128), PWM_USEC(color->c[0])); + if (err) { + LOG_ERR("pwm_set_dt, error:%d", err); + return; + } + + /* GREEN */ + err = pwm_set_dt(&led2, PWM_USEC(128), PWM_USEC(color->c[1])); + if (err) { + LOG_ERR("pwm_set_dt, error:%d", err); + return; + } + + /* BLUE */ + err = pwm_set_dt(&led1, PWM_USEC(128), PWM_USEC(color->c[2])); + if (err) { + LOG_ERR("pwm_set_dt, error:%d", err); + return; + } +} + +static void pwm_off(const struct led *led) +{ + ARG_UNUSED(led); + + struct led_color nocolor = { 0 }; + + pwm_out(&nocolor); +} + +static void work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + const struct led_effect_step *effect_step = + &leds.effect->steps[leds.effect_step]; + int substeps_left = effect_step->substep_cnt - leds.effect_substep; + + for (size_t i = 0; i < ARRAY_SIZE(leds.color.c); i++) { + int diff = (effect_step->color.c[i] - leds.color.c[i]) / + substeps_left; + leds.color.c[i] += diff; + } + + pwm_out(&leds.color); + + leds.effect_substep++; + if (leds.effect_substep == effect_step->substep_cnt) { + leds.effect_substep = 0; + leds.effect_step++; + + if (leds.effect_step == leds.effect->step_cnt) { + if (leds.effect->loop_forever) { + leds.effect_step = 0; + } + } else { + __ASSERT_NO_MSG(leds.effect->steps[leds.effect_step] + .substep_cnt > 0); + } + } + + if (leds.effect_step < leds.effect->step_cnt) { + int32_t next_delay = + leds.effect->steps[leds.effect_step].substep_time; + + k_work_reschedule(&leds.work, K_MSEC(next_delay)); + } +} + +static void led_update(struct led *led) +{ + k_work_cancel_delayable_sync(&led->work, &led->work_sync); + + led->effect_step = 0; + led->effect_substep = 0; + + if (!led->effect) { + LOG_DBG("No effect set"); + return; + } + + __ASSERT_NO_MSG(led->effect->steps); + + if (led->effect->step_cnt > 0) { + int32_t next_delay = + led->effect->steps[led->effect_step].substep_time; + + k_work_schedule(&led->work, K_MSEC(next_delay)); + } else { + LOG_DBG("LED effect with no effect"); + } +} + +void led_pwm_start(void) +{ + int err = pm_device_action_run(leds.pwm_dev, PM_DEVICE_ACTION_RESUME); + + if (err) { + LOG_ERR("PWM enable failed"); + return; + } + + led_update(&leds); +} + +void led_pwm_stop(void) +{ + k_work_cancel_delayable_sync(&leds.work, &leds.work_sync); + + int err = pm_device_action_run(leds.pwm_dev, PM_DEVICE_ACTION_SUSPEND); + + if (err) { + LOG_ERR("PWM enable failed"); + return; + } + + pwm_off(&leds); +} + +void led_pwm_set_effect(enum led_state state) +{ + leds.effect = &effect[state]; + led_update(&leds); +} + +int led_pwm_set_rgb(uint8_t red, uint8_t green, uint8_t blue) +{ + struct led_effect effect = LED_EFFECT_LED_ON(LED_COLOR(red, green, blue)); + + leds.effect = &effect; + led_update(&leds); + + return 0; +} + +static int led_pwm_init(void) +{ + k_work_init_delayable(&leds.work, work_handler); + + return 0; +} + +SYS_INIT(led_pwm_init, POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/modules/led/led_pwm.h b/app/src/modules/led/led_pwm.h new file mode 100644 index 00000000..12e8cbc6 --- /dev/null +++ b/app/src/modules/led/led_pwm.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/**@file + * + * @brief LED PWM control. + */ + +#ifndef LED_PWM_H__ +#define LED_PWM_H__ + +#include +#include "led.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/**@brief Starts the PWM and handling of LED effects. */ +void led_pwm_start(void); + +/**@brief Stops the LED effects. */ +void led_pwm_stop(void); + +/**@brief Sets LED effect based in UI LED state. */ +void led_pwm_set_effect(enum led_state state); + +/**@brief Sets RGB and light intensity values, in 0 - 255 ranges. */ +int led_pwm_set_rgb(uint8_t red, uint8_t green, uint8_t blue); + +#ifdef __cplusplus +} +#endif + +#endif /* LED_PWM_H__ */ diff --git a/app/src/modules/network/network.c b/app/src/modules/network/network.c index 5c147636..e9165fa5 100644 --- a/app/src/modules/network/network.c +++ b/app/src/modules/network/network.c @@ -76,33 +76,35 @@ static const struct smf_state states[] = { NULL), }; +static void network_status_notify(enum network_status status) +{ + int err; + + err = zbus_chan_pub(&NETWORK_CHAN, &status, K_SECONDS(1)); + if (err) { + LOG_ERR("zbus_chan_pub, error: %d", err); + SEND_FATAL_ERROR(); + return; + } +} static void l4_event_handler(struct net_mgmt_event_callback *cb, uint32_t event, struct net_if *iface) { - int err; - enum network_status status; - switch (event) { case NET_EVENT_L4_CONNECTED: LOG_INF("Network connectivity established"); - status = NETWORK_CONNECTED; + network_status_notify(NETWORK_CONNECTED); break; case NET_EVENT_L4_DISCONNECTED: LOG_INF("Network connectivity lost"); - status = NETWORK_DISCONNECTED; + network_status_notify(NETWORK_DISCONNECTED); break; default: /* Don't care */ return; } - - err = zbus_chan_pub(&NETWORK_CHAN, &status, K_SECONDS(1)); - if (err) { - LOG_ERR("zbus_chan_pub, error: %d", err); - SEND_FATAL_ERROR(); - } } static void connectivity_event_handler(struct net_mgmt_event_callback *cb, @@ -250,6 +252,8 @@ static void network_task(void) return; } + network_status_notify(NETWORK_DISCONNECTED); + if (IS_ENABLED(CONFIG_LTE_LINK_CONTROL)) { /* Subscribe to modem events */ err = lte_lc_modem_events_enable(); diff --git a/app/src/modules/trigger/trigger.c b/app/src/modules/trigger/trigger.c index 5c17edc4..1991789d 100644 --- a/app/src/modules/trigger/trigger.c +++ b/app/src/modules/trigger/trigger.c @@ -264,6 +264,16 @@ static void frequent_poll_entry(void *o) LOG_DBG("trigger poll work timeout: %lld seconds", user_object->update_interval_sec); LOG_DBG("trigger data sample work timeout: %lld seconds", user_object->update_interval_sec); + /* Send message on trigger mode channel */ + enum trigger_mode trigger_mode = TRIGGER_MODE_POLL; + + int err = zbus_chan_pub(&TRIGGER_MODE_CHAN, &trigger_mode, K_SECONDS(1)); + if (err) { + LOG_ERR("zbus_chan_pub, error: %d", err); + SEND_FATAL_ERROR(); + return; + } + refresh_timers(); } @@ -310,6 +320,16 @@ static void normal_entry(void *o) LOG_DBG("trigger poll work timeout: %lld seconds", user_object->update_interval_sec); LOG_DBG("trigger data sample work timeout: %lld seconds", user_object->update_interval_sec); + /* Send message on trigger mode channel */ + enum trigger_mode trigger_mode = TRIGGER_MODE_NORMAL; + + int err = zbus_chan_pub(&TRIGGER_MODE_CHAN, &trigger_mode, K_SECONDS(1)); + if (err) { + LOG_ERR("zbus_chan_pub, error: %d", err); + SEND_FATAL_ERROR(); + return; + } + user_object->poll_interval_sec = user_object->update_interval_sec; refresh_timers(); }