Extending the App
This exercise extends the existing application with sensor functionality. Three new components are added, each developed and tested independently by a separate group.
- temp_alert
Listen to sensor readings, publish alert on threshold. Pure logic, no hardware, no threads.
- tempsense
Read sensor via Zephyr Sensor API, publish periodically, react to system state. Uses
k_work_delayableand sensor driver.- sensor_log
Subscribe to two channels, store entries in ring buffer, expose via shell commands. Ring buffer and shell registration are provided in the stub.
The Scenario: Cold-Chain Monitoring
The application becomes a cold-chain monitoring device for a refrigerated transport truck. These devices are common in the food and pharmaceutical industry. They track the temperature inside the cargo area during transport and raise an alarm if it gets too warm. A tamper-proof log of all readings serves as proof that the cold chain was never broken — a legal requirement for food safety.
The existing application already provides the foundation:
Button: Starts and stops a transport trip (toggles SLEEP/ACTIVE)
LED: Shows system state on
led0(blinks in ACTIVE, off in SLEEP), flashesled1on each sensor reading as activity indicatorsys_ctrl: Manages the system state (SLEEP or ACTIVE)
What is missing are the three components that turn this into a monitoring device.
Architecture
Two ZBus channels connect all components:
event_chcarries events: button presses, sensor readings, temperature alerts. The message is astruct event_msgwith an event type and an optional payload (e.g. sensor data).sys_ctl_chcarries the current system state (enum sys_states:SYS_SLEEPorSYS_ACTIVE). Published by sys_ctrl whenever the state changes.
event_ch sys_ctl_ch
│ ┌────────────┐ │
│◀─── PRESSED ──│ Button │ │
│ └────────────┘ │
│ ┌────────────┐ │
│──── PRESSED ─▶│ sys_ctrl │── ACTIVE/SLEEP ─▶│
│ └────────────┘ │
│ ┌────────────┐ │
│─ TEMP/ALERT ─▶│ LED │◀─ ACTIVE/SLEEP ──│
│ └────────────┘ │
│ ----------- New Components ------------ │
│ ┌────────────┐ │
│◀──── TEMP ────│ tempsense │◀─ ACTIVE/SLEEP ──│
│ └────────────┘ │
│ ┌────────────┐ │
│◀─── ALERT ────│ temp_alert │ │
│──── TEMP ────▶│ │ │
│ └────────────┘ │
│ ┌────────────┐ │
│─ TEMP/ALERT ─▶│ sensor_log │◀─ ACTIVE/SLEEP ──│
└────────────┘
Hardware
Two LEDs are available via devicetree aliases:
led0— system state indicator (blinks in ACTIVE, off in SLEEP)led1— sensor activity indicator (50 ms flash on each measurement)
Both are driven by the LED component and emulated as GPIOs on native_sim.
Most hardware boards provide at least two LEDs.
Message Definitions
The event channel carries a generic message struct. The event type determines which fields in the payload are valid:
enum sys_events {
SYS_BUTTON_PRESSED,
SYS_SENSOR_READING,
SYS_TEMP_ALERT,
};
struct sensor_data {
int32_t temp; /* Temperature in 0.01 °C */
};
struct event_msg {
enum sys_events event;
union {
struct sensor_data sensor;
};
};
The state channel carries a simple enum:
enum sys_states {
SYS_SLEEP,
SYS_ACTIVE,
};
New Components
Group 1: tempsense — The Sensor Driver
Reads the temperature from the sensor and publishes readings to event_ch
every 1 s while the system is in ACTIVE.
During operation (ACTIVE), the device measures continuously. When idle (SLEEP), it pauses sampling to save battery — these devices run on battery for days or weeks.
What to implement:
Use the Zephyr Sensor API (see 05 Sensor API) (
sensor_sample_fetch,sensor_channel_get) to read the emulated TI HDC sensorUse
k_work_delayablefor periodic 1 s measurementSubscribe to
sys_ctl_ch: start measuring in ACTIVE, stop in SLEEPPublish a
SYS_SENSOR_READINGevent with the temperature toevent_chafter each measurement
Group 2: temp_alert — The Alarm
Watches sensor readings on event_ch. When the temperature exceeds 5 °C for
two consecutive readings, it publishes a SYS_TEMP_ALERT event. After that,
one alert is published for every further reading that still exceeds the
threshold.
This automatically triggers an alarm in the led component (blink led for 3 s) for demonstration. In a real device, this would activate a buzzer or send a notification to a fleet management system. In addition the alert is recorded by sensor_log.
What to implement:
Subscribe to
event_ch(ZBus listener), filter forSYS_SENSOR_READINGTrack consecutive readings above the threshold (configurable via
CONFIG_TEMP_ALERT_THRESHOLD, default 5 °C)After 2 consecutive readings above the threshold, publish
SYS_TEMP_ALERTtoevent_chContinue publishing one
SYS_TEMP_ALERTper reading as long as temperature stays above the thresholdReset the counter when temperature drops back below the threshold
Group 3: sensor_log — The Compliance Record
Records all events and state changes with timestamps. Provides shell commands to inspect the log.
This is the component that makes the device useful for regulatory compliance. After a transport trip, an inspector connects to the device and retrieves the temperature log to verify the cold chain was maintained throughout.
What to implement:
Subscribe to
event_ch(record sensor readings and alerts)Subscribe to
sys_ctl_ch(record state changes)Store entries in a ring buffer with uptime timestamp (
k_uptime_get())Provide shell commands:
sensor_log last— print the most recent entrysensor_log history— print all buffered entries with timestamps
Buffer size configurable via Kconfig (
CONFIG_SENSOR_LOG_BUFFER_SIZE)
Example shell output:
uart:~$ sensor_log history
[00:00:01.000] STATE ACTIVE
[00:00:02.000] SENSOR temp=3.20 °C
[00:00:03.000] SENSOR temp=3.25 °C
[00:00:04.000] SENSOR temp=5.80 °C
[00:00:05.000] SENSOR temp=6.10 °C
[00:00:05.000] ALERT temp=6.10 °C
[00:00:06.000] SENSOR temp=6.30 °C
[00:00:06.000] ALERT temp=6.30 °C
[00:00:10.000] STATE SLEEP
- Note:
In a real device, events would be saved in a flash storage not in memory. Zephyr offers the right tools for this (filesystems, flash partition, settings subsys etc.). However, in this example keeping the log in memory is ok.
How a Trip Works
When all components are active, a typical trip looks like this:
The device is idle (SLEEP). Both LEDs are off. No sensor readings.
The driver presses the button to start the trip. sys_ctrl transitions to ACTIVE and broadcasts the new state on
sys_ctl_ch.The LED component receives ACTIVE:
led0starts blinking.tempsense receives ACTIVE, starts measuring every 1 s, publishes
SYS_SENSOR_READINGevents toevent_ch. The LED component flashesled1for 50 ms on each reading as a visual heartbeat.sensor_log records every reading with a timestamp.
temp_alert watches the readings. If someone left the truck door open and temperature rises above 5 °C for two consecutive readings, it publishes
SYS_TEMP_ALERT. sensor_log records the alert.The driver presses the button again. sys_ctrl transitions to SLEEP. tempsense stops measuring. Both LEDs go off.
An inspector runs
sensor_log historyvia the shell to verify the cold chain. The log shows all readings, alerts, and state transitions with timestamps.
What Is Already Provided
The following is prepared and does not need to be changed:
All 3 components (
app/src/components/), but incompleteshell interfaces inside each component (manual testing and introspection)
Tests for all (e.g.
app/src/components/tempsense/test). Check below on how to build and run the tests.message_channel declarations (
app/src/common/message_channel.h)Emulated TI HDC sensor on I2C, two LEDs, button (
native_sim.overlay)By default the new components in
app/prj.confare deactivated
Overview of relevant parts:
app
├── prj.conf
├── src
│ ├── common
│ │ └── message_channel.h
│ ├── components
│ │ ├── sensor_log
│ │ │ └── tests
│ │ ├── temp_alert
│ │ │ └── tests
│ │ ├── tempsense
│ │ │ └── tests
│ └── main.c
└── test_cfg
├── sensor_log.conf
├── temp_alert.conf
└── tempsense.conf
Development Workflow
Each group works independently on their component:
Read the component stub and the test to understand what is expected
Run the test — it will fail:
host:~$ west build -b native_sim app/src/components/tempsense/tests -p or host:~$ west build -b native_sim app/src/components/temp_alert/tests -p or host:~$ west build -b native_sim app/src/components/sensor_log/tests -p Execute the build on native_sim: host:~$ west build -t run
Implement the component to make the tests pass
Run the test again — it should pass now
- Tip:
it is sufficient to run
west build -t runafter an initial build, if only .c/.h files have been changed.
Enable the component in
prj.confand build the full applicationVerify that everything works end-to-end. You can do that by building and probing the isolated modules with shell commands:
host:~$ west build -b native_sim app -p -- -DCONF_FILE=test_cfg/tempsense.conf or host:~$ west build -b native_sim app -p -- -DCONF_FILE=test_cfg/temp_alert.conf or host:~$ west build -b native_sim app -p -- -DCONF_FILE=test_cfg/sensor_log.conf Execute the build on native_sim: host:~$ west build -t run Open the console in another terminal (number in the boot-log): host:~$ tio dev/pts/<n> Example: uart:~$ tempsense read Temperature: 4.059753 C
This follows a test-driven development (TDD) approach: the tests define the expected behavior.
Integration
Once all groups have their tests passing, the results are merged together:
Each group can open a pull request to the zephyr-workshop repository
CI runs all component tests automatically
If tests are green, the PR is merged
Once 3 modules are merged, the cold chain monitor should be ready and working
Since each group only touches their own component directory and one line in
prj.conf, merge conflicts should be minimal. After all three PRs are merged,
build and run the full application for native_sim and for the reel_board to see
all components working together.
Running Tests
# Single component test (during development, faster)
host:~$ west build -b native_sim app/src/components/tempsense/tests -p
host:~$ west build -t run
# If only .c/.h files changed, just re-run (incremental build):
host:~$ west build -t run
# Build a component in isolation for interacting via shell (without the ZTest setup)
host:~$ west build -b native_sim app -p -- -DCONF_FILE=test_cfg/tempsense.conf
host:~$ west build -t run
# Single component test via Twister
host:~$ west twister -T app/src/components/tempsense/tests --integration -p native_sim
# All component tests
host:~$ west twister -T app/src/components/ --integration -p native_sim
# Full application with all components
host:~$ west build -b native_sim app -p
host:~$ west build -t run