/* * Copyright (c) 2020 The ZMK Contributors * * SPDX-License-Identifier: MIT */ #define DT_DRV_COMPAT zmk_animation_control #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)), struct animation_control_work_context { const struct device *animation; struct k_work_delayable 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 { bool active; uint8_t brightness; 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) { #if IS_ENABLED(CONFIG_SETTINGS) 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; #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); 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_work_cancel_delayable(&ctx->save_work); return k_work_reschedule(&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; 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]); break; } 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; } #if IS_ENABLED(CONFIG_SETTINGS) animation_control_save_settings(dev); #endif /* IS_ENABLED(CONFIG_SETTINGS) */ // Force refresh zmk_animation_request_frames(1); return 0; } 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; 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 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) { #if IS_ENABLED(CONFIG_SETTINGS) const struct animation_control_config *config = dev->config; settings_subsys_init(); settings_register(config->settings_handler); k_work_init_delayable(&config->work->save_work, animation_control_save_work); settings_load_subtree(dev->name); #endif /* IS_ENABLED(CONFIG_SETTINGS) */ 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[] = { \ 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 = { \ .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);