From 3db163aa2cf7db2bd710ba93b57f3eb804b086c7 Mon Sep 17 00:00:00 2001 From: Nick Conway Date: Mon, 23 May 2022 16:33:08 -0400 Subject: [PATCH] feat(behaviors): Add reusable sensor behaviors. * Add new sensor behaviors that either take full bindings add definition, or accept parameters when bound in the keymap. * Remove existing hard-coded key press sensor behavior and instead leverage new generic sensor behaviors to achieve the same functionality. Co-authored-by: nick@conway.dev --- app/CMakeLists.txt | 4 +- app/Kconfig | 6 +- app/Kconfig.behaviors | 24 ++++++ .../behaviors/sensor_rotate_key_press.dtsi | 3 +- .../zmk,behavior-sensor-rotate-key-press.yaml | 19 ----- .../zmk,behavior-sensor-rotate-var.yaml | 25 ++++++ .../behaviors/zmk,behavior-sensor-rotate.yaml | 21 +++++ app/include/drivers/behavior.h | 10 +-- app/src/behaviors/behavior_sensor_rotate.c | 40 ++++++++++ .../behaviors/behavior_sensor_rotate_common.c | 52 +++++++++++++ .../behaviors/behavior_sensor_rotate_common.h | 13 ++++ .../behavior_sensor_rotate_key_press.c | 69 ----------------- .../behaviors/behavior_sensor_rotate_var.c | 31 ++++++++ app/src/keymap.c | 5 +- docs/docs/behaviors/sensor-rotate.md | 77 +++++++++++++++++++ docs/docs/features/encoders.md | 8 +- docs/sidebars.js | 1 + 17 files changed, 301 insertions(+), 107 deletions(-) create mode 100644 app/Kconfig.behaviors delete mode 100644 app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-key-press.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-var.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-sensor-rotate.yaml create mode 100644 app/src/behaviors/behavior_sensor_rotate.c create mode 100644 app/src/behaviors/behavior_sensor_rotate_common.c create mode 100644 app/src/behaviors/behavior_sensor_rotate_common.h delete mode 100644 app/src/behaviors/behavior_sensor_rotate_key_press.c create mode 100644 app/src/behaviors/behavior_sensor_rotate_var.c create mode 100644 docs/docs/behaviors/sensor-rotate.md diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3da50b57..a647e883 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -51,7 +51,9 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_to_layer.c) target_sources(app PRIVATE src/behaviors/behavior_transparent.c) target_sources(app PRIVATE src/behaviors/behavior_none.c) - target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE app PRIVATE src/behaviors/behavior_sensor_rotate.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_VAR app PRIVATE src/behaviors/behavior_sensor_rotate_var.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON app PRIVATE src/behaviors/behavior_sensor_rotate_common.c) target_sources(app PRIVATE src/combo.c) target_sources(app PRIVATE src/behaviors/behavior_tap_dance.c) target_sources(app PRIVATE src/behavior_queue.c) diff --git a/app/Kconfig b/app/Kconfig index 431a1bb1..ccc5f42d 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -350,11 +350,7 @@ config ZMK_BEHAVIORS_QUEUE_SIZE int "Maximum number of behaviors to allow queueing from a macro or other complex behavior" default 64 -DT_COMPAT_ZMK_BEHAVIOR_KEY_TOGGLE := zmk,behavior-key-toggle - -config ZMK_BEHAVIOR_KEY_TOGGLE - bool - default $(dt_compat_enabled,$(DT_COMPAT_ZMK_BEHAVIOR_KEY_TOGGLE)) +rsource "Kconfig.behaviors" config ZMK_MACRO_DEFAULT_WAIT_MS int "Default time to wait (in milliseconds) before triggering the next behavior in macros" diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors new file mode 100644 index 00000000..17850eae --- /dev/null +++ b/app/Kconfig.behaviors @@ -0,0 +1,24 @@ +# Copyright (c) 2023 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config ZMK_BEHAVIOR_KEY_TOGGLE + bool + default y + depends on DT_HAS_ZMK_BEHAVIOR_KEY_TOGGLE_ENABLED + + +config ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON + bool + default n + +config ZMK_BEHAVIOR_SENSOR_ROTATE + bool + default y + depends on DT_HAS_ZMK_BEHAVIOR_SENSOR_ROTATE_ENABLED + select ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON + +config ZMK_BEHAVIOR_SENSOR_ROTATE_VAR + bool + default y + depends on DT_HAS_ZMK_BEHAVIOR_SENSOR_ROTATE_VAR_ENABLED + select ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON \ No newline at end of file diff --git a/app/dts/behaviors/sensor_rotate_key_press.dtsi b/app/dts/behaviors/sensor_rotate_key_press.dtsi index d3f084b0..ed1b4cd0 100644 --- a/app/dts/behaviors/sensor_rotate_key_press.dtsi +++ b/app/dts/behaviors/sensor_rotate_key_press.dtsi @@ -8,9 +8,10 @@ behaviors { /* DEPRECATED: `inc_dec_cp` will be removed in the future */ /omit-if-no-ref/ inc_dec_cp: inc_dec_kp: behavior_sensor_rotate_key_press { - compatible = "zmk,behavior-sensor-rotate-key-press"; + compatible = "zmk,behavior-sensor-rotate-var"; label = "ENC_KEY_PRESS"; #sensor-binding-cells = <2>; + bindings = <&kp>, <&kp>; }; }; }; diff --git a/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-key-press.yaml b/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-key-press.yaml deleted file mode 100644 index 1fc60fcf..00000000 --- a/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-key-press.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2020 The ZMK Contributors -# SPDX-License-Identifier: MIT - -description: Sensor rotate key press/release behavior - -compatible: "zmk,behavior-sensor-rotate-key-press" - -properties: - label: - type: string - required: true - "#sensor-binding-cells": - type: int - required: true - const: 2 - -sensor-binding-cells: - - param1 - - param2 diff --git a/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-var.yaml b/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-var.yaml new file mode 100644 index 00000000..0da3b4db --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate-var.yaml @@ -0,0 +1,25 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Sensor rotate behavior + +compatible: "zmk,behavior-sensor-rotate-var" + +properties: + label: + type: string + required: true + "#sensor-binding-cells": + type: int + required: true + const: 2 + bindings: + type: phandles + required: true + tap-ms: + type: int + default: 5 + +sensor-binding-cells: + - param1 + - param2 diff --git a/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate.yaml b/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate.yaml new file mode 100644 index 00000000..d20777b8 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-sensor-rotate.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Sensor rotate behavior + +compatible: "zmk,behavior-sensor-rotate" + +properties: + label: + type: string + required: true + "#sensor-binding-cells": + type: int + required: true + const: 0 + bindings: + type: phandle-array + required: true + tap-ms: + type: int + default: 5 diff --git a/app/include/drivers/behavior.h b/app/include/drivers/behavior.h index df18385f..380fc76f 100644 --- a/app/include/drivers/behavior.h +++ b/app/include/drivers/behavior.h @@ -26,8 +26,7 @@ typedef int (*behavior_keymap_binding_callback_t)(struct zmk_behavior_binding *b struct zmk_behavior_binding_event event); typedef int (*behavior_sensor_keymap_binding_callback_t)(struct zmk_behavior_binding *binding, const struct device *sensor, - uint32_t virtual_key_position, - int64_t timestamp); + struct zmk_behavior_binding_event event); enum behavior_locality { BEHAVIOR_LOCALITY_CENTRAL, @@ -161,13 +160,12 @@ static inline int z_impl_behavior_keymap_binding_released(struct zmk_behavior_bi */ __syscall int behavior_sensor_keymap_binding_triggered(struct zmk_behavior_binding *binding, const struct device *sensor, - uint32_t virtual_key_position, - int64_t timestamp); + struct zmk_behavior_binding_event event); static inline int z_impl_behavior_sensor_keymap_binding_triggered(struct zmk_behavior_binding *binding, const struct device *sensor, - uint32_t virtual_key_position, int64_t timestamp) { + struct zmk_behavior_binding_event event) { const struct device *dev = device_get_binding(binding->behavior_dev); if (dev == NULL) { @@ -180,7 +178,7 @@ z_impl_behavior_sensor_keymap_binding_triggered(struct zmk_behavior_binding *bin return -ENOTSUP; } - return api->sensor_binding_triggered(binding, sensor, virtual_key_position, timestamp); + return api->sensor_binding_triggered(binding, sensor, event); } /** diff --git a/app/src/behaviors/behavior_sensor_rotate.c b/app/src/behaviors/behavior_sensor_rotate.c new file mode 100644 index 00000000..e12278bb --- /dev/null +++ b/app/src/behaviors/behavior_sensor_rotate.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_sensor_rotate + +#include + +#include + +#include "behavior_sensor_rotate_common.h" + +static const struct behavior_driver_api behavior_sensor_rotate_driver_api = { + .sensor_binding_triggered = zmk_behavior_sensor_rotate_common_trigger}; + +static int behavior_sensor_rotate_init(const struct device *dev) { return 0; }; + +#define _TRANSFORM_ENTRY(idx, node) \ + { \ + .behavior_dev = DT_PROP(DT_INST_PHANDLE_BY_IDX(node, bindings, idx), label), \ + .param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), \ + (DT_INST_PHA_BY_IDX(node, bindings, idx, param1))), \ + .param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), \ + (DT_INST_PHA_BY_IDX(node, bindings, idx, param2))), \ + } + +#define SENSOR_ROTATE_INST(n) \ + static struct behavior_sensor_rotate_config behavior_sensor_rotate_config_##n = { \ + .cw_binding = _TRANSFORM_ENTRY(0, n), \ + .ccw_binding = _TRANSFORM_ENTRY(1, n), \ + .tap_ms = DT_INST_PROP_OR(n, tap_ms, 5), \ + .override_params = false, \ + }; \ + DEVICE_DT_INST_DEFINE( \ + n, behavior_sensor_rotate_init, NULL, NULL, &behavior_sensor_rotate_config_##n, \ + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_sensor_rotate_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(SENSOR_ROTATE_INST) diff --git a/app/src/behaviors/behavior_sensor_rotate_common.c b/app/src/behaviors/behavior_sensor_rotate_common.c new file mode 100644 index 00000000..bd31170e --- /dev/null +++ b/app/src/behaviors/behavior_sensor_rotate_common.c @@ -0,0 +1,52 @@ + +#include +#include +#include +#include + +#include + +#include "behavior_sensor_rotate_common.h" + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +int zmk_behavior_sensor_rotate_common_trigger(struct zmk_behavior_binding *binding, + const struct device *sensor, + struct zmk_behavior_binding_event event) { + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct behavior_sensor_rotate_config *cfg = dev->config; + + struct sensor_value value; + + const int err = sensor_channel_get(sensor, SENSOR_CHAN_ROTATION, &value); + + if (err < 0) { + LOG_WRN("Failed to get sensor rotation value: %d", err); + return err; + } + + struct zmk_behavior_binding triggered_binding; + switch (value.val1) { + case 1: + triggered_binding = cfg->cw_binding; + if (cfg->override_params) { + triggered_binding.param1 = binding->param1; + } + break; + case -1: + triggered_binding = cfg->ccw_binding; + if (cfg->override_params) { + triggered_binding.param1 = binding->param2; + } + break; + default: + return -ENOTSUP; + } + + LOG_DBG("Sensor binding: %s", binding->behavior_dev); + + zmk_behavior_queue_add(event.position, triggered_binding, true, cfg->tap_ms); + zmk_behavior_queue_add(event.position, triggered_binding, false, 0); + + return ZMK_BEHAVIOR_OPAQUE; +} diff --git a/app/src/behaviors/behavior_sensor_rotate_common.h b/app/src/behaviors/behavior_sensor_rotate_common.h new file mode 100644 index 00000000..2d58218d --- /dev/null +++ b/app/src/behaviors/behavior_sensor_rotate_common.h @@ -0,0 +1,13 @@ + +#include + +struct behavior_sensor_rotate_config { + struct zmk_behavior_binding cw_binding; + struct zmk_behavior_binding ccw_binding; + int tap_ms; + bool override_params; +}; + +int zmk_behavior_sensor_rotate_common_trigger(struct zmk_behavior_binding *binding, + const struct device *sensor, + struct zmk_behavior_binding_event event); \ No newline at end of file diff --git a/app/src/behaviors/behavior_sensor_rotate_key_press.c b/app/src/behaviors/behavior_sensor_rotate_key_press.c deleted file mode 100644 index 72e33ea4..00000000 --- a/app/src/behaviors/behavior_sensor_rotate_key_press.c +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2020 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#define DT_DRV_COMPAT zmk_behavior_sensor_rotate_key_press - -#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_sensor_rotate_key_press_init(const struct device *dev) { return 0; }; - -static int on_sensor_binding_triggered(struct zmk_behavior_binding *binding, - const struct device *sensor, uint32_t virtual_key_position, - int64_t timestamp) { - struct sensor_value value; - int err; - uint32_t keycode; - LOG_DBG("inc keycode 0x%02X dec keycode 0x%02X", binding->param1, binding->param2); - - err = sensor_channel_get(sensor, SENSOR_CHAN_ROTATION, &value); - - if (err) { - LOG_WRN("Failed to ge sensor rotation value: %d", err); - return err; - } - - switch (value.val1) { - case 1: - keycode = binding->param1; - break; - case -1: - keycode = binding->param2; - break; - default: - return -ENOTSUP; - } - - LOG_DBG("SEND %d", keycode); - - ZMK_EVENT_RAISE(zmk_keycode_state_changed_from_encoded(keycode, true, timestamp)); - - // TODO: Better way to do this? - k_msleep(5); - - return ZMK_EVENT_RAISE(zmk_keycode_state_changed_from_encoded(keycode, false, timestamp)); -} - -static const struct behavior_driver_api behavior_sensor_rotate_key_press_driver_api = { - .sensor_binding_triggered = on_sensor_binding_triggered}; - -#define KP_INST(n) \ - DEVICE_DT_INST_DEFINE(n, behavior_sensor_rotate_key_press_init, NULL, NULL, NULL, APPLICATION, \ - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ - &behavior_sensor_rotate_key_press_driver_api); - -DT_INST_FOREACH_STATUS_OKAY(KP_INST) - -#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/behaviors/behavior_sensor_rotate_var.c b/app/src/behaviors/behavior_sensor_rotate_var.c new file mode 100644 index 00000000..a82267a5 --- /dev/null +++ b/app/src/behaviors/behavior_sensor_rotate_var.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_sensor_rotate_var + +#include + +#include + +#include "behavior_sensor_rotate_common.h" + +static const struct behavior_driver_api behavior_sensor_rotate_var_driver_api = { + .sensor_binding_triggered = zmk_behavior_sensor_rotate_common_trigger}; + +static int behavior_sensor_rotate_var_init(const struct device *dev) { return 0; }; + +#define SENSOR_ROTATE_VAR_INST(n) \ + static struct behavior_sensor_rotate_config behavior_sensor_rotate_var_config_##n = { \ + .cw_binding = {.behavior_dev = DT_PROP(DT_INST_PHANDLE_BY_IDX(n, bindings, 0), label)}, \ + .ccw_binding = {.behavior_dev = DT_PROP(DT_INST_PHANDLE_BY_IDX(n, bindings, 1), label)}, \ + .tap_ms = DT_INST_PROP(n, tap_ms), \ + .override_params = true, \ + }; \ + DEVICE_DT_INST_DEFINE( \ + n, behavior_sensor_rotate_var_init, NULL, NULL, &behavior_sensor_rotate_var_config_##n, \ + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_sensor_rotate_var_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(SENSOR_ROTATE_VAR_INST) diff --git a/app/src/keymap.c b/app/src/keymap.c index 1f55ad03..909fd20d 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -270,8 +270,9 @@ int zmk_keymap_sensor_triggered(uint8_t sensor_number, const struct device *sens continue; } - const uint32_t position = ZMK_VIRTUAL_KEY_POSITION_SENSOR(sensor_number); - ret = behavior_sensor_keymap_binding_triggered(binding, sensor, position, timestamp); + struct zmk_behavior_binding_event event = { + .position = ZMK_VIRTUAL_KEY_POSITION_SENSOR(sensor_number), .timestamp = timestamp}; + ret = behavior_sensor_keymap_binding_triggered(binding, sensor, event); if (ret > 0) { LOG_DBG("behavior processing to continue to next layer"); diff --git a/docs/docs/behaviors/sensor-rotate.md b/docs/docs/behaviors/sensor-rotate.md new file mode 100644 index 00000000..bd8a50bc --- /dev/null +++ b/docs/docs/behaviors/sensor-rotate.md @@ -0,0 +1,77 @@ +--- +title: Sensor Rotation +sidebar_label: Sensor Rotation +--- + +## Summary + +The Sensor Rotation behavior triggers a different behavior, depending on whether the sensor is rotated clockwise or counter-clockwise. Two variants of this behavior are available, allowing either fully specifying the +two behaviors and their parameters together, or allowing binding the sensor rotation with different clockwise and counterclockwise parameters in the keymap itself. + +## Sensor Rotation + +The standard sensor rotation behavior allows fully binding behaviors to be invoked: + +- If rotated counter-clockwise, the first bound behavior is triggered. +- If rotated clockwise, the second bound behavior is triggered. + +### Configuration + +Here is an example that binds the [RGB Underglow Behavior](/docs/behaviors/underglow.md) to change the RGB brightness: + +``` +/ { + behaviors { + rgb_encoder: rgb_encoder { + compatible = "zmk,behavior-sensor-rotate"; + label = "RGB_ENCODER"; + #sensor-binding-cells = <0>; + bindings = <&rgb_ug RGB_BRD>, <&rgb_ug RGB_BRI>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + + base { + ... + sensor-bindings = <&rgb_encoder>; + } + }; +}; +``` + +## Variable Sensor Rotation + +The variable sensor rotation behavior is configured with two behaviors that each expect a single parameter, +allowing the sensor rotation instance to be bound with two parameters at usage time. + +- If rotated counter-clockwise, the first bound behavior is triggered with the first parameter passed to the sensor rotation. +- If rotated clockwise, the second bound behavior is triggered with the second parameter passed to the sensor rotation. + +### Configuration + +Here is an example, showing how send key presses on rotation: + +First, defining the sensor rotation itself, binding the [Key Press Behavior](/docs/behaviors/key-press.md) twice, then binding it in the `sensor-bindings` property of a keymap layer: + +``` +/ { + behaviors { + rot_kp: behavior_sensor_rotate_kp { + compatible = "zmk,behavior-sensor-rotate-var"; + label = "ENC_KP"; + #sensor-binding-cells = <2>; + bindings = <&kp>, <&kp>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + base { + ... + sensor-bindings = <&rot_kp PG_DN PG_UP>; + } + } +}; +``` diff --git a/docs/docs/features/encoders.md b/docs/docs/features/encoders.md index 225ee6f3..29906c90 100644 --- a/docs/docs/features/encoders.md +++ b/docs/docs/features/encoders.md @@ -23,17 +23,17 @@ Keyboards and macropads with encoder support will typically take the two EC11 pi ### Rotation -Rotation is handled separately as a type of sensor. The behavior for this is set in `sensor-bindings`, which is defined in each keymap layer in the following format: +Rotation is handled separately as a type of sensor. The behavior for this is set in `sensor-bindings`. See [Sensor Rotation](../behaviors/sensor-rotate.md) for customizing this behavior. ``` -sensor-bindings = ; +sensor-bindings = ; ``` -- `BINDING`, for now, has only one behavior available; `&inc_dec_kp` for key presses (see [Key Press](../behaviors/key-press.md) for details on available keycodes). +- `BINDING` is either a user-defined behavior, or `&inc_dec_kp` for key presses (see [Key Press](../behaviors/key-press.md) for details on available keycodes). - `CW_KEY` is the keycode activated by a clockwise turn. - `CCW_KEY` is the keycode activated by a counter-clockwise turn. -Additional encoders can be configured by adding more `BINDING CW_KEY CCW_KEY` sets immediately after the first. +Additional encoders can be configured by adding more bindings immediately after the first. As an example, a complete `sensor-bindings` for a Kyria with two encoders could look like: diff --git a/docs/sidebars.js b/docs/sidebars.js index a52f3302..21585747 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -35,6 +35,7 @@ module.exports = { "behaviors/tap-dance", "behaviors/caps-word", "behaviors/key-repeat", + "behaviors/sensor-rotate", "behaviors/reset", "behaviors/bluetooth", "behaviors/outputs",