From c3beca12bde0003711fca7f74c9d3f3aec33c72f Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Thu, 2 Dec 2021 12:54:03 +0100 Subject: [PATCH 01/34] Add animation devicetree bindings --- app/dts/bindings/zmk,animation-pixel.yaml | 11 +++++++++ app/dts/bindings/zmk,animation.yaml | 28 +++++++++++++++++++++++ app/include/dt-bindings/zmk/animation.h | 15 ++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 app/dts/bindings/zmk,animation-pixel.yaml create mode 100644 app/dts/bindings/zmk,animation.yaml create mode 100644 app/include/dt-bindings/zmk/animation.h diff --git a/app/dts/bindings/zmk,animation-pixel.yaml b/app/dts/bindings/zmk,animation-pixel.yaml new file mode 100644 index 00000000..a5588dc6 --- /dev/null +++ b/app/dts/bindings/zmk,animation-pixel.yaml @@ -0,0 +1,11 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Pixel configuration + +compatible: "zmk,animation-pixel" + +pixel-cells: + - animation + - position_x + - position_y diff --git a/app/dts/bindings/zmk,animation.yaml b/app/dts/bindings/zmk,animation.yaml new file mode 100644 index 00000000..664de459 --- /dev/null +++ b/app/dts/bindings/zmk,animation.yaml @@ -0,0 +1,28 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Global animation configuration + +compatible: "zmk,animation" + +properties: + drivers: + type: phandles + required: true + description: | + This array should contain all driver devices responsible for illuminating animated LEDs. + The devices must implement Zephyr's LED Strip Interface and expose a chain-lenght devicetree property. + + animations: + type: phandles + required: true + description: | + Handles to all active animations. + + pixels: + type: phandle-array + required: true + description: | + This field contains the pixel configuration for the entire board. + The order of this array determines in what order pixels are sent to the driver device API. + If multiple driving devices are used, their chain-length property determines the size of the buffer for each device. diff --git a/app/include/dt-bindings/zmk/animation.h b/app/include/dt-bindings/zmk/animation.h new file mode 100644 index 00000000..3b74d025 --- /dev/null +++ b/app/include/dt-bindings/zmk/animation.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/** + * Maps HSL color settings into a single uint32_t value + * that can be cast to zmk_color_hsl. + */ +#ifdef CONFIG_BIG_ENDIAN +#define HSL(h, s, l) ((h << 16) + (s << 8) + l) +#else +#define HSL(h, s, l) (h + (s << 16) + (l << 24)) +#endif From 4da01b63b074aa43cad378db022bd2dbdaef6ece Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Thu, 2 Dec 2021 12:54:44 +0100 Subject: [PATCH 02/34] Implement the initial version of the animation interface --- app/include/drivers/animation.h | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 app/include/drivers/animation.h diff --git a/app/include/drivers/animation.h b/app/include/drivers/animation.h new file mode 100644 index 00000000..45e80524 --- /dev/null +++ b/app/include/drivers/animation.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +/** + * @file + * #brief Public API for controlling animations. + * + * This library abstracts the implementation details + * for various types of 2D animations. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @typedef animation_api_prep_next_frame + * @brief Callback API for generating the next animation frame + * + * @see animation_prep_next_frame() for argument descriptions. + */ +typedef void (*animation_api_prep_next_frame)(const struct device *dev); + +/** + * @typedef animation_api_prep_next_frame + * @brief Callback API for generating the next animation frame + * + * @see animation_prep_next_frame() for argument descriptions. + */ +typedef void (*animation_api_get_pixel)(const struct device *dev, const struct animation_pixel_position *pixel_position, + struct zmk_color_rgb *value); + +struct animation_api { + animation_api_prep_next_frame prep_next_frame; + animation_api_get_pixel get_pixel; +}; + +/** + * [animation_prep_next_frame description] + * @param dev [description] + */ +static inline void animation_prep_next_frame(const struct device *dev) { + const struct animation_api *api = (const struct animation_api *) dev->api; + + return api->prep_next_frame(dev); +} + +/** + * [animation_get_pixel description] + * @param dev [description] + * @param pixel [description] + */ +static inline void animation_get_pixel(const struct device *dev, const struct animation_pixel_position *pixel_position, + struct zmk_color_rgb *value) { + const struct animation_api *api = (const struct animation_api *) dev->api; + + return api->get_pixel(dev, pixel_position, value); +} + +#ifdef __cplusplus +} +#endif From 352fe394680e57261d4cd7ae856de5c9c61224f4 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Thu, 2 Dec 2021 12:56:29 +0100 Subject: [PATCH 03/34] Add an implementation for the core animations system --- app/CMakeLists.txt | 3 + app/include/zmk/animation.h | 70 ++++++++++++++ app/src/animation/animation.c | 166 ++++++++++++++++++++++++++++++++++ app/src/animation/color.c | 84 +++++++++++++++++ 4 files changed, 323 insertions(+) create mode 100644 app/include/zmk/animation.h create mode 100644 app/src/animation/animation.c create mode 100644 app/src/animation/color.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index a647e883..d35ca3cb 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -80,6 +80,9 @@ target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_bac target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/battery.c) +target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation.c) +target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/color.c) + target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c) add_subdirectory(src/split) diff --git a/app/include/zmk/animation.h b/app/include/zmk/animation.h new file mode 100644 index 00000000..df488851 --- /dev/null +++ b/app/include/zmk/animation.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +struct animation_pixel_position { + const uint8_t x; + const uint8_t y; +}; + +struct animation_pixel { + const struct device *animation; + const struct animation_pixel_position position; +}; + +struct zmk_color_rgb { + float r; + float g; + float b; +}; + +struct zmk_color_hsl { + uint16_t h; + uint8_t s; + uint8_t l; +}; + +/** + * Converts color from HSL to RGB. + * + * @param hsl [description] + * @param rgb [description] + */ +void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb); + +/** + * Converts the internal RGB representation into a led_rgb struct + * for use with led_strip drivers. + * + * @param rgb [description] + * @param led [description] + */ +void zmk_rgb_to_led_rgb(const struct zmk_color_rgb *rgb, struct led_rgb *led); + +/** + * Returns true if two HSL colors are the same. + * + * @param a [description] + * @param b [description] + * @return [description] + */ +bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b); + +/** + * Perform linear interpolation between HSL values of two colors + * at a given distance (step) and store the resulting value in the given pointer. + * + * @param from [description] + * @param to [description] + * @param result [description] + * @param step [description] + */ +void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to, + struct zmk_color_hsl *result, float step); diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c new file mode 100644 index 00000000..c34dd9e2 --- /dev/null +++ b/app/src/animation/animation.c @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_animation + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define PHANDLE_TO_DEVICE(node_id, prop, idx) \ + DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), + +#define PHANDLE_TO_CHAIN_LENGTH(node_id, prop, idx) \ + DT_PROP_BY_PHANDLE_IDX(node_id, prop, idx, chain_length), + +#define PHANDLE_TO_PIXEL(node_id, prop, idx) \ + { \ + .animation = DEVICE_DT_GET(DT_PHA_BY_IDX(node_id, prop, idx, animation)), \ + .position = { \ + .x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \ + .y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \ + }, \ + }, + +/** + * LED Driver device pointers. + */ +static const struct device *drivers[] = { + DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE) +}; + +/** + * Size of the LED driver device pointers array. + */ +static const size_t drivers_size = DT_INST_PROP_LEN(0, drivers); + +/** + * Array containing the number of LEDs handled by each device. + */ +static const uint8_t pixels_per_driver[] = { + DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH) +}; + +/** + * Pointers to all active animation devices. + */ +static const struct device *animations[] = { + DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_DEVICE) +}; + +/** + * Size of the animation device pointers array. + */ +static const size_t animations_size = DT_INST_PROP_LEN(0, animations); + +/** + * Pixel configuration. + */ +static const struct animation_pixel pixels[] = { + DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_PIXEL) +}; + +/** + * Size of the pixels array. + */ +const size_t pixels_size = DT_INST_PROP_LEN(0, pixels); + +/** + * Buffer for RGB values ready to be sent to the drivers. + */ +struct led_rgb px_buffer[DT_INST_PROP_LEN(0, pixels)]; + +void zmk_animation_tick() { + for (size_t i = 0; i < ZMK_ANIMATIONS_LENGTH; ++i) { + animation_prep_next_frame(animations[i]); + } + + for (size_t i = 0; i < pixels_size; ++i) { + zmk_color_rgb rgb = { + .r = 0, + .g = 0, + .b = 0, + }; + + animation_get_pixel(pixel.animation, &pixel.position, &rgb); + + zmk_rgb_to_led_rgb(&rgb, &px_buffer[i]); + } + + size_t pixels_updated = 0; + + for (size_t i = 0; i < drivers_size; ++i) { + led_strip_update_rgb( + drivers[i], + &px_buffer[pixels_updated], + pixels_per_driver[i] + ); + + pixels_updated += pixels_per_driver; + } +} + +// Set up timer here + +K_WORK_DEFINE(animation_work, zmk_animation_tick); + +static void zmk_animation_tick_handler(struct k_timer *timer) { + k_work_submit(&animation_work); +} + +K_TIMER_DEFINE(animation_tick, zmk_animation_tick_handler, NULL); + +// Init + +void zmk_animation_init(const struct device *dev) { + // default FPS: 24, 30 or 60? + k_timer_start(&animation_tick, K_NO_WAIT, K_MSEC(1000 / ZMK_ANIMATION_FPS)); +} + +// Actually, all of this might be a little hard to represent inside a config file. +// We need: +// +// - Each animation has its own node +// +// - animations[] = phandle array of animations +// +// - pixel.pos = x,y coordinates of each pixels, could be a byte array +// +// - pixel.animations = Number array, but it's per pixel and it's not fixed length per pixel!! PROBLEM!!! +// - pixel.blending_modes = Could be a number array again. Use some macros. +// +// - unless we say there can only be 32 different animations and use uint32_t as a bitmask? +// - but then blending modes suck and you can't order them +// + +// ALTERNATIVE: +// +// Eventually we don't want to be using the device tree to set these up anyway. +// It should be possible to instantiate animations dynamically and the settings should be stored in flash. +// Maybe it's something to look into now? +// + +// ALTERNATIVE no2: +// +// Look at behaviors. +// It seems that pixels themselves could be 'drivers' ? +// +// That's probably best tbh + +// NOTE WHEN DEFINING PIXELS: +// See if it's maybe possible to re-use default_transform to assign pixels to keys? +// Assuming the first batch is always the keys which is not an easy assumption to make. +// Otherwise need it's own map diff --git a/app/src/animation/color.c b/app/src/animation/color.c new file mode 100644 index 00000000..5681b667 --- /dev/null +++ b/app/src/animation/color.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +/** + * HSL chosen over HSV/HSB as it shares the same parameters with LCh or HSLuv. + * The latter color spaces could be interesting to experiment with because of their + * perceptual uniformity, but it would come at the cost of some performance. + * Using the same parameters would make it easy to toggle any such behavior using a single config flag. + * + * Algorithm source: https://www.tlbx.app/color-converter + */ +void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb) { + // float a = hsl->h / 60; + uint8_t a = hsl->h / 60; + float chroma = hsl->s * (1 - abs(2 * hsl->l - 1)); + // I think 'a' actually needs to be a float here or this doesn't make sense. + // If uint, possible values are: (0,1), if float: (0...1) + float x = chroma * (1 - abs(a % 2 - 1)); + float m = hsl->l - chroma / 2; + + switch (a) { + case 0: + rgb->r = m + chroma; + rgb->g = m + x; + rgb->b = m; + case 1: + rgb->r = m + x; + rgb->g = m + chroma; + rgb->b = m; + case 2: + rgb->r = m; + rgb->g = m + chroma; + rgb->b = m + x; + case 3: + rgb->r = m; + rgb->g = m + x; + rgb->b = m + chroma; + case 4: + rgb->r = m + x; + rgb->g = m; + rgb->b = m + chroma; + case 5: + rgb->r = m + chroma; + rgb->g = m; + rgb->b = m + x; + } +} + +/** + * Converts ZMKs RGB (float) to Zephyr's led_rgb (uint8_t) format. + */ +void zmk_rgb_to_led_rgb(const struct zmk_color_rgb *rgb, struct led_rgb *led) { + led->r = rgb->r * 255; + led->g = rgb->g * 255; + led->b = rgb->b * 255; +} + +/** + * Compares two HSL colors. + */ +bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b) { + return a->h == b->h && a->s == b->s && a->l == b->l; +} + +/** + * Interpolate between two colors using the cylindrical model (HSL). + */ +void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to, + struct zmk_color_hsl *result, float step) { + int16_t hue_delta; + + hue_delta = from->h - to->h; + hue_delta = hue_delta + (180 < abs(hue_delta) ? (hue_delta < 0 ? 360 : -360) : 0); + + result->h = (uint16_t) (360 + from->h - (hue_delta * step)) % 360; + result->s = from->s - (from->s - to->s) * step; + result->l = from->l - (from->l - to->l) * step; +} From be02e7835678579d5bf8fdc081de9dd03b68e45d Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Thu, 2 Dec 2021 12:57:19 +0100 Subject: [PATCH 04/34] Implement a simple solid-color-cycle animation --- app/drivers/CMakeLists.txt | 1 + app/drivers/Kconfig | 1 + app/drivers/animation/CMakeLists.txt | 6 + app/drivers/animation/Kconfig | 9 ++ app/drivers/animation/solid_color.c | 130 ++++++++++++++++++ .../animations/zmk,animation-solid.yaml | 21 +++ 6 files changed, 168 insertions(+) create mode 100644 app/drivers/animation/CMakeLists.txt create mode 100644 app/drivers/animation/Kconfig create mode 100644 app/drivers/animation/solid_color.c create mode 100644 app/dts/bindings/animations/zmk,animation-solid.yaml diff --git a/app/drivers/CMakeLists.txt b/app/drivers/CMakeLists.txt index 44d69ac3..cb4917ce 100644 --- a/app/drivers/CMakeLists.txt +++ b/app/drivers/CMakeLists.txt @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT add_subdirectory_ifdef(CONFIG_ZMK_DRIVERS_GPIO gpio) +add_subdirectory_ifdef(CONFIG_ZMK_ANIUMATION animation) add_subdirectory(kscan) add_subdirectory(sensor) add_subdirectory(display) diff --git a/app/drivers/Kconfig b/app/drivers/Kconfig index c57ed334..0c6431b2 100644 --- a/app/drivers/Kconfig +++ b/app/drivers/Kconfig @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT rsource "gpio/Kconfig" +rsource "animation/Kconfig" rsource "kscan/Kconfig" rsource "sensor/Kconfig" rsource "display/Kconfig" diff --git a/app/drivers/animation/CMakeLists.txt b/app/drivers/animation/CMakeLists.txt new file mode 100644 index 00000000..8cb5a708 --- /dev/null +++ b/app/drivers/animation/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_library() + +zephyr_library_sources(solid_color.c) diff --git a/app/drivers/animation/Kconfig b/app/drivers/animation/Kconfig new file mode 100644 index 00000000..2f1ac02a --- /dev/null +++ b/app/drivers/animation/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config ZMK_ANIMATION + bool "RGB Animations" + depends on I2C + depends on LED_STRIP + help + Enable RGB animations. diff --git a/app/drivers/animation/solid_color.c b/app/drivers/animation/solid_color.c new file mode 100644 index 00000000..d86b77ed --- /dev/null +++ b/app/drivers/animation/solid_color.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_colors_animation + +#include + +struct solid_color_animation_config config { + zmk_color_hsl[] colors; + uint8_t num_colors; + uint16_t duration; + uint16_t transition_duration; +}; + +struct solid_color_animation_data data { + bool has_changed; + + uint16_t counter; + + zmk_color_hsl current_hsl; + zmk_color_rgb current_rgb; +}; + +// or just colors array ? And if just one it stays the same otherwise it just chagnes color. genius + +static void colors_animation_prep_next_frame(const struct device *dev) { + const struct solid_color_animation_config *config = dev->config; + const struct solid_color_animation_data *data = dev->data; + + // Animation only contains a single color, nothing to do + if (config->num_colors == 1) { + return; + } + + const size_t from = data->counter / config->transition_duration; + const size_t to = (from + 1) % config->num_colors; + + struct zmk_color_hsl next_hsl; + + zmk_interpolate_hsl( + &config->colors[from], + &config->colors[to], + &next_hsl, + (data->counter % config->transition_duration) / (float) config->transition_duration + ); + + data->has_changed = !zmk_cmp_hsl(&data->current_hsl, &next_hsl); + + data->current_hsl = next_hsl; + zmk_hsl_to_rgb(data->current_rgb, data->current_hsl); + + data->counter = (data->counter + 1) % config.duration; +} + +static void colors_animation_solid(const struct device *dev, animation_pixel *pixel) { + const struct solid_color_animation_config config = dev->config; + + pixel->rgb = data->current_rgb; +} + +static void animation_solid_color_init(const struct device *dev) { + const struct solid_color_animation_config config = dev->config; + + // Actually... I'm not sure fi there's anything to set up here, is there? + // But I do need a public function for has_changed +} + +// #define ANIMATION_SOLID_COLOR_DEVICE(idx) +// + + +// use union type to cast from uint_32t to struct { uint16_t, uint8_t, uint8_t } ? + +static const struct animation_api solid_color_animation_api = { + .prep_next_frame = solid_color_animation_prep_next_frame, + .get_pixel = solid_color_animation_get_pixel, +}; + +static struct solid_color_animation_data solid_color_animation_##idx##_data; + +static uint32_t solid_color_animation_##idx##_colors[DT_INST_PROP_LEN(idx, colors)] = DT_INST_PROP(idx, colors); + +static struct solid_color_animation_config solid_color_animation_##idx##_config = { + .colors = (struct zmk_color_hsl *) solid_color_animation_##idx##_colors, + .num_colors = DT_INST_PROP_LEN(idx, colors), + .duration = DT_INST_PROP(idx, duration), + .transition_duration = DT_INST_PROP(idx, duration) / DT_INST_PROP_LEN(idx, colors), +}; + + + +// To do: +// +// STEP 1: single animation +// - Start with a single animation, just color +// - Add layer for taking the output from here and putting it to the led strip +// - Make it work +// +// STEP 2: areas, in fact, instead of defining them explicitly we can just use appropriate x,y coordinates and animation. +// - Split keyboard in two independent areas +// - Make it work +// +// STEP 3: add additional animation effects +// - Basically, carry over rgb_underglow. +// - Make it work +// +// STEP 4: add animation triggers +// - Allow an animation to be triggered by behaviors or key-presses +// - Make it work +// +// STEP 5: add animation layers and a MULTIPLY mode (again, opacity would be set on individual pixels so... that affects some optimizations I guess) +// - Normal mode: overrides layers below +// - Multiply mode: auguments whatever is below (opacity, whatever) +// +// Voila! Animation composition! +// +// STEP 6, BONUS!: +// - Figure out a way to switch animations during runtime? +// +// Notes: +// - Any animation settings go into 'driver' config & data, so they can be updated at runtime. +// - Main limitation is space, so the amount of different animations one can have loaded +// +// More notes: +// - Solid color would be one animation (just transitions between colors) +// - Gradient (SPECTRUM) would be another, you choose how they're distributed accross the keys and if they move? +// - Effects like 'breathe' can be implemented by specifying #000 as one of the colors or using a multiply layer? diff --git a/app/dts/bindings/animations/zmk,animation-solid.yaml b/app/dts/bindings/animations/zmk,animation-solid.yaml new file mode 100644 index 00000000..93979561 --- /dev/null +++ b/app/dts/bindings/animations/zmk,animation-solid.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: | + Animation for displaying solid colors across the entire area + +compatible: "zmk,animation-solid" + +properties: + duration: + type: int + required: true + description: | + Animation duration in seconds. + This is the time it takes for the animation to complete a full cycle and return to the original color. + + colors: + type: array + required: true + description: | + The colors to cycle through during the animation. From 5da7a6e29f4b254e80a89425e68ed28002bc0a7d Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sat, 4 Dec 2021 15:09:28 +0100 Subject: [PATCH 05/34] Remove pixel device definition --- app/dts/bindings/zmk,animation-pixel.yaml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 app/dts/bindings/zmk,animation-pixel.yaml diff --git a/app/dts/bindings/zmk,animation-pixel.yaml b/app/dts/bindings/zmk,animation-pixel.yaml deleted file mode 100644 index a5588dc6..00000000 --- a/app/dts/bindings/zmk,animation-pixel.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2020, The ZMK Contributors -# SPDX-License-Identifier: MIT - -description: Pixel configuration - -compatible: "zmk,animation-pixel" - -pixel-cells: - - animation - - position_x - - position_y From 2326f36e50dc8916ba66ec00fdc362f1c0500ca6 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sat, 4 Dec 2021 17:12:28 +0100 Subject: [PATCH 06/34] Update animation devicetree bindings --- app/dts/bindings/animations/animation_base.yaml | 6 ++++++ app/dts/bindings/animations/zmk,animation-solid.yaml | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 app/dts/bindings/animations/animation_base.yaml diff --git a/app/dts/bindings/animations/animation_base.yaml b/app/dts/bindings/animations/animation_base.yaml new file mode 100644 index 00000000..aa2a5342 --- /dev/null +++ b/app/dts/bindings/animations/animation_base.yaml @@ -0,0 +1,6 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +pixel-cells: + - position_x + - position_y diff --git a/app/dts/bindings/animations/zmk,animation-solid.yaml b/app/dts/bindings/animations/zmk,animation-solid.yaml index 93979561..f84aaeef 100644 --- a/app/dts/bindings/animations/zmk,animation-solid.yaml +++ b/app/dts/bindings/animations/zmk,animation-solid.yaml @@ -6,7 +6,13 @@ description: | compatible: "zmk,animation-solid" +include: animation_base.yaml + properties: + label: + type: string + required: true + duration: type: int required: true From f50037dcbb47b54dc81bbacbff9c29d165119b11 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sat, 4 Dec 2021 17:13:40 +0100 Subject: [PATCH 07/34] Fix HSL->RGB conversion --- app/src/animation/color.c | 44 +++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/app/src/animation/color.c b/app/src/animation/color.c index 5681b667..53bec6e4 100644 --- a/app/src/animation/color.c +++ b/app/src/animation/color.c @@ -5,8 +5,24 @@ */ #include + #include +static float fmod(float a, float b) { + float mod = a < 0 ? -a : a; + float x = b < 0 ? -b : b; + + while (mod >= x) { + mod = mod - x; + } + + return a < 0 ? -mod : mod; +} + +static float fabs(float a) { + return a < 0 ? -a : a; +} + /** * HSL chosen over HSV/HSB as it shares the same parameters with LCh or HSLuv. * The latter color spaces could be interesting to experiment with because of their @@ -16,39 +32,45 @@ * Algorithm source: https://www.tlbx.app/color-converter */ void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb) { - // float a = hsl->h / 60; - uint8_t a = hsl->h / 60; - float chroma = hsl->s * (1 - abs(2 * hsl->l - 1)); - // I think 'a' actually needs to be a float here or this doesn't make sense. - // If uint, possible values are: (0,1), if float: (0...1) - float x = chroma * (1 - abs(a % 2 - 1)); - float m = hsl->l - chroma / 2; + float s = (float) hsl->s / 100; + float l = (float) hsl->l / 100; - switch (a) { + float a = (float) hsl->h / 60; + float chroma = s * (1 - fabs(2 * l - 1)); + float x = chroma * (1 - fabs(fmod(a, 2) - 1)); + float m = l - chroma / 2; + + switch ((uint8_t) a % 6) { case 0: rgb->r = m + chroma; rgb->g = m + x; rgb->b = m; + break; case 1: rgb->r = m + x; rgb->g = m + chroma; rgb->b = m; + break; case 2: rgb->r = m; rgb->g = m + chroma; rgb->b = m + x; + break; case 3: rgb->r = m; rgb->g = m + x; rgb->b = m + chroma; + break; case 4: rgb->r = m + x; rgb->g = m; rgb->b = m + chroma; + break; case 5: rgb->r = m + chroma; rgb->g = m; rgb->b = m + x; + break; } } @@ -56,9 +78,9 @@ void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb) * Converts ZMKs RGB (float) to Zephyr's led_rgb (uint8_t) format. */ void zmk_rgb_to_led_rgb(const struct zmk_color_rgb *rgb, struct led_rgb *led) { - led->r = rgb->r * 255; - led->g = rgb->g * 255; - led->b = rgb->b * 255; + led->r = (uint8_t) (rgb->r * 255); + led->g = (uint8_t) (rgb->g * 255); + led->b = (uint8_t) (rgb->b * 255); } /** From 105afca3d95adffc0448826e3a7540461073154d Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sat, 4 Dec 2021 17:22:13 +0100 Subject: [PATCH 08/34] Clean up the main animation module --- app/src/animation/animation.c | 91 ++++++++++++----------------------- 1 file changed, 31 insertions(+), 60 deletions(-) diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index c34dd9e2..e9c572f8 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -20,26 +20,31 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); -#define PHANDLE_TO_DEVICE(node_id, prop, idx) \ +// 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)), -#define PHANDLE_TO_CHAIN_LENGTH(node_id, prop, idx) \ +#define PHANDLE_TO_CHAIN_LENGTH(idx, node_id, prop) \ DT_PROP_BY_PHANDLE_IDX(node_id, prop, idx, chain_length), -#define PHANDLE_TO_PIXEL(node_id, prop, idx) \ - { \ - .animation = DEVICE_DT_GET(DT_PHA_BY_IDX(node_id, prop, idx, animation)), \ - .position = { \ - .x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \ - .y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \ - }, \ +#define PHANDLE_TO_PIXEL(idx, node_id, prop) \ + { \ + .animation = PHANDLE_TO_DEVICE(idx, node_id, prop) \ + .position = { \ + .x = DT_PHA_BY_IDX(node_id, prop, idx, position_x),\ + .y = DT_PHA_BY_IDX(node_id, prop, idx, position_y),\ + }, \ }, /** * LED Driver device pointers. */ static const struct device *drivers[] = { - DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE) + ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE) }; /** @@ -51,14 +56,14 @@ static const size_t drivers_size = DT_INST_PROP_LEN(0, drivers); * Array containing the number of LEDs handled by each device. */ static const uint8_t pixels_per_driver[] = { - DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH) + ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH) }; /** * Pointers to all active animation devices. */ static const struct device *animations[] = { - DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_DEVICE) + ZMK_DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_DEVICE) }; /** @@ -70,32 +75,32 @@ static const size_t animations_size = DT_INST_PROP_LEN(0, animations); * Pixel configuration. */ static const struct animation_pixel pixels[] = { - DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_PIXEL) + ZMK_DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL) }; /** * Size of the pixels array. */ -const size_t pixels_size = DT_INST_PROP_LEN(0, pixels); +static const size_t pixels_size = DT_INST_PROP_LEN(0, pixels); /** * Buffer for RGB values ready to be sent to the drivers. */ -struct led_rgb px_buffer[DT_INST_PROP_LEN(0, pixels)]; +static struct led_rgb px_buffer[DT_INST_PROP_LEN(0, pixels)]; -void zmk_animation_tick() { - for (size_t i = 0; i < ZMK_ANIMATIONS_LENGTH; ++i) { +static void zmk_animation_tick(struct k_work *work) { + for (size_t i = 0; i < animations_size; ++i) { animation_prep_next_frame(animations[i]); } for (size_t i = 0; i < pixels_size; ++i) { - zmk_color_rgb rgb = { + struct zmk_color_rgb rgb = { .r = 0, .g = 0, .b = 0, }; - animation_get_pixel(pixel.animation, &pixel.position, &rgb); + animation_get_pixel(pixels[i].animation, &pixels[i].position, &rgb); zmk_rgb_to_led_rgb(&rgb, &px_buffer[i]); } @@ -109,12 +114,10 @@ void zmk_animation_tick() { pixels_per_driver[i] ); - pixels_updated += pixels_per_driver; + pixels_updated += (size_t) pixels_per_driver; } } -// Set up timer here - K_WORK_DEFINE(animation_work, zmk_animation_tick); static void zmk_animation_tick_handler(struct k_timer *timer) { @@ -123,44 +126,12 @@ static void zmk_animation_tick_handler(struct k_timer *timer) { K_TIMER_DEFINE(animation_tick, zmk_animation_tick_handler, NULL); -// Init +static int zmk_animation_init(const struct device *dev) { + LOG_INF("ZMK Animation Ready"); -void zmk_animation_init(const struct device *dev) { - // default FPS: 24, 30 or 60? - k_timer_start(&animation_tick, K_NO_WAIT, K_MSEC(1000 / ZMK_ANIMATION_FPS)); + k_timer_start(&animation_tick, K_NO_WAIT, K_MSEC(1000 / CONFIG_ZMK_ANIMATION_FPS)); + + return 0; } -// Actually, all of this might be a little hard to represent inside a config file. -// We need: -// -// - Each animation has its own node -// -// - animations[] = phandle array of animations -// -// - pixel.pos = x,y coordinates of each pixels, could be a byte array -// -// - pixel.animations = Number array, but it's per pixel and it's not fixed length per pixel!! PROBLEM!!! -// - pixel.blending_modes = Could be a number array again. Use some macros. -// -// - unless we say there can only be 32 different animations and use uint32_t as a bitmask? -// - but then blending modes suck and you can't order them -// - -// ALTERNATIVE: -// -// Eventually we don't want to be using the device tree to set these up anyway. -// It should be possible to instantiate animations dynamically and the settings should be stored in flash. -// Maybe it's something to look into now? -// - -// ALTERNATIVE no2: -// -// Look at behaviors. -// It seems that pixels themselves could be 'drivers' ? -// -// That's probably best tbh - -// NOTE WHEN DEFINING PIXELS: -// See if it's maybe possible to re-use default_transform to assign pixels to keys? -// Assuming the first batch is always the keys which is not an easy assumption to make. -// Otherwise need it's own map +SYS_INIT(zmk_animation_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); From 8e5574ab497bac2f9793a6ddcf7c3fd8daf02d6c Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sat, 4 Dec 2021 17:23:43 +0100 Subject: [PATCH 09/34] Move animation_solid from drivers to zmk/src/animation --- app/CMakeLists.txt | 4 +- app/Kconfig | 14 +++ app/drivers/Kconfig | 1 - app/drivers/animation/CMakeLists.txt | 6 -- app/drivers/animation/Kconfig | 9 -- app/drivers/animation/solid_color.c | 130 ------------------------ app/src/animation/animation_solid.c | 144 +++++++++++++++++++++++++++ 7 files changed, 161 insertions(+), 147 deletions(-) delete mode 100644 app/drivers/animation/CMakeLists.txt delete mode 100644 app/drivers/animation/Kconfig delete mode 100644 app/drivers/animation/solid_color.c create mode 100644 app/src/animation/animation_solid.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d35ca3cb..3515dbf2 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -80,14 +80,16 @@ target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_bac target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/battery.c) -target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation.c) target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/color.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_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c) add_subdirectory(src/split) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c) target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c) +target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/behaviors/behavior_bt.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) target_sources(app PRIVATE src/main.c) diff --git a/app/Kconfig b/app/Kconfig index 1537bd61..9762d6c8 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -301,6 +301,20 @@ config ZMK_BACKLIGHT_AUTO_OFF_USB #ZMK_BACKLIGHT endif +config ZMK_ANIMATION + bool "RGB Animations" + select LED_STRIP + +if ZMK_ANIMATION + +config ZMK_ANIMATION_FPS + int "Animation FPS" + range 0 60 + default 30 + +#ZMK_ANIMATION +endif + #Display/LED Options endmenu diff --git a/app/drivers/Kconfig b/app/drivers/Kconfig index 0c6431b2..c57ed334 100644 --- a/app/drivers/Kconfig +++ b/app/drivers/Kconfig @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT rsource "gpio/Kconfig" -rsource "animation/Kconfig" rsource "kscan/Kconfig" rsource "sensor/Kconfig" rsource "display/Kconfig" diff --git a/app/drivers/animation/CMakeLists.txt b/app/drivers/animation/CMakeLists.txt deleted file mode 100644 index 8cb5a708..00000000 --- a/app/drivers/animation/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) 2020 The ZMK Contributors -# SPDX-License-Identifier: MIT - -zephyr_library() - -zephyr_library_sources(solid_color.c) diff --git a/app/drivers/animation/Kconfig b/app/drivers/animation/Kconfig deleted file mode 100644 index 2f1ac02a..00000000 --- a/app/drivers/animation/Kconfig +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2020 The ZMK Contributors -# SPDX-License-Identifier: MIT - -config ZMK_ANIMATION - bool "RGB Animations" - depends on I2C - depends on LED_STRIP - help - Enable RGB animations. diff --git a/app/drivers/animation/solid_color.c b/app/drivers/animation/solid_color.c deleted file mode 100644 index d86b77ed..00000000 --- a/app/drivers/animation/solid_color.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2020 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#define DT_DRV_COMPAT zmk_colors_animation - -#include - -struct solid_color_animation_config config { - zmk_color_hsl[] colors; - uint8_t num_colors; - uint16_t duration; - uint16_t transition_duration; -}; - -struct solid_color_animation_data data { - bool has_changed; - - uint16_t counter; - - zmk_color_hsl current_hsl; - zmk_color_rgb current_rgb; -}; - -// or just colors array ? And if just one it stays the same otherwise it just chagnes color. genius - -static void colors_animation_prep_next_frame(const struct device *dev) { - const struct solid_color_animation_config *config = dev->config; - const struct solid_color_animation_data *data = dev->data; - - // Animation only contains a single color, nothing to do - if (config->num_colors == 1) { - return; - } - - const size_t from = data->counter / config->transition_duration; - const size_t to = (from + 1) % config->num_colors; - - struct zmk_color_hsl next_hsl; - - zmk_interpolate_hsl( - &config->colors[from], - &config->colors[to], - &next_hsl, - (data->counter % config->transition_duration) / (float) config->transition_duration - ); - - data->has_changed = !zmk_cmp_hsl(&data->current_hsl, &next_hsl); - - data->current_hsl = next_hsl; - zmk_hsl_to_rgb(data->current_rgb, data->current_hsl); - - data->counter = (data->counter + 1) % config.duration; -} - -static void colors_animation_solid(const struct device *dev, animation_pixel *pixel) { - const struct solid_color_animation_config config = dev->config; - - pixel->rgb = data->current_rgb; -} - -static void animation_solid_color_init(const struct device *dev) { - const struct solid_color_animation_config config = dev->config; - - // Actually... I'm not sure fi there's anything to set up here, is there? - // But I do need a public function for has_changed -} - -// #define ANIMATION_SOLID_COLOR_DEVICE(idx) -// - - -// use union type to cast from uint_32t to struct { uint16_t, uint8_t, uint8_t } ? - -static const struct animation_api solid_color_animation_api = { - .prep_next_frame = solid_color_animation_prep_next_frame, - .get_pixel = solid_color_animation_get_pixel, -}; - -static struct solid_color_animation_data solid_color_animation_##idx##_data; - -static uint32_t solid_color_animation_##idx##_colors[DT_INST_PROP_LEN(idx, colors)] = DT_INST_PROP(idx, colors); - -static struct solid_color_animation_config solid_color_animation_##idx##_config = { - .colors = (struct zmk_color_hsl *) solid_color_animation_##idx##_colors, - .num_colors = DT_INST_PROP_LEN(idx, colors), - .duration = DT_INST_PROP(idx, duration), - .transition_duration = DT_INST_PROP(idx, duration) / DT_INST_PROP_LEN(idx, colors), -}; - - - -// To do: -// -// STEP 1: single animation -// - Start with a single animation, just color -// - Add layer for taking the output from here and putting it to the led strip -// - Make it work -// -// STEP 2: areas, in fact, instead of defining them explicitly we can just use appropriate x,y coordinates and animation. -// - Split keyboard in two independent areas -// - Make it work -// -// STEP 3: add additional animation effects -// - Basically, carry over rgb_underglow. -// - Make it work -// -// STEP 4: add animation triggers -// - Allow an animation to be triggered by behaviors or key-presses -// - Make it work -// -// STEP 5: add animation layers and a MULTIPLY mode (again, opacity would be set on individual pixels so... that affects some optimizations I guess) -// - Normal mode: overrides layers below -// - Multiply mode: auguments whatever is below (opacity, whatever) -// -// Voila! Animation composition! -// -// STEP 6, BONUS!: -// - Figure out a way to switch animations during runtime? -// -// Notes: -// - Any animation settings go into 'driver' config & data, so they can be updated at runtime. -// - Main limitation is space, so the amount of different animations one can have loaded -// -// More notes: -// - Solid color would be one animation (just transitions between colors) -// - Gradient (SPECTRUM) would be another, you choose how they're distributed accross the keys and if they move? -// - Effects like 'breathe' can be implemented by specifying #000 as one of the colors or using a multiply layer? diff --git a/app/src/animation/animation_solid.c b/app/src/animation/animation_solid.c new file mode 100644 index 00000000..6d485dce --- /dev/null +++ b/app/src/animation/animation_solid.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_animation_solid + +#include +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct animation_solid_config { + struct zmk_color_hsl *colors; + uint8_t num_colors; + uint16_t duration; + uint16_t transition_duration; +}; + +struct animation_solid_data { + bool has_changed; + + uint16_t counter; + + struct zmk_color_hsl current_hsl; + struct zmk_color_rgb current_rgb; +}; + +static void animation_solid_prep_next_frame(const struct device *dev) { + const struct animation_solid_config *config = dev->config; + struct animation_solid_data *data = dev->data; + + // Animation only contains a single color, nothing to do + if (config->num_colors == 1) { + return; + } + + const size_t from = data->counter / config->transition_duration; + const size_t to = (from + 1) % config->num_colors; + + struct zmk_color_hsl next_hsl; + + zmk_interpolate_hsl( + &config->colors[from], + &config->colors[to], + &next_hsl, + (data->counter % config->transition_duration) / (float) config->transition_duration + ); + + data->has_changed = !zmk_cmp_hsl(&data->current_hsl, &next_hsl); + + data->current_hsl = next_hsl; + zmk_hsl_to_rgb(&data->current_hsl, &data->current_rgb); + + data->counter = (data->counter + 1) % config->duration; +} + +static void animation_solid_get_pixel(const struct device *dev, const struct animation_pixel_position *position, + struct zmk_color_rgb *value) { + const struct animation_solid_data *data = dev->data; + + value->r = data->current_rgb.r; + value->g = data->current_rgb.g; + value->b = data->current_rgb.b; +} + +static int animation_solid_init(const struct device *dev) { + const struct animation_solid_config *config = dev->config; + struct animation_solid_data *data = dev->data; + + data->counter = 0; + data->current_hsl = config->colors[0]; + + zmk_hsl_to_rgb(&data->current_hsl, &data->current_rgb); + + return 0; +} + +static const struct animation_api animation_solid_api = { + .prep_next_frame = animation_solid_prep_next_frame, + .get_pixel = animation_solid_get_pixel, +}; + +#define ANIMATION_SOLID_DEVICE(idx) \ + \ + static struct animation_solid_data animation_solid_##idx##_data; \ + \ + static uint32_t animation_solid_##idx##_colors[DT_INST_PROP_LEN(idx, colors)] = DT_INST_PROP(idx, colors); \ + \ + static struct animation_solid_config animation_solid_##idx##_config = { \ + .colors = (struct zmk_color_hsl *) animation_solid_##idx##_colors, \ + .num_colors = DT_INST_PROP_LEN(idx, colors), \ + .duration = DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS, \ + .transition_duration = (DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS) / DT_INST_PROP_LEN(idx, colors), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(idx, &animation_solid_init, NULL, &animation_solid_##idx##_data, \ + &animation_solid_##idx##_config, POST_KERNEL, CONFIG_LED_STRIP_INIT_PRIORITY, \ + &animation_solid_api); + +DT_INST_FOREACH_STATUS_OKAY(ANIMATION_SOLID_DEVICE); + + +// To do: +// +// STEP 1: single animation +// - Start with a single animation, just color +// - Add layer for taking the output from here and putting it to the led strip +// - Make it work +// +// STEP 2: areas, in fact, instead of defining them explicitly we can just use appropriate x,y coordinates and animation. +// - Split keyboard in two independent areas +// - Make it work +// +// STEP 3: add additional animation effects +// - Basically, carry over rgb_underglow. +// - Make it work +// +// STEP 4: add animation triggers +// - Allow an animation to be triggered by behaviors or key-presses +// - Make it work +// +// STEP 5: add animation layers and a MULTIPLY mode (again, opacity would be set on individual pixels so... that affects some optimizations I guess) +// - Normal mode: overrides layers below +// - Multiply mode: auguments whatever is below (opacity, whatever) +// +// Voila! Animation composition! +// +// STEP 6, BONUS!: +// - Figure out a way to switch animations during runtime? +// +// Notes: +// - Any animation settings go into 'driver' config & data, so they can be updated at runtime. +// - Main limitation is space, so the amount of different animations one can have loaded +// +// More notes: +// - Solid color would be one animation (just transitions between colors) +// - Gradient (SPECTRUM) would be another, you choose how they're distributed accross the keys and if they move? +// - Effects like 'breathe' can be implemented by specifying #000 as one of the colors or using a multiply layer? From 2dfec75038751af231711f755ad0135da8939d44 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sat, 4 Dec 2021 19:49:56 +0100 Subject: [PATCH 10/34] Fix formatting --- app/include/drivers/animation.h | 12 ++-- app/include/zmk/animation.h | 24 +++---- app/src/animation/animation.c | 88 ++++++++++------------- app/src/animation/animation_solid.c | 86 +++++++--------------- app/src/animation/color.c | 107 ++++++++++++++-------------- 5 files changed, 135 insertions(+), 182 deletions(-) diff --git a/app/include/drivers/animation.h b/app/include/drivers/animation.h index 45e80524..0d47f13a 100644 --- a/app/include/drivers/animation.h +++ b/app/include/drivers/animation.h @@ -37,7 +37,8 @@ typedef void (*animation_api_prep_next_frame)(const struct device *dev); * * @see animation_prep_next_frame() for argument descriptions. */ -typedef void (*animation_api_get_pixel)(const struct device *dev, const struct animation_pixel_position *pixel_position, +typedef void (*animation_api_get_pixel)(const struct device *dev, + const struct animation_pixel_position *pixel_position, struct zmk_color_rgb *value); struct animation_api { @@ -50,7 +51,7 @@ struct animation_api { * @param dev [description] */ static inline void animation_prep_next_frame(const struct device *dev) { - const struct animation_api *api = (const struct animation_api *) dev->api; + const struct animation_api *api = (const struct animation_api *)dev->api; return api->prep_next_frame(dev); } @@ -60,9 +61,10 @@ static inline void animation_prep_next_frame(const struct device *dev) { * @param dev [description] * @param pixel [description] */ -static inline void animation_get_pixel(const struct device *dev, const struct animation_pixel_position *pixel_position, - struct zmk_color_rgb *value) { - const struct animation_api *api = (const struct animation_api *) dev->api; +static inline void animation_get_pixel(const struct device *dev, + const struct animation_pixel_position *pixel_position, + struct zmk_color_rgb *value) { + const struct animation_api *api = (const struct animation_api *)dev->api; return api->get_pixel(dev, pixel_position, value); } diff --git a/app/include/zmk/animation.h b/app/include/zmk/animation.h index df488851..64bffd8e 100644 --- a/app/include/zmk/animation.h +++ b/app/include/zmk/animation.h @@ -34,8 +34,8 @@ struct zmk_color_hsl { /** * Converts color from HSL to RGB. * - * @param hsl [description] - * @param rgb [description] + * @param hsl Color to convert + * @param rgb Converted color */ void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb); @@ -43,17 +43,17 @@ void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb); * Converts the internal RGB representation into a led_rgb struct * for use with led_strip drivers. * - * @param rgb [description] - * @param led [description] + * @param rgb Color to convert + * @param led Converted color */ void zmk_rgb_to_led_rgb(const struct zmk_color_rgb *rgb, struct led_rgb *led); /** * Returns true if two HSL colors are the same. * - * @param a [description] - * @param b [description] - * @return [description] + * @param a HSL color to compare + * @param b HSL color to compare + * @return True when colors share the same values */ bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b); @@ -61,10 +61,10 @@ bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b); * Perform linear interpolation between HSL values of two colors * at a given distance (step) and store the resulting value in the given pointer. * - * @param from [description] - * @param to [description] - * @param result [description] - * @param step [description] + * @param from HSL color to interpolate + * @param to HSL color to interpolate + * @param result Resulting HSL color + * @param step Interpolation step */ void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to, - struct zmk_color_hsl *result, float step); + struct zmk_color_hsl *result, float step); diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index e9c572f8..a94f7a20 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -22,30 +22,29 @@ 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 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)), +#define PHANDLE_TO_DEVICE(idx, node_id, prop) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), -#define PHANDLE_TO_CHAIN_LENGTH(idx, node_id, prop) \ - DT_PROP_BY_PHANDLE_IDX(node_id, prop, idx, chain_length), +#define PHANDLE_TO_CHAIN_LENGTH(idx, node_id, prop) \ + DT_PROP_BY_PHANDLE_IDX(node_id, prop, idx, chain_length), -#define PHANDLE_TO_PIXEL(idx, node_id, prop) \ - { \ - .animation = PHANDLE_TO_DEVICE(idx, node_id, prop) \ - .position = { \ - .x = DT_PHA_BY_IDX(node_id, prop, idx, position_x),\ - .y = DT_PHA_BY_IDX(node_id, prop, idx, position_y),\ - }, \ - }, +#define PHANDLE_TO_PIXEL(idx, node_id, prop) \ + { \ + .animation = DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), \ + .position = \ + { \ + .x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \ + .y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \ + }, \ + }, /** * LED Driver device pointers. */ static const struct device *drivers[] = { - ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE) -}; + ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE)}; /** * Size of the LED driver device pointers array. @@ -56,15 +55,13 @@ static const size_t drivers_size = DT_INST_PROP_LEN(0, drivers); * Array containing the number of LEDs handled by each device. */ static const uint8_t pixels_per_driver[] = { - ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH) -}; + ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH)}; /** * Pointers to all active animation devices. */ static const struct device *animations[] = { - ZMK_DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_DEVICE) -}; + ZMK_DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_DEVICE)}; /** * Size of the animation device pointers array. @@ -75,8 +72,7 @@ static const size_t animations_size = DT_INST_PROP_LEN(0, animations); * Pixel configuration. */ static const struct animation_pixel pixels[] = { - ZMK_DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL) -}; + ZMK_DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL)}; /** * Size of the pixels array. @@ -89,49 +85,43 @@ static const size_t pixels_size = DT_INST_PROP_LEN(0, pixels); static struct led_rgb px_buffer[DT_INST_PROP_LEN(0, pixels)]; static void zmk_animation_tick(struct k_work *work) { - for (size_t i = 0; i < animations_size; ++i) { - animation_prep_next_frame(animations[i]); - } + for (size_t i = 0; i < animations_size; ++i) { + animation_prep_next_frame(animations[i]); + } - for (size_t i = 0; i < pixels_size; ++i) { - struct zmk_color_rgb rgb = { - .r = 0, - .g = 0, - .b = 0, - }; + for (size_t i = 0; i < pixels_size; ++i) { + struct zmk_color_rgb rgb = { + .r = 0, + .g = 0, + .b = 0, + }; - animation_get_pixel(pixels[i].animation, &pixels[i].position, &rgb); + animation_get_pixel(pixels[i].animation, &pixels[i].position, &rgb); - zmk_rgb_to_led_rgb(&rgb, &px_buffer[i]); - } + zmk_rgb_to_led_rgb(&rgb, &px_buffer[i]); + } - size_t pixels_updated = 0; + size_t pixels_updated = 0; - for (size_t i = 0; i < drivers_size; ++i) { - led_strip_update_rgb( - drivers[i], - &px_buffer[pixels_updated], - pixels_per_driver[i] - ); + for (size_t i = 0; i < drivers_size; ++i) { + led_strip_update_rgb(drivers[i], &px_buffer[pixels_updated], pixels_per_driver[i]); - pixels_updated += (size_t) pixels_per_driver; - } + pixels_updated += (size_t)pixels_per_driver; + } } K_WORK_DEFINE(animation_work, zmk_animation_tick); -static void zmk_animation_tick_handler(struct k_timer *timer) { - k_work_submit(&animation_work); -} +static void zmk_animation_tick_handler(struct k_timer *timer) { k_work_submit(&animation_work); } K_TIMER_DEFINE(animation_tick, zmk_animation_tick_handler, NULL); static int zmk_animation_init(const struct device *dev) { - LOG_INF("ZMK Animation Ready"); + LOG_INF("ZMK Animation Ready"); - k_timer_start(&animation_tick, K_NO_WAIT, K_MSEC(1000 / CONFIG_ZMK_ANIMATION_FPS)); + k_timer_start(&animation_tick, K_NO_WAIT, K_MSEC(1000 / CONFIG_ZMK_ANIMATION_FPS)); - return 0; + return 0; } SYS_INIT(zmk_animation_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/animation/animation_solid.c b/app/src/animation/animation_solid.c index 6d485dce..6512b427 100644 --- a/app/src/animation/animation_solid.c +++ b/app/src/animation/animation_solid.c @@ -45,12 +45,9 @@ static void animation_solid_prep_next_frame(const struct device *dev) { struct zmk_color_hsl next_hsl; - zmk_interpolate_hsl( - &config->colors[from], - &config->colors[to], - &next_hsl, - (data->counter % config->transition_duration) / (float) config->transition_duration - ); + zmk_interpolate_hsl(&config->colors[from], &config->colors[to], &next_hsl, + (data->counter % config->transition_duration) / + (float)config->transition_duration); data->has_changed = !zmk_cmp_hsl(&data->current_hsl, &next_hsl); @@ -60,8 +57,9 @@ static void animation_solid_prep_next_frame(const struct device *dev) { data->counter = (data->counter + 1) % config->duration; } -static void animation_solid_get_pixel(const struct device *dev, const struct animation_pixel_position *position, - struct zmk_color_rgb *value) { +static void animation_solid_get_pixel(const struct device *dev, + const struct animation_pixel_position *position, + struct zmk_color_rgb *value) { const struct animation_solid_data *data = dev->data; value->r = data->current_rgb.r; @@ -86,59 +84,23 @@ static const struct animation_api animation_solid_api = { .get_pixel = animation_solid_get_pixel, }; -#define ANIMATION_SOLID_DEVICE(idx) \ - \ - static struct animation_solid_data animation_solid_##idx##_data; \ - \ - static uint32_t animation_solid_##idx##_colors[DT_INST_PROP_LEN(idx, colors)] = DT_INST_PROP(idx, colors); \ - \ - static struct animation_solid_config animation_solid_##idx##_config = { \ - .colors = (struct zmk_color_hsl *) animation_solid_##idx##_colors, \ - .num_colors = DT_INST_PROP_LEN(idx, colors), \ - .duration = DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS, \ - .transition_duration = (DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS) / DT_INST_PROP_LEN(idx, colors), \ - }; \ - \ - DEVICE_DT_INST_DEFINE(idx, &animation_solid_init, NULL, &animation_solid_##idx##_data, \ - &animation_solid_##idx##_config, POST_KERNEL, CONFIG_LED_STRIP_INIT_PRIORITY, \ - &animation_solid_api); +#define ANIMATION_SOLID_DEVICE(idx) \ + \ + static struct animation_solid_data animation_solid_##idx##_data; \ + \ + static uint32_t animation_solid_##idx##_colors[DT_INST_PROP_LEN(idx, colors)] = \ + DT_INST_PROP(idx, colors); \ + \ + static struct animation_solid_config animation_solid_##idx##_config = { \ + .colors = (struct zmk_color_hsl *)animation_solid_##idx##_colors, \ + .num_colors = DT_INST_PROP_LEN(idx, colors), \ + .duration = DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS, \ + .transition_duration = (DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS) / \ + DT_INST_PROP_LEN(idx, colors), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(idx, &animation_solid_init, NULL, &animation_solid_##idx##_data, \ + &animation_solid_##idx##_config, POST_KERNEL, \ + CONFIG_APPLICATION_INIT_PRIORITY, &animation_solid_api); DT_INST_FOREACH_STATUS_OKAY(ANIMATION_SOLID_DEVICE); - - -// To do: -// -// STEP 1: single animation -// - Start with a single animation, just color -// - Add layer for taking the output from here and putting it to the led strip -// - Make it work -// -// STEP 2: areas, in fact, instead of defining them explicitly we can just use appropriate x,y coordinates and animation. -// - Split keyboard in two independent areas -// - Make it work -// -// STEP 3: add additional animation effects -// - Basically, carry over rgb_underglow. -// - Make it work -// -// STEP 4: add animation triggers -// - Allow an animation to be triggered by behaviors or key-presses -// - Make it work -// -// STEP 5: add animation layers and a MULTIPLY mode (again, opacity would be set on individual pixels so... that affects some optimizations I guess) -// - Normal mode: overrides layers below -// - Multiply mode: auguments whatever is below (opacity, whatever) -// -// Voila! Animation composition! -// -// STEP 6, BONUS!: -// - Figure out a way to switch animations during runtime? -// -// Notes: -// - Any animation settings go into 'driver' config & data, so they can be updated at runtime. -// - Main limitation is space, so the amount of different animations one can have loaded -// -// More notes: -// - Solid color would be one animation (just transitions between colors) -// - Gradient (SPECTRUM) would be another, you choose how they're distributed accross the keys and if they move? -// - Effects like 'breathe' can be implemented by specifying #000 as one of the colors or using a multiply layer? diff --git a/app/src/animation/color.c b/app/src/animation/color.c index 53bec6e4..25892dc4 100644 --- a/app/src/animation/color.c +++ b/app/src/animation/color.c @@ -9,98 +9,97 @@ #include static float fmod(float a, float b) { - float mod = a < 0 ? -a : a; - float x = b < 0 ? -b : b; + float mod = a < 0 ? -a : a; + float x = b < 0 ? -b : b; - while (mod >= x) { - mod = mod - x; - } + while (mod >= x) { + mod = mod - x; + } - return a < 0 ? -mod : mod; + return a < 0 ? -mod : mod; } -static float fabs(float a) { - return a < 0 ? -a : a; -} +static float fabs(float a) { return a < 0 ? -a : a; } /** * HSL chosen over HSV/HSB as it shares the same parameters with LCh or HSLuv. * The latter color spaces could be interesting to experiment with because of their * perceptual uniformity, but it would come at the cost of some performance. - * Using the same parameters would make it easy to toggle any such behavior using a single config flag. + * Using the same parameters would make it easy to toggle any such behavior + * using a single config flag. * * Algorithm source: https://www.tlbx.app/color-converter */ void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb) { - float s = (float) hsl->s / 100; - float l = (float) hsl->l / 100; + float s = (float)hsl->s / 100; + float l = (float)hsl->l / 100; - float a = (float) hsl->h / 60; - float chroma = s * (1 - fabs(2 * l - 1)); - float x = chroma * (1 - fabs(fmod(a, 2) - 1)); - float m = l - chroma / 2; + float a = (float)hsl->h / 60; + float chroma = s * (1 - fabs(2 * l - 1)); + float x = chroma * (1 - fabs(fmod(a, 2) - 1)); + float m = l - chroma / 2; - switch ((uint8_t) a % 6) { - case 0: - rgb->r = m + chroma; - rgb->g = m + x; - rgb->b = m; - break; - case 1: - rgb->r = m + x; - rgb->g = m + chroma; - rgb->b = m; - break; - case 2: - rgb->r = m; - rgb->g = m + chroma; - rgb->b = m + x; - break; - case 3: - rgb->r = m; - rgb->g = m + x; - rgb->b = m + chroma; - break; - case 4: - rgb->r = m + x; - rgb->g = m; - rgb->b = m + chroma; - break; - case 5: - rgb->r = m + chroma; - rgb->g = m; - rgb->b = m + x; - break; - } + switch ((uint8_t)a % 6) { + case 0: + rgb->r = m + chroma; + rgb->g = m + x; + rgb->b = m; + break; + case 1: + rgb->r = m + x; + rgb->g = m + chroma; + rgb->b = m; + break; + case 2: + rgb->r = m; + rgb->g = m + chroma; + rgb->b = m + x; + break; + case 3: + rgb->r = m; + rgb->g = m + x; + rgb->b = m + chroma; + break; + case 4: + rgb->r = m + x; + rgb->g = m; + rgb->b = m + chroma; + break; + case 5: + rgb->r = m + chroma; + rgb->g = m; + rgb->b = m + x; + break; + } } /** * Converts ZMKs RGB (float) to Zephyr's led_rgb (uint8_t) format. */ void zmk_rgb_to_led_rgb(const struct zmk_color_rgb *rgb, struct led_rgb *led) { - led->r = (uint8_t) (rgb->r * 255); - led->g = (uint8_t) (rgb->g * 255); - led->b = (uint8_t) (rgb->b * 255); + led->r = rgb->r * 255; + led->g = rgb->g * 255; + led->b = rgb->b * 255; } /** * Compares two HSL colors. */ bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b) { - return a->h == b->h && a->s == b->s && a->l == b->l; + return a->h == b->h && a->s == b->s && a->l == b->l; } /** * Interpolate between two colors using the cylindrical model (HSL). */ void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to, - struct zmk_color_hsl *result, float step) { + struct zmk_color_hsl *result, float step) { int16_t hue_delta; hue_delta = from->h - to->h; hue_delta = hue_delta + (180 < abs(hue_delta) ? (hue_delta < 0 ? 360 : -360) : 0); - result->h = (uint16_t) (360 + from->h - (hue_delta * step)) % 360; + result->h = (uint16_t)(360 + from->h - (hue_delta * step)) % 360; result->s = from->s - (from->s - to->s) * step; result->l = from->l - (from->l - to->l) * step; } From cc70d7523825e010f934824cb3e0b0e4557448ae Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sat, 11 Dec 2021 17:57:46 +0100 Subject: [PATCH 11/34] Refactor the animation api to allow for pre- and post-frame hooks --- app/include/drivers/animation.h | 69 +++++++++++++++++++---------- app/include/zmk/animation.h | 10 ----- app/src/animation/animation.c | 16 ++++--- app/src/animation/animation_solid.c | 13 +++--- 4 files changed, 62 insertions(+), 46 deletions(-) diff --git a/app/include/drivers/animation.h b/app/include/drivers/animation.h index 0d47f13a..cba8232b 100644 --- a/app/include/drivers/animation.h +++ b/app/include/drivers/animation.h @@ -9,6 +9,7 @@ #include #include #include + #include /** @@ -19,17 +20,33 @@ * for various types of 2D animations. */ +struct animation_pixel { + const size_t id; + const uint8_t position_x; + const uint8_t position_y; + + const struct device *animation; +}; + #ifdef __cplusplus extern "C" { #endif /** * @typedef animation_api_prep_next_frame - * @brief Callback API for generating the next animation frame + * @brief Callback run before every frame is rendered. * - * @see animation_prep_next_frame() for argument descriptions. + * @see animation_api_before_frame() for argument descriptions. */ -typedef void (*animation_api_prep_next_frame)(const struct device *dev); +typedef void (*animation_api_on_before_frame)(const struct device *dev); + +/** + * @typedef animation_api_prep_next_frame + * @brief Callback run after every frame is rendered. + * + * @see animation_api_before_frame() for argument descriptions. + */ +typedef void (*animation_api_on_after_frame)(const struct device *dev); /** * @typedef animation_api_prep_next_frame @@ -37,36 +54,42 @@ typedef void (*animation_api_prep_next_frame)(const struct device *dev); * * @see animation_prep_next_frame() for argument descriptions. */ -typedef void (*animation_api_get_pixel)(const struct device *dev, - const struct animation_pixel_position *pixel_position, - struct zmk_color_rgb *value); +typedef void (*animation_api_render_pixel)(const struct device *dev, + const struct animation_pixel *pixel, + struct zmk_color_rgb *value); struct animation_api { - animation_api_prep_next_frame prep_next_frame; - animation_api_get_pixel get_pixel; + animation_api_on_before_frame on_before_frame; + animation_api_on_after_frame on_after_frame; + animation_api_render_pixel render_pixel; }; -/** - * [animation_prep_next_frame description] - * @param dev [description] - */ -static inline void animation_prep_next_frame(const struct device *dev) { +static inline void animation_on_before_frame(const struct device *dev) { const struct animation_api *api = (const struct animation_api *)dev->api; - return api->prep_next_frame(dev); + if (api->on_before_frame == NULL) { + return; + } + + api->on_before_frame(dev); } -/** - * [animation_get_pixel description] - * @param dev [description] - * @param pixel [description] - */ -static inline void animation_get_pixel(const struct device *dev, - const struct animation_pixel_position *pixel_position, - struct zmk_color_rgb *value) { +static inline void animation_on_after_frame(const struct device *dev) { const struct animation_api *api = (const struct animation_api *)dev->api; - return api->get_pixel(dev, pixel_position, value); + if (api->on_after_frame == NULL) { + return; + } + + api->on_after_frame(dev); +} + +static inline void animation_render_pixel(const struct device *dev, + const struct animation_pixel *pixel, + struct zmk_color_rgb *value) { + const struct animation_api *api = (const struct animation_api *)dev->api; + + return api->render_pixel(dev, pixel, value); } #ifdef __cplusplus diff --git a/app/include/zmk/animation.h b/app/include/zmk/animation.h index 64bffd8e..a2dae1c1 100644 --- a/app/include/zmk/animation.h +++ b/app/include/zmk/animation.h @@ -9,16 +9,6 @@ #include #include -struct animation_pixel_position { - const uint8_t x; - const uint8_t y; -}; - -struct animation_pixel { - const struct device *animation; - const struct animation_pixel_position position; -}; - struct zmk_color_rgb { float r; float g; diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index a94f7a20..e4c3df58 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -32,12 +32,10 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define PHANDLE_TO_PIXEL(idx, node_id, prop) \ { \ + .id = idx, \ + .position_x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \ + .position_y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \ .animation = DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), \ - .position = \ - { \ - .x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \ - .y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \ - }, \ }, /** @@ -86,7 +84,7 @@ static struct led_rgb px_buffer[DT_INST_PROP_LEN(0, pixels)]; static void zmk_animation_tick(struct k_work *work) { for (size_t i = 0; i < animations_size; ++i) { - animation_prep_next_frame(animations[i]); + animation_on_before_frame(animations[i]); } for (size_t i = 0; i < pixels_size; ++i) { @@ -96,7 +94,7 @@ static void zmk_animation_tick(struct k_work *work) { .b = 0, }; - animation_get_pixel(pixels[i].animation, &pixels[i].position, &rgb); + animation_render_pixel(pixels[i].animation, &pixels[i], &rgb); zmk_rgb_to_led_rgb(&rgb, &px_buffer[i]); } @@ -108,6 +106,10 @@ static void zmk_animation_tick(struct k_work *work) { pixels_updated += (size_t)pixels_per_driver; } + + for (size_t i = 0; i < animations_size; ++i) { + animation_on_after_frame(animations[i]); + } } K_WORK_DEFINE(animation_work, zmk_animation_tick); diff --git a/app/src/animation/animation_solid.c b/app/src/animation/animation_solid.c index 6512b427..f49041ab 100644 --- a/app/src/animation/animation_solid.c +++ b/app/src/animation/animation_solid.c @@ -31,7 +31,7 @@ struct animation_solid_data { struct zmk_color_rgb current_rgb; }; -static void animation_solid_prep_next_frame(const struct device *dev) { +static void animation_solid_on_before_frame(const struct device *dev) { const struct animation_solid_config *config = dev->config; struct animation_solid_data *data = dev->data; @@ -57,9 +57,9 @@ static void animation_solid_prep_next_frame(const struct device *dev) { data->counter = (data->counter + 1) % config->duration; } -static void animation_solid_get_pixel(const struct device *dev, - const struct animation_pixel_position *position, - struct zmk_color_rgb *value) { +static void animation_solid_render_pixel(const struct device *dev, + const struct animation_pixel *pixel, + struct zmk_color_rgb *value) { const struct animation_solid_data *data = dev->data; value->r = data->current_rgb.r; @@ -80,8 +80,9 @@ static int animation_solid_init(const struct device *dev) { } static const struct animation_api animation_solid_api = { - .prep_next_frame = animation_solid_prep_next_frame, - .get_pixel = animation_solid_get_pixel, + .on_before_frame = animation_solid_on_before_frame, + .on_after_frame = NULL, + .render_pixel = animation_solid_render_pixel, }; #define ANIMATION_SOLID_DEVICE(idx) \ From df35aa554748aab041f1c017297da8b01858a176 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sun, 12 Dec 2021 08:36:56 +0100 Subject: [PATCH 12/34] Implement helpers for key pixel position and pixel distance --- app/Kconfig | 4 +++ app/dts/bindings/zmk,animation.yaml | 9 +++++++ app/include/zmk/animation.h | 12 +++++++++ app/src/animation/animation.c | 38 +++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/app/Kconfig b/app/Kconfig index 9762d6c8..354c0b36 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -312,6 +312,10 @@ config ZMK_ANIMATION_FPS range 0 60 default 30 +config ZMK_ANIMATION_PIXEL_DISTANCE + bool "Generate a lookup table for distances between pixels" + default y + #ZMK_ANIMATION endif diff --git a/app/dts/bindings/zmk,animation.yaml b/app/dts/bindings/zmk,animation.yaml index 664de459..a2ad35b7 100644 --- a/app/dts/bindings/zmk,animation.yaml +++ b/app/dts/bindings/zmk,animation.yaml @@ -26,3 +26,12 @@ properties: This field contains the pixel configuration for the entire board. The order of this array determines in what order pixels are sent to the driver device API. If multiple driving devices are used, their chain-length property determines the size of the buffer for each device. + + key-pixels: + type: array + required: false + description: | + Use this field to specify the pixel index corresponding to each key + following the order used in your keymap. + When left unspecified, the driver assumes that for every key, the pixel has a matching id. + So for N keys, the first N pixels are exactly in the same order as keys in your keymap. diff --git a/app/include/zmk/animation.h b/app/include/zmk/animation.h index a2dae1c1..aeede8ea 100644 --- a/app/include/zmk/animation.h +++ b/app/include/zmk/animation.h @@ -21,6 +21,18 @@ struct zmk_color_hsl { uint8_t l; }; +#if DT_NODE_HAS_PROP(DT_INST(0, animation), key_position) +size_t zmk_animation_get_pixel_by_key_position(size_t key_position); +#else +static inline size_t zmk_animation_get_pixel_by_key_position(size_t key_position) { + return key_position; +} +#endif + +#if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1) +uint16_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx); +#endif + /** * Converts color from HSL to RGB. * diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index e4c3df58..7fcece19 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -11,6 +11,9 @@ #include #include +#include +#include + #include #include @@ -82,6 +85,31 @@ static const size_t pixels_size = DT_INST_PROP_LEN(0, pixels); */ static struct led_rgb px_buffer[DT_INST_PROP_LEN(0, pixels)]; +/** + * Conditional implementation of zmk_animation_get_pixel_by_key_position + * if key-pixels is set. + */ +#if DT_INST_NODE_HAS_PROP(0, key_position) +static const uint8_t pixels_by_key_position[] = DT_INST_PROP(0, key_pixels); + +size_t zmk_animation_get_pixel_by_key_position(size_t key_position) { + return pixels_by_key_position[key_position]; +} +#endif + +#if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1) + +/** + * Lookup table for distance between any two pixels. + */ +static uint16_t pixel_distance[DT_INST_PROP_LEN(0, pixels)][DT_INST_PROP_LEN(0, pixels)]; + +uint16_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx) { + return pixel_distance[pixel_idx][other_pixel_idx]; +} + +#endif + static void zmk_animation_tick(struct k_work *work) { for (size_t i = 0; i < animations_size; ++i) { animation_on_before_frame(animations[i]); @@ -119,6 +147,16 @@ static void zmk_animation_tick_handler(struct k_timer *timer) { k_work_submit(&a K_TIMER_DEFINE(animation_tick, zmk_animation_tick_handler, NULL); static int zmk_animation_init(const struct device *dev) { +#if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1) + // Prefill the pixel distance lookup table + for (size_t i = 0; i < pixels_size; ++i) { + for (size_t j = 0; j < pixels_size; ++j) { + pixel_distance[i][j] = sqrt(pow(pixels[i].position_x - pixels[j].position_x, 2) + + pow(pixels[i].position_y - pixels[j].position_y, 2)); + } + } +#endif + LOG_INF("ZMK Animation Ready"); k_timer_start(&animation_tick, K_NO_WAIT, K_MSEC(1000 / CONFIG_ZMK_ANIMATION_FPS)); From 30e0903d8eefcb17f17edd939f6398e2f26cfe06 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sun, 12 Dec 2021 08:44:18 +0100 Subject: [PATCH 13/34] Implement a 'ripple' per-key effect animation --- app/CMakeLists.txt | 1 + .../animations/zmk,animation-ripple.yaml | 46 +++++ app/src/animation/animation_ripple.c | 174 ++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 app/dts/bindings/animations/zmk,animation-ripple.yaml create mode 100644 app/src/animation/animation_ripple.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3515dbf2..1ee31865 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -81,6 +81,7 @@ target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed 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_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) diff --git a/app/dts/bindings/animations/zmk,animation-ripple.yaml b/app/dts/bindings/animations/zmk,animation-ripple.yaml new file mode 100644 index 00000000..e611a6f5 --- /dev/null +++ b/app/dts/bindings/animations/zmk,animation-ripple.yaml @@ -0,0 +1,46 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: | + Animated 'ripples' trigered by key presses. + +compatible: "zmk,animation-ripple" + +include: animation_base.yaml + +properties: + label: + type: string + required: true + + duration: + type: int + required: false + default: 1000 + description: | + Approximate ripple travel time in milliseconds. + + color: + type: int + required: true + description: | + Ripple color. + + buffer-size: + type: int + default: 10 + description: | + This will limit how many keystroke events the animation is able to track + at the same time. Depending on how fast you type and the animation duration + you might need to increase this number. + + ripple-width: + type: int + default: 25 + description: | + This setting determines the thickness of the ripple 'ring'. + Think of it as antialiasing. The further apart the pixels are, or if they're + spaced irregularly, the larger this number should be. Otherwise the animation + will look uneven or LEDs might not light up at all. + The effect is especially pronounced when lowering the effect duration or running + low FPS. diff --git a/app/src/animation/animation_ripple.c b/app/src/animation/animation_ripple.c new file mode 100644 index 00000000..bbeb1ddb --- /dev/null +++ b/app/src/animation/animation_ripple.c @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_animation_ripple + +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct animation_ripple_event { + size_t pixel_id; + uint16_t distance; + uint8_t counter; +}; + +struct animation_ripple_config { + struct zmk_color_hsl *color; + size_t event_buffer_size; + uint8_t distance_per_frame; + uint8_t ripple_width; + uint8_t event_frames; +}; + +struct animation_ripple_data { + struct animation_ripple_event *event_buffer; + size_t events_start; + size_t events_end; + size_t num_events; +}; + +static int animation_ripple_on_key_press(const struct device *dev, const zmk_event_t *event) { + const struct animation_ripple_config *config = dev->config; + struct animation_ripple_data *data = dev->data; + + const struct zmk_position_state_changed *pos_event; + + if ((pos_event = as_zmk_position_state_changed(event)) == NULL) { + // Event not supported. + return -ENOTSUP; + } + + if (!pos_event->state) { + // Don't track key releases. + return 0; + } + + if (data->num_events == config->event_buffer_size) { + // Event buffer is full - new key press events are dropped. + return -ENOMEM; + } + + data->event_buffer[data->events_end].pixel_id = + zmk_animation_get_pixel_by_key_position(pos_event->position); + data->event_buffer[data->events_end].distance = 0; + data->event_buffer[data->events_end].counter = 0; + + data->events_end = (data->events_end + 1) % config->event_buffer_size; + data->num_events += 1; + + return 0; +} + +static void animation_ripple_on_after_frame(const struct device *dev) { + const struct animation_ripple_config *config = dev->config; + struct animation_ripple_data *data = dev->data; + + size_t i = data->events_start; + + while (i != data->events_end) { + struct animation_ripple_event *event = &data->event_buffer[i]; + + if (event->counter < config->event_frames) { + event->distance += config->distance_per_frame; + event->counter += 1; + } else { + data->events_start = (data->events_start + 1) % config->event_buffer_size; + data->num_events -= 1; + } + + if (++i == config->event_buffer_size) { + i = 0; + } + } +} + +static void animation_ripple_render_pixel(const struct device *dev, + const struct animation_pixel *pixel, + struct zmk_color_rgb *value) { + const struct animation_ripple_config *config = dev->config; + struct animation_ripple_data *data = dev->data; + + size_t i = data->events_start; + + while (i != data->events_end) { + const struct animation_ripple_event *event = &data->event_buffer[i]; + + uint16_t pixel_distance = zmk_animation_get_pixel_distance(event->pixel_id, pixel->id); + + if (config->ripple_width > abs(pixel_distance - event->distance)) { + float intensity = + (float)abs(pixel_distance - event->distance) / (float)config->ripple_width; + + value->r = value->r < intensity ? intensity : value->r; + value->g = value->g < intensity ? intensity : value->g; + value->b = value->b < intensity ? intensity : value->b; + } + + if (++i == config->event_buffer_size) { + i = 0; + } + } +} + +static int animation_ripple_init(const struct device *dev) { return 0; } + +static const struct animation_api animation_ripple_api = { + .on_before_frame = NULL, + .on_after_frame = animation_ripple_on_after_frame, + .render_pixel = animation_ripple_render_pixel, +}; + +#define ANIMATION_RIPPLE_DEVICE(idx) \ + \ + static struct animation_ripple_event \ + animation_ripple_##idx##_events[DT_INST_PROP(idx, buffer_size)]; \ + \ + static struct animation_ripple_data animation_ripple_##idx##_data = { \ + .event_buffer = animation_ripple_##idx##_events, \ + .events_start = 0, \ + .events_end = 0, \ + .num_events = 0, \ + }; \ + \ + static uint32_t animation_ripple_##idx##_color = DT_INST_PROP(idx, color); \ + \ + static struct animation_ripple_config animation_ripple_##idx##_config = { \ + .color = (struct zmk_color_hsl *)&animation_ripple_##idx##_color, \ + .event_buffer_size = DT_INST_PROP(idx, buffer_size), \ + .distance_per_frame = \ + (255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS, \ + .ripple_width = DT_INST_PROP(idx, ripple_width) / 2, \ + .event_frames = \ + 360 / ((255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(idx, &animation_ripple_init, NULL, &animation_ripple_##idx##_data, \ + &animation_ripple_##idx##_config, POST_KERNEL, \ + CONFIG_APPLICATION_INIT_PRIORITY, &animation_ripple_api); \ + \ + static int animation_ripple_##idx##_event_handler(const zmk_event_t *event) { \ + const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(idx)); \ + \ + return animation_ripple_on_key_press(dev, event); \ + } \ + \ + ZMK_LISTENER(animation_ripple_##idx, animation_ripple_##idx##_event_handler); \ + ZMK_SUBSCRIPTION(animation_ripple_##idx, zmk_position_state_changed); + +DT_INST_FOREACH_STATUS_OKAY(ANIMATION_RIPPLE_DEVICE); From d7d1c0abcf0f7dd201c54f24da16b039ac7872d1 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Thu, 16 Dec 2021 18:59:59 +0100 Subject: [PATCH 14/34] Update ripple animation to use the given color --- app/src/animation/animation_ripple.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/animation/animation_ripple.c b/app/src/animation/animation_ripple.c index bbeb1ddb..445efda2 100644 --- a/app/src/animation/animation_ripple.c +++ b/app/src/animation/animation_ripple.c @@ -29,7 +29,7 @@ struct animation_ripple_event { }; struct animation_ripple_config { - struct zmk_color_hsl *color; + struct zmk_color_hsl *color_hsl; size_t event_buffer_size; uint8_t distance_per_frame; uint8_t ripple_width; @@ -37,6 +37,7 @@ struct animation_ripple_config { }; struct animation_ripple_data { + struct zmk_color_rgb color_rgb; struct animation_ripple_event *event_buffer; size_t events_start; size_t events_end; @@ -115,9 +116,9 @@ static void animation_ripple_render_pixel(const struct device *dev, float intensity = (float)abs(pixel_distance - event->distance) / (float)config->ripple_width; - value->r = value->r < intensity ? intensity : value->r; - value->g = value->g < intensity ? intensity : value->g; - value->b = value->b < intensity ? intensity : value->b; + value->r = intensity * data->color_rgb.r; + value->g = intensity * data->color_rgb.g; + value->b = intensity * data->color_rgb.b; } if (++i == config->event_buffer_size) { @@ -126,7 +127,14 @@ static void animation_ripple_render_pixel(const struct device *dev, } } -static int animation_ripple_init(const struct device *dev) { return 0; } +static int animation_ripple_init(const struct device *dev) { + const struct animation_ripple_config *config = dev->config; + struct animation_ripple_data *data = dev->data; + + zmk_hsl_to_rgb(config->color_hsl, &data->color_rgb); + + return 0; +} static const struct animation_api animation_ripple_api = { .on_before_frame = NULL, @@ -149,7 +157,7 @@ static const struct animation_api animation_ripple_api = { static uint32_t animation_ripple_##idx##_color = DT_INST_PROP(idx, color); \ \ static struct animation_ripple_config animation_ripple_##idx##_config = { \ - .color = (struct zmk_color_hsl *)&animation_ripple_##idx##_color, \ + .color_hsl = (struct zmk_color_hsl *)&animation_ripple_##idx##_color, \ .event_buffer_size = DT_INST_PROP(idx, buffer_size), \ .distance_per_frame = \ (255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS, \ From 38a70ea747d3f338287c046536ec424ebcb4e3d1 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Thu, 16 Dec 2021 19:02:27 +0100 Subject: [PATCH 15/34] Update devicetree bindings for solid animation --- app/dts/bindings/animations/zmk,animation-solid.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/dts/bindings/animations/zmk,animation-solid.yaml b/app/dts/bindings/animations/zmk,animation-solid.yaml index f84aaeef..f175f134 100644 --- a/app/dts/bindings/animations/zmk,animation-solid.yaml +++ b/app/dts/bindings/animations/zmk,animation-solid.yaml @@ -15,10 +15,12 @@ properties: duration: type: int - required: true + required: false + default: 5 description: | Animation duration in seconds. This is the time it takes for the animation to complete a full cycle and return to the original color. + Ignored if only a single color is given. colors: type: array From ae518fabc938595ff2542d9b634e7d41da41fadd Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Thu, 16 Dec 2021 19:02:46 +0100 Subject: [PATCH 16/34] Add 'compose' animation --- app/CMakeLists.txt | 1 + .../animations/zmk,animation-compose.yaml | 27 +++++ .../dt-bindings/zmk/animation_compose.h | 12 ++ app/src/animation/animation_compose.c | 106 ++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 app/dts/bindings/animations/zmk,animation-compose.yaml create mode 100644 app/include/dt-bindings/zmk/animation_compose.h create mode 100644 app/src/animation/animation_compose.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 1ee31865..9e5cbdad 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -81,6 +81,7 @@ target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed 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_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) diff --git a/app/dts/bindings/animations/zmk,animation-compose.yaml b/app/dts/bindings/animations/zmk,animation-compose.yaml new file mode 100644 index 00000000..97bdb1ad --- /dev/null +++ b/app/dts/bindings/animations/zmk,animation-compose.yaml @@ -0,0 +1,27 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: | + Higher-order animation which allows for composing other animation + drivers and using different blending modes. + +compatible: "zmk,animation-compose" + +include: animation_base.yaml + +properties: + label: + type: string + required: true + + animations: + type: phandles + required: true + description: | + Animations to be combined. + + blending-modes: + type: array + required: true + description: | + Blending modes for each animation. diff --git a/app/include/dt-bindings/zmk/animation_compose.h b/app/include/dt-bindings/zmk/animation_compose.h new file mode 100644 index 00000000..7030efec --- /dev/null +++ b/app/include/dt-bindings/zmk/animation_compose.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#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 diff --git a/app/src/animation/animation_compose.c b/app/src/animation/animation_compose.c new file mode 100644 index 00000000..25c3d2b5 --- /dev/null +++ b/app/src/animation/animation_compose.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_animation_compose + +#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_compose_config { + const struct device **animations; + const size_t animations_size; + const uint8_t *blending_modes; +}; + +static void animation_compose_render_pixel(const struct device *dev, + const struct animation_pixel *pixel, + struct zmk_color_rgb *value) { + const struct animation_compose_config *config = dev->config; + + const struct device **animations = config->animations; + const uint8_t *blending_modes = config->blending_modes; + + struct zmk_color_rgb rgb = { + .r = 0, + .g = 0, + .b = 0, + }; + + for (size_t i = 0; i < config->animations_size; ++i) { + animation_render_pixel(animations[i], pixel, + blending_modes[i] == BLENDING_MODE_NORMAL ? value : &rgb); + + switch (blending_modes[i]) { + case BLENDING_MODE_MULTIPLY: + value->r = value->r * rgb.r; + value->g = value->g * rgb.g; + value->b = value->b * rgb.b; + break; + case BLENDING_MODE_LIGHTEN: + value->r = value->r > rgb.r ? value->r : rgb.r; + value->g = value->g > rgb.g ? value->g : rgb.g; + value->b = value->b > rgb.b ? value->b : rgb.b; + break; + case BLENDING_MODE_DARKEN: + value->r = value->r > rgb.r ? rgb.r : value->r; + value->g = value->g > rgb.g ? rgb.g : value->g; + value->b = value->b > rgb.b ? rgb.b : value->b; + break; + case BLENDING_MODE_SCREEN: + value->r = value->r + (1.0f - value->r) * rgb.r; + value->g = value->g + (1.0f - value->g) * rgb.g; + value->b = value->b + (1.0f - value->b) * rgb.b; + break; + case BLENDING_MODE_SUBTRACT: + value->r = value->r - value->r * rgb.r; + value->g = value->g - value->g * rgb.g; + value->b = value->b - value->b * rgb.b; + break; + } + } +} + +static int animation_compose_init(const struct device *dev) { return 0; } + +static const struct animation_api animation_compose_api = { + .on_before_frame = NULL, + .on_after_frame = NULL, + .render_pixel = animation_compose_render_pixel, +}; + +#define ANIMATION_COMPOSE_DEVICE(idx) \ + \ + static const uint8_t animation_compose_##idx##_blending_modes[] = \ + DT_INST_PROP(idx, blending_modes); \ + \ + static const struct device *animation_compose_##idx##_animations[] = { \ + ZMK_DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \ + \ + static struct animation_compose_config animation_compose_##idx##_config = { \ + .animations = animation_compose_##idx##_animations, \ + .animations_size = DT_INST_PROP_LEN(idx, animations), \ + .blending_modes = animation_compose_##idx##_blending_modes, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(idx, &animation_compose_init, NULL, NULL, \ + &animation_compose_##idx##_config, POST_KERNEL, \ + CONFIG_APPLICATION_INIT_PRIORITY, &animation_compose_api); + +DT_INST_FOREACH_STATUS_OKAY(ANIMATION_COMPOSE_DEVICE); From 2fa6063c9baf6e42da8a67645e7ff52e3cd820ed Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sun, 19 Dec 2021 15:28:21 +0100 Subject: [PATCH 17/34] Refactor animation API to allow for processing entire frames at once --- app/dts/animation.dtsi | 13 ++++ .../bindings/animations/animation_base.yaml | 23 ++++++- .../animations/zmk,animation-compose.yaml | 12 ---- .../animations/zmk,animation-ripple.yaml | 4 -- .../animations/zmk,animation-solid.yaml | 4 -- app/dts/bindings/zmk,animation-pixel.yaml | 10 +++ app/dts/bindings/zmk,animation.yaml | 6 -- app/include/drivers/animation.h | 59 +++------------- app/include/zmk/animation.h | 22 +++++- app/src/animation/animation.c | 41 +++--------- app/src/animation/animation_compose.c | 55 ++------------- app/src/animation/animation_ripple.c | 67 ++++++++++--------- app/src/animation/animation_solid.c | 37 ++++++---- app/src/animation/color.c | 33 +++++++++ 14 files changed, 179 insertions(+), 207 deletions(-) create mode 100644 app/dts/animation.dtsi create mode 100644 app/dts/bindings/zmk,animation-pixel.yaml diff --git a/app/dts/animation.dtsi b/app/dts/animation.dtsi new file mode 100644 index 00000000..15ed2145 --- /dev/null +++ b/app/dts/animation.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + /omit-if-no-ref/ pixel: animation_pixel { + compatible = "zmk,animation-pixel"; + label = "PIXEL"; + #pixel-cells = <2>; + }; +}; diff --git a/app/dts/bindings/animations/animation_base.yaml b/app/dts/bindings/animations/animation_base.yaml index aa2a5342..a8f10655 100644 --- a/app/dts/bindings/animations/animation_base.yaml +++ b/app/dts/bindings/animations/animation_base.yaml @@ -1,6 +1,23 @@ # Copyright (c) 2020, The ZMK Contributors # SPDX-License-Identifier: MIT -pixel-cells: - - position_x - - position_y +properties: + pixels: + type: array + required: true + description: | + Pixel positions to which the animation should apply. + + blending-mode: + type: int + required: false + enum: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + default: 0 + description: | + Blending mode for the animation to use during render. diff --git a/app/dts/bindings/animations/zmk,animation-compose.yaml b/app/dts/bindings/animations/zmk,animation-compose.yaml index 97bdb1ad..d91f0a2a 100644 --- a/app/dts/bindings/animations/zmk,animation-compose.yaml +++ b/app/dts/bindings/animations/zmk,animation-compose.yaml @@ -7,21 +7,9 @@ description: | compatible: "zmk,animation-compose" -include: animation_base.yaml - properties: - label: - type: string - required: true - animations: type: phandles required: true description: | Animations to be combined. - - blending-modes: - type: array - required: true - description: | - Blending modes for each animation. diff --git a/app/dts/bindings/animations/zmk,animation-ripple.yaml b/app/dts/bindings/animations/zmk,animation-ripple.yaml index e611a6f5..88f5416c 100644 --- a/app/dts/bindings/animations/zmk,animation-ripple.yaml +++ b/app/dts/bindings/animations/zmk,animation-ripple.yaml @@ -9,10 +9,6 @@ compatible: "zmk,animation-ripple" include: animation_base.yaml properties: - label: - type: string - required: true - duration: type: int required: false diff --git a/app/dts/bindings/animations/zmk,animation-solid.yaml b/app/dts/bindings/animations/zmk,animation-solid.yaml index f175f134..b4247885 100644 --- a/app/dts/bindings/animations/zmk,animation-solid.yaml +++ b/app/dts/bindings/animations/zmk,animation-solid.yaml @@ -9,10 +9,6 @@ compatible: "zmk,animation-solid" include: animation_base.yaml properties: - label: - type: string - required: true - duration: type: int required: false diff --git a/app/dts/bindings/zmk,animation-pixel.yaml b/app/dts/bindings/zmk,animation-pixel.yaml new file mode 100644 index 00000000..2b3b5ebc --- /dev/null +++ b/app/dts/bindings/zmk,animation-pixel.yaml @@ -0,0 +1,10 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Pixel configuration + +compatible: "zmk,animation-pixel" + +pixel-cells: + - position_x + - position_y diff --git a/app/dts/bindings/zmk,animation.yaml b/app/dts/bindings/zmk,animation.yaml index a2ad35b7..32b90395 100644 --- a/app/dts/bindings/zmk,animation.yaml +++ b/app/dts/bindings/zmk,animation.yaml @@ -13,12 +13,6 @@ properties: This array should contain all driver devices responsible for illuminating animated LEDs. The devices must implement Zephyr's LED Strip Interface and expose a chain-lenght devicetree property. - animations: - type: phandles - required: true - description: | - Handles to all active animations. - pixels: type: phandle-array required: true diff --git a/app/include/drivers/animation.h b/app/include/drivers/animation.h index cba8232b..ad4ad956 100644 --- a/app/include/drivers/animation.h +++ b/app/include/drivers/animation.h @@ -21,11 +21,10 @@ */ struct animation_pixel { - const size_t id; const uint8_t position_x; const uint8_t position_y; - const struct device *animation; + struct zmk_color_rgb value; }; #ifdef __cplusplus @@ -33,63 +32,23 @@ extern "C" { #endif /** - * @typedef animation_api_prep_next_frame - * @brief Callback run before every frame is rendered. - * - * @see animation_api_before_frame() for argument descriptions. - */ -typedef void (*animation_api_on_before_frame)(const struct device *dev); - -/** - * @typedef animation_api_prep_next_frame - * @brief Callback run after every frame is rendered. - * - * @see animation_api_before_frame() for argument descriptions. - */ -typedef void (*animation_api_on_after_frame)(const struct device *dev); - -/** - * @typedef animation_api_prep_next_frame + * @typedef animation_render_frame * @brief Callback API for generating the next animation frame * - * @see animation_prep_next_frame() for argument descriptions. + * @see animation_render_frame() for argument descriptions. */ -typedef void (*animation_api_render_pixel)(const struct device *dev, - const struct animation_pixel *pixel, - struct zmk_color_rgb *value); +typedef void (*animation_api_render_frame)(const struct device *dev, struct animation_pixel *pixels, + size_t num_pixels); struct animation_api { - animation_api_on_before_frame on_before_frame; - animation_api_on_after_frame on_after_frame; - animation_api_render_pixel render_pixel; + animation_api_render_frame render_frame; }; -static inline void animation_on_before_frame(const struct device *dev) { +static inline void animation_render_frame(const struct device *dev, struct animation_pixel *pixels, + size_t num_pixels) { const struct animation_api *api = (const struct animation_api *)dev->api; - if (api->on_before_frame == NULL) { - return; - } - - api->on_before_frame(dev); -} - -static inline void animation_on_after_frame(const struct device *dev) { - const struct animation_api *api = (const struct animation_api *)dev->api; - - if (api->on_after_frame == NULL) { - return; - } - - api->on_after_frame(dev); -} - -static inline void animation_render_pixel(const struct device *dev, - const struct animation_pixel *pixel, - struct zmk_color_rgb *value) { - const struct animation_api *api = (const struct animation_api *)dev->api; - - return api->render_pixel(dev, pixel, value); + return api->render_frame(dev, pixels, num_pixels); } #ifdef __cplusplus diff --git a/app/include/zmk/animation.h b/app/include/zmk/animation.h index aeede8ea..e48b38e3 100644 --- a/app/include/zmk/animation.h +++ b/app/include/zmk/animation.h @@ -9,6 +9,13 @@ #include #include +#define ZMK_ANIMATION_BLENDING_MODE_NORMAL 0 +#define ZMK_ANIMATION_BLENDING_MODE_MULTIPLY 1 +#define ZMK_ANIMATION_BLENDING_MODE_LIGHTEN 2 +#define ZMK_ANIMATION_BLENDING_MODE_DARKEN 3 +#define ZMK_ANIMATION_BLENDING_MODE_SCREEN 4 +#define ZMK_ANIMATION_BLENDING_MODE_SUBTRACT 5 + struct zmk_color_rgb { float r; float g; @@ -30,7 +37,7 @@ static inline size_t zmk_animation_get_pixel_by_key_position(size_t key_position #endif #if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1) -uint16_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx); +uint8_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx); #endif /** @@ -70,3 +77,16 @@ bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b); */ void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to, struct zmk_color_hsl *result, float step); + +struct zmk_color_rgb __zmk_apply_blending_mode(struct zmk_color_rgb base_value, + struct zmk_color_rgb blend_value, uint8_t mode); + +static inline struct zmk_color_rgb zmk_apply_blending_mode(struct zmk_color_rgb base_value, + struct zmk_color_rgb blend_value, + uint8_t mode) { + if (mode == ZMK_ANIMATION_BLENDING_MODE_NORMAL) { + return blend_value; + } + + return __zmk_apply_blending_mode(base_value, blend_value, mode); +} diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index 7fcece19..ac8a86ad 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -35,10 +35,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define PHANDLE_TO_PIXEL(idx, node_id, prop) \ { \ - .id = idx, \ .position_x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \ .position_y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \ - .animation = DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), \ }, /** @@ -59,20 +57,14 @@ static const uint8_t pixels_per_driver[] = { ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH)}; /** - * Pointers to all active animation devices. + * Pointer to the root animation */ -static const struct device *animations[] = { - ZMK_DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_DEVICE)}; - -/** - * Size of the animation device pointers array. - */ -static const size_t animations_size = DT_INST_PROP_LEN(0, animations); +static const struct device *animation_root = DEVICE_DT_GET(DT_CHOSEN(zmk_animation)); /** * Pixel configuration. */ -static const struct animation_pixel pixels[] = { +static struct animation_pixel pixels[] = { ZMK_DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL)}; /** @@ -102,29 +94,19 @@ size_t zmk_animation_get_pixel_by_key_position(size_t key_position) { /** * Lookup table for distance between any two pixels. */ -static uint16_t pixel_distance[DT_INST_PROP_LEN(0, pixels)][DT_INST_PROP_LEN(0, pixels)]; +static uint8_t pixel_distance[DT_INST_PROP_LEN(0, pixels)][DT_INST_PROP_LEN(0, pixels)]; -uint16_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx) { +uint8_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx) { return pixel_distance[pixel_idx][other_pixel_idx]; } #endif static void zmk_animation_tick(struct k_work *work) { - for (size_t i = 0; i < animations_size; ++i) { - animation_on_before_frame(animations[i]); - } + animation_render_frame(animation_root, &pixels[0], pixels_size); for (size_t i = 0; i < pixels_size; ++i) { - struct zmk_color_rgb rgb = { - .r = 0, - .g = 0, - .b = 0, - }; - - animation_render_pixel(pixels[i].animation, &pixels[i], &rgb); - - zmk_rgb_to_led_rgb(&rgb, &px_buffer[i]); + zmk_rgb_to_led_rgb(&pixels[i].value, &px_buffer[i]); } size_t pixels_updated = 0; @@ -134,10 +116,6 @@ static void zmk_animation_tick(struct k_work *work) { pixels_updated += (size_t)pixels_per_driver; } - - for (size_t i = 0; i < animations_size; ++i) { - animation_on_after_frame(animations[i]); - } } K_WORK_DEFINE(animation_work, zmk_animation_tick); @@ -151,8 +129,11 @@ static int zmk_animation_init(const struct device *dev) { // Prefill the pixel distance lookup table for (size_t i = 0; i < pixels_size; ++i) { for (size_t j = 0; j < pixels_size; ++j) { + // Distances are normalized to fit inside 0-255 range to fit inside uint8_t + // for better space efficiency pixel_distance[i][j] = sqrt(pow(pixels[i].position_x - pixels[j].position_x, 2) + - pow(pixels[i].position_y - pixels[j].position_y, 2)); + pow(pixels[i].position_y - pixels[j].position_y, 2)) * + 255 / 360; } } #endif diff --git a/app/src/animation/animation_compose.c b/app/src/animation/animation_compose.c index 25c3d2b5..5acce91e 100644 --- a/app/src/animation/animation_compose.c +++ b/app/src/animation/animation_compose.c @@ -12,7 +12,6 @@ #include #include -#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -26,77 +25,31 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); struct animation_compose_config { const struct device **animations; const size_t animations_size; - const uint8_t *blending_modes; }; -static void animation_compose_render_pixel(const struct device *dev, - const struct animation_pixel *pixel, - struct zmk_color_rgb *value) { +static void animation_compose_render_frame(const struct device *dev, struct animation_pixel *pixels, + size_t num_pixels) { const struct animation_compose_config *config = dev->config; - const struct device **animations = config->animations; - const uint8_t *blending_modes = config->blending_modes; - - struct zmk_color_rgb rgb = { - .r = 0, - .g = 0, - .b = 0, - }; - for (size_t i = 0; i < config->animations_size; ++i) { - animation_render_pixel(animations[i], pixel, - blending_modes[i] == BLENDING_MODE_NORMAL ? value : &rgb); - - switch (blending_modes[i]) { - case BLENDING_MODE_MULTIPLY: - value->r = value->r * rgb.r; - value->g = value->g * rgb.g; - value->b = value->b * rgb.b; - break; - case BLENDING_MODE_LIGHTEN: - value->r = value->r > rgb.r ? value->r : rgb.r; - value->g = value->g > rgb.g ? value->g : rgb.g; - value->b = value->b > rgb.b ? value->b : rgb.b; - break; - case BLENDING_MODE_DARKEN: - value->r = value->r > rgb.r ? rgb.r : value->r; - value->g = value->g > rgb.g ? rgb.g : value->g; - value->b = value->b > rgb.b ? rgb.b : value->b; - break; - case BLENDING_MODE_SCREEN: - value->r = value->r + (1.0f - value->r) * rgb.r; - value->g = value->g + (1.0f - value->g) * rgb.g; - value->b = value->b + (1.0f - value->b) * rgb.b; - break; - case BLENDING_MODE_SUBTRACT: - value->r = value->r - value->r * rgb.r; - value->g = value->g - value->g * rgb.g; - value->b = value->b - value->b * rgb.b; - break; - } + animation_render_frame(config->animations[i], pixels, num_pixels); } } static int animation_compose_init(const struct device *dev) { return 0; } static const struct animation_api animation_compose_api = { - .on_before_frame = NULL, - .on_after_frame = NULL, - .render_pixel = animation_compose_render_pixel, + .render_frame = animation_compose_render_frame, }; #define ANIMATION_COMPOSE_DEVICE(idx) \ \ - static const uint8_t animation_compose_##idx##_blending_modes[] = \ - DT_INST_PROP(idx, blending_modes); \ - \ static const struct device *animation_compose_##idx##_animations[] = { \ ZMK_DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \ \ static struct animation_compose_config animation_compose_##idx##_config = { \ .animations = animation_compose_##idx##_animations, \ .animations_size = DT_INST_PROP_LEN(idx, animations), \ - .blending_modes = animation_compose_##idx##_blending_modes, \ }; \ \ DEVICE_DT_INST_DEFINE(idx, &animation_compose_init, NULL, NULL, \ diff --git a/app/src/animation/animation_ripple.c b/app/src/animation/animation_ripple.c index 445efda2..b74b4af2 100644 --- a/app/src/animation/animation_ripple.c +++ b/app/src/animation/animation_ripple.c @@ -30,7 +30,10 @@ struct animation_ripple_event { struct animation_ripple_config { struct zmk_color_hsl *color_hsl; + size_t *pixel_map; + size_t pixel_map_size; size_t event_buffer_size; + uint8_t blending_mode; uint8_t distance_per_frame; uint8_t ripple_width; uint8_t event_frames; @@ -76,15 +79,38 @@ static int animation_ripple_on_key_press(const struct device *dev, const zmk_eve return 0; } -static void animation_ripple_on_after_frame(const struct device *dev) { +static void animation_ripple_render_frame(const struct device *dev, struct animation_pixel *pixels, + size_t num_pixels) { const struct animation_ripple_config *config = dev->config; struct animation_ripple_data *data = dev->data; + size_t *pixel_map = config->pixel_map; + size_t i = data->events_start; while (i != data->events_end) { struct animation_ripple_event *event = &data->event_buffer[i]; + // Render all pixels for each event + for (int j = 0; j < config->pixel_map_size; ++j) { + uint8_t pixel_distance = zmk_animation_get_pixel_distance(event->pixel_id, j); + + if (config->ripple_width > abs(pixel_distance - event->distance)) { + float intensity = + (float)abs(pixel_distance - event->distance) / (float)config->ripple_width; + + struct zmk_color_rgb color = { + .r = intensity * data->color_rgb.r, + .g = intensity * data->color_rgb.g, + .b = intensity * data->color_rgb.b, + }; + + pixels[pixel_map[j]].value = zmk_apply_blending_mode(pixels[pixel_map[j]].value, + color, config->blending_mode); + } + } + + // Update event counter if (event->counter < config->event_frames) { event->distance += config->distance_per_frame; event->counter += 1; @@ -99,34 +125,6 @@ static void animation_ripple_on_after_frame(const struct device *dev) { } } -static void animation_ripple_render_pixel(const struct device *dev, - const struct animation_pixel *pixel, - struct zmk_color_rgb *value) { - const struct animation_ripple_config *config = dev->config; - struct animation_ripple_data *data = dev->data; - - size_t i = data->events_start; - - while (i != data->events_end) { - const struct animation_ripple_event *event = &data->event_buffer[i]; - - uint16_t pixel_distance = zmk_animation_get_pixel_distance(event->pixel_id, pixel->id); - - if (config->ripple_width > abs(pixel_distance - event->distance)) { - float intensity = - (float)abs(pixel_distance - event->distance) / (float)config->ripple_width; - - value->r = intensity * data->color_rgb.r; - value->g = intensity * data->color_rgb.g; - value->b = intensity * data->color_rgb.b; - } - - if (++i == config->event_buffer_size) { - i = 0; - } - } -} - static int animation_ripple_init(const struct device *dev) { const struct animation_ripple_config *config = dev->config; struct animation_ripple_data *data = dev->data; @@ -137,9 +135,7 @@ static int animation_ripple_init(const struct device *dev) { } static const struct animation_api animation_ripple_api = { - .on_before_frame = NULL, - .on_after_frame = animation_ripple_on_after_frame, - .render_pixel = animation_ripple_render_pixel, + .render_frame = animation_ripple_render_frame, }; #define ANIMATION_RIPPLE_DEVICE(idx) \ @@ -154,16 +150,21 @@ static const struct animation_api animation_ripple_api = { .num_events = 0, \ }; \ \ + static size_t animation_ripple_##idx##_pixel_map[] = DT_INST_PROP(idx, pixels); \ + \ static uint32_t animation_ripple_##idx##_color = DT_INST_PROP(idx, color); \ \ static struct animation_ripple_config animation_ripple_##idx##_config = { \ .color_hsl = (struct zmk_color_hsl *)&animation_ripple_##idx##_color, \ + .pixel_map = &animation_ripple_##idx##_pixel_map[0], \ + .pixel_map_size = DT_INST_PROP_LEN(idx, pixels), \ .event_buffer_size = DT_INST_PROP(idx, buffer_size), \ + .blending_mode = DT_INST_PROP(idx, blending_mode), \ .distance_per_frame = \ (255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS, \ .ripple_width = DT_INST_PROP(idx, ripple_width) / 2, \ .event_frames = \ - 360 / ((255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS), \ + 255 / ((255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS), \ }; \ \ DEVICE_DT_INST_DEFINE(idx, &animation_ripple_init, NULL, &animation_ripple_##idx##_data, \ diff --git a/app/src/animation/animation_solid.c b/app/src/animation/animation_solid.c index f49041ab..d9d22aa7 100644 --- a/app/src/animation/animation_solid.c +++ b/app/src/animation/animation_solid.c @@ -16,6 +16,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); struct animation_solid_config { + size_t *pixel_map; + size_t pixel_map_size; struct zmk_color_hsl *colors; uint8_t num_colors; uint16_t duration; @@ -31,7 +33,7 @@ struct animation_solid_data { struct zmk_color_rgb current_rgb; }; -static void animation_solid_on_before_frame(const struct device *dev) { +static void animation_solid_tick(const struct device *dev) { const struct animation_solid_config *config = dev->config; struct animation_solid_data *data = dev->data; @@ -57,14 +59,16 @@ static void animation_solid_on_before_frame(const struct device *dev) { data->counter = (data->counter + 1) % config->duration; } -static void animation_solid_render_pixel(const struct device *dev, - const struct animation_pixel *pixel, - struct zmk_color_rgb *value) { - const struct animation_solid_data *data = dev->data; +static void animation_solid_render_frame(const struct device *dev, struct animation_pixel *pixels, + size_t num_pixels) { + const struct animation_solid_config *config = dev->config; + struct animation_solid_data *data = dev->data; - value->r = data->current_rgb.r; - value->g = data->current_rgb.g; - value->b = data->current_rgb.b; + animation_solid_tick(dev); + + for (size_t i = 0; i < config->pixel_map_size; ++i) { + pixels[config->pixel_map[i]].value = data->current_rgb; + } } static int animation_solid_init(const struct device *dev) { @@ -76,23 +80,30 @@ static int animation_solid_init(const struct device *dev) { zmk_hsl_to_rgb(&data->current_hsl, &data->current_rgb); + // if (config->num_colors == 1) { + // return 0; + // } + + // start timer here, so the only option is inline + return 0; } static const struct animation_api animation_solid_api = { - .on_before_frame = animation_solid_on_before_frame, - .on_after_frame = NULL, - .render_pixel = animation_solid_render_pixel, + .render_frame = animation_solid_render_frame, }; #define ANIMATION_SOLID_DEVICE(idx) \ \ static struct animation_solid_data animation_solid_##idx##_data; \ \ - static uint32_t animation_solid_##idx##_colors[DT_INST_PROP_LEN(idx, colors)] = \ - DT_INST_PROP(idx, colors); \ + static size_t animation_ripple_##idx##_pixel_map[] = DT_INST_PROP(idx, pixels); \ + \ + static uint32_t animation_solid_##idx##_colors[] = DT_INST_PROP(idx, colors); \ \ static struct animation_solid_config animation_solid_##idx##_config = { \ + .pixel_map = &animation_ripple_##idx##_pixel_map[0], \ + .pixel_map_size = DT_INST_PROP_LEN(idx, pixels), \ .colors = (struct zmk_color_hsl *)animation_solid_##idx##_colors, \ .num_colors = DT_INST_PROP_LEN(idx, colors), \ .duration = DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS, \ diff --git a/app/src/animation/color.c b/app/src/animation/color.c index 25892dc4..55630ec1 100644 --- a/app/src/animation/color.c +++ b/app/src/animation/color.c @@ -103,3 +103,36 @@ void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_colo result->s = from->s - (from->s - to->s) * step; result->l = from->l - (from->l - to->l) * step; } + +struct zmk_color_rgb __zmk_apply_blending_mode(struct zmk_color_rgb base_value, + struct zmk_color_rgb blend_value, uint8_t mode) { + switch (mode) { + case ZMK_ANIMATION_BLENDING_MODE_MULTIPLY: + base_value.r = base_value.r * blend_value.r; + base_value.g = base_value.g * blend_value.g; + base_value.b = base_value.b * blend_value.b; + break; + case ZMK_ANIMATION_BLENDING_MODE_LIGHTEN: + base_value.r = base_value.r > blend_value.r ? base_value.r : blend_value.r; + base_value.g = base_value.g > blend_value.g ? base_value.g : blend_value.g; + base_value.b = base_value.b > blend_value.b ? base_value.b : blend_value.b; + break; + case ZMK_ANIMATION_BLENDING_MODE_DARKEN: + base_value.r = base_value.r > blend_value.r ? blend_value.r : base_value.r; + base_value.g = base_value.g > blend_value.g ? blend_value.g : base_value.g; + base_value.b = base_value.b > blend_value.b ? blend_value.b : base_value.b; + break; + case ZMK_ANIMATION_BLENDING_MODE_SCREEN: + base_value.r = base_value.r + (1.0f - base_value.r) * blend_value.r; + base_value.g = base_value.g + (1.0f - base_value.g) * blend_value.g; + base_value.b = base_value.b + (1.0f - base_value.b) * blend_value.b; + break; + case ZMK_ANIMATION_BLENDING_MODE_SUBTRACT: + base_value.r = base_value.r - base_value.r * blend_value.r; + base_value.g = base_value.g - base_value.g * blend_value.g; + base_value.b = base_value.b - base_value.b * blend_value.b; + break; + } + + return base_value; +} From 6697fc4582de15daf8a2294dbdf0e4e7cfde36a0 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sun, 19 Dec 2021 18:54:08 +0100 Subject: [PATCH 18/34] Fix intensity calculation for the ripple animation --- app/src/animation/animation_ripple.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/animation/animation_ripple.c b/app/src/animation/animation_ripple.c index b74b4af2..03e24f59 100644 --- a/app/src/animation/animation_ripple.c +++ b/app/src/animation/animation_ripple.c @@ -97,7 +97,7 @@ static void animation_ripple_render_frame(const struct device *dev, struct anima if (config->ripple_width > abs(pixel_distance - event->distance)) { float intensity = - (float)abs(pixel_distance - event->distance) / (float)config->ripple_width; + 1.0f - (float)abs(pixel_distance - event->distance) / (float)config->ripple_width; struct zmk_color_rgb color = { .r = intensity * data->color_rgb.r, From 19790f2e1a4f1652522584db6030052758cc0699 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Tue, 21 Dec 2021 18:14:42 +0100 Subject: [PATCH 19/34] Implement animation activation/deactivation API and frame scheduling --- app/include/drivers/animation.h | 32 +++++++++++++++++++++- app/include/zmk/animation.h | 2 ++ app/src/animation/animation.c | 29 ++++++++++++++++++-- app/src/animation/animation_compose.c | 18 +++++++++++++ app/src/animation/animation_ripple.c | 21 +++++++++++++-- app/src/animation/animation_solid.c | 38 ++++++++++++++------------- 6 files changed, 117 insertions(+), 23 deletions(-) diff --git a/app/include/drivers/animation.h b/app/include/drivers/animation.h index ad4ad956..df05d194 100644 --- a/app/include/drivers/animation.h +++ b/app/include/drivers/animation.h @@ -31,9 +31,25 @@ struct animation_pixel { extern "C" { #endif +/** + * @typedef animation_start + * @brief Callback API for starting an animation. + * + * @see animation_start() for argument descriptions. + */ +typedef void (*animation_api_start)(const struct device *dev); + +/** + * @typedef animation_stop + * @brief Callback API for stopping an animation. + * + * @see animation_stop() for argument descriptions. + */ +typedef void (*animation_api_stop)(const struct device *dev); + /** * @typedef animation_render_frame - * @brief Callback API for generating the next animation frame + * @brief Callback API for generating the next animation frame. * * @see animation_render_frame() for argument descriptions. */ @@ -41,9 +57,23 @@ typedef void (*animation_api_render_frame)(const struct device *dev, struct anim size_t num_pixels); struct animation_api { + animation_api_start on_start; + animation_api_stop on_stop; animation_api_render_frame render_frame; }; +static inline void animation_start(const struct device *dev) { + const struct animation_api *api = (const struct animation_api *)dev->api; + + return api->on_start(dev); +} + +static inline void animation_stop(const struct device *dev) { + const struct animation_api *api = (const struct animation_api *)dev->api; + + return api->on_stop(dev); +} + static inline void animation_render_frame(const struct device *dev, struct animation_pixel *pixels, size_t num_pixels) { const struct animation_api *api = (const struct animation_api *)dev->api; diff --git a/app/include/zmk/animation.h b/app/include/zmk/animation.h index e48b38e3..c17dccf4 100644 --- a/app/include/zmk/animation.h +++ b/app/include/zmk/animation.h @@ -78,6 +78,8 @@ bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b); void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to, struct zmk_color_hsl *result, float step); +void zmk_animation_request_frames(uint32_t frames); + struct zmk_color_rgb __zmk_apply_blending_mode(struct zmk_color_rgb base_value, struct zmk_color_rgb blend_value, uint8_t mode); diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index ac8a86ad..bd29bb03 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -77,6 +77,11 @@ static const size_t pixels_size = DT_INST_PROP_LEN(0, pixels); */ static struct led_rgb px_buffer[DT_INST_PROP_LEN(0, pixels)]; +/** + * Counter for animation frames that have been requested but have yet to be executed. + */ +static uint32_t animation_timer_countdown = 0; + /** * Conditional implementation of zmk_animation_get_pixel_by_key_position * if key-pixels is set. @@ -103,6 +108,7 @@ uint8_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_id #endif static void zmk_animation_tick(struct k_work *work) { + LOG_DBG("Animation tick"); animation_render_frame(animation_root, &pixels[0], pixels_size); for (size_t i = 0; i < pixels_size; ++i) { @@ -120,10 +126,29 @@ static void zmk_animation_tick(struct k_work *work) { K_WORK_DEFINE(animation_work, zmk_animation_tick); -static void zmk_animation_tick_handler(struct k_timer *timer) { k_work_submit(&animation_work); } +static void zmk_animation_tick_handler(struct k_timer *timer) { + if (--animation_timer_countdown == 0) { + k_timer_stop(timer); + } + + k_work_submit(&animation_work); +} K_TIMER_DEFINE(animation_tick, zmk_animation_tick_handler, NULL); +void zmk_animation_request_frames(uint32_t frames) { + if (frames <= animation_timer_countdown) { + return; + } + + if (animation_timer_countdown == 0) { + k_timer_start(&animation_tick, K_MSEC(1000 / CONFIG_ZMK_ANIMATION_FPS), + K_MSEC(1000 / CONFIG_ZMK_ANIMATION_FPS)); + } + + animation_timer_countdown = frames; +} + static int zmk_animation_init(const struct device *dev) { #if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1) // Prefill the pixel distance lookup table @@ -140,7 +165,7 @@ static int zmk_animation_init(const struct device *dev) { LOG_INF("ZMK Animation Ready"); - k_timer_start(&animation_tick, K_NO_WAIT, K_MSEC(1000 / CONFIG_ZMK_ANIMATION_FPS)); + animation_start(animation_root); return 0; } diff --git a/app/src/animation/animation_compose.c b/app/src/animation/animation_compose.c index 5acce91e..b946791f 100644 --- a/app/src/animation/animation_compose.c +++ b/app/src/animation/animation_compose.c @@ -36,9 +36,27 @@ static void animation_compose_render_frame(const struct device *dev, struct anim } } +static void animation_compose_start(const struct device *dev) { + const struct animation_compose_config *config = dev->config; + + for (size_t i = 0; i < config->animations_size; ++i) { + animation_start(config->animations[i]); + } +} + +static void animation_compose_stop(const struct device *dev) { + const struct animation_compose_config *config = dev->config; + + for (size_t i = 0; i < config->animations_size; ++i) { + animation_stop(config->animations[i]); + } +} + static int animation_compose_init(const struct device *dev) { return 0; } static const struct animation_api animation_compose_api = { + .on_start = animation_compose_start, + .on_stop = animation_compose_stop, .render_frame = animation_compose_render_frame, }; diff --git a/app/src/animation/animation_ripple.c b/app/src/animation/animation_ripple.c index 03e24f59..e57eae5c 100644 --- a/app/src/animation/animation_ripple.c +++ b/app/src/animation/animation_ripple.c @@ -76,6 +76,8 @@ static int animation_ripple_on_key_press(const struct device *dev, const zmk_eve data->events_end = (data->events_end + 1) % config->event_buffer_size; data->num_events += 1; + zmk_animation_request_frames(config->event_frames); + return 0; } @@ -96,8 +98,8 @@ static void animation_ripple_render_frame(const struct device *dev, struct anima uint8_t pixel_distance = zmk_animation_get_pixel_distance(event->pixel_id, j); if (config->ripple_width > abs(pixel_distance - event->distance)) { - float intensity = - 1.0f - (float)abs(pixel_distance - event->distance) / (float)config->ripple_width; + float intensity = 1.0f - (float)abs(pixel_distance - event->distance) / + (float)config->ripple_width; struct zmk_color_rgb color = { .r = intensity * data->color_rgb.r, @@ -125,6 +127,19 @@ static void animation_ripple_render_frame(const struct device *dev, struct anima } } +static void animation_ripple_start(const struct device *dev) { + // Nothing to do. +} + +static void animation_ripple_stop(const struct device *dev) { + struct animation_ripple_data *data = dev->data; + + // Cancel the processing of any ongoing events. + data->num_events = 0; + data->events_start = 0; + data->events_end = 0; +} + static int animation_ripple_init(const struct device *dev) { const struct animation_ripple_config *config = dev->config; struct animation_ripple_data *data = dev->data; @@ -135,6 +150,8 @@ static int animation_ripple_init(const struct device *dev) { } static const struct animation_api animation_ripple_api = { + .on_start = animation_ripple_start, + .on_stop = animation_ripple_stop, .render_frame = animation_ripple_render_frame, }; diff --git a/app/src/animation/animation_solid.c b/app/src/animation/animation_solid.c index d9d22aa7..b13a2835 100644 --- a/app/src/animation/animation_solid.c +++ b/app/src/animation/animation_solid.c @@ -25,23 +25,16 @@ struct animation_solid_config { }; struct animation_solid_data { - bool has_changed; - uint16_t counter; struct zmk_color_hsl current_hsl; struct zmk_color_rgb current_rgb; }; -static void animation_solid_tick(const struct device *dev) { +static void animation_solid_update_color(const struct device *dev) { const struct animation_solid_config *config = dev->config; struct animation_solid_data *data = dev->data; - // Animation only contains a single color, nothing to do - if (config->num_colors == 1) { - return; - } - const size_t from = data->counter / config->transition_duration; const size_t to = (from + 1) % config->num_colors; @@ -51,8 +44,6 @@ static void animation_solid_tick(const struct device *dev) { (data->counter % config->transition_duration) / (float)config->transition_duration); - data->has_changed = !zmk_cmp_hsl(&data->current_hsl, &next_hsl); - data->current_hsl = next_hsl; zmk_hsl_to_rgb(&data->current_hsl, &data->current_rgb); @@ -64,11 +55,26 @@ static void animation_solid_render_frame(const struct device *dev, struct animat const struct animation_solid_config *config = dev->config; struct animation_solid_data *data = dev->data; - animation_solid_tick(dev); - for (size_t i = 0; i < config->pixel_map_size; ++i) { pixels[config->pixel_map[i]].value = data->current_rgb; } + + if (config->num_colors == 1) { + return; + } + + // Request frames on counter reset + if (data->counter == 0) { + zmk_animation_request_frames(config->duration); + } + + animation_solid_update_color(dev); +} + +static void animation_solid_start(const struct device *dev) { zmk_animation_request_frames(1); } + +static void animation_solid_stop(const struct device *dev) { + // Nothing to do. } static int animation_solid_init(const struct device *dev) { @@ -80,16 +86,12 @@ static int animation_solid_init(const struct device *dev) { zmk_hsl_to_rgb(&data->current_hsl, &data->current_rgb); - // if (config->num_colors == 1) { - // return 0; - // } - - // start timer here, so the only option is inline - return 0; } static const struct animation_api animation_solid_api = { + .on_start = animation_solid_start, + .on_stop = animation_solid_stop, .render_frame = animation_solid_render_frame, }; From 0e09446cf8ddae07e0968b08d72c21e86636394d Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Tue, 18 Jan 2022 22:47:11 +0100 Subject: [PATCH 20/34] Optimize the lookup table for pixel distances --- app/src/animation/animation.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index bd29bb03..b818a903 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -98,11 +98,18 @@ size_t zmk_animation_get_pixel_by_key_position(size_t key_position) { /** * Lookup table for distance between any two pixels. + * + * The values are stored as a triangular matrix which cuts the space requirement roughly in half. */ -static uint8_t pixel_distance[DT_INST_PROP_LEN(0, pixels)][DT_INST_PROP_LEN(0, pixels)]; +static uint8_t + pixel_distance[((DT_INST_PROP_LEN(0, pixels) + 1) * DT_INST_PROP_LEN(0, pixels)) / 2]; uint8_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx) { - return pixel_distance[pixel_idx][other_pixel_idx]; + if (pixel_idx < other_pixel_idx) { + return zmk_animation_get_pixel_distance(other_pixel_idx, pixel_idx); + } + + return pixel_distance[(((pixel_idx + 1) * pixel_idx) >> 1) + other_pixel_idx]; } #endif @@ -152,13 +159,14 @@ void zmk_animation_request_frames(uint32_t frames) { static int zmk_animation_init(const struct device *dev) { #if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1) // Prefill the pixel distance lookup table + int k = 0; for (size_t i = 0; i < pixels_size; ++i) { - for (size_t j = 0; j < pixels_size; ++j) { + for (size_t j = 0; j <= i; ++j) { // Distances are normalized to fit inside 0-255 range to fit inside uint8_t // for better space efficiency - pixel_distance[i][j] = sqrt(pow(pixels[i].position_x - pixels[j].position_x, 2) + - pow(pixels[i].position_y - pixels[j].position_y, 2)) * - 255 / 360; + pixel_distance[k++] = sqrt(pow(pixels[i].position_x - pixels[j].position_x, 2) + + pow(pixels[i].position_y - pixels[j].position_y, 2)) * + 255 / 360; } } #endif From eb1fc0a21b94165a737ab121c4d5a314b754dffa Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Tue, 18 Jan 2022 22:51:46 +0100 Subject: [PATCH 21/34] Prevent ripple animation from requesting frames unless active --- app/src/animation/animation_ripple.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/animation/animation_ripple.c b/app/src/animation/animation_ripple.c index e57eae5c..dc61c148 100644 --- a/app/src/animation/animation_ripple.c +++ b/app/src/animation/animation_ripple.c @@ -45,6 +45,7 @@ struct animation_ripple_data { size_t events_start; size_t events_end; size_t num_events; + bool is_active; }; static int animation_ripple_on_key_press(const struct device *dev, const zmk_event_t *event) { @@ -53,6 +54,10 @@ static int animation_ripple_on_key_press(const struct device *dev, const zmk_eve const struct zmk_position_state_changed *pos_event; + if (!data->is_active) { + return 0; + } + if ((pos_event = as_zmk_position_state_changed(event)) == NULL) { // Event not supported. return -ENOTSUP; @@ -128,13 +133,17 @@ static void animation_ripple_render_frame(const struct device *dev, struct anima } static void animation_ripple_start(const struct device *dev) { - // Nothing to do. + struct animation_ripple_data *data = dev->data; + + data->is_active = true; } static void animation_ripple_stop(const struct device *dev) { struct animation_ripple_data *data = dev->data; - // Cancel the processing of any ongoing events. + data->is_active = false; + + // Cancel processing of any ongoing events. data->num_events = 0; data->events_start = 0; data->events_end = 0; From 69de72c6f4da2f2c22a2ebf720a75421ae530f9f Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Tue, 18 Jan 2022 22:52:02 +0100 Subject: [PATCH 22/34] Ensure the buffer is always reset before calculating each frame --- app/src/animation/animation.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index b818a903..6e431b0a 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -120,6 +120,11 @@ static void zmk_animation_tick(struct k_work *work) { for (size_t i = 0; i < pixels_size; ++i) { zmk_rgb_to_led_rgb(&pixels[i].value, &px_buffer[i]); + + // Reset values for the next cycle + pixels[i].value.r = 0; + pixels[i].value.g = 0; + pixels[i].value.b = 0; } size_t pixels_updated = 0; From fc8b91a2ba21a8998b80f67b5f100aa691f94dd5 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Tue, 18 Jan 2022 22:52:24 +0100 Subject: [PATCH 23/34] 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); From 6a551851aaa9493d8e71f9537856be75b7e0aa02 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sun, 30 Jan 2022 11:50:03 +0100 Subject: [PATCH 24/34] Persist settings for control animations --- .../animations/zmk,animation-control.yaml | 4 + app/src/animation/animation_control.c | 127 +++++++++++++++--- 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/app/dts/bindings/animations/zmk,animation-control.yaml b/app/dts/bindings/animations/zmk,animation-control.yaml index 6befe89a..7549f044 100644 --- a/app/dts/bindings/animations/zmk,animation-control.yaml +++ b/app/dts/bindings/animations/zmk,animation-control.yaml @@ -9,6 +9,10 @@ description: | compatible: "zmk,animation-control" properties: + label: + type: string + required: true + animations: type: phandles required: true diff --git a/app/src/animation/animation_control.c b/app/src/animation/animation_control.c index f30923a6..ae9a1075 100644 --- a/app/src/animation/animation_control.c +++ b/app/src/animation/animation_control.c @@ -6,10 +6,12 @@ #define DT_DRV_COMPAT zmk_animation_control +#include #include #include #include #include +#include #include #include @@ -23,10 +25,17 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define PHANDLE_TO_DEVICE(idx, node_id, prop) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), +struct animation_control_work_context { + const struct device *animation; + struct k_delayed_work save_work; +}; + struct animation_control_config { const struct device **animations; const size_t animations_size; const uint8_t brightness_steps; + struct animation_control_work_context *work; + struct settings_handler *settings_handler; }; struct animation_control_data { @@ -35,6 +44,47 @@ struct animation_control_data { size_t current_animation; }; +static int animation_control_load_settings(const struct device *dev, const char *name, size_t len, + settings_read_cb read_cb, void *cb_arg) { + const char *next; + int rc; + + if (settings_name_steq(name, "state", &next) && !next) { + if (len != sizeof(struct animation_control_data)) { + return -EINVAL; + } + + rc = read_cb(cb_arg, dev->data, sizeof(struct animation_control_data)); + if (rc >= 0) { + return 0; + } + + return rc; + } + + return -ENOENT; +} + +static void animation_control_save_work(struct k_work *work) { + struct animation_control_work_context *ctx = + CONTAINER_OF(work, struct animation_control_work_context, save_work); + const struct device *dev = ctx->animation; + + char path[40]; + snprintf(path, 40, "%s/state", dev->name); + + settings_save_one(path, dev->data, sizeof(struct animation_control_data)); +}; + +static int animation_control_save_settings(const struct device *dev) { + const struct animation_control_config *config = dev->config; + struct animation_control_work_context *ctx = config->work; + + k_delayed_work_cancel(&ctx->save_work); + + return k_delayed_work_submit(&ctx->save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); +} + 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; @@ -45,7 +95,7 @@ int animation_control_handle_command(const struct device *dev, uint8_t command, if (data->active) { animation_start(config->animations[data->current_animation]); - return 0; + break; } animation_stop(config->animations[data->current_animation]); @@ -95,32 +145,17 @@ int animation_control_handle_command(const struct device *dev, uint8_t command, break; } + // Save the new settings + animation_control_save_settings(dev); + // 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) { +static 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; @@ -143,7 +178,37 @@ void animation_control_render_frame(const struct device *dev, struct animation_p } } -static int animation_control_init(const struct device *dev) { return 0; } +static 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]); +} + +static 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]); +} + +static int animation_control_init(const struct device *dev) { + const struct animation_control_config *config = dev->config; + + settings_subsys_init(); + + settings_register(config->settings_handler); + + k_delayed_work_init(&config->work->save_work, animation_control_save_work); + + settings_load_subtree(dev->name); + + return 0; +} static const struct animation_api animation_control_api = { .on_start = animation_control_start, @@ -156,10 +221,28 @@ static const struct animation_api animation_control_api = { static const struct device *animation_control_##idx##_animations[] = { \ ZMK_DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \ \ + static struct animation_control_work_context animation_control_##idx##_work = { \ + .animation = DEVICE_DT_GET(DT_DRV_INST(idx)), \ + }; \ + \ + static int animation_control_##idx##_load_settings(const char *name, size_t len, \ + settings_read_cb read_cb, void *cb_arg) { \ + const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(idx)); \ + \ + return animation_control_load_settings(dev, name, len, read_cb, cb_arg); \ + } \ + \ + static struct settings_handler animation_control_##idx##_settings_handler = { \ + .name = DT_INST_PROP(idx, label), \ + .h_set = animation_control_##idx##_load_settings, \ + }; \ + \ 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, \ + .work = &animation_control_##idx##_work, \ + .settings_handler = &animation_control_##idx##_settings_handler, \ }; \ \ static struct animation_control_data animation_control_##idx##_data = { \ From 331573471d6624ec4b08c342fa61edb66709e468 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sun, 30 Jan 2022 13:52:10 +0100 Subject: [PATCH 25/34] Disable animations when entering sleep state --- app/src/animation/animation.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index 6e431b0a..64f385cd 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -20,6 +20,8 @@ #include #include +#include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -161,6 +163,28 @@ void zmk_animation_request_frames(uint32_t frames) { animation_timer_countdown = frames; } +static int zmk_animation_on_activity_state_changed(const zmk_event_t *event) { + const struct zmk_activity_state_changed *activity_state_event; + + if ((activity_state_event = as_zmk_activity_state_changed(event)) == NULL) { + // Event not supported. + return -ENOTSUP; + } + + switch (activity_state_event->state) { + case ZMK_ACTIVITY_ACTIVE: + animation_start(animation_root); + return 0; + case ZMK_ACTIVITY_SLEEP: + animation_stop(animation_root); + k_timer_stop(&animation_tick); + animation_timer_countdown = 0; + return 0; + default: + return 0; + } +} + static int zmk_animation_init(const struct device *dev) { #if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1) // Prefill the pixel distance lookup table @@ -184,3 +208,6 @@ static int zmk_animation_init(const struct device *dev) { } SYS_INIT(zmk_animation_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + +ZMK_LISTENER(amk_animation, zmk_animation_on_activity_state_changed); +ZMK_SUBSCRIPTION(amk_animation, zmk_activity_state_changed); From e2cdd017e34baf30b3410f284d92234efcf680be Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sun, 30 Jan 2022 14:01:47 +0100 Subject: [PATCH 26/34] Put animation control settings code behind config flags --- app/src/animation/animation_control.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/animation/animation_control.c b/app/src/animation/animation_control.c index ae9a1075..a6311074 100644 --- a/app/src/animation/animation_control.c +++ b/app/src/animation/animation_control.c @@ -46,6 +46,7 @@ struct animation_control_data { static int animation_control_load_settings(const struct device *dev, const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { +#if IS_ENABLED(CONFIG_SETTINGS) const char *next; int rc; @@ -63,8 +64,12 @@ static int animation_control_load_settings(const struct device *dev, const char } return -ENOENT; +#else + return 0; +#endif /* IS_ENABLED(CONFIG_SETTINGS) */ } +#if IS_ENABLED(CONFIG_SETTINGS) static void animation_control_save_work(struct k_work *work) { struct animation_control_work_context *ctx = CONTAINER_OF(work, struct animation_control_work_context, save_work); @@ -84,6 +89,7 @@ static int animation_control_save_settings(const struct device *dev) { return k_delayed_work_submit(&ctx->save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); } +#endif /* IS_ENABLED(CONFIG_SETTINGS) */ int animation_control_handle_command(const struct device *dev, uint8_t command, uint8_t param) { const struct animation_control_config *config = dev->config; @@ -145,8 +151,9 @@ int animation_control_handle_command(const struct device *dev, uint8_t command, break; } - // Save the new settings +#if IS_ENABLED(CONFIG_SETTINGS) animation_control_save_settings(dev); +#endif /* IS_ENABLED(CONFIG_SETTINGS) */ // Force refresh zmk_animation_request_frames(1); @@ -197,6 +204,7 @@ static void animation_control_stop(const struct device *dev) { } static int animation_control_init(const struct device *dev) { +#if IS_ENABLED(CONFIG_SETTINGS) const struct animation_control_config *config = dev->config; settings_subsys_init(); @@ -206,6 +214,7 @@ static int animation_control_init(const struct device *dev) { k_delayed_work_init(&config->work->save_work, animation_control_save_work); settings_load_subtree(dev->name); +#endif /* IS_ENABLED(CONFIG_SETTINGS) */ return 0; } From abeb5c8c49dc8fab200c70761a9b108384a04803 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Tue, 13 Jun 2023 15:19:59 +0200 Subject: [PATCH 27/34] Update the animation API for Zephyr 3.* --- app/include/drivers/animation.h | 4 +-- app/include/zmk/animation.h | 4 +-- app/include/zmk/animation/animation_control.h | 4 +-- app/src/animation/animation.c | 29 +++++++------------ app/src/animation/animation_compose.c | 16 ++++------ app/src/animation/animation_control.c | 27 ++++++++--------- app/src/animation/animation_ripple.c | 7 ++--- app/src/animation/animation_solid.c | 7 +++-- app/src/behaviors/behavior_animation.c | 9 +++--- 9 files changed, 47 insertions(+), 60 deletions(-) diff --git a/app/include/drivers/animation.h b/app/include/drivers/animation.h index df05d194..e0ee303b 100644 --- a/app/include/drivers/animation.h +++ b/app/include/drivers/animation.h @@ -7,8 +7,8 @@ #pragma once #include -#include -#include +#include +#include #include diff --git a/app/include/zmk/animation.h b/app/include/zmk/animation.h index c17dccf4..add3e5f5 100644 --- a/app/include/zmk/animation.h +++ b/app/include/zmk/animation.h @@ -6,8 +6,8 @@ #pragma once -#include -#include +#include +#include #define ZMK_ANIMATION_BLENDING_MODE_NORMAL 0 #define ZMK_ANIMATION_BLENDING_MODE_MULTIPLY 1 diff --git a/app/include/zmk/animation/animation_control.h b/app/include/zmk/animation/animation_control.h index 653afcf3..a21b4d77 100644 --- a/app/include/zmk/animation/animation_control.h +++ b/app/include/zmk/animation/animation_control.h @@ -6,8 +6,8 @@ #pragma once -#include -#include +#include +#include /** * Animation control commands diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index 64f385cd..3d94bfb2 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -6,18 +6,16 @@ #define DT_DRV_COMPAT zmk_animation -#include -#include -#include -#include - #include #include -#include +#include +#include +#include +#include +#include #include -#include #include #include @@ -25,17 +23,12 @@ 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(node_id, prop, idx) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), -#define PHANDLE_TO_DEVICE(idx, node_id, prop) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), - -#define PHANDLE_TO_CHAIN_LENGTH(idx, node_id, prop) \ +#define PHANDLE_TO_CHAIN_LENGTH(node_id, prop, idx) \ DT_PROP_BY_PHANDLE_IDX(node_id, prop, idx, chain_length), -#define PHANDLE_TO_PIXEL(idx, node_id, prop) \ +#define PHANDLE_TO_PIXEL(node_id, prop, idx) \ { \ .position_x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \ .position_y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \ @@ -45,7 +38,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); * LED Driver device pointers. */ static const struct device *drivers[] = { - ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE)}; + DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE)}; /** * Size of the LED driver device pointers array. @@ -56,7 +49,7 @@ static const size_t drivers_size = DT_INST_PROP_LEN(0, drivers); * Array containing the number of LEDs handled by each device. */ static const uint8_t pixels_per_driver[] = { - ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH)}; + DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH)}; /** * Pointer to the root animation @@ -67,7 +60,7 @@ static const struct device *animation_root = DEVICE_DT_GET(DT_CHOSEN(zmk_animati * Pixel configuration. */ static struct animation_pixel pixels[] = { - ZMK_DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL)}; + DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL)}; /** * Size of the pixels array. diff --git a/app/src/animation/animation_compose.c b/app/src/animation/animation_compose.c index b946791f..79db7c0e 100644 --- a/app/src/animation/animation_compose.c +++ b/app/src/animation/animation_compose.c @@ -6,21 +6,17 @@ #define DT_DRV_COMPAT zmk_animation_compose -#include -#include +#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)), +#define PHANDLE_TO_DEVICE(node_id, prop, idx) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), struct animation_compose_config { const struct device **animations; @@ -63,7 +59,7 @@ static const struct animation_api animation_compose_api = { #define ANIMATION_COMPOSE_DEVICE(idx) \ \ static const struct device *animation_compose_##idx##_animations[] = { \ - ZMK_DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \ + DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \ \ static struct animation_compose_config animation_compose_##idx##_config = { \ .animations = animation_compose_##idx##_animations, \ diff --git a/app/src/animation/animation_control.c b/app/src/animation/animation_control.c index a6311074..6e298ab3 100644 --- a/app/src/animation/animation_control.c +++ b/app/src/animation/animation_control.c @@ -7,27 +7,24 @@ #define DT_DRV_COMPAT zmk_animation_control #include -#include -#include + +#include +#include +#include +#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)), +#define PHANDLE_TO_DEVICE(node_id, prop, idx) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), struct animation_control_work_context { const struct device *animation; - struct k_delayed_work save_work; + struct k_work_delayable save_work; }; struct animation_control_config { @@ -85,9 +82,9 @@ static int animation_control_save_settings(const struct device *dev) { const struct animation_control_config *config = dev->config; struct animation_control_work_context *ctx = config->work; - k_delayed_work_cancel(&ctx->save_work); + k_work_cancel_delayable(&ctx->save_work); - return k_delayed_work_submit(&ctx->save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); + return k_work_reschedule(&ctx->save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); } #endif /* IS_ENABLED(CONFIG_SETTINGS) */ @@ -211,7 +208,7 @@ static int animation_control_init(const struct device *dev) { settings_register(config->settings_handler); - k_delayed_work_init(&config->work->save_work, animation_control_save_work); + k_work_init_delayable(&config->work->save_work, animation_control_save_work); settings_load_subtree(dev->name); #endif /* IS_ENABLED(CONFIG_SETTINGS) */ @@ -228,7 +225,7 @@ static const struct animation_api animation_control_api = { #define ANIMATION_CONTROL_DEVICE(idx) \ \ static const struct device *animation_control_##idx##_animations[] = { \ - ZMK_DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \ + DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \ \ static struct animation_control_work_context animation_control_##idx##_work = { \ .animation = DEVICE_DT_GET(DT_DRV_INST(idx)), \ diff --git a/app/src/animation/animation_ripple.c b/app/src/animation/animation_ripple.c index dc61c148..80cae740 100644 --- a/app/src/animation/animation_ripple.c +++ b/app/src/animation/animation_ripple.c @@ -6,13 +6,12 @@ #define DT_DRV_COMPAT zmk_animation_ripple -#include -#include - #include #include -#include +#include +#include +#include #include diff --git a/app/src/animation/animation_solid.c b/app/src/animation/animation_solid.c index b13a2835..02017b43 100644 --- a/app/src/animation/animation_solid.c +++ b/app/src/animation/animation_solid.c @@ -6,10 +6,11 @@ #define DT_DRV_COMPAT zmk_animation_solid -#include -#include +#include +#include +#include + #include -#include #include diff --git a/app/src/behaviors/behavior_animation.c b/app/src/behaviors/behavior_animation.c index 1f6823c8..fa086a9c 100644 --- a/app/src/behaviors/behavior_animation.c +++ b/app/src/behaviors/behavior_animation.c @@ -4,10 +4,11 @@ * SPDX-License-Identifier: MIT */ -#include -#include +#include +#include +#include + #include -#include #include @@ -89,5 +90,5 @@ static const struct behavior_driver_api behavior_animation_driver_api = { .binding_released = on_keymap_binding_released, }; -DEVICE_DT_INST_DEFINE(0, behavior_animation_init, device_pm_control_nop, NULL, NULL, APPLICATION, +DEVICE_DT_INST_DEFINE(0, behavior_animation_init, NULL, NULL, NULL, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_animation_driver_api); From cb22fa885a1818ad213cd496965751e24b37b49f Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Tue, 13 Jun 2023 16:00:21 +0200 Subject: [PATCH 28/34] Fix formatting --- app/Kconfig | 14 +++++++------- app/dts/animation.dtsi | 10 +++++----- app/src/animation/animation.c | 6 ++---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 354c0b36..258f21a0 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -302,19 +302,19 @@ config ZMK_BACKLIGHT_AUTO_OFF_USB endif config ZMK_ANIMATION - bool "RGB Animations" - select LED_STRIP + bool "RGB Animations" + select LED_STRIP if ZMK_ANIMATION config ZMK_ANIMATION_FPS - int "Animation FPS" - range 0 60 - default 30 + int "Animation FPS" + range 0 60 + default 30 config ZMK_ANIMATION_PIXEL_DISTANCE - bool "Generate a lookup table for distances between pixels" - default y + bool "Generate a lookup table for distances between pixels" + default y #ZMK_ANIMATION endif diff --git a/app/dts/animation.dtsi b/app/dts/animation.dtsi index 15ed2145..1d11c915 100644 --- a/app/dts/animation.dtsi +++ b/app/dts/animation.dtsi @@ -5,9 +5,9 @@ */ / { - /omit-if-no-ref/ pixel: animation_pixel { - compatible = "zmk,animation-pixel"; - label = "PIXEL"; - #pixel-cells = <2>; - }; + /omit-if-no-ref/ pixel: animation_pixel { + compatible = "zmk,animation-pixel"; + label = "PIXEL"; + #pixel-cells = <2>; + }; }; diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index 3d94bfb2..53a2f5f8 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -37,8 +37,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); /** * LED Driver device pointers. */ -static const struct device *drivers[] = { - DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE)}; +static const struct device *drivers[] = {DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE)}; /** * Size of the LED driver device pointers array. @@ -59,8 +58,7 @@ static const struct device *animation_root = DEVICE_DT_GET(DT_CHOSEN(zmk_animati /** * Pixel configuration. */ -static struct animation_pixel pixels[] = { - DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL)}; +static struct animation_pixel pixels[] = {DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL)}; /** * Size of the pixels array. From 70c6f9f1a0a7af806cb9c4aed8f14ac5fda895c1 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Mon, 5 Jun 2023 23:52:16 -0500 Subject: [PATCH 29/34] feat(docs): added documentation for animation subsystem Added documentation for animation subsystem, to make it easier for developers to get started using it. Signed-off-by: Daniel DeGrasse --- docs/docs/behaviors/animations.md | 52 +++++++ docs/docs/features/animations.md | 226 ++++++++++++++++++++++++++++++ docs/sidebars.js | 2 + 3 files changed, 280 insertions(+) create mode 100644 docs/docs/behaviors/animations.md create mode 100644 docs/docs/features/animations.md diff --git a/docs/docs/behaviors/animations.md b/docs/docs/behaviors/animations.md new file mode 100644 index 00000000..93942207 --- /dev/null +++ b/docs/docs/behaviors/animations.md @@ -0,0 +1,52 @@ +--- +title: Animation Control Behavior +sidebar_label: Animation Control +--- + +## Summary + +This page contains [Animations](../features/animations.md) behaviors supported by ZMK. + +## Animation Control Defines + +RGB actions defines are provided through the `dt-bindings/zmk/animation.h` header, +which is added at the top of the keymap file: + +``` +#include +``` + +This will allow you to reference the actions defined in this header such as +`ANIMATION_TOGGLE`. + +Here is a table describing the action for each define: + +| Define | Action | +| ------------------------------------ | ------------------------------------------------------------- | +| `ANIMATION_TOGGLE` | Toggles active animation | +| `ANIMATION_NEXT` | Selects next animation within root animation node array | +| `ANIMATION_PREVIOUS` | Selects previous animation within root animation node array | +| `ANIMATION_SELECT(x)` | Selects animation within root animation node at index `x` | +| `ANIMATION_BRIGHTEN` | Increases brightness (applied after other blending) | +| `ANIMATION_DIM` | Decreases brightness (applied after other blending) | + +## Examples + +1. Toggle active animation on/off + + ``` + &animation ANIMATION_TOGGLE(0) + ``` + +2. Switch to next animation + + ``` + &animation ANIMATION_NEXT(0) + ``` + +3. Select animation at index 3 + + ``` + &animation ANIMATION_SELECT(3) + ``` + diff --git a/docs/docs/features/animations.md b/docs/docs/features/animations.md new file mode 100644 index 00000000..02628fbd --- /dev/null +++ b/docs/docs/features/animations.md @@ -0,0 +1,226 @@ +--- +title: RGB Animations +sidebar_label: RGB Animations +--- + +ZMK supports animation effects on RGB control strips via the animation +subsystem. + +## Enabling Animation Support + +Set `CONFIG_ZMK_ANIMATION=y` to enable animation support on your keyboard. +See animation configuration for instructions on how to customize animation +support. + +If your board does not have animation support configured, see +[Adding Animation Support.](#adding-animation-support) + +## Color settings + +All colors are given in [HSL](https://www.w3schools.com/colors/colors_hsl.asp) +format. The user should specify colors in this format, although they will +be written to the LED chain driver in RGB format. + +## System Design + +This subsystem is made up of 3 key components: +- The core animations layer +- Animation drivers +- Color utilites + +### Core Animation Layer + +The core animation layer is responsible for scheduling animations, kicking +off rendering of new frames, and sending frame data to the LED strip via +Zephyr's LED Strip API. + +### Animation Drivers + +Each animation effect is implemented as a driver. The animation API exposes +the following functions: + +| Function | Purpose | +| ------------------------ | ------------------------------------- | +| `animation_start` | starts rendering the animation effect | +| `animation_stop` | ends rendering of the effect | +| `animation_render_frame` | renders a frame of the animation | + +Each driver will also likely utilize `ZMK_LISTENER` to listen for keypress +events. A driver can then request the animation subsystem render frames +using `zmk_animation_request_frames`. + + +### Color Utilities + +Color utilities are used by the animation drivers to perform color conversions. +Since colors are given in HSL format, `zmk_hsl_to_rgb` is provided to +convert user color input from devicetree to RGB data before writing to an +animation output pixel. + +## Adding Animation Support + +Adding animation support to your board will require the following devicetree +changes: +- Animation core node with `zmk,animation` compatible describing + "pixels" within animation as well as LED drivers to write output pixels to +- root animation node with `zmk,animation-control` compatible to cycle though + animations +- animation driver nodes for all animations the user would like to implement + +### Animation Core Node + +This node describes all output "pixels" on your keyboard. These pixels +correspond to individual LEDs on your LED chain. The animation subsystem +will write these pixels to your LED chain driver in the order they are +declared. + +Each pixel has an X and Y coordinate, which helps the animation +subsystem determine how effects like a ripple should be rendered on your +keyboard. X and Y coordinates can range from 0-255. + +The node also needs to have references to your LED strip driver nodes within +devicetree. Multiple LED strip nodes can be provided, but each node must have +a `chain-length` property equal to the number of pixels in the LED chain. +When multiple LED strip nodes are provided, the `chain-length` of each will be +used to determine how many pixels to write. Pixels are written sequentially, so +the pixels for the second LED driver should be declared after those written +to the first one. + +:::note + +The total number of pixels defined in your `pixel` array property should match +the sum of all `chain-length` properties for the LED strip drivers you will be +using. + +::: + +For example, here is a chain of 6 pixels, using two LED drivers: +``` +animation { + compatible = "zmk,animation"; + drivers = <&led_ctrl1 &led_ctrl2>; + pixels = <&pixel 0 0>, /* Pixel 0 is at (0,0) */ + <&pixel 21 0>, /* Pixel 1 is (0, 21) */ + <&pixel 42 0>, + <&pixel 63 0>, + <&pixel 84 0>, + <&pixel 105 0>, + <&pixel 255 255>; /* Max coordinate (255, 255) */ +} +``` + +In this case, if `led_ctrl1` has a `chain-length` of 4 and `led_ctrl2` has +a `chain-length` of 2, then the first 4 pixels will be written to `led_ctrl1`, +and the final two will be written to `led_ctrl2`. + + +## Animation Root Node + +Your animation root node should be an instance of the `zmk,animation-control` +driver. This driver allows you to cycle though other animation effects using +a keybinding. + +A simple root animation node looks like this: + +``` +root_animation: animation_0 { + compatible = "zmk,animation-control"; + label = "ANIMATION_CTRL_0"; + /* List of all animations to cycle through */ + animations = <&ripple_effect &solid_color>; +}; +``` +You should set the `zmk,animation` chosen node to this root animation: + +``` +chosen { + zmk,animation = &root_animation; +}; +``` + +Finally, you should add a keybinding to use the animation control feature +within your keymap: +``` +#include + +default_layer { + /* Basic binding, toggles animation on and off */ + bindings = <&animation ANIMATION_TOGGLE(0)>; +} +``` + +## Animation Drivers + +Animation drivers allow you to create custom effects on your keyboard, +and implement different animation zones. Zone implementation is performed +using the `pixels` property, which sets the pixels from the `pixels` property +in the core animation node that the animation effect will apply to. + +### Blending Modes + +Several animations can utilize blending effects via the +`zmk_apply_blending_mode` API. The following blending modes are supported: + +| Mode | Affect | +|----------|---------------------------------------------------------------------------| +| Multiply | Multiplies each pixel's color by the new effect's pixel color | +| Lighten | Selects the highest R, G, and B values between old and new for each pixel | +| Darken | Selects the lowest R, G, and B values between old and new for each pixel | +| Screen | Adds (1 - base {R,G,B}) * new {R,G,B} value to base {R,G,B} value | + + +### Ripple Effect + +Creates a ripple effect on your keyboard each time a key is pressed, +originating at the pixel corresponding to the key. An example of +a ripple effect node is given below. + +``` +ripple_effect: animation_1 { + compatible = "zmk,animation-ripple"; + status = "okay"; + /* Will only apply the effect to the first 4 pixels */ + pixels = <0 1 2 3>; + /* Sets how this effect will be blended with other animations */ + blending-mode = ; + /* Duration of animation in ms */ + duration = <1000>; + /* Will result in a pure white */ + color = ; + ripple-width = <50>; +}; +``` + +### Solid Color + +Creates a solid color background. This effect can be combined with other +effects such as the ripple animation to produce a static background, or +zone specific lighting + +``` +solid_color: color { + compatible = "zmk,animation-solid"; + status = "okay"; + /* All pixels will be illuminated */ + pixels = <0 1 2 3 4 5 6>; + colors = ; +}; +``` + +### Compose Animation + +Composes multiple animations together, allowing you to combine animation zones +or effects. Each animation will be rendered sequentially. If an animation has +a blending mode set, then it will be blended onto the current state of the +pixels + +``` +combine_zones: animation_1 { + compatible = "zmk,animation-compose"; + status = "okay"; + animations = < + &solid_color + &ripple_effect + >; +}; +``` diff --git a/docs/sidebars.js b/docs/sidebars.js index 43f17b41..8f8da93d 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -20,6 +20,7 @@ module.exports = { "features/backlight", "features/battery", "features/beta-testing", + "features/animations", ], Behaviors: [ "behaviors/key-press", @@ -42,6 +43,7 @@ module.exports = { "behaviors/underglow", "behaviors/backlight", "behaviors/power", + "behaviors/animations", ], Codes: [ "codes/index", From e929ec7175f669cf94288c87406a1f13d2fe0648 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Wed, 21 Jun 2023 11:44:38 +0200 Subject: [PATCH 30/34] Remove accidental CMakeLists.txt entry --- app/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 2593ab65..b030ccc3 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -93,7 +93,6 @@ add_subdirectory(src/split) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c) target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c) -target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/behaviors/behavior_bt.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) target_sources(app PRIVATE src/main.c) From 66180b5480341489e060e6117bdc18fa9fdad8b1 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Wed, 21 Jun 2023 11:46:00 +0200 Subject: [PATCH 31/34] Update ZMK_ANIMATION_FPS range --- app/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Kconfig b/app/Kconfig index 164e1f36..699b72f3 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -313,7 +313,7 @@ if ZMK_ANIMATION config ZMK_ANIMATION_FPS int "Animation FPS" - range 0 60 + range 1 60 default 30 config ZMK_ANIMATION_PIXEL_DISTANCE From 8bcee4f3b9b340486e7f9dd11acf5ae7bee30b5f Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Wed, 21 Jun 2023 12:19:46 +0200 Subject: [PATCH 32/34] Tidy up devicetree bindings --- app/dts/bindings/animations/animation_base.yaml | 17 ++++++++--------- .../animations/zmk,animation-control.yaml | 1 - .../animations/zmk,animation-ripple.yaml | 3 +-- .../animations/zmk,animation-solid.yaml | 3 +-- app/dts/bindings/zmk,animation.yaml | 9 +++++++-- app/src/animation/animation.c | 6 +----- app/src/animation/animation_ripple.c | 2 +- 7 files changed, 19 insertions(+), 22 deletions(-) diff --git a/app/dts/bindings/animations/animation_base.yaml b/app/dts/bindings/animations/animation_base.yaml index a8f10655..342ee894 100644 --- a/app/dts/bindings/animations/animation_base.yaml +++ b/app/dts/bindings/animations/animation_base.yaml @@ -9,15 +9,14 @@ properties: Pixel positions to which the animation should apply. blending-mode: - type: int - required: false + type: string enum: - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - default: 0 + - "normal" + - "multiply" + - "lighten" + - "darken" + - "screen" + - "subtract" + default: "normal" description: | Blending mode for the animation to use during render. diff --git a/app/dts/bindings/animations/zmk,animation-control.yaml b/app/dts/bindings/animations/zmk,animation-control.yaml index 7549f044..82f049b1 100644 --- a/app/dts/bindings/animations/zmk,animation-control.yaml +++ b/app/dts/bindings/animations/zmk,animation-control.yaml @@ -21,7 +21,6 @@ properties: brightness-steps: type: int - required: false default: 5 description: | How many brightness steps should be supported. diff --git a/app/dts/bindings/animations/zmk,animation-ripple.yaml b/app/dts/bindings/animations/zmk,animation-ripple.yaml index 88f5416c..df1a7eea 100644 --- a/app/dts/bindings/animations/zmk,animation-ripple.yaml +++ b/app/dts/bindings/animations/zmk,animation-ripple.yaml @@ -11,7 +11,6 @@ include: animation_base.yaml properties: duration: type: int - required: false default: 1000 description: | Approximate ripple travel time in milliseconds. @@ -20,7 +19,7 @@ properties: type: int required: true description: | - Ripple color. + Ripple color in HSL format. buffer-size: type: int diff --git a/app/dts/bindings/animations/zmk,animation-solid.yaml b/app/dts/bindings/animations/zmk,animation-solid.yaml index b4247885..5cca9f27 100644 --- a/app/dts/bindings/animations/zmk,animation-solid.yaml +++ b/app/dts/bindings/animations/zmk,animation-solid.yaml @@ -11,7 +11,6 @@ include: animation_base.yaml properties: duration: type: int - required: false default: 5 description: | Animation duration in seconds. @@ -22,4 +21,4 @@ properties: type: array required: true description: | - The colors to cycle through during the animation. + The colors to cycle through during the animation in HSL format. diff --git a/app/dts/bindings/zmk,animation.yaml b/app/dts/bindings/zmk,animation.yaml index 32b90395..1f82f7b4 100644 --- a/app/dts/bindings/zmk,animation.yaml +++ b/app/dts/bindings/zmk,animation.yaml @@ -11,7 +11,13 @@ properties: required: true description: | This array should contain all driver devices responsible for illuminating animated LEDs. - The devices must implement Zephyr's LED Strip Interface and expose a chain-lenght devicetree property. + The devices must implement Zephyr's LED Strip Interface. + + chain-lengths: + type: array + required: true + description: | + This field contains the number of LEDs controlled by each driver device. pixels: type: phandle-array @@ -23,7 +29,6 @@ properties: key-pixels: type: array - required: false description: | Use this field to specify the pixel index corresponding to each key following the order used in your keymap. diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index 53a2f5f8..ad3b1500 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -25,9 +25,6 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define PHANDLE_TO_DEVICE(node_id, prop, idx) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), -#define PHANDLE_TO_CHAIN_LENGTH(node_id, prop, idx) \ - DT_PROP_BY_PHANDLE_IDX(node_id, prop, idx, chain_length), - #define PHANDLE_TO_PIXEL(node_id, prop, idx) \ { \ .position_x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \ @@ -47,8 +44,7 @@ static const size_t drivers_size = DT_INST_PROP_LEN(0, drivers); /** * Array containing the number of LEDs handled by each device. */ -static const uint8_t pixels_per_driver[] = { - DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH)}; +static const uint8_t pixels_per_driver[] = DT_INST_PROP(0, chain_lengths); /** * Pointer to the root animation diff --git a/app/src/animation/animation_ripple.c b/app/src/animation/animation_ripple.c index 80cae740..aa1b2966 100644 --- a/app/src/animation/animation_ripple.c +++ b/app/src/animation/animation_ripple.c @@ -184,7 +184,7 @@ static const struct animation_api animation_ripple_api = { .pixel_map = &animation_ripple_##idx##_pixel_map[0], \ .pixel_map_size = DT_INST_PROP_LEN(idx, pixels), \ .event_buffer_size = DT_INST_PROP(idx, buffer_size), \ - .blending_mode = DT_INST_PROP(idx, blending_mode), \ + .blending_mode = DT_INST_ENUM_IDX(idx, blending_mode), \ .distance_per_frame = \ (255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS, \ .ripple_width = DT_INST_PROP(idx, ripple_width) / 2, \ From 82591843d136eaabee3e28196c0f3f650369c49d Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Wed, 21 Jun 2023 12:36:20 +0200 Subject: [PATCH 33/34] Fix bug with counting updated pixels --- app/src/animation/animation.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c index ad3b1500..7aed1000 100644 --- a/app/src/animation/animation.c +++ b/app/src/animation/animation.c @@ -44,7 +44,7 @@ static const size_t drivers_size = DT_INST_PROP_LEN(0, drivers); /** * Array containing the number of LEDs handled by each device. */ -static const uint8_t pixels_per_driver[] = DT_INST_PROP(0, chain_lengths); +static const size_t pixels_per_driver[] = DT_INST_PROP(0, chain_lengths); /** * Pointer to the root animation @@ -121,7 +121,7 @@ static void zmk_animation_tick(struct k_work *work) { for (size_t i = 0; i < drivers_size; ++i) { led_strip_update_rgb(drivers[i], &px_buffer[pixels_updated], pixels_per_driver[i]); - pixels_updated += (size_t)pixels_per_driver; + pixels_updated += pixels_per_driver[i]; } } From 180882cb89a6e152d956ad7f2d087ad4fcbe8a1a Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Wed, 21 Jun 2023 12:38:32 +0200 Subject: [PATCH 34/34] Fix issue with calculating discance in animation_ripple.c --- app/src/animation/animation_ripple.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/animation/animation_ripple.c b/app/src/animation/animation_ripple.c index aa1b2966..7704d90e 100644 --- a/app/src/animation/animation_ripple.c +++ b/app/src/animation/animation_ripple.c @@ -99,7 +99,8 @@ static void animation_ripple_render_frame(const struct device *dev, struct anima // Render all pixels for each event for (int j = 0; j < config->pixel_map_size; ++j) { - uint8_t pixel_distance = zmk_animation_get_pixel_distance(event->pixel_id, j); + uint8_t pixel_distance = + zmk_animation_get_pixel_distance(event->pixel_id, pixel_map[j]); if (config->ripple_width > abs(pixel_distance - event->distance)) { float intensity = 1.0f - (float)abs(pixel_distance - event->distance) /