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
This commit is contained in:
Nick Conway 2022-05-23 16:33:08 -04:00 committed by Pete Johanson
parent 9a73650041
commit 3db163aa2c
17 changed files with 301 additions and 107 deletions

View file

@ -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)

View file

@ -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"

24
app/Kconfig.behaviors Normal file
View file

@ -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

View file

@ -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>;
};
};
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);
}
/**

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_sensor_rotate
#include <zephyr/device.h>
#include <drivers/behavior.h>
#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)

View file

@ -0,0 +1,52 @@
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zmk/behavior_queue.h>
#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;
}

View file

@ -0,0 +1,13 @@
#include <zmk/behavior.h>
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);

View file

@ -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 <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include <drivers/behavior.h>
#include <zmk/event_manager.h>
#include <zmk/events/keycode_state_changed.h>
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) */

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_sensor_rotate_var
#include <zephyr/device.h>
#include <drivers/behavior.h>
#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)

View file

@ -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");

View file

@ -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>;
}
}
};
```

View file

@ -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 = <BINDING CW_KEY CCW_KEY>;
sensor-bindings = <BINDING [CW_KEY] [CCW_KEY]>;
```
- `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:

View file

@ -35,6 +35,7 @@ module.exports = {
"behaviors/tap-dance",
"behaviors/caps-word",
"behaviors/key-repeat",
"behaviors/sensor-rotate",
"behaviors/reset",
"behaviors/bluetooth",
"behaviors/outputs",