Instructions

Asset Tracker Template

tags
Asset Tracker Template

To add your own module, complete the following steps:

  1. Create the module directory structure:

    mkdir -p app/src/modules/dummy
    
  2. Create the following files in the app/src/modules/dummy directory:

    • 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.c file is added later in the Add shell support section.

  3. 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_ */
    
  4. 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);
    
  5. 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
    
  6. 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 .)
    
  7. Register the module directory in the app/CMakeLists.txt file. Use add_subdirectory_ifdef so the module is only compiled when CONFIG_APP_DUMMY is enabled, matching the pattern used by the other optional modules:

    add_subdirectory_ifdef(CONFIG_APP_DUMMY src/modules/dummy)
    
  8. Add the module's Kconfig file to the app/Kconfig file:

    rsource "src/modules/dummy/Kconfig.dummy"
    
  9. Increase the value of the CONFIG_TASK_WDT_CHANNELS Kconfig option in the app/prj.conf file by 1 to 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.