From 42ba19ac9c5cc00fb66eef210f295d5c22082744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simen=20S=2E=20R=C3=B8stad?= Date: Thu, 6 Jun 2024 13:09:11 +0200 Subject: [PATCH] modules: trigger: Add SMF support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SMF support Signed-off-by: Simen S. Røstad --- app/prj.conf | 2 + app/src/common/message_channel.c | 8 + app/src/common/message_channel.h | 3 +- app/src/modules/trigger/trigger.c | 318 ++++++++++++++++++++++++++---- 4 files changed, 295 insertions(+), 36 deletions(-) diff --git a/app/prj.conf b/app/prj.conf index d728e51d..d83174af 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -116,6 +116,8 @@ CONFIG_ZBUS=y CONFIG_ZBUS_MSG_SUBSCRIBER=y CONFIG_ZBUS_MSG_SUBSCRIBER_NET_BUF_POOL_SIZE=32 CONFIG_SMF=y +CONFIG_SMF_ANCESTOR_SUPPORT=y +CONFIG_SMF_INITIAL_TRANSITION=y # Location CONFIG_LOCATION=y diff --git a/app/src/common/message_channel.c b/app/src/common/message_channel.c index af333bc6..b61492db 100644 --- a/app/src/common/message_channel.c +++ b/app/src/common/message_channel.c @@ -65,3 +65,11 @@ ZBUS_CHAN_DEFINE(CLOUD_CHAN, ZBUS_OBSERVERS(fota, app, location, trigger), CLOUD_DISCONNECTED ); + +ZBUS_CHAN_DEFINE(BUTTON_CHAN, + uint8_t, + NULL, + NULL, + ZBUS_OBSERVERS(trigger), + ZBUS_MSG_INIT(0) +); diff --git a/app/src/common/message_channel.h b/app/src/common/message_channel.h index c8ad3eda..22095ebb 100644 --- a/app/src/common/message_channel.h +++ b/app/src/common/message_channel.h @@ -60,7 +60,8 @@ ZBUS_CHAN_DECLARE( LED_CHAN, CLOUD_CHAN, FOTA_ONGOING_CHAN, - CONFIG_CHAN + CONFIG_CHAN, + BUTTON_CHAN ); #ifdef __cplusplus diff --git a/app/src/modules/trigger/trigger.c b/app/src/modules/trigger/trigger.c index 232b23a7..9fd266e3 100644 --- a/app/src/modules/trigger/trigger.c +++ b/app/src/modules/trigger/trigger.c @@ -9,64 +9,306 @@ #include #include #include +#include #include "message_channel.h" /* Register log module */ LOG_MODULE_REGISTER(trigger, CONFIG_APP_TRIGGER_LOG_LEVEL); -#define MSG_SEND_TIMEOUT_SECONDS 1 +/* Trigger interval in real-time mode */ +#define REAL_TIME_TRIGGER_INTERVAL_SEC 10 + +/* Duration in which the trigger modules sends triggers every 10 seconds */ +#define REAL_TIME_DURATION_INTERVAL_SEC 600 /* Forward declarations */ static void trigger_work_fn(struct k_work *work); +static const struct smf_state states[]; /* Delayable work used to schedule triggers. */ static K_WORK_DELAYABLE_DEFINE(trigger_work, trigger_work_fn); -/* Set default update interval */ -k_timeout_t update_interval = K_SECONDS(CONFIG_APP_TRIGGER_TIMEOUT_SECONDS); +/* Timer used to exit real-time mode after 10 minutes */ +static void trigger_timeout_handler(struct k_timer * timer_id); +static K_TIMER_DEFINE(rtm_timer, trigger_timeout_handler, NULL); -static void trigger_work_fn(struct k_work *work) +enum state { + STATE_INIT, + STATE_CONNECTED, + STATE_REAL_TIME_MODE, + STATE_NORMAL_MODE, + STATE_DISCONNECTED +}; + +/* Private channel used to signal expired real-time mode timer */ +ZBUS_CHAN_DECLARE(PRIV_TIMER_CHAN); +ZBUS_CHAN_DEFINE(PRIV_TIMER_CHAN, + int, /* Unused */ + NULL, + NULL, + ZBUS_OBSERVERS(trigger), + ZBUS_MSG_INIT(0) +); + +/* User defined state object. + * Used to transfer data between state changes. + */ +static struct s_object { + /* This must be first */ + struct smf_ctx ctx; + + /* Last channel type that a message was received on */ + const struct zbus_channel *chan; + + /* Set default update interval */ + uint64_t update_interval_sec; + + /* Set current update interval */ + uint64_t update_interval_current_sec; + + /* Button number */ + uint8_t button_number; + + /* Cloud status */ + enum cloud_status status; + + /* Time in real-time mode */ + int64_t time_in_real_time_mode_ms; + +} state_object; + +static void trigger_timeout_handler(struct k_timer * timer_id) { + ARG_UNUSED(timer_id); + + int unused = 0; int err; - int not_used = -1; - bool fota_ongoing = true; - err = zbus_chan_read(&FOTA_ONGOING_CHAN, &fota_ongoing, K_NO_WAIT); + LOG_DBG("Exiting real-time mode"); + + err = zbus_chan_pub(&PRIV_TIMER_CHAN, &unused, K_NO_WAIT); if (err) { - LOG_ERR("zbus_chan_read, error: %d", err); + LOG_ERR("zbus_chan_pub, error: %d", err); SEND_FATAL_ERROR(); return; } +} - if (fota_ongoing) { - LOG_DBG("FOTA ongoing, skipping trigger message"); - return; - } +/* Delayed work used to schedule triggers */ +static void trigger_work_fn(struct k_work *work) +{ + int err; + int not_used = -1; LOG_DBG("Sending trigger message"); - err = zbus_chan_pub(&TRIGGER_CHAN, ¬_used, K_SECONDS(MSG_SEND_TIMEOUT_SECONDS)); + err = zbus_chan_pub(&TRIGGER_CHAN, ¬_used, K_SECONDS(1)); if (err) { LOG_ERR("zbus_chan_pub, error: %d", err); SEND_FATAL_ERROR(); return; } - k_work_reschedule(&trigger_work, update_interval); + k_work_reschedule(&trigger_work, K_SECONDS(state_object.update_interval_current_sec)); } +/* Button handler that can be used to send asyncronous triggers */ static void button_handler(uint32_t button_states, uint32_t has_changed) { - if (has_changed & button_states) { - trigger_work_fn(NULL); + int err; + uint8_t button_number = 1; + + if (has_changed & button_states & DK_BTN1_MSK) { + LOG_DBG("Button 1 pressed!"); + + err = zbus_chan_pub(&BUTTON_CHAN, &button_number, K_SECONDS(1)); + if (err) { + LOG_ERR("zbus_chan_pub, error: %d", err); + SEND_FATAL_ERROR(); + return; + } + } +} + +/* Zephyr State Machine framework handlers */ + +/* HSM states: + * + * STATE_INIT: Initializing module + * STATE_CONNECTED: Connected to cloud. Sending triggers on timers and button presses + * - STATE_REAL_TIME_MODE: Sending triggers every 10 seconds for 10 minutes + * - STATE_NORMAL_MODE: Sending triggers every configured update interval + * STATE_DISCONNECTED: Sending of triggers are blocked + */ + +/* STATE_INIT */ + +static void init_entry(void *o) +{ + ARG_UNUSED(o); + + LOG_DBG("init_entry"); + + int err = dk_buttons_init(button_handler); + + if (err) { + LOG_ERR("dk_buttons_init, error: %d", err); + SEND_FATAL_ERROR(); + return; + } +} + +static void init_run(void *o) +{ + struct s_object *user_object = o; + + LOG_DBG("init_run"); + + if ((user_object->status == CLOUD_CONNECTED) && user_object->chan == &CLOUD_CHAN) { + smf_set_state(SMF_CTX(&state_object), &states[STATE_CONNECTED]); + return; + } +} + +/* STATE_CONNECTED */ + +static void connected_run(void *o) +{ + struct s_object *user_object = o; + + LOG_DBG("connected_run"); + + if ((user_object->status == CLOUD_DISCONNECTED) && user_object->chan == &CLOUD_CHAN) { + smf_set_state(SMF_CTX(&state_object), &states[STATE_DISCONNECTED]); + return; + } +} + +static void connected_exit(void *o) +{ + ARG_UNUSED(o); + + LOG_DBG("connected_exit"); + + LOG_DBG("Clearing timers"); + + k_work_cancel_delayable(&trigger_work); + k_timer_stop(&rtm_timer); +} + +/* STATE_REAL_TIME_MODE */ + +static void real_time_mode_init(void *o) +{ + struct s_object *user_object = o; + + LOG_DBG("ream_time_mode_init"); + + user_object->update_interval_current_sec = REAL_TIME_TRIGGER_INTERVAL_SEC; + + k_timer_start(&rtm_timer, K_SECONDS(REAL_TIME_DURATION_INTERVAL_SEC), K_NO_WAIT); + k_work_reschedule(&trigger_work, K_SECONDS(user_object->update_interval_current_sec)); +} + +static void real_time_mode_run(void *o) +{ + struct s_object *user_object = o; + + LOG_DBG("real_time_mode_run"); + + if (user_object->chan == &BUTTON_CHAN && user_object->chan == &CONFIG_CHAN) { + LOG_DBG("Button %d pressed in real-time mode, restarting RTM duration timer", + user_object->button_number); + + k_timer_start(&rtm_timer, K_SECONDS(REAL_TIME_DURATION_INTERVAL_SEC), K_NO_WAIT); + } else if (user_object->chan == &PRIV_TIMER_CHAN) { + LOG_DBG("RTM timer expired! going into normal mode"); + smf_set_state(SMF_CTX(&state_object), &states[STATE_NORMAL_MODE]); + return; + } +} + +/* STATE_NORMAL_MODE */ + +static void normal_mode_init(void *o) +{ + struct s_object *user_object = o; + + LOG_DBG("normal_mode_init"); + + /* Set update trigger work to the actual configured interval */ + user_object->update_interval_current_sec = user_object->update_interval_sec; + + k_work_reschedule(&trigger_work, K_SECONDS(user_object->update_interval_current_sec)); +} + +static void normal_mode_run(void *o) +{ + struct s_object *user_object = o; + + LOG_DBG("normal_mode_run"); + + if (user_object->chan == &BUTTON_CHAN && user_object->chan == &CONFIG_CHAN) { + LOG_DBG("Button %d pressed in normal mode, going into RTM", + user_object->button_number); + smf_set_state(SMF_CTX(&state_object), &states[STATE_REAL_TIME_MODE]); + } +} + +/* STATE_DISCONNECTED */ + +static void disconnected_run(void *o) +{ + ARG_UNUSED(o); + + LOG_DBG("disconnected_run"); + + struct s_object *user_object = o; + + if ((user_object->status == CLOUD_CONNECTED) && user_object->chan == &CLOUD_CHAN) { + smf_set_state(SMF_CTX(&state_object), &states[STATE_CONNECTED]); + return; } } +/* Construct state table */ +static const struct smf_state states[] = { + [STATE_INIT] = SMF_CREATE_STATE( + init_entry, init_run, NULL, NULL, NULL + ), + [STATE_CONNECTED] = SMF_CREATE_STATE( + NULL, connected_run, connected_exit, NULL, &states[STATE_REAL_TIME_MODE] + ), + [STATE_REAL_TIME_MODE] = SMF_CREATE_STATE( + real_time_mode_init, real_time_mode_run, NULL, &states[STATE_CONNECTED], NULL + ), + [STATE_NORMAL_MODE] = SMF_CREATE_STATE( + normal_mode_init, normal_mode_run, NULL, &states[STATE_CONNECTED], NULL + ), + [STATE_DISCONNECTED] = SMF_CREATE_STATE( + NULL, disconnected_run, NULL, NULL, NULL + ) +}; + +/* Function called when there is a message received on a channel that the module listens to */ void trigger_callback(const struct zbus_channel *chan) { + int err; + + if ((chan != &CONFIG_CHAN) && + (chan != &CLOUD_CHAN) && + (chan != &BUTTON_CHAN) && + (chan != &PRIV_TIMER_CHAN)) { + LOG_ERR("Unknown channel"); + return; + } + + /* Update the state object with the channel that the message was received on */ + state_object.chan = chan; + + /* Copy corresponding data to the state object depending on the incoming channel */ if (&CONFIG_CHAN == chan) { - /* Get update interval configuration from channel. */ const struct configuration *config = zbus_chan_const_msg(chan); if (config->config_present == false) { @@ -74,36 +316,42 @@ void trigger_callback(const struct zbus_channel *chan) return; } - LOG_DBG("New update interval: %lld", config->update_interval); + state_object.update_interval_sec = config->update_interval; + } - update_interval = K_SECONDS(config->update_interval); + if (&CLOUD_CHAN == chan) { + const enum cloud_status *status = zbus_chan_const_msg(chan); - /* Reschedule work */ - k_work_reschedule(&trigger_work, update_interval); + state_object.status = *status; } - if (&CLOUD_CHAN == chan) { - LOG_DBG("Cloud connection status received"); + if (&BUTTON_CHAN == chan) { + const int *button_number = zbus_chan_const_msg(chan); - const enum cloud_status *status = zbus_chan_const_msg(chan); + state_object.button_number = *button_number; + } - if (*status == CLOUD_CONNECTED) { - LOG_DBG("Cloud connected, starting trigger"); - k_work_reschedule(&trigger_work, update_interval); - } else { - LOG_DBG("Cloud disconnected, stopping trigger"); - k_work_cancel_delayable(&trigger_work); - } + /* State object updated, run SMF */ + err = smf_run_state(SMF_CTX(&state_object)); + if (err) { + LOG_ERR("smf_run_state, error: %d", err); + SEND_FATAL_ERROR(); + return; } } -ZBUS_LISTENER_DEFINE(trigger, trigger_callback); - static int trigger_init(void) { - __ASSERT((dk_buttons_init(button_handler) == 0), "dk_buttons_init failure"); + /* Set default update interval */ + state_object.update_interval_sec = CONFIG_APP_TRIGGER_TIMEOUT_SECONDS; + + smf_set_initial(SMF_CTX(&state_object), &states[STATE_INIT]); return 0; } -SYS_INIT(trigger_init, POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY); +/* Define a ZBUS listener for this module */ +ZBUS_LISTENER_DEFINE(trigger, trigger_callback); + +/* Initialize module at SYS_INIT() */ +SYS_INIT(trigger_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);