diff --git a/app/Kconfig b/app/Kconfig index fcfc09d..255e66a 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -81,6 +81,13 @@ config SLEEP_TIME_MS help Duration for which the device will sleep before waking up. This value is in milliseconds. +config GPIO_WAKEUP_DEBOUNCE_MS + int "GPIO Wakeup Debounce Time (ms)" + default 1000 + help + Time in milliseconds to ignore subsequent transitions on wakeup pins + to prevent multiple triggers from switch bouncing. + config ENABLE_ANALOG bool "Enable Analog Sensor" default n diff --git a/app/boards/nucleo_wl55jc.overlay b/app/boards/nucleo_wl55jc.overlay index 62fffa1..277c53e 100644 --- a/app/boards/nucleo_wl55jc.overlay +++ b/app/boards/nucleo_wl55jc.overlay @@ -8,15 +8,18 @@ zephyr,user { io-channels = <&adc1 6>; io-channels-names = "a0"; + wakeup-gpios = <&gpioc 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>, + <&gpioa 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; }; }; &i2c2 { - pinctrl-0 = <&i2c2_scl_pa12 &i2c2_sda_pa11>; - pinctrl-names = "default"; status = "okay"; clock-frequency = ; + pinctrl-0 = <&i2c2_scl_pa12 &i2c2_sda_pa11>; + pinctrl-names = "default"; + bme280@76 { compatible = "bosch,bme280"; reg = <0x76>; @@ -38,3 +41,7 @@ zephyr,resolution = <12>; }; }; + +&pwr { + status = "okay"; +}; \ No newline at end of file diff --git a/app/prj.conf b/app/prj.conf index 2e94ce6..5229519 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -1,53 +1,84 @@ -# Copyright (c) 2021 Nordic Semiconductor ASA -# SPDX-License-Identifier: Apache-2.0 -# -# This file contains selected Kconfig options for the application. - +# --- Language / libc --- CONFIG_CPP=y CONFIG_GLIBCXX_LIBCPP=y CONFIG_NEWLIB_LIBC=y + +# --- App deps --- CONFIG_NANOPB=y +# --- Reset / power --- +CONFIG_RESET=y CONFIG_REBOOT=y +CONFIG_POWEROFF=y -CONFIG_I2C=y +# STM32 wakeup pins support +CONFIG_STM32_WKUP_PINS=y + +# --- Buses / peripherals --- CONFIG_GPIO=y +CONFIG_I2C=y +CONFIG_I2C_STM32_BUS_RECOVERY=y CONFIG_SPI=y +CONFIG_ADC=y -CONFIG_BQ274XX_PM=y - -CONFIG_PM_DEVICE=y -CONFIG_POWEROFF=y - +# --- Sensors --- CONFIG_SENSOR=y CONFIG_SENSOR_ASYNC_API=y -# General Zephyr settings -CONFIG_MAIN_STACK_SIZE=2048 -CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 -CONFIG_LOG=y +# --- Stacks --- +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_IDLE_STACK_SIZE=2048 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 + +# --- Logging / console --- +# CONFIG_LOG=y +# CONFIG_LOG_PRINTK=n CONFIG_PRINTK=y +CONFIG_SERIAL=y + +CONFIG_LOG=y +CONFIG_LOG_BACKEND_UART=y + +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y -# Random number generator required for several LoRaWAN services +# Helpful crash info +CONFIG_FAULT_DUMP=2 +CONFIG_EXCEPTION_STACK_TRACE=y + +# --- RNG --- CONFIG_ENTROPY_GENERATOR=y -# LoRaWAN application layer +# --- RTC / alarm --- +CONFIG_RTC=y +CONFIG_RTC_ALARM=y + +# --- Flash / settings --- +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y +CONFIG_SETTINGS=y +CONFIG_SETTINGS_RUNTIME=y + +# --- LoRa / LoRaWAN --- CONFIG_LORA=y CONFIG_LORAWAN=y CONFIG_LORAWAN_NVM_SETTINGS=y -# LoRaWAN services required for FUOTA +# LoRaWAN services (FUOTA etc.) CONFIG_LORAWAN_SERVICES=y CONFIG_LORAWAN_LOG_LEVEL_DBG=y -CONFIG_NVS=y -CONFIG_SETTINGS=y -CONFIG_SETTINGS_RUNTIME=y -CONFIG_FLASH=y -CONFIG_FLASH_MAP=y +CONFIG_PM=n +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y +CONFIG_PM_DEVICE_SYSTEM_MANAGED=y -CONFIG_BOOTLOADER_MCUBOOT=y +# --- Bootloader --- +CONFIG_BOOTLOADER_MCUBOOT=n -# ADC config -CONFIG_ADC=y -CONFIG_CBPRINTF_FP_SUPPORT=y \ No newline at end of file +CONFIG_STACK_SENTINEL=y +CONFIG_THREAD_NAME=y +CONFIG_THREAD_MONITOR=y +CONFIG_INIT_STACKS=y +CONFIG_STACK_CANARIES=y \ No newline at end of file diff --git a/app/protobufs b/app/protobufs index 2ad0cf8..d2578b5 160000 --- a/app/protobufs +++ b/app/protobufs @@ -1 +1 @@ -Subproject commit 2ad0cf8d53c186a9fe53f264de15780f4ca91b4b +Subproject commit d2578b556f4a0ae0dbb673023ffc6b7c6896ec5a diff --git a/app/src/Application.cpp b/app/src/Application.cpp index 178d363..778a4c1 100644 --- a/app/src/Application.cpp +++ b/app/src/Application.cpp @@ -5,18 +5,12 @@ #include "buzzverse/packet.pb.h" #include "peripherals/lorawan_handler/lorawan_handler.hpp" -#include "utils/sleep-manager.hpp" LOG_MODULE_REGISTER(application, CONFIG_APP_LOG_LEVEL); -Application::Application( - etl::array, NUMBER_OF_SENSORS>& sensors, - LoRaWANHandler& lorawan, - etl::unique_ptr sleep_manager) - : - m_sensors(sensors), - m_lorawan(lorawan), - m_sleep_manager(etl::move(sleep_manager)) {} +Application::Application(etl::array, NUMBER_OF_SENSORS>& sensors, + LoRaWANHandler& lorawan, SleepManager* sleep_manager) + : m_sensors(sensors), m_lorawan(lorawan), m_sleep_manager(sleep_manager) {} bool Application::init() { LOG_INF("Application core initializing..."); @@ -26,12 +20,9 @@ bool Application::init() { return false; } -#if defined(CONFIG_SOC_ESP32S3) if (m_sleep_manager && m_sleep_manager->is_ready()) { - esp_sleep_wakeup_cause_t cause = m_sleep_manager->get_wakeup_cause(); - LOG_INF("ESP32S3 Wakeup cause: %d", cause); + LOG_INF("Wakeup cause: %d", static_cast(m_sleep_manager->get_wakeup_cause())); } -#endif LOG_INF("Application core initialization complete."); return true; } @@ -40,19 +31,19 @@ bool Application::initialize_peripherals() { LOG_DBG("Initializing all application peripherals..."); bool all_essential_ready = true; - // Initialize sensors - for(auto& sensor: m_sensors) { - if(sensor) { - LOG_DBG("Initializing: %s", sensor->get_name().c_str()); - if (sensor->init() != Peripheral::Status::OK) { - LOG_ERR("%s initialization failed.", sensor->get_name().c_str()); - } else { - LOG_INF("%s initialized.", sensor->get_name().c_str()); - } - } - } - - // Initialize the LoRaWAN manager + // Initialize sensors + for (auto& sensor : m_sensors) { + if (sensor) { + LOG_DBG("Initializing: %s", sensor->get_name().c_str()); + if (sensor->init() != Peripheral::Status::OK) { + LOG_ERR("%s initialization failed.", sensor->get_name().c_str()); + } else { + LOG_INF("%s initialized.", sensor->get_name().c_str()); + } + } + } + + // Initialize the LoRaWAN manager LOG_DBG("Initializing: %s", m_lorawan.get_name().c_str()); if (m_lorawan.init() != Peripheral::Status::OK) { LOG_ERR("%s initialization failed.", m_lorawan.get_name().c_str()); @@ -76,40 +67,52 @@ bool Application::initialize_peripherals() { } void Application::generate_init_failure_report(buzzverse_v1_Packet& packet) { - LOG_INF("Generating initialization failure report..."); + LOG_INF("Generating initialization failure report..."); - packet.which_data = buzzverse_v1_Packet_status_tag; - auto& status_msg = packet.data.status; + packet.which_data = buzzverse_v1_Packet_status_tag; + auto& status_msg = packet.data.status; - for(const auto& sensor: m_sensors) { - if(sensor) - sensor->get_status(status_msg); - } + for (const auto& sensor : m_sensors) { + if (sensor) sensor->get_status(status_msg); + } - if (m_lorawan.is_ready()) { - status_msg.lorawan_status = buzzverse_v1_Status_ComponentState_NORMAL; - } else { - status_msg.lorawan_status = buzzverse_v1_Status_ComponentState_INITIALIZATION_FAILED; - } + if (m_lorawan.is_ready()) { + status_msg.lorawan_status = buzzverse_v1_Status_ComponentState_NORMAL; + } else { + status_msg.lorawan_status = buzzverse_v1_Status_ComponentState_INITIALIZATION_FAILED; + } } void Application::run_cycle() { - LOG_INF("--- Starting Application Cycle ---"); - - for(auto& sensor: m_sensors) { - if (sensor && sensor->is_ready()) { - buzzverse_v1_Packet packet; - if (sensor->get_packet(packet) == Sensor::Status::OK) { - send_lora_packet(packet); - } else { - LOG_ERR("Failed to get %s packet", sensor->get_name().c_str()); - } - } else { - LOG_ERR("%s not ready for reading.", sensor->get_name().c_str()); - } - } - - LOG_INF("--- Application Cycle Complete ---"); + k_msleep(50); + LOG_INF("--- Starting Application Cycle ---"); + + if (m_sleep_manager) { + uint32_t count = m_sleep_manager->get_and_clear_wakeup_count(); + + if (count > 0) { + LOG_INF("Detected %u wakeup events. Sending report...", count); + + buzzverse_v1_Packet packet = buzzverse_v1_Packet_init_default; + packet.which_data = buzzverse_v1_Packet_counter_tag; + packet.data.counter.counter = count; + + send_lora_packet(packet); + } + } + + for (auto& sensor : m_sensors) { + if (sensor && sensor->is_ready()) { + buzzverse_v1_Packet packet = buzzverse_v1_Packet_init_default; + if (sensor->get_packet(packet) == Sensor::Status::OK) { + send_lora_packet(packet); + } else { + LOG_ERR("Failed to get %s packet", sensor->get_name().c_str()); + } + } + } + + LOG_INF("--- Application Cycle Complete ---"); } void Application::enter_low_power_mode(int sleep_duration_ms) { @@ -117,18 +120,14 @@ void Application::enter_low_power_mode(int sleep_duration_ms) { if (!m_sleep_manager || !m_sleep_manager->is_ready()) { LOG_WRN("SleepManager not available/ready. Defaulting to k_sleep for %d ms.", sleep_duration_ms); - k_sleep(K_MSEC(sleep_duration_ms)); - sys_reboot(SYS_REBOOT_COLD); + k_msleep(sleep_duration_ms); + return; } LOG_INF("Preparing system for deep sleep (duration: %d ms)...", sleep_duration_ms); m_sleep_manager->set_sleep_duration(sleep_duration_ms); m_sleep_manager->enter_sleep(SleepManager::SleepMode::DEEP_SLEEP); - - // This code should not be reached on platforms where deep sleep causes a reset. - LOG_ERR("!!! CRITICAL: Execution continued after deep sleep call. Deep sleep failed. !!!"); - sys_reboot(SYS_REBOOT_COLD); } void Application::send_lora_packet(const buzzverse_v1_Packet& packet) { diff --git a/app/src/Application.hpp b/app/src/Application.hpp index 4da4e54..23320e4 100644 --- a/app/src/Application.hpp +++ b/app/src/Application.hpp @@ -1,8 +1,10 @@ #ifndef APPLICATION_HPP #define APPLICATION_HPP +#include #include "buzzverse/packet.pb.h" #include "sensor.hpp" +#include "peripherals/sleep/sleep_manager.hpp" // Number of supported sensor types used in the sensors array #define BASE_SENSOR_COUNT 1 @@ -17,7 +19,6 @@ #define NUMBER_OF_SENSORS (BASE_SENSOR_COUNT + ANALOG_SENSOR_COUNT) class LoRaWANHandler; -class SleepManager; class Application { public: @@ -29,9 +30,9 @@ class Application { * @param sleep_manager Pointer to the SleepManager. Can be nullptr if sleep is disabled. */ Application( - etl::array, NUMBER_OF_SENSORS>& sensors, - LoRaWANHandler& lorawan, - etl::unique_ptr sleep_manager); + etl::array, NUMBER_OF_SENSORS>& sensors, + LoRaWANHandler& lorawan, + SleepManager* sleep_manager); ~Application() = default; // Main phases of the application @@ -63,7 +64,7 @@ class Application { etl::array, NUMBER_OF_SENSORS>& m_sensors; LoRaWANHandler& m_lorawan; - etl::unique_ptr m_sleep_manager; + SleepManager* m_sleep_manager; }; #endif // APPLICATION_HPP diff --git a/app/src/main.cpp b/app/src/main.cpp index 75defa0..f250d9a 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -7,11 +7,11 @@ #include "Application.hpp" #include "peripherals/lorawan_handler/lorawan_handler.hpp" +#include "peripherals/sleep/sleep_manager.hpp" #include "sensors/bme280/bme280.hpp" #include "sensors/analog/analog.hpp" #include "sensors/bq27441/bq27441.hpp" #include "utils/banner.hpp" -#include "utils/sleep-manager.hpp" LOG_MODULE_REGISTER(main_entry, LOG_LEVEL_DBG); @@ -24,6 +24,7 @@ LOG_MODULE_REGISTER(main_entry, LOG_LEVEL_DBG); static const struct adc_dt_spec soil_sensor_adc_spec = ADC_DT_SPEC_GET_BY_IDX(DT_PATH(zephyr_user), 0); +K_SEM_DEFINE(wakeup_sem, 0, 1); int main(void) { printk("%s\n", APP_ASCII_BANNER); @@ -34,7 +35,7 @@ int main(void) { // Array of available sensors etl::array, NUMBER_OF_SENSORS> sensors { - etl::unique_ptr(etl::move(&bme280)), + etl::unique_ptr(etl::move(&bme280)), #ifdef CONFIG_ENABLE_ANALOG etl::unique_ptr(etl::move(&analog)), #endif @@ -43,13 +44,13 @@ int main(void) { BQ27441 bq27441(DEVICE_DT_GET_ANY(ti_bq274xx)); LoRaWANHandler lorawan(bq27441); - etl::unique_ptr p_sleep_manager(nullptr); #ifdef CONFIG_ENABLE_DEVICE_SLEEP - p_sleep_manager = etl::unique_ptr(new SleepManager); + SleepManager sleep_manager; + Application app(sensors, lorawan, &sleep_manager); +#else + Application app(sensors, lorawan, nullptr); #endif - Application app(sensors, lorawan, etl::move(p_sleep_manager)); - if (!app.init()) { LOG_ERR("Critical application initialization failed!"); @@ -65,18 +66,16 @@ int main(void) { sys_reboot(SYS_REBOOT_COLD); } - app.run_cycle(); + while (true) { + app.run_cycle(); #ifdef CONFIG_ENABLE_DEVICE_SLEEP - app.enter_low_power_mode(APP_SLEEP_DURATION_MS); - LOG_WRN("Execution continued after enter_low_power_mode - this is unexpected for deep sleep."); + app.enter_low_power_mode(APP_SLEEP_DURATION_MS); #else - LOG_INF("Device sleep not enabled. Entering polling loop."); - while (true) { - k_sleep(K_MSEC(APP_SLEEP_DURATION_MS)); - app.run_cycle(); - } + LOG_INF("Device sleep not enabled. Entering polling loop."); + k_msleep(APP_SLEEP_DURATION_MS); #endif + } return 0; // Should not be reached } diff --git a/app/src/peripherals/lorawan_handler/lorawan_handler.hpp b/app/src/peripherals/lorawan_handler/lorawan_handler.hpp index 4dcb168..5dca59c 100644 --- a/app/src/peripherals/lorawan_handler/lorawan_handler.hpp +++ b/app/src/peripherals/lorawan_handler/lorawan_handler.hpp @@ -2,7 +2,6 @@ #define LORAWAN_HANDLER_HPP #include -#include #include #include "buzzverse/bq27441.pb.h" diff --git a/app/src/peripherals/peripheral.hpp b/app/src/peripherals/peripheral.hpp index 17c0c73..3026239 100644 --- a/app/src/peripherals/peripheral.hpp +++ b/app/src/peripherals/peripheral.hpp @@ -22,6 +22,7 @@ class Peripheral { ERROR_INVALID_PARAM = -3, /**< Invalid parameter provided */ ERROR_NOT_INITIALIZED = -4, /**< Initialization failed */ ERROR_HW_CONFIG_FAILED = -5, /**< Hardware configuration failed */ + ERROR_ALREADY_INITIALIZED = -6 /**< Peripheral is already initialized */ }; /** diff --git a/app/src/peripherals/rtc/rtc_peripheral.cpp b/app/src/peripherals/rtc/rtc_peripheral.cpp new file mode 100644 index 0000000..549d060 --- /dev/null +++ b/app/src/peripherals/rtc/rtc_peripheral.cpp @@ -0,0 +1,121 @@ +#include "rtc_peripheral.hpp" + +LOG_MODULE_REGISTER(rtc_periph, LOG_LEVEL_INF); + +extern struct k_sem wakeup_sem; + +RtcPeripheral::RtcPeripheral() : rtc_dev(DEVICE_DT_GET(DT_NODELABEL(rtc))), initialized(false) {} + +static void rtc_alarm_handler(const struct device* dev, uint16_t id, void* user_data) { + k_sem_give(&wakeup_sem); +} + +Peripheral::Status RtcPeripheral::init() { + if (initialized) { + return Peripheral::Status::ERROR_ALREADY_INITIALIZED; + } + + if (!device_is_ready(rtc_dev)) { + LOG_ERR("rtc not ready"); + return Peripheral::Status::NOT_READY; + } + + if (!ensure_time_valid()) { + LOG_WRN("rtc no valid time, init default"); + if (!set_default_time()) { + LOG_ERR("rtc time set failed"); + return Peripheral::Status::ERROR_HW_CONFIG_FAILED; + } + } + + int rc = rtc_alarm_set_callback(rtc_dev, 0, rtc_alarm_handler, nullptr); + if (rc != 0) { + LOG_ERR("rtc alarm callback registration failed (err %d)", rc); + return Peripheral::Status::ERROR_HW_CONFIG_FAILED; + } + + initialized = true; + return Peripheral::Status::OK; +} + +bool RtcPeripheral::is_ready() const { + return initialized && device_is_ready(rtc_dev); +} + +etl::string RtcPeripheral::get_name() const { + return etl::string("RtcPeripheral"); +} + +bool RtcPeripheral::get_time(rtc_time& out_time) const { + return rtc_get_time(rtc_dev, &out_time) == 0; +} + +bool RtcPeripheral::ensure_time_valid() { + rtc_time tmp; + return rtc_get_time(rtc_dev, &tmp) == 0; +} + +bool RtcPeripheral::set_alarm(const rtc_time& t) { + uint16_t supported = 0; + if (rtc_alarm_get_supported_fields(rtc_dev, 0, &supported) != 0) { + return false; + } + + uint16_t mask = 0; + if (supported & RTC_ALARM_TIME_MASK_SECOND) mask |= RTC_ALARM_TIME_MASK_SECOND; + if (supported & RTC_ALARM_TIME_MASK_MINUTE) mask |= RTC_ALARM_TIME_MASK_MINUTE; + if (supported & RTC_ALARM_TIME_MASK_HOUR) mask |= RTC_ALARM_TIME_MASK_HOUR; + if (supported & RTC_ALARM_TIME_MASK_MONTHDAY) mask |= RTC_ALARM_TIME_MASK_MONTHDAY; + if (supported & RTC_ALARM_TIME_MASK_MONTH) mask |= RTC_ALARM_TIME_MASK_MONTH; + if (supported & RTC_ALARM_TIME_MASK_YEAR) mask |= RTC_ALARM_TIME_MASK_YEAR; + + (void)rtc_alarm_is_pending(rtc_dev, 0); + + return rtc_alarm_set_time(rtc_dev, 0, mask, &t) == 0; +} + +int64_t RtcPeripheral::to_epoch(const rtc_time& t) const { + struct rtc_time tmp = t; + struct tm tm_now = *rtc_time_to_tm(&tmp); + return timeutil_timegm64(&tm_now); +} + +bool RtcPeripheral::epoch_to_rtc_time(int64_t epoch, rtc_time& out) const { + time_t epoch32 = static_cast(epoch); + + struct tm tm_alarm; + if (gmtime_r(&epoch32, &tm_alarm) == nullptr) { + return false; + } + + out.tm_sec = tm_alarm.tm_sec; + out.tm_min = tm_alarm.tm_min; + out.tm_hour = tm_alarm.tm_hour; + out.tm_mday = tm_alarm.tm_mday; + out.tm_mon = tm_alarm.tm_mon; + out.tm_year = tm_alarm.tm_year; + out.tm_wday = tm_alarm.tm_wday; + out.tm_yday = tm_alarm.tm_yday; + out.tm_isdst = -1; + + return true; +} + +bool RtcPeripheral::set_default_time() { +#ifndef CONFIG_DEFAULT_RTC_YEAR + #define CONFIG_DEFAULT_RTC_YEAR 2025 +#endif + + rtc_time def{}; + def.tm_sec = 0; + def.tm_min = 0; + def.tm_hour = 0; + def.tm_mday = 1; + def.tm_mon = 0; + def.tm_year = CONFIG_DEFAULT_RTC_YEAR - 1900; + def.tm_wday = 0; + def.tm_yday = 0; + def.tm_isdst = 0; + + return rtc_set_time(rtc_dev, &def) == 0; +} diff --git a/app/src/peripherals/rtc/rtc_peripheral.hpp b/app/src/peripherals/rtc/rtc_peripheral.hpp new file mode 100644 index 0000000..8847c2f --- /dev/null +++ b/app/src/peripherals/rtc/rtc_peripheral.hpp @@ -0,0 +1,32 @@ +#ifndef RTC_PERIPHERAL_HPP +#define RTC_PERIPHERAL_HPP + +#include +#include +#include +#include + +#include "peripheral.hpp" + +class RtcPeripheral : public Peripheral { + public: + RtcPeripheral(); + + Status init() override; + bool is_ready() const override; + etl::string get_name() const override; + + bool get_time(rtc_time& out_time) const; + bool ensure_time_valid(); + bool set_alarm(const rtc_time& t); + int64_t to_epoch(const rtc_time& t) const; + bool epoch_to_rtc_time(int64_t epoch, rtc_time& out) const; + + private: + bool set_default_time(); + + const device* rtc_dev; + bool initialized; +}; + +#endif \ No newline at end of file diff --git a/app/src/peripherals/sleep/sleep_manager.hpp b/app/src/peripherals/sleep/sleep_manager.hpp new file mode 100644 index 0000000..dbbe474 --- /dev/null +++ b/app/src/peripherals/sleep/sleep_manager.hpp @@ -0,0 +1,73 @@ +#ifndef SLEEP_MANAGER_HPP +#define SLEEP_MANAGER_HPP + +#include + +#include "peripheral.hpp" +#include "sleep_manager_base.hpp" + +#if defined(CONFIG_SOC_SERIES_STM32WLX) + #include "sleep_manager_stm.hpp" +typedef SleepManagerStm SleepManagerImpl; +#elif defined(CONFIG_SOC_ESP32S3) + #include "sleep_manager_esp.hpp" +typedef SleepManagerEsp SleepManagerImpl; +#else +class SleepManagerDummy : public SleepManagerBase { + public: + Peripheral::Status init() override { + return Peripheral::Status::OK; + } + bool is_ready() const override { + return true; + } + etl::string get_name() const override { + return "Dummy"; + } + void enter_sleep(SleepMode mode) override {} + void set_sleep_duration(int duration_ms) override {} + void timed_sleep() override {} +}; +typedef SleepManagerDummy SleepManagerImpl; +#endif + +class SleepManager : public Peripheral { + public: + using SleepMode = SleepManagerBase::SleepMode; + using WakeCause = SleepManagerBase::WakeCause; + + SleepManager() = default; + + Peripheral::Status init() override { + return impl.init(); + } + bool is_ready() const override { + return impl.is_ready(); + } + etl::string get_name() const override { + return impl.get_name(); + } + + uint32_t get_and_clear_wakeup_count() { + return impl.get_and_clear_wakeup_count(); + } + + void enter_sleep(SleepMode mode) { + impl.enter_sleep(mode); + } + void set_sleep_duration(int ms) { + impl.set_sleep_duration(ms); + } + void timed_sleep() { + impl.timed_sleep(); + } + + WakeCause get_wakeup_cause() const { + return impl.get_wakeup_cause(); + } + + private: + SleepManagerImpl impl; +}; + +#endif diff --git a/app/src/peripherals/sleep/sleep_manager_base.hpp b/app/src/peripherals/sleep/sleep_manager_base.hpp new file mode 100644 index 0000000..89e6df8 --- /dev/null +++ b/app/src/peripherals/sleep/sleep_manager_base.hpp @@ -0,0 +1,34 @@ +#ifndef SLEEP_MANAGER_BASE_HPP +#define SLEEP_MANAGER_BASE_HPP + +#include + +#include "peripheral.hpp" + +constexpr size_t SLEEP_MANAGER_NAME_SIZE = 32; +constexpr int DEFAULT_SLEEP_DURATION_MS = 5000; + +class SleepManagerBase : public Peripheral { + public: + enum class SleepMode { LIGHT_SLEEP, DEEP_SLEEP }; + + enum class WakeCause { UNKNOWN = 0, RTC_ALARM, WAKEUP_PIN, TIMER, POWER_ON_RESET }; + + virtual ~SleepManagerBase() = default; + + virtual Peripheral::Status init() = 0; + virtual bool is_ready() const = 0; + virtual etl::string get_name() const = 0; + + virtual uint32_t get_and_clear_wakeup_count() { return 0; } + + virtual void enter_sleep(SleepMode mode) = 0; + virtual void set_sleep_duration(int duration_ms) = 0; + virtual void timed_sleep() = 0; + + virtual WakeCause get_wakeup_cause() const { + return WakeCause::UNKNOWN; + } +}; + +#endif \ No newline at end of file diff --git a/app/src/peripherals/sleep/sleep_manager_esp.cpp b/app/src/peripherals/sleep/sleep_manager_esp.cpp new file mode 100644 index 0000000..75fa4f6 --- /dev/null +++ b/app/src/peripherals/sleep/sleep_manager_esp.cpp @@ -0,0 +1,92 @@ +#include "sleep_manager_esp.hpp" + +#include +#include + +#ifdef CONFIG_SOC_ESP32S3 + #include + #include +#endif + +LOG_MODULE_REGISTER(sleep_mgr_esp, LOG_LEVEL_INF); + +Peripheral::Status SleepManagerEsp::init() { + initialized = true; + return Peripheral::Status::OK; +} + +bool SleepManagerEsp::is_ready() const { + return initialized; +} + +etl::string SleepManagerEsp::get_name() const { + return etl::string("SleepManagerEsp"); +} + +void SleepManagerEsp::enter_sleep(SleepMode mode) { + if (!initialized) { + LOG_ERR("sleep_mgr not initialized"); + return; + } + +#ifdef CONFIG_SOC_ESP32S3 + if (mode == SleepMode::LIGHT_SLEEP) { + k_msleep(sleep_duration_ms); + return; + } + + const uint64_t us = static_cast(sleep_duration_ms) * 1000ULL; + esp_err_t rc = esp_sleep_enable_timer_wakeup(us); + if (rc != ESP_OK) { + LOG_WRN("timer_wakeup setup failed (%d), fallback msleep", (int)rc); + k_msleep(sleep_duration_ms); + return; + } + + k_msleep(50); + esp_deep_sleep_start(); +#else + k_msleep(sleep_duration_ms); +#endif +} + +void SleepManagerEsp::timed_sleep() { + if (!initialized) { + LOG_ERR("sleep_mgr not initialized"); + return; + } + k_msleep(sleep_duration_ms); +} + +void SleepManagerEsp::set_sleep_duration(int duration_ms) { + if (duration_ms <= 0) { + LOG_WRN("invalid sleep timeout %d", duration_ms); + return; + } + sleep_duration_ms = duration_ms; +} + +SleepManagerBase::WakeCause SleepManagerEsp::get_wakeup_cause() const { + if (!initialized) { + return WakeCause::UNKNOWN; + } + +#ifdef CONFIG_SOC_ESP32S3 + esp_sleep_source_t src = esp_sleep_get_wakeup_cause(); + switch (src) { + case ESP_SLEEP_WAKEUP_TIMER: + return WakeCause::TIMER; + + case ESP_SLEEP_WAKEUP_GPIO: + return WakeCause::WAKEUP_PIN; + + case ESP_SLEEP_WAKEUP_UNDEFINED: + return WakeCause::POWER_ON_RESET; + + default: + return WakeCause::UNKNOWN; + } +#else + return WakeCause::UNKNOWN; +#endif +} \ No newline at end of file diff --git a/app/src/peripherals/sleep/sleep_manager_esp.hpp b/app/src/peripherals/sleep/sleep_manager_esp.hpp new file mode 100644 index 0000000..8d0fb11 --- /dev/null +++ b/app/src/peripherals/sleep/sleep_manager_esp.hpp @@ -0,0 +1,30 @@ +#ifndef SLEEP_MANAGER_ESP_HPP +#define SLEEP_MANAGER_ESP_HPP + +#include "sleep_manager_base.hpp" + +#ifdef CONFIG_SOC_ESP32S3 + #include + #include +#endif + +class SleepManagerEsp final : public SleepManagerBase { + public: + SleepManagerEsp() = default; + + Peripheral::Status init() override; + bool is_ready() const override; + etl::string get_name() const override; + + void enter_sleep(SleepMode mode) override; + void set_sleep_duration(int duration_ms) override; + void timed_sleep() override; + + WakeCause get_wakeup_cause() const override; + + private: + bool initialized = false; + int sleep_duration_ms = DEFAULT_SLEEP_DURATION_MS; +}; + +#endif \ No newline at end of file diff --git a/app/src/peripherals/sleep/sleep_manager_stm.cpp b/app/src/peripherals/sleep/sleep_manager_stm.cpp new file mode 100644 index 0000000..e894e9a --- /dev/null +++ b/app/src/peripherals/sleep/sleep_manager_stm.cpp @@ -0,0 +1,155 @@ +#include "sleep_manager_stm.hpp" + +#include +#include + +LOG_MODULE_REGISTER(sleep_mgr_stm, LOG_LEVEL_INF); + +/* Use the real global semaphore defined elsewhere */ +extern struct k_sem wakeup_sem; + +namespace { + +/* + * ISR/thread shared state: + * - Use atomics (volatile is NOT sufficient for concurrency). + * - Keep ISR extremely small: no LOG_* calls here. + */ +static atomic_t wkup_count; +static atomic_t last_wkup_time; + +static void wkup_isr(const struct device* dev, struct gpio_callback* cb, uint32_t pins) { + ARG_UNUSED(dev); + ARG_UNUSED(cb); + ARG_UNUSED(pins); + + const uint32_t now = k_uptime_get_32(); + const uint32_t last = (uint32_t)atomic_get(&last_wkup_time); + + if ((uint32_t)(now - last) > CONFIG_GPIO_WAKEUP_DEBOUNCE_MS) { + atomic_set(&last_wkup_time, now); + atomic_inc(&wkup_count); + } +} + +} // namespace + +#define WAKEUP_PIN_CFG(node_id, prop, idx) GPIO_DT_SPEC_GET_BY_IDX(node_id, prop, idx), + +SleepManagerStm::SleepManagerStm() + : initialized(false), sleep_timeout_ms(DEFAULT_SLEEP_DURATION_MS) { +#if defined(CONFIG_SOC_SERIES_STM32WLX) + #if DT_NODE_HAS_PROP(DT_PATH(zephyr_user), wakeup_gpios) + static const struct gpio_dt_spec specs[] = { + DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), wakeup_gpios, WAKEUP_PIN_CFG)}; + + for (size_t i = 0; i < ARRAY_SIZE(specs); i++) { + if (!wakeup_pins.full()) { + WakeupPin pin; + pin.spec = specs[i]; + wakeup_pins.push_back(pin); + } else { + LOG_WRN("Max wakeup pins reached."); + break; + } + } + #else + LOG_WRN("No wakeup-gpios defined in zephyr,user node."); + #endif +#endif +} + +Peripheral::Status SleepManagerStm::init() { +#if defined(CONFIG_SOC_SERIES_STM32WLX) + if (wakeup_pins.empty()) { + LOG_INF("No wakeup pins to configure."); + } + + for (auto& pin : wakeup_pins) { + if (!device_is_ready(pin.spec.port)) { + LOG_ERR("GPIO port %s not ready", pin.spec.port->name); + continue; + } + + if (gpio_pin_configure_dt(&pin.spec, GPIO_INPUT) != 0) { + LOG_ERR("Pin %d cfg failed", pin.spec.pin); + continue; + } + + (void)gpio_pin_interrupt_configure_dt(&pin.spec, GPIO_INT_EDGE_TO_ACTIVE); + + gpio_init_callback(&pin.cb_data, wkup_isr, BIT(pin.spec.pin)); + (void)gpio_add_callback(pin.spec.port, &pin.cb_data); + + LOG_INF("Wakeup source initialized: Pin %d on %s", pin.spec.pin, pin.spec.port->name); + } + + const Peripheral::Status rtc_rc = rtc.init(); + if (rtc_rc != Peripheral::Status::OK && rtc_rc != Peripheral::Status::ERROR_ALREADY_INITIALIZED) { + return rtc_rc; + } +#endif + + initialized = true; + return Peripheral::Status::OK; +} + +uint32_t SleepManagerStm::get_and_clear_wakeup_count() { + /* atomically get-and-clear */ + return (uint32_t)atomic_set(&wkup_count, 0); +} + +bool SleepManagerStm::is_ready() const { +#if defined(CONFIG_SOC_SERIES_STM32WLX) + return initialized && rtc.is_ready(); +#else + return initialized; +#endif +} + +etl::string SleepManagerStm::get_name() const { + return etl::string("SleepManagerStm"); +} + +void SleepManagerStm::enter_sleep(SleepMode mode) { + ARG_UNUSED(mode); + if (!initialized) { + return; + } + +#if defined(CONFIG_SOC_SERIES_STM32WLX) + if (!rtc.is_ready() || !rtc.ensure_time_valid()) { + /* + * Wait until something wakes us (wkup_isr gives wakeup_sem). + * This is thread context; safe to block here. + */ + (void)k_sem_take(&wakeup_sem, K_FOREVER); + return; + } + + rtc_time now; + if (rtc.get_time(now)) { + int64_t epoch_alarm = rtc.to_epoch(now) + (sleep_timeout_ms / 1000); + rtc_time alarm_time; + if (rtc.epoch_to_rtc_time(epoch_alarm, alarm_time)) { + rtc.set_alarm(alarm_time); + } + } + + (void)k_sem_take(&wakeup_sem, K_FOREVER); +#else + k_msleep(sleep_timeout_ms); +#endif +} + +void SleepManagerStm::set_sleep_duration(int duration_ms) { + if (duration_ms > 0) { + sleep_timeout_ms = duration_ms; + } +} + +void SleepManagerStm::timed_sleep() { + if (initialized) { + k_msleep(sleep_timeout_ms); + } +} \ No newline at end of file diff --git a/app/src/peripherals/sleep/sleep_manager_stm.hpp b/app/src/peripherals/sleep/sleep_manager_stm.hpp new file mode 100644 index 0000000..26a6358 --- /dev/null +++ b/app/src/peripherals/sleep/sleep_manager_stm.hpp @@ -0,0 +1,44 @@ +#ifndef SLEEP_MANAGER_STM_HPP +#define SLEEP_MANAGER_STM_HPP + +#include +#include +#include +#include +#include + +#include "peripherals/rtc/rtc_peripheral.hpp" +#include "sleep_manager_base.hpp" + +struct WakeupPin { + struct gpio_dt_spec spec; + struct gpio_callback cb_data; +}; + +class SleepManagerStm : public SleepManagerBase { + public: + SleepManagerStm(); + + Peripheral::Status init() override; + bool is_ready() const override; + etl::string get_name() const override; + + uint32_t get_and_clear_wakeup_count() override; + + void enter_sleep(SleepMode mode) override; + void set_sleep_duration(int duration_ms) override; + void timed_sleep() override; + + private: + static constexpr size_t MAX_WAKEUP_PINS = 4; + etl::vector wakeup_pins; + + bool initialized; + int sleep_timeout_ms; + +#if defined(CONFIG_SOC_SERIES_STM32WLX) + RtcPeripheral rtc; +#endif +}; + +#endif diff --git a/app/src/sensors/bme280/bme280.cpp b/app/src/sensors/bme280/bme280.cpp index 309aa57..2bb715f 100644 --- a/app/src/sensors/bme280/bme280.cpp +++ b/app/src/sensors/bme280/bme280.cpp @@ -94,4 +94,4 @@ bool BME280::validate_data(buzzverse_v1_BME280Data& data) const { return true; else return false; -} +} \ No newline at end of file diff --git a/app/src/utils/sleep-manager.cpp b/app/src/utils/sleep-manager.cpp deleted file mode 100644 index 8eddac8..0000000 --- a/app/src/utils/sleep-manager.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "sleep-manager.hpp" - -#include -#include - -#ifdef CONFIG_SOC_ESP32S3 - #include -#endif - -LOG_MODULE_REGISTER(sleep_manager, LOG_LEVEL_DBG); - -SleepManager::SleepManager() {} - -Peripheral::Status SleepManager::init() { - initialized = true; - LOG_INF("SleepManager initialized."); - return Status::OK; -} - -bool SleepManager::is_ready() const { - return initialized; -} - -etl::string SleepManager::get_name() const { - return "SleepManager"; -} - -void SleepManager::enter_sleep(SleepMode mode) { - if (!initialized) { - LOG_WRN("Sleep Manager not initialized, cannot sleep."); - return; - } - -#ifdef CONFIG_SOC_SERIES_STM32 - LOG_WRN("STM32 Sleep not implemented yet."); - timed_sleep(); -#elif CONFIG_SOC_ESP32S3 - - if (mode == SleepMode::LIGHT_SLEEP) { - LOG_INF("Entering light sleep (using k_sleep)..."); - k_sleep(K_MSEC(DEFAULT_SLEEP_DURATION_MS)); - - } else if (mode == SleepMode::DEEP_SLEEP) { - LOG_INF("Configuring wake-up sources for TIMED deep sleep..."); - - // --- Enable Timer Wakeup --- - uint64_t sleep_time_us = (uint64_t)sleep_duration_ms * 1000ULL; - LOG_INF("Enabling timer wakeup for %llu us (%d ms)", sleep_time_us, sleep_duration_ms); - esp_err_t err = esp_sleep_enable_timer_wakeup(sleep_time_us); - if (err != ESP_OK) { - LOG_ERR("Failed to enable timer wakeup (err %d)", err); - LOG_WRN("Falling back to k_sleep instead of deep sleep."); - k_sleep(K_MSEC(sleep_duration_ms)); - return; - } - - LOG_INF("Entering ESP32 deep sleep mode (timer wake)..."); - k_sleep(K_MSEC(50)); // Allow logs to propagate before deep sleep - - esp_deep_sleep_start(); - - LOG_ERR("Should not reach here after deep sleep start!"); - } -#else - LOG_WRN("Deep sleep not implemented for this SOC."); - timed_sleep(); -#endif -} - -void SleepManager::timed_sleep() { - if (!initialized) { - LOG_WRN("Sleep Manager not initialized, cannot sleep."); - return; - } - LOG_INF("Entering timed sleep (k_sleep) for %dms...", sleep_duration_ms); - k_sleep(K_MSEC(sleep_duration_ms)); -} - -void SleepManager::set_sleep_duration(int duration_ms) { - if (duration_ms > 0) { - sleep_duration_ms = duration_ms; - LOG_INF("Sleep duration set to %d ms", sleep_duration_ms); - } else { - LOG_WRN("Invalid sleep duration: %d ms. Keeping previous value: %d ms", duration_ms, - sleep_duration_ms); - } -} - -#ifdef CONFIG_SOC_ESP32S3 -esp_sleep_wakeup_cause_t SleepManager::get_wakeup_cause() { - return esp_sleep_get_wakeup_cause(); -} -#endif \ No newline at end of file diff --git a/app/src/utils/sleep-manager.hpp b/app/src/utils/sleep-manager.hpp deleted file mode 100644 index 01212e0..0000000 --- a/app/src/utils/sleep-manager.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef SLEEPMANAGER_HPP -#define SLEEPMANAGER_HPP - -#include -#include -#include -#include - -#ifdef CONFIG_SOC_ESP32S3 - #include -#endif - -#include "peripheral.hpp" - -constexpr size_t SLEEP_MANAGER_NAME_SIZE = 32; -constexpr int DEFAULT_SLEEP_DURATION_MS = 5000; - -class SleepManager : public Peripheral { - public: - enum class SleepMode { LIGHT_SLEEP, DEEP_SLEEP }; - - SleepManager(); - ~SleepManager() override = default; - - Status init() override; - bool is_ready() const override; - etl::string get_name() const override; - - void enter_sleep(SleepMode mode); - void set_sleep_duration(int duration_ms); - void timed_sleep(); - -#ifdef CONFIG_SOC_ESP32S3 - esp_sleep_wakeup_cause_t get_wakeup_cause(); -#endif - - private: - // --- Member Variables --- - bool initialized = false; - int sleep_duration_ms = DEFAULT_SLEEP_DURATION_MS; -}; - -#endif // SLEEPMANAGER_HPP \ No newline at end of file diff --git a/west.yml b/west.yml index d972f5f..1ed6b37 100644 --- a/west.yml +++ b/west.yml @@ -8,12 +8,12 @@ manifest: projects: - name: zephyr url: https://github.com/zephyrproject-rtos/zephyr - revision: v4.0.0 + revision: a143581 import: name-allowlist: - hal_espressif - hal_stm32 - - cmsis + - cmsis_6 - loramac-node - mcuboot - nanopb