From 7b6d40ac3589260a2fd633517fe989016644f815 Mon Sep 17 00:00:00 2001 From: Ruben Lohberg Date: Mon, 4 May 2026 15:15:19 +0200 Subject: [PATCH 1/9] feat(auto-off): add auto off manager for dynamic handling of auto-off functionality --- prj.conf | 8 + src/Battery/AutoOffManager.cpp | 326 +++++++++++++++++++++++++++++++++ src/Battery/AutoOffManager.h | 143 +++++++++++++++ src/Battery/CMakeLists.txt | 3 +- src/Battery/Kconfig | 52 ++++++ src/Battery/PowerManager.cpp | 9 +- 6 files changed, 539 insertions(+), 2 deletions(-) create mode 100644 src/Battery/AutoOffManager.cpp create mode 100644 src/Battery/AutoOffManager.h diff --git a/prj.conf b/prj.conf index 6e2a1100..4ace7e54 100644 --- a/prj.conf +++ b/prj.conf @@ -85,6 +85,14 @@ CONFIG_I2C_NRFX=y CONFIG_POWEROFF=y +# Auto-off power saving levels: 0=off, 1=minimal, 2=balanced, 3=aggressive +CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE=2 +CONFIG_POWER_SAVING_LEVEL_LEAUDIO=3 +CONFIG_POWER_SAVING_LEVEL_SENSOR_MANAGER=2 +CONFIG_AUTO_OFF_TIMEOUT_AGGRESSIVE=5 +CONFIG_AUTO_OFF_TIMEOUT_BALANCED=15 +CONFIG_AUTO_OFF_TIMEOUT_MINIMAL=30 + CONFIG_PM=y # may cause delay issues for ISO stream ? CONFIG_PM_DEVICE=y CONFIG_PM_DEVICE_RUNTIME=y diff --git a/src/Battery/AutoOffManager.cpp b/src/Battery/AutoOffManager.cpp new file mode 100644 index 00000000..2951102f --- /dev/null +++ b/src/Battery/AutoOffManager.cpp @@ -0,0 +1,326 @@ +#include "AutoOffManager.h" + +#include +#include +#include +#include + +#include +#include + +#include "PowerManager.h" + +#include +LOG_MODULE_REGISTER(auto_off, LOG_LEVEL_DBG); + +void auto_off_work_handler(struct k_work *work); + +// anonymous namespace for helpers +namespace { + +struct PowerSavingModeConfig { + const char *name; + int timeout_minutes; +}; + +/* Indexed by power_saving_level_t. Keep the order in sync with the enum values. */ +constexpr std::array power_saving_modes = { { + { "off", 0 }, + { "minimal", CONFIG_AUTO_OFF_TIMEOUT_MINIMAL }, + { "balanced", CONFIG_AUTO_OFF_TIMEOUT_BALANCED }, + { "aggressive", CONFIG_AUTO_OFF_TIMEOUT_AGGRESSIVE }, +} }; +static_assert(power_saving_modes.size() == POWER_SAVING_LEVEL_COUNT, + "power_saving_modes must contain every concrete power saving level"); + +K_MUTEX_DEFINE(auto_off_mutex); +K_WORK_DELAYABLE_DEFINE(auto_off_work, auto_off_work_handler); + +class AutoOffLock { +public: + AutoOffLock() + { + k_mutex_lock(&auto_off_mutex, K_FOREVER); + } + + ~AutoOffLock() + { + k_mutex_unlock(&auto_off_mutex); + } + + AutoOffLock(const AutoOffLock &) = delete; + AutoOffLock &operator=(const AutoOffLock &) = delete; +}; + +} + +bool AutoOffManager::participant_is_considered(const ParticipantEntry &entry) const +{ + if (!entry.registered || current_mode_ == POWER_SAVING_LEVEL_OFF || + entry.level == POWER_SAVING_LEVEL_OFF) { + return false; + } + + return entry.level >= current_mode_; +} + +AutoOffManager::ParticipantEntry *AutoOffManager::find_participant(const char *participant_token) +{ + auto participant = std::find_if( + participants_.begin(), participants_.end(), + [participant_token](const auto &entry) { + return entry.registered && + std::strcmp(entry.token, participant_token) == 0; + }); + + if (participant == participants_.end()) { + return nullptr; + } + + return &(*participant); +} + +bool AutoOffManager::all_considered_participants_allow() const +{ + for (const auto &participant : participants_) { + if (participant_is_considered(participant) && !participant.allowed) { + return false; + } + } + + return true; +} + +void AutoOffManager::schedule() const +{ + if (current_mode_ < POWER_SAVING_LEVEL_OFF || + static_cast(current_mode_) >= power_saving_modes.size()) { + LOG_WRN("Cannot schedule auto-off for invalid mode %d", current_mode_); + return; + } + + const auto &mode = power_saving_modes[static_cast(current_mode_)]; + + (void)k_work_reschedule(&auto_off_work, K_MINUTES(mode.timeout_minutes)); + LOG_INF("Auto-off armed for %d min in %s mode", mode.timeout_minutes, mode.name); +} + +void AutoOffManager::cancel() const +{ + (void)k_work_cancel_delayable(&auto_off_work); +} + +void AutoOffManager::evaluate() +{ + if (!initialized_) { + return; + } + + if (current_mode_ == POWER_SAVING_LEVEL_OFF) { + cancel(); + return; + } + + if (all_considered_participants_allow()) { + schedule(); + } else { + cancel(); + } +} + +int AutoOffManager::init() +{ + AutoOffLock lock; + + if (initialized_) { + return -EALREADY; + } + + current_mode_ = (power_saving_level_t)CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE; + + if (current_mode_ < POWER_SAVING_LEVEL_OFF || + static_cast(current_mode_) >= power_saving_modes.size()) { + LOG_WRN("Invalid default auto-off mode %d, disabling auto-off", current_mode_); + current_mode_ = POWER_SAVING_LEVEL_OFF; + } + + const auto &mode = power_saving_modes[static_cast(current_mode_)]; + + initialized_ = true; + + LOG_INF("Auto-off initialized in %s mode", mode.name); + evaluate(); + + return 0; +} + +/* + * Register one auto-off participant. + * + * participant_token is the identity key for a participant. It must be unique + * across all participants and stable for the lifetime of the registration. A + * participant must reuse exactly the same token for register_participant(), + * allow(), and prohibit(); a different token is treated as a different + * participant and will not update the original registration. + */ +int AutoOffManager::register_participant(const char *participant_token, power_saving_level_t level) +{ + if (participant_token == nullptr || level < POWER_SAVING_LEVEL_OFF || + static_cast(level) >= power_saving_modes.size()) { + return -EINVAL; + } + + const auto &level_config = power_saving_modes[static_cast(level)]; + + AutoOffLock lock; + + ParticipantEntry *entry = find_participant(participant_token); + if (entry != nullptr) { + entry->level = level; + evaluate(); + return -EALREADY; + } + + auto free_participant = std::find_if( + participants_.begin(), participants_.end(), + [](const auto &entry) { return !entry.registered; }); + + if (free_participant != participants_.end()) { + free_participant->token = participant_token; + free_participant->level = level; + free_participant->allowed = false; + free_participant->registered = true; + + LOG_INF("Registered auto-off participant token %s at %s level", + participant_token, level_config.name); + evaluate(); + + return 0; + } + + LOG_WRN("No room to register auto-off participant token %s", participant_token); + + return -ENOMEM; +} + +void AutoOffManager::allow(const char *participant_token) +{ + if (participant_token == nullptr) { + return; + } + + AutoOffLock lock; + + ParticipantEntry *entry = find_participant(participant_token); + if (entry == nullptr) { + LOG_WRN("Auto-off allow from unregistered participant token %s", + participant_token); + return; + } + + if (!entry->allowed) { + entry->allowed = true; + LOG_DBG("Auto-off allowed by token %s", participant_token); + evaluate(); + } +} + +void AutoOffManager::prohibit(const char *participant_token) +{ + if (participant_token == nullptr) { + return; + } + + AutoOffLock lock; + + ParticipantEntry *entry = find_participant(participant_token); + if (entry == nullptr) { + LOG_WRN("Auto-off prohibit from unregistered participant token %s", + participant_token); + return; + } + + if (entry->allowed) { + entry->allowed = false; + LOG_DBG("Auto-off prohibited by token %s", participant_token); + evaluate(); + } +} + +void AutoOffManager::set_mode(power_saving_level_t mode) +{ + if (mode < POWER_SAVING_LEVEL_OFF || + static_cast(mode) >= power_saving_modes.size()) { + LOG_WRN("Ignoring invalid auto-off mode %d", mode); + return; + } + + const auto &mode_config = power_saving_modes[static_cast(mode)]; + + AutoOffLock lock; + + if (current_mode_ != mode) { + current_mode_ = mode; + LOG_INF("Auto-off mode set to %s", mode_config.name); + evaluate(); + } +} + +power_saving_level_t AutoOffManager::get_mode() +{ + AutoOffLock lock; + + return current_mode_; +} + +void AutoOffManager::handle_timeout() +{ + bool should_power_down; + + { + AutoOffLock lock; + should_power_down = initialized_ && current_mode_ != POWER_SAVING_LEVEL_OFF && + all_considered_participants_allow(); + } + + if (!should_power_down) { + LOG_DBG("Auto-off skipped because a participant now prohibits it"); + return; + } + + LOG_INF("Auto-off timeout reached"); + power_manager.power_down(); +} + +void auto_off_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + auto_off_manager.handle_timeout(); +} + +int auto_off_register(const char *participant_token, power_saving_level_t level) +{ + return auto_off_manager.register_participant(participant_token, level); +} + +void auto_off_allow(const char *participant_token) +{ + auto_off_manager.allow(participant_token); +} + +void auto_off_prohibit(const char *participant_token) +{ + auto_off_manager.prohibit(participant_token); +} + +void auto_off_set_mode(power_saving_level_t mode) +{ + auto_off_manager.set_mode(mode); +} + +power_saving_level_t auto_off_get_mode(void) +{ + return auto_off_manager.get_mode(); +} + +AutoOffManager auto_off_manager; diff --git a/src/Battery/AutoOffManager.h b/src/Battery/AutoOffManager.h new file mode 100644 index 00000000..ce5dea05 --- /dev/null +++ b/src/Battery/AutoOffManager.h @@ -0,0 +1,143 @@ +#ifndef _AUTO_OFF_MANAGER_H +#define _AUTO_OFF_MANAGER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Power saving levels used by AutoOffManager. + * + * Numeric values intentionally match the Kconfig settings: + * 0=off, 1=minimal, 2=balanced, 3=aggressive. + */ +typedef enum power_saving_level { + POWER_SAVING_LEVEL_OFF = 0, + POWER_SAVING_LEVEL_MINIMAL = 1, + POWER_SAVING_LEVEL_BALANCED = 2, + POWER_SAVING_LEVEL_AGGRESSIVE = 3, + /** Number of concrete levels. Not a selectable power saving mode. */ + POWER_SAVING_LEVEL_COUNT, +} power_saving_level_t; + +/** + * @brief C wrapper for AutoOffManager::register_participant(). + * + * See AutoOffManager::register_participant() for token and return-value semantics. + */ +int auto_off_register(const char *participant_token, power_saving_level_t level); + +/** + * @brief C wrapper for AutoOffManager::allow(). + * + * See AutoOffManager::allow() for token semantics. + */ +void auto_off_allow(const char *participant_token); + +/** + * @brief C wrapper for AutoOffManager::prohibit(). + * + * See AutoOffManager::prohibit() for token semantics. + */ +void auto_off_prohibit(const char *participant_token); + +/** + * @brief C wrapper for AutoOffManager::set_mode(). + * + * See AutoOffManager::set_mode() for mode semantics. + */ +void auto_off_set_mode(power_saving_level_t mode); + +/** + * @brief C wrapper for AutoOffManager::get_mode(). + * + * See AutoOffManager::get_mode() for mode semantics. + */ +power_saving_level_t auto_off_get_mode(void); + +#ifdef __cplusplus +} + +#include + +struct k_work; + +/** + * @brief Coordinates automatic power-off across dynamically registered participants. + */ +class AutoOffManager { +public: + /** + * @brief Initialize the manager and load the default mode from Kconfig. + * + * @return 0 on success or -EALREADY if already initialized. + */ + int init(); + + /** + * @brief Register an auto-off participant. + * + * @param participant_token Unique, stable token identifying the participant. + * @param level First power saving mode where this participant participates. + * + * @return 0 on success, -EINVAL for invalid arguments, -EALREADY if the + * token was already registered, or -ENOMEM if the registry is full. + */ + int register_participant(const char *participant_token, power_saving_level_t level); + + /** + * @brief Mark a registered participant as allowing auto-off. + * + * @param participant_token The same unique token used for registration. + */ + void allow(const char *participant_token); + + /** + * @brief Mark a registered participant as prohibiting auto-off. + * + * @param participant_token The same unique token used for registration. + */ + void prohibit(const char *participant_token); + + /** + * @brief Set the current power saving mode. + * + * @param mode New power saving mode. + */ + void set_mode(power_saving_level_t mode); + + /** + * @brief Get the current power saving mode. + * + * @return Current power saving mode. + */ + power_saving_level_t get_mode(); + +private: + struct ParticipantEntry { + const char *token = nullptr; + power_saving_level_t level = POWER_SAVING_LEVEL_OFF; + bool allowed = false; + bool registered = false; + }; + + friend void auto_off_work_handler(struct k_work *work); + + ParticipantEntry *find_participant(const char *participant_token); + bool participant_is_considered(const ParticipantEntry &entry) const; + bool all_considered_participants_allow() const; + void schedule() const; + void cancel() const; + void evaluate(); + void handle_timeout(); + + std::array participants_{}; + power_saving_level_t current_mode_ = POWER_SAVING_LEVEL_OFF; + bool initialized_ = false; +}; + +/** @brief Global AutoOffManager singleton. */ +extern AutoOffManager auto_off_manager; +#endif + +#endif diff --git a/src/Battery/CMakeLists.txt b/src/Battery/CMakeLists.txt index a1c2dd11..de32a855 100644 --- a/src/Battery/CMakeLists.txt +++ b/src/Battery/CMakeLists.txt @@ -2,7 +2,8 @@ target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/BQ27220.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BQ25120a.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/AutoOffManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PowerManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PowerManager.c ${CMAKE_CURRENT_SOURCE_DIR}/BootState.c -) \ No newline at end of file +) diff --git a/src/Battery/Kconfig b/src/Battery/Kconfig index 47b8645d..e6ba7239 100644 --- a/src/Battery/Kconfig +++ b/src/Battery/Kconfig @@ -1,5 +1,57 @@ menu "Battery Configuration" +config AUTO_OFF_MAX_PARTICIPANTS + int "Maximum number of participants in auto-off" + range 1 32 + default 8 + help + Maximum number of participants that can register with AutoOffManager. + +config AUTO_OFF_DEFAULT_POWER_SAVING_MODE + int "Default auto-off power saving mode" + range 0 3 + default 2 + help + Default mode for automatic power-off. + Values: 0=off, 1=minimal, 2=balanced, 3=aggressive. + +config POWER_SAVING_LEVEL_LEAUDIO + int "LE Audio auto-off power saving level" + range 0 3 + default 3 + help + First power saving mode where LE Audio participates in auto-off. + Values: 0=off, 1=minimal, 2=balanced, 3=aggressive. + +config POWER_SAVING_LEVEL_SENSOR_MANAGER + int "SensorManager auto-off power saving level" + range 0 3 + default 2 + help + First power saving mode where SensorManager participates in auto-off. + Values: 0=off, 1=minimal, 2=balanced, 3=aggressive. + +config AUTO_OFF_TIMEOUT_AGGRESSIVE + int "Aggressive auto-off timeout in minutes" + range 1 1440 + default 5 + help + Idle time before power-off when auto-off is in aggressive mode. + +config AUTO_OFF_TIMEOUT_BALANCED + int "Balanced auto-off timeout in minutes" + range 1 1440 + default 15 + help + Idle time before power-off when auto-off is in balanced mode. + +config AUTO_OFF_TIMEOUT_MINIMAL + int "Minimal auto-off timeout in minutes" + range 1 1440 + default 30 + help + Idle time before power-off when auto-off is in minimal mode. + config BATTERY_SYSDOWN_SET_OFFSET int "System Down Set Voltage Offset (mV)" default 250 diff --git a/src/Battery/PowerManager.cpp b/src/Battery/PowerManager.cpp index 628739da..dc687ec1 100644 --- a/src/Battery/PowerManager.cpp +++ b/src/Battery/PowerManager.cpp @@ -2,6 +2,7 @@ #include "macros_common.h" +#include #include #include #include @@ -29,6 +30,7 @@ #include "bt_mgmt.h" #include "bt_mgmt_ctlr_cfg_internal.h" +#include "AutoOffManager.h" #include @@ -436,6 +438,11 @@ int PowerManager::begin() { oe_boot_state.device_id = (((uint64_t) device_id[1]) << 32) | device_id[0]; + ret = auto_off_manager.init(); + if (ret && ret != -EALREADY) { + LOG_WRN("Failed to initialize auto-off manager: %d", ret); + } + return 0; } @@ -758,4 +765,4 @@ SHELL_STATIC_SUBCMD_SET_CREATE(battery_cmd, SHELL_CMD_REGISTER(battery, &battery_cmd, "Power Manager Commands", NULL); -PowerManager power_manager; \ No newline at end of file +PowerManager power_manager; From 5e03c0adcb59ecacf6b00ce5f7d4f70674ab839d Mon Sep 17 00:00:00 2001 From: Ruben Lohberg Date: Mon, 4 May 2026 15:15:45 +0200 Subject: [PATCH 2/9] feat(auto-off): implement auto-off for sensor manager --- src/SensorManager/SensorManager.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/SensorManager/SensorManager.cpp b/src/SensorManager/SensorManager.cpp index 1c8a994b..e8a8c01d 100644 --- a/src/SensorManager/SensorManager.cpp +++ b/src/SensorManager/SensorManager.cpp @@ -1,5 +1,6 @@ #include "SensorManager.h" +#include #include #include "macros_common.h" @@ -16,6 +17,7 @@ #include "openearable_common.h" #include "StateIndicator.h" +#include "AutoOffManager.h" #include #include "../SD_Card/SDLogger/SDLogger.h" @@ -62,6 +64,7 @@ struct k_work_q sensor_work_q; K_THREAD_STACK_DEFINE(sensor_publish_thread_stack, CONFIG_SENSOR_PUB_STACK_SIZE); int active_sensors = 0; +static const char sensor_manager_auto_off_token[] = "SensorManager"; static void config_work_handler(struct k_work *work); @@ -100,6 +103,15 @@ void init_sensor_manager() { k_poll_signal_init(&sensor_manager_sig); sdlogger.init(); + + int ret = auto_off_manager.register_participant( + sensor_manager_auto_off_token, + (power_saving_level_t)CONFIG_POWER_SAVING_LEVEL_SENSOR_MANAGER); + if (ret && ret != -EALREADY) { + LOG_WRN("Failed to register SensorManager with auto-off: %d", ret); + } else { + auto_off_manager.allow(sensor_manager_auto_off_token); + } } void start_sensor_manager() { @@ -136,6 +148,7 @@ void stop_sensor_manager() { Microphone::sensor.stop(); active_sensors = 0; + auto_off_manager.allow(sensor_manager_auto_off_token); k_work_queue_drain(&sensor_work_q, true); @@ -211,6 +224,7 @@ static void config_work_handler(struct k_work *work) { sensor->start(config.sampleRateIndex); if (sensor->is_running()) { active_sensors++; + auto_off_manager.prohibit(sensor_manager_auto_off_token); } } } @@ -244,7 +258,10 @@ static void config_work_handler(struct k_work *work) { set_sensor_config_status(config); - if (active_sensors == 0) stop_sensor_manager(); + if (active_sensors == 0) { + auto_off_manager.allow(sensor_manager_auto_off_token); + stop_sensor_manager(); + } } void config_sensor(struct sensor_config * config) { @@ -257,4 +274,4 @@ void config_sensor(struct sensor_config * config) { //k_work_queue_drain(&sensor_work_q, true); k_work_submit(&config_work); //k_work_queue_unplug(&sensor_work_q); -} \ No newline at end of file +} From 1ba00d00f8499992f52b7d1b32c0e78e3a04102e Mon Sep 17 00:00:00 2001 From: Ruben Lohberg Date: Mon, 4 May 2026 15:16:04 +0200 Subject: [PATCH 3/9] feat(auto-off): implement auto-off for LEAudio --- src/bluetooth/bt_stream/le_audio.c | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/bluetooth/bt_stream/le_audio.c b/src/bluetooth/bt_stream/le_audio.c index 50a8d5ed..7490c381 100644 --- a/src/bluetooth/bt_stream/le_audio.c +++ b/src/bluetooth/bt_stream/le_audio.c @@ -6,12 +6,69 @@ #include "le_audio.h" +#include + #include #include +#include +#include + +#include "AutoOffManager.h" +#include "zbus_common.h" #include LOG_MODULE_REGISTER(le_audio, CONFIG_BLE_LOG_LEVEL); +static const char le_audio_auto_off_token[] = "LEAudio"; + +ZBUS_CHAN_DECLARE(le_audio_chan); + +static void le_audio_auto_off_event_handler(const struct zbus_channel *chan); +ZBUS_LISTENER_DEFINE(le_audio_auto_off_listener, le_audio_auto_off_event_handler); + +static int le_audio_auto_off_init(void) +{ + int ret; + + ret = auto_off_register(le_audio_auto_off_token, + (power_saving_level_t)CONFIG_POWER_SAVING_LEVEL_LEAUDIO); + if (ret && ret != -EALREADY) { + LOG_WRN("Failed to register LE Audio with auto-off: %d", ret); + return ret; + } + + auto_off_allow(le_audio_auto_off_token); + + ret = zbus_chan_add_obs(&le_audio_chan, &le_audio_auto_off_listener, + ZBUS_ADD_OBS_TIMEOUT_MS); + if (ret) { + LOG_WRN("Failed to add LE Audio auto-off listener: %d", ret); + return ret; + } + + return 0; +} + +static void le_audio_auto_off_event_handler(const struct zbus_channel *chan) +{ + const struct le_audio_msg *msg = zbus_chan_const_msg(chan); + + switch (msg->event) { + case LE_AUDIO_EVT_STREAMING: + auto_off_prohibit(le_audio_auto_off_token); + break; + case LE_AUDIO_EVT_NOT_STREAMING: + case LE_AUDIO_EVT_SYNC_LOST: + case LE_AUDIO_EVT_NO_VALID_CFG: + auto_off_allow(le_audio_auto_off_token); + break; + default: + break; + } +} + +SYS_INIT(le_audio_auto_off_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + int le_audio_ep_state_get(struct bt_bap_ep *ep, uint8_t *state) { int ret; From bfb9085ce4d4f2f6615cc93346fd799cf3e4e7ad Mon Sep 17 00:00:00 2001 From: Ruben Lohberg Date: Mon, 11 May 2026 10:13:38 +0200 Subject: [PATCH 4/9] refactor: Improve readability of auto-off code --- prj.conf | 7 +- src/Battery/AutoOffManager.cpp | 110 +++++++++++++---------------- src/Battery/AutoOffManager.h | 22 +++--- src/Battery/Kconfig | 17 +---- src/bluetooth/bt_stream/le_audio.c | 2 +- 5 files changed, 72 insertions(+), 86 deletions(-) diff --git a/prj.conf b/prj.conf index 4ace7e54..7835d3b5 100644 --- a/prj.conf +++ b/prj.conf @@ -85,10 +85,15 @@ CONFIG_I2C_NRFX=y CONFIG_POWEROFF=y -# Auto-off power saving levels: 0=off, 1=minimal, 2=balanced, 3=aggressive +# Auto-off power saving modes: 0=off, 1=minimal, 2=balanced, 3=aggressive CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE=2 + +# Auto-off power saving levels of different components. Higher level means the +# component can prevent an auto-off even at a more aggressive power saving mode CONFIG_POWER_SAVING_LEVEL_LEAUDIO=3 CONFIG_POWER_SAVING_LEVEL_SENSOR_MANAGER=2 + +# Auto-off timeouts in minutes for each power saving mode CONFIG_AUTO_OFF_TIMEOUT_AGGRESSIVE=5 CONFIG_AUTO_OFF_TIMEOUT_BALANCED=15 CONFIG_AUTO_OFF_TIMEOUT_MINIMAL=30 diff --git a/src/Battery/AutoOffManager.cpp b/src/Battery/AutoOffManager.cpp index 2951102f..3d4c9f94 100644 --- a/src/Battery/AutoOffManager.cpp +++ b/src/Battery/AutoOffManager.cpp @@ -1,6 +1,5 @@ #include "AutoOffManager.h" -#include #include #include #include @@ -36,6 +35,7 @@ static_assert(power_saving_modes.size() == POWER_SAVING_LEVEL_COUNT, K_MUTEX_DEFINE(auto_off_mutex); K_WORK_DELAYABLE_DEFINE(auto_off_work, auto_off_work_handler); +// RAII implementation for k_mutext instead of std::lock_guard class AutoOffLock { public: AutoOffLock() @@ -54,35 +54,31 @@ class AutoOffLock { } -bool AutoOffManager::participant_is_considered(const ParticipantEntry &entry) const +bool AutoOffManager::participant_is_considered(const ParticipantEntry &participant) const { - if (!entry.registered || current_mode_ == POWER_SAVING_LEVEL_OFF || - entry.level == POWER_SAVING_LEVEL_OFF) { + if (!participant.registered || current_mode == POWER_SAVING_LEVEL_OFF || + participant.level == POWER_SAVING_LEVEL_OFF) { return false; } - return entry.level >= current_mode_; + return participant.level >= current_mode; } AutoOffManager::ParticipantEntry *AutoOffManager::find_participant(const char *participant_token) { - auto participant = std::find_if( - participants_.begin(), participants_.end(), - [participant_token](const auto &entry) { - return entry.registered && - std::strcmp(entry.token, participant_token) == 0; - }); - - if (participant == participants_.end()) { - return nullptr; + for (auto &entry : participants) { + if (entry.registered && std::strcmp(entry.token, participant_token) == 0) { + return &entry; + } } - return &(*participant); + return nullptr; } + bool AutoOffManager::all_considered_participants_allow() const { - for (const auto &participant : participants_) { + for (const auto &participant : participants) { if (participant_is_considered(participant) && !participant.allowed) { return false; } @@ -93,13 +89,13 @@ bool AutoOffManager::all_considered_participants_allow() const void AutoOffManager::schedule() const { - if (current_mode_ < POWER_SAVING_LEVEL_OFF || - static_cast(current_mode_) >= power_saving_modes.size()) { - LOG_WRN("Cannot schedule auto-off for invalid mode %d", current_mode_); + if (current_mode < POWER_SAVING_LEVEL_OFF || + static_cast(current_mode) >= power_saving_modes.size()) { + LOG_WRN("Cannot schedule auto-off for invalid mode %d", current_mode); return; } - const auto &mode = power_saving_modes[static_cast(current_mode_)]; + const auto &mode = power_saving_modes[static_cast(current_mode)]; (void)k_work_reschedule(&auto_off_work, K_MINUTES(mode.timeout_minutes)); LOG_INF("Auto-off armed for %d min in %s mode", mode.timeout_minutes, mode.name); @@ -112,11 +108,11 @@ void AutoOffManager::cancel() const void AutoOffManager::evaluate() { - if (!initialized_) { + if (!initialized) { return; } - if (current_mode_ == POWER_SAVING_LEVEL_OFF) { + if (current_mode == POWER_SAVING_LEVEL_OFF) { cancel(); return; } @@ -132,21 +128,21 @@ int AutoOffManager::init() { AutoOffLock lock; - if (initialized_) { + if (initialized) { return -EALREADY; } - current_mode_ = (power_saving_level_t)CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE; + current_mode = (power_saving_level_t)CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE; - if (current_mode_ < POWER_SAVING_LEVEL_OFF || - static_cast(current_mode_) >= power_saving_modes.size()) { - LOG_WRN("Invalid default auto-off mode %d, disabling auto-off", current_mode_); - current_mode_ = POWER_SAVING_LEVEL_OFF; + if (current_mode < POWER_SAVING_LEVEL_OFF || + static_cast(current_mode) >= power_saving_modes.size()) { + LOG_WRN("Invalid default auto-off mode %d, disabling auto-off", current_mode); + current_mode = POWER_SAVING_LEVEL_OFF; } - const auto &mode = power_saving_modes[static_cast(current_mode_)]; + const auto &mode = power_saving_modes[static_cast(current_mode)]; - initialized_ = true; + initialized = true; LOG_INF("Auto-off initialized in %s mode", mode.name); evaluate(); @@ -154,15 +150,6 @@ int AutoOffManager::init() return 0; } -/* - * Register one auto-off participant. - * - * participant_token is the identity key for a participant. It must be unique - * across all participants and stable for the lifetime of the registration. A - * participant must reuse exactly the same token for register_participant(), - * allow(), and prohibit(); a different token is treated as a different - * participant and will not update the original registration. - */ int AutoOffManager::register_participant(const char *participant_token, power_saving_level_t level) { if (participant_token == nullptr || level < POWER_SAVING_LEVEL_OFF || @@ -181,26 +168,29 @@ int AutoOffManager::register_participant(const char *participant_token, power_sa return -EALREADY; } - auto free_participant = std::find_if( - participants_.begin(), participants_.end(), - [](const auto &entry) { return !entry.registered; }); - - if (free_participant != participants_.end()) { - free_participant->token = participant_token; - free_participant->level = level; - free_participant->allowed = false; - free_participant->registered = true; - - LOG_INF("Registered auto-off participant token %s at %s level", - participant_token, level_config.name); - evaluate(); + ParticipantEntry *free_participant = nullptr; + for (auto &participant : participants) { + if (!participant.registered) { + free_participant = &participant; + break; + } + } - return 0; + if (free_participant == nullptr) { + LOG_WRN("No room to register auto-off participant token %s", participant_token); + return -ENOMEM; } - LOG_WRN("No room to register auto-off participant token %s", participant_token); + free_participant->token = participant_token; + free_participant->level = level; + free_participant->allowed = false; + free_participant->registered = true; - return -ENOMEM; + LOG_INF("Registered auto-off participant token %s at %s level", + participant_token, level_config.name); + evaluate(); + + return 0; } void AutoOffManager::allow(const char *participant_token) @@ -259,8 +249,8 @@ void AutoOffManager::set_mode(power_saving_level_t mode) AutoOffLock lock; - if (current_mode_ != mode) { - current_mode_ = mode; + if (current_mode != mode) { + current_mode = mode; LOG_INF("Auto-off mode set to %s", mode_config.name); evaluate(); } @@ -270,7 +260,7 @@ power_saving_level_t AutoOffManager::get_mode() { AutoOffLock lock; - return current_mode_; + return current_mode; } void AutoOffManager::handle_timeout() @@ -279,7 +269,7 @@ void AutoOffManager::handle_timeout() { AutoOffLock lock; - should_power_down = initialized_ && current_mode_ != POWER_SAVING_LEVEL_OFF && + should_power_down = initialized && current_mode != POWER_SAVING_LEVEL_OFF && all_considered_participants_allow(); } @@ -298,7 +288,7 @@ void auto_off_work_handler(struct k_work *work) auto_off_manager.handle_timeout(); } -int auto_off_register(const char *participant_token, power_saving_level_t level) +int auto_off_register_participant(const char *participant_token, power_saving_level_t level) { return auto_off_manager.register_participant(participant_token, level); } diff --git a/src/Battery/AutoOffManager.h b/src/Battery/AutoOffManager.h index ce5dea05..b6a122ba 100644 --- a/src/Battery/AutoOffManager.h +++ b/src/Battery/AutoOffManager.h @@ -23,35 +23,35 @@ typedef enum power_saving_level { /** * @brief C wrapper for AutoOffManager::register_participant(). * - * See AutoOffManager::register_participant() for token and return-value semantics. + * See AutoOffManager::register_participant() for documentation. */ -int auto_off_register(const char *participant_token, power_saving_level_t level); +int auto_off_register_participant(const char *participant_token, power_saving_level_t level); /** * @brief C wrapper for AutoOffManager::allow(). * - * See AutoOffManager::allow() for token semantics. + * See AutoOffManager::allow() for documentation. */ void auto_off_allow(const char *participant_token); /** * @brief C wrapper for AutoOffManager::prohibit(). * - * See AutoOffManager::prohibit() for token semantics. + * See AutoOffManager::prohibit() for documentation. */ void auto_off_prohibit(const char *participant_token); /** * @brief C wrapper for AutoOffManager::set_mode(). * - * See AutoOffManager::set_mode() for mode semantics. + * See AutoOffManager::set_mode() for documentation. */ void auto_off_set_mode(power_saving_level_t mode); /** * @brief C wrapper for AutoOffManager::get_mode(). * - * See AutoOffManager::get_mode() for mode semantics. + * See AutoOffManager::get_mode() for documentation. */ power_saving_level_t auto_off_get_mode(void); @@ -79,6 +79,8 @@ class AutoOffManager { * * @param participant_token Unique, stable token identifying the participant. * @param level First power saving mode where this participant participates. + * Higher level means the participant can prevent an auto-off even at a more + * aggressive power saving mode * * @return 0 on success, -EINVAL for invalid arguments, -EALREADY if the * token was already registered, or -ENOMEM if the registry is full. @@ -114,6 +116,8 @@ class AutoOffManager { power_saving_level_t get_mode(); private: + static constexpr int max_participants = 8; + struct ParticipantEntry { const char *token = nullptr; power_saving_level_t level = POWER_SAVING_LEVEL_OFF; @@ -131,9 +135,9 @@ class AutoOffManager { void evaluate(); void handle_timeout(); - std::array participants_{}; - power_saving_level_t current_mode_ = POWER_SAVING_LEVEL_OFF; - bool initialized_ = false; + std::array participants{}; + power_saving_level_t current_mode = POWER_SAVING_LEVEL_OFF; + bool initialized = false; }; /** @brief Global AutoOffManager singleton. */ diff --git a/src/Battery/Kconfig b/src/Battery/Kconfig index e6ba7239..b6c99371 100644 --- a/src/Battery/Kconfig +++ b/src/Battery/Kconfig @@ -1,15 +1,7 @@ menu "Battery Configuration" -config AUTO_OFF_MAX_PARTICIPANTS - int "Maximum number of participants in auto-off" - range 1 32 - default 8 - help - Maximum number of participants that can register with AutoOffManager. - config AUTO_OFF_DEFAULT_POWER_SAVING_MODE int "Default auto-off power saving mode" - range 0 3 default 2 help Default mode for automatic power-off. @@ -17,37 +9,32 @@ config AUTO_OFF_DEFAULT_POWER_SAVING_MODE config POWER_SAVING_LEVEL_LEAUDIO int "LE Audio auto-off power saving level" - range 0 3 default 3 help First power saving mode where LE Audio participates in auto-off. - Values: 0=off, 1=minimal, 2=balanced, 3=aggressive. + See AUTO_OFF_DEFAULT_POWER_SAVING_MODE config POWER_SAVING_LEVEL_SENSOR_MANAGER int "SensorManager auto-off power saving level" - range 0 3 default 2 help First power saving mode where SensorManager participates in auto-off. - Values: 0=off, 1=minimal, 2=balanced, 3=aggressive. + See AUTO_OFF_DEFAULT_POWER_SAVING_MODE config AUTO_OFF_TIMEOUT_AGGRESSIVE int "Aggressive auto-off timeout in minutes" - range 1 1440 default 5 help Idle time before power-off when auto-off is in aggressive mode. config AUTO_OFF_TIMEOUT_BALANCED int "Balanced auto-off timeout in minutes" - range 1 1440 default 15 help Idle time before power-off when auto-off is in balanced mode. config AUTO_OFF_TIMEOUT_MINIMAL int "Minimal auto-off timeout in minutes" - range 1 1440 default 30 help Idle time before power-off when auto-off is in minimal mode. diff --git a/src/bluetooth/bt_stream/le_audio.c b/src/bluetooth/bt_stream/le_audio.c index 7490c381..d4f1c50a 100644 --- a/src/bluetooth/bt_stream/le_audio.c +++ b/src/bluetooth/bt_stream/le_audio.c @@ -30,7 +30,7 @@ static int le_audio_auto_off_init(void) { int ret; - ret = auto_off_register(le_audio_auto_off_token, + ret = auto_off_register_participant(le_audio_auto_off_token, (power_saving_level_t)CONFIG_POWER_SAVING_LEVEL_LEAUDIO); if (ret && ret != -EALREADY) { LOG_WRN("Failed to register LE Audio with auto-off: %d", ret); From 15622f89451ecbe7f06b7d9a621d8fddcde3f60a Mon Sep 17 00:00:00 2001 From: Ruben Lohberg Date: Mon, 11 May 2026 10:43:58 +0200 Subject: [PATCH 5/9] feat(auto-off): Make auto-off mode persistent --- src/Battery/AutoOffManager.cpp | 5 +++++ src/Battery/AutoOffManager.h | 7 +++++++ src/Battery/PowerManager.cpp | 6 ------ src/audio/streamctrl.c | 5 +++++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Battery/AutoOffManager.cpp b/src/Battery/AutoOffManager.cpp index 3d4c9f94..26e9eb42 100644 --- a/src/Battery/AutoOffManager.cpp +++ b/src/Battery/AutoOffManager.cpp @@ -288,6 +288,11 @@ void auto_off_work_handler(struct k_work *work) auto_off_manager.handle_timeout(); } +int auto_off_init(void) +{ + return auto_off_manager.init(); +} + int auto_off_register_participant(const char *participant_token, power_saving_level_t level) { return auto_off_manager.register_participant(participant_token, level); diff --git a/src/Battery/AutoOffManager.h b/src/Battery/AutoOffManager.h index b6a122ba..0e69bf3d 100644 --- a/src/Battery/AutoOffManager.h +++ b/src/Battery/AutoOffManager.h @@ -20,6 +20,13 @@ typedef enum power_saving_level { POWER_SAVING_LEVEL_COUNT, } power_saving_level_t; +/** + * @brief C wrapper for AutoOffManager::init(). + * + * See AutoOffManager::init() for documentation. + */ +int auto_off_init(void); + /** * @brief C wrapper for AutoOffManager::register_participant(). * diff --git a/src/Battery/PowerManager.cpp b/src/Battery/PowerManager.cpp index dc687ec1..e239208b 100644 --- a/src/Battery/PowerManager.cpp +++ b/src/Battery/PowerManager.cpp @@ -30,7 +30,6 @@ #include "bt_mgmt.h" #include "bt_mgmt_ctlr_cfg_internal.h" -#include "AutoOffManager.h" #include @@ -438,11 +437,6 @@ int PowerManager::begin() { oe_boot_state.device_id = (((uint64_t) device_id[1]) << 32) | device_id[0]; - ret = auto_off_manager.init(); - if (ret && ret != -EALREADY) { - LOG_WRN("Failed to initialize auto-off manager: %d", ret); - } - return 0; } diff --git a/src/audio/streamctrl.c b/src/audio/streamctrl.c index 5a1e7edb..47effba7 100644 --- a/src/audio/streamctrl.c +++ b/src/audio/streamctrl.c @@ -8,6 +8,7 @@ #include #include +#include #include "unicast_server.h" #include "zbus_common.h" @@ -26,6 +27,7 @@ #include "le_audio_rx.h" #include "fw_info_app.h" +#include "AutoOffManager.h" #include "BootState.h" #include @@ -660,6 +662,9 @@ int streamctrl_start() //streamctrl_start ret = bt_mgmt_init(); ERR_CHK(ret); + ret = auto_off_init(); + ERR_CHK_MSG(ret, "Failed to initialize auto-off manager"); + ret = audio_system_init(); ERR_CHK(ret); From e31ba2d58443e336401994fb9a8e9b4e4b9a4441 Mon Sep 17 00:00:00 2001 From: Ruben Lohberg Date: Mon, 11 May 2026 12:30:39 +0200 Subject: [PATCH 6/9] feat(power-saving-service): Expose ble service for power saving --- src/Battery/AutoOffManager.cpp | 109 +++++++++++++-- src/Battery/AutoOffManager.h | 27 ++++ src/Battery/PowerManager.cpp | 3 +- src/bluetooth/gatt_services/CMakeLists.txt | 1 + .../gatt_services/power_saving_service.c | 125 ++++++++++++++++++ .../gatt_services/power_saving_service.h | 29 ++++ 6 files changed, 278 insertions(+), 16 deletions(-) create mode 100644 src/bluetooth/gatt_services/power_saving_service.c create mode 100644 src/bluetooth/gatt_services/power_saving_service.h diff --git a/src/Battery/AutoOffManager.cpp b/src/Battery/AutoOffManager.cpp index 26e9eb42..bd87d438 100644 --- a/src/Battery/AutoOffManager.cpp +++ b/src/Battery/AutoOffManager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "PowerManager.h" @@ -24,14 +25,19 @@ struct PowerSavingModeConfig { /* Indexed by power_saving_level_t. Keep the order in sync with the enum values. */ constexpr std::array power_saving_modes = { { - { "off", 0 }, - { "minimal", CONFIG_AUTO_OFF_TIMEOUT_MINIMAL }, - { "balanced", CONFIG_AUTO_OFF_TIMEOUT_BALANCED }, - { "aggressive", CONFIG_AUTO_OFF_TIMEOUT_AGGRESSIVE }, + { "Off", 0 }, + { "Minimal", CONFIG_AUTO_OFF_TIMEOUT_MINIMAL }, + { "Balanced", CONFIG_AUTO_OFF_TIMEOUT_BALANCED }, + { "Aggressive", CONFIG_AUTO_OFF_TIMEOUT_AGGRESSIVE }, } }; static_assert(power_saving_modes.size() == POWER_SAVING_LEVEL_COUNT, "power_saving_modes must contain every concrete power saving level"); +constexpr const char *auto_off_settings_mode_key = "auto_off/mode"; + +power_saving_level_t loaded_mode = (power_saving_level_t)CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE; +bool loaded_mode_is_valid = false; + K_MUTEX_DEFINE(auto_off_mutex); K_WORK_DELAYABLE_DEFINE(auto_off_work, auto_off_work_handler); @@ -52,6 +58,53 @@ class AutoOffLock { AutoOffLock &operator=(const AutoOffLock &) = delete; }; +int auto_off_settings_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) +{ + if (std::strcmp(name, "mode") != 0) { + return -ENOENT; + } + + if (len != sizeof(int)) { + LOG_WRN("Ignoring auto-off mode setting with invalid size %zu", len); + return 0; + } + + int stored_mode; + const int ret = read_cb(cb_arg, &stored_mode, sizeof(stored_mode)); + if (ret < 0) { + return ret; + } + + if (ret != sizeof(stored_mode)) { + LOG_WRN("Ignoring incomplete auto-off mode setting read: %d", ret); + return 0; + } + + const auto mode = static_cast(stored_mode); + if (!auto_off_mode_is_supported(mode)) { + LOG_WRN("Ignoring invalid stored auto-off mode %d", stored_mode); + return 0; + } + + loaded_mode = mode; + loaded_mode_is_valid = true; + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(auto_off, "auto_off", nullptr, auto_off_settings_set, nullptr, + nullptr); + +void save_auto_off_mode(power_saving_level_t mode) +{ + const int stored_mode = static_cast(mode); + const int ret = settings_save_one(auto_off_settings_mode_key, &stored_mode, + sizeof(stored_mode)); + if (ret) { + LOG_WRN("Failed to persist auto-off mode %d: %d", stored_mode, ret); + } +} + } bool AutoOffManager::participant_is_considered(const ParticipantEntry &participant) const @@ -132,10 +185,11 @@ int AutoOffManager::init() return -EALREADY; } - current_mode = (power_saving_level_t)CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE; + current_mode = loaded_mode_is_valid ? + loaded_mode : + (power_saving_level_t)CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE; - if (current_mode < POWER_SAVING_LEVEL_OFF || - static_cast(current_mode) >= power_saving_modes.size()) { + if (!auto_off_mode_is_supported(current_mode)) { LOG_WRN("Invalid default auto-off mode %d, disabling auto-off", current_mode); current_mode = POWER_SAVING_LEVEL_OFF; } @@ -239,20 +293,27 @@ void AutoOffManager::prohibit(const char *participant_token) void AutoOffManager::set_mode(power_saving_level_t mode) { - if (mode < POWER_SAVING_LEVEL_OFF || - static_cast(mode) >= power_saving_modes.size()) { + if (!auto_off_mode_is_supported(mode)) { LOG_WRN("Ignoring invalid auto-off mode %d", mode); return; } const auto &mode_config = power_saving_modes[static_cast(mode)]; + bool mode_changed = false; - AutoOffLock lock; + { + AutoOffLock lock; - if (current_mode != mode) { - current_mode = mode; - LOG_INF("Auto-off mode set to %s", mode_config.name); - evaluate(); + if (current_mode != mode) { + current_mode = mode; + mode_changed = true; + LOG_INF("Auto-off mode set to %s", mode_config.name); + evaluate(); + } + } + + if (mode_changed) { + save_auto_off_mode(mode); } } @@ -318,4 +379,24 @@ power_saving_level_t auto_off_get_mode(void) return auto_off_manager.get_mode(); } +uint8_t auto_off_get_supported_mode_count(void) +{ + return static_cast(power_saving_modes.size()); +} + +const char *auto_off_get_mode_name(power_saving_level_t mode) +{ + if (!auto_off_mode_is_supported(mode)) { + return nullptr; + } + + return power_saving_modes[static_cast(mode)].name; +} + +int auto_off_mode_is_supported(power_saving_level_t mode) +{ + return mode >= POWER_SAVING_LEVEL_OFF && + static_cast(mode) < power_saving_modes.size(); +} + AutoOffManager auto_off_manager; diff --git a/src/Battery/AutoOffManager.h b/src/Battery/AutoOffManager.h index 0e69bf3d..370b665b 100644 --- a/src/Battery/AutoOffManager.h +++ b/src/Battery/AutoOffManager.h @@ -1,6 +1,8 @@ #ifndef _AUTO_OFF_MANAGER_H #define _AUTO_OFF_MANAGER_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -62,6 +64,31 @@ void auto_off_set_mode(power_saving_level_t mode); */ power_saving_level_t auto_off_get_mode(void); +/** + * @brief Get the number of selectable power saving modes. + * + * @return Number of selectable modes in the firmware-defined mode table. + */ +uint8_t auto_off_get_supported_mode_count(void); + +/** + * @brief Get the display name for a selectable power saving mode. + * + * @param mode Power saving mode identifier. + * + * @return Null-terminated mode name, or NULL if @p mode is not supported. + */ +const char *auto_off_get_mode_name(power_saving_level_t mode); + +/** + * @brief Check whether a power saving mode identifier is supported. + * + * @param mode Power saving mode identifier. + * + * @return 1 if @p mode is supported, otherwise 0. + */ +int auto_off_mode_is_supported(power_saving_level_t mode); + #ifdef __cplusplus } diff --git a/src/Battery/PowerManager.cpp b/src/Battery/PowerManager.cpp index e239208b..628739da 100644 --- a/src/Battery/PowerManager.cpp +++ b/src/Battery/PowerManager.cpp @@ -2,7 +2,6 @@ #include "macros_common.h" -#include #include #include #include @@ -759,4 +758,4 @@ SHELL_STATIC_SUBCMD_SET_CREATE(battery_cmd, SHELL_CMD_REGISTER(battery, &battery_cmd, "Power Manager Commands", NULL); -PowerManager power_manager; +PowerManager power_manager; \ No newline at end of file diff --git a/src/bluetooth/gatt_services/CMakeLists.txt b/src/bluetooth/gatt_services/CMakeLists.txt index dc64e7b9..141acb9e 100644 --- a/src/bluetooth/gatt_services/CMakeLists.txt +++ b/src/bluetooth/gatt_services/CMakeLists.txt @@ -4,5 +4,6 @@ target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/button_service.c ${CMAKE_CURRENT_SOURCE_DIR}/sensor_service.c ${CMAKE_CURRENT_SOURCE_DIR}/audio_config_service.c + ${CMAKE_CURRENT_SOURCE_DIR}/power_saving_service.c ${CMAKE_CURRENT_SOURCE_DIR}/led_service.cpp ) diff --git a/src/bluetooth/gatt_services/power_saving_service.c b/src/bluetooth/gatt_services/power_saving_service.c new file mode 100644 index 00000000..edec397b --- /dev/null +++ b/src/bluetooth/gatt_services/power_saving_service.c @@ -0,0 +1,125 @@ +#include "power_saving_service.h" + +#include +#include +#include +#include + +#include +#include + +#include "AutoOffManager.h" + +#include +LOG_MODULE_REGISTER(power_saving_service, CONFIG_BLE_LOG_LEVEL); + +#define POWER_SAVING_SUPPORTED_MODES_MAX_PAYLOAD_LEN 128 + +static uint8_t supported_modes_payload[POWER_SAVING_SUPPORTED_MODES_MAX_PAYLOAD_LEN]; + +static ssize_t encode_supported_modes(uint8_t *payload, size_t capacity) +{ + uint8_t mode_count = auto_off_get_supported_mode_count(); + size_t payload_len = 0; + + if (capacity < 1) { + return -ENOMEM; + } + + payload[payload_len++] = mode_count; + + for (uint8_t mode_id = 0; mode_id < mode_count; mode_id++) { + const char *name = auto_off_get_mode_name((power_saving_level_t)mode_id); + size_t name_len; + + if (name == NULL) { + return -EINVAL; + } + + name_len = strlen(name); + if (name_len > UINT8_MAX || + payload_len + 2 + name_len > capacity) { + return -ENOMEM; + } + + payload[payload_len++] = mode_id; + payload[payload_len++] = (uint8_t)name_len; + memcpy(&payload[payload_len], name, name_len); + payload_len += name_len; + } + + return payload_len; +} + +static ssize_t read_power_saving_mode(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset) +{ + uint8_t mode = (uint8_t)auto_off_get_mode(); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &mode, sizeof(mode)); +} + +static ssize_t write_power_saving_mode(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, + uint16_t len, + uint16_t offset, + uint8_t flags) +{ + const uint8_t *mode_buf = buf; + power_saving_level_t mode; + + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset != 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(uint8_t)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + mode = (power_saving_level_t)mode_buf[0]; + if (!auto_off_mode_is_supported(mode)) { + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + auto_off_set_mode(mode); + return len; +} + +static ssize_t read_supported_power_saving_modes(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset) +{ + ssize_t payload_len = encode_supported_modes( + supported_modes_payload, + sizeof(supported_modes_payload)); + + if (payload_len < 0) { + LOG_ERR("Failed to encode supported power saving modes: %d", (int)payload_len); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, supported_modes_payload, + (uint16_t)payload_len); +} + +BT_GATT_SERVICE_DEFINE(power_saving_svc, + BT_GATT_PRIMARY_SERVICE(BT_UUID_POWER_SAVING_SERVICE), + BT_GATT_CHARACTERISTIC(BT_UUID_POWER_SAVING_MODE, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, + read_power_saving_mode, write_power_saving_mode, NULL), + BT_GATT_CHARACTERISTIC(BT_UUID_POWER_SAVING_SUPPORTED_MODES, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + read_supported_power_saving_modes, NULL, NULL), +); diff --git a/src/bluetooth/gatt_services/power_saving_service.h b/src/bluetooth/gatt_services/power_saving_service.h new file mode 100644 index 00000000..e4ac1e74 --- /dev/null +++ b/src/bluetooth/gatt_services/power_saving_service.h @@ -0,0 +1,29 @@ +#ifndef _POWER_SAVING_SERVICE_H_ +#define _POWER_SAVING_SERVICE_H_ + +#include +#include +#include + +/** @brief Power saving service UUID. */ +#define BT_UUID_POWER_SAVING_SERVICE_VAL \ + BT_UUID_128_ENCODE(0xd63fd1f0, 0x5f68, 0x4ebb, 0xa7c7, 0x5e0fb9ae7557) + +/** @brief Current power saving mode characteristic UUID. */ +#define BT_UUID_POWER_SAVING_MODE_VAL \ + BT_UUID_128_ENCODE(0xd63fd1f1, 0x5f68, 0x4ebb, 0xa7c7, 0x5e0fb9ae7557) + +/** @brief Supported power saving modes characteristic UUID. */ +#define BT_UUID_POWER_SAVING_SUPPORTED_MODES_VAL \ + BT_UUID_128_ENCODE(0xd63fd1f2, 0x5f68, 0x4ebb, 0xa7c7, 0x5e0fb9ae7557) + +#define BT_UUID_POWER_SAVING_SERVICE \ + BT_UUID_DECLARE_128(BT_UUID_POWER_SAVING_SERVICE_VAL) + +#define BT_UUID_POWER_SAVING_MODE \ + BT_UUID_DECLARE_128(BT_UUID_POWER_SAVING_MODE_VAL) + +#define BT_UUID_POWER_SAVING_SUPPORTED_MODES \ + BT_UUID_DECLARE_128(BT_UUID_POWER_SAVING_SUPPORTED_MODES_VAL) + +#endif /* _POWER_SAVING_SERVICE_H_ */ From 12d46617f4f3083e7261420a39896b398306f922 Mon Sep 17 00:00:00 2001 From: Ruben Lohberg Date: Mon, 18 May 2026 20:47:00 +0200 Subject: [PATCH 7/9] refactor(auto-off): clarify power saving mode handling --- prj.conf | 2 +- src/Battery/AutoOffManager.cpp | 6 ++-- src/Battery/AutoOffManager.h | 32 +++++++++++++++++-- src/Battery/Kconfig | 6 ++-- .../gatt_services/power_saving_service.c | 17 ++++++++++ 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/prj.conf b/prj.conf index 7835d3b5..a5151953 100644 --- a/prj.conf +++ b/prj.conf @@ -86,7 +86,7 @@ CONFIG_I2C_NRFX=y CONFIG_POWEROFF=y # Auto-off power saving modes: 0=off, 1=minimal, 2=balanced, 3=aggressive -CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE=2 +CONFIG_POWER_SAVING_DEFAULT_MODE=2 # Auto-off power saving levels of different components. Higher level means the # component can prevent an auto-off even at a more aggressive power saving mode diff --git a/src/Battery/AutoOffManager.cpp b/src/Battery/AutoOffManager.cpp index bd87d438..0ffc8b42 100644 --- a/src/Battery/AutoOffManager.cpp +++ b/src/Battery/AutoOffManager.cpp @@ -35,7 +35,7 @@ static_assert(power_saving_modes.size() == POWER_SAVING_LEVEL_COUNT, constexpr const char *auto_off_settings_mode_key = "auto_off/mode"; -power_saving_level_t loaded_mode = (power_saving_level_t)CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE; +power_saving_level_t loaded_mode = (power_saving_level_t)CONFIG_POWER_SAVING_DEFAULT_MODE; bool loaded_mode_is_valid = false; K_MUTEX_DEFINE(auto_off_mutex); @@ -187,7 +187,7 @@ int AutoOffManager::init() current_mode = loaded_mode_is_valid ? loaded_mode : - (power_saving_level_t)CONFIG_AUTO_OFF_DEFAULT_POWER_SAVING_MODE; + (power_saving_level_t)CONFIG_POWER_SAVING_DEFAULT_MODE; if (!auto_off_mode_is_supported(current_mode)) { LOG_WRN("Invalid default auto-off mode %d, disabling auto-off", current_mode); @@ -206,7 +206,7 @@ int AutoOffManager::init() int AutoOffManager::register_participant(const char *participant_token, power_saving_level_t level) { - if (participant_token == nullptr || level < POWER_SAVING_LEVEL_OFF || + if (participant_token == nullptr || level <= POWER_SAVING_LEVEL_OFF || static_cast(level) >= power_saving_modes.size()) { return -EINVAL; } diff --git a/src/Battery/AutoOffManager.h b/src/Battery/AutoOffManager.h index 370b665b..18e20d4c 100644 --- a/src/Battery/AutoOffManager.h +++ b/src/Battery/AutoOffManager.h @@ -98,6 +98,33 @@ struct k_work; /** * @brief Coordinates automatic power-off across dynamically registered participants. + * + * The manager uses a participant-based permission model. Firmware components + * that may need to keep the device awake register themselves as auto-off + * participants by providing a unique token and a power saving level. The level + * defines the first power saving mode in which the participant is considered by + * the auto-off decision. A participant registered for a higher level can still + * prevent auto-off in more aggressive modes, but is ignored in less aggressive + * modes. + * + * Once registered, a participant calls prohibit() whenever it enters a critical + * region where power-off would be unsafe or disruptive, for example while + * writing data, streaming audio, or handling a time-sensitive operation. When + * the participant leaves that region, it calls allow(). Auto-off is armed only + * when every participant that is relevant for the current power saving mode is + * currently allowing power-off. If any relevant participant prohibits auto-off, + * the pending auto-off work is cancelled until the system is allowed again. + * + * The current power saving mode controls both which participants are considered + * and which timeout is used before power_down() is called. POWER_SAVING_LEVEL_OFF + * disables auto-off. + * + * Implementation notes: + * - AutoOffManager is used as a process-wide singleton via auto_off_manager. + * The firmware should not create additional instances. + * - Public C++ methods have thin C wrappers such as auto_off_allow() and + * auto_off_register_participant() so C firmware modules can use the same + * manager without depending on C++ linkage. */ class AutoOffManager { public: @@ -113,8 +140,9 @@ class AutoOffManager { * * @param participant_token Unique, stable token identifying the participant. * @param level First power saving mode where this participant participates. - * Higher level means the participant can prevent an auto-off even at a more - * aggressive power saving mode + * Higher level means the participant can prevent an auto-off even at a more + * aggressive power saving mode. POWER_SAVING_LEVEL_OFF is not a valid + * participant level. * * @return 0 on success, -EINVAL for invalid arguments, -EALREADY if the * token was already registered, or -ENOMEM if the registry is full. diff --git a/src/Battery/Kconfig b/src/Battery/Kconfig index b6c99371..6ee4d7d9 100644 --- a/src/Battery/Kconfig +++ b/src/Battery/Kconfig @@ -1,6 +1,6 @@ menu "Battery Configuration" -config AUTO_OFF_DEFAULT_POWER_SAVING_MODE +config POWER_SAVING_DEFAULT_MODE int "Default auto-off power saving mode" default 2 help @@ -12,14 +12,14 @@ config POWER_SAVING_LEVEL_LEAUDIO default 3 help First power saving mode where LE Audio participates in auto-off. - See AUTO_OFF_DEFAULT_POWER_SAVING_MODE + See POWER_SAVING_DEFAULT_MODE config POWER_SAVING_LEVEL_SENSOR_MANAGER int "SensorManager auto-off power saving level" default 2 help First power saving mode where SensorManager participates in auto-off. - See AUTO_OFF_DEFAULT_POWER_SAVING_MODE + See POWER_SAVING_DEFAULT_MODE config AUTO_OFF_TIMEOUT_AGGRESSIVE int "Aggressive auto-off timeout in minutes" diff --git a/src/bluetooth/gatt_services/power_saving_service.c b/src/bluetooth/gatt_services/power_saving_service.c index edec397b..faed5082 100644 --- a/src/bluetooth/gatt_services/power_saving_service.c +++ b/src/bluetooth/gatt_services/power_saving_service.c @@ -17,6 +17,21 @@ LOG_MODULE_REGISTER(power_saving_service, CONFIG_BLE_LOG_LEVEL); static uint8_t supported_modes_payload[POWER_SAVING_SUPPORTED_MODES_MAX_PAYLOAD_LEN]; +/* + * Build the value returned by the "supported power saving modes" GATT + * characteristic. + * + * The client needs more than the currently selected mode: it also needs to know + * which numeric mode IDs are valid and which labels should be shown in the UI. + * This function serializes the AutoOffManager mode table into a compact, + * self-describing byte stream: + * + * byte 0: number of modes + * repeated for each mode: + * byte 0: mode ID, matching power_saving_level_t + * byte 1: mode name length in bytes + * bytes: mode name, without a terminating NUL + */ static ssize_t encode_supported_modes(uint8_t *payload, size_t capacity) { uint8_t mode_count = auto_off_get_supported_mode_count(); @@ -26,6 +41,7 @@ static ssize_t encode_supported_modes(uint8_t *payload, size_t capacity) return -ENOMEM; } + /* Prefix the payload with the number of following mode records. */ payload[payload_len++] = mode_count; for (uint8_t mode_id = 0; mode_id < mode_count; mode_id++) { @@ -42,6 +58,7 @@ static ssize_t encode_supported_modes(uint8_t *payload, size_t capacity) return -ENOMEM; } + /* Append one length-prefixed record so clients can parse without NULs. */ payload[payload_len++] = mode_id; payload[payload_len++] = (uint8_t)name_len; memcpy(&payload[payload_len], name, name_len); From 55e2e25d3a3752f44261782c507083847f3ea8b1 Mon Sep 17 00:00:00 2001 From: Ruben Lohberg Date: Mon, 1 Jun 2026 11:26:08 +0200 Subject: [PATCH 8/9] docs(auto-off): clarify power_saving_level semantics --- prj.conf | 7 ++++--- src/Battery/AutoOffManager.cpp | 8 +++++++- src/Battery/AutoOffManager.h | 34 +++++++++++++++++++++++++--------- src/Battery/Kconfig | 16 +++++++++++----- 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/prj.conf b/prj.conf index a5151953..ec8593c4 100644 --- a/prj.conf +++ b/prj.conf @@ -85,11 +85,12 @@ CONFIG_I2C_NRFX=y CONFIG_POWEROFF=y -# Auto-off power saving modes: 0=off, 1=minimal, 2=balanced, 3=aggressive +# Auto-off power saving modes: 0=off, 1=minimal, 2=balanced, 3=aggressive. +# Higher modes are more aggressive: shorter timeout, fewer participants can veto. CONFIG_POWER_SAVING_DEFAULT_MODE=2 -# Auto-off power saving levels of different components. Higher level means the -# component can prevent an auto-off even at a more aggressive power saving mode +# Auto-off participant levels are the most aggressive mode where a component +# may veto. Level 2 applies in modes 1 and 2, but not mode 3. CONFIG_POWER_SAVING_LEVEL_LEAUDIO=3 CONFIG_POWER_SAVING_LEVEL_SENSOR_MANAGER=2 diff --git a/src/Battery/AutoOffManager.cpp b/src/Battery/AutoOffManager.cpp index 0ffc8b42..6d1bb38b 100644 --- a/src/Battery/AutoOffManager.cpp +++ b/src/Battery/AutoOffManager.cpp @@ -41,7 +41,7 @@ bool loaded_mode_is_valid = false; K_MUTEX_DEFINE(auto_off_mutex); K_WORK_DELAYABLE_DEFINE(auto_off_work, auto_off_work_handler); -// RAII implementation for k_mutext instead of std::lock_guard +// RAII implementation for k_mutex instead of std::lock_guard class AutoOffLock { public: AutoOffLock() @@ -114,6 +114,12 @@ bool AutoOffManager::participant_is_considered(const ParticipantEntry &participa return false; } + /* + * The participant level is the most aggressive power saving mode where it may veto + * auto-off. Example: a Balanced participant is considered in Minimal and + * Balanced modes, but ignored in Aggressive mode. Aggressive power saving + * considers fewer participant vetoes, so auto-offs are more likely. + */ return participant.level >= current_mode; } diff --git a/src/Battery/AutoOffManager.h b/src/Battery/AutoOffManager.h index 18e20d4c..35006c7b 100644 --- a/src/Battery/AutoOffManager.h +++ b/src/Battery/AutoOffManager.h @@ -12,6 +12,14 @@ extern "C" { * * Numeric values intentionally match the Kconfig settings: * 0=off, 1=minimal, 2=balanced, 3=aggressive. + * + * When used as the current mode, a higher numeric value means more aggressive + * power saving: the auto-off timeout is shorter and fewer participants are + * allowed to veto power-off, making auto-off more likely. + * + * When used as a participant level, a higher numeric value means larger veto + * power: that participant may still prohibit auto-off in higher, more + * aggressive power saving modes. */ typedef enum power_saving_level { POWER_SAVING_LEVEL_OFF = 0, @@ -101,11 +109,18 @@ struct k_work; * * The manager uses a participant-based permission model. Firmware components * that may need to keep the device awake register themselves as auto-off - * participants by providing a unique token and a power saving level. The level - * defines the first power saving mode in which the participant is considered by - * the auto-off decision. A participant registered for a higher level can still - * prevent auto-off in more aggressive modes, but is ignored in less aggressive - * modes. + * participants by providing a unique token and a power saving level. + * + * The current power saving mode controls how strict auto-off is. More + * aggressive modes have shorter timeouts and consider fewer participant vetoes, + * so auto-off is more likely to happen. + * + * A participant's level is its veto power: it defines the most aggressive power + * saving mode in which the participant is still considered by the auto-off + * decision. A participant registered for level 2 (balanced) can prevent + * auto-off in levels 1 and 2, but is ignored in level 3 (aggressive). A + * participant registered for level 3 has the largest veto power and may still + * block auto-off even in aggressive mode. * * Once registered, a participant calls prohibit() whenever it enters a critical * region where power-off would be unsafe or disruptive, for example while @@ -139,10 +154,11 @@ class AutoOffManager { * @brief Register an auto-off participant. * * @param participant_token Unique, stable token identifying the participant. - * @param level First power saving mode where this participant participates. - * Higher level means the participant can prevent an auto-off even at a more - * aggressive power saving mode. POWER_SAVING_LEVEL_OFF is not a valid - * participant level. + * @param level Most aggressive power saving mode where this participant may + * prohibit auto-off. Higher levels grant larger veto power because the + * participant remains relevant in more aggressive modes. The participant is + * considered when the current mode is less than or equal to this level. + * POWER_SAVING_LEVEL_OFF is not a valid participant level. * * @return 0 on success, -EINVAL for invalid arguments, -EALREADY if the * token was already registered, or -ENOMEM if the registry is full. diff --git a/src/Battery/Kconfig b/src/Battery/Kconfig index 6ee4d7d9..b0939acd 100644 --- a/src/Battery/Kconfig +++ b/src/Battery/Kconfig @@ -5,21 +5,27 @@ config POWER_SAVING_DEFAULT_MODE default 2 help Default mode for automatic power-off. - Values: 0=off, 1=minimal, 2=balanced, 3=aggressive. + Values: 0=off, 1=minimal, 2=balanced, 3=aggressive. Higher + values are more aggressive: the timeout is shorter and fewer + participants can prohibit auto-off. config POWER_SAVING_LEVEL_LEAUDIO int "LE Audio auto-off power saving level" default 3 help - First power saving mode where LE Audio participates in auto-off. - See POWER_SAVING_DEFAULT_MODE + Most aggressive power saving mode where LE Audio may prohibit + auto-off. A participant is considered when the current mode is + less than or equal to its level. + See POWER_SAVING_DEFAULT_MODE. config POWER_SAVING_LEVEL_SENSOR_MANAGER int "SensorManager auto-off power saving level" default 2 help - First power saving mode where SensorManager participates in auto-off. - See POWER_SAVING_DEFAULT_MODE + Most aggressive power saving mode where SensorManager may prohibit + auto-off. For example, level 2 applies in minimal and balanced + modes, but not in aggressive mode. + See POWER_SAVING_DEFAULT_MODE. config AUTO_OFF_TIMEOUT_AGGRESSIVE int "Aggressive auto-off timeout in minutes" From 351c68da66b36693317532d398d3ca8ea2e137b5 Mon Sep 17 00:00:00 2001 From: Ruben Lohberg Date: Mon, 1 Jun 2026 11:47:55 +0200 Subject: [PATCH 9/9] refactor(auto-off): tidy integration changes --- src/Battery/AutoOffManager.cpp | 1 - src/Battery/AutoOffManager.h | 2 +- src/Battery/CMakeLists.txt | 2 +- src/SensorManager/SensorManager.cpp | 7 ++----- src/audio/streamctrl.c | 3 +-- src/bluetooth/gatt_services/power_saving_service.h | 2 -- 6 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/Battery/AutoOffManager.cpp b/src/Battery/AutoOffManager.cpp index 6d1bb38b..5e62bf1a 100644 --- a/src/Battery/AutoOffManager.cpp +++ b/src/Battery/AutoOffManager.cpp @@ -134,7 +134,6 @@ AutoOffManager::ParticipantEntry *AutoOffManager::find_participant(const char *p return nullptr; } - bool AutoOffManager::all_considered_participants_allow() const { for (const auto &participant : participants) { diff --git a/src/Battery/AutoOffManager.h b/src/Battery/AutoOffManager.h index 35006c7b..7488ca1a 100644 --- a/src/Battery/AutoOffManager.h +++ b/src/Battery/AutoOffManager.h @@ -132,7 +132,7 @@ struct k_work; * * The current power saving mode controls both which participants are considered * and which timeout is used before power_down() is called. POWER_SAVING_LEVEL_OFF - * disables auto-off. + * disables auto-off. * * Implementation notes: * - AutoOffManager is used as a process-wide singleton via auto_off_manager. diff --git a/src/Battery/CMakeLists.txt b/src/Battery/CMakeLists.txt index de32a855..65ad0603 100644 --- a/src/Battery/CMakeLists.txt +++ b/src/Battery/CMakeLists.txt @@ -6,4 +6,4 @@ target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/PowerManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PowerManager.c ${CMAKE_CURRENT_SOURCE_DIR}/BootState.c -) +) \ No newline at end of file diff --git a/src/SensorManager/SensorManager.cpp b/src/SensorManager/SensorManager.cpp index e8a8c01d..8ad2459a 100644 --- a/src/SensorManager/SensorManager.cpp +++ b/src/SensorManager/SensorManager.cpp @@ -258,10 +258,7 @@ static void config_work_handler(struct k_work *work) { set_sensor_config_status(config); - if (active_sensors == 0) { - auto_off_manager.allow(sensor_manager_auto_off_token); - stop_sensor_manager(); - } + if (active_sensors == 0) stop_sensor_manager(); } void config_sensor(struct sensor_config * config) { @@ -274,4 +271,4 @@ void config_sensor(struct sensor_config * config) { //k_work_queue_drain(&sensor_work_q, true); k_work_submit(&config_work); //k_work_queue_unplug(&sensor_work_q); -} +} \ No newline at end of file diff --git a/src/audio/streamctrl.c b/src/audio/streamctrl.c index 47effba7..340b4cc5 100644 --- a/src/audio/streamctrl.c +++ b/src/audio/streamctrl.c @@ -8,7 +8,6 @@ #include #include -#include #include "unicast_server.h" #include "zbus_common.h" @@ -663,7 +662,7 @@ int streamctrl_start() //streamctrl_start ERR_CHK(ret); ret = auto_off_init(); - ERR_CHK_MSG(ret, "Failed to initialize auto-off manager"); + ERR_CHK(ret); ret = audio_system_init(); ERR_CHK(ret); diff --git a/src/bluetooth/gatt_services/power_saving_service.h b/src/bluetooth/gatt_services/power_saving_service.h index e4ac1e74..a03bedbb 100644 --- a/src/bluetooth/gatt_services/power_saving_service.h +++ b/src/bluetooth/gatt_services/power_saving_service.h @@ -1,8 +1,6 @@ #ifndef _POWER_SAVING_SERVICE_H_ #define _POWER_SAVING_SERVICE_H_ -#include -#include #include /** @brief Power saving service UUID. */