Skip to content

Commit 874a310

Browse files
committed
add support for MQTT
This commit adds support for MQTT in the cloud module. Signed-off-by: Simen S. Røstad <simen.rostad@nordicsemi.no>
1 parent 3ca27e0 commit 874a310

File tree

21 files changed

+1251
-14
lines changed

21 files changed

+1251
-14
lines changed

.github/actions/build-step/action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ runs:
5757
-d build \
5858
-p --sysbuild \
5959
-- -DEXTRA_CONF_FILE="overlay-debug-att.conf" 2>&1 | tee ../../artifacts/build_output_${{ inputs.short_board }}_debug.log
60+
elif [[ "${{ inputs.mqtt }}" == "true" ]]; then
61+
west build -b ${{ inputs.board }} \
62+
-d build \
63+
-p --sysbuild \
64+
-- -DEXTRA_CONF_FILE="$(PWD)/../examples/modules/cloud/overlay-mqtt.conf" 2>&1 | tee ../../artifacts/build_output_${{ inputs.short_board }}_mqtt.log
6065
else
6166
west build -b ${{ inputs.board }} \
6267
-d build \

.github/workflows/build.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,16 @@ jobs:
188188
version: ${{ env.VERSION }}-patched
189189
path: asset-tracker-template/app
190190

191+
# Asset Tracker Template firmware build with MQTT cloud moduel for Thingy91x
192+
- name: Build thingy91x firmware with MQTT cloud module
193+
if: ${{ github.event_name != 'pull_request' }}
194+
uses: ./asset-tracker-template/.github/actions/build-step
195+
with:
196+
board: thingy91x/nrf9151/ns
197+
short_board: thingy91x
198+
version: ${{ env.VERSION }}-mqtt
199+
path: asset-tracker-template/app
200+
191201
- name: Upload artifacts
192202
uses: actions/upload-artifact@v4
193203
with:

.github/workflows/target-test.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,27 @@ jobs:
206206
MEMFAULT_PROJECT_SLUG: ${{ vars.MEMFAULT_PROJECT_SLUG }}
207207
APP_BUNDLEID: ${{ env.APP_BUNDLEID }}
208208

209+
- name: Test MQTT Firmware
210+
if: ${{ matrix.device == 'thingy91x' && endsWith(inputs.artifact_fw_version, '-mqtt') }}
211+
working-directory: asset-tracker-template/tests/on_target
212+
run: |
213+
pytest -v \
214+
--junit-xml=results/test-results-mqtt.xml \
215+
--html=results/test-results-mqtt.html --self-contained-html \
216+
tests/test_functional/test_mqtt.py
217+
shell: bash
218+
env:
219+
SEGGER: ${{ env.RUNNER_SERIAL_NUMBER }}
220+
DUT_DEVICE_TYPE: ${{ vars.DUT_DEVICE_TYPE }}
221+
UUID: ${{ env.UUID }}
222+
NRFCLOUD_API_KEY: ${{ secrets.NRF_CLOUD_API_KEY }}
223+
LOG_FILENAME: att_test_log_mqtt
224+
TEST_REPORT_NAME: ATT MQTT Firwmare Test Report
225+
MEMFAULT_ORGANIZATION_TOKEN: ${{ secrets.MEMFAULT_ORGANIZATION_TOKEN }}
226+
MEMFAULT_ORGANIZATION_SLUG: ${{ vars.MEMFAULT_ORGANIZATION_SLUG }}
227+
MEMFAULT_PROJECT_SLUG: ${{ vars.MEMFAULT_PROJECT_SLUG }}
228+
APP_BUNDLEID: ${{ env.APP_BUNDLEID }}
229+
209230
- name: Generate and Push Power Badge
210231
if: ${{ matrix.device }} == ppk_thingy91x
211232
continue-on-error: true

CMakeLists.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,4 @@
44
# This CMake file is picked by the Zephyr build system because it is defined
55
# as the module CMake entry point (see zephyr/module.yml).
66

7-
8-
zephyr_include_directories(include)
9-
10-
add_subdirectory(drivers)
7+
add_subdirectory(examples/modules/cloud)

Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) 2025 Nordic Semiconductor ASA
2+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
3+
#
4+
# This Kconfig file is picked by the Zephyr build system because it is defined
5+
# as the module Kconfig entry point (see zephyr/module.yml). You can browse
6+
# module options by going to Zephyr -> Modules in Kconfig.
7+
8+
rsource "examples/modules/cloud/Kconfig.cloud_mqtt"

app/CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ target_include_directories(app PRIVATE src/common)
1515
# Add main application source
1616
target_sources(app PRIVATE src/main.c)
1717

