From e3ed07a367e2f90871b99c8b955df2e07b2d6284 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Tue, 27 Aug 2024 22:18:38 -0700 Subject: [PATCH] Add behaviors to show indicators on demand --- CMakeLists.txt | 1 + Kconfig | 10 ++- README.md | 25 +++++++ dts/behaviors/rgbled_widget.dtsi | 19 +++++ dts/bindings/behaviors/zmk,rgbled-widget.yaml | 13 ++++ include/zmk_rgbled_widget/widget.h | 2 +- src/behaviors/behavior_rgbled_widget.c | 69 +++++++++++++++++++ src/widget.c | 30 ++++---- 8 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 dts/behaviors/rgbled_widget.dtsi create mode 100644 dts/bindings/behaviors/zmk,rgbled-widget.yaml create mode 100644 src/behaviors/behavior_rgbled_widget.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ede1115..28e66f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,4 @@ target_sources_ifdef(CONFIG_RGBLED_WIDGET app PRIVATE src/widget.c) +target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_RGBLED_WIDGET app PRIVATE src/behaviors/behavior_rgbled_widget.c) zephyr_include_directories(include) diff --git a/Kconfig b/Kconfig index ee98a15..f8b38d8 100644 --- a/Kconfig +++ b/Kconfig @@ -33,16 +33,22 @@ config RGBLED_WIDGET_BATTERY_LEVEL_CRITICAL config RGBLED_WIDGET_SHOW_LAYER_CHANGE bool "Indicate highest active layer on each layer change with a sequence of blinks" -if RGBLED_WIDGET_SHOW_LAYER_CHANGE - config RGBLED_WIDGET_LAYER_BLINK_MS int "Blink and wait duration for layer indicator" default 100 +if RGBLED_WIDGET_SHOW_LAYER_CHANGE + config RGBLED_WIDGET_LAYER_DEBOUNCE_MS int "Wait duration after a layer change before showing the highest active layer" default 100 endif # RGBLED_WIDGET_SHOW_LAYER_CHANGE +DT_COMPAT_ZMK_BEHAVIOR_RGBLED_WIDGET := zmk,behavior-rgbled-widget + +config ZMK_BEHAVIOR_RGBLED_WIDGET + bool + default $(dt_compat_enabled,$(DT_COMPAT_ZMK_BEHAVIOR_RGBLED_WIDGET)) + endif diff --git a/README.md b/README.md index 816fd1c..5731440 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,31 @@ Currently the widget does the following: In addition, you can enable `CONFIG_RGBLED_WIDGET_SHOW_LAYER_CHANGE` to show the highest active layer on every layer activation using a sequence of N cyan color blinks, where N is the zero-based index of the layer. +### Showing status on demand + +This module also defines keymap [behaviors](https://zmk.dev/docs/keymaps/behaviors) to let you show battery or connection status on demand: + +```dts +#include // needed to use the behaviors + +/ { + keymap { + ... + some_layer { + bindings = < + ... + &ind_bat // indicate battery level + &ind_con // indicate connectivity status + ... + >; + }; + }; +}; +``` + +When you invoke the behavior by pressing the corresponding key (or combo), it will trigger the LED color display. +This will happen on all keyboard parts for split keyboards, so make sure to flash firmware to all parts after enabling. + ## Configuration Blink durations can also be adjusted, see the [Kconfig file](Kconfig) for available config properties. diff --git a/dts/behaviors/rgbled_widget.dtsi b/dts/behaviors/rgbled_widget.dtsi new file mode 100644 index 0000000..65ca32d --- /dev/null +++ b/dts/behaviors/rgbled_widget.dtsi @@ -0,0 +1,19 @@ +/ { + behaviors { + /omit-if-no-ref/ ind_bat: ind_bat { + compatible = "zmk,behavior-rgbled-widget"; + #binding-cells = <0>; + indicate-battery; + }; + /omit-if-no-ref/ ind_con: ind_con { + compatible = "zmk,behavior-rgbled-widget"; + #binding-cells = <0>; + indicate-connectivity; + }; + /omit-if-no-ref/ ind_lyr: ind_lyr { + compatible = "zmk,behavior-rgbled-widget"; + #binding-cells = <0>; + indicate-layer; + }; + }; +}; diff --git a/dts/bindings/behaviors/zmk,rgbled-widget.yaml b/dts/bindings/behaviors/zmk,rgbled-widget.yaml new file mode 100644 index 0000000..c662659 --- /dev/null +++ b/dts/bindings/behaviors/zmk,rgbled-widget.yaml @@ -0,0 +1,13 @@ +description: RGB LED widget indicator behavior + +compatible: "zmk,behavior-rgbled-widget" + +include: zero_param.yaml + +properties: + indicate-battery: + type: boolean + indicate-connectivity: + type: boolean + indicate-layer: + type: boolean diff --git a/include/zmk_rgbled_widget/widget.h b/include/zmk_rgbled_widget/widget.h index 0421734..22d0739 100644 --- a/include/zmk_rgbled_widget/widget.h +++ b/include/zmk_rgbled_widget/widget.h @@ -10,6 +10,6 @@ void indicate_battery(void); void indicate_connectivity(void); #endif -#if SHOW_LAYER_CHANGE +#if !IS_ENABLED(CONFIG_ZMK_SPLIT) || IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) void indicate_layer(void); #endif diff --git a/src/behaviors/behavior_rgbled_widget.c b/src/behaviors/behavior_rgbled_widget.c new file mode 100644 index 0000000..6a4af07 --- /dev/null +++ b/src/behaviors/behavior_rgbled_widget.c @@ -0,0 +1,69 @@ +#define DT_DRV_COMPAT zmk_behavior_rgbled_widget + +#include +#include +#include + +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct behavior_rgb_wdg_config { + bool indicate_battery; + bool indicate_connectivity; + bool indicate_layer; +}; + +static int behavior_rgb_wdg_init(const struct device *dev) { return 0; } + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev); + const struct behavior_rgb_wdg_config *cfg = dev->config; + +#if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING) + if (cfg->indicate_battery) { + indicate_battery(); + } +#endif +#if IS_ENABLED(CONFIG_ZMK_BLE) + if (cfg->indicate_connectivity) { + indicate_connectivity(); + } +#endif +#if !IS_ENABLED(CONFIG_ZMK_SPLIT) || IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) + if (cfg->indicate_layer) { + indicate_layer(); + } +#endif + + return ZMK_BEHAVIOR_OPAQUE; +} + +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_rgb_wdg_driver_api = { + .binding_pressed = on_keymap_binding_pressed, + .binding_released = on_keymap_binding_released, + .locality = BEHAVIOR_LOCALITY_GLOBAL, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; + +#define RGBIND_INST(n) \ + static struct behavior_rgb_wdg_config behavior_rgb_wdg_config_##n = { \ + .indicate_battery = DT_INST_PROP(n, indicate_battery), \ + .indicate_connectivity = DT_INST_PROP(n, indicate_connectivity), \ + .indicate_layer = DT_INST_PROP(n, indicate_layer), \ + }; \ + BEHAVIOR_DT_INST_DEFINE(n, behavior_rgb_wdg_init, NULL, NULL, &behavior_rgb_wdg_config_##n, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &behavior_rgb_wdg_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(RGBIND_INST) diff --git a/src/widget.c b/src/widget.c index c62247c..5aaf851 100644 --- a/src/widget.c +++ b/src/widget.c @@ -159,21 +159,7 @@ ZMK_LISTENER(led_battery_listener, led_battery_listener_cb); ZMK_SUBSCRIPTION(led_battery_listener, zmk_battery_state_changed); #endif // IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING) -#if SHOW_LAYER_CHANGE -static struct k_work_delayable layer_indicate_work; - -static int led_layer_listener_cb(const zmk_event_t *eh) { - // ignore if not initialized yet or layer off events - if (initialized && as_zmk_layer_state_changed(eh)->state) { - k_work_reschedule(&layer_indicate_work, K_MSEC(CONFIG_RGBLED_WIDGET_LAYER_DEBOUNCE_MS)); - } - return 0; -} - -static void indicate_layer_cb(struct k_work *work) { - indicate_layer(); -} - +#if !IS_ENABLED(CONFIG_ZMK_SPLIT) || IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) void indicate_layer(void) { uint8_t index = zmk_keymap_highest_layer_active(); static const struct blink_item blink = {.duration_ms = CONFIG_RGBLED_WIDGET_LAYER_BLINK_MS, @@ -189,6 +175,20 @@ void indicate_layer(void) { } } } +#endif + +#if SHOW_LAYER_CHANGE +static struct k_work_delayable layer_indicate_work; + +static int led_layer_listener_cb(const zmk_event_t *eh) { + // ignore if not initialized yet or layer off events + if (initialized && as_zmk_layer_state_changed(eh)->state) { + k_work_reschedule(&layer_indicate_work, K_MSEC(CONFIG_RGBLED_WIDGET_LAYER_DEBOUNCE_MS)); + } + return 0; +} + +static void indicate_layer_cb(struct k_work *work) { indicate_layer(); } ZMK_LISTENER(led_layer_listener, led_layer_listener_cb); ZMK_SUBSCRIPTION(led_layer_listener, zmk_layer_state_changed);