/** * @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 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, with blinking white static uint8_t blink_state = 0; 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}; rgb_t white = {255, 255, 255}; // Left side red (first 3 LEDs of strip A) if (num_leds_a >= 3) { led_set_pixel_a(0, red); led_set_pixel_a(1, red); led_set_pixel_a(2, red); } // Right side green (last 3 LEDs) 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); } else if (num_leds_a >= 6) { led_set_pixel_a(num_leds_a - 1, green); led_set_pixel_a(num_leds_a - 2, green); led_set_pixel_a(num_leds_a - 3, green); } // Blinking white lights (positions 5-6 and 37-38 from original) if (blink_state < FRAMES_PER_SECOND / 2) { if (num_leds_a > 6) { led_set_pixel_a(5, white); led_set_pixel_a(6, white); } if (num_leds_b > 2) { led_set_pixel_b(1, white); led_set_pixel_b(2, white); } else if (num_leds_a > 38) { led_set_pixel_a(37, white); led_set_pixel_a(38, white); } } blink_state = (blink_state + 1) % FRAMES_PER_SECOND; } 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 = 0; frame_counter = 0; ESP_LOGI(TAG, "Animation system initialized"); return ESP_OK; } void animation_set_mode(animation_mode_t mode) { if (mode >= ANIM_MODE_COUNT) { mode = ANIM_BLACK; } current_mode = mode; frame_counter = 0; ESP_LOGI(TAG, "Animation mode set to: %s", animation_get_mode_name(mode)); } animation_mode_t animation_get_mode(void) { return current_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", "BPM", "Navigation", "Chase", "Chase RGB", "Random"}; if (mode >= ANIM_MODE_COUNT) { return "Unknown"; } return mode_names[mode]; }