18-
# Include mandatory module source folders
18+
# Module source folders
19+
add_subdirectory(src/modules/location)
20+
add_subdirectory(src/modules/cloud)
21+
add_subdirectory(src/modules/fota)
1922
add_subdirectory(src/modules/network)
2023
add_subdirectory(src/modules/button)
2124
add_subdirectory(src/cbor)
@@ -24,6 +27,3 @@ add_subdirectory(src/cbor)
2427
add_subdirectory_ifdef(CONFIG_APP_POWER src/modules/power)
2528
add_subdirectory_ifdef(CONFIG_APP_ENVIRONMENTAL src/modules/environmental)
2629
add_subdirectory_ifdef(CONFIG_APP_LED src/modules/led)
27-
add_subdirectory_ifdef(CONFIG_APP_LOCATION src/modules/location)
28-
add_subdirectory_ifdef(CONFIG_APP_CLOUD src/modules/cloud)
29-
add_subdirectory_ifdef(CONFIG_APP_FOTA src/modules/fota)

app/src/modules/cloud/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
55
#
66

7-
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cloud.c)
7+
target_sources_ifdef(CONFIG_APP_CLOUD app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cloud.c)
88
target_sources_ifdef(CONFIG_APP_CLOUD_SHELL app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cloud_shell.c)
99
target_include_directories(app PRIVATE .)
1010

app/src/modules/fota/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
55
#
66

7-
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/fota.c)
7+
target_sources_ifdef(CONFIG_APP_FOTA app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/fota.c)
88
target_include_directories(app PRIVATE .)

app/src/modules/location/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
55
#
66

7-
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/location.c)
7+
target_sources_ifdef(CONFIG_APP_LOCATION app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/location.c)
88
target_include_directories(app PRIVATE .)

docs/common/customization.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,3 +424,108 @@ The dummy module is now ready to use. It provides the following functionality:
424424
To test the module, send a `DUMMY_SAMPLE_REQUEST` message to its zbus channel. The module will respond with a `DUMMY_SAMPLE_RESPONSE` containing the incremented counter value.
425425

