Initial commit: D1 Mini Blinkin LED Driver Emulator for FTC

This commit is contained in:
Ryan Hill 2025-11-18 12:03:24 -06:00
commit dca4c4cb26
12 changed files with 2155 additions and 0 deletions

898
src/main.cpp Normal file
View file

@ -0,0 +1,898 @@
/**
* D1 Mini Blinkin LED Controller - Corrected to match REV Blinkin Java Driver
*
* This version correctly matches the pattern ordering from the Java RevBlinkinLedDriver class
*
* Hardware connections:
* - D2 (GPIO4): PWM input from REV Control Hub servo port
* - D4 (GPIO2): LED data output (WS2812B)
* - GND: Common ground with Control Hub
* - 5V: Power for D1 Mini (can be from USB or external)
*
* IMPORTANT: The pattern enum order MUST match the Java driver exactly!
*/
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <FastLED.h>
// Configuration
#define NUM_LEDS 120 // Number of LEDs in your strip
#define BRIGHTNESS 128 // LED brightness (0-255)
#define DEBUG_MODE true // Enable serial debugging
#define PWM_INPUT_PIN D2 // Pin for PWM input (GPIO4)
#define LED_DATA_PIN D4 // Pin for LED data (GPIO2)
#define ONBOARD_LED LED_BUILTIN // Onboard LED for status
// PWM constants
#define PWM_MIN 900 // Minimum valid PWM
#define PWM_MAX 2100 // Maximum valid PWM
#define PWM_TIMEOUT 100000 // Timeout in microseconds (100ms)
#define PWM_DEADBAND 5 // Deadband for PWM changes
#define PWM_FILTER_SAMPLES 3 // Number of samples for filtering
// LED strip
CRGB leds[NUM_LEDS];
// Pattern enumeration - MUST MATCH Java RevBlinkinLedDriver enum order EXACTLY!
enum Pattern {
// Fixed Palette Patterns (0-31)
RAINBOW_RAINBOW_PALETTE, // 0
RAINBOW_PARTY_PALETTE, // 1
RAINBOW_OCEAN_PALETTE, // 2
RAINBOW_LAVA_PALETTE, // 3
RAINBOW_FOREST_PALETTE, // 4
RAINBOW_WITH_GLITTER, // 5
CONFETTI, // 6
SHOT_RED, // 7
SHOT_BLUE, // 8
SHOT_WHITE, // 9
SINELON_RAINBOW_PALETTE, // 10
SINELON_PARTY_PALETTE, // 11
SINELON_OCEAN_PALETTE, // 12
SINELON_LAVA_PALETTE, // 13
SINELON_FOREST_PALETTE, // 14
BEATS_PER_MINUTE_RAINBOW_PALETTE, // 15
BEATS_PER_MINUTE_PARTY_PALETTE, // 16
BEATS_PER_MINUTE_OCEAN_PALETTE, // 17
BEATS_PER_MINUTE_LAVA_PALETTE, // 18
BEATS_PER_MINUTE_FOREST_PALETTE, // 19
FIRE_MEDIUM, // 20
FIRE_LARGE, // 21
TWINKLES_RAINBOW_PALETTE, // 22
TWINKLES_PARTY_PALETTE, // 23
TWINKLES_OCEAN_PALETTE, // 24
TWINKLES_LAVA_PALETTE, // 25
TWINKLES_FOREST_PALETTE, // 26
COLOR_WAVES_RAINBOW_PALETTE, // 27
COLOR_WAVES_PARTY_PALETTE, // 28
COLOR_WAVES_OCEAN_PALETTE, // 29
COLOR_WAVES_LAVA_PALETTE, // 30
COLOR_WAVES_FOREST_PALETTE, // 31
LARSON_SCANNER_RED, // 32
LARSON_SCANNER_GRAY, // 33
LIGHT_CHASE_RED, // 34
LIGHT_CHASE_BLUE, // 35
LIGHT_CHASE_GRAY, // 36
HEARTBEAT_RED, // 37
HEARTBEAT_BLUE, // 38
HEARTBEAT_WHITE, // 39
HEARTBEAT_GRAY, // 40
BREATH_RED, // 41
BREATH_BLUE, // 42
BREATH_GRAY, // 43
STROBE_RED, // 44
STROBE_BLUE, // 45
STROBE_GOLD, // 46
STROBE_WHITE, // 47
// CP1: Color 1 Patterns (48-57)
CP1_END_TO_END_BLEND_TO_BLACK, // 48
CP1_LARSON_SCANNER, // 49
CP1_LIGHT_CHASE, // 50
CP1_HEARTBEAT_SLOW, // 51
CP1_HEARTBEAT_MEDIUM, // 52
CP1_HEARTBEAT_FAST, // 53
CP1_BREATH_SLOW, // 54
CP1_BREATH_FAST, // 55
CP1_SHOT, // 56
CP1_STROBE, // 57
// CP2: Color 2 Patterns (58-67)
CP2_END_TO_END_BLEND_TO_BLACK, // 58
CP2_LARSON_SCANNER, // 59
CP2_LIGHT_CHASE, // 60
CP2_HEARTBEAT_SLOW, // 61
CP2_HEARTBEAT_MEDIUM, // 62
CP2_HEARTBEAT_FAST, // 63
CP2_BREATH_SLOW, // 64
CP2_BREATH_FAST, // 65
CP2_SHOT, // 66
CP2_STROBE, // 67
// CP1_2: Color 1 and 2 Patterns (68-77)
CP1_2_SPARKLE_1_ON_2, // 68
CP1_2_SPARKLE_2_ON_1, // 69
CP1_2_COLOR_GRADIENT, // 70
CP1_2_BEATS_PER_MINUTE, // 71
CP1_2_END_TO_END_BLEND_1_TO_2, // 72
CP1_2_END_TO_END_BLEND, // 73
CP1_2_NO_BLENDING, // 74
CP1_2_TWINKLES, // 75
CP1_2_COLOR_WAVES, // 76
CP1_2_SINELON, // 77
// Solid Colors (78-99)
HOT_PINK, // 78
DARK_RED, // 79
RED, // 80
RED_ORANGE, // 81
ORANGE, // 82
GOLD, // 83
YELLOW, // 84
LAWN_GREEN, // 85
LIME, // 86
DARK_GREEN, // 87
GREEN, // 88
BLUE_GREEN, // 89
AQUA, // 90
SKY_BLUE, // 91
DARK_BLUE, // 92
BLUE, // 93
BLUE_VIOLET, // 94
VIOLET, // 95
WHITE, // 96
GRAY, // 97
DARK_GRAY, // 98
BLACK // 99
};
// Custom color palette support
CRGB customColor1 = CRGB::Red;
CRGB customColor2 = CRGB::Blue;
// Global variables
Pattern currentPattern = BLACK;
Pattern lastPattern = BLACK;
unsigned long lastPWM = 0;
unsigned long lastPatternChange = 0;
unsigned long lastPWMReceived = 0;
bool isActive = false;
uint8_t gHue = 0; // For rainbow effects
// PWM filtering buffer
unsigned long pwmBuffer[PWM_FILTER_SAMPLES];
int pwmBufferIndex = 0;
void setup() {
// Initialize serial for debugging
if (DEBUG_MODE) {
Serial.begin(115200);
Serial.println("\n\nD1 Mini Blinkin LED Controller - Corrected Version");
Serial.println("Pattern order matches Java RevBlinkinLedDriver");
Serial.println("Using D2 for PWM input (GPIO4)");
Serial.println("Using D4 for LED output (GPIO2)");
Serial.println("Waiting for PWM signal...");
}
// Disable WiFi to save power
WiFi.mode(WIFI_OFF);
WiFi.forceSleepBegin();
// Initialize pins
pinMode(PWM_INPUT_PIN, INPUT);
pinMode(ONBOARD_LED, OUTPUT);
digitalWrite(ONBOARD_LED, HIGH); // LED off (inverted on D1 Mini)
// Initialize LED strip
FastLED.addLeds<WS2812B, LED_DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
FastLED.clear();
FastLED.show();
// Initialize PWM buffer
for (int i = 0; i < PWM_FILTER_SAMPLES; i++) {
pwmBuffer[i] = 0;
}
// Show startup animation
startupAnimation();
}
void loop() {
// Read PWM with filtering
unsigned long pwm = readPWMFiltered();
// Check if we have a valid PWM signal
if (pwm > 0) {
lastPWMReceived = millis();
isActive = true;
digitalWrite(ONBOARD_LED, LOW); // LED on
// Convert PWM to pattern
int newPattern = pwmToPattern(pwm);
// Check if pattern changed (with deadband)
if (newPattern != currentPattern && abs((int)(pwm - lastPWM)) > PWM_DEADBAND) {
lastPattern = currentPattern;
currentPattern = (Pattern)newPattern;
lastPatternChange = millis();
lastPWM = pwm;
if (DEBUG_MODE) {
Serial.print("Pattern: ");
Serial.print(getPatternName(currentPattern));
Serial.print(" (#");
Serial.print(newPattern);
Serial.print(", PWM: ");
Serial.print(pwm);
Serial.println(" us)");
}
}
} else {
// No PWM signal - check for timeout
if (millis() - lastPWMReceived > 500) { // 500ms timeout
isActive = false;
digitalWrite(ONBOARD_LED, HIGH); // LED off
currentPattern = BLACK;
}
}
// Update LED pattern
updatePattern(currentPattern);
// Periodic status update
static unsigned long lastStatus = 0;
if (DEBUG_MODE && millis() - lastStatus > 1000) {
lastStatus = millis();
Serial.print("Status: ");
Serial.print(isActive ? "Active" : "Inactive");
Serial.print(" | PWM: ");
Serial.print(lastPWM);
Serial.print(" | Pattern: ");
Serial.println(getPatternName(currentPattern));
}
}
unsigned long readPWMFiltered() {
// Read PWM pulse width with filtering
unsigned long pulse = pulseIn(PWM_INPUT_PIN, HIGH, PWM_TIMEOUT);
// Filter out invalid readings
if (pulse < PWM_MIN || pulse > PWM_MAX) {
return 0;
}
// Add to filter buffer
pwmBuffer[pwmBufferIndex] = pulse;
pwmBufferIndex = (pwmBufferIndex + 1) % PWM_FILTER_SAMPLES;
// Calculate average
unsigned long sum = 0;
int validSamples = 0;
for (int i = 0; i < PWM_FILTER_SAMPLES; i++) {
if (pwmBuffer[i] > 0) {
sum += pwmBuffer[i];
validSamples++;
}
}
if (validSamples > 0) {
return sum / validSamples;
}
return 0;
}
int pwmToPattern(unsigned long pwm) {
// Based on Java driver calculation:
// BASE_SERVO_POSITION = 0.2525 (505 * 0.0005)
// PATTERN_OFFSET = 10
// PWM = 1005 + (pattern_number * 10) microseconds
// Valid PWM range check
if (pwm < 1000 || pwm > 2000) {
return BLACK; // Default to BLACK for invalid PWM
}
// Calculate pattern number from PWM
// Pattern 0 should be at 1005μs, Pattern 1 at 1015μs, etc.
int patternNumber = (pwm - 1005) / 10;
// Add rounding for values in between
if ((pwm - 1005) % 10 >= 5) {
patternNumber++;
}
// Clamp to valid pattern range (0-99)
if (patternNumber < 0) patternNumber = 0;
if (patternNumber > 99) patternNumber = 99;
if (DEBUG_MODE && pwm != lastPWM) {
Serial.print("PWM decode: ");
Serial.print(pwm);
Serial.print("us -> Pattern #");
Serial.println(patternNumber);
}
return patternNumber;
}
void updatePattern(Pattern pattern) {
static unsigned long lastUpdate = 0;
unsigned long currentMillis = millis();
// Update animations at 60 FPS
if (currentMillis - lastUpdate < 16) return;
lastUpdate = currentMillis;
switch(pattern) {
// Fixed Palette Patterns
case RAINBOW_RAINBOW_PALETTE: rainbowCycle(); break;
case RAINBOW_PARTY_PALETTE: partyMode(); break;
case RAINBOW_OCEAN_PALETTE: oceanWave(); break;
case RAINBOW_LAVA_PALETTE: lavaFlow(); break;
case RAINBOW_FOREST_PALETTE: forestBreeze(); break;
case RAINBOW_WITH_GLITTER: rainbowWithGlitter(); break;
case CONFETTI: confetti(); break;
case SHOT_RED: shot(CRGB::Red); break;
case SHOT_BLUE: shot(CRGB::Blue); break;
case SHOT_WHITE: shot(CRGB::White); break;
case SINELON_RAINBOW_PALETTE: sinelon(RainbowColors_p); break;
case SINELON_PARTY_PALETTE: sinelon(PartyColors_p); break;
case SINELON_OCEAN_PALETTE: sinelon(OceanColors_p); break;
case SINELON_LAVA_PALETTE: sinelon(LavaColors_p); break;
case SINELON_FOREST_PALETTE: sinelon(ForestColors_p); break;
case BEATS_PER_MINUTE_RAINBOW_PALETTE: bpm(RainbowColors_p); break;
case BEATS_PER_MINUTE_PARTY_PALETTE: bpm(PartyColors_p); break;
case BEATS_PER_MINUTE_OCEAN_PALETTE: bpm(OceanColors_p); break;
case BEATS_PER_MINUTE_LAVA_PALETTE: bpm(LavaColors_p); break;
case BEATS_PER_MINUTE_FOREST_PALETTE: bpm(ForestColors_p); break;
case FIRE_MEDIUM: fire(55); break;
case FIRE_LARGE: fire(120); break;
case TWINKLES_RAINBOW_PALETTE: twinkles(RainbowColors_p); break;
case TWINKLES_PARTY_PALETTE: twinkles(PartyColors_p); break;
case TWINKLES_OCEAN_PALETTE: twinkles(OceanColors_p); break;
case TWINKLES_LAVA_PALETTE: twinkles(LavaColors_p); break;
case TWINKLES_FOREST_PALETTE: twinkles(ForestColors_p); break;
case COLOR_WAVES_RAINBOW_PALETTE: colorWaves(RainbowColors_p); break;
case COLOR_WAVES_PARTY_PALETTE: colorWaves(PartyColors_p); break;
case COLOR_WAVES_OCEAN_PALETTE: colorWaves(OceanColors_p); break;
case COLOR_WAVES_LAVA_PALETTE: colorWaves(LavaColors_p); break;
case COLOR_WAVES_FOREST_PALETTE: colorWaves(ForestColors_p); break;
case LARSON_SCANNER_RED: larsonScanner(CRGB::Red); break;
case LARSON_SCANNER_GRAY: larsonScanner(CRGB::Gray); break;
case LIGHT_CHASE_RED: lightChase(CRGB::Red); break;
case LIGHT_CHASE_BLUE: lightChase(CRGB::Blue); break;
case LIGHT_CHASE_GRAY: lightChase(CRGB::Gray); break;
case HEARTBEAT_RED: heartbeat(CRGB::Red); break;
case HEARTBEAT_BLUE: heartbeat(CRGB::Blue); break;
case HEARTBEAT_WHITE: heartbeat(CRGB::White); break;
case HEARTBEAT_GRAY: heartbeat(CRGB::Gray); break;
case BREATH_RED: breathe(CRGB::Red); break;
case BREATH_BLUE: breathe(CRGB::Blue); break;
case BREATH_GRAY: breathe(CRGB::Gray); break;
case STROBE_RED: strobe(CRGB::Red); break;
case STROBE_BLUE: strobe(CRGB::Blue); break;
case STROBE_GOLD: strobe(CRGB::Gold); break;
case STROBE_WHITE: strobe(CRGB::White); break;
// CP1 Patterns (use customColor1)
case CP1_END_TO_END_BLEND_TO_BLACK: endToEndBlendToBlack(customColor1); break;
case CP1_LARSON_SCANNER: larsonScanner(customColor1); break;
case CP1_LIGHT_CHASE: lightChase(customColor1); break;
case CP1_HEARTBEAT_SLOW: heartbeatSlow(customColor1); break;
case CP1_HEARTBEAT_MEDIUM: heartbeat(customColor1); break;
case CP1_HEARTBEAT_FAST: heartbeatFast(customColor1); break;
case CP1_BREATH_SLOW: breatheSlow(customColor1); break;
case CP1_BREATH_FAST: breatheFast(customColor1); break;
case CP1_SHOT: shot(customColor1); break;
case CP1_STROBE: strobe(customColor1); break;
// CP2 Patterns (use customColor2)
case CP2_END_TO_END_BLEND_TO_BLACK: endToEndBlendToBlack(customColor2); break;
case CP2_LARSON_SCANNER: larsonScanner(customColor2); break;
case CP2_LIGHT_CHASE: lightChase(customColor2); break;
case CP2_HEARTBEAT_SLOW: heartbeatSlow(customColor2); break;
case CP2_HEARTBEAT_MEDIUM: heartbeat(customColor2); break;
case CP2_HEARTBEAT_FAST: heartbeatFast(customColor2); break;
case CP2_BREATH_SLOW: breatheSlow(customColor2); break;
case CP2_BREATH_FAST: breatheFast(customColor2); break;
case CP2_SHOT: shot(customColor2); break;
case CP2_STROBE: strobe(customColor2); break;
// CP1_2 Patterns (use both colors)
case CP1_2_SPARKLE_1_ON_2: sparkle(customColor1, customColor2); break;
case CP1_2_SPARKLE_2_ON_1: sparkle(customColor2, customColor1); break;
case CP1_2_COLOR_GRADIENT: colorGradient(customColor1, customColor2); break;
case CP1_2_BEATS_PER_MINUTE: bpmTwoColor(customColor1, customColor2); break;
case CP1_2_END_TO_END_BLEND_1_TO_2: endToEndBlend(customColor1, customColor2); break;
case CP1_2_END_TO_END_BLEND: endToEndBlend(customColor1, customColor2); break;
case CP1_2_NO_BLENDING: noBlending(customColor1, customColor2); break;
case CP1_2_TWINKLES: twinklesTwoColor(customColor1, customColor2); break;
case CP1_2_COLOR_WAVES: colorWavesTwoColor(customColor1, customColor2); break;
case CP1_2_SINELON: sinelonTwoColor(customColor1, customColor2); break;
// Solid Colors
case HOT_PINK: setAll(CRGB::HotPink); break;
case DARK_RED: setAll(CRGB::DarkRed); break;
case RED: setAll(CRGB::Red); break;
case RED_ORANGE: setAll(CRGB::OrangeRed); break;
case ORANGE: setAll(CRGB::Orange); break;
case GOLD: setAll(CRGB::Gold); break;
case YELLOW: setAll(CRGB::Yellow); break;
case LAWN_GREEN: setAll(CRGB::LawnGreen); break;
case LIME: setAll(CRGB::Lime); break;
case DARK_GREEN: setAll(CRGB::DarkGreen); break;
case GREEN: setAll(CRGB::Green); break;
case BLUE_GREEN: setAll(CRGB(0, 128, 128)); break;
case AQUA: setAll(CRGB::Aqua); break;
case SKY_BLUE: setAll(CRGB::SkyBlue); break;
case DARK_BLUE: setAll(CRGB::DarkBlue); break;
case BLUE: setAll(CRGB::Blue); break;
case BLUE_VIOLET: setAll(CRGB::BlueViolet); break;
case VIOLET: setAll(CRGB::Violet); break;
case WHITE: setAll(CRGB::White); break;
case GRAY: setAll(CRGB::Gray); break;
case DARK_GRAY: setAll(CRGB::DarkGray); break;
case BLACK: setAll(CRGB::Black); break;
default: setAll(CRGB::Black); break;
}
FastLED.show();
}
// Pattern implementation functions
void setAll(CRGB color) {
fill_solid(leds, NUM_LEDS, color);
}
void rainbowCycle() {
fill_rainbow(leds, NUM_LEDS, gHue, 7);
gHue++;
}
void rainbowWithGlitter() {
rainbowCycle();
if(random8() < 80) {
leds[random16(NUM_LEDS)] += CRGB::White;
}
}
void partyMode() {
fadeToBlackBy(leds, NUM_LEDS, 10);
int pos = random16(NUM_LEDS);
leds[pos] += CHSV(gHue + random8(64), 200, 255);
gHue++;
}
void oceanWave() {
fill_palette(leds, NUM_LEDS, gHue, 9, OceanColors_p, 255, LINEARBLEND);
gHue++;
}
void lavaFlow() {
fill_palette(leds, NUM_LEDS, gHue, 9, LavaColors_p, 255, LINEARBLEND);
gHue++;
}
void forestBreeze() {
fill_palette(leds, NUM_LEDS, gHue, 9, ForestColors_p, 255, LINEARBLEND);
gHue++;
}
void confetti() {
fadeToBlackBy(leds, NUM_LEDS, 10);
int pos = random16(NUM_LEDS);
leds[pos] += CHSV(gHue + random8(64), 200, 255);
}
void shot(CRGB color) {
fadeToBlackBy(leds, NUM_LEDS, 10);
static int pos = 0;
leds[pos] = color;
pos = (pos + 1) % NUM_LEDS;
}
void sinelon(CRGBPalette16 palette) {
fadeToBlackBy(leds, NUM_LEDS, 20);
int pos = beatsin16(13, 0, NUM_LEDS - 1);
leds[pos] += ColorFromPalette(palette, gHue, 192);
gHue++;
}
void bpm(CRGBPalette16 palette) {
uint8_t BeatsPerMinute = 62;
uint8_t beat = beatsin8(BeatsPerMinute, 64, 255);
for(int i = 0; i < NUM_LEDS; i++) {
leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10));
}
gHue++;
}
void fire(int cooling) {
static byte heat[NUM_LEDS];
// Cool down
for(int i = 0; i < NUM_LEDS; i++) {
heat[i] = qsub8(heat[i], random8(0, ((cooling * 10) / NUM_LEDS) + 2));
}
// Heat drift
for(int k = NUM_LEDS - 1; k >= 2; k--) {
heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
}
// Ignite
if(random8() < 120) {
int y = random8(7);
heat[y] = qadd8(heat[y], random8(160, 255));
}
// Convert to LED colors
for(int j = 0; j < NUM_LEDS; j++) {
CRGB color = HeatColor(heat[j]);
leds[j] = color;
}
}
void twinkles(CRGBPalette16 palette) {
fadeToBlackBy(leds, NUM_LEDS, 80);
if(random8() < 80) {
leds[random16(NUM_LEDS)] = ColorFromPalette(palette, random8(), 255);
}
}
void colorWaves(CRGBPalette16 palette) {
static uint16_t sPseudotime = 0;
static uint16_t sLastMillis = 0;
static uint16_t sHue16 = 0;
uint8_t brightdepth = beatsin88(341, 96, 224);
uint16_t brightnessthetainc16 = beatsin88(203, (25 * 256), (40 * 256));
uint8_t msmultiplier = beatsin88(147, 23, 60);
uint16_t hue16 = sHue16;
uint16_t hueinc16 = beatsin88(113, 1, 3000);
uint16_t ms = millis();
uint16_t deltams = ms - sLastMillis;
sLastMillis = ms;
sPseudotime += deltams * msmultiplier;
sHue16 += deltams * beatsin88(400, 5, 9);
uint16_t brightnesstheta16 = sPseudotime;
for(uint16_t i = 0; i < NUM_LEDS; i++) {
hue16 += hueinc16;
uint8_t hue8 = hue16 / 256;
brightnesstheta16 += brightnessthetainc16;
uint16_t b16 = sin16(brightnesstheta16) + 32768;
uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536;
uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536;
bri8 += (255 - brightdepth);
leds[i] = ColorFromPalette(palette, hue8, bri8);
}
}
void larsonScanner(CRGB color) {
static int pos = 0;
static int dir = 1;
fadeToBlackBy(leds, NUM_LEDS, 50);
leds[pos] = color;
pos += dir;
if(pos <= 0 || pos >= NUM_LEDS - 1) dir = -dir;
}
void lightChase(CRGB color) {
static int pos = 0;
fadeToBlackBy(leds, NUM_LEDS, 90);
for(int i = 0; i < NUM_LEDS; i += 3) {
leds[(i + pos) % NUM_LEDS] = color;
}
pos = (pos + 1) % 3;
}
void heartbeat(CRGB color) {
static uint8_t BeatsPerMinute = 62;
uint8_t beat = beatsin8(BeatsPerMinute, 0, 255);
for(int i = 0; i < NUM_LEDS; i++) {
leds[i] = color;
leds[i].fadeLightBy(255 - beat);
}
}
void heartbeatSlow(CRGB color) {
static uint8_t BeatsPerMinute = 40;
uint8_t beat = beatsin8(BeatsPerMinute, 0, 255);
for(int i = 0; i < NUM_LEDS; i++) {
leds[i] = color;
leds[i].fadeLightBy(255 - beat);
}
}
void heartbeatFast(CRGB color) {
static uint8_t BeatsPerMinute = 100;
uint8_t beat = beatsin8(BeatsPerMinute, 0, 255);
for(int i = 0; i < NUM_LEDS; i++) {
leds[i] = color;
leds[i].fadeLightBy(255 - beat);
}
}
void breathe(CRGB color) {
uint8_t brightness = beatsin8(12, 50, 255);
fill_solid(leds, NUM_LEDS, color);
FastLED.setBrightness(scale8(BRIGHTNESS, brightness));
}
void breatheSlow(CRGB color) {
uint8_t brightness = beatsin8(8, 50, 255);
fill_solid(leds, NUM_LEDS, color);
FastLED.setBrightness(scale8(BRIGHTNESS, brightness));
}
void breatheFast(CRGB color) {
uint8_t brightness = beatsin8(20, 50, 255);
fill_solid(leds, NUM_LEDS, color);
FastLED.setBrightness(scale8(BRIGHTNESS, brightness));
}
void strobe(CRGB color) {
static unsigned long lastStrobe = 0;
static bool on = false;
if(millis() - lastStrobe > 50) {
on = !on;
lastStrobe = millis();
fill_solid(leds, NUM_LEDS, on ? color : CRGB::Black);
}
}
void endToEndBlendToBlack(CRGB color) {
static uint8_t pos = 0;
fadeToBlackBy(leds, NUM_LEDS, 10);
// Create gradient from color to black
for(int i = 0; i < NUM_LEDS; i++) {
uint8_t brightness = 255 - (abs(i - pos) * 8);
if(brightness > 0) {
leds[i] = color;
leds[i].fadeLightBy(255 - brightness);
}
}
pos = (pos + 1) % NUM_LEDS;
}
void sparkle(CRGB sparkleColor, CRGB baseColor) {
fill_solid(leds, NUM_LEDS, baseColor);
if(random8() < 80) {
leds[random16(NUM_LEDS)] = sparkleColor;
}
}
void colorGradient(CRGB color1, CRGB color2) {
fill_gradient_RGB(leds, 0, color1, NUM_LEDS-1, color2);
}
void bpmTwoColor(CRGB color1, CRGB color2) {
uint8_t BeatsPerMinute = 62;
uint8_t beat = beatsin8(BeatsPerMinute, 0, 255);
for(int i = 0; i < NUM_LEDS; i++) {
if(i % 2 == 0) {
leds[i] = color1;
leds[i].fadeLightBy(255 - beat);
} else {
leds[i] = color2;
leds[i].fadeLightBy(beat);
}
}
}
void endToEndBlend(CRGB color1, CRGB color2) {
static uint8_t pos = 0;
for(int i = 0; i < NUM_LEDS; i++) {
uint8_t blend = (i + pos) * 255 / NUM_LEDS;
leds[i] = blend_CRGB(color1, color2, blend);
}
pos = (pos + 1) % NUM_LEDS;
}
void noBlending(CRGB color1, CRGB color2) {
// Split the strip in half
for(int i = 0; i < NUM_LEDS/2; i++) {
leds[i] = color1;
}
for(int i = NUM_LEDS/2; i < NUM_LEDS; i++) {
leds[i] = color2;
}
}
void twinklesTwoColor(CRGB color1, CRGB color2) {
fadeToBlackBy(leds, NUM_LEDS, 80);
if(random8() < 80) {
int pos = random16(NUM_LEDS);
leds[pos] = (random8() < 128) ? color1 : color2;
}
}
void colorWavesTwoColor(CRGB color1, CRGB color2) {
static uint8_t hue = 0;
for(int i = 0; i < NUM_LEDS; i++) {
uint8_t brightness = sin8(hue + (i * 10));
uint8_t blend = sin8(hue + (i * 5));
CRGB color = blend_CRGB(color1, color2, blend);
leds[i] = color;
leds[i].fadeLightBy(255 - brightness);
}
hue++;
}
void sinelonTwoColor(CRGB color1, CRGB color2) {
fadeToBlackBy(leds, NUM_LEDS, 20);
int pos1 = beatsin16(13, 0, NUM_LEDS - 1);
int pos2 = beatsin16(11, 0, NUM_LEDS - 1);
leds[pos1] += color1;
leds[pos2] += color2;
}
// Helper function for blending colors
CRGB blend_CRGB(CRGB color1, CRGB color2, uint8_t blend) {
return CRGB(
((color1.r * (255 - blend)) + (color2.r * blend)) / 255,
((color1.g * (255 - blend)) + (color2.g * blend)) / 255,
((color1.b * (255 - blend)) + (color2.b * blend)) / 255
);
}
void startupAnimation() {
// Quick rainbow sweep
for(int i = 0; i < NUM_LEDS; i++) {
leds[i] = CHSV(i * 255 / NUM_LEDS, 255, 255);
FastLED.show();
delay(10);
}
// Fade to black
for(int i = 0; i < 50; i++) {
fadeToBlackBy(leds, NUM_LEDS, 10);
FastLED.show();
delay(20);
}
}
String getPatternName(Pattern pattern) {
switch(pattern) {
// Fixed Palette Patterns
case RAINBOW_RAINBOW_PALETTE: return "RAINBOW_RAINBOW_PALETTE";
case RAINBOW_PARTY_PALETTE: return "RAINBOW_PARTY_PALETTE";
case RAINBOW_OCEAN_PALETTE: return "RAINBOW_OCEAN_PALETTE";
case RAINBOW_LAVA_PALETTE: return "RAINBOW_LAVA_PALETTE";
case RAINBOW_FOREST_PALETTE: return "RAINBOW_FOREST_PALETTE";
case RAINBOW_WITH_GLITTER: return "RAINBOW_WITH_GLITTER";
case CONFETTI: return "CONFETTI";
case SHOT_RED: return "SHOT_RED";
case SHOT_BLUE: return "SHOT_BLUE";
case SHOT_WHITE: return "SHOT_WHITE";
case SINELON_RAINBOW_PALETTE: return "SINELON_RAINBOW_PALETTE";
case SINELON_PARTY_PALETTE: return "SINELON_PARTY_PALETTE";
case SINELON_OCEAN_PALETTE: return "SINELON_OCEAN_PALETTE";
case SINELON_LAVA_PALETTE: return "SINELON_LAVA_PALETTE";
case SINELON_FOREST_PALETTE: return "SINELON_FOREST_PALETTE";
case BEATS_PER_MINUTE_RAINBOW_PALETTE: return "BPM_RAINBOW_PALETTE";
case BEATS_PER_MINUTE_PARTY_PALETTE: return "BPM_PARTY_PALETTE";
case BEATS_PER_MINUTE_OCEAN_PALETTE: return "BPM_OCEAN_PALETTE";
case BEATS_PER_MINUTE_LAVA_PALETTE: return "BPM_LAVA_PALETTE";
case BEATS_PER_MINUTE_FOREST_PALETTE: return "BPM_FOREST_PALETTE";
case FIRE_MEDIUM: return "FIRE_MEDIUM";
case FIRE_LARGE: return "FIRE_LARGE";
case TWINKLES_RAINBOW_PALETTE: return "TWINKLES_RAINBOW_PALETTE";
case TWINKLES_PARTY_PALETTE: return "TWINKLES_PARTY_PALETTE";
case TWINKLES_OCEAN_PALETTE: return "TWINKLES_OCEAN_PALETTE";
case TWINKLES_LAVA_PALETTE: return "TWINKLES_LAVA_PALETTE";
case TWINKLES_FOREST_PALETTE: return "TWINKLES_FOREST_PALETTE";
case COLOR_WAVES_RAINBOW_PALETTE: return "COLOR_WAVES_RAINBOW_PALETTE";
case COLOR_WAVES_PARTY_PALETTE: return "COLOR_WAVES_PARTY_PALETTE";
case COLOR_WAVES_OCEAN_PALETTE: return "COLOR_WAVES_OCEAN_PALETTE";
case COLOR_WAVES_LAVA_PALETTE: return "COLOR_WAVES_LAVA_PALETTE";
case COLOR_WAVES_FOREST_PALETTE: return "COLOR_WAVES_FOREST_PALETTE";
case LARSON_SCANNER_RED: return "LARSON_SCANNER_RED";
case LARSON_SCANNER_GRAY: return "LARSON_SCANNER_GRAY";
case LIGHT_CHASE_RED: return "LIGHT_CHASE_RED";
case LIGHT_CHASE_BLUE: return "LIGHT_CHASE_BLUE";
case LIGHT_CHASE_GRAY: return "LIGHT_CHASE_GRAY";
case HEARTBEAT_RED: return "HEARTBEAT_RED";
case HEARTBEAT_BLUE: return "HEARTBEAT_BLUE";
case HEARTBEAT_WHITE: return "HEARTBEAT_WHITE";
case HEARTBEAT_GRAY: return "HEARTBEAT_GRAY";
case BREATH_RED: return "BREATH_RED";
case BREATH_BLUE: return "BREATH_BLUE";
case BREATH_GRAY: return "BREATH_GRAY";
case STROBE_RED: return "STROBE_RED";
case STROBE_BLUE: return "STROBE_BLUE";
case STROBE_GOLD: return "STROBE_GOLD";
case STROBE_WHITE: return "STROBE_WHITE";
// CP1 Patterns
case CP1_END_TO_END_BLEND_TO_BLACK: return "CP1_END_TO_END_BLEND_TO_BLACK";
case CP1_LARSON_SCANNER: return "CP1_LARSON_SCANNER";
case CP1_LIGHT_CHASE: return "CP1_LIGHT_CHASE";
case CP1_HEARTBEAT_SLOW: return "CP1_HEARTBEAT_SLOW";
case CP1_HEARTBEAT_MEDIUM: return "CP1_HEARTBEAT_MEDIUM";
case CP1_HEARTBEAT_FAST: return "CP1_HEARTBEAT_FAST";
case CP1_BREATH_SLOW: return "CP1_BREATH_SLOW";
case CP1_BREATH_FAST: return "CP1_BREATH_FAST";
case CP1_SHOT: return "CP1_SHOT";
case CP1_STROBE: return "CP1_STROBE";
// CP2 Patterns
case CP2_END_TO_END_BLEND_TO_BLACK: return "CP2_END_TO_END_BLEND_TO_BLACK";
case CP2_LARSON_SCANNER: return "CP2_LARSON_SCANNER";
case CP2_LIGHT_CHASE: return "CP2_LIGHT_CHASE";
case CP2_HEARTBEAT_SLOW: return "CP2_HEARTBEAT_SLOW";
case CP2_HEARTBEAT_MEDIUM: return "CP2_HEARTBEAT_MEDIUM";
case CP2_HEARTBEAT_FAST: return "CP2_HEARTBEAT_FAST";
case CP2_BREATH_SLOW: return "CP2_BREATH_SLOW";
case CP2_BREATH_FAST: return "CP2_BREATH_FAST";
case CP2_SHOT: return "CP2_SHOT";
case CP2_STROBE: return "CP2_STROBE";
// CP1_2 Patterns
case CP1_2_SPARKLE_1_ON_2: return "CP1_2_SPARKLE_1_ON_2";
case CP1_2_SPARKLE_2_ON_1: return "CP1_2_SPARKLE_2_ON_1";
case CP1_2_COLOR_GRADIENT: return "CP1_2_COLOR_GRADIENT";
case CP1_2_BEATS_PER_MINUTE: return "CP1_2_BEATS_PER_MINUTE";
case CP1_2_END_TO_END_BLEND_1_TO_2: return "CP1_2_END_TO_END_BLEND_1_TO_2";
case CP1_2_END_TO_END_BLEND: return "CP1_2_END_TO_END_BLEND";
case CP1_2_NO_BLENDING: return "CP1_2_NO_BLENDING";
case CP1_2_TWINKLES: return "CP1_2_TWINKLES";
case CP1_2_COLOR_WAVES: return "CP1_2_COLOR_WAVES";
case CP1_2_SINELON: return "CP1_2_SINELON";
// Solid Colors
case HOT_PINK: return "HOT_PINK";
case DARK_RED: return "DARK_RED";
case RED: return "RED";
case RED_ORANGE: return "RED_ORANGE";
case ORANGE: return "ORANGE";
case GOLD: return "GOLD";
case YELLOW: return "YELLOW";
case LAWN_GREEN: return "LAWN_GREEN";
case LIME: return "LIME";
case DARK_GREEN: return "DARK_GREEN";
case GREEN: return "GREEN";
case BLUE_GREEN: return "BLUE_GREEN";
case AQUA: return "AQUA";
case SKY_BLUE: return "SKY_BLUE";
case DARK_BLUE: return "DARK_BLUE";
case BLUE: return "BLUE";
case BLUE_VIOLET: return "BLUE_VIOLET";
case VIOLET: return "VIOLET";
case WHITE: return "WHITE";
case GRAY: return "GRAY";
case DARK_GRAY: return "DARK_GRAY";
case BLACK: return "BLACK";
default: return "UNKNOWN";
}
}