Most modules in the Asset Tracker Template have dedicated threads. If a module uses blocking calls while processing messages, a dedicated thread is required. For example, the Network module may react to a message by sending an AT command to the modem, which might be blocked until signaling with the network completes and a response is received. Separate threads also help to keep the required stack size for each module more predictable.
Each module's thread follows a similar pattern of waiting for new messages and handling them by executing a state machine, illustrated as follows:
For example, a simplified version of the network module's thread looks like this:
static void network_module_thread(void)
{
int err;
static struct network_state_object network_state;
/* Initialize the state machine to the initial state */
smf_set_initial(SMF_CTX(&network_state), &states[STATE_RUNNING]);
while (true) {
/* Wait for a message on any subscribed channel */
err = zbus_sub_wait_msg(&network, &network_state.chan,
network_state.msg_buf, K_FOREVER);
if (err == -ENOMSG) {
continue;
} else if (err) {
LOG_ERR("zbus_sub_wait_msg, error: %d", err);
return;
}
/* Run the state machine with the received message */
err = smf_run_state(SMF_CTX(&network_state));
if (err) {
LOG_ERR("smf_run_state(), error: %d", err);
return;
}
}
}
In this pattern:
- The thread waits for a message using
zbus_sub_wait_msg(), which blocks until a message is received. - When a message arrives, it is stored in the state object's buffer along with the channel it was received on.
- The state machine is then executed with
smf_run_state(), which calls the appropriate handler for the current state. - The handler processes the message based on its type and the current state, potentially triggering state transitions.
- The loop continues, waiting for the next message.