zmk/app/src/animation/animation.c
2023-06-11 10:23:03 +02:00

213 lines
6.3 KiB
C

/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_animation
#include <zephyr.h>
#include <device.h>
#include <init.h>
#include <kernel.h>
#include <stdlib.h>
#include <math.h>
#include <logging/log.h>
#include <drivers/animation.h>
#include <drivers/led_strip.h>
#include <zmk/animation.h>
#include <zmk/event_manager.h>
#include <zmk/events/activity_state_changed.h>
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_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) \
{ \
.position_x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \
.position_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)};
/**
* 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[] = {
ZMK_DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH)};
/**
* Pointer to the root animation
*/
static const struct device *animation_root = DEVICE_DT_GET(DT_CHOSEN(zmk_animation));
/**
* Pixel configuration.
*/
static struct animation_pixel pixels[] = {
ZMK_DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL)};
/**
* Size of the pixels array.
*/
static const size_t pixels_size = DT_INST_PROP_LEN(0, pixels);
/**
* Buffer for RGB values ready to be sent to the drivers.
*/
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.
*/
#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.
*
* 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) + 1) * DT_INST_PROP_LEN(0, pixels)) / 2];
uint8_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t 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
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) {
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;
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;
}
}
K_WORK_DEFINE(animation_work, zmk_animation_tick);
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_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
int k = 0;
for (size_t i = 0; i < pixels_size; ++i) {
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[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
LOG_INF("ZMK Animation Ready");
animation_start(animation_root);
return 0;
}
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);