Architecture
The sample has a modular structure, where each module has a defined scope of responsibility. The communication between modules is handled by the Zephyr bus (zbus) using messages that are passed over channels. If a module has internal state handling, it is implemented using the Zephyr State Machine Framework. The following figure illustrates the relationship between modules, channels, and network stacks in the sample:
Definitions and payloads of the channel are owned by the system and placed in a common folder that is included by all modules.
See the src/common/channel.c
and src/common/channel.h
files for more details.
Note
The sample does not include a main.c
file.
To follow the typical flow of the application, see the Sequence diagram section.
Modules
The Module list tables lists all the modules in the sample together with information on each module’s channel handling and general behavior. A common feature for almost all the modules in the sample is that they each have a dedicated thread. The thread is used to initialize functionality specific to each module, and to process incoming messages in case the module is set up as a subscriber.
Subscribers use its thread primarily to monitor and process incoming messages from other modules by continuously polling on zbus_sub_wait()
.
When a channel that a module subscribes to, is invoked, the subscriber will handle the incoming message depending on its content.
The following code snippet shows how a module thread polls for incoming messages on a subscribed channel:
static void sampler_task(void)
{
const struct zbus_channel *chan;
while (!zbus_sub_wait(&sampler, &chan, K_FOREVER)) {
if (&TRIGGER_CHAN == chan) {
sample();
}
}
}
K_THREAD_DEFINE(sampler_task_id,
CONFIG_MQTT_SAMPLE_SAMPLER_THREAD_STACK_SIZE,
sampler_task, NULL, NULL, NULL, 3, 0, 0);
Note
Zbus implements internal message queues for subscribers.
In some cases, depending on the use case, it might be necessary to increase the queue size for a particular subscriber.
Especially if the module thread can block for some time.
To increase the message queue associated with a subscriber, increase the value of the corresponding Kconfig option, CONFIG_MQTT_SAMPLE_<MODULE_NAME>_MESSAGE_QUEUE_SIZE
.
Modules that are setup as listeners have dedicated callbacks that are invoked every time there is a change to an observing channel.
The difference between a listener and a subscriber is that listeners do not require a dedicated thread to process incoming messages.
The callbacks are called in the context of the thread that published the message.
The following code snippet shows how a listener is setup in order to listen to changes to the NETWORK
channel:
void led_callback(const struct zbus_channel *chan)
{
int err = 0;
const enum network_status *status;
if (&NETWORK_CHAN == chan) {
/* Get network status from channel. */
status = zbus_chan_const_msg(chan);
switch (*status) {
case NETWORK_CONNECTED:
err = led_on(led_device, LED_1_GREEN);
if (err) {
LOG_ERR("led_on, error: %d", err);
}
break;
case NETWORK_DISCONNECTED:
err = led_off(led_device, LED_1_GREEN);
if (err) {
LOG_ERR("led_off, error: %d", err);
}
break;
default:
LOG_ERR("Unknown event: %d", *status);
break;
}
}
}
ZBUS_LISTENER_DEFINE(led, led_callback);
A module publishes a message to a channel by calling the zbus_chan_pub()
function.
The following code snippet shows how this is typically carried out throughout the sample:
int err;
struct payload payload = "Some payload";
err = zbus_chan_pub(&PAYLOAD_CHAN, &payload, K_SECONDS(1));
if (err) {
LOG_ERR("zbus_chan_pub, error: %d", err);
}
Module name |
Observes channel |
Subscriber / Listener |
Description |
---|---|---|---|
Trigger |
None |
Sends messages on the trigger channel at an interval set by the CONFIG_MQTT_SAMPLE_TRIGGER_TIMEOUT_SECONDS and upon a button press. |
|
Sampler |
Trigger |
Subscriber |
Samples data every time a message is received on the trigger channel. The sampled payload is sent on the payload channel. |
Transport |
Network Payload |
Subscriber |
Handles MQTT connection. Will auto connect and keep the MQTT connection alive as long as the network is available. Receives network status messages on the network channel. Publishes messages received on the payload channel to a configured MQTT topic. |
Network |
None |
Auto connects to either Wi-Fi or LTE after boot, depending on the board and the sample configuration. Sends network status messages on the network channel. |
|
LED |
Network |
Listener |
Listens to changes in the network status received on the network channel. Displays LED pattern accordingly. If network is connected, LED 1 on the board will light up. On Thingy:91 and Thingy:91 X, the LED turns green |
Error |
Fatal error |
Listener |
Listens to messages sent on the fatal error channel. If a message is received on the fatal error channel, the default behavior is to reboot the device. |
Channels
Name |
Channel payload |
Payload description |
---|---|---|
Trigger channel |
None |
|
Network channel |
network status |
Enumerator. Signifies if the network is connected or not.
Can be either |
Payload channel |
string |
String buffer that contains a message that is sent to the MQTT broker. |
Fatal error channel |
None |
States
Currently, only the sample’s transport module implements state handling.
Transport module
The following figure explains the state transitions of the transport module:
Sequence diagram
The following sequence diagram illustrates the most significant chain of events during normal operation of the sample: