436 lines
9.6 KiB
C
436 lines
9.6 KiB
C
/**
|
|
* @file animation.c
|
|
* @brief LED animation patterns implementation
|
|
*/
|
|
|
|
#include "animation.h"
|
|
#include "led.h"
|
|
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "esp_random.h"
|
|
|
|
#include <math.h>
|
|
|
|
static const char *TAG = "ANIMATION";
|
|
|
|
#define FRAMES_PER_SECOND 60
|
|
|
|
static animation_mode_t current_mode = ANIM_BLACK;
|
|
static uint8_t global_hue = 0;
|
|
static uint32_t frame_counter = 0;
|
|
|
|
// Beat calculation helper (similar to FastLED beatsin16)
|
|
static int16_t beatsin16(uint8_t bpm, int16_t min_val, int16_t max_val)
|
|
{
|
|
uint32_t ms = esp_timer_get_time() / 1000;
|
|
uint32_t beat = (ms * bpm * 256) / 60000;
|
|
uint8_t beat8 = (beat >> 8) & 0xFF;
|
|
|
|
// Sin approximation
|
|
float angle = (beat8 / 255.0f) * 2.0f * M_PI;
|
|
float sin_val = sinf(angle);
|
|
|
|
int16_t range = max_val - min_val;
|
|
int16_t result = min_val + (int16_t)((sin_val + 1.0f) * range / 2.0f);
|
|
|
|
return result;
|
|
}
|
|
|
|
// Beat calculation helper (beatsin8 variant)
|
|
static uint8_t beatsin8(uint8_t bpm, uint8_t min_val, uint8_t max_val)
|
|
{
|
|
return (uint8_t)beatsin16(bpm, min_val, max_val);
|
|
}
|
|
|
|
// Random helper
|
|
static uint8_t random8(void)
|
|
{
|
|
return esp_random() & 0xFF;
|
|
}
|
|
|
|
static uint16_t random16(uint16_t max)
|
|
{
|
|
if (max == 0)
|
|
return 0;
|
|
return esp_random() % max;
|
|
}
|
|
|
|
// Animation implementations
|
|
static void anim_black(void)
|
|
{
|
|
rgb_t black = {0, 0, 0};
|
|
led_fill_a(black);
|
|
led_fill_b(black);
|
|
}
|
|
|
|
static void anim_red(void)
|
|
{
|
|
rgb_t red = {255, 0, 0};
|
|
led_fill_a(red);
|
|
led_fill_b(red);
|
|
}
|
|
|
|
static void anim_blue(void)
|
|
{
|
|
rgb_t blue = {0, 0, 255};
|
|
led_fill_a(blue);
|
|
led_fill_b(blue);
|
|
}
|
|
|
|
static void anim_green(void)
|
|
{
|
|
rgb_t green = {0, 255, 0};
|
|
led_fill_a(green);
|
|
led_fill_b(green);
|
|
}
|
|
|
|
static void anim_white(void)
|
|
{
|
|
rgb_t white = {255, 255, 255};
|
|
led_fill_a(white);
|
|
led_fill_b(white);
|
|
}
|
|
|
|
static void anim_rainbow(void)
|
|
{
|
|
// FastLED's built-in rainbow generator
|
|
uint16_t num_leds_a = led_get_num_leds_a();
|
|
uint16_t num_leds_b = led_get_num_leds_b();
|
|
|
|
for (uint16_t i = 0; i < num_leds_a; i++)
|
|
{
|
|
hsv_t hsv = {(uint8_t)(global_hue + (i * 7)), 255, 255};
|
|
led_set_pixel_a(i, led_hsv_to_rgb(hsv));
|
|
}
|
|
|
|
for (uint16_t i = 0; i < num_leds_b; i++)
|
|
{
|
|
hsv_t hsv = {(uint8_t)(global_hue + (i * 7)), 255, 255};
|
|
led_set_pixel_b(i, led_hsv_to_rgb(hsv));
|
|
}
|
|
}
|
|
|
|
static void add_glitter(uint8_t chance_of_glitter)
|
|
{
|
|
if (random8() < chance_of_glitter)
|
|
{
|
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
|
uint16_t pos = random16(num_leds);
|
|
rgb_t white = {255, 255, 255};
|
|
|
|
if (pos < led_get_num_leds_a())
|
|
{
|
|
led_add_pixel_a(pos, white);
|
|
}
|
|
else
|
|
{
|
|
led_add_pixel_b(pos - led_get_num_leds_a(), white);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void anim_rainbow_glitter(void)
|
|
{
|
|
anim_rainbow();
|
|
add_glitter(80);
|
|
}
|
|
|
|
static void anim_confetti(void)
|
|
{
|
|
// Random colored speckles that blink in and fade smoothly
|
|
led_fade_to_black(10);
|
|
|
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
|
uint16_t pos = random16(num_leds);
|
|
|
|
hsv_t hsv = {(uint8_t)(global_hue + random8()), 200, 255};
|
|
rgb_t color = led_hsv_to_rgb(hsv);
|
|
|
|
if (pos < led_get_num_leds_a())
|
|
{
|
|
led_add_pixel_a(pos, color);
|
|
}
|
|
else
|
|
{
|
|
led_add_pixel_b(pos - led_get_num_leds_a(), color);
|
|
}
|
|
}
|
|
|
|
static void anim_sinelon(void)
|
|
{
|
|
// A colored dot sweeping back and forth, with fading trails
|
|
led_fade_to_black(20);
|
|
|
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
|
int16_t pos = beatsin16(13, 0, num_leds - 1);
|
|
|
|
hsv_t hsv = {global_hue, 255, 192};
|
|
rgb_t color = led_hsv_to_rgb(hsv);
|
|
|
|
if (pos < led_get_num_leds_a())
|
|
{
|
|
led_add_pixel_a(pos, color);
|
|
}
|
|
else
|
|
{
|
|
led_add_pixel_b(pos - led_get_num_leds_a(), color);
|
|
}
|
|
}
|
|
|
|
static void anim_bpm(void)
|
|
{
|
|
// Colored stripes pulsing at 33 BPM
|
|
uint8_t bpm = 33;
|
|
uint8_t beat = beatsin8(bpm, 64, 255);
|
|
|
|
uint16_t num_leds_a = led_get_num_leds_a();
|
|
uint16_t num_leds_b = led_get_num_leds_b();
|
|
|
|
// PartyColors palette simulation
|
|
const uint8_t palette_colors[] = {
|
|
170, 240, 90, 150, 210, 30, 180, 0,
|
|
210, 255, 150, 240, 255, 60, 255, 120};
|
|
|
|
for (uint16_t i = 0; i < num_leds_a; i++)
|
|
{
|
|
uint8_t color_index = (global_hue + (i * 2)) & 0x0F;
|
|
uint8_t brightness = beat - global_hue + (i * 10);
|
|
hsv_t hsv = {palette_colors[color_index], 255, brightness};
|
|
led_set_pixel_a(i, led_hsv_to_rgb(hsv));
|
|
}
|
|
|
|
for (uint16_t i = 0; i < num_leds_b; i++)
|
|
{
|
|
uint8_t color_index = (global_hue + ((i + num_leds_a) * 2)) & 0x0F;
|
|
uint8_t brightness = beat - global_hue + ((i + num_leds_a) * 10);
|
|
hsv_t hsv = {palette_colors[color_index], 255, brightness};
|
|
led_set_pixel_b(i, led_hsv_to_rgb(hsv));
|
|
}
|
|
}
|
|
|
|
static void anim_navigation(void)
|
|
{
|
|
// Navigation lights: left red, right green
|
|
led_clear_all();
|
|
|
|
uint16_t num_leds_a = led_get_num_leds_a();
|
|
uint16_t num_leds_b = led_get_num_leds_b();
|
|
|
|
rgb_t red = {255, 0, 0};
|
|
rgb_t green = {0, 255, 0};
|
|
|
|
// Side red (last 3 LEDs of strip A)
|
|
if (num_leds_a >= 3)
|
|
{
|
|
led_set_pixel_a(num_leds_a - 1, red);
|
|
led_set_pixel_a(num_leds_a - 2, red);
|
|
led_set_pixel_a(num_leds_a - 3, red);
|
|
}
|
|
|
|
// Side green (last 3 LEDs of strip B)
|
|
if (num_leds_b >= 3)
|
|
{
|
|
led_set_pixel_b(num_leds_b - 1, green);
|
|
led_set_pixel_b(num_leds_b - 2, green);
|
|
led_set_pixel_b(num_leds_b - 3, green);
|
|
}
|
|
}
|
|
|
|
static void anim_chase(void)
|
|
{
|
|
// Red dot sweeping with trailing dots
|
|
led_clear_all();
|
|
|
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
|
int16_t pos = beatsin16(40, 0, num_leds - 1);
|
|
|
|
rgb_t red = {255, 0, 0};
|
|
|
|
// Set main dot and trailing dots
|
|
for (int offset = -2; offset <= 2; offset++)
|
|
{
|
|
int16_t led_pos = pos + offset;
|
|
if (led_pos >= 0 && led_pos < num_leds)
|
|
{
|
|
if (led_pos < led_get_num_leds_a())
|
|
{
|
|
led_set_pixel_a(led_pos, red);
|
|
}
|
|
else
|
|
{
|
|
led_set_pixel_b(led_pos - led_get_num_leds_a(), red);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void anim_chase_rgb(void)
|
|
{
|
|
// RGB cycling dot sweeping with trailing dots
|
|
led_clear_all();
|
|
|
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
|
int16_t pos = beatsin16(40, 0, num_leds - 1);
|
|
|
|
hsv_t hsv = {global_hue, 255, 192};
|
|
rgb_t color = led_hsv_to_rgb(hsv);
|
|
|
|
// Set main dot and trailing dots
|
|
for (int offset = -2; offset <= 2; offset++)
|
|
{
|
|
int16_t led_pos = pos + offset;
|
|
if (led_pos >= 0 && led_pos < num_leds)
|
|
{
|
|
if (led_pos < led_get_num_leds_a())
|
|
{
|
|
led_add_pixel_a(led_pos, color);
|
|
}
|
|
else
|
|
{
|
|
led_add_pixel_b(led_pos - led_get_num_leds_a(), color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void anim_random(void)
|
|
{
|
|
// Random LEDs get random colors
|
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
|
uint16_t random_pos = random16(num_leds);
|
|
|
|
// Randomly clear all (rare event)
|
|
if (random_pos == num_leds - 1 && random8() > 200)
|
|
{
|
|
led_clear_all();
|
|
return;
|
|
}
|
|
|
|
// Set random LED to random color
|
|
rgb_t random_color = {
|
|
random8(),
|
|
random8(),
|
|
random8()};
|
|
|
|
if (random_pos < led_get_num_leds_a())
|
|
{
|
|
led_set_pixel_a(random_pos, random_color);
|
|
}
|
|
else
|
|
{
|
|
led_set_pixel_b(random_pos - led_get_num_leds_a(), random_color);
|
|
}
|
|
}
|
|
|
|
esp_err_t animation_init(void)
|
|
{
|
|
current_mode = ANIM_BLACK;
|
|
global_hue = 0U;
|
|
frame_counter = 0U;
|
|
|
|
ESP_LOGI(TAG, "Animation initialized");
|
|
return ESP_OK;
|
|
}
|
|
|
|
void animation_set_mode(animation_mode_t mode)
|
|
{
|
|
if ((mode >= ANIM_MODE_COUNT) || (mode < 0U))
|
|
{
|
|
mode = ANIM_BLACK;
|
|
}
|
|
|
|
current_mode = mode;
|
|
frame_counter = 0U;
|
|
|
|
ESP_LOGI(TAG, "Animation mode set to: %s", animation_get_mode_name(mode));
|
|
}
|
|
|
|
void animation_update(void)
|
|
{
|
|
// Update global hue every frame (slowly cycles colors)
|
|
frame_counter++;
|
|
if (frame_counter % 3 == 0)
|
|
{
|
|
global_hue++;
|
|
}
|
|
|
|
// Execute current animation
|
|
switch (current_mode)
|
|
{
|
|
case ANIM_BLACK:
|
|
anim_black();
|
|
break;
|
|
case ANIM_RED:
|
|
anim_red();
|
|
break;
|
|
case ANIM_BLUE:
|
|
anim_blue();
|
|
break;
|
|
case ANIM_GREEN:
|
|
anim_green();
|
|
break;
|
|
case ANIM_WHITE:
|
|
anim_white();
|
|
break;
|
|
case ANIM_RAINBOW:
|
|
//anim_rainbow();
|
|
break;
|
|
case ANIM_RAINBOW_GLITTER:
|
|
//anim_rainbow_glitter();
|
|
break;
|
|
case ANIM_CONFETTI:
|
|
//anim_confetti();
|
|
break;
|
|
case ANIM_SINELON:
|
|
//anim_sinelon();
|
|
break;
|
|
case ANIM_BPM:
|
|
//anim_bpm();
|
|
break;
|
|
case ANIM_NAVIGATION:
|
|
anim_navigation();
|
|
break;
|
|
case ANIM_CHASE:
|
|
//anim_chase();
|
|
break;
|
|
case ANIM_CHASE_RGB:
|
|
//anim_chase_rgb();
|
|
break;
|
|
case ANIM_RANDOM:
|
|
//anim_random();
|
|
break;
|
|
default:
|
|
anim_black();
|
|
break;
|
|
}
|
|
|
|
led_show();
|
|
}
|
|
|
|
const char *animation_get_mode_name(animation_mode_t mode)
|
|
{
|
|
static const char *mode_names[] = {
|
|
"Black",
|
|
"Red",
|
|
"Blue",
|
|
"Green",
|
|
"White",
|
|
"Rainbow",
|
|
"Rainbow with Glitter",
|
|
"Confetti",
|
|
"Sinelon",
|
|
"33BPM",
|
|
"Navigation",
|
|
"Chase",
|
|
"Chase RGB",
|
|
"Random"};
|
|
|
|
if (mode >= ANIM_MODE_COUNT)
|
|
{
|
|
return "Unknown";
|
|
}
|
|
|
|
return mode_names[mode];
|
|
}
|