diff --git a/app/prj.conf b/app/prj.conf index fc15fcc9..7dc5807a 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -14,6 +14,13 @@ CONFIG_DK_LIBRARY=y CONFIG_ZVFS_OPEN_MAX=10 CONFIG_PWM=y +# TFM logging over uart0 +CONFIG_TFM_LOG_LEVEL_SILENCE=n +CONFIG_TFM_SECURE_UART0=y +CONFIG_TFM_SECURE_UART_SHARE_INSTANCE=y +CONFIG_TFM_EXCEPTION_INFO_DUMP=y +CONFIG_TFM_SPM_LOG_LEVEL_DEBUG=y + # Heap and stacks CONFIG_MAIN_STACK_SIZE=1856 # Extended AT host/monitor stack/heap sizes since some nrf_cloud credentials are longer than 1024 bytes. diff --git a/app/src/modules/environmental/Kconfig.environmental b/app/src/modules/environmental/Kconfig.environmental index 172fe1f5..1a908265 100644 --- a/app/src/modules/environmental/Kconfig.environmental +++ b/app/src/modules/environmental/Kconfig.environmental @@ -13,7 +13,7 @@ if APP_ENVIRONMENTAL config APP_ENVIRONMENTAL_THREAD_STACK_SIZE int "Thread stack size" - default 768 + default 1024 config APP_ENVIRONMENTAL_WATCHDOG_TIMEOUT_SECONDS int "Watchdog timeout" diff --git a/app/src/modules/power/power.c b/app/src/modules/power/power.c index b1297064..41dd8714 100644 --- a/app/src/modules/power/power.c +++ b/app/src/modules/power/power.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "lp803448_model.h" #include "app_common.h" diff --git a/docs/common/customization.md b/docs/common/customization.md index dc3f7d11..041b7fd3 100644 --- a/docs/common/customization.md +++ b/docs/common/customization.md @@ -1,7 +1,426 @@ # Customization +## Table of Contents + +- [Add environmental sensor](#add-environmental-sensor) +- [Add your own module](#add-your-own-module) + +This section contains guides for modifying specific aspects of the template. + +## Add environmental sensor + +TL;DR: To add basic support for the BMM350 magnetometer, apply the following patch: + +```bash +git apply /Asset-Tracker-Template/docs/patches/magnetometer.patch +``` + +To add a new sensor to the environmental module, ensure that: + +1. The sensor is properly configured in the Device Tree (DTS) +2. The corresponding driver is available in the Zephyr RTOS +3. The sensor is compatible with the Zephyr Sensor API + +In this example, we'll add support for the Bosch BMM350 Magnetometer sensor using the [Zephyr Sensor API](https://docs.zephyrproject.org/latest/hardware/peripherals/sensor/index.html). The BMM350 driver in Zephyr integrates with the Sensor API, making it straightforward to use. +This guide will apply in general for all sensors that support the Zephyr Sensor API. + +This guide uses the Thingy91x as an example, as it's a supported board in the template with defined board files in the nRF Connect SDK (NCS). + +1. First, enable the sensor in the Device Tree by setting its status to "okay". This will: + - Instantiate the Device Tree node for the sensor + - Initialize the driver during boot + - Make the sensor ready for use + +Add the following to the board-specific Device Tree overlay file (`thingy91x_nrf9151_ns.overlay`): + +```c +&magnetometer { + status = "okay"; +}; +``` + +2. Update the environmental module's state structure to include the magnetometer device reference and data storage: + +```c +struct environmental_state { + /* ... existing fields ... */ + + /* BMM350 sensor device reference */ + const struct device *const bmm350; + + /* Magnetic field measurements (X, Y, Z) in gauss */ + double magnetic_field[3]; + + /* ... existing fields ... */ +}; +``` + +3. Initialize the device reference using the Device Tree label: + +```c +struct environmental_state environmental_state = { + .bme680 = DEVICE_DT_GET(DT_NODELABEL(bme680)), + .bmm350 = DEVICE_DT_GET(DT_NODELABEL(magnetometer)), +}; +``` + +4. Update the sensor sampling function signature to include the magnetometer device: + +```c +static void sample_sensors(const struct device *const bme680, const struct device *const bmm350) +``` + +And update the function call: + +```c +sample_sensors(state_object->bme680, state_object->bmm350); +``` + +5. Implement sensor data acquisition using the Zephyr Sensor API: + +```c +err = sensor_sample_fetch(bmm350); +if (err) { + LOG_ERR("Failed to fetch magnetometer sample: %d", err); + SEND_FATAL_ERROR(); + return; +} + +err = sensor_channel_get(bmm350, SENSOR_CHAN_MAGN_XYZ, &magnetic_field); +if (err) { + LOG_ERR("Failed to get magnetometer data: %d", err); + SEND_FATAL_ERROR(); + return; +} + +LOG_DBG("Magnetic field: X: %.2f µT, Y: %.2f µT, Z: %.2f µT", + sensor_value_to_double(&magnetic_field[0]), + sensor_value_to_double(&magnetic_field[1]), + sensor_value_to_double(&magnetic_field[2])); + +struct environmental_msg msg = { + .type = ENVIRONMENTAL_SENSOR_SAMPLE_RESPONSE, + .temperature = sensor_value_to_double(&temp), + .pressure = sensor_value_to_double(&press), + .humidity = sensor_value_to_double(&humidity), + .magnetic_field[0] = sensor_value_to_double(&magnetic_field[0]), + .magnetic_field[1] = sensor_value_to_double(&magnetic_field[1]), + .magnetic_field[2] = sensor_value_to_double(&magnetic_field[2]), +}; +``` + +6. Add cloud integration for the magnetometer data: + +```c +char message[100] = { 0 }; + +err = snprintk(message, sizeof(message), + "%.2f %.2f %.2f", + msg.magnetic_field[0], + msg.magnetic_field[1], + msg.magnetic_field[2]); +if (err < 0 || err >= sizeof(message)) { + LOG_ERR("Failed to format magnetometer data: %d", err); + SEND_FATAL_ERROR(); + return; +} + +err = nrf_cloud_coap_message_send(CUSTOM_JSON_APPID_VAL_MAGNETIC, + message, + false, + NRF_CLOUD_NO_TIMESTAMP, + confirmable); +if (err == -ENETUNREACH) { + LOG_WRN("Network unreachable: %d", err); + return; +} else if (err) { + LOG_ERR("Failed to send magnetometer data: %d", err); + SEND_FATAL_ERROR(); + return; +} +``` + +Define a custom APP ID for magnetic field data: + +```c +#define CUSTOM_JSON_APPID_VAL_MAGNETIC "MAGNETIC_FIELD" +``` + ## Add your own module -- Instructions on adding your own module -## Add sensor -- Instructions on how to add a new sensor to the environmental module +TL;DR: To add the dummy module to the template, apply the following patch: + +```bash +git apply /Asset-Tracker-Template/docs/patches/dummy-module.patch +``` + +This guide demonstrates how to create a new module in the template. The dummy module serves as a template for understanding the module architecture and can be used as a foundation for custom modules. + +1. Create the module directory structure: + +```bash +mkdir -p app/src/modules/dummy +``` + +2. Create the following files in the module directory: + - `dummy.h` - Module interface definitions + - `dummy.c` - Module implementation + - `Kconfig.dummy` - Module configuration options + - `CMakeLists.txt` - Build system configuration + +3. In `dummy.h`, define the module's interface: + +```c +#ifndef _DUMMY_H_ +#define _DUMMY_H_ + +#include +#include + +#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; +}; + +#define MSG_TO_DUMMY_MSG(_msg) (*(const struct dummy_msg *)_msg) + +#ifdef __cplusplus +} +#endif + +#endif /* _DUMMY_H_ */ +``` + +4. In `dummy.c`, implement the module's functionality: + +```c +#include +#include +#include +#include +#include + +#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 void 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 void state_running_run(void *o) +{ + const struct dummy_state *state_object = (const struct dummy_state *)o; + + if (&DUMMY_CHAN == state_object->chan) { + struct dummy_msg msg = MSG_TO_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, K_NO_WAIT); + if (err) { + LOG_ERR("Failed to publish response: %d", err); + SEND_FATAL_ERROR(); + return; + } + } + } +} + +/* 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 `Kconfig.dummy`, define module configuration options: + +```kconfig +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 `CMakeLists.txt`, configure the build system: + +```cmake +target_sources_ifdef(CONFIG_APP_DUMMY app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/dummy.c) +target_include_directories(app PRIVATE .) +``` + +7. Add the module to the main application's CMakeLists.txt: + +```cmake +add_subdirectory(src/modules/dummy) +``` + +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 using zbus +- Includes error handling and watchdog support +- Follows the state machine pattern used by other modules + +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. + +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. diff --git a/docs/common/tooling_troubleshooting.md b/docs/common/tooling_troubleshooting.md index 95e9344b..4d72c742 100644 --- a/docs/common/tooling_troubleshooting.md +++ b/docs/common/tooling_troubleshooting.md @@ -1,73 +1,177 @@ -# Troubleshooting -General overview of tools used to troubleshoot the template and/or modem/network behavior. -It's recommended to complete the Nordic Developer Academy lesson: [Debugging and troubleshooting](https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-2-debugging/) for tips in general how to debug applications based on [nRF Connect SDK](https://github.com/nrfconnect/sdk-nrf). +# Tooling and Troubleshooting -# Shell -To control and get information about certain aspects of the template, shell can be used. -To use shell, connect to the devices UART interface either via your own terminal or the [nRF Connect for Desktop](https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-Desktop) Serial terminal application. -Here is an example of how shell can be used to send a message to [nRF Cloud](https://nrfcloud.com): +## Table of Contents -``` -uart:~$ att_cloud_publish TEMP "24" -Sending on payload channel: {"messageType":"DATA","appId":"TEMP","data":"24","ts":1744359144653} (68 bytes) -``` +- [Shell Commands](#shell-commands) + - [Available Commands](#available-commands) + - [Shell Command Examples](#shell-command-examples) +- [Debugging Tools](#debugging-tools) + - [Low Power Profiling](#low-power-profiling) + - [GDB Debugging](#gdb-debugging) + - [SEGGER SystemView](#segger-systemview) + - [Thread Analysis](#thread-analysis) + - [Hardfaults](#hardfaults) -The following command "help" lists all the available commands in the template: +General overview of tools used to troubleshoot the template code and/or modem/network behavior. +For more knowledge on debugging and troubleshooting [nRF Connect SDK](https://github.com/nrfconnect/sdk-nrf) based applications in general, refer to these links: -``` +- [Debugging and troubleshooting](https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-2-debugging/) +- [Cellular IoT Fundamentals Developer Academy Course](https://academy.nordicsemi.com/courses/cellular-iot-fundamentals/) +- [nRF Connect SDK Debugging Guide](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/test_and_optimize/debugging.html) +- [Zephyr Debugging Guide](https://docs.zephyrproject.org/latest/develop/debug/index.html) + +## Shell Commands + +The template provides several shell commands for controlling and monitoring device behavior. Connect to the device's UART interface using either: + +- Your preferred terminal application (e.g., `putty`, `minicom`, `terraterm`) +- [nRF Connect for Desktop](https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-Desktop) Serial terminal application + +### Available Commands + +Run `help` to list all available commands: + +```bash uart:~$ help Available commands: at : Execute an AT command att_button_press : Asset Tracker Template Button CMDs att_cloud_publish : Asset Tracker Template Cloud CMDs att_network : Asset Tracker Template Network CMDs + clear : Clear screen. + date : Date commands + device : Device commands + devmem : Read/write physical memory + Usage: + Read memory at address with optional width: + devmem
[] + Write memory at address with mandatory width and value: + devmem
+ help : Prints the help message. + history : Command history. + kernel : Kernel commands + mflt : Memfault Test Commands mflt_nrf : Memfault nRF Connect SDK Test Commands - ... + pm : PM commands + rem : Ignore lines beginning with 'rem ' + resize : Console gets terminal screen size or assumes default in + case the readout fails. It must be executed after each + terminal width change to ensure correct text display. + retval : Print return value of most recent command + shell : Useful, not Unix-like shell commands. ``` -The commands prefixed *att* are commands that are specific to the template. -These shell commands are implemented in shell modules corresponding to the aspect of the template they control. -For example, the network module implements the file `network_shell.c` and is located in the module folder. +### Shell Command Examples -# Low Power Profiling -The Power consumption of the device can be profiled using PPK. +#### Cloud Publishing -# Debugging using West (GDB) -The template can be easily debugged using GDB via the west commands `west attach`. -The following example shows how GDB can be used to set a breakpoint and print a backtrace once the breakpoint is hit: +```bash +uart:~$ att_cloud_publish TEMP "24" +Sending on payload channel: {"messageType":"DATA","appId":"TEMP","data":"24","ts":1744359144653} (68 bytes) +``` + +#### Network disconnect +```bash +uart:~$ att_network disconnect +[00:00:36.758,758] network: state_disconnecting_entry: state_disconnecting_entry +[00:00:37.196,746] network: Not registered, check rejection cause +[00:00:37.197,021] network: Network connectivity lost +[00:00:37.198,608] cloud: state_connected_paused_entry: state_connected_paused_entry +[00:00:37.198,974] main: wait_for_trigger_exit: wait_for_trigger_exit +[00:00:37.199,005] main: idle_entry: idle_entry +[00:00:37.205,444] network: state_disconnected_entry: state_disconnected_entry ``` -Insert terminal session showing how its used here. + +#### AT Command Execution + +```bash +uart:~$ at at+cgsn ++CGSN: "123456789012345" +OK ``` -Fore more information about debugging with west, see [West debugging](https://docs.zephyrproject.org/latest/develop/west/build-flash-debug.html#debugging-west-debug-west-debugserver) +```bash +uart:~$ at at+cpsms? ++CPSMS: 1,,,"00001100","00000011" +OK +``` + +## Debugging Tools + +### Low Power Profiling + +To get a rough estimate of the power consumption of the device and what you should expect depending on your network configuration and data transmission, you can use the [Online Power Profiler for LTE](https://devzone.nordicsemi.com/power/w/opp/3/online-power-profiler-for-lte). + +For exact measurements, it's recommended to use a Power Analyzer or the [PPK: Power Profiler Kit 2](https://www.nordicsemi.com/Products/Development-hardware/Power-Profiler-Kit-2). + +For detailed guidance on how the PPK can be used to profile and measure power, see: -# Debugging using SEGGER SystemView -[Segger SystemView](https://www.segger.com/products/development-tools/systemview/) is a real-time analysis tool that can be used to analyse thread execution and scheduling in the device. -To build the template with RTT tracing to Segger SystemView set the following options in your build configuration: +- [Power Profiler Kit User Guide](https://docs.nordicsemi.com/bundle/ug_ppk2/page/UG/ppk/PPK_user_guide_Intro.html) +### GDB Debugging + +Debug the template using GDB via west commands: + +```bash +# Attach GDB, skip rebuilding application +west attach --skip-rebuild +``` + +Common GDB commands: + +```bash +(gdb) tui enable +(gdb) monitor reset +(gdb) break main +(gdb) continue +(gdb) backtrace +(gdb) print variable_name +(gdb) next +(gdb) step ``` + +For more information, see: + +- [West Debugging Guide](https://docs.zephyrproject.org/latest/develop/west/build-flash-debug.html#debugging-west-debug-west-debugserver) +- [nRF Connect SDK VS Code Debugging](https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-2-debugging/topic/debugging-in-vs-code/) +- [GDB Manual](https://man7.org/linux/man-pages/man1/gdb.1.html) + +### SEGGER SystemView + +Analyze thread execution and scheduling using [SEGGER SystemView](https://www.segger.com/products/development-tools/systemview/). + +![Segger Systemview](../gifs/sysview-ui.gif) + +#### Configuration + +Add to `prj.conf`: + +```bash CONFIG_TRACING=y -CONFIG_USE_SEGGER_RTT=y CONFIG_SEGGER_SYSTEMVIEW=y -CONFIG_SEGGER_SYSTEMVIEW_BOOT_ENABLE=n -CONFIG_SEGGER_SYSVIEW_POST_MORTEM_MODE=n - ``` -Or build with the predefined RTT trace snippet: +And build/flash the template for the respective board. +Or build with the necessary configurations passed in via the west build command: +```bash +west build -p -b -- -DCONFIG_TRACING=y -DCONFIG_SEGGER_SYSTEMVIEW=y ``` -west build -p -b -S rtt-tracing + +Or RTT tracing snippet: + +```bash +west build -p -b -- -Dapp_SNIPPET=rtt-tracing ``` -Note that this snippet disables debug optimizations and that it may not be enough space in your application to use it as disabling optimizations increases the overall memory use of the application. +### Thread Analysis -# Thread Analyser -The Trace analyzer subsystem is useful when optimizing the applications stack sizes. -Add the following options to your build configurations to get stack information printed every 30 seconds: +Monitor and optimize stack sizes using the Thread Analyzer: -``` +Add to `prj.conf`: + +```bash CONFIG_THREAD_ANALYZER=y CONFIG_THREAD_ANALYZER_USE_LOG=y CONFIG_THREAD_ANALYZER_AUTO=y @@ -76,106 +180,321 @@ CONFIG_THREAD_ANALYZER_AUTO_STACK_SIZE=1024 CONFIG_THREAD_NAME=y ``` -# TF-M logging -To get logs from the secure image TF-M of the application forwarded to UART0 (application UART output) you can build with the following snippet: - +The listed configurations configure the thread analyzer to print thread information every 30 seconds: + +```bash +[00:00:30.725,463] thread_analyzer: location_api_workq : STACK: unused 376 usage 3720 / 4096 (90 %); CPU: 0 % +[00:00:30.725,494] thread_analyzer: : Total CPU cycles used: 242 +[00:00:30.725,738] thread_analyzer: downloader : STACK: unused 1480 usage 184 / 1664 (11 %); CPU: 0 % +[00:00:30.725,769] thread_analyzer: : Total CPU cycles used: 0 +[00:00:30.725,891] thread_analyzer: thread_analyzer : STACK: unused 480 usage 544 / 1024 (53 %); CPU: 0 % +[00:00:30.725,921] thread_analyzer: : Total CPU cycles used: 148 +[00:00:30.725,982] thread_analyzer: power_task_id : STACK: unused 168 usage 1176 / 1344 (87 %); CPU: 0 % +[00:00:30.726,013] thread_analyzer: : Total CPU cycles used: 85 +[00:00:30.726,104] thread_analyzer: network_module_thread_id: STACK: unused 160 usage 1504 / 1664 (90 %); CPU: 0 % +[00:00:30.726,165] thread_analyzer: : Total CPU cycles used: 2011 +[00:00:30.726,257] thread_analyzer: location_module_thread_id: STACK: unused 216 usage 1000 / 1216 (82 %); CPU: 0 % +[00:00:30.726,287] thread_analyzer: : Total CPU cycles used: 185 +[00:00:30.726,440] thread_analyzer: fota_task_id : STACK: unused 968 usage 1536 / 2504 (61 %); CPU: 0 % +[00:00:30.726,470] thread_analyzer: : Total CPU cycles used: 187 +[00:00:30.726,562] thread_analyzer: environmental_task_id: STACK: unused 168 usage 856 / 1024 (83 %); CPU: 0 % +[00:00:30.726,593] thread_analyzer: : Total CPU cycles used: 37 +[00:00:30.726,715] thread_analyzer: coap_client_recv_thread: STACK: unused 592 usage 688 / 1280 (53 %); CPU: 0 % +[00:00:30.726,745] thread_analyzer: : Total CPU cycles used: 273 +[00:00:30.726,867] thread_analyzer: cloud_module_thread_id: STACK: unused 328 usage 3000 / 3328 (90 %); CPU: 0 % +[00:00:30.726,898] thread_analyzer: : Total CPU cycles used: 1081 +[00:00:30.726,959] thread_analyzer: date_time_work_q : STACK: unused 80 usage 368 / 448 (82 %); CPU: 0 % +[00:00:30.726,989] thread_analyzer: : Total CPU cycles used: 11 +[00:00:30.727,050] thread_analyzer: conn_mgr_monitor : STACK: unused 72 usage 312 / 384 (81 %); CPU: 0 % +[00:00:30.727,081] thread_analyzer: : Total CPU cycles used: 13 +[00:00:30.727,203] thread_analyzer: work_q : STACK: unused 576 usage 192 / 768 (25 %); CPU: 0 % +[00:00:30.727,233] thread_analyzer: : Total CPU cycles used: 3 +[00:00:30.727,294] thread_analyzer: rx_q[0] : STACK: unused 24 usage 168 / 192 (87 %); CPU: 0 % +[00:00:30.727,325] thread_analyzer: : Total CPU cycles used: 1 +[00:00:30.727,386] thread_analyzer: tx_q[0] : STACK: unused 24 usage 168 / 192 (87 %); CPU: 0 % +[00:00:30.727,416] thread_analyzer: : Total CPU cycles used: 1 +[00:00:30.727,539] thread_analyzer: net_mgmt : STACK: unused 504 usage 776 / 1280 (60 %); CPU: 0 % +[00:00:30.727,569] thread_analyzer: : Total CPU cycles used: 124 +[00:00:30.727,783] thread_analyzer: shell_uart : STACK: unused 1312 usage 736 / 2048 (35 %); CPU: 0 % +[00:00:30.727,813] thread_analyzer: : Total CPU cycles used: 3971 +[00:00:30.727,905] thread_analyzer: sysworkq : STACK: unused 400 usage 880 / 1280 (68 %); CPU: 0 % +[00:00:30.727,935] thread_analyzer: : Total CPU cycles used: 278 +[00:00:30.728,027] thread_analyzer: nrf70_intr_wq : STACK: unused 120 usage 712 / 832 (85 %); CPU: 0 % +[00:00:30.728,057] thread_analyzer: : Total CPU cycles used: 806 +[00:00:30.728,118] thread_analyzer: nrf70_bh_wq : STACK: unused 112 usage 656 / 768 (85 %); CPU: 0 % +[00:00:30.728,149] thread_analyzer: : Total CPU cycles used: 102 +[00:00:30.728,271] thread_analyzer: logging : STACK: unused 448 usage 320 / 768 (41 %); CPU: 0 % +[00:00:30.728,302] thread_analyzer: : Total CPU cycles used: 224 +[00:00:30.728,363] thread_analyzer: idle : STACK: unused 256 usage 64 / 320 (20 %); CPU: 98 % +[00:00:30.728,393] thread_analyzer: : Total CPU cycles used: 985191 +[00:00:30.728,485] thread_analyzer: main : STACK: unused 208 usage 1648 / 1856 (88 %); CPU: 0 % +[00:00:30.728,515] thread_analyzer: : Total CPU cycles used: 2055 +[00:00:30.728,759] thread_analyzer: ISR0 : STACK: unused 1736 usage 312 / 2048 (15 %) +``` + +For more information, see [Zephyr Thread Analyzer](https://docs.zephyrproject.org/latest/services/debugging/thread-analyzer.html) + +### Hardfaults + +When a hardfault occurs, the [LR and PC](https://stackoverflow.com/questions/8236959/what-are-sp-stack-and-lr-in-arm) registers can be looked up in order to find the offending instruction. +For example, in this fault frame the PC is `0x00002681`, thread is `main` and type of error is a stack overflow. +So in this case, there is no need to look up the PC or LR to understand the issue. +The main stack size needs to be increased. + +For more information on how to debug hardfaults, see [Memfault Cortex Hardfault debug](https://interrupt.memfault.com/blog/cortex-m-hardfault-debug) + +```bash +*** Using Zephyr OS v4.0.99-7607c6585566 *** +[00:00:00.756,317] main: main: Main has started +[00:00:00.764,770] os: ***** USAGE FAULT ***** +[00:00:00.772,552] os: Stack overflow (context area not valid) +[00:00:00.781,951] os: r0/a1: 0x0000267e r1/a2: 0x0007b6f7 r2/a3: 0x0000267f +[00:00:00.792,785] os: r3/a4: 0x0007b6f7 r12/ip: 0x00002680 r14/lr: 0x0007b6f7 +[00:00:00.803,619] os: xpsr: 0x0007b600 +[00:00:00.811,035] os: s[ 0]: 0x00002682 s[ 1]: 0x0007b6f7 s[ 2]: 0x00002683 s[ 3]: 0x0007b6f7 +[00:00:00.823,608] os: s[ 4]: 0x00002684 s[ 5]: 0x0007b6f7 s[ 6]: 0x00002685 s[ 7]: 0x0007b6f7 +[00:00:00.836,212] os: s[ 8]: 0x00002686 s[ 9]: 0x0007b6f7 s[10]: 0x00002687 s[11]: 0x0007b6f7 +[00:00:00.848,815] os: s[12]: 0x00002688 s[13]: 0x0007b6f7 s[14]: 0x00002689 s[15]: 0x0007b6f7 +[00:00:00.861,389] os: fpscr: 0x0000268a +[00:00:00.868,774] os: Faulting instruction address (r15/pc): 0x00002681 +[00:00:00.878,845] os: >>> ZEPHYR FATAL ERROR 2: Stack overflow on CPU 0 +[00:00:00.888,916] os: Current thread: 0x200132b8 (main) +[00:00:00.897,583] os: Halting system +``` + +However, if the fault source is more ambiguous it might be needed to use Address-2-Line to lookup the offending function. +In this example, the LR address is used to find the function address stored in the LR register. +This function is the parent in the callstack of the address the PC points to. + +```bash +/arm-zephyr-eabi/bin/arm-zephyr-eabi-addr2line -e build/app/zephyr/zephyr.elf 0x0007b6f7 +/app/src/main.c:771 +``` + +The template is configured to forward logging in TF-M (Secure image) to UART 0 (application log output). +If a secure fault occurs, the fault frame from TF-M will look like this: + +```bash +uart:~$ FATAL ERROR: SecureFault +Here is some context for the exception: + EXC_RETURN (LR): 0xFFFFFFAD + Exception came from non-secure FW in thread mode. + xPSR: 0x60000007 + MSP: 0x20000BF8 + PSP: 0x20001CF8 + MSP_NS: 0x2002C580 + PSP_NS: 0x2002CD40 + Exception frame at: 0x2002CD40 + R0: 0x00000000 + R1: 0x00000000 + R2: 0x20013288 + R3: 0x00000000 + R12: 0x00000000 + LR: 0x00044181 + PC: 0x0003D7B6 + xPSR: 0x61000000 + Callee saved register state: R4: 0x2000D414 + R5: 0x0008A0B8 + R6: 0x00088835 + R7: 0x00000000 + R8: 0x00000000 + R9: 0x00000008 + R10: 0x00048A04 + R11: 0x00048A04 + CFSR: 0x00000000 + BFSR: 0x00000000 + BFAR: Not Valid + MMFSR: 0x00000000 + MMFAR: Not Valid + UFSR: 0x00000000 + HFSR: 0x00000000 + SFSR: 0x00000048 + SFAR: 0x00000000 ``` -west build -p -b -S tfm-enable-share-uart + +Here we can again lookup the PC and LR in the Non-Secure image to find the offending function: + +```bash +~/dev/projects/att/Asset-Tracker-Template/app add-sensor-docs *18 !5 ❯ a2l 0x0003D7B6 +/dev/projects/att/Asset-Tracker-Template/app/src/main.c:789 ``` -In case of secure faults, the fault frame information will be printed with accompanied information of the violating non-secure SP and LR registers. +Secure faults will display: -# Debugging using Memfault (Remote debugging) -The template implements overlays to enable remote debugging to [Memfault](https://memfault.com/). -To get familiar with Memfault and how to do remote debugging, its recommended to go through the Nordic Developer Academy excersise: [Remote Debugging with Memfault](https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-2-debugging/topic/exercise-4-remote-debugging-with-memfault/) +- Fault frame information +- Non-secure SP and LR registers +- Violation details -Refer to the [Getting Started](getting_started.md) section to see how the template can be built with overlays enabling Memfault functionality. -If you want to test/simulate faults on the device to test Memfault, the template implements Memfault shell commands that can be used to trigger typical faults. -Here is an example of triggering a usagefault: +For more information: +- [TF-M Documentation](https://tf-m-user-guide.trustedfirmware.org/) +- [nRF Connect SDK TF-M Guide](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/security/tfm/index.html) + +**NOTE** +It may be that upon a hardfault, the fault frame is not printed due to the device rebooting before the log buffer is flushed. +To circumvent this issue add the following configurations: + +```bash +CONFIG_LOG_MODE_IMMEDIATE=y +CONFIG_RESET_ON_FATAL_ERROR=n ``` -insert example here + +Note that when enabling immediate logging, it might be necessary to increase the stack size of certain threads due to logging being executed in context which increases stack usage. + +## Memfault Remote Debugging + +The template supports remote debugging via [Memfault](https://memfault.com/). +Remote debugging enables the device to send metrics suchs as LTE, GNSS and memory statistics as well as coredump captures on crashes to analyse problems across single or fleet of devices once they occur. + +For more information see: + +- [Memfault Sample](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/samples/debug/memfault/README.html) +- [Memfault Integration](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/libraries/debug/memfault_ncs.html) + +### Recommended Prerequisites + +1. Register at [Memfault](https://app.memfault.com/register-nordic) +2. Complete the [Remote Debugging with Memfault](https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-2-debugging/topic/exercise-4-remote-debugging-with-memfault/) exercise. +3. Memfault project key retrieved during the aforementioned steps. + +To build the application with support for Memfault you need to build with the Memfault overlay `overlay-memfault.conf`. If you want to capture and send modem traces to Memfault on coredumps, you can include the overlay `overlay-publish-modem-traces-to-memfault.conf`. + +If you also want to upload the [Embedded Trace Buffer](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/libraries/debug/etb_trace.html) you can include the overlay `overlay-etb.conf`. + +**Important Note on Data Usage**: Enabling Memfault will increase your device's data usage. This is especially true when using the modem trace upload feature, which can send upwards of 1MB of modem trace data in case of application crashes. Consider this when planning your data usage and costs. + +For detailed build instructions and how to configure the project key, refer to the [Getting Started Guide](getting_started.md) where build instructions for building with Memfault are given. +To build with all available Memfault functionality: + +```bash +west build -p -b -- -DEXTRA_CONF_FILE="overlay-memfault.conf;overlay-upload-modem-traces-to-memfault.conf;overlay-etb.conf" -DCONFIG_MEMFAULT_NCS_PROJECT_KEY=\"\" ``` -Fore more information on NCSs Memfault implementation, refer to the Memfault NCS sample documentation, integration as well as the templates CI Memfault on-target tests: +Screen capture from a coredump received in Memfault: -* [Memfault sample](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/samples/debug/memfault/README.html) -* [Memfault integration](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/libraries/debug/memfault_ncs.html) -* [CI tests](Asset-Tracker-Template/tests/on_target/tests/test_functional/test_memfault.py) +![Memfault UI](../images/memfault.png) -To use Memfault, you will need to register and setup a project. -This can be done via the following link, [Memfault registration page](https://app.memfault.com/register-nordic) +**IMPORTANT** In order to properly use Memfault and be able to decode metrics and coredumps sent from the device, you need to upload the ELF file located in the build folder of the template once you have built the application. This is covered in the [Remote Debugging with Memfault](https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-2-debugging/topic/exercise-4-remote-debugging-with-memfault/) developer Academy excersise. -# Debugging using Modem traces -Modem traces can be captured over UART to debug LTE, IP, and modem issues. -This can be done by including a snippet in the build command specifying which medium you want to trace to. -This build command enables modem tracing over UART1: +#### Test shell commands +Trigger test faults using shell commands: + +```bash +uart:~$ mflt_nrf test hardfault +uart:~$ mflt_nrf test assert +uart:~$ mflt_nrf test usagefault ``` -west build -p -b -S nrf91-modem-trace-uart + +## Modem Tracing + +Capture and analyze modem behavior live (AT, LTE, IP) via Wireshark. + +### UART Tracing + +Build with: + +```bash +west build -p -b -- -Dapp_SNIPPET=nrf91-modem-trace-uart ``` -This build commands enables modem tracing over RTT: +Capture traces using [nRF Connect for Desktop](https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-Desktop) Cellular Monitor application or manually using nRF Util: +```bash +nrfutil trace lte --input-serialport /dev/tty.usbmodem141405 --output-pcapng trace.pcapng ``` -west build -p -b -S nrf91-modem-trace-uart + +```bash +~/pcap ❯ nrfutil trace lte --input-serialport /dev/tty.usbmodem141405 --output-pcapng trace.pcapng 10:25:31 +⠒ Saving trace to trace.pcapng (11952 bytes) ``` -To capture modem traces over UART on a host computer either [nRF Connect for Desktop](https://www.nordicsemi.com/Products/Development-tools/) Cellular Monitor application can be used nRFUtil CLI: +If not traces are captured it might be needed to reset the device. +After capturing the trace it can be opened in wireshark: +```bash +wireshark trace.pcapng ``` -nrfutil trace lte --input-serialport /dev/ttyACM1 --output-pcapng trace + +You can also do live tracing by piping the traces to wireshark: + +```bash +nrfutil trace lte --input-serialport /dev/tty.usbmodem141405 --output-pcapng trace.pcapng --output-wireshark wireshark ``` -for RTT traces you can use the RTT logger to capture the modem traces and later convert them to PCAP using nRF Util or the Cellular Monitor: +### RTT Tracing + +Build with: +```bash +west build -p -b -- -Dapp_SNIPPET=nrf91-modem-trace-rtt ``` -JLinkRTTLogger -Device NRF9160_XXAA -If SWD -RTTChannel 1 modem_trace.bin + +Capture traces using Segger JLink RTT Logger: + +```bash +JLinkRTTLogger -Device NRF9160_XXAA -If SWD -Speed 50000 -RTTChannel 1 modem_trace.bin ``` -If you also want to route logging via RTT you can capture both modem traces and application logs at the same time using debuggers RTT multichannel functionality. -To do so you build the application with the RTT modem trace snippet and add the following options to the project configurations: +and then convert the captured modem trace to pcapng using the Cellular Monitor application or nRF Util: +```bash +nrfutil trace lte --input-file modem_trace.bin --output-pcapng rtt-trace.pcapng ``` + +### Application logs and modem traces over RTT - Parrallell capture + +For simultaneous modem traces and application logs over RTT: + +Add to `prj.conf`: + +```bash CONFIG_USE_SEGGER_RTT=y CONFIG_LOG_BACKEND_RTT=y CONFIG_SHELL_BACKEND_RTT=y CONFIG_SHELL_BACKEND_RTT_BUFFER=1 ``` -This will split RTT tracing and logging into two channels, the you can call two separate commands to trace on both channels: +Capture in separate terminals on different RTT channels: -Terminal 1: +```bash +# Terminal 1 - Modem traces +JLinkRTTLogger -Device NRF9160_XXAA -If SWD -Speed 50000 -RTTChannel 2 modem_trace.bin -``` -JLinkRTTLogger -Device NRF9160_XXAA -If SWD -RTTChannel 1 modem_trace.bin +# Terminal 2 - Application logs +JLinkRTTLogger -Device NRF9160_XXAA -If SWD -Speed 50000 -RTTChannel 0 terminal.txt ``` -Terminal 2: +It might be needed to change the channel name depending. Default should be: termina: 0, shell: 1, modem trace: 2. -``` -JLinkRTTLogger -Device NRF9160_XXAA -If SWD -Speed 50000 -RTTChannel 0 terminal.txt -``` +For more information: -Just remember that the modem trace is captured in a binary format that needs to be converted by either nRF Util or the Cellular Monitor application. -To convert a binary modem trace using nRF Util you can use the following command: +- [nRF Connect SDK Modem Tracing](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/nrf_modem/doc/modem_trace.html) -``` -nrfutil trace lte --input-file modemtraces.bin --output-wireshark -``` +## Common Issues and Solutions + +If you are not able to resolve the issue with the tools and instructions given in this documentation its recommended to create an issue in the [template repository](https://github.com/nrfconnect/Asset-Tracker-Template/issues) or register a support ticket in Nordics support portal . + +## Network Connection Issues + +### Symptoms + +- Device fails to connect to network +- Frequent disconnections + +### Debugging Steps + +1. Capture and analyse modem traces +2. Attach traces in ticket or issue reported to Nordic via DevZone + +## Hardfault -Its recommended to go through the [Nordic Developer Academy Debugging with a Modem Trace excersise](https://academy.nordicsemi.com/courses/cellular-iot-fundamentals/lessons/lesson-7-cellular-fundamentals/) to familiarize yourself with modem tracing on the nRF91 Series. +## Symptons -# Common issues +- Device crashes +- Reboot loop -## Not getting connected to network -- problem (description, UART/trace output) - -- suggested tooling/debuggin to find the source of the issue - (modem trace / memfault) -- Suggested fixes - -- Link to similar devzone cases - +Debugging steps -## Cloud connection timeout -- problem (description, UART/trace output) - -- suggested tooling/debuggin to find the source of the issue - (modem trace / memfault) -- Suggested fixes - -- Link to similar devzone cases - \ No newline at end of file +- Lookup LR/PC if printed +- Debug using GDB diff --git a/docs/gifs/sysview-ui.gif b/docs/gifs/sysview-ui.gif new file mode 100644 index 00000000..c328592d Binary files /dev/null and b/docs/gifs/sysview-ui.gif differ diff --git a/docs/images/memfault.png b/docs/images/memfault.png new file mode 100644 index 00000000..f9930ec5 Binary files /dev/null and b/docs/images/memfault.png differ diff --git a/docs/patches/dummy-module.patch b/docs/patches/dummy-module.patch new file mode 100644 index 00000000..ffa1c9da --- /dev/null +++ b/docs/patches/dummy-module.patch @@ -0,0 +1,281 @@ +diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt +index e1948cc..8d20bcd 100644 +--- a/app/CMakeLists.txt ++++ b/app/CMakeLists.txt +@@ -27,3 +27,4 @@ add_subdirectory_ifdef(CONFIG_APP_LED src/modules/led) + add_subdirectory_ifdef(CONFIG_APP_LOCATION src/modules/location) + add_subdirectory_ifdef(CONFIG_APP_CLOUD src/modules/cloud) + add_subdirectory_ifdef(CONFIG_APP_FOTA src/modules/fota) ++add_subdirectory_ifdef(CONFIG_APP_DUMMY src/modules/dummy) +diff --git a/app/src/modules/dummy/CMakeLists.txt b/app/src/modules/dummy/CMakeLists.txt +new file mode 100644 +index 0000000..a299b3b +--- /dev/null ++++ b/app/src/modules/dummy/CMakeLists.txt +@@ -0,0 +1,8 @@ ++# ++# Copyright (c) 2025 Nordic Semiconductor ASA ++# ++# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause ++# ++ ++target_sources_ifdef(CONFIG_APP_DUMMY app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/dummy.c) ++target_include_directories(app PRIVATE .) +diff --git a/app/src/modules/dummy/Kconfig.dummy b/app/src/modules/dummy/Kconfig.dummy +new file mode 100644 +index 0000000..2319814 +--- /dev/null ++++ b/app/src/modules/dummy/Kconfig.dummy +@@ -0,0 +1,37 @@ ++# ++# Copyright (c) 2024 Nordic Semiconductor ++# ++# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause ++# ++ ++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 +diff --git a/app/src/modules/dummy/dummy.c b/app/src/modules/dummy/dummy.c +new file mode 100644 +index 0000000..8c2450a +--- /dev/null ++++ b/app/src/modules/dummy/dummy.c +@@ -0,0 +1,162 @@ ++/* ++ * Copyright (c) 2025 Nordic Semiconductor ASA ++ * ++ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#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 void 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 void state_running_run(void *o) ++{ ++ const struct dummy_state *state_object = (const struct dummy_state *)o; ++ ++ if (&DUMMY_CHAN == state_object->chan) { ++ struct dummy_msg msg = MSG_TO_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, K_NO_WAIT); ++ if (err) { ++ LOG_ERR("Failed to publish response: %d", err); ++ SEND_FATAL_ERROR(); ++ return; ++ } ++ } ++ } ++} ++ ++/* 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()); ++ if (task_wdt_id < 0) { ++ LOG_ERR("Failed to add task to watchdog: %d", task_wdt_id); ++ SEND_FATAL_ERROR(); ++ return; ++ } ++ ++ 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); +diff --git a/app/src/modules/dummy/dummy.h b/app/src/modules/dummy/dummy.h +new file mode 100644 +index 0000000..4d3816e +--- /dev/null ++++ b/app/src/modules/dummy/dummy.h +@@ -0,0 +1,41 @@ ++/* ++ * Copyright (c) 2025 Nordic Semiconductor ASA ++ * ++ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause ++ */ ++ ++#ifndef _DUMMY_H_ ++#define _DUMMY_H_ ++ ++#include ++#include ++ ++#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; ++}; ++ ++#define MSG_TO_DUMMY_MSG(_msg) (*(const struct dummy_msg *)_msg) ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* _DUMMY_H_ */ diff --git a/docs/patches/magnetometer.patch b/docs/patches/magnetometer.patch new file mode 100644 index 00000000..421752e2 --- /dev/null +++ b/docs/patches/magnetometer.patch @@ -0,0 +1,166 @@ +diff --git a/app/boards/thingy91x_nrf9151_ns.overlay b/app/boards/thingy91x_nrf9151_ns.overlay +index e68d98b..1901eca 100644 +--- a/app/boards/thingy91x_nrf9151_ns.overlay ++++ b/app/boards/thingy91x_nrf9151_ns.overlay +@@ -10,6 +10,10 @@ + status = "okay"; + }; + ++&magnetometer { ++ status = "okay"; ++}; ++ + / { + chosen { + zephyr,wifi = &nordic_wlan0; +diff --git a/app/src/modules/cloud/cloud_module.c b/app/src/modules/cloud/cloud_module.c +index 9696914..a701140 100644 +--- a/app/src/modules/cloud/cloud_module.c ++++ b/app/src/modules/cloud/cloud_module.c +@@ -38,6 +38,7 @@ LOG_MODULE_REGISTER(cloud, CONFIG_APP_CLOUD_LOG_LEVEL); + + #define CUSTOM_JSON_APPID_VAL_CONEVAL "CONEVAL" + #define CUSTOM_JSON_APPID_VAL_BATTERY "BATTERY" ++#define CUSTOM_JSON_APPID_VAL_MAGNETIC "MAGNETIC_FIELD" + + BUILD_ASSERT(CONFIG_APP_CLOUD_WATCHDOG_TIMEOUT_SECONDS > + CONFIG_APP_CLOUD_MSG_PROCESSING_TIMEOUT_SECONDS, +@@ -672,6 +673,33 @@ static void state_connected_ready_run(void *o) + return; + } + ++ char message[100] = { 0 }; ++ ++ err = snprintk(message, sizeof(message), ++ "%.2f %.2f %.2f", ++ msg.magnetic_field[0], ++ msg.magnetic_field[1], ++ msg.magnetic_field[2]); ++ if (err < 0 || err >= sizeof(message)) { ++ LOG_ERR("snprintk, error: %d", err); ++ SEND_FATAL_ERROR(); ++ return; ++ } ++ ++ err = nrf_cloud_coap_message_send(CUSTOM_JSON_APPID_VAL_MAGNETIC, ++ message, ++ false, ++ NRF_CLOUD_NO_TIMESTAMP, ++ confirmable); ++ if (err == -ENETUNREACH) { ++ LOG_WRN("Network is unreachable, error: %d", err); ++ return; ++ } else if (err) { ++ LOG_ERR("nrf_cloud_coap_message_send, error: %d", err); ++ SEND_FATAL_ERROR(); ++ return; ++ } ++ + return; + } + } +diff --git a/app/src/modules/environmental/environmental.c b/app/src/modules/environmental/environmental.c +index 9caa7aa..5767f91 100644 +--- a/app/src/modules/environmental/environmental.c ++++ b/app/src/modules/environmental/environmental.c +@@ -65,10 +65,16 @@ struct environmental_state { + /* Pointer to the BME680 sensor device */ + const struct device *const bme680; + ++ /* Pointer to the BMM350 sensor device */ ++ const struct device *const bmm350; ++ + /* Sensor values */ + double temperature; + double pressure; + double humidity; ++ ++ /* Magnetic field values */ ++ double magnetic_field[3]; + }; + + /* Forward declarations of state handlers */ +@@ -79,12 +85,13 @@ static const struct smf_state states[] = { + SMF_CREATE_STATE(NULL, state_running_run, NULL, NULL, NULL), + }; + +-static void sample_sensors(const struct device *const bme680) ++static void sample_sensors(const struct device *const bme680, const struct device *const bmm350) + { + int err; + struct sensor_value temp = { 0 }; + struct sensor_value press = { 0 }; + struct sensor_value humidity = { 0 }; ++ struct sensor_value magnetic_field[3] = { { 0 }, { 0 }, { 0 } }; + + err = sensor_sample_fetch(bme680); + if (err) { +@@ -114,17 +121,37 @@ static void sample_sensors(const struct device *const bme680) + return; + } + ++ err = sensor_sample_fetch(bmm350); ++ if (err) { ++ LOG_ERR("sensor_sample_fetch, error: %d", err); ++ SEND_FATAL_ERROR(); ++ return; ++ } ++ ++ err = sensor_channel_get(bmm350, SENSOR_CHAN_MAGN_XYZ, magnetic_field); ++ if (err) { ++ LOG_ERR("sensor_channel_get, error: %d", err); ++ SEND_FATAL_ERROR(); ++ return; ++ } ++ + struct environmental_msg msg = { + .type = ENVIRONMENTAL_SENSOR_SAMPLE_RESPONSE, + .temperature = sensor_value_to_double(&temp), + .pressure = sensor_value_to_double(&press), + .humidity = sensor_value_to_double(&humidity), ++ .magnetic_field[0] = sensor_value_to_double(&magnetic_field[0]), ++ .magnetic_field[1] = sensor_value_to_double(&magnetic_field[1]), ++ .magnetic_field[2] = sensor_value_to_double(&magnetic_field[2]), + }; + + /* Log the environmental values and limit to 2 decimals */ + LOG_DBG("Temperature: %.2f C, Pressure: %.2f Pa, Humidity: %.2f %%", + msg.temperature, msg.pressure, msg.humidity); + ++ LOG_DBG("Magnetic field data: X: %.2f G, Y: %.2f G, Z: %.2f G", ++ msg.magnetic_field[0], msg.magnetic_field[1], msg.magnetic_field[2]); ++ + err = zbus_chan_pub(&ENVIRONMENTAL_CHAN, &msg, K_NO_WAIT); + if (err) { + LOG_ERR("zbus_chan_pub, error: %d", err); +@@ -152,7 +179,7 @@ static void state_running_run(void *o) + + if (msg.type == ENVIRONMENTAL_SENSOR_SAMPLE_REQUEST) { + LOG_DBG("Environmental values sample request received, getting data"); +- sample_sensors(state_object->bme680); ++ sample_sensors(state_object->bme680, state_object->bmm350); + } + } + } +@@ -170,6 +197,7 @@ static void environmental_task(void) + const k_timeout_t zbus_wait_ms = K_MSEC(wdt_timeout_ms - execution_time_ms); + struct environmental_state environmental_state = { + .bme680 = DEVICE_DT_GET(DT_NODELABEL(bme680)), ++ .bmm350 = DEVICE_DT_GET(DT_NODELABEL(magnetometer)), + }; + + LOG_DBG("Environmental module task started"); +diff --git a/app/src/modules/environmental/environmental.h b/app/src/modules/environmental/environmental.h +index 12e1c46..5c896b3 100644 +--- a/app/src/modules/environmental/environmental.h ++++ b/app/src/modules/environmental/environmental.h +@@ -47,6 +47,9 @@ struct environmental_msg { + + /** Contains the current pressure in Pa. */ + double pressure; ++ ++ /** Contains the current magnetic field values in gauss. */ ++ double magnetic_field[3]; + }; + + #define MSG_TO_ENVIRONMENTAL_MSG(_msg) (*(const struct environmental_msg *)_msg)