From 6b364b171f039152b2ebc0d544b2a6e13649151c Mon Sep 17 00:00:00 2001 From: Andrew Kannan Date: Thu, 13 Mar 2025 22:54:18 -0400 Subject: [PATCH] feat(power): Allow runtime toggling of deep sleep functionality --- app/CMakeLists.txt | 1 + app/dts/behaviors.dtsi | 3 +- app/dts/behaviors/set_sleep.dtsi | 20 +++++ .../behaviors/zmk,behavior-set-sleep.yaml | 8 ++ app/include/dt-bindings/zmk/set_sleep.h | 9 ++ app/include/zmk/activity.h | 8 +- app/src/activity.c | 68 ++++++++++++++- app/src/behaviors/behavior_set_sleep.c | 84 +++++++++++++++++++ 8 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 app/dts/behaviors/set_sleep.dtsi create mode 100644 app/dts/bindings/behaviors/zmk,behavior-set-sleep.yaml create mode 100644 app/include/dt-bindings/zmk/set_sleep.h create mode 100644 app/src/behaviors/behavior_set_sleep.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 123935402..08d40da98 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -43,6 +43,7 @@ target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_sta target_sources(app PRIVATE src/behaviors/behavior_reset.c) target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SOFT_OFF app PRIVATE src/behaviors/behavior_soft_off.c) +target_sources_ifdef(CONFIG_ZMK_SLEEP app PRIVATE src/behaviors/behavior_set_sleep.c) add_subdirectory_ifdef(CONFIG_ZMK_POINTING src/pointing/) if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/hid.c) diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index 653b085d5..ed2506379 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 The ZMK Contributors + * Copyright (c) 2025 The ZMK Contributors * * SPDX-License-Identifier: MIT */ @@ -28,3 +28,4 @@ #include #include #include +#include diff --git a/app/dts/behaviors/set_sleep.dtsi b/app/dts/behaviors/set_sleep.dtsi new file mode 100644 index 000000000..e01b7b0f0 --- /dev/null +++ b/app/dts/behaviors/set_sleep.dtsi @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + behaviors { +#if ZMK_BEHAVIOR_OMIT(SLEEP) + /omit-if-no-ref/ +#endif + sleep: sleep { + compatible = "zmk,behavior-set-sleep"; + #binding-cells = <1>; + display-name = "Sleep"; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-set-sleep.yaml b/app/dts/bindings/behaviors/zmk,behavior-set-sleep.yaml new file mode 100644 index 000000000..17548d086 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-set-sleep.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Set sleep behavior + +compatible: "zmk,behavior-set-sleep" + +include: one_param.yaml diff --git a/app/include/dt-bindings/zmk/set_sleep.h b/app/include/dt-bindings/zmk/set_sleep.h new file mode 100644 index 000000000..cc5308a8b --- /dev/null +++ b/app/include/dt-bindings/zmk/set_sleep.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2025 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define SLEEP_ON 0 +#define SLEEP_OFF 1 +#define SLEEP_TOGG 2 \ No newline at end of file diff --git a/app/include/zmk/activity.h b/app/include/zmk/activity.h index 2aad024a8..5cc72566e 100644 --- a/app/include/zmk/activity.h +++ b/app/include/zmk/activity.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 The ZMK Contributors + * Copyright (c) 2025 The ZMK Contributors * * SPDX-License-Identifier: MIT */ @@ -9,3 +9,9 @@ enum zmk_activity_state { ZMK_ACTIVITY_ACTIVE, ZMK_ACTIVITY_IDLE, ZMK_ACTIVITY_SLEEP }; enum zmk_activity_state zmk_activity_get_state(void); + +#if IS_ENABLED(CONFIG_ZMK_SLEEP) +void zmk_enable_sleep(void); +void zmk_disable_sleep(void); +void zmk_toggle_sleep(void); +#endif \ No newline at end of file diff --git a/app/src/activity.c b/app/src/activity.c index b109d46d8..dcb7f26a3 100644 --- a/app/src/activity.c +++ b/app/src/activity.c @@ -10,6 +10,7 @@ #include #include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -46,7 +47,67 @@ static uint32_t activity_last_uptime; #if IS_ENABLED(CONFIG_ZMK_SLEEP) #define MAX_SLEEP_MS CONFIG_ZMK_IDLE_SLEEP_TIMEOUT + +struct runtime_sleep_state { + bool enabled; +}; + +static struct runtime_sleep_state sleep_state = {.enabled = true}; + +#endif // IS_ENABLED(CONFIG_ZMK_SLEEP) + +#if IS_ENABLED(CONFIG_SETTINGS) && IS_ENABLED(CONFIG_ZMK_SLEEP) +static int sleep_settings_load_cb(const char *name, size_t len, settings_read_cb read_cb, + void *cb_arg) { + const char *next; + if (settings_name_steq(name, "state", &next) && !next) { + if (len != sizeof(sleep_state)) { + return -EINVAL; + } + + int rc = read_cb(cb_arg, &sleep_state, sizeof(sleep_state)); + return MIN(rc, 0); + } + return -ENOENT; +} + +SETTINGS_STATIC_HANDLER_DEFINE(sleep, "sleep", NULL, sleep_settings_load_cb, NULL, NULL); + +static void sleep_save_work_handler(struct k_work *work) { + settings_save_one("sleep/state", &sleep_state, sizeof(sleep_state)); +} + +static struct k_work_delayable sleep_save_work; + +#endif // IS_ENABLED(CONFIG_SETTINGS) && IS_ENABLED(CONFIG_ZMK_SLEEP) + +#if IS_ENABLED(CONFIG_ZMK_SLEEP) + +void zmk_enable_sleep(void) { + sleep_state.enabled = true; + LOG_DBG("Enabling sleep\n"); +#if IS_ENABLED(CONFIG_SETTINGS) + k_work_reschedule(&sleep_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); #endif +} + +void zmk_disable_sleep(void) { + sleep_state.enabled = false; + LOG_DBG("Disabling sleep\n"); +#if IS_ENABLED(CONFIG_SETTINGS) + k_work_reschedule(&sleep_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); +#endif +} + +void zmk_toggle_sleep(void) { + LOG_DBG("Toggle sleep\n"); + sleep_state.enabled = !sleep_state.enabled; +#if IS_ENABLED(CONFIG_SETTINGS) + k_work_reschedule(&sleep_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); +#endif +} + +#endif // IS_ENABLED(CONFIG_ZMK_SLEEP) int raise_event(void) { return raise_zmk_activity_state_changed( @@ -75,7 +136,7 @@ void activity_work_handler(struct k_work *work) { int32_t current = k_uptime_get(); int32_t inactive_time = current - activity_last_uptime; #if IS_ENABLED(CONFIG_ZMK_SLEEP) - if (inactive_time > MAX_SLEEP_MS && !is_usb_power_present()) { + if (inactive_time > MAX_SLEEP_MS && !is_usb_power_present() && sleep_state.enabled) { // Put devices in suspend power mode before sleeping set_state(ZMK_ACTIVITY_SLEEP); @@ -103,6 +164,11 @@ static int activity_init(void) { activity_last_uptime = k_uptime_get(); k_timer_start(&activity_timer, K_SECONDS(1), K_SECONDS(1)); + +#if IS_ENABLED(CONFIG_SETTINGS) && IS_ENABLED(CONFIG_ZMK_SLEEP) + k_work_init_delayable(&sleep_save_work, sleep_save_work_handler); +#endif + return 0; } diff --git a/app/src/behaviors/behavior_set_sleep.c b/app/src/behaviors/behavior_set_sleep.c new file mode 100644 index 000000000..79a4756b8 --- /dev/null +++ b/app/src/behaviors/behavior_set_sleep.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_set_sleep + +#include +#include +#include + +#include + +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata std_values[] = { + { + .value = SLEEP_TOGG, + .display_name = "Toggle Sleep State", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + }, + { + .value = SLEEP_ON, + .display_name = "Enable Sleep", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + }, + { + .value = SLEEP_OFF, + .display_name = "Disable Sleep", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + }, +}; + +static const struct behavior_parameter_metadata_set std_set = { + .param1_values = std_values, + .param1_values_len = ARRAY_SIZE(std_values), +}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = 1, + .sets = &std_set, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + switch (binding->param1) { + case SLEEP_TOGG: + zmk_toggle_sleep(); + return 0; + case SLEEP_ON: + zmk_enable_sleep(); + return 0; + case SLEEP_OFF: + zmk_disable_sleep(); + return 0; + default: + LOG_ERR("Unknown output command: %d", binding->param1); + } + + return -ENOTSUP; +} + +static const struct behavior_driver_api behavior_set_sleep_driver_api = { + .binding_pressed = on_keymap_binding_pressed, + .locality = BEHAVIOR_LOCALITY_GLOBAL, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; + +BEHAVIOR_DT_INST_DEFINE(0, NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &behavior_set_sleep_driver_api); + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */