Skip to content

Commit

Permalink
Add behaviors to show indicators on demand
Browse files Browse the repository at this point in the history
  • Loading branch information
caksoylar committed Aug 28, 2024
1 parent f519fe8 commit e3ed07a
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 18 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
10 changes: 8 additions & 2 deletions Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <behaviors/rgbled_widget.dtsi> // 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.
Expand Down
19 changes: 19 additions & 0 deletions dts/behaviors/rgbled_widget.dtsi
Original file line number Diff line number Diff line change
@@ -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;
};
};
};
13 changes: 13 additions & 0 deletions dts/bindings/behaviors/zmk,rgbled-widget.yaml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion include/zmk_rgbled_widget/widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
69 changes: 69 additions & 0 deletions src/behaviors/behavior_rgbled_widget.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#define DT_DRV_COMPAT zmk_behavior_rgbled_widget

#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>

#include <zmk/behavior.h>

#include <zmk_rgbled_widget/widget.h>

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)
30 changes: 15 additions & 15 deletions src/widget.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down

0 comments on commit e3ed07a

Please sign in to comment.