From 85b2d30bd521424686ba7f1c5ac4fe492f5f2ca5 Mon Sep 17 00:00:00 2001 From: Alessandro Bortolin Date: Sat, 9 Oct 2021 18:24:37 +0200 Subject: [PATCH] feat(lighting): add backlight behavior --- app/CMakeLists.txt | 2 + app/Kconfig | 37 ++- app/dts/behaviors.dtsi | 1 + app/dts/behaviors/backlight.dtsi | 15 ++ .../behaviors/zmk,behavior-backlight.yaml | 8 + app/include/dt-bindings/zmk/backlight.h | 11 + app/include/zmk/backlight.h | 19 ++ app/src/backlight.c | 224 ++++++++++++++++++ app/src/behaviors/behavior_backlight.c | 56 +++++ 9 files changed, 371 insertions(+), 2 deletions(-) create mode 100644 app/dts/behaviors/backlight.dtsi create mode 100644 app/dts/bindings/behaviors/zmk,behavior-backlight.yaml create mode 100644 app/include/dt-bindings/zmk/backlight.h create mode 100644 app/include/zmk/backlight.h create mode 100644 app/src/backlight.c create mode 100644 app/src/behaviors/behavior_backlight.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 7681efab..25f6c6cd 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -64,6 +64,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) target_sources(app PRIVATE src/keymap.c) endif() target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c) +target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_backlight.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/behaviors/behavior_bt.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/battery.c) @@ -77,6 +78,7 @@ endif() target_sources_ifdef(CONFIG_USB app PRIVATE src/usb.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/hog.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) +target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) target_sources(app PRIVATE src/endpoints.c) target_sources(app PRIVATE src/hid_listener.c) target_sources(app PRIVATE src/main.c) diff --git a/app/Kconfig b/app/Kconfig index 76035147..49eec835 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -257,7 +257,7 @@ menu "Display/LED Options" rsource "src/display/Kconfig" -config ZMK_RGB_UNDERGLOW +menuconfig ZMK_RGB_UNDERGLOW bool "RGB Adressable LED Underglow" select LED_STRIP @@ -328,6 +328,39 @@ config ZMK_RGB_UNDERGLOW_ON_START #ZMK_RGB_UNDERGLOW endif +menuconfig ZMK_BACKLIGHT + bool "LED backlight" + select PWM + select LED + select ZMK_LED_PWM + +if ZMK_BACKLIGHT + +config ZMK_BACKLIGHT_BRT_STEP + int "Brightness step in percent" + range 1 100 + default 20 + +config ZMK_BACKLIGHT_BRT_START + int "Default brightness in percent" + range 1 100 + default 40 + +config ZMK_BACKLIGHT_ON_START + bool "Default backlight state" + default y + +config ZMK_BACKLIGHT_AUTO_OFF_IDLE + bool "Turn off backlight when keyboard goes into idle state" + default y + +config ZMK_BACKLIGHT_AUTO_OFF_USB + bool "Turn off backlight when USB is disconnected" + default n + +#ZMK_BACKLIGHT +endif + #Display/LED Options endmenu @@ -378,7 +411,7 @@ config ZMK_COMBO_MAX_KEYS_PER_COMBO int "Maximum number of keys per combo" default 4 -#Display/LED Options +#Combo options endmenu menu "Advanced" diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index 06489616..3e797cc9 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -16,3 +16,4 @@ #include #include #include +#include diff --git a/app/dts/behaviors/backlight.dtsi b/app/dts/behaviors/backlight.dtsi new file mode 100644 index 00000000..b05d97ae --- /dev/null +++ b/app/dts/behaviors/backlight.dtsi @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + + / { + behaviors { + /omit-if-no-ref/ bl: behavior_backlight { + compatible = "zmk,behavior-backlight"; + label = "BACKLIGHT"; + #binding-cells = <1>; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-backlight.yaml b/app/dts/bindings/behaviors/zmk,behavior-backlight.yaml new file mode 100644 index 00000000..e035e15e --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-backlight.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Backlight behavior + +compatible: "zmk,behavior-backlight" + +include: one_param.yaml diff --git a/app/include/dt-bindings/zmk/backlight.h b/app/include/dt-bindings/zmk/backlight.h new file mode 100644 index 00000000..c33e4344 --- /dev/null +++ b/app/include/dt-bindings/zmk/backlight.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define BL_TOG 0 +#define BL_ON 1 +#define BL_OFF 2 +#define BL_INC 3 +#define BL_DEC 4 diff --git a/app/include/zmk/backlight.h b/app/include/zmk/backlight.h new file mode 100644 index 00000000..817efe7a --- /dev/null +++ b/app/include/zmk/backlight.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +int zmk_backlight_set_on(bool on); +bool zmk_backlight_is_on(); + +int zmk_backlight_set_brt(int brt); +int zmk_backlight_get_brt(); + +int zmk_backlight_toggle(); +int zmk_backlight_on(); +int zmk_backlight_off(); +int zmk_backlight_inc(); +int zmk_backlight_dec(); diff --git a/app/src/backlight.c b/app/src/backlight.c new file mode 100644 index 00000000..517e6133 --- /dev/null +++ b/app/src/backlight.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +BUILD_ASSERT(DT_HAS_CHOSEN(zmk_backlight), + "CONFIG_ZMK_BACKLIGHT is enabled but no zmk,backlight chosen node found"); + +static const struct device *const backlight_dev = DEVICE_DT_GET(DT_CHOSEN(zmk_backlight)); + +#define CHILD_COUNT(...) +1 +#define DT_NUM_CHILD(node_id) (DT_FOREACH_CHILD(node_id, CHILD_COUNT)) + +#define BACKLIGHT_NUM_LEDS (DT_NUM_CHILD(DT_CHOSEN(zmk_backlight))) + +struct backlight_state { + uint8_t brightness; + bool on; +}; + +static struct backlight_state state = {.brightness = CONFIG_ZMK_BACKLIGHT_BRT_START, + .on = IS_ENABLED(CONFIG_ZMK_BACKLIGHT_ON_START)}; + +static int zmk_backlight_update() { + uint8_t brt = state.on ? state.brightness : 0; + for (int i = 0; i < BACKLIGHT_NUM_LEDS; i++) { + int rc = led_set_brightness(backlight_dev, i, brt); + if (rc != 0) { + return rc; + } + } + return 0; +} + +#if IS_ENABLED(CONFIG_SETTINGS) +static int backlight_settings_set(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(state)) { + return -EINVAL; + } + + int rc = read_cb(cb_arg, &state, sizeof(state)); + if (rc < 0) { + return rc; + } + + return zmk_backlight_update(); + } + + return -ENOENT; +} + +static struct settings_handler backlight_conf = {.name = "backlight", + .h_set = backlight_settings_set}; + +static void zmk_backlight_save_state_work() { + settings_save_one("backlight/state", &state, sizeof(state)); +} + +static struct k_delayed_work backlight_save_work; +#endif // IS_ENABLED(CONFIG_SETTINGS) + +static int zmk_backlight_save_state() { +#if IS_ENABLED(CONFIG_SETTINGS) + k_delayed_work_cancel(&backlight_save_work); + return k_delayed_work_submit(&backlight_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); +#else + return 0; +#endif +} + +static int zmk_backlight_init(const struct device *_arg) { + if (!device_is_ready(backlight_dev)) { + LOG_ERR("Backlight device \"%s\" is not ready", backlight_dev->name); + return -ENODEV; + } + +#if IS_ENABLED(CONFIG_SETTINGS) + settings_subsys_init(); + + int err = settings_register(&backlight_conf); + if (err) { + LOG_ERR("Failed to register the backlight settings handler (err %d)", err); + return err; + } + + k_delayed_work_init(&backlight_save_work, zmk_backlight_save_state_work); + + settings_load_subtree("backlight"); +#endif + + return zmk_backlight_update(); +} + +int zmk_backlight_set_on(bool on) { + if (!state.on && state.brightness == 0) { + state.brightness = CONFIG_ZMK_BACKLIGHT_BRT_STEP; + } + state.on = on; + + int rc = zmk_backlight_update(); + if (rc != 0) { + return rc; + } + + return zmk_backlight_save_state(); +} + +bool zmk_backlight_is_on() { return state.on; } + +int zmk_backlight_set_brt(int brt) { + state.on = (brt > 0); + state.brightness = CLAMP(brt, 0, 100); + + int rc = zmk_backlight_update(); + if (rc != 0) { + return rc; + } + + return zmk_backlight_save_state(); +} + +int zmk_backlight_get_brt() { return state.on ? state.brightness : 0; } + +int zmk_backlight_toggle() { return zmk_backlight_set_on(!state.on); } + +int zmk_backlight_on() { return zmk_backlight_set_on(true); } + +int zmk_backlight_off() { return zmk_backlight_set_on(false); } + +int zmk_backlight_inc() { + if (!state.on) { + return zmk_backlight_set_brt(MAX(state.brightness, CONFIG_ZMK_BACKLIGHT_BRT_STEP)); + } + return zmk_backlight_set_brt(state.brightness + CONFIG_ZMK_BACKLIGHT_BRT_STEP); +} + +int zmk_backlight_dec() { + return zmk_backlight_set_brt(state.brightness - CONFIG_ZMK_BACKLIGHT_BRT_STEP); +} + +#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE) +static bool auto_off_idle_prev_state = false; +#endif + +#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB) +static bool auto_off_usb_prev_state = false; +#endif + +#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE) || IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB) +static int backlight_event_listener(const zmk_event_t *eh) { + +#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE) + if (as_zmk_activity_state_changed(eh)) { + bool new_state = (zmk_activity_get_state() == ZMK_ACTIVITY_ACTIVE); + if (state.on == new_state) { + return 0; + } + if (new_state) { + state.on = auto_off_idle_prev_state; + auto_off_idle_prev_state = false; + } else { + state.on = false; + auto_off_idle_prev_state = true; + } + return zmk_backlight_update(); + } +#endif + +#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB) + if (as_zmk_usb_conn_state_changed(eh)) { + bool new_state = zmk_usb_is_powered(); + if (state.on == new_state) { + return 0; + } + if (new_state) { + state.on = auto_off_usb_prev_state; + auto_off_usb_prev_state = false; + } else { + state.on = false; + auto_off_usb_prev_state = true; + } + return zmk_backlight_update(); + } +#endif + + return -ENOTSUP; +} + +ZMK_LISTENER(backlight, backlight_event_listener); +#endif // IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE) || + // IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB) + +#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE) +ZMK_SUBSCRIPTION(backlight, zmk_activity_state_changed); +#endif + +#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB) +ZMK_SUBSCRIPTION(backlight, zmk_usb_conn_state_changed); +#endif + +SYS_INIT(zmk_backlight_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/behaviors/behavior_backlight.c b/app/src/behaviors/behavior_backlight.c new file mode 100644 index 00000000..8d921f45 --- /dev/null +++ b/app/src/behaviors/behavior_backlight.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_backlight + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int behavior_backlight_init(const struct device *dev) { return 0; } + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + switch (binding->param1) { + case BL_TOG: + return zmk_backlight_toggle(); + case BL_ON: + return zmk_backlight_on(); + case BL_OFF: + return zmk_backlight_off(); + case BL_INC: + return zmk_backlight_inc(); + case BL_DEC: + return zmk_backlight_dec(); + default: + LOG_ERR("Unknown backlight command: %d", binding->param1); + } + + return -ENOTSUP; +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + return ZMK_BEHAVIOR_OPAQUE; +} + +static const struct behavior_driver_api behavior_backlight_driver_api = { + .binding_pressed = on_keymap_binding_pressed, + .binding_released = on_keymap_binding_released, +}; + +DEVICE_DT_INST_DEFINE(0, behavior_backlight_init, device_pm_control_nop, NULL, NULL, APPLICATION, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_backlight_driver_api); + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */