diff --git a/CODEOWNERS b/CODEOWNERS index 44959e814b77..35dd12ceaade 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -38,6 +38,7 @@ /applications/nrf_desktop/ @MarekPieta /applications/nrf5340_audio/ @nrfconnect/ncs-audio /applications/audio/ @nrfconnect/ncs-audio +/applications/peripheral_sensor_node/ @rakons maje-emb /applications/serial_lte_modem/ @SeppoTakalo @MarkusLassila @rlubos @tomi-font /applications/zigbee_weather_station/ @milewr # Boards diff --git a/applications/peripheral_sensor_node/CMakeLists.txt b/applications/peripheral_sensor_node/CMakeLists.txt new file mode 100644 index 000000000000..7b7ec91180ec --- /dev/null +++ b/applications/peripheral_sensor_node/CMakeLists.txt @@ -0,0 +1,45 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +cmake_minimum_required(VERSION 3.20.0) + +if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/configuration/${BOARD}") + message(FATAL_ERROR + "Board ${BOARD} is not supported.\n" + "Please make sure board specific configuration files are added to " + "${CMAKE_CURRENT_SOURCE_DIR}/configuration/${BOARD}") +endif() + +################################################################################ + +# The application uses the configuration/ scheme for configuration files. +# Configuring here to ensure BOARD contains BOARD_REVISION +set(APPLICATION_CONFIG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/configuration/\${NORMALIZED_BOARD_TARGET}") + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project("Peripheral Sensor Node" + VERSION 0.1) + +################################################################################ + +# NORDIC SDK APP START +target_sources(app PRIVATE + src/main.c + ) +# NORDIC SDK APP END + +# Include application events and configuration headers +target_include_directories(app PRIVATE + src/events + src/modules +) + +zephyr_include_directories( + configuration/common + ${APPLICATION_CONFIG_DIR} +) + +add_subdirectory(src/events) +add_subdirectory(src/modules) diff --git a/applications/peripheral_sensor_node/Kconfig b/applications/peripheral_sensor_node/Kconfig new file mode 100644 index 000000000000..66761f8fa837 --- /dev/null +++ b/applications/peripheral_sensor_node/Kconfig @@ -0,0 +1,14 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "Peripheral sensor node application" +rsource "src/events/Kconfig" +rsource "src/modules/Kconfig" +endmenu + +menu "Zephyr Kernel" +source "Kconfig.zephyr" +endmenu diff --git a/applications/peripheral_sensor_node/configuration/common/sensor_sampler.h b/applications/peripheral_sensor_node/configuration/common/sensor_sampler.h new file mode 100644 index 000000000000..7c336aa9dc4a --- /dev/null +++ b/applications/peripheral_sensor_node/configuration/common/sensor_sampler.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _SENSOR_SAMPLER_H_ +#define _SENSOR_SAMPLER_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Description of single channel + * + * The description of the channel to do the measurement on. + */ +struct sampled_channel { + /** @brief Channel identifier */ + enum sensor_channel chan; + /** @brief Number of data samples in selected channel */ + uint8_t data_cnt; +}; + +/** + * @brief Sensor configuration + * + * The sensor configuration is provided by the application in file specified by + * the :kconfig:option:`CONFIG_CAF_SENSOR_SAMPLER_DEF_PATH` option. + */ +struct sensor_config { + /** + * @brief Device + */ + const struct device *dev; + /** + * @brief Event descriptor + * + * Descriptor that would be used to identify source of sensor_events + * by the application modules. + */ + const char *event_descr; + /** + * @brief Used channels description + * + * Channels in the sensor that should be handled by sensor sampler module + */ + const struct sampled_channel *chans; + /** + * @brief Number of channels + * + * Number of channels handled for the sensor + */ + uint8_t chan_cnt; + /** + * @brief Allowed number of unprocessed events + * + * This is a protection against OOM error when event processing is blocked. + * No more events related to this sensor than the number defined would + * be passed to Application Event Manager. + */ + uint8_t events_limit; +}; + + +#ifdef __cplusplus +} +#endif + +#endif /* _SENSOR_SAMPLER_H_ */ diff --git a/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/app.overlay b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/app.overlay new file mode 100644 index 000000000000..0ccf4fed23ef --- /dev/null +++ b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/app.overlay @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/{ + /delete-node/ leds; + + leds1 { + compatible = "gpio-leds"; + led1: led_1 { + gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>; + label = "Green LED 1"; + }; + }; + + aliases { + /delete-property/ led0; + /delete-property/ led1; + /delete-property/ led2; + /delete-property/ led3; + /delete-property/ sw0; + }; +}; + +/delete-node/ &button0; // P1.13 is bmi270 chip select signal + + &cpuapp_rram { + /delete-node/ partitions; + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + boot_partition: partition@0 { + label = "mcuboot"; + reg = <0x0 DT_SIZE_K(64)>; + }; + slot0_partition: partition@10000 { + label = "image-0"; + reg = <0x10000 DT_SIZE_K(312)>; + }; + slot0_ns_partition: partition@5e000 { + label = "image-0-nonsecure"; + reg = <0x5e000 DT_SIZE_K(312)>; + }; + slot1_partition: partition@ac000 { + label = "image-1"; + reg = <0xac000 DT_SIZE_K(312)>; + }; + slot1_ns_partition: partition@fa000 { + label = "image-1-nonsecure"; + reg = <0xfa000 DT_SIZE_K(312)>; + }; + sensor_data_storage: partition@148000 { + label = "sensor-data-storage"; + reg = <0x00148000 DT_SIZE_K(48)>; + }; + /* 32k from 0x154000 to 0x15bfff reserved for TF-M partitions */ + storage_partition: partition@15c000 { + label = "storage"; + reg = <0x15c000 DT_SIZE_K(36)>; + }; + }; + }; diff --git a/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/led_state_def.h b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/led_state_def.h new file mode 100644 index 000000000000..91c7d5ecafb2 --- /dev/null +++ b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/led_state_def.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +/** + * @file + * @brief Configuration file for LED states and effects. + * + * This configuration file is included only once from led_state module and holds + * information about LED effect associated with each state. + */ + +/** + * @brief Structure to ensure single inclusion of this header file. + * + * This structure enforces the header file is included only once in the build. + * Violating this requirement triggers a multiple definition error at link time. + */ +const struct {} led_state_def_include_once; + +/** @brief Enumeration of LED identifiers. */ +enum led_id { + LED_ID_1, /**< Identifier for the LED 1. */ + + LED_ID_COUNT /**< Total count of LED identifiers. */ +}; + +/** @brief LED effect for turning the LED. */ +static const struct led_effect led_effect_on = LED_EFFECT_LED_ON(LED_COLOR(255, 255, 255)); diff --git a/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/prj.conf b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/prj.conf new file mode 100644 index 000000000000..031ffb84b36e --- /dev/null +++ b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/prj.conf @@ -0,0 +1,92 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_HEAP_MEM_POOL_SIZE=2048 + +CONFIG_GPIO=y + +# Enable UART console +CONFIG_SERIAL=y +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_UART_ASYNC_API=y +# Enable float printing +CONFIG_NEWLIB_LIBC=y +CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y + +CONFIG_BT=y +CONFIG_BT_SMP=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_DEVICE_NAME="Peripheral Sensor Node" +CONFIG_BT_DEVICE_APPEARANCE=833 +CONFIG_BT_ADV_PROV_DEVICE_NAME=y +CONFIG_BT_MAX_CONN=1 +CONFIG_BT_MAX_PAIRED=1 +CONFIG_BT_L2CAP_TX_MTU=498 +CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 + +# Enable the Nordic Status Message service +CONFIG_BT_NSMS=y +CONFIG_BT_BUF_ACL_RX_SIZE=502 +CONFIG_BT_BUF_ACL_TX_SIZE=502 +CONFIG_BT_L2CAP_TX_MTU=247 +CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 + +# Enable bonding +CONFIG_BT_SETTINGS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y +CONFIG_SETTINGS=y + +# This example requires more workqueue stack +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 + +# Config logger +CONFIG_LOG=y +CONFIG_LOG_BACKEND_UART=y +CONFIG_FILE_SYSTEM=y +CONFIG_LOG_PRINTK=n + +CONFIG_ASSERT=y + +CONFIG_SENSOR=y +CONFIG_ADXL362=y +CONFIG_ADXL362_ACCEL_RANGE_8G=y +CONFIG_ADXL362_ACCEL_ODR_100=y +CONFIG_ADXL362_TRIGGER_GLOBAL_THREAD=y +CONFIG_ADXL362_INACTIVITY_THRESHOLD=0 +CONFIG_ADXL362_ACTIVITY_THRESHOLD=70 +CONFIG_ADXL362_ABS_REF_MODE=1 + +CONFIG_APP_EVENT_MANAGER=y +CONFIG_REBOOT=y + +# Dependencies for CAF_LEDS +CONFIG_LED=y + +# CAF modules configuration +CONFIG_CAF=y +CONFIG_CAF_LEDS=y +CONFIG_CAF_BLE_ADV=y +CONFIG_CAF_BLE_STATE=y +CONFIG_CAF_SETTINGS_LOADER=y + +# CAF events configuration +CONFIG_CAF_INIT_LOG_SENSOR_EVENTS=n + +# Application configuration +CONFIG_APP_SENSORS=y + +# Enable nordic security backend and PSA APIs +CONFIG_NRF_SECURITY=y +CONFIG_MBEDTLS_PSA_CRYPTO_C=y + +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=8192 + +CONFIG_PSA_WANT_GENERATE_RANDOM=y diff --git a/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/sensor_def.h b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/sensor_def.h new file mode 100644 index 000000000000..2acacffc3bb8 --- /dev/null +++ b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/sensor_def.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "sensor_sampler.h" + +/** + * @file + * @brief Configuration file for sampled sensors. + * + * This configuration file is included only once from sensor_sampler module and holds + * information about the sampled sensors. + */ + +/** + * @brief Structure to ensure single inclusion of this header file. + * + * This structure enforces the header file is included only once in the build. + * Violating this requirement triggers a multiple definition error at link time. + */ +const struct {} app_sensors_def_include_once; + +/** @brief Configuration of sampled channels for the BME688 sensor. */ +static const struct sampled_channel bme688_chan[] = { + { + .chan = SENSOR_CHAN_AMBIENT_TEMP, /**< Ambient temperature channel. */ + .data_cnt = 1, /**< Number of data samples. */ + }, + { + .chan = SENSOR_CHAN_PRESS, /**< Pressure channel. */ + .data_cnt = 1, /**< Number of data samples. */ + }, + { + .chan = SENSOR_CHAN_HUMIDITY, /**< Humidity channel. */ + .data_cnt = 1, /**< Number of data samples. */ + }, + { + .chan = SENSOR_CHAN_GAS_RES, /**< Gas resistance channel. */ + .data_cnt = 1, /**< Number of data samples. */ + }, +}; + +/** @brief Configuration of sampled channels for the BMI270 sensor. */ +static const struct sampled_channel bmi270_chan[] = { + { + .chan = SENSOR_CHAN_ACCEL_XYZ, /**< Accelerometer XYZ channel. */ + .data_cnt = 3, /**< Number of data samples. */ + }, + { + .chan = SENSOR_CHAN_GYRO_XYZ, /**< Gyroscope XYZ channel. */ + .data_cnt = 3, /**< Number of data samples. */ + }, +}; + +/** @brief Configuration of sampled channels for the ADXL362 sensor. */ +static const struct sampled_channel adxl362_chan[] = { + { + .chan = SENSOR_CHAN_ACCEL_XYZ, /**< Accelerometer XYZ channel. */ + .data_cnt = 3, /**< Gyroscope XYZ channel. */ + }, +}; + +/** @brief Configuration array for all sensors used in the application. */ +static const struct sensor_config sensor_configs[] = { + { + .dev = DEVICE_DT_GET(DT_NODELABEL(bme688)), /**< BME688 sensor device. */ + .event_descr = "env", /**< Event description. */ + .chans = bme688_chan, /**< Used channels description. */ + .chan_cnt = ARRAY_SIZE(bme688_chan), /**< Number of channels. */ + .events_limit = 3, /**< Maximum unprocessed events. */ + }, + { + .dev = DEVICE_DT_GET(DT_NODELABEL(bmi270)), /**< BMI270 sensor device. */ + .event_descr = "imu", /**< Event description. */ + .chans = bmi270_chan, /**< Used channels description. */ + .chan_cnt = ARRAY_SIZE(bmi270_chan), /**< Number of channels. */ + .events_limit = 3, /**< Maximum unprocessed events. */ + }, + { + .dev = DEVICE_DT_GET(DT_NODELABEL(adxl362)), /**< ADXL362 sensor device. */ + .event_descr = "wu_imu", /**< Event description. */ + .chans = adxl362_chan, /**< Used channels description. */ + .chan_cnt = ARRAY_SIZE(adxl362_chan), /**< Number of channels. */ + .events_limit = 3, /**< Maximum unprocessed events. */ + }, +}; diff --git a/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/settings_loader_def.h b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/settings_loader_def.h new file mode 100644 index 000000000000..7ebcb9842aa8 --- /dev/null +++ b/applications/peripheral_sensor_node/configuration/nrf54l15pdk_nrf54l15_cpuapp/settings_loader_def.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +/** + * @file + * @brief Configuration file for settings loader. + * + * This configuration file defines modules that need to be loaded before + * calling settings_load. + */ + +/** + * @brief Structure to ensure single inclusion of this header file. + * + * This structure enforces the header file is included only once in the build. + * Violating this requirement triggers a multiple definition error at link time. + */ +const struct {} settings_loader_def_include_once; + +/** + * @brief Set required modules that need to be loaded. + * + * @param mf Pointer to the structure holding module flags. + */ +static void get_req_modules(struct module_flags *mf) +{ + module_flags_set_bit(mf, MODULE_IDX(main)); +#ifdef CONFIG_CAF_BLE_STATE + module_flags_set_bit(mf, MODULE_IDX(ble_state)); +#endif +} diff --git a/applications/peripheral_sensor_node/sample.yaml b/applications/peripheral_sensor_node/sample.yaml new file mode 100644 index 000000000000..1e070a77faa0 --- /dev/null +++ b/applications/peripheral_sensor_node/sample.yaml @@ -0,0 +1,12 @@ +sample: + name: Peripheral Sensor Node + description: Peripheral Sensor Node application reference design +tests: + applications.peripheral_sensor_node: + sysbuild: true + build_only: true + platform_allow: nrf54l15pdk/nrf54l15/cpuapp + integration_platforms: + - nrf54l15pdk/nrf54l15/cpuapp + tags: ci_build sysbuild + extra_args: SHIELD=pca63565 diff --git a/applications/peripheral_sensor_node/src/events/CMakeLists.txt b/applications/peripheral_sensor_node/src/events/CMakeLists.txt new file mode 100644 index 000000000000..d93eb3dd1b28 --- /dev/null +++ b/applications/peripheral_sensor_node/src/events/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +target_sources_ifdef(CONFIG_APP_SENSOR_EVENTS app PRIVATE app_sensor_event.c) diff --git a/applications/peripheral_sensor_node/src/events/Kconfig b/applications/peripheral_sensor_node/src/events/Kconfig new file mode 100644 index 000000000000..3d2f1da5ce7b --- /dev/null +++ b/applications/peripheral_sensor_node/src/events/Kconfig @@ -0,0 +1,11 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "Event options" + +rsource "Kconfig.app_sensor_event" + +endmenu diff --git a/applications/peripheral_sensor_node/src/events/Kconfig.app_sensor_event b/applications/peripheral_sensor_node/src/events/Kconfig.app_sensor_event new file mode 100644 index 000000000000..9fe7bc5c1cc0 --- /dev/null +++ b/applications/peripheral_sensor_node/src/events/Kconfig.app_sensor_event @@ -0,0 +1,18 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config APP_SENSOR_EVENTS + bool "Enable application sensor events" + help + Enable support for application sensor events. + +config APP_INIT_LOG_SENSOR_EVENTS + bool "Log app sensor events" + depends on APP_SENSOR_EVENTS + depends on LOG + default y + help + Log app sensor events, used to notify about sensor start/stop. diff --git a/applications/peripheral_sensor_node/src/events/app_sensor_event.c b/applications/peripheral_sensor_node/src/events/app_sensor_event.c new file mode 100644 index 000000000000..6d6d74606f7c --- /dev/null +++ b/applications/peripheral_sensor_node/src/events/app_sensor_event.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "app_sensor_event.h" + +static void log_sensor_start_event(const struct app_event_header *aeh) +{ + const struct sensor_start_event *event = cast_sensor_start_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "Start %s sensor, delay:%u, period:%u", + event->descr, event->delay, event->period); +} + +APP_EVENT_TYPE_DEFINE(sensor_start_event, + log_sensor_start_event, + NULL, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); + + +static void log_sensor_stop_event(const struct app_event_header *aeh) +{ + const struct sensor_stop_event *event = cast_sensor_stop_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "Stop %s sensor", event->descr); +} + +APP_EVENT_TYPE_DEFINE(sensor_stop_event, + log_sensor_stop_event, + NULL, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/applications/peripheral_sensor_node/src/events/app_sensor_event.h b/applications/peripheral_sensor_node/src/events/app_sensor_event.h new file mode 100644 index 000000000000..17afcdf68dca --- /dev/null +++ b/applications/peripheral_sensor_node/src/events/app_sensor_event.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _APP_SENSOR_EVENT_H_ +#define _APP_SENSOR_EVENT_H_ + +/** + * @file + * @defgroup app_sensor_event App Sensor Event + * @{ + * @brief App Sensor Event. + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Sensor start event. */ +struct sensor_start_event { + struct app_event_header header; /**< Event header. */ + + const char *descr; /**< Description of the sensor. */ + uint32_t delay; /**< Initial sampling delay in milliseconds. */ + uint32_t period; /**< Sampling period in milliseconds. */ +}; + +APP_EVENT_TYPE_DECLARE(sensor_start_event); + + +/** @brief Sensor stop event. */ +struct sensor_stop_event { + struct app_event_header header; /**< Event header. */ + + const char *descr; /**< Description of the sensor. */ +}; + +APP_EVENT_TYPE_DECLARE(sensor_stop_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _APP_SENSOR_EVENT_H_ */ diff --git a/applications/peripheral_sensor_node/src/main.c b/applications/peripheral_sensor_node/src/main.c new file mode 100644 index 000000000000..11aa9b72f36d --- /dev/null +++ b/applications/peripheral_sensor_node/src/main.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#define MODULE main +#include + +#include +LOG_MODULE_REGISTER(MODULE); + + +int main(void) +{ + if (app_event_manager_init()) { + LOG_ERR("Application Event Manager initialization failed"); + } else { + module_set_state(MODULE_STATE_READY); + } +} diff --git a/applications/peripheral_sensor_node/src/modules/CMakeLists.txt b/applications/peripheral_sensor_node/src/modules/CMakeLists.txt new file mode 100644 index 000000000000..dad28c7e07ef --- /dev/null +++ b/applications/peripheral_sensor_node/src/modules/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +target_sources(app PRIVATE + app_control.c + nsms.c + env_history.c + led_state.c +) + +target_sources_ifdef(CONFIG_APP_SENSORS app PRIVATE sensors.c) diff --git a/applications/peripheral_sensor_node/src/modules/Kconfig b/applications/peripheral_sensor_node/src/modules/Kconfig new file mode 100644 index 000000000000..3167ea3a9ca7 --- /dev/null +++ b/applications/peripheral_sensor_node/src/modules/Kconfig @@ -0,0 +1,12 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menu "Application modules" + +rsource "Kconfig.app_control" +rsource "Kconfig.sensors" + +endmenu diff --git a/applications/peripheral_sensor_node/src/modules/Kconfig.app_control b/applications/peripheral_sensor_node/src/modules/Kconfig.app_control new file mode 100644 index 000000000000..c43d776a717f --- /dev/null +++ b/applications/peripheral_sensor_node/src/modules/Kconfig.app_control @@ -0,0 +1,44 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig APP_CONTROL + bool "Application control" + depends on CAF + select APP_SENSOR_EVENTS + select CAF_SENSOR_EVENTS + select CAF_PM_EVENTS + default y + +if APP_CONTROL + +config APP_CONTROL_BURST_INTERVAL_S + int "Burst interval [s]" + range 60 5400 + default 120 + help + Time in seconds between burst data acquisition from sensors. + If sensor data burst is not triggered by wake_up event from IMU, + burst is performed every interval value. + +config APP_CONTROL_BURST_MEAS_CNT + int "Number of measurements for a single burst" + range 1 30 + default 10 + help + Number of measurements single burst acquires before going to sleep. + +config APP_CONTROL_BURST_MEAS_INTERVAL_MS + int "Burst measurements interval [ms]" + range 1000 15000 + default 1000 + help + Interval between measurements for a single burst. + +module = APP_CONTROL +module-str = app control +source "subsys/logging/Kconfig.template.log_config" + +endif # APP_CONTROL diff --git a/applications/peripheral_sensor_node/src/modules/Kconfig.sensors b/applications/peripheral_sensor_node/src/modules/Kconfig.sensors new file mode 100644 index 000000000000..71421cdc4dcc --- /dev/null +++ b/applications/peripheral_sensor_node/src/modules/Kconfig.sensors @@ -0,0 +1,41 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig APP_SENSORS + bool "Sensors module" + depends on SENSOR + select APP_SENSOR_EVENTS + select CAF_SENSOR_EVENTS + help + The module is handling sensor sampling and management. + +if APP_SENSORS + +config APP_SENSORS_DEF_PATH + string "Configuration file" + default "sensor_def.h" + help + Location of configuration file for sensor sampler module. + +config APP_SENSORS_THREAD_STACK_SIZE + int "Size of sensor sampler thread stack" + default 2048 + help + The module samples sensors in a dedicated thread. The thread stack size must be big + enough for used sensors. + +config APP_SENSORS_THREAD_PRIORITY + int "Priority of sensor sampler thread" + default 2 + help + It is recommended to use preemptive thread priority to make sure that the thread will + not block other operations in the system. + +module = APP_SENSORS +module-str = app sensors module +source "subsys/logging/Kconfig.template.log_config" + +endif # APP_SENSORS diff --git a/applications/peripheral_sensor_node/src/modules/app_control.c b/applications/peripheral_sensor_node/src/modules/app_control.c new file mode 100644 index 000000000000..3a24c7e24e07 --- /dev/null +++ b/applications/peripheral_sensor_node/src/modules/app_control.c @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include + +#include "app_sensor_event.h" + +#define MODULE app_control +#include + +#define BURST_INTERVAL CONFIG_APP_CONTROL_BURST_INTERVAL_S +#define BURST_MEAS_CNT CONFIG_APP_CONTROL_BURST_MEAS_CNT +#define BURST_MEAS_INTERVAL CONFIG_APP_CONTROL_BURST_MEAS_INTERVAL_MS + +static size_t meas_cnt; +static struct k_work_delayable burst_trigger; + + +static void burst_trigger_fn(struct k_work *w) +{ + APP_EVENT_SUBMIT(new_wake_up_event()); + + k_work_reschedule(&burst_trigger, K_SECONDS(BURST_INTERVAL)); +} + +static void init(void) +{ + k_work_init_delayable(&burst_trigger, burst_trigger_fn); + k_work_schedule(&burst_trigger, K_NO_WAIT); + module_set_state(MODULE_STATE_READY); +}; + +/** + * @brief Process the wake_up_event. + * + * @param[in] event Wake up event. + * @return true Consume the event. + * @return false Do not consume the event. + */ +static bool handle_wake_up_event(const struct wake_up_event *event) +{ + /* Cancel cannot fail if executed from another work's context. */ + (void)k_work_cancel_delayable(&burst_trigger); + meas_cnt = 0; + + struct sensor_start_event *start_env = new_sensor_start_event(); + + start_env->descr = "env"; + start_env->delay = 0; + start_env->period = BURST_MEAS_INTERVAL; + + struct sensor_start_event *start_bmi = new_sensor_start_event(); + + start_bmi->descr = "imu"; + start_bmi->delay = 0; + start_bmi->period = BURST_MEAS_INTERVAL; + + APP_EVENT_SUBMIT(start_bmi); + APP_EVENT_SUBMIT(start_env); + + return false; +} + +/** + * @brief Process the power_down_event. + * + * @param[in] event Power down event. + * @return true Consume the event. + * @return false Do not consume the event. + */ +static bool handle_power_down_event(const struct power_down_event *event) +{ + k_work_reschedule(&burst_trigger, K_SECONDS(BURST_INTERVAL)); + + struct sensor_stop_event *stop_env = new_sensor_stop_event(); + struct sensor_stop_event *stop_imu = new_sensor_stop_event(); + + stop_env->descr = "env"; + stop_imu->descr = "imu"; + + APP_EVENT_SUBMIT(stop_env); + APP_EVENT_SUBMIT(stop_imu); + + return false; +} + +/** + * @brief Process the sensor_event. + * + * @param event Sensor event. + * @return true Consume the event. + * @return false Do not consume the event. + */ +static bool handle_sensor_event(const struct sensor_event *event) +{ + if (!strcmp(event->descr, "env")) { + if (++meas_cnt == BURST_MEAS_CNT) { + meas_cnt = 0; + + struct power_down_event *evt = new_power_down_event(); + + evt->error = false; + APP_EVENT_SUBMIT(evt); + } + } + + return false; +} + +/** + * @brief Handler for application event manager events. + * + * @param[in] aeh Application event header. + * @return true Consume the event. + * @return false Do not consume the event. + */ +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_wake_up_event(aeh)) { + return handle_wake_up_event(cast_wake_up_event(aeh)); + } + + if (is_power_down_event(aeh)) { + return handle_power_down_event(cast_power_down_event(aeh)); + } + + if (is_sensor_event(aeh)) { + return handle_sensor_event(cast_sensor_event(aeh)); + } + + if (is_module_state_event(aeh)) { + const struct module_state_event *event = cast_module_state_event(aeh); + + if (check_state(event, MODULE_ID(app_sensors), MODULE_STATE_READY)) { + static bool initialized; + + __ASSERT_NO_MSG(!initialized); + init(); + initialized = true; + } + + return false; + } + + /* If event is unhandled, unsubscribe. */ + __ASSERT_NO_MSG(false); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); +APP_EVENT_SUBSCRIBE(MODULE, wake_up_event); +APP_EVENT_SUBSCRIBE(MODULE, power_down_event); +APP_EVENT_SUBSCRIBE(MODULE, sensor_event); diff --git a/applications/peripheral_sensor_node/src/modules/env_history.c b/applications/peripheral_sensor_node/src/modules/env_history.c new file mode 100644 index 000000000000..a619898802d7 --- /dev/null +++ b/applications/peripheral_sensor_node/src/modules/env_history.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "app_sensor_event.h" + +#define MODULE env_history +#include + +LOG_MODULE_REGISTER(MODULE); + +size_t strnlen(const char *s, size_t maxlen); + +#define NVS_PARTITION sensor_data_storage +#define NVS_PARTITION_DEVICE FIXED_PARTITION_DEVICE(NVS_PARTITION) +#define NVS_PARTITION_OFFSET FIXED_PARTITION_OFFSET(NVS_PARTITION) +#define NVS_PARTITION_SIZE FIXED_PARTITION_SIZE(NVS_PARTITION) + +#define BT_UUID_PSN_VAL \ + BT_UUID_128_ENCODE(0xde550000, 0xc9f9, 0x4f0d, 0xa6e1, 0x766437949322) + +#define BT_UUID_PSN_READ_REQ_VAL \ + BT_UUID_128_ENCODE(0xde550002, 0xacb6, 0x4c73, 0x8445, 0x2563acbb43c2) + +#define BT_UUID_PSN_READ_VAL \ + BT_UUID_128_ENCODE(0xde550004, 0xacb6, 0x4c73, 0x8445, 0x2563acbb43c2) + +#define BT_UUID_PSN_SERVICE BT_UUID_DECLARE_128(BT_UUID_PSN_VAL) +#define BT_UUID_PSN_READ_REQ BT_UUID_DECLARE_128(BT_UUID_PSN_READ_REQ_VAL) +#define BT_UUID_PSN_READ BT_UUID_DECLARE_128(BT_UUID_PSN_READ_VAL) + +#define ENV_VALUES_CNT 4 +#define ENV_NVS_ID 0x859 +#define TMP_BUF_SIZE 64 +#define XFER_BUF_SIZE 250 +#define MTX_LOCK_WAIT_TIME K_MSEC(200) + +#define PSN_ENV_SVC_ATTR_IDX 2 + +#define DATA_SIZE_MSG "use uint16_t little endian as input" +#define NO_DATA_MSG "No data" + +static char xfer_buf[XFER_BUF_SIZE]; +static struct nvs_fs fs = { + .flash_device = NVS_PARTITION_DEVICE, + .offset = NVS_PARTITION_OFFSET, +}; + +static int xfer_cnt; +static int xfer_read_id; +static uint16_t xfer_depth; +static uint32_t latest_id; +static K_MUTEX_DEFINE(nvs_mtx); + +struct env_storage_chunk { + uint32_t id; + struct sensor_value values[ENV_VALUES_CNT]; +}; + +size_t env_get_history(char *print_buf, const uint16_t depth) +{ + struct env_storage_chunk env_chunk; + + k_mutex_lock(&nvs_mtx, MTX_LOCK_WAIT_TIME); + int rc = nvs_read_hist(&fs, ENV_NVS_ID, &env_chunk, sizeof(env_chunk), depth); + + k_mutex_unlock(&nvs_mtx); + + if (rc < 0) { + return -EOVERFLOW; + } + int len = sprintf(print_buf, "%d ", env_chunk.id); + + for (size_t i = 0; i < ENV_VALUES_CNT; i++) { + float tmp = sensor_value_to_double(&env_chunk.values[i]); + + len += sprintf(print_buf + len, "%.2f ", (double)tmp); + } + + return len; +} + +static ssize_t bt_psn_read_req(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags); + +BT_GATT_SERVICE_DEFINE(psn_env_svc, + BT_GATT_PRIMARY_SERVICE(BT_UUID_PSN_SERVICE), + BT_GATT_CHARACTERISTIC(BT_UUID_PSN_READ, + BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ, + NULL, + NULL, + NULL), + BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), + BT_GATT_CUD("Env read", BT_GATT_PERM_READ), + BT_GATT_CHARACTERISTIC(BT_UUID_PSN_READ_REQ, + BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, + NULL, + bt_psn_read_req, + NULL), + BT_GATT_CCC(NULL, BT_GATT_PERM_READ), + BT_GATT_CUD("Env read req", BT_GATT_PERM_READ), +); + +static const struct bt_gatt_attr *psn_read_attr = &psn_env_svc.attrs[PSN_ENV_SVC_ATTR_IDX]; + +static void send_tail(struct bt_conn *conn, void *user_data) +{ + ARG_UNUSED(user_data); + + char tmp_buf[TMP_BUF_SIZE]; + int pos = 0; + + if (latest_id == 0) { + bt_gatt_notify(conn, psn_read_attr, NO_DATA_MSG, sizeof(NO_DATA_MSG)-1); + LOG_INF("%s", NO_DATA_MSG); + } + + while (xfer_cnt < xfer_depth) { + int len = env_get_history(tmp_buf, (latest_id - xfer_read_id)); + + if (len < 0) { + xfer_depth = xfer_cnt; + break; + } + + if ((XFER_BUF_SIZE - pos) > len) { + memcpy(xfer_buf+pos, tmp_buf, len); + pos += len; + xfer_read_id--; + xfer_cnt++; + } else { + break; + } + } + + if (pos > 0) { + struct bt_gatt_notify_params params = { + .attr = psn_read_attr, + .data = xfer_buf, + .len = pos, + .func = send_tail, + }; + bt_gatt_notify_cb(conn, ¶ms); + + LOG_INF("size %d up to %d", pos, xfer_cnt); + } +} + +static ssize_t bt_psn_read_req(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags) +{ + if (len != sizeof(uint16_t)) { + bt_gatt_notify(conn, psn_read_attr, DATA_SIZE_MSG, sizeof(DATA_SIZE_MSG)-1); + LOG_INF("%s", DATA_SIZE_MSG); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + xfer_cnt = 0; + xfer_depth = *((uint16_t *)buf); + + xfer_read_id = latest_id; + send_tail(conn, NULL); + + return 0; +} + +static void init(void) +{ + struct flash_pages_info info; + + if (!device_is_ready(fs.flash_device)) { + LOG_ERR("Flash device %s is not ready.", fs.flash_device->name); + return; + } + int rc = flash_get_page_info_by_offs(fs.flash_device, fs.offset, &info); + + if (rc) { + LOG_ERR("Unable to get page info."); + return; + } + + fs.sector_size = info.size; + fs.sector_count = NVS_PARTITION_SIZE / fs.sector_size; + + rc = nvs_mount(&fs); + if (rc) { + LOG_ERR("Flash Init failed."); + return; + } + + struct env_storage_chunk latest_env; + + rc = nvs_read(&fs, ENV_NVS_ID, &latest_env, sizeof(latest_env)); + if (rc > 0) { + LOG_INF("NVS latest id %d", latest_env.id); + latest_id = latest_env.id; + } else { + latest_id = 0; + } + + module_set_state(MODULE_STATE_READY); +} + +static bool handle_sensor_event(const struct sensor_event *event) +{ + size_t data_cnt = sensor_event_get_data_cnt(event); + const struct sensor_value *data_ptr = sensor_event_get_data_ptr(event); + + if (!strcmp(event->descr, "env")) { + struct env_storage_chunk env_chunk; + + memcpy(&env_chunk.values, data_ptr, data_cnt*sizeof(struct sensor_value)); + latest_id++; + env_chunk.id = latest_id; + + k_mutex_lock(&nvs_mtx, MTX_LOCK_WAIT_TIME); + nvs_write(&fs, ENV_NVS_ID, &env_chunk, sizeof(env_chunk)); + k_mutex_unlock(&nvs_mtx); + } + + return false; +} + + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_sensor_event(aeh)) { + return handle_sensor_event(cast_sensor_event(aeh)); + } + + if (is_module_state_event(aeh)) { + const struct module_state_event *event = cast_module_state_event(aeh); + + if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { + init(); + } + + return false; + } + + /* If event is unhandled, unsubscribe. */ + __ASSERT_NO_MSG(false); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); +APP_EVENT_SUBSCRIBE(MODULE, sensor_event); diff --git a/applications/peripheral_sensor_node/src/modules/led_state.c b/applications/peripheral_sensor_node/src/modules/led_state.c new file mode 100644 index 000000000000..d8b43854d1ab --- /dev/null +++ b/applications/peripheral_sensor_node/src/modules/led_state.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include + +#define MODULE led_state +#include + +#include "led_state_def.h" + +static bool handle_wake_up_event(const struct wake_up_event *evt) +{ + struct led_event *event = new_led_event(); + + event->led_id = LED_ID_1; + event->led_effect = &led_effect_on; + APP_EVENT_SUBMIT(event); + + return false; +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_wake_up_event(aeh)) { + return handle_wake_up_event(cast_wake_up_event(aeh)); + } + + /* If event is unhandled, unsubscribe. */ + __ASSERT_NO_MSG(false); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, wake_up_event); diff --git a/applications/peripheral_sensor_node/src/modules/nsms.c b/applications/peripheral_sensor_node/src/modules/nsms.c new file mode 100644 index 000000000000..70b12ddf3c08 --- /dev/null +++ b/applications/peripheral_sensor_node/src/modules/nsms.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include +#include + +#include +#include + +#include "app_sensor_event.h" + +#define MODULE nsms +#include + +#include +LOG_MODULE_REGISTER(MODULE); + + +#define BUF_SIZE 64 + +BT_NSMS_DEF(nsms_imu, "IMU", false, "Unknown", BUF_SIZE); +BT_NSMS_DEF(nsms_env, "Environmental", false, "Unknown", BUF_SIZE); + +static void init(void) +{ + module_set_state(MODULE_STATE_READY); +} + +static bool handle_sensor_event(const struct sensor_event *event) +{ + size_t data_cnt = sensor_event_get_data_cnt(event); + float float_data[data_cnt]; + const struct sensor_value *data_ptr = sensor_event_get_data_ptr(event); + + char buf[BUF_SIZE]; + + int len = sprintf(buf, "%s ", event->descr); + + for (size_t i = 0; i < data_cnt; i++) { + float_data[i] = sensor_value_to_double(&data_ptr[i]); + + len += sprintf(buf + len, "%.3f ", (double)float_data[i]); + } + + LOG_INF("%s", buf); + + if (!strcmp(event->descr, "env")) { + bt_nsms_set_status(&nsms_env, buf); + } else if (!strcmp(event->descr, "imu")) { + bt_nsms_set_status(&nsms_imu, buf); + } + + return false; +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_sensor_event(aeh)) { + return handle_sensor_event(cast_sensor_event(aeh)); + } + + if (is_module_state_event(aeh)) { + const struct module_state_event *event = cast_module_state_event(aeh); + + if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { + init(); + } + + return false; + } + + /* If event is unhandled, unsubscribe. */ + __ASSERT_NO_MSG(false); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); +APP_EVENT_SUBSCRIBE(MODULE, sensor_event); diff --git a/applications/peripheral_sensor_node/src/modules/sensors.c b/applications/peripheral_sensor_node/src/modules/sensors.c new file mode 100644 index 000000000000..21641528ddc5 --- /dev/null +++ b/applications/peripheral_sensor_node/src/modules/sensors.c @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include + +#include CONFIG_APP_SENSORS_DEF_PATH + +#include "app_sensor_event.h" + +#define MODULE app_sensors +#include +#include + +#include +LOG_MODULE_REGISTER(MODULE, CONFIG_APP_SENSORS_LOG_LEVEL); + +#define SAMPLE_THREAD_STACK_SIZE CONFIG_APP_SENSORS_THREAD_STACK_SIZE +#define SAMPLE_THREAD_PRIORITY CONFIG_APP_SENSORS_THREAD_PRIORITY + +struct sensor_data { + int64_t timeout; + uint32_t period; + uint32_t delay; + atomic_t state; + atomic_t event_cnt; +}; + +static struct sensor_data sensor_data[ARRAY_SIZE(sensor_configs)]; + +static K_THREAD_STACK_DEFINE(sample_thread_stack, SAMPLE_THREAD_STACK_SIZE); +static struct k_thread sample_thread; +static struct k_sem can_sample; + +static struct sensor_trigger trig = { + .type = SENSOR_TRIG_MOTION, + .chan = SENSOR_CHAN_ACCEL_XYZ, +}; + +static int bmi270_configure(const struct device *dev) +{ + struct sensor_value full_scale, sampling_freq, oversampling; + int ret = 0; + + /* Setting scale in G, due to loss of precision if the SI unit m/s^2 + * is used + */ + full_scale.val1 = 2; /* G */ + full_scale.val2 = 0; + sampling_freq.val1 = 100; /* Hz. Performance mode */ + sampling_freq.val2 = 0; + oversampling.val1 = 1; /* Normal mode */ + oversampling.val2 = 0; + + ret = sensor_attr_set(dev, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_FULL_SCALE, + &full_scale); + if (ret != 0) { + LOG_ERR("Failed to set %s attribute", dev->name); + return ret; + } + + ret = sensor_attr_set(dev, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_OVERSAMPLING, + &oversampling); + if (ret != 0) { + LOG_ERR("Failed to set %s attribute", dev->name); + return ret; + } + + /* Set sampling frequency last as this also sets the appropriate + * power mode. If already sampling, change to 0.0Hz before changing + * other attributes + */ + ret = sensor_attr_set(dev, SENSOR_CHAN_ACCEL_XYZ, + SENSOR_ATTR_SAMPLING_FREQUENCY, + &sampling_freq); + if (ret != 0) { + LOG_ERR("Failed to set %s attribute", dev->name); + return ret; + } + + /* Setting scale in degrees/s to match the sensor scale */ + full_scale.val1 = 500; /* dps */ + full_scale.val2 = 0; + sampling_freq.val1 = 100; /* Hz. Performance mode */ + sampling_freq.val2 = 0; + oversampling.val1 = 1; /* Normal mode */ + oversampling.val2 = 0; + + ret = sensor_attr_set(dev, SENSOR_CHAN_GYRO_XYZ, SENSOR_ATTR_FULL_SCALE, + &full_scale); + if (ret != 0) { + LOG_ERR("Failed to set %s attribute", dev->name); + return ret; + } + + ret = sensor_attr_set(dev, SENSOR_CHAN_GYRO_XYZ, SENSOR_ATTR_OVERSAMPLING, + &oversampling); + if (ret != 0) { + LOG_ERR("Failed to set %s attribute", dev->name); + return ret; + } + + /* Set sampling frequency last as this also sets the appropriate + * power mode. If already sampling, change sampling frequency to + * 0.0Hz before changing other attributes + */ + ret = sensor_attr_set(dev, SENSOR_CHAN_GYRO_XYZ, + SENSOR_ATTR_SAMPLING_FREQUENCY, + &sampling_freq); + if (ret != 0) { + LOG_ERR("Failed to set %s attribute", dev->name); + return ret; + } + + return ret; +} + +/** + * @brief Motion trigger callback. + * + * Callback registered to imu sensor, that is called on motion detection. + * + * @param dev Device. + * @param trigger Sensor trigger configuration. + */ +static void motion(const struct device *dev, const struct sensor_trigger *trigger) +{ + /* The procedure might be more complex, as sensor interrupt should be disabled. */ + int ret = sensor_trigger_set(dev, trigger, NULL); + + if (ret != 0) { + LOG_ERR("Can not disable trigger, err: %d", ret); + } + + APP_EVENT_SUBMIT(new_wake_up_event()); +} + +/** + * @brief Notify about sensor state. + * + * Send sensor_state_event notifying about state change. + * + * @param[in] descr Sensor descriptor. + * @param[in] data Data to be send with an event. + * @param[in] data_cnt Data cnt. + * @param[in] event_cnt Event cnt. Used to track active events. + */ +static void update_sensor_state(const struct sensor_config *sc, struct sensor_data *sd, + const enum sensor_state state) +{ + if (atomic_get(&sd->state) != state) { + struct sensor_state_event *event = new_sensor_state_event(); + + event->descr = sc->event_descr; + event->state = state; + + atomic_set(&sd->state, state); + + APP_EVENT_SUBMIT(event); + } +} + +/** + * @brief Send sensor_event. + * + * Send sensor_event with measured data. + * + * @param[in] descr Sensor descriptor. + * @param[in] data Data to be send with an event. + * @param[in] data_cnt Data cnt. + * @param[in] event_cnt Event cnt. Used to track active events. + */ +static void send_sensor_event(const char *descr, const struct sensor_value *data, + const size_t data_cnt, atomic_t *event_cnt) +{ + struct sensor_event *event = new_sensor_event(sizeof(struct sensor_value) * data_cnt); + struct sensor_value *data_ptr = sensor_event_get_data_ptr(event); + + event->descr = descr; + + __ASSERT_NO_MSG(sensor_event_get_data_cnt(event) == data_cnt); + memcpy(data_ptr, data, sizeof(struct sensor_value) * data_cnt); + + atomic_inc(event_cnt); + APP_EVENT_SUBMIT(event); +} + +/** + * @brief Get the sensor data cnt value. + * + * @param[in] sc Sensor config. + * @return size_t data_cnt for a sensor config. + */ +static size_t get_sensor_data_cnt(const struct sensor_config *sc) +{ + size_t data_cnt = 0; + + for (size_t i = 0; i < sc->chan_cnt; i++) { + data_cnt += sc->chans[i].data_cnt; + } + + return data_cnt; +} + +/** + * @brief Get the sensor data based on the sensor descriptor. + * + * @param[in] event_descr Event descriptor. + * @return struct sensor_data* Pointer to the sensor_data. + */ +static struct sensor_data *get_sensor_data(const char *const event_descr) +{ + for (size_t i = 0; i < ARRAY_SIZE(sensor_configs); i++) { + if (event_descr == sensor_configs[i].event_descr || + !strcmp(event_descr, sensor_configs[i].event_descr)) { + return &sensor_data[i]; + } + } + + return NULL; +} + +/** + * @brief Get the sensor config based on the sensor descriptor. + * + * @param[in] event_descr Event descriptor. + * @return struct sensor_config* Pointer to the sensor_data. + */ +static const struct sensor_config *get_sensor_config(const char *const event_descr) +{ + for (size_t i = 0; i < ARRAY_SIZE(sensor_configs); i++) { + if (event_descr == sensor_configs[i].event_descr || + !strcmp(event_descr, sensor_configs[i].event_descr)) { + return &sensor_configs[i]; + } + } + + return NULL; +} + +/** + * @brief Initialize sensors. + * + * @return size_t Number of correctly initialized sensors. + */ +static size_t sensors_init(void) +{ + size_t alive_sensors = 0; + + for (size_t i = 0; i < ARRAY_SIZE(sensor_configs); i++) { + const struct sensor_config *sc = &sensor_configs[i]; + struct sensor_data *sd = &sensor_data[i]; + + sd->period = UINT32_MAX; + sd->timeout = INT64_MAX; + + if (!device_is_ready(sc->dev)) { + update_sensor_state(sc, sd, SENSOR_STATE_ERROR); + LOG_ERR("%s sensor not ready", sc->dev->name); + continue; + } + + if (sc == get_sensor_config("imu")) { + if (bmi270_configure(sc->dev) != 0) { + update_sensor_state(sc, sd, SENSOR_STATE_ERROR); + LOG_ERR("%s sensor not ready", sc->dev->name); + continue; + } + } + + if (sc == get_sensor_config("wu_imu")) { + /* TODO: Trigger enable should be done in response to + * application event. + */ + int ret = sensor_trigger_set(sc->dev, &trig, motion); + + if (ret != 0 && ret != -ENOSYS) { + LOG_ERR("Can not set trigger, err: %d", ret); + update_sensor_state(sc, sd, SENSOR_STATE_ERROR); + continue; + } + } + + update_sensor_state(sc, sd, SENSOR_STATE_SLEEP); + alive_sensors++; + } + + return alive_sensors; +} + +/** + * @brief Sample sensor based on its configuration + * + * @param[in] sc Sensor configuration. + * @param[in] sd Sensor data. + */ +static void sample_sensor(const struct sensor_config *sc, struct sensor_data *sd) +{ + size_t data_idx = 0; + size_t data_cnt = get_sensor_data_cnt(sc); + struct sensor_value data[data_cnt]; + + int err = sensor_sample_fetch(sc->dev); + + for (size_t i = 0; !err && (i < sc->chan_cnt); i++) { + const struct sampled_channel *sampled_chan = &sc->chans[i]; + + err = sensor_channel_get(sc->dev, sampled_chan->chan, &data[data_idx]); + data_idx += sampled_chan->data_cnt; + } + + if (err) { + LOG_ERR("Sensor sampling error (err %d)", err); + update_sensor_state(sc, sd, SENSOR_STATE_ERROR); + } else { + if (atomic_get(&sd->event_cnt) < sc->events_limit) { + send_sensor_event(sc->event_descr, data, ARRAY_SIZE(data), + &sd->event_cnt); + } else { + LOG_WRN("Too many events, drop: %s", + sc->dev->name); + } + } +} + +/** + * @brief Sample sensors handled by the sensor_sampler module. If the sensor + * timeout expires, sample the sensor and send sensor_event with the + * measured data. + * + * @param[inout] next_timeout Minimal timeout when the next sampling should + * be performed. Calculated based on the + * configured sensors by the sensor_sampler + * module. + */ +static void sample_sensors(int64_t *next_timeout) +{ + int64_t uptime = k_uptime_get(); + + *next_timeout = INT64_MAX; + + for (size_t i = 0; i < ARRAY_SIZE(sensor_data); i++) { + const struct sensor_config *sc = &sensor_configs[i]; + struct sensor_data *sd = &sensor_data[i]; + + /* Synchronize access to sensor_data. */ + k_sched_lock(); + if (atomic_get(&sd->state) == SENSOR_STATE_ACTIVE) { + if (sd->timeout <= uptime) { + sample_sensor(sc, sd); + } + + int drops = 0; + + if ((sd->period > 0) && (sd->timeout <= uptime)) { + int64_t diff = uptime - sd->timeout; + + drops = diff / sd->period; + sd->timeout += sd->period - (diff % sd->period); + } + + if (drops > 0) { + LOG_WRN("%d sample dropped", drops); + } + } + + if (atomic_get(&sd->state) == SENSOR_STATE_ACTIVE) { + if (*next_timeout > sd->timeout) { + *next_timeout = sd->timeout; + } + } + + k_sched_unlock(); + } +} + +/** + * @brief Sampling thread function. + */ +static void sample_thread_fn(void) +{ + int64_t next_timeout = INT64_MAX; + + while (true) { + k_sem_take(&can_sample, next_timeout == INT64_MAX ? + K_FOREVER : K_TIMEOUT_ABS_MS(next_timeout)); + + sample_sensors(&next_timeout); + } +} + +/** + * @brief Initialize the module. + */ +static void init(void) +{ + size_t alive_sensors = sensors_init(); + + if (!alive_sensors) { + module_set_state(MODULE_STATE_ERROR); + return; + } + + k_sem_init(&can_sample, 0, 1); + + k_thread_create(&sample_thread, sample_thread_stack, SAMPLE_THREAD_STACK_SIZE, + (k_thread_entry_t)sample_thread_fn, NULL, NULL, NULL, + SAMPLE_THREAD_PRIORITY, 0, K_NO_WAIT); + k_thread_name_set(&sample_thread, "app_sensors"); + + module_set_state(MODULE_STATE_READY); +} + +/** + * @brief Process the power_down_event. + * + * @param[in] event Power down event. + * @return true Consume the event. + * @return false Do not consume the event. + */ +static bool handle_power_down_event(const struct power_down_event *event) +{ + const struct sensor_config *sc = get_sensor_config("wu_imu"); + + if (sc) { + int ret = sensor_trigger_set(sc->dev, &trig, motion); + + if (ret != 0) { + LOG_ERR("Can not set trigger, err: %d", ret); + } + } + + return false; +} + +/** + * @brief Process the sensor_start_event. + * + * @param[in] event Sensor start event. + * @return true Consume the event. + * @return false Do not consume the event. + */ +static bool handle_sensor_start_event(const struct sensor_start_event *event) +{ + int64_t uptime = k_uptime_get(); + + const struct sensor_config *sc = get_sensor_config(event->descr); + struct sensor_data *sd = get_sensor_data(event->descr); + + if (sd && sc) { + sd->period = event->period; + sd->timeout = uptime + event->delay; + + update_sensor_state(sc, sd, SENSOR_STATE_ACTIVE); + + /* Let the sampling thread spin. */ + k_sem_give(&can_sample); + } + + return false; +} + +/** + * @brief Process the sensor_stop_event. + * + * @param[in] event Sensor stop event. + * @return true Consume the event. + * @return false Do not consume the event. + */ +static bool handle_sensor_stop_event(const struct sensor_stop_event *event) +{ + const struct sensor_config *sc = get_sensor_config(event->descr); + struct sensor_data *sd = get_sensor_data(event->descr); + + if (sd && sc) { + sd->period = UINT32_MAX; + sd->timeout = INT64_MAX; + + update_sensor_state(sc, sd, SENSOR_STATE_SLEEP); + } + + return false; +} + +/** + * @brief Process the sensor_event. + * + * The funciton is used to calculate active events in the system. + * + * @param[in] event Sensor event. + * @return true Consume the event. + * @return false Do not consume the event. + */ +static bool handle_sensor_event(const struct sensor_event *event) +{ + struct sensor_data *sd = get_sensor_data(event->descr); + + if (sd == NULL) { + return false; + } + + atomic_dec(&sd->event_cnt); + __ASSERT_NO_MSG(!(atomic_get(&sd->event_cnt) < 0)); + + return false; +} + +/** + * @brief Handler for application event manager events. + * + * @param[in] aeh Application event header. + * @return true Consume the event. + * @return false Do not consume the event. + */ +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_power_down_event(aeh)) { + return handle_power_down_event(cast_power_down_event(aeh)); + } + + if (is_sensor_start_event(aeh)) { + return handle_sensor_start_event(cast_sensor_start_event(aeh)); + } + + if (is_sensor_stop_event(aeh)) { + return handle_sensor_stop_event(cast_sensor_stop_event(aeh)); + } + + if (is_sensor_event(aeh)) { + return handle_sensor_event(cast_sensor_event(aeh)); + } + + if (is_module_state_event(aeh)) { + struct module_state_event *event = cast_module_state_event(aeh); + + if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { + static bool initialized; + + __ASSERT_NO_MSG(!initialized); + init(); + initialized = true; + } + + return false; + } + + /* If event is unhandled, unsubscribe. */ + __ASSERT_NO_MSG(false); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); +APP_EVENT_SUBSCRIBE(MODULE, power_down_event); +APP_EVENT_SUBSCRIBE_FIRST(MODULE, sensor_start_event); +APP_EVENT_SUBSCRIBE_FINAL(MODULE, sensor_stop_event); +APP_EVENT_SUBSCRIBE_FINAL(MODULE, sensor_event); diff --git a/applications/peripheral_sensor_node/sysbuild.cmake b/applications/peripheral_sensor_node/sysbuild.cmake new file mode 100644 index 000000000000..b7a363d5bdc7 --- /dev/null +++ b/applications/peripheral_sensor_node/sysbuild.cmake @@ -0,0 +1,10 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +list(LENGTH SHIELD SHIELD_NUM) +if(SHIELD_NUM EQUAL 0) + message(FATAL_ERROR "Missing configuration for the ${NORMALIZED_BOARD_TARGET}. It requires additional shield or snippet") +endif() diff --git a/applications/peripheral_sensor_node/sysbuild.conf b/applications/peripheral_sensor_node/sysbuild.conf new file mode 100644 index 000000000000..71b8f9f9b757 --- /dev/null +++ b/applications/peripheral_sensor_node/sysbuild.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +SB_CONFIG_PARTITION_MANAGER=n