To add your own module, complete the following steps:
-
Create the module directory structure:
mkdir -p app/src/modules/dummy -
Create the following files in the
app/src/modules/dummydirectory:app/src/modules/dummy/dummy.h- Module interface definitions.app/src/modules/dummy/dummy.c- Module implementation.app/src/modules/dummy/Kconfig.dummy- Module configuration options.app/src/modules/dummy/CMakeLists.txt- Build system configuration.
An optional
dummy_shell.cfile is added later in the Add shell support section. -
In
app/src/modules/dummy/dummy.h, define the module's interface:#ifndef _DUMMY_H_ #define _DUMMY_H_ #include <zephyr/kernel.h> #include <zephyr/zbus/zbus.h> #ifdef __cplusplus extern "C" { #endif /* Module's zbus channel */ ZBUS_CHAN_DECLARE(dummy_chan); /* Module message types */ enum dummy_msg_type { /* Output message types */ DUMMY_SAMPLE_RESPONSE = 0x1, /* Input message types */ DUMMY_SAMPLE_REQUEST, }; /* Module message structure */ struct dummy_msg { enum dummy_msg_type type; int32_t value; }; #ifdef __cplusplus } #endif #endif /* _DUMMY_H_ */ -
In
app/src/modules/dummy/dummy.c, implement the module's functionality:#include <zephyr/kernel.h> #include <zephyr/logging/log.h> #include <zephyr/zbus/zbus.h> #include <zephyr/task_wdt/task_wdt.h> #include <zephyr/smf.h> #include "app_common.h" #include "dummy.h" /* Register log module */ LOG_MODULE_REGISTER(dummy_module, CONFIG_APP_DUMMY_LOG_LEVEL); /* Define module's zbus channel */ ZBUS_CHAN_DEFINE(dummy_chan, struct dummy_msg, NULL, NULL, ZBUS_OBSERVERS_EMPTY, ZBUS_MSG_INIT(0) ); /* Register zbus subscriber */ ZBUS_MSG_SUBSCRIBER_DEFINE(dummy); /* Add subscriber to channel */ ZBUS_CHAN_ADD_OBS(dummy_chan, dummy, 0); #define MAX_MSG_SIZE sizeof(struct dummy_msg) BUILD_ASSERT(CONFIG_APP_DUMMY_WATCHDOG_TIMEOUT_SECONDS > CONFIG_APP_DUMMY_MSG_PROCESSING_TIMEOUT_SECONDS, "Watchdog timeout must be greater than maximum message processing time"); /* State machine states */ enum dummy_module_state { STATE_RUNNING, }; /* Module state structure */ struct dummy_state { /* State machine context (must be first) */ struct smf_ctx ctx; /* Last received zbus channel */ const struct zbus_channel *chan; /* Message buffer */ uint8_t msg_buf[MAX_MSG_SIZE]; /* Current counter value */ int32_t current_value; }; /* Forward declarations */ static enum smf_state_result state_running_run(void *o); /* State machine definition */ static const struct smf_state states[] = { [STATE_RUNNING] = SMF_CREATE_STATE(NULL, state_running_run, NULL, NULL, NULL), }; /* Watchdog callback */ static void task_wdt_callback(int channel_id, void *user_data) { LOG_ERR("Watchdog expired, Channel: %d, Thread: %s", channel_id, k_thread_name_get((k_tid_t)user_data)); SEND_FATAL_ERROR_WATCHDOG_TIMEOUT(); } /* State machine handlers */ static enum smf_state_result state_running_run(void *obj) { struct dummy_state *state_object = obj; if (&dummy_chan == state_object->chan) { const struct dummy_msg *msg = (const struct dummy_msg *)state_object->msg_buf; if (msg->type == DUMMY_SAMPLE_REQUEST) { LOG_DBG("Received sample request"); state_object->current_value++; struct dummy_msg response = { .type = DUMMY_SAMPLE_RESPONSE, .value = state_object->current_value, }; int err = zbus_chan_pub(&dummy_chan, &response, PUB_TIMEOUT); if (err) { LOG_ERR("Failed to publish response: %d", err); SEND_FATAL_ERROR(); return SMF_EVENT_HANDLED; } return SMF_EVENT_HANDLED; } } return SMF_EVENT_PROPAGATE; } /* Module task function */ static void dummy_task(void) { int err; int task_wdt_id; const uint32_t wdt_timeout_ms = (CONFIG_APP_DUMMY_WATCHDOG_TIMEOUT_SECONDS * MSEC_PER_SEC); const uint32_t execution_time_ms = (CONFIG_APP_DUMMY_MSG_PROCESSING_TIMEOUT_SECONDS * MSEC_PER_SEC); const k_timeout_t zbus_wait_ms = K_MSEC(wdt_timeout_ms - execution_time_ms); struct dummy_state dummy_state = { .current_value = 0 }; LOG_DBG("Starting dummy module task"); task_wdt_id = task_wdt_add(wdt_timeout_ms, task_wdt_callback, (void *)k_current_get()); smf_set_initial(SMF_CTX(&dummy_state), &states[STATE_RUNNING]); while (true) { err = task_wdt_feed(task_wdt_id); if (err) { LOG_ERR("Failed to feed watchdog: %d", err); SEND_FATAL_ERROR(); return; } err = zbus_sub_wait_msg(&dummy, &dummy_state.chan, dummy_state.msg_buf, zbus_wait_ms); if (err == -ENOMSG) { continue; } else if (err) { LOG_ERR("Failed to wait for message: %d", err); SEND_FATAL_ERROR(); return; } err = smf_run_state(SMF_CTX(&dummy_state)); if (err) { LOG_ERR("Failed to run state machine: %d", err); SEND_FATAL_ERROR(); return; } } } /* Define module thread */ K_THREAD_DEFINE(dummy_task_id, CONFIG_APP_DUMMY_THREAD_STACK_SIZE, dummy_task, NULL, NULL, NULL, K_LOWEST_APPLICATION_THREAD_PRIO, 0, 0); -
In
app/src/modules/dummy/Kconfig.dummy, define module configuration options:menuconfig APP_DUMMY bool "Dummy module" default y help Enable the dummy module. if APP_DUMMY config APP_DUMMY_THREAD_STACK_SIZE int "Dummy module thread stack size" default 2048 help Stack size for the dummy module thread. config APP_DUMMY_WATCHDOG_TIMEOUT_SECONDS int "Dummy module watchdog timeout in seconds" default 30 help Watchdog timeout for the dummy module. config APP_DUMMY_MSG_PROCESSING_TIMEOUT_SECONDS int "Dummy module message processing timeout in seconds" default 5 help Maximum time allowed for processing a single message in the dummy module. module = APP_DUMMY module-str = DUMMY source "subsys/logging/Kconfig.template.log_config" endif # APP_DUMMY -
In
app/src/modules/dummy/CMakeLists.txt, configure the build system to include the source files of the module:target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/dummy.c) target_include_directories(app PRIVATE .) -
Register the module directory in the
app/CMakeLists.txtfile. Useadd_subdirectory_ifdefso the module is only compiled whenCONFIG_APP_DUMMYis enabled, matching the pattern used by the other optional modules:add_subdirectory_ifdef(CONFIG_APP_DUMMY src/modules/dummy) -
Add the module's Kconfig file to the
app/Kconfigfile:rsource "src/modules/dummy/Kconfig.dummy" -
Increase the value of the
CONFIG_TASK_WDT_CHANNELSKconfig option in theapp/prj.conffile by1to accommodate for the new module's task watchdog integration.
The dummy module is now ready to use. It provides the following functionality:
- Initializes with a counter value of
0. - Increments the counter on each sample request.
- Responds with the current counter value over zbus.
- Includes error handling and watchdog support.
- Follows the state machine pattern used by the other modules.
To trigger the module from C code, publish a DUMMY_SAMPLE_REQUEST to dummy_chan:
struct dummy_msg req = { .type = DUMMY_SAMPLE_REQUEST };
err = zbus_chan_pub(&dummy_chan, &req, PUB_TIMEOUT);
if (err) {
LOG_ERR("Failed to request dummy sample: %d", err);
}
For an interactive way to drive the module during development, add a shell command as described in the next section.