From fc8b91a2ba21a8998b80f67b5f100aa691f94dd5 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Tue, 18 Jan 2022 22:52:24 +0100 Subject: [PATCH] Implement control animation and basic animation behaviors --- app/CMakeLists.txt | 2 + app/dts/behaviors.dtsi | 3 +- app/dts/behaviors/animation.dtsi | 15 ++ .../animations/zmk,animation-control.yaml | 23 +++ .../behaviors/zmk,behavior-animation.yaml | 8 + app/include/dt-bindings/zmk/animation.h | 41 ++++ app/include/zmk/animation/animation_control.h | 24 +++ app/src/animation/animation_control.c | 175 ++++++++++++++++++ app/src/behaviors/behavior_animation.c | 93 ++++++++++ 9 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 app/dts/behaviors/animation.dtsi create mode 100644 app/dts/bindings/animations/zmk,animation-control.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-animation.yaml create mode 100644 app/include/zmk/animation/animation_control.h create mode 100644 app/src/animation/animation_control.c create mode 100644 app/src/behaviors/behavior_animation.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 9e5cbdad..49572b37 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -82,9 +82,11 @@ target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/battery.c) target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/color.c) target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation_compose.c) +target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation_control.c) target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation_ripple.c) target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation_solid.c) target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation.c) +target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/behaviors/behavior_animation.c) target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c) add_subdirectory(src/split) diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index b3502cbb..6bf952e0 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -18,4 +18,5 @@ #include #include #include -#include \ No newline at end of file +#include +#include diff --git a/app/dts/behaviors/animation.dtsi b/app/dts/behaviors/animation.dtsi new file mode 100644 index 00000000..d4551528 --- /dev/null +++ b/app/dts/behaviors/animation.dtsi @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + behaviors { + /omit-if-no-ref/ animation: behavior_animation { + compatible = "zmk,behavior-animation"; + label = "ANIMATION"; + #binding-cells = <1>; + }; + }; +}; diff --git a/app/dts/bindings/animations/zmk,animation-control.yaml b/app/dts/bindings/animations/zmk,animation-control.yaml new file mode 100644 index 00000000..6befe89a --- /dev/null +++ b/app/dts/bindings/animations/zmk,animation-control.yaml @@ -0,0 +1,23 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: | + Higher-order animation which allows for controlling animation drivers + placed underneath it by turning them on and off, cycling though them, + or changing the brightness. + +compatible: "zmk,animation-control" + +properties: + animations: + type: phandles + required: true + description: | + Animations to be combined. + + brightness-steps: + type: int + required: false + default: 5 + description: | + How many brightness steps should be supported. diff --git a/app/dts/bindings/behaviors/zmk,behavior-animation.yaml b/app/dts/bindings/behaviors/zmk,behavior-animation.yaml new file mode 100644 index 00000000..0ec36fcc --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-animation.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Animation Action + +compatible: "zmk,behavior-animation" + +include: one_param.yaml diff --git a/app/include/dt-bindings/zmk/animation.h b/app/include/dt-bindings/zmk/animation.h index 3b74d025..d27162dd 100644 --- a/app/include/dt-bindings/zmk/animation.h +++ b/app/include/dt-bindings/zmk/animation.h @@ -13,3 +13,44 @@ #else #define HSL(h, s, l) (h + (s << 16) + (l << 24)) #endif + +/** + * Animation blending modes + */ +#define BLENDING_MODE_NORMAL 0 +#define BLENDING_MODE_MULTIPLY 1 +#define BLENDING_MODE_LIGHTEN 2 +#define BLENDING_MODE_DARKEN 3 +#define BLENDING_MODE_SCREEN 4 +#define BLENDING_MODE_SUBTRACT 5 + +/** + * Animation control commands + */ +#define ANIMATION_CMD_TOGGLE 0 +#define ANIMATION_CMD_NEXT 1 +#define ANIMATION_CMD_PREVIOUS 2 +#define ANIMATION_CMD_SELECT 3 +#define ANIMATION_CMD_BRIGHTEN 4 +#define ANIMATION_CMD_DIM 5 +#define ANIMATION_CMD_NEXT_CONTROL_ZONE 6 +#define ANIMATION_CMD_PREVIOUS_CONTROL_ZONE 7 + +/** + * Generic animation command macro + */ +#define ANIMATION_CONTROL_CMD(command, zone, param) ((zone << 24) | (command << 16) | (param)) + +/** + * Animation behavior helpers + */ +#define ANIMATION_TOGGLE(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_TOGGLE, zone, 0) +#define ANIMATION_NEXT(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_NEXT, zone, 0) +#define ANIMATION_PREVIOUS(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_PREVIOUS, zone, 0) +#define ANIMATION_SELECT(zone, target_animation) \ + ANIMATION_CONTROL_CMD(ANIMATION_CMD_SELECT, zone, target_animation) +#define ANIMATION_BRIGHTEN(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_BRIGHTEN, zone, 0) +#define ANIMATION_DIM(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_DIM, zone, 0) +#define ANIMATION_NEXT_CONTROL_ZONE ANIMATION_CONTROL_CMD(ANIMATION_CMD_NEXT_CONTROL_ZONE, 0, 0) +#define ANIMATION_PREVIOUS_CONTROL_ZONE \ + ANIMATION_CONTROL_CMD(ANIMATION_CMD_PREVIOUS_CONTROL_ZONE, 0, 0) diff --git a/app/include/zmk/animation/animation_control.h b/app/include/zmk/animation/animation_control.h new file mode 100644 index 00000000..653afcf3 --- /dev/null +++ b/app/include/zmk/animation/animation_control.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +/** + * Animation control commands + */ +#define ANIMATION_CMD_TOGGLE 0 +#define ANIMATION_CMD_NEXT 1 +#define ANIMATION_CMD_PREVIOUS 2 +#define ANIMATION_CMD_SELECT 3 +#define ANIMATION_CMD_BRIGHTEN 4 +#define ANIMATION_CMD_DIM 5 +#define ANIMATION_CMD_NEXT_CONTROL_ZONE 6 +#define ANIMATION_CMD_PREVIOUS_CONTROL_ZONE 7 + +int animation_control_handle_command(const struct device *dev, uint8_t command, uint8_t param); \ No newline at end of file diff --git a/app/src/animation/animation_control.c b/app/src/animation/animation_control.c new file mode 100644 index 00000000..f30923a6 --- /dev/null +++ b/app/src/animation/animation_control.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_animation_control + +#include +#include +#include +#include + +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +// Zephyr 2.7.0 comes with DT_INST_FOREACH_PROP_ELEM +// that we can't use quite yet as we're still on 2.5.* +#define ZMK_DT_INST_FOREACH_PROP_ELEM(inst, prop, fn) \ + UTIL_LISTIFY(DT_INST_PROP_LEN(inst, prop), fn, DT_DRV_INST(inst), prop) + +#define PHANDLE_TO_DEVICE(idx, node_id, prop) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), + +struct animation_control_config { + const struct device **animations; + const size_t animations_size; + const uint8_t brightness_steps; +}; + +struct animation_control_data { + bool active; + uint8_t brightness; + size_t current_animation; +}; + +int animation_control_handle_command(const struct device *dev, uint8_t command, uint8_t param) { + const struct animation_control_config *config = dev->config; + struct animation_control_data *data = dev->data; + + switch (command) { + case ANIMATION_CMD_TOGGLE: + data->active = !data->active; + + if (data->active) { + animation_start(config->animations[data->current_animation]); + return 0; + } + + animation_stop(config->animations[data->current_animation]); + break; + case ANIMATION_CMD_NEXT: + data->current_animation++; + + if (data->current_animation == config->animations_size) { + data->current_animation = 0; + } + break; + case ANIMATION_CMD_PREVIOUS: + if (data->current_animation == 0) { + data->current_animation = config->animations_size; + } + + data->current_animation--; + break; + case ANIMATION_CMD_SELECT: + if (config->animations_size < param) { + return -ENOTSUP; + } + + data->current_animation = param; + break; + case ANIMATION_CMD_DIM: + if (data->brightness == 0) { + return 0; + } + + data->brightness--; + + if (data->brightness == 0) { + animation_stop(config->animations[data->current_animation]); + } + break; + case ANIMATION_CMD_BRIGHTEN: + if (data->brightness == config->brightness_steps) { + return 0; + } + + if (data->brightness == 0) { + animation_start(config->animations[data->current_animation]); + } + + data->brightness++; + break; + } + + // Force refresh + zmk_animation_request_frames(1); + + return 0; +} + +void animation_control_start(const struct device *dev) { + const struct animation_control_config *config = dev->config; + const struct animation_control_data *data = dev->data; + + if (!data->active) { + return; + } + + animation_start(config->animations[data->current_animation]); +} + +void animation_control_stop(const struct device *dev) { + const struct animation_control_config *config = dev->config; + const struct animation_control_data *data = dev->data; + + animation_stop(config->animations[data->current_animation]); +} + +void animation_control_render_frame(const struct device *dev, struct animation_pixel *pixels, + size_t num_pixels) { + const struct animation_control_config *config = dev->config; + const struct animation_control_data *data = dev->data; + + if (!data->active) { + return; + } + + animation_render_frame(config->animations[data->current_animation], pixels, num_pixels); + + if (data->brightness == config->brightness_steps) { + return; + } + + float brightness = (float)data->brightness / (float)config->brightness_steps; + + for (size_t i = 0; i < num_pixels; ++i) { + pixels[i].value.r *= brightness; + pixels[i].value.g *= brightness; + pixels[i].value.b *= brightness; + } +} + +static int animation_control_init(const struct device *dev) { return 0; } + +static const struct animation_api animation_control_api = { + .on_start = animation_control_start, + .on_stop = animation_control_stop, + .render_frame = animation_control_render_frame, +}; + +#define ANIMATION_CONTROL_DEVICE(idx) \ + \ + static const struct device *animation_control_##idx##_animations[] = { \ + ZMK_DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \ + \ + static const struct animation_control_config animation_control_##idx##_config = { \ + .animations = animation_control_##idx##_animations, \ + .animations_size = DT_INST_PROP_LEN(idx, animations), \ + .brightness_steps = DT_INST_PROP(idx, brightness_steps) - 1, \ + }; \ + \ + static struct animation_control_data animation_control_##idx##_data = { \ + .active = true, \ + .brightness = DT_INST_PROP(idx, brightness_steps) - 1, \ + .current_animation = 0, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(idx, &animation_control_init, NULL, &animation_control_##idx##_data, \ + &animation_control_##idx##_config, POST_KERNEL, \ + CONFIG_APPLICATION_INIT_PRIORITY, &animation_control_api); + +DT_INST_FOREACH_STATUS_OKAY(ANIMATION_CONTROL_DEVICE); diff --git a/app/src/behaviors/behavior_animation.c b/app/src/behaviors/behavior_animation.c new file mode 100644 index 00000000..1f6823c8 --- /dev/null +++ b/app/src/behaviors/behavior_animation.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define DT_DRV_COMPAT zmk_animation_control + +#define DEVICE_ADDR(idx) DEVICE_DT_GET(DT_INST(idx, zmk_animation_control)), + +/** + * Control animation instance pointers. + */ +static const struct device *control_animations[] = {DT_INST_FOREACH_STATUS_OKAY(DEVICE_ADDR)}; + +/** + * Size of control animation instance pointers array. + */ +static const uint8_t control_animations_size = sizeof(control_animations); + +/** + * Index of the current default control animation instance. + */ +static uint8_t current_zone = 0; + +#define DT_DRV_COMPAT zmk_behavior_animation + +static int +on_keymap_binding_convert_central_state_dependent_params(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + if ((binding->param1 >> 24) == 0xff) { + binding->param1 = (current_zone << 24) | (binding->param1 & 0x00ffffff); + } + + return 0; +} + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + uint8_t value = binding->param1 & 0xff; + uint8_t command = (binding->param1 >> 16) & 0xff; + uint8_t zone = (binding->param1 >> 24) & 0xff; + + if (command == ANIMATION_CMD_NEXT_CONTROL_ZONE) { + current_zone++; + + if (current_zone == control_animations_size) { + current_zone = 0; + } + return 0; + } + + if (command == ANIMATION_CMD_PREVIOUS_CONTROL_ZONE) { + if (current_zone == 0) { + current_zone = control_animations_size; + } + + current_zone--; + return 0; + } + + if (control_animations_size <= zone) { + return -ENOTSUP; + } + + return animation_control_handle_command(control_animations[zone], command, value); +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + return ZMK_BEHAVIOR_OPAQUE; +} + +static int behavior_animation_init(const struct device *dev) { return 0; } + +static const struct behavior_driver_api behavior_animation_driver_api = { + .binding_convert_central_state_dependent_params = + on_keymap_binding_convert_central_state_dependent_params, + .binding_pressed = on_keymap_binding_pressed, + .binding_released = on_keymap_binding_released, +}; + +DEVICE_DT_INST_DEFINE(0, behavior_animation_init, device_pm_control_nop, NULL, NULL, APPLICATION, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_animation_driver_api);