426426
This dummy module serves as a template that you can extend to implement more complex functionality. You can add additional message types, state variables, and processing logic as needed for your specific use case.
427+
428+
## Using a different Cloud
429+
430+
To connect to a generic MQTT server using the Asset Tracker Template, you can use the example cloud module provided under `examples/modules/cloud`. This module replaces the default nRF Cloud CoAP-based cloud integration with a flexible MQTT client implementation.
431+
432+
### Overview
433+
434+
- **Location and FOTA**: These features are deactivated when using the example MQTT module because they depend on nRF Cloud CoAP.
435+
- **FOTA and LOCATION channels**: The MQTT cloud module provides stub channel declarations for FOTA and LOCATION to avoid build errors. You can implement your own FOTA and LOCATION modules based on your chosen cloud service if needed.
436+
- **MQTT Client default configurations:**
437+
438+
- **Broker hostname:** mqtt.nordicsemi.academy
439+
- **Port:** 8883
440+
- **TLS:** Yes
441+
- **Authentication:** Server only
442+
- **CA:** modules/examples/cloud/creds/ca-cert.pem
443+
- **Device ID** IMEI
444+
- **Subscribed topic** imei/att-pub-topic
445+
- **Publishing toptic** imei/att-sub-topic
446+
447+
The default configuration does not require/enable mutual authentication meaning that the device does not authenticate itself to the server.
448+
That would require a device certificate/private key pair. However, the server is authenticated using the server certificate `ca-cert.pem`located in the module example folder.
449+
450+
### Configuration
451+
452+
Configurations for the MQTT stack can be set in the `overlay-mqtt.conf` file and Kconfig options defined in `examples/modules/cloud/Kconfig.cloud_mqtt`.
453+
Some of the available options for controlling the MQTT module are:
454+
455+
- `CONFIG_APP_CLOUD_MQTT`
456+
- `CONFIG_APP_CLOUD_MQTT_HOSTNAME`
457+
- `CONFIG_APP_CLOD_MQTT_TOPIC_SIZE_MAX`
458+
- `CONFIG_APP_CLOUD_MQTT_PUB_TOPIC`
459+
- `CONFIG_APP_CLOUD_MQTT_SUB_TOPIC`
460+
- `CONFIG_APP_CLOUD_MQTT_SEC_TAG`
461+
- `CONFIG_APP_CLOUD_MQTT_SHELL`
462+
- `CONFIG_APP_CLOUD_PAYLOAD_BUFFER_MAX_SIZE`
463+
- `CONFIG_APP_CLOUD_SHADOW_RESPONSE_BUFFER_MAX_SIZE`
464+
- `CONFIG_APP_CLOUD_BACKOFF_INITIAL_SECONDS`
465+
- `CONFIG_APP_CLOUD_BACKOFF_TYPE_LINEAR`
466+
- `CONFIG_APP_CLOUD_BACKOFF_TYPE_EXPONENTIAL`
467+
- `CONFIG_APP_CLOUD_BACKOFF_TYPE_NONE`
468+
- `CONFIG_APP_CLOUD_BACKOFF_LINEAR_INCREMENT_SECONDS`
469+
- `CONFIG_APP_CLOUD_BACKOFF_MAX_SECONDS`
470+
- `CONFIG_APP_CLOUD_THREAD_STACK_SIZE`
471+
- `CONFIG_APP_CLOUD_MESSAGE_QUEUE_SIZE`
472+
- `CONFIG_APP_CLOUD_WATCHDOG_TIMEOUT_SECONDS`
473+
- `CONFIG_APP_CLOUD_MSG_PROCESSING_TIMEOUT_SECONDS`
474+
475+
### How to use the MQTT Cloud Example
476+
477+
1. **Build and flash with the MQTT overlay**
478+
479+
In the template's `app` folder, run:
480+
481+
```sh
482+
west build -p -b thingy91x/nrf9151/ns -- -DEXTRA_CONF_FILE="$(PWD)/../examples/modules/cloud/overlay-mqtt.conf" && west flash --erase --skip-rebuild
483+
```
484+
485+
2. **Observe that the device connects to the broker**
486+
487+
3. **Test using shell commands**
488+
489+
```bash
490+
uart:~$ att_cloud_publish_mqtt test-payload
491+
Sending on payload channel: "data":"test-payload","ts":1746534066186 (40 bytes)
492+
[00:00:18.607,421] <dbg> cloud: on_cloud_payload_json: MQTT Publish Details:
493+
[00:00:18.607,482] <dbg> cloud: on_cloud_payload_json: -Payload: "data":"test-payload","ts":1746534066186
494+
[00:00:18.607,513] <dbg> cloud: on_cloud_payload_json: -Payload Length: 40
495+
[00:00:18.607,543] <dbg> cloud: on_cloud_payload_json: -Topic: 359404230261381/att-pub-topic
496+
[00:00:18.607,574] <dbg> cloud: on_cloud_payload_json: -Topic Size: 29
497+
[00:00:18.607,635] <dbg> cloud: on_cloud_payload_json: -QoS: 1
498+
[00:00:18.607,635] <dbg> cloud: on_cloud_payload_json: -Message ID: 1
499+
[00:00:18.607,696] <dbg> mqtt_helper: mqtt_helper_publish: Publishing to topic: 359404230261381/att-pub-topic
500+
[00:00:19.141,235] <dbg> mqtt_helper: mqtt_evt_handler: MQTT_EVT_PUBACK: id = 1 result = 0
501+
[00:00:19.141,265] <dbg> cloud: on_mqtt_puback: Publish acknowledgment received, message id: 1
502+
[00:00:19.141,296] <dbg> mqtt_helper: mqtt_helper_poll_loop: Polling on socket fd: 0
503+
[00:00:48.653,503] <dbg> mqtt_helper: mqtt_helper_poll_loop: Polling on socket fd: 0
504+
[00:00:49.587,463] <dbg> mqtt_helper: mqtt_evt_handler: MQTT_EVT_PINGRESP
505+
[00:00:49.587,493] <dbg> mqtt_helper: mqtt_helper_poll_loop: Polling on socket fd: 0
506+
[00:01:18.697,692] <dbg> mqtt_helper: mqtt_helper_poll_loop: Polling on socket fd: 0
507+
[00:01:19.350,921] <dbg> mqtt_helper: mqtt_evt_handler: MQTT_EVT_PINGRESP
508+
```
509+
510+
The MQTT cloud module includes a shell module/commands for testing cloud publishing much like the default CoAP configuration.
511+
To implement custom cloud shell commands this module can be used `examples/modules/cloud/cloud_mqtt_shell.c`.
512+
513+
### **Module State Machine**
514+
515+
The cloud MQTT module uses a state machine to manage connection and reconnection logic. Below is a mermaid diagram representing the module's states:
516+
517+
```mermaid
518+
stateDiagram-v2
519+
[*] --> STATE_RUNNING
520+
STATE_RUNNING --> STATE_DISCONNECTED : NETWORK_DISCONNECTED
521+
STATE_DISCONNECTED --> STATE_CONNECTING : NETWORK_CONNECTED
522+
STATE_CONNECTING --> STATE_CONNECTING_ATTEMPT
523+
STATE_CONNECTING_ATTEMPT --> STATE_CONNECTING_BACKOFF : CLOUD_CONN_FAILED
524+
STATE_CONNECTING_BACKOFF --> STATE_CONNECTING_ATTEMPT : CLOUD_BACKOFF_EXPIRED
525+
STATE_CONNECTING_ATTEMPT --> STATE_CONNECTED : CLOUD_CONN_SUCCESS
526+
STATE_CONNECTED --> STATE_DISCONNECTED : NETWORK_DISCONNECTED
527+
STATE_CONNECTED --> STATE_CONNECTED : PAYLOAD_CHAN / send_data()
528+
STATE_CONNECTED --> STATE_CONNECTED : <module>_SAMPLE_RESPONSE / send_<module>_data()
529+
STATE_CONNECTED --> STATE_CONNECTED : NETWORK_CONNECTED / (noop)
530+
STATE_CONNECTED --> STATE_DISCONNECTED : exit / mqtt_helper_disconnect()
531+
```

0 commit comments

Comments
 (0)