├── LICENSE ├── PinkyLEDs ├── PinkyLEDs.ino ├── config.h └── effects.ino ├── README.md └── example.yaml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jennifer Gorton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PinkyLEDs/PinkyLEDs.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************/ 2 | /* _____ | ________ ______ ____ 3 | * | \ | | | | \ / \ 4 | * | | o | | | | \ | 5 | * | | _____ | / | | | | | | | 6 | * |____/ | |/ \ | / | | | |______ | | \____ 7 | * | | | | | / | | | | | | \ 8 | * | | | | |/\ | | | | | | | 9 | * | | | | | \ \_____/ | | | / | 10 | * | | | | | \ / | |_______ |_____/ \_____/ 11 | * \________________________________________/ |________________________________________/ 12 | */ 13 | #define VERSION "0.11.0" 14 | 15 | String (*externalEffect[])(char,bool){ 16 | // add your custom effects to this list 17 | // this also sets the order of the effects list in Home Assistant 18 | confettiColorEffect, 19 | glitterColorEffect, 20 | juggleColorEffect, 21 | sinelonColorEffect, 22 | christmasEffect, 23 | candyCaneEffect, 24 | hollyJollyEffect, 25 | valentineEffect, 26 | loveyDayEffect, 27 | stPatEffect, 28 | easterEffect, 29 | usaEffect, 30 | independenceEffect, 31 | goBlueEffect, 32 | hailEffect, 33 | touchdownEffect, 34 | halloweenEffect, 35 | punkinEffect, 36 | thanksgivingEffect, 37 | turkeyDayEffect, 38 | bpmEffect, 39 | cyclonRainbowEffect, 40 | dotsEffect, 41 | fireEffect, 42 | lightningEffect, 43 | policeAllEffect, 44 | policeOneEffect, 45 | rainbowEffect, 46 | glitterRainbowEffect, 47 | rippleEffect, 48 | twinkleEffect 49 | }; 50 | 51 | #define RUN 0 52 | #define INITIALIZE 1 53 | #define GET_NAME 2 54 | 55 | #include 56 | #ifdef ESP32 57 | #include 58 | #include 59 | #elif defined(ESP8266) 60 | #include 61 | #include 62 | #endif 63 | 64 | #include 65 | 66 | #ifdef ESP8266 67 | /* 68 | Spending too much time in the ISR or disabling the interrupts for too long 69 | time could cause WDT reset. 70 | 71 | For info see: 72 | https://github.com/espressif/ESP8266_NONOS_SDK/issues/90 73 | and 74 | https://github.com/FastLED/FastLED/wiki/Interrupt-problems 75 | */ 76 | //#define FASTLED_ALLOW_INTERRUPTS 0 77 | #define FASTLED_INTERRUPT_RETRY_COUNT 1 78 | #endif 79 | 80 | #ifdef ARDUINO_ESP8266_NODEMCU 81 | #define FASTLED_ESP8266_RAW_PIN_ORDER 82 | #endif 83 | 84 | #include 85 | #include 86 | #include "config.h" 87 | #ifdef ENABLE_E131 88 | #include 89 | 90 | #if (UNIVERSE_START < 1) || (UNIVERSE_START > 63999) 91 | #error "UNIVERSE_START has to be 1 - 63999, check config.h" 92 | #endif 93 | #endif 94 | 95 | #if defined(BRIGHTNESS_ENCODER_DT) || defined(SPEED_ENCODER_DT) 96 | #include 97 | #endif 98 | 99 | #ifdef ARDUINO_ESP8266_NODEMCU 100 | #define HW_PLATFORM "NodeMCU" 101 | #elif defined(ARDUINO_ESP8266_WEMOS_D1MINI) 102 | #define HW_PLATFORM "D1 mini" 103 | #elif defined(ESP32) 104 | #define HW_PLATFORM "ESP32" 105 | #else 106 | #define HW_PLATFORM "other" 107 | #endif 108 | 109 | #define VERSION_FULL VERSION " " HW_PLATFORM 110 | 111 | #ifdef ESP32 112 | #define LED_ON HIGH 113 | #define LED_OFF LOW 114 | #else 115 | #define LED_ON LOW 116 | #define LED_OFF HIGH 117 | #endif 118 | 119 | int OTAport = 8266; 120 | #ifdef USE_DISCOVERY 121 | #define DISCOVERY_TOPIC "homeassistant/light/" DEVICE_NAME "/config" 122 | #define DISCOVERY_BASE "{ \"unique_id\": \"PinkyLED_" DEVICE_NAME "\", \"device\":{\"identifiers\":\"" DEVICE_NAME \ 123 | "\", \"model\": \"PinkyLEDs\", \"manufacturer\": \"Pinkywafer\", \"name\": \"" DEVICE_NAME "\", \"sw_version\": \"" VERSION_FULL \ 124 | "\"}, \"name\": \"" DEVICE_NAME "\", \"platform\": \"mqtt\", \"schema\": \"json\", \"state_topic\": \"" mqttstate \ 125 | "\", \"command_topic\": \"" mqttcommand "\", \"white_value\": \"" WHITE_VALUE "\", \"optimistic\": \"false\", " \ 126 | "\"availability_topic\": \"" LWTTOPIC "\", \"payload_available\": \"Online\", \"payload_not_available\": \"Offline\", " \ 127 | "\"rgb\": \"true\", \"flash_time_short\": \"1\", \"flash_time_long\": \"5\", \"brightness\": \"true\", " \ 128 | "\"effect\": \"true\", \"effect_list\": [" 129 | 130 | #endif 131 | const byte colorList[][3] = {{255,0,0}, {0,255,0}, {0,0,255}, {0,255,127}, {191,255,0}, 132 | {255,255,255}, {255,255,36}, {255,191,0}, {255,127,0}, {255,163,72}, 133 | {255,36,36}, {255,72,118}, {255,0,127}, {255,0,255}, {191,0,255}, 134 | {145,36,255}, {127,0,255}, {91,36,255}, {63,0,255}, {109,109,255}, 135 | {72,72,255}, {0,63,255}, {36,91,255}, {109,145,255}, {0,127,255}, 136 | {72,163,255}, {0,191,255}, {72,209,255}, {36,255,255}, {109,255,255}, 137 | {255,109,109}, {163,72,255}}; 138 | 139 | String (*builtInEffect[])(char,bool){ 140 | #ifdef AUDIO_REACTIVE_PIN 141 | audioColorEffect, 142 | audioLevelRainbowEffect, 143 | audioRandomFlash, 144 | #endif 145 | solidEffect 146 | #ifdef ENABLE_E131 147 | , E131Effect 148 | #endif 149 | }; 150 | 151 | const int noOfExternalEffects = sizeof(externalEffect) / sizeof(externalEffect[0]); 152 | const int noOfBuiltInEffects = sizeof(builtInEffect) / sizeof(builtInEffect[0]); 153 | 154 | /***** MQTT TOPICS ********************/ 155 | #ifdef USE_WHITE_BALANCE_FOR_SPEED 156 | #define SPEEDTOPIC "white_value" 157 | #define WHITE_VALUE "true" 158 | #else 159 | #define SPEEDTOPIC "speed" 160 | #define WHITE_VALUE "false" 161 | #endif 162 | #define mqttcommand "cmnd/" DEVICE_NAME 163 | #define mqttstate "stat/" DEVICE_NAME 164 | #define LWTTOPIC "LWT/" DEVICE_NAME 165 | #ifdef ENABLE_E131 166 | #if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3003002) 167 | #warning "Requires FastLED 3.3.2 or later; check github for latest code." 168 | #endif 169 | #endif 170 | 171 | int newEffect = -1; 172 | int oldEffect = -1; 173 | bool powerChanged = false; 174 | char setRed = 0; 175 | char setGreen = 0; 176 | char setBlue = 150; 177 | String setPower = "OFF"; 178 | int brightness = 150; 179 | int animationSpeed = 240; 180 | unsigned long flashTime = 0; 181 | CRGB leds[2][NUM_LEDS]; 182 | CRGB outputLEDS[NUM_LEDS]; 183 | bool startupMQTTconnect = true; 184 | bool fadeDone = true; 185 | boolean useStrip = 0; 186 | 187 | #ifdef BRIGHTNESS_ENCODER_DT 188 | volatile boolean brightnessEncoderChanged = false; 189 | RotaryEncoder brightnessEncoder(BRIGHTNESS_ENCODER_DT, BRIGHTNESS_ENCODER_CLK); 190 | void ICACHE_RAM_ATTR brightnessEncoderCallback(){ 191 | brightnessEncoder.tick(); 192 | brightnessEncoderChanged = true; 193 | } 194 | #endif 195 | 196 | #ifdef SPEED_ENCODER_DT 197 | volatile boolean animationSpeedEncoderChanged = false; 198 | RotaryEncoder animationSpeedEncoder(SPEED_ENCODER_DT, SPEED_ENCODER_CLK); 199 | void ICACHE_RAM_ATTR animationSpeedEncoderCallback() { 200 | animationSpeedEncoder.tick(); 201 | animationSpeedEncoderChanged = true; 202 | } 203 | #endif 204 | 205 | WiFiClient espClient; 206 | PubSubClient client(espClient); 207 | #ifdef ENABLE_E131 208 | uint16_t universesRequired; 209 | ESPAsyncE131* e131; 210 | #endif 211 | 212 | void setup() { 213 | Serial.begin(115200); 214 | #ifdef BUILTIN_LED 215 | pinMode(BUILTIN_LED, OUTPUT); 216 | #endif 217 | #ifdef POWER_BUTTON_PIN 218 | pinMode(POWER_BUTTON_PIN, INPUT_PULLUP); 219 | #endif 220 | #ifdef COLOR_BUTTON_PIN 221 | pinMode(COLOR_BUTTON_PIN, INPUT_PULLUP); 222 | #endif 223 | #ifdef EFFECT_BUTTON_PIN 224 | pinMode(EFFECT_BUTTON_PIN, INPUT_PULLUP); 225 | #endif 226 | #ifdef BRIGHTNESS_ENCODER_DT 227 | attachInterrupt(digitalPinToInterrupt(BRIGHTNESS_ENCODER_DT), brightnessEncoderCallback, CHANGE); 228 | attachInterrupt(digitalPinToInterrupt(BRIGHTNESS_ENCODER_CLK), brightnessEncoderCallback, CHANGE); 229 | #ifdef BRIGHTNESS_ENCODER_DT 230 | #ifdef BRIGHTNESS_ENCODER_LESS_STEPS 231 | brightnessEncoder.setPosition(brightness / 15); 232 | #else 233 | brightnessEncoder.setPosition(brightness / 5); 234 | #endif 235 | #endif 236 | #endif 237 | #ifdef SPEED_ENCODER_DT 238 | attachInterrupt(digitalPinToInterrupt(SPEED_ENCODER_DT), animationSpeedEncoderCallback, CHANGE); 239 | attachInterrupt(digitalPinToInterrupt(SPEED_ENCODER_CLK), animationSpeedEncoderCallback, CHANGE); 240 | #ifdef SPEED_ENCODER_DT 241 | #ifdef SPEED_ENCODER_LESS_STEPS 242 | animationSpeedEncoder.setPosition(animationSpeed / 15); 243 | #else 244 | animationSpeedEncoder.setPosition(animationSpeed / 5); 245 | #endif 246 | #endif 247 | #endif 248 | #ifdef AUDIO_REACTIVE_PIN 249 | pinMode(AUDIO_REACTIVE_PIN, INPUT); 250 | #ifdef ESP32 251 | analogReadResolution(10); 252 | analogSetWidth(10); 253 | #endif 254 | #endif 255 | #ifdef DEBUG 256 | Serial.println("GPIO Setup complete"); 257 | #endif 258 | FastLED.addLeds(outputLEDS, NUM_LEDS).setCorrection(TypicalLEDStrip); 259 | FastLED.setMaxPowerInVoltsAndMilliamps(12, 10000); //experimental for power management. Feel free to try in your own setup. 260 | FastLED.setBrightness(brightness); 261 | #ifdef DEBUG 262 | Serial.println("FastLED initialised"); 263 | #endif 264 | // initially set to solid color (if it is defined) 265 | for (int i = 0;i< noOfBuiltInEffects;i++){ 266 | if (builtInEffect[i](GET_NAME,0) == "Solid Color"){ 267 | newEffect = i + noOfExternalEffects; 268 | break; 269 | } 270 | } 271 | #ifdef DEBUG 272 | Serial.println("Palettes initialised"); 273 | #endif 274 | fill_solid(outputLEDS, NUM_LEDS, CRGB(255, 0, 0)); // Startup LED Lights 275 | FastLED.show(); 276 | #ifdef DEBUG 277 | Serial.println("Initial setup complete - LEDs on RED"); 278 | #endif 279 | setup_wifi(); 280 | #ifdef DEBUG 281 | Serial.println("WiFi Setup complete"); 282 | Serial.print("Hostname: "); 283 | #ifdef ESP32 284 | Serial.println(WiFi.getHostname()); 285 | #else 286 | Serial.println(WiFi.hostname()); 287 | #endif 288 | #endif 289 | client.setServer(MQTT_BROKER, MQTT_PORT); 290 | client.setCallback(callback); 291 | #ifdef DEBUG 292 | Serial.println("MQTT Initialised"); 293 | #endif 294 | ArduinoOTA.setPort(OTAport); 295 | ArduinoOTA.setHostname(DEVICE_NAME); 296 | ArduinoOTA.setPassword((const char *)OTA_PASSWORD); 297 | ArduinoOTA.onStart([]() { 298 | String type; 299 | if (ArduinoOTA.getCommand() == U_FLASH) { 300 | type = "sketch"; 301 | } else { // U_SPIFFS 302 | type = "filesystem"; 303 | } 304 | Serial.println("Start updating " + type); 305 | }); 306 | ArduinoOTA.onEnd([]() { 307 | Serial.println("\nEnd"); 308 | }); 309 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 310 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 311 | }); 312 | ArduinoOTA.onError([](ota_error_t error) { 313 | Serial.printf("Error[%u]: ", error); 314 | if (error == OTA_AUTH_ERROR) { 315 | Serial.println("Auth Failed"); 316 | } else if (error == OTA_BEGIN_ERROR) { 317 | Serial.println("Begin Failed"); 318 | } else if (error == OTA_CONNECT_ERROR) { 319 | Serial.println("Connect Failed"); 320 | } else if (error == OTA_RECEIVE_ERROR) { 321 | Serial.println("Receive Failed"); 322 | } else if (error == OTA_END_ERROR) { 323 | Serial.println("End Failed"); 324 | } 325 | }); 326 | ArduinoOTA.begin(); 327 | #ifdef DEBUG 328 | Serial.println("OTA setup complete"); 329 | #endif 330 | #ifdef ENABLE_E131 331 | if ( (NUM_LEDS % 170) > 0 ){ 332 | universesRequired = (NUM_LEDS / 170) + 1; 333 | } else { 334 | universesRequired = (NUM_LEDS / 170); 335 | } 336 | Serial.println(universesRequired); 337 | e131 = new ESPAsyncE131(universesRequired); 338 | e131->begin(E131_UNICAST); 339 | #ifdef DEBUG 340 | Serial.printf("E131 buffer initialised with %u Buffers\n", universesRequired); 341 | #endif 342 | #endif 343 | } 344 | 345 | 346 | void setup_wifi() { 347 | #ifdef ESP32 348 | WiFi.setSleep(false); 349 | #else 350 | WiFi.setSleepMode(WIFI_NONE_SLEEP); 351 | #endif 352 | 353 | delay(10); 354 | Serial.println(); 355 | Serial.print("Connecting to "); 356 | Serial.println(WIFI_SSID); 357 | 358 | WiFi.mode(WIFI_STA); 359 | 360 | #ifdef ESP32 361 | WiFi.setHostname(DEVICE_NAME); 362 | #else 363 | WiFi.hostname(DEVICE_NAME); 364 | #endif 365 | 366 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 367 | 368 | while ((WiFi.status() != WL_CONNECTED) && (WiFi.status() != WL_NO_SSID_AVAIL) && (WiFi.status() != WL_CONNECT_FAILED)) { 369 | delay(500); 370 | Serial.print("."); 371 | } 372 | if (WiFi.status() == WL_CONNECTED) { 373 | fill_solid(outputLEDS, NUM_LEDS, CRGB(255, 191, 0)); 374 | FastLED.show(); 375 | Serial.println(""); 376 | Serial.println("WiFi connected"); 377 | Serial.println("IP address: "); 378 | Serial.println(WiFi.localIP()); 379 | delay(750); 380 | } else if (WiFi.status() == WL_NO_SSID_AVAIL) { 381 | Serial.println(""); 382 | Serial.println("WiFi connection failed. SSID not available"); 383 | } else if (WiFi.status() == WL_CONNECT_FAILED) { 384 | Serial.println(""); 385 | Serial.println("WiFi connection failed. Unable to connect"); 386 | } 387 | } 388 | 389 | void callback(char* topic, byte* payload, unsigned int length) { 390 | char message[length + 1]; 391 | Serial.print("MQTT message received "); 392 | for (int i = 0; i < length; i++) { 393 | message[i] = (char)payload[i]; 394 | } 395 | if (String(topic) == mqttstate){ 396 | Serial.println("State message - Unsubscribing from state topic"); 397 | client.unsubscribe(mqttstate); 398 | } 399 | message[length] = '\0'; 400 | Serial.println(message); 401 | 402 | StaticJsonBuffer<250> jsonBuffer; 403 | JsonObject& root = jsonBuffer.parseObject(message); 404 | 405 | if (!root.success()) { 406 | Serial.println("parseObject() failed"); 407 | } else { 408 | #ifdef DEBUG 409 | Serial.println("JSON message parsed succesfully"); 410 | #endif 411 | if (root.containsKey("state")) { 412 | const char* power = root["state"]; 413 | String spower = power; 414 | if (spower != setPower){ 415 | setPower = power; 416 | powerChanged = true; 417 | fadeDone = false; 418 | if (setPower == "ON"){ 419 | initializeEffect(); 420 | } 421 | } 422 | #ifdef DEBUG 423 | Serial.print("Power set: "); 424 | Serial.println(setPower); 425 | #endif 426 | } 427 | 428 | if (root.containsKey("color")) { 429 | setRed = root["color"]["r"]; 430 | setGreen = root["color"]["g"]; 431 | setBlue = root["color"]["b"]; 432 | #ifdef DEBUG 433 | Serial.print("Colour Set: Red:"); 434 | Serial.print(setRed, DEC); 435 | Serial.print(" Green: "); 436 | Serial.print(setGreen, DEC); 437 | Serial.print(" Blue: "); 438 | Serial.println(setBlue, DEC); 439 | #endif 440 | } 441 | 442 | if (root.containsKey("brightness")) { 443 | brightness = root["brightness"]; 444 | #ifdef DEBUG 445 | Serial.print("Brightness Set: "); 446 | Serial.println(brightness); 447 | #endif 448 | #ifdef BRIGHTNESS_ENCODER_DT 449 | #ifdef BRIGHTNESS_ENCODER_LESS_STEPS 450 | brightnessEncoder.setPosition(brightness / 15); 451 | #else 452 | brightnessEncoder.setPosition(brightness / 5); 453 | #endif 454 | #endif 455 | } 456 | 457 | if (root.containsKey("effect")) { 458 | const char* getEffect = root["effect"]; 459 | String setEffect = getEffect; 460 | #ifdef DEBUG 461 | Serial.print("Effect Set: "); 462 | Serial.println(setEffect); 463 | #endif 464 | int callEffect = findEffect(setEffect); 465 | if (callEffect != newEffect) { 466 | oldEffect = newEffect; 467 | newEffect = callEffect; 468 | useStrip = !useStrip; 469 | Serial.println(useStrip); 470 | initializeEffect(); 471 | if (String(topic) == mqttstate){ 472 | fadeDone = true; 473 | } else { 474 | fadeDone = false; 475 | } 476 | } 477 | } 478 | 479 | if (root.containsKey(SPEEDTOPIC)) { 480 | animationSpeed = root[SPEEDTOPIC]; 481 | #ifdef DEBUG 482 | Serial.print("Speed Set: "); 483 | Serial.println(animationSpeed); 484 | #endif 485 | #ifdef SPEED_ENCODER_DT 486 | #ifdef SPEED_ENCODER_LESS_STEPS 487 | animationSpeedEncoder.setPosition(animationSpeed / 15); 488 | #else 489 | animationSpeedEncoder.setPosition(animationSpeed / 5); 490 | #endif 491 | 492 | #endif 493 | } 494 | 495 | if (root.containsKey("flash")) { 496 | flashTime = (int)root["flash"] * 1000; 497 | #ifdef DEBUG 498 | Serial.print("Flash Set: "); 499 | Serial.println(flashTime); 500 | #endif 501 | }else{ 502 | flashTime = 0; 503 | #ifdef DEBUG 504 | Serial.println("Flash Set: Off"); 505 | #endif 506 | } 507 | } 508 | #ifdef DEBUG 509 | Serial.println("Prepare to publish state..."); 510 | #endif 511 | publishState(); 512 | #ifdef DEBUG 513 | Serial.println("MQTT command complete"); 514 | #endif 515 | } 516 | 517 | void initializeEffect(){ 518 | if (newEffect < noOfExternalEffects){ 519 | Serial.println(externalEffect[newEffect](INITIALIZE,useStrip)); 520 | } else { 521 | builtInEffect[newEffect-noOfExternalEffects](INITIALIZE,useStrip); 522 | } 523 | } 524 | 525 | int findEffect(String effectName){ 526 | for (int i = 0; i <= noOfExternalEffects+noOfBuiltInEffects;i++){ 527 | if (i == noOfExternalEffects+noOfBuiltInEffects){ 528 | return 0; 529 | } else { 530 | if (i < noOfExternalEffects){ 531 | if (effectName == externalEffect[i](GET_NAME,0)){ 532 | return i; 533 | break; 534 | } 535 | } else { 536 | if (effectName == builtInEffect[i-noOfExternalEffects](GET_NAME,0)){ 537 | return i; 538 | break; 539 | } 540 | } 541 | } 542 | } 543 | } 544 | 545 | void publishState() { 546 | StaticJsonBuffer<250> jsonBuffer; 547 | JsonObject& root = jsonBuffer.createObject(); 548 | #ifdef DEBUG 549 | Serial.print("Initialising JSON State Message... "); 550 | #endif 551 | root["state"] = setPower; 552 | JsonObject& color = root.createNestedObject("color"); 553 | color["r"] = setRed; 554 | color["g"] = setGreen; 555 | color["b"] = setBlue; 556 | root["brightness"] = brightness; 557 | if (newEffect < noOfExternalEffects){ 558 | root["effect"] = externalEffect[newEffect](GET_NAME,0); 559 | } else { 560 | root["effect"] = builtInEffect[newEffect-noOfExternalEffects](GET_NAME,0); 561 | } 562 | root[SPEEDTOPIC] = animationSpeed; 563 | if (flashTime > 0){ 564 | root["flash"] = flashTime / 1000; 565 | } 566 | uint8_t buffer[root.measureLength() + 1]; 567 | root.printTo((char*)buffer, sizeof(buffer)); 568 | #ifdef DEBUG 569 | Serial.println("Done"); 570 | #endif 571 | client.beginPublish(mqttstate,sizeof(buffer)-1,true); 572 | client.write(buffer,sizeof(buffer)-1); 573 | client.endPublish(); 574 | #ifdef DEBUG 575 | Serial.println("State Sent"); 576 | #endif 577 | } 578 | 579 | void loop() { 580 | 581 | if (!client.connected()) { 582 | reconnect(); 583 | } else { 584 | client.loop(); 585 | } 586 | ArduinoOTA.handle(); 587 | #ifdef POWER_BUTTON_PIN 588 | handlePowerButton(); 589 | #endif 590 | #ifdef COLOR_BUTTON_PIN 591 | handleColorButton(); 592 | #endif 593 | #ifdef EFFECT_BUTTON_PIN 594 | handleEffectButton(); 595 | #endif 596 | #ifdef BRIGHTNESS_ENCODER_DT 597 | if (brightnessEncoderChanged) { 598 | brightnessFromEncoder(); 599 | } 600 | #endif 601 | #ifdef SPEED_ENCODER_DT 602 | if (animationSpeedEncoderChanged) { 603 | animationSpeedFromEncoder(); 604 | } 605 | #endif 606 | 607 | static bool flashOff = false; 608 | static String effectParams = ""; 609 | static int fadeVal = 255; 610 | static unsigned int flashDelay = 0; 611 | if (flashTime > 0) { 612 | if(millis() - flashDelay >= flashTime) { 613 | flashDelay = millis(); 614 | flashOff = !flashOff; 615 | } 616 | } else { 617 | flashOff = false; 618 | } 619 | if (setPower == "OFF") { 620 | powerOff(useStrip); 621 | } else { 622 | #ifdef BUILTIN_LED 623 | digitalWrite(BUILTIN_LED, LED_ON); 624 | #endif 625 | if (newEffect < noOfExternalEffects){ 626 | effectParams = externalEffect[newEffect](RUN,useStrip); 627 | } else { 628 | effectParams = builtInEffect[newEffect-noOfExternalEffects](RUN, useStrip); 629 | } 630 | } 631 | 632 | if (!fadeDone){ 633 | int fadeEffect = oldEffect; 634 | if (powerChanged){ 635 | if (setPower == "ON"){ 636 | fadeEffect = -1; 637 | } else { 638 | fadeEffect = newEffect; 639 | } 640 | } else { 641 | fadeEffect = oldEffect; 642 | } 643 | if (fadeEffect == -1){ 644 | powerOff(!useStrip); 645 | } else if (fadeEffect < noOfExternalEffects){ 646 | externalEffect[fadeEffect](RUN, !useStrip); 647 | } else { 648 | builtInEffect[fadeEffect-noOfExternalEffects](RUN,!useStrip); 649 | } 650 | static uint8_t fadeVal = 255; 651 | static int fadeMillis = millis(); 652 | if (millis() > fadeMillis + 30){ 653 | fadeVal -=5; 654 | fadeMillis = millis(); 655 | } 656 | for (int i = 0; i= 5000) { 681 | mqttReconnectMillis = millis(); 682 | Serial.print("Attempting MQTT connection..."); 683 | if (client.connect(DEVICE_NAME, MQTT_USERNAME, MQTT_PASSWORD, LWTTOPIC, 0, true, "Offline")) { 684 | Serial.println("connected"); 685 | client.publish(LWTTOPIC, "Online", true); 686 | #ifdef USE_DISCOVERY 687 | String payload = DISCOVERY_BASE; 688 | if (noOfExternalEffects > 0) { 689 | for (int i = 0; i < noOfExternalEffects; i++){ 690 | payload += "\"" + externalEffect[i](GET_NAME,0) + "\""; 691 | if (i + 1 < noOfExternalEffects){ 692 | payload += ", "; 693 | } 694 | } 695 | if (noOfExternalEffects > 0){ 696 | payload += ", "; 697 | } 698 | } 699 | if (noOfBuiltInEffects > 0) { 700 | for (int i = 0; i < noOfBuiltInEffects; i++){ 701 | payload += "\"" + builtInEffect[i](GET_NAME,0) + "\""; 702 | if (i + 1 < noOfBuiltInEffects){ 703 | payload += ", "; 704 | } 705 | } 706 | } 707 | payload += "] }"; 708 | byte b[payload.length()+1]; 709 | payload.getBytes(b, payload.length()+1); 710 | client.beginPublish(DISCOVERY_TOPIC,sizeof(b)-1,true);//){; 711 | client.write(b,sizeof(b)-1); 712 | client.endPublish(); 713 | #ifdef DEBUG 714 | for (int i = 0; i= 50) { //avoid delay for debounce 763 | powerDebounce = millis(); 764 | int powerButtonState = digitalRead(POWER_BUTTON_PIN); 765 | if (powerButtonState != lastPowerButtonState) { // button state has changed 766 | if (powerButtonState == LOW) { // button is pressed 767 | if (setPower == "ON") { //if on, turn off 768 | setPower = "OFF"; 769 | } else { //if off turn on 770 | setPower = "ON"; 771 | initializeEffect(); 772 | } 773 | powerChanged = true; 774 | fadeDone = false; 775 | publishState(); 776 | Serial.println("Button press - Set Power: " + setPower); 777 | } 778 | } 779 | lastPowerButtonState = powerButtonState; 780 | } 781 | } 782 | #endif 783 | #ifdef COLOR_BUTTON_PIN 784 | void handleColorButton() { 785 | static int nextColor = 0; 786 | static unsigned int colorDebounce = 0; 787 | static int lastColorButtonState = HIGH; 788 | if (millis() - colorDebounce >= 50) { //avoid delay for debounce 789 | colorDebounce = millis(); 790 | int colorButtonState = digitalRead(COLOR_BUTTON_PIN); 791 | if (colorButtonState != lastColorButtonState) { // button state has changed 792 | if (colorButtonState == LOW) { // button is pressed 793 | setRed = colorList[nextColor][0]; 794 | setGreen = colorList[nextColor][1]; 795 | setBlue = colorList[nextColor][2]; 796 | if (nextColor + 1 >= (sizeof(colorList) / 3)){ 797 | nextColor = 0; 798 | } else { 799 | nextColor++; 800 | } 801 | publishState(); 802 | } 803 | } 804 | lastColorButtonState = colorButtonState; 805 | } 806 | } 807 | #endif 808 | #ifdef EFFECT_BUTTON_PIN 809 | void handleEffectButton() { 810 | static unsigned int effectDebounce = 0; 811 | static int lastEffectButtonState = HIGH; 812 | if (millis() - effectDebounce >= 50) { //avoid delay for debounce 813 | effectDebounce = millis(); 814 | int effectButtonState = digitalRead(EFFECT_BUTTON_PIN); 815 | if (effectButtonState != lastEffectButtonState) { // button state has changed 816 | if (effectButtonState == LOW) { // button is pressed 817 | oldEffect = newEffect; 818 | if (newEffect + 1 < noOfExternalEffects + noOfBuiltInEffects){ 819 | newEffect++; 820 | } else { 821 | newEffect = 0; 822 | } 823 | if (newEffect>=noOfExternalEffects){ 824 | if (builtInEffect[newEffect-noOfExternalEffects](GET_NAME,0) == "E131"){ 825 | if (newEffect + 1 >= noOfExternalEffects + noOfBuiltInEffects){ 826 | newEffect = 0; 827 | }else{ 828 | newEffect++; 829 | } 830 | } 831 | } 832 | useStrip = !useStrip; 833 | initializeEffect(); 834 | fadeDone = false; 835 | publishState(); 836 | } 837 | } 838 | lastEffectButtonState = effectButtonState; 839 | } 840 | } 841 | #endif 842 | #ifdef BRIGHTNESS_ENCODER_DT 843 | void brightnessFromEncoder(){ 844 | #ifdef BRIGHTNESS_ENCODER_LESS_STEPS 845 | int encoderSpeed = 15; 846 | #else 847 | int encoderSpeed = 5; 848 | #endif 849 | int newPos = brightnessEncoder.getPosition() * encoderSpeed; 850 | if (newPos != brightness) { 851 | if (newPos < 0) { 852 | brightnessEncoder.setPosition(0); 853 | newPos = 0; 854 | } else if (newPos > 255) { 855 | brightnessEncoder.setPosition(255 / encoderSpeed); 856 | newPos = 255; 857 | } 858 | brightness = newPos; 859 | publishState(); 860 | } 861 | } 862 | #endif 863 | 864 | #ifdef SPEED_ENCODER_DT 865 | void animationSpeedFromEncoder(){ 866 | #ifdef SPEED_ENCODER_LESS_STEPS 867 | int encoderSpeed = 15; 868 | #else 869 | int encoderSpeed = 5; 870 | #endif 871 | int newPos = animationSpeedEncoder.getPosition() * encoderSpeed; 872 | if (newPos != animationSpeed) { 873 | if (newPos < 0) { 874 | animationSpeedEncoder.setPosition(0); 875 | newPos = 0; 876 | } else if (newPos > 255) { 877 | animationSpeedEncoder.setPosition(255 / encoderSpeed); 878 | newPos = 255; 879 | } 880 | animationSpeed = newPos; 881 | publishState(); 882 | } 883 | } 884 | #endif 885 | 886 | String solidEffect(char mode, bool strip){ 887 | switch (mode){ 888 | case RUN: 889 | { 890 | fill_solid(leds[strip], NUM_LEDS, CRGB(setRed, setGreen, setBlue)); 891 | } 892 | return ""; 893 | break; 894 | default: 895 | return "Solid Color"; 896 | } 897 | } 898 | 899 | #ifdef ENABLE_E131 900 | String E131Effect(char mode, bool strip){ 901 | static boolean clearStrip = false; 902 | switch (mode){ 903 | case RUN: 904 | { 905 | #ifdef BUILTIN_LED 906 | digitalWrite(BUILTIN_LED, LED_ON); 907 | #endif 908 | if (clearStrip){ 909 | fill_solid(leds[strip], NUM_LEDS, CRGB(0, 0, 0)); 910 | clearStrip = false; 911 | FastLED.setBrightness(255); 912 | } 913 | if (!e131->isEmpty()) { 914 | e131_packet_t packet; 915 | e131->pull(&packet); // Pull packet from ring buffer 916 | 917 | uint16_t universe = htons(packet.universe); 918 | uint16_t universeLast = universe + universesRequired - 1; 919 | uint16_t maxChannels = htons(packet.property_value_count) - 1; 920 | 921 | if ( universe >= UNIVERSE_START ) 922 | { 923 | // Calculate LED range to update 924 | uint16_t firstLed = ((universe - UNIVERSE_START) * 170); 925 | uint16_t lastLed = firstLed + (maxChannels / 3); // -1 926 | 927 | #ifdef DEBUG 928 | Serial.printf("Universe %u / %u Channels | Packet#: %u / Errors: %u / FirstLed: %3u/ LastLed: %3u / CH1: %3u / CH2: %3u / CH3: %3u\n", 929 | universe, // The Universe for this packet 930 | maxChannels, // Start code is ignored, we're interested in dimmer data 931 | e131->stats.num_packets, // Packet counter 932 | e131->stats.packet_errors, // Packet error counter 933 | firstLed, // First LED to update 934 | lastLed-1, // Last LED to update 935 | packet.property_values[1], // Dimmer data for Channel 1 936 | packet.property_values[2], // Dimmer data for Channel 2 937 | packet.property_values[3]); // Dimmer data for Channel 3 938 | #endif 939 | 940 | int j = 1; 941 | for (int i = firstLed; i < min(lastLed,(uint16_t)NUM_LEDS); i++) 942 | { 943 | // Calculate channel 944 | leds[strip][i].setRGB(packet.property_values[j], packet.property_values[j + 1], packet.property_values[j + 2]); 945 | j += 3; 946 | } 947 | 948 | FastLED.setBrightness(255); 949 | } 950 | } 951 | } 952 | return "BR"; 953 | break; 954 | case INITIALIZE: 955 | clearStrip = true; 956 | return "E131"; 957 | break; 958 | case GET_NAME: 959 | return "E131"; 960 | } 961 | } 962 | #endif 963 | 964 | #ifdef AUDIO_REACTIVE_PIN 965 | int getAudioLevel(){ 966 | unsigned int inputMax = 0; 967 | unsigned int inputMin = 1024; 968 | #ifdef ESP32 969 | int average = 0; 970 | for (unsigned int i = 0; i<300;i++){ 971 | average = analogRead(AUDIO_REACTIVE_PIN); 972 | for (int i = 0; i < 20; i++) { 973 | average = average + (analogRead(AUDIO_REACTIVE_PIN) - average) / 64; 974 | } 975 | inputMin = min(inputMin, (unsigned int) average); 976 | inputMax = max(inputMax, (unsigned int) average); 977 | } 978 | #else 979 | for (unsigned int i = 0; i<200;i++){ 980 | unsigned int inputSample = analogRead(AUDIO_REACTIVE_PIN); 981 | inputMin = min(inputMin, inputSample); 982 | inputMax = max(inputMax, inputSample); 983 | yield(); 984 | } 985 | #endif 986 | return inputMax-inputMin; 987 | } 988 | 989 | String audioColorEffect(char mode, bool strip){ 990 | switch (mode){ 991 | case RUN: 992 | { 993 | int Rcolor = setRed; 994 | int Gcolor = setGreen; 995 | int Bcolor = setBlue; 996 | fill_solid(leds[strip], NUM_LEDS, CRGB(Rcolor, Gcolor, Bcolor)); 997 | int level = map(getAudioLevel(), AUDIO_LOW_LEVEL, AUDIO_HIGH_LEVEL, -20, 250); 998 | if (level < 0) level = 0; 999 | if (level > 250) level = 250; 1000 | FastLED.setBrightness(level); 1001 | } 1002 | return "BR"; 1003 | break; 1004 | default: 1005 | return "Audio Color"; 1006 | } 1007 | } 1008 | 1009 | String audioLevelRainbowEffect(char mode, bool strip){ 1010 | static int hue = 0; 1011 | switch (mode){ 1012 | case RUN: 1013 | { 1014 | int level = map(getAudioLevel(), AUDIO_LOW_LEVEL, AUDIO_HIGH_LEVEL, 0, NUM_LEDS / 2); 1015 | if (level < 0) level = 0; 1016 | if (level > NUM_LEDS / 2) level = NUM_LEDS / 2; 1017 | for(int a = 0; a < NUM_LEDS; a++) { 1018 | leds[strip][a].nscale8(255-animationSpeed); 1019 | } 1020 | for ( int i = 0; i < level; i++) { 1021 | leds[strip][i + (NUM_LEDS / 2)] = CHSV(hue+i, 255,255); 1022 | leds[strip][(NUM_LEDS / 2) - i] = CHSV(hue+i, 255,255); 1023 | } 1024 | hue ++; 1025 | } 1026 | return ""; 1027 | break; 1028 | case INITIALIZE: 1029 | hue = 0; 1030 | return ""; 1031 | break; 1032 | case GET_NAME: 1033 | return "Audio Level Rainbow"; 1034 | } 1035 | } 1036 | 1037 | String audioRandomFlash(char mode, bool strip){ 1038 | static boolean flashed = false; 1039 | switch (mode){ 1040 | case RUN: 1041 | { 1042 | int level = map(getAudioLevel(), AUDIO_LOW_LEVEL, AUDIO_HIGH_LEVEL, 0, 100); 1043 | for(int a = 0; a < NUM_LEDS; a++) { 1044 | leds[strip][a].nscale8(255-animationSpeed); 1045 | } 1046 | if (level>40){ 1047 | if (!flashed){ 1048 | fill_solid(leds[strip], NUM_LEDS, CHSV(random8(),255,255)); 1049 | flashed = true; 1050 | } 1051 | } else { 1052 | flashed = false; 1053 | } 1054 | } 1055 | return ""; 1056 | break; 1057 | case INITIALIZE: 1058 | flashed = false; 1059 | return ""; 1060 | break; 1061 | case GET_NAME: 1062 | return "Audio Random Flash"; 1063 | } 1064 | } 1065 | #endif 1066 | 1067 | void powerOff(bool strip){ 1068 | #ifdef BUILTIN_LED 1069 | digitalWrite(BUILTIN_LED, LED_OFF); 1070 | #endif 1071 | fill_solid(leds[strip], NUM_LEDS, CRGB::Black); 1072 | } 1073 | -------------------------------------------------------------------------------- /PinkyLEDs/config.h: -------------------------------------------------------------------------------- 1 | // For more Information on this configuration file, please see the Wiki 2 | // (https://github.com/pinkywafer/PinkyLEDs/wiki) 3 | 4 | // REQUIRED CONFIGURATION 5 | // WIFI // 6 | #define WIFI_SSID "yourSSID" // your WIFI SSID 7 | #define WIFI_PASSWORD "yourWiFiPassword" // your WIFI Password 8 | // MQTT // 9 | #define MQTT_BROKER "MQTTipAddress" // your MQTT broker address or IP. 10 | #define MQTT_PORT 1883 // your MQTT port (usually 1883 for unencrypted connection) 11 | #define MQTT_USERNAME "MQTTuser" // your MQTT username 12 | #define MQTT_PASSWORD "MQTTpassword" // your MQTT password 13 | 14 | //DEVICE CONFIG // 15 | #define DEVICE_NAME "PinkyLED1" // Unique name for this device 16 | #define LED_TYPE WS2812 // your LED type WS2812 / WS2811 17 | #define COLOR_ORDER GRB // your color order (normally: RGB for WS2811 | GRB for WS2812) 18 | #define NUM_LEDS 150 // number of LEDs in your strip 19 | #define DATA_PIN D3 // Pin the LEDs are connected to 20 | #define OTA_PASSWORD "OTA_PASSWORD" //the password you will need to enter to upload remotely via the ArduinoIDE 21 | 22 | // OPTIONS 23 | #define USE_DISCOVERY // remove if you do not want to use auto-discovery 24 | #define USE_WHITE_BALANCE_FOR_SPEED // remove if you do not want to use Home Assistant's built in white balance slider for animation speed 25 | #define MQTT_GROUP_TOPIC "cmnd/PinkyLEDs" // MQTT group topic set to your own choice of group or remove if not wanted 26 | #define ENABLE_E131 // remove if you do not want to use E1.31 27 | #define UNIVERSE_START 1 // first universe to listen for for E1.31 (remove if not using E1.31 28 | 29 | #define POWER_BUTTON_PIN D4 // The pin your power button is connected to (Remove if not using power button) 30 | #define COLOR_BUTTON_PIN D7 // The pin your color button is connected to (Remove if not using color button) 31 | #define EFFECT_BUTTON_PIN RX // The pin your effect button is connected to (Remove if not effect power button) 32 | #define BRIGHTNESS_ENCODER_DT D1 // The pin your Brightness encoder DT is connected to (Remove if not using brightness encoder) 33 | #define BRIGHTNESS_ENCODER_CLK D2 // The pin your Brightness encoder CLK is connected to (Remove if not using brightness encoder) 34 | #define SPEED_ENCODER_DT D5 // The pin your Speed encoder DT is connected to (Remove if not using Speed encoder) 35 | #define SPEED_ENCODER_CLK D6 // The pin your Speed encoder CLK is connected to (Remove if not using Speed encoder) 36 | 37 | #define BRIGHTNESS_ENCODER_LESS_STEPS // reduces the steps needed for the brightness encoder (remove if encoder is too responsive) 38 | #define SPEED_ENCODER_LESS_STEPS // reduces the steps needed for the speed encoder (remove if encoder is too responsive) 39 | 40 | #define AUDIO_REACTIVE_PIN A0 // The pin your analog mic is connected to (Remove if not using audio input) 41 | #define AUDIO_HIGH_LEVEL 50 // Audio High level adjustment (Remove if not using audio input) 42 | #define AUDIO_LOW_LEVEL 0 // Audio low level adjustment(Remove if not using audio input) 43 | -------------------------------------------------------------------------------- /PinkyLEDs/effects.ino: -------------------------------------------------------------------------------- 1 | //palette for Christmas 2 | DEFINE_GRADIENT_PALETTE( christmas_palette ) { 3 | 0, 0, 12, 0, 4 | 40, 0, 55, 0, 5 | 66, 1, 117, 2, 6 | 77, 1, 84, 1, 7 | 81, 0, 55, 0, 8 | 119, 0, 12, 0, 9 | 153, 42, 0, 0, 10 | 181, 121, 0, 0, 11 | 204, 255, 12, 8, 12 | 224, 121, 0, 0, 13 | 244, 42, 0, 0, 14 | 255, 42, 0, 0 15 | }; 16 | String christmasEffect(char mode, bool strip){ 17 | static uint8_t hue = 0; 18 | switch (mode){ 19 | case RUN: 20 | { 21 | CRGBPalette16 palette = christmas_palette; 22 | uint8_t beat = beatsin8( animationSpeed / 3, 64, 255); 23 | for ( int i = 0; i < NUM_LEDS; i++) { 24 | leds[strip][i] = ColorFromPalette(palette, hue + (i * 2), beat - hue + (i * 10)); 25 | } 26 | EVERY_N_MILLISECONDS(10) { 27 | hue++; 28 | } 29 | } 30 | return ""; 31 | break; 32 | case INITIALIZE: 33 | hue = 0; 34 | return "Ok"; 35 | case GET_NAME: 36 | return "Christmas"; 37 | } 38 | } 39 | 40 | //palette for St Patty 41 | DEFINE_GRADIENT_PALETTE( st_pat_palette ) { 42 | 0, 1, 22, 1, 43 | 130, 1, 168, 2, 44 | 255, 1, 22, 1 45 | }; 46 | String stPatEffect(char mode, bool strip){ 47 | static uint8_t gHue = 0; 48 | switch (mode){ 49 | case RUN: 50 | { 51 | CRGBPalette16 palette = st_pat_palette; 52 | uint8_t beat = beatsin8( animationSpeed / 3, 64, 255); 53 | for ( int i = 0; i < NUM_LEDS; i++) { 54 | leds[strip][i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10)); 55 | } 56 | EVERY_N_MILLISECONDS(10) { 57 | gHue++; 58 | } 59 | } 60 | return ""; 61 | break; 62 | case INITIALIZE: 63 | gHue = 0; 64 | return "Ok"; 65 | break; 66 | case GET_NAME: 67 | return "St Patty"; 68 | } 69 | } 70 | 71 | //palette for Valentine 72 | DEFINE_GRADIENT_PALETTE( valentine_palette ) { 73 | 0, 103, 1, 10, 74 | 33, 109, 1, 12, 75 | 76, 159, 5, 48, 76 | 119, 175, 55, 103, 77 | 127, 175, 55, 103, 78 | 178, 159, 5, 48, 79 | 221, 109, 1, 12, 80 | 255, 103, 1, 10 81 | }; 82 | String valentineEffect(char mode, bool strip){ 83 | static uint8_t gHue = 0; 84 | switch (mode){ 85 | case RUN: 86 | { 87 | CRGBPalette16 palette = valentine_palette; 88 | uint8_t beat = beatsin8( animationSpeed / 3, 64, 255); 89 | for ( int i = 0; i < NUM_LEDS; i++) { 90 | leds[strip][i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10)); 91 | } 92 | EVERY_N_MILLISECONDS(10) { 93 | gHue++; 94 | } 95 | } 96 | return ""; 97 | break; 98 | case INITIALIZE: 99 | gHue = 0; 100 | return "Ok"; 101 | break; 102 | case GET_NAME: 103 | return "Valentine"; 104 | } 105 | } 106 | 107 | DEFINE_GRADIENT_PALETTE( turkey_day_palette ) { 108 | 0, 9, 5, 1, 109 | 48, 25, 9, 1, 110 | 76, 137, 27, 1, 111 | 96, 98, 42, 1, 112 | 124, 144, 79, 1, 113 | 153, 98, 42, 1, 114 | 178, 137, 27, 1, 115 | 211, 23, 9, 1, 116 | 255, 9, 5, 1 117 | }; 118 | String turkeyDayEffect(char mode, bool strip){ 119 | static uint8_t gHue = 0; 120 | switch (mode){ 121 | case RUN: 122 | { 123 | CRGBPalette16 palette = turkey_day_palette; 124 | uint8_t beat = beatsin8( animationSpeed / 3, 64, 255); 125 | for ( int i = 0; i < NUM_LEDS; i++) { 126 | leds[strip][i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10)); 127 | } 128 | EVERY_N_MILLISECONDS(10) { 129 | gHue++; 130 | } 131 | } 132 | return ""; 133 | break; 134 | case INITIALIZE: 135 | gHue = 0; 136 | return "Ok"; 137 | break; 138 | case GET_NAME: 139 | return "Turkey Day"; 140 | } 141 | } 142 | 143 | 144 | String thanksgivingEffect(char mode, bool strip){ 145 | static uint8_t pos = 0; 146 | static int timer = 0; 147 | static CRGBPalette16 ThxPalettestriped; 148 | switch (mode){ 149 | case RUN: 150 | { 151 | uint8_t speed = (256 - animationSpeed)/2; 152 | if (millis() > timer + speed) { 153 | pos += 2; 154 | timer = millis(); 155 | } 156 | fill_palette( leds[strip], NUM_LEDS, pos, 16, ThxPalettestriped, 255, LINEARBLEND); 157 | return ""; 158 | break; 159 | } 160 | case INITIALIZE: 161 | pos = 0; 162 | {CRGB A = CRGB::OrangeRed; 163 | CRGB AB = CRGB::Olive; 164 | CRGB B = CRGB::Maroon; 165 | ThxPalettestriped = CRGBPalette16(A, A, A, A, A, A, A, AB, AB, AB, B, B, B, B, B, B);} 166 | return "Ok"; 167 | break; 168 | case GET_NAME: 169 | return "Thanksgiving"; 170 | } 171 | } 172 | 173 | //palette for USA 174 | DEFINE_GRADIENT_PALETTE( usa_palette ) { 175 | 0, 0, 0, 45, 176 | 71, 7, 12, 255, 177 | 76, 75, 91, 255, 178 | 76, 255, 255, 255, 179 | 81, 255, 255, 255, 180 | 178, 255, 255, 255, 181 | 179, 255, 55, 45, 182 | 196, 255, 0, 0, 183 | 255, 42, 0, 0 184 | }; 185 | String usaEffect(char mode, bool strip){ 186 | static uint8_t gHue = 0; 187 | switch (mode){ 188 | case RUN: 189 | { 190 | CRGBPalette16 palette = usa_palette; 191 | uint8_t beat = beatsin8( animationSpeed / 3, 64, 255); 192 | for ( int i = 0; i < NUM_LEDS; i++) { 193 | leds[strip][i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10)); 194 | } 195 | EVERY_N_MILLISECONDS(10) { 196 | gHue++; 197 | } 198 | } 199 | return ""; 200 | break; 201 | case INITIALIZE: 202 | gHue = 0; 203 | return "Ok"; 204 | break; 205 | case GET_NAME: 206 | return "USA"; 207 | } 208 | } 209 | 210 | String independenceEffect(char mode, bool strip){ 211 | static uint8_t pos = 0; 212 | static int timer = 0; 213 | static CRGBPalette16 IndPalettestriped; 214 | switch (mode){ 215 | case RUN: 216 | { 217 | uint8_t speed = (256 - animationSpeed)/2; 218 | if (millis() > timer + speed) { 219 | pos += 2; 220 | timer = millis(); 221 | } 222 | fill_palette( leds[strip], NUM_LEDS, pos, 16, IndPalettestriped, 255, LINEARBLEND); 223 | } 224 | return ""; 225 | case INITIALIZE: 226 | { 227 | CRGB A = CRGB::FireBrick; 228 | CRGB AB = CRGB::Cornsilk; 229 | CRGB B = CRGB::MediumBlue; 230 | IndPalettestriped = CRGBPalette16(A, A, A, A, A, AB, AB, AB, AB, AB, B, B, B, B, B, B); 231 | } 232 | pos = 0; 233 | return "Ok"; 234 | break; 235 | case GET_NAME: 236 | return "Independence"; 237 | } 238 | } 239 | 240 | //palette for Halloween 241 | DEFINE_GRADIENT_PALETTE( halloween_palette ) { 242 | 0, 208, 50, 1, 243 | 127, 146, 27, 45, 244 | 255, 97, 12, 178 245 | }; 246 | String halloweenEffect(char mode, bool strip){ 247 | static uint8_t gHue = 0; 248 | switch (mode){ 249 | case RUN: 250 | { 251 | CRGBPalette16 palette = halloween_palette; 252 | uint8_t beat = beatsin8( animationSpeed / 3, 64, 255); 253 | for ( int i = 0; i < NUM_LEDS; i++) { 254 | leds[strip][i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10)); 255 | } 256 | EVERY_N_MILLISECONDS(10) { 257 | gHue++; 258 | } 259 | } 260 | return ""; 261 | case INITIALIZE: 262 | gHue = 0; 263 | return "Ok"; 264 | break; 265 | case GET_NAME: 266 | return "Halloween"; 267 | } 268 | } 269 | 270 | //palette for Go Blue 271 | DEFINE_GRADIENT_PALETTE( go_blue_palette ) { 272 | 0, 4, 12, 122, 273 | 127, 55, 58, 50, 274 | 255, 192, 147, 11 275 | }; 276 | String goBlueEffect(char mode, bool strip){ 277 | static uint8_t gHue = 0; 278 | switch (mode){ 279 | case RUN: 280 | { 281 | CRGBPalette16 palette = go_blue_palette; 282 | uint8_t beat = beatsin8( animationSpeed / 3, 64, 255); 283 | for ( int i = 0; i < NUM_LEDS; i++) { 284 | leds[strip][i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10)); 285 | } 286 | EVERY_N_MILLISECONDS(10) { 287 | gHue++; 288 | } 289 | } 290 | return ""; 291 | break; 292 | case INITIALIZE: 293 | gHue = 0; 294 | return "Ok"; 295 | break; 296 | case GET_NAME: 297 | return "Go Blue"; 298 | } 299 | } 300 | 301 | String hailEffect(char mode, bool strip){ 302 | static uint8_t pos = 0; 303 | static int timer = 0; 304 | static CRGBPalette16 hailPalettestriped; 305 | switch (mode){ 306 | case RUN: 307 | { 308 | uint8_t speed = (256 - animationSpeed)/2; 309 | if (millis() > timer + speed) { 310 | pos += 2; 311 | timer = millis(); 312 | } 313 | fill_palette( leds[strip], NUM_LEDS, pos, 16, hailPalettestriped, 255, LINEARBLEND); 314 | } 315 | return ""; 316 | case INITIALIZE: 317 | { 318 | CRGB A = CRGB::Blue; 319 | CRGB B = CRGB::Yellow; 320 | hailPalettestriped = CRGBPalette16(A, A, A, A, A, A, A, A, B, B, B, B, B, B, B, B); 321 | } 322 | pos = 0; 323 | return "Ok"; 324 | break; 325 | case GET_NAME: 326 | return "Hail"; 327 | } 328 | } 329 | 330 | String touchdownEffect(char mode, bool strip){ 331 | static int idex = 0; 332 | static int timer = 0; 333 | switch (mode){ 334 | case RUN: 335 | { 336 | uint8_t speed = (256 - animationSpeed)/2; 337 | if (millis() > timer + speed) { 338 | idex++; 339 | timer = millis(); 340 | } 341 | if (idex >= NUM_LEDS) { 342 | idex = 0; 343 | } 344 | int idexY = idex; 345 | int idexB; 346 | if (idexY >= (NUM_LEDS / 2)) { 347 | idexB = ( idexY + NUM_LEDS / 2 ) % NUM_LEDS; 348 | } else { 349 | idexB = idexY + (NUM_LEDS / 2); 350 | } 351 | leds[strip][idexY] = CHSV(64, 255, 255); 352 | leds[strip][idexB] = CHSV(160, 255, 255); 353 | } 354 | return ""; 355 | break; 356 | case INITIALIZE: 357 | idex = 0; 358 | return "Ok"; 359 | break; 360 | case GET_NAME: 361 | return "Touchdown"; 362 | } 363 | } 364 | 365 | String punkinEffect(char mode, bool strip){ 366 | static uint8_t pos = 0; 367 | static int timer = 0; 368 | static CRGBPalette16 punkinPalettestriped; 369 | switch (mode){ 370 | case RUN: 371 | { 372 | uint8_t speed = (256 - animationSpeed)/2; 373 | if (millis() > timer + speed) { 374 | pos += 2; 375 | timer = millis(); 376 | } 377 | fill_palette( leds[strip], NUM_LEDS, pos, 16, punkinPalettestriped, 255, LINEARBLEND); 378 | } 379 | return ""; 380 | case INITIALIZE: 381 | { 382 | CRGB A = CRGB::DarkOrange; 383 | CRGB B = CRGB::Indigo; 384 | punkinPalettestriped = CRGBPalette16(A, A, A, A, A, A, A, A, B, B, B, B, B, B, B, B); 385 | } 386 | pos = 0; 387 | return "Ok"; 388 | break; 389 | case GET_NAME: 390 | return "Punkin"; 391 | } 392 | } 393 | 394 | String loveyDayEffect(char mode, bool strip){ 395 | static int idex = 0; 396 | static int timer = 0; 397 | switch (mode){ 398 | case RUN: 399 | { 400 | uint8_t speed = (256 - animationSpeed)/2; 401 | if (millis() > timer + speed) { 402 | idex++; 403 | timer = millis(); 404 | } 405 | if (idex >= NUM_LEDS) { 406 | idex = 0; 407 | } 408 | int idexR = idex; 409 | int idexB; 410 | if (idexR >= (NUM_LEDS / 2)) { 411 | idexB = ( idexR + NUM_LEDS / 2 ) % NUM_LEDS; 412 | } else { 413 | idexB = idexR + (NUM_LEDS / 2); 414 | } 415 | leds[strip][idexR] = CHSV(0, 255, 255); 416 | leds[strip][idexB] = CHSV(244, 255, 255); 417 | } 418 | return ""; 419 | case INITIALIZE: 420 | idex = 0; 421 | return "Ok"; 422 | break; 423 | case GET_NAME: 424 | return "Lovey Day"; 425 | } 426 | } 427 | 428 | String hollyJollyEffect(char mode, bool strip){ 429 | static int pos = 0; 430 | static int timer = 0; 431 | static CRGBPalette16 HJPalettestriped; 432 | switch (mode){ 433 | case RUN: 434 | { 435 | uint8_t speed = (256 - animationSpeed)/2; 436 | if (millis() > timer + speed) { 437 | pos++; 438 | timer = millis(); 439 | } 440 | fill_palette( leds[strip], NUM_LEDS, pos, 16, HJPalettestriped, 255, LINEARBLEND); 441 | } 442 | return ""; 443 | break; 444 | case INITIALIZE: 445 | { 446 | CRGB A = CRGB::Red; 447 | CRGB B = CRGB::Green; 448 | HJPalettestriped = CRGBPalette16(A, A, A, A, A, A, A, A, B, B, B, B, B, B, B, B); 449 | } 450 | pos = 0; 451 | return "Ok"; 452 | break; 453 | case GET_NAME: 454 | return "Holly Jolly"; 455 | } 456 | } 457 | 458 | String sinelonColorEffect(char mode, bool strip){ 459 | switch (mode){ 460 | case RUN: 461 | { 462 | int fadeSpeed = (animationSpeed + 10) / 2; 463 | fadeToBlackBy( leds[strip], NUM_LEDS, fadeSpeed); 464 | int pos = beatsin16((animationSpeed / 15)+1 , 0, NUM_LEDS - 1); 465 | leds[strip][pos] += CRGB(setRed, setGreen, setBlue); 466 | } 467 | return ""; 468 | case INITIALIZE: 469 | return "Ok"; 470 | break; 471 | case GET_NAME: 472 | return "Sinelon Color"; 473 | } 474 | } 475 | 476 | String juggleColorEffect(char mode, bool strip){ 477 | switch (mode){ 478 | case RUN: 479 | { 480 | int fadeSpeed = (animationSpeed + 10) / 2; 481 | fadeToBlackBy( leds[strip], NUM_LEDS, fadeSpeed); 482 | byte dothue = 0; 483 | for ( int i = 0; i < 8; i++) { 484 | leds[strip][beatsin16(i +((animationSpeed / 15)+1), 0, NUM_LEDS - 1)] |= CRGB(setRed, setGreen, setBlue); 485 | dothue += 32; 486 | } 487 | } 488 | return ""; 489 | break; 490 | case INITIALIZE: 491 | return "Ok"; 492 | break; 493 | case GET_NAME: 494 | return "Juggle Color"; 495 | } 496 | } 497 | 498 | String confettiColorEffect(char mode, bool strip){ 499 | static int timer = 0; 500 | switch (mode){ 501 | case RUN: 502 | { 503 | fadeToBlackBy( leds[strip], NUM_LEDS, 20); 504 | uint8_t speed = (256 - animationSpeed); 505 | if (millis() > timer + speed) { 506 | int pos = random16(NUM_LEDS); 507 | leds[strip][pos] += CRGB(setRed + random8(64), setGreen, setBlue); 508 | timer = millis(); 509 | } 510 | } 511 | return ""; 512 | break; 513 | case INITIALIZE: 514 | return "Ok"; 515 | break; 516 | case GET_NAME: 517 | return "Confetti Color"; 518 | } 519 | } 520 | 521 | String rainbowEffect(char mode, bool strip){ 522 | static uint8_t thishue = 0; 523 | static int timer = 0; 524 | switch (mode){ 525 | case RUN: 526 | { 527 | uint8_t speed = (256 - animationSpeed)/2; 528 | if (millis() > timer + speed) { 529 | thishue++; 530 | timer = millis(); 531 | } 532 | fill_rainbow(leds[strip], NUM_LEDS, thishue, 10); 533 | } 534 | return ""; 535 | case INITIALIZE: 536 | thishue = 0; 537 | return "Ok"; 538 | break; 539 | case GET_NAME: 540 | return "Rainbow"; 541 | } 542 | } 543 | 544 | String glitterRainbowEffect(char mode, bool strip){ 545 | static uint8_t thishue = 0; 546 | static int timer = 0; 547 | switch (mode){ 548 | case RUN: 549 | { 550 | uint8_t speed = (256 - animationSpeed)/2; 551 | if (millis() > timer + speed) { 552 | thishue++; 553 | timer = millis(); 554 | } 555 | fill_rainbow(leds[strip], NUM_LEDS, thishue, 10); 556 | //add glitter 557 | if ( random8() < 80) { //chance of glitter = 80 558 | leds[strip][ random16(NUM_LEDS) ] += CRGB::White; 559 | } 560 | } 561 | return ""; 562 | break; 563 | case INITIALIZE: 564 | thishue = 0; 565 | return "Ok"; 566 | break; 567 | case GET_NAME: 568 | return "Glitter Rainbow"; 569 | } 570 | } 571 | 572 | String glitterColorEffect(char mode, bool strip){ 573 | switch (mode){ 574 | case RUN: 575 | { 576 | fadeToBlackBy( leds[strip], NUM_LEDS, 20); 577 | if ( random8() < (animationSpeed/2)+10) { 578 | leds[strip][ random16(NUM_LEDS) ] += CRGB(setRed, setGreen, setBlue); 579 | } 580 | } 581 | return ""; 582 | case INITIALIZE: 583 | return "Ok"; 584 | break; 585 | case GET_NAME: 586 | return "Glitter Color"; 587 | } 588 | } 589 | 590 | String bpmEffect(char mode, bool strip){ 591 | static uint8_t gHue = 0; 592 | switch (mode){ 593 | case RUN: 594 | { 595 | CRGBPalette16 palette = PartyColors_p; 596 | uint8_t beat = beatsin8( animationSpeed / 3, 64, 255); 597 | for ( int i = 0; i < NUM_LEDS; i++) { 598 | leds[strip][i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10)); 599 | } 600 | EVERY_N_MILLISECONDS(10) { 601 | gHue++; 602 | } 603 | } 604 | return ""; 605 | break; 606 | case INITIALIZE: 607 | gHue = 0; 608 | return "Ok"; 609 | break; 610 | case GET_NAME: 611 | return "BPM"; 612 | } 613 | } 614 | 615 | String twinkleEffect(char mode, bool strip){ 616 | static int timer = 0; 617 | switch (mode){ 618 | case RUN: 619 | { 620 | const CRGB lightcolor(8, 7, 1); 621 | for ( int i = 0; i < NUM_LEDS; i++) { 622 | if ( !leds[strip][i]) continue; // skip black pixels 623 | if ( leds[strip][i].r & 1) { // is red odd? 624 | leds[strip][i] -= lightcolor; // darken if red is odd 625 | } else { 626 | leds[strip][i] += lightcolor; // brighten if red is even 627 | } 628 | } 629 | if (millis() > timer + 255 - animationSpeed){ 630 | int j = random16(NUM_LEDS); 631 | if ( !leds[strip][j] ) leds[strip][j] = lightcolor; 632 | timer = millis(); 633 | } 634 | } 635 | return ""; 636 | case INITIALIZE: 637 | return "Ok"; 638 | break; 639 | case GET_NAME: 640 | return "Twinkle"; 641 | } 642 | } 643 | 644 | String lightningEffect(char mode, bool strip){ 645 | static unsigned int dimmer; 646 | static uint16_t ledstart; 647 | static uint16_t ledlen; 648 | static int lightningFlashes = 0; 649 | static int lightningFlashCounter = 0; 650 | static int nextLightningFlashTime = 0; 651 | static int flashOffTime; 652 | static boolean flashOn = false; 653 | switch (mode){ 654 | case RUN: 655 | { 656 | if (lightningFlashCounter >= lightningFlashes){ 657 | lightningFlashes = random8(3, 8); 658 | lightningFlashCounter = 0; 659 | ledstart = random16(NUM_LEDS); // Determine starting location of flash 660 | ledlen = random16(NUM_LEDS - ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) 661 | nextLightningFlashTime = millis() + (random8(50) * 100) + (255-animationSpeed); // delay between strikes 662 | } else { 663 | if (millis() >= nextLightningFlashTime){ 664 | if (lightningFlashCounter == 0) { 665 | dimmer = 5; 666 | } else { 667 | dimmer = random8(1,3); 668 | } 669 | if (!flashOn){ 670 | fill_solid(leds[strip] + ledstart, ledlen, CHSV(255, 0, 255 / dimmer)); 671 | flashOffTime = millis() + random(4,10); 672 | flashOn = true; 673 | } else { 674 | if (millis() >= flashOffTime) { 675 | fill_solid(leds[strip] + ledstart, ledlen, CHSV(255, 0, 0)); // Clear the section of LED's 676 | flashOn = false; 677 | if (lightningFlashCounter == 0){ 678 | nextLightningFlashTime = millis() + 200 + random8(100); // longer delay until next flash after the leader 679 | } else { 680 | nextLightningFlashTime = millis() + 50 + random8(100); // shorter delay between strokes 681 | } 682 | lightningFlashCounter++; 683 | } 684 | } 685 | } 686 | } 687 | } 688 | return ""; 689 | case INITIALIZE: 690 | lightningFlashes = 0; 691 | lightningFlashCounter = 0; 692 | return "Ok"; 693 | break; 694 | case GET_NAME: 695 | return "Lightning"; 696 | } 697 | } 698 | 699 | 700 | String policeOneEffect(char mode, bool strip){ 701 | static int idex = 0; 702 | static int timer = 0; 703 | switch (mode){ 704 | case RUN: 705 | { 706 | uint8_t speed = (256 - animationSpeed)/2; 707 | if (millis() > timer + speed) { 708 | idex++; 709 | timer = millis(); 710 | } 711 | if (idex >= NUM_LEDS) { 712 | idex = 0; 713 | } 714 | int idexR = idex; 715 | int idexB; 716 | if (idexR >= (NUM_LEDS / 2)) { 717 | idexB = ( idexR + NUM_LEDS / 2 ) % NUM_LEDS; 718 | } else { 719 | idexB = idexR + (NUM_LEDS / 2); 720 | } 721 | for (int i = 0; i < NUM_LEDS; i++ ) { 722 | if (i == idexR) { 723 | leds[strip][i] = CHSV(0, 255, 255); 724 | } 725 | else if (i == idexB) { 726 | leds[strip][i] = CHSV(160, 255, 255); 727 | } 728 | else { 729 | leds[strip][i] = CHSV(0, 0, 0); 730 | } 731 | } 732 | } 733 | return ""; 734 | break; 735 | case INITIALIZE: 736 | idex = 0; 737 | return "Ok"; 738 | break; 739 | case GET_NAME: 740 | return "Police One"; 741 | } 742 | } 743 | 744 | String policeAllEffect(char mode, bool strip){ 745 | static int idex = 0; 746 | static int timer = 0; 747 | switch (mode){ 748 | case RUN: 749 | { 750 | uint8_t speed = (256 - animationSpeed)/2; 751 | if (millis() > timer + speed) { 752 | idex++; 753 | timer = millis(); 754 | } 755 | if (idex >= NUM_LEDS) { 756 | idex = 0; 757 | } 758 | int idexR = idex; 759 | int idexB; 760 | if (idexR >= (NUM_LEDS / 2)) { 761 | idexB = ( idexR + NUM_LEDS / 2 ) % NUM_LEDS; 762 | } else { 763 | idexB = idexR + (NUM_LEDS / 2); 764 | } 765 | leds[strip][idexR] = CHSV(0, 255, 255); 766 | leds[strip][idexB] = CHSV(160, 255, 255); 767 | } 768 | return ""; 769 | case INITIALIZE: 770 | idex = 0; 771 | return "Ok"; 772 | break; 773 | case GET_NAME: 774 | return "Police All"; 775 | } 776 | } 777 | 778 | String candyCaneEffect(char mode, bool strip){ 779 | static uint8_t pos = 0; 780 | static int timer = 0; 781 | static CRGBPalette16 candyCanePalette; 782 | switch (mode){ 783 | case RUN: 784 | { 785 | uint8_t speed = (256 - animationSpeed)/2; 786 | if (millis() > timer + speed) { 787 | pos += 2; 788 | timer = millis(); 789 | } 790 | fill_palette( leds[strip], NUM_LEDS, pos, 16, candyCanePalette, 255, LINEARBLEND); 791 | } 792 | return ""; 793 | break; 794 | case INITIALIZE: 795 | { 796 | CRGB A = CRGB::Red; 797 | CRGB B = CRGB::White; 798 | candyCanePalette = CRGBPalette16(A, A, A, A, A, A, A, A, B, B, B, B, B, B, B, B); 799 | } 800 | pos = 0; 801 | return "Ok"; 802 | break; 803 | case GET_NAME: 804 | return "Candy Cane"; 805 | } 806 | } 807 | 808 | String cyclonRainbowEffect(char mode, bool strip){ 809 | static uint8_t cyclonHue = 0; 810 | static int timer = 0; 811 | static int cyclonPos = 0; 812 | switch (mode){ 813 | case RUN: 814 | { 815 | static bool forwards = false; 816 | for(int a = 0; a < NUM_LEDS; a++) { 817 | leds[strip][a].nscale8(250); 818 | } 819 | uint8_t speed = (256 - animationSpeed)/4; 820 | if (forwards) { 821 | if (cyclonPos < NUM_LEDS - 1){ 822 | leds[strip][cyclonPos] = CHSV(cyclonHue++, 255,255); 823 | if (millis() > timer + speed) { 824 | cyclonPos++; 825 | timer = millis(); 826 | } 827 | } else { 828 | forwards = false; 829 | } 830 | } else { 831 | if (cyclonPos >= 0) { 832 | leds[strip][cyclonPos] = CHSV(cyclonHue++, 255,255); 833 | if (millis() > timer + speed) { 834 | cyclonPos--; 835 | timer = millis(); 836 | } 837 | } else { 838 | forwards = true; 839 | } 840 | } 841 | } 842 | return ""; 843 | break; 844 | case INITIALIZE: 845 | cyclonHue = 0; 846 | cyclonPos = 0; 847 | return "Ok"; 848 | break; 849 | case GET_NAME: 850 | return "Cyclon Rainbow"; 851 | } 852 | } 853 | 854 | String fireEffect(char mode, bool strip){ 855 | static byte heat[NUM_LEDS]; 856 | static int delayStartMillis = 0; 857 | static int speedMillis = 0; 858 | switch (mode){ 859 | case RUN: 860 | { 861 | if (millis() > delayStartMillis) { 862 | if (millis() >= speedMillis + 85-(animationSpeed/3)){ 863 | CRGBPalette16 gPal = HeatColors_p; //FastLED built in palette 864 | // Step 1. Cool down every cell a little 865 | for ( int i = 0; i < NUM_LEDS; i++) { 866 | heat[i] = qsub8( heat[i], random8(0, ((55 * 10) / NUM_LEDS) + 2)); 867 | } 868 | // Step 2. Heat from each cell drifts 'up' and diffuses a little 869 | for ( int k = NUM_LEDS - 1; k >= 2; k--) { 870 | heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3; 871 | } 872 | // Step 3. Randomly ignite new 'sparks' of heat near the bottom 873 | if ( random8() < 120 ) { 874 | int y = random8(7); 875 | heat[y] = qadd8( heat[y], random8(160, 255) ); 876 | } 877 | // Step 4. Map from heat cells to LED colors 878 | for ( int j = 0; j < NUM_LEDS; j++) { 879 | // Scale the heat value from 0-255 down to 0-240 880 | // for best results with color palettes. 881 | byte colorindex = scale8( heat[j], 240); 882 | CRGB color = ColorFromPalette( gPal, colorindex); 883 | leds[strip][j] = color; 884 | } 885 | speedMillis = millis(); 886 | } 887 | } 888 | } 889 | return ""; 890 | break; 891 | case INITIALIZE: 892 | delayStartMillis = millis() + 700; 893 | for ( int i = 0; i < NUM_LEDS; i++) { 894 | heat[i] = 0; 895 | } 896 | return "Ok"; 897 | break; 898 | case GET_NAME: 899 | return "Fire"; 900 | } 901 | } 902 | 903 | String easterEffect(char mode, bool strip){ 904 | const uint16_t scale = 30; // Wouldn't recommend changing this on the fly, or the animation will be really blocky. 905 | static uint16_t dist; // A random number for our noise generator. 906 | static CRGBPalette16 targetPalette(OceanColors_p); 907 | static CRGBPalette16 currentPalette(CRGB::Black); 908 | static int timer = 0; 909 | static int paletteTimer = 0; 910 | switch (mode){ 911 | case RUN: 912 | { 913 | if (millis() >= timer){ 914 | nblendPaletteTowardPalette(currentPalette, targetPalette, 48); // FOR NOISE ANIMATION 915 | for (int i = 0; i < NUM_LEDS; i++) { // Just ONE loop to fill up the LED array as all of the pixels change. 916 | uint8_t index = inoise8(i * scale, dist + i * scale) % 255; // Get a value from the noise function. I'm using both x and y axis. 917 | leds[strip][i] = ColorFromPalette(currentPalette, index, 255, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. 918 | } 919 | dist += beatsin8(10, 1, 4); 920 | timer = millis() + ((256-animationSpeed)/5); 921 | } 922 | if (millis() >= paletteTimer){ 923 | targetPalette = CRGBPalette16(CHSV(random8(), 255, random8(128, 255)), CHSV(random8(), 255, random8(128, 255)), CHSV(random8(), 192, random8(128, 255)), CHSV(random8(), 255, random8(128, 255))); 924 | paletteTimer = millis() + (((256-animationSpeed)/15)*1000); 925 | } 926 | } 927 | return ""; 928 | break; 929 | case INITIALIZE: 930 | timer = 0; 931 | paletteTimer = 0; 932 | return "Ok"; 933 | break; 934 | case GET_NAME: 935 | return "Easter"; 936 | } 937 | } 938 | 939 | 940 | String rippleEffect(char mode, bool strip){ 941 | static uint8_t colour; // Ripple colour is randomized. 942 | static int center; // Center of the current ripple. 943 | static int step; // -1 is the initializing step. 944 | static uint8_t bgcol; // Background colour rotates. 945 | static int timer = 0; 946 | switch (mode){ 947 | case RUN: 948 | { 949 | if (millis() >= timer) { 950 | for (int i = 0; i < NUM_LEDS; i++) { 951 | leds[strip][i] = CHSV(bgcol++, 255, 15); // Rotate background colour. 952 | } 953 | switch (step) { 954 | case -1: // Initialize ripple variables. 955 | center = random(NUM_LEDS); 956 | colour = random8(); 957 | step = 0; 958 | break; 959 | case 0: 960 | leds[strip][center] = CHSV(colour, 255, 255); // Display the first pixel of the ripple. 961 | step ++; 962 | break; 963 | case 16: // At the end of the ripples. 964 | step = -1; 965 | break; 966 | default: // Middle of the ripples. 967 | leds[strip][(center + step + NUM_LEDS) % NUM_LEDS] += CHSV(colour, 255, 255 / step * 2); // Simple wrap from Marc Miller 968 | leds[strip][(center - step + NUM_LEDS) % NUM_LEDS] += CHSV(colour, 255, 255 / step * 2); 969 | step ++; // Next step. 970 | break; 971 | } 972 | timer = millis() + ((256-animationSpeed)/5); 973 | } 974 | } 975 | return ""; 976 | break; 977 | case INITIALIZE: 978 | timer = 0; 979 | step = -1; 980 | bgcol = 0; 981 | return "Ok"; 982 | break; 983 | case GET_NAME: 984 | return "Ripple"; 985 | } 986 | } 987 | 988 | String dotsEffect(char mode, bool strip){ 989 | switch (mode){ 990 | case RUN: 991 | { 992 | uint8_t bpm = (animationSpeed / 3) + 1; 993 | uint8_t fadeval = 224; // Trail behind the LED's. Lower => faster fade. 994 | uint16_t inner = beatsin16(bpm, NUM_LEDS / 4, NUM_LEDS / 4 * 3); 995 | uint16_t outer = beatsin16(bpm, 0, NUM_LEDS - 1); 996 | uint16_t middle = beatsin16(bpm, NUM_LEDS / 3, NUM_LEDS / 3 * 2); 997 | leds[strip][middle] = CRGB::Purple; 998 | leds[strip][inner] = CRGB::Blue; 999 | leds[strip][outer] = CRGB::Aqua; 1000 | nscale8(leds[strip], NUM_LEDS, fadeval); 1001 | } 1002 | return ""; 1003 | break; 1004 | case INITIALIZE: 1005 | break; 1006 | case GET_NAME: 1007 | return "Dots"; 1008 | } 1009 | } 1010 | 1011 | // String myNewEffect(char mode, bool strip){ 1012 | // // declare any static variables needed here! 1013 | // switch (mode){ 1014 | // case RUN: 1015 | // { 1016 | // // do some things here to set the LEDs to display 1017 | // // one animation frame the led strip is: 1018 | // // leds[strip] 1019 | // // to address an individual led, use: 1020 | // // leds[strip][LED NUMBER] 1021 | // // 1022 | // // speed can be accessed from the variable: 1023 | // // animationSpeed (0 slowest - 255 fastest) 1024 | // // N.B. Effect speed should be controlled by the code HERE! 1025 | // // but animationSpeed MUST NOT be written to! 1026 | // // 1027 | // // The selected color is stored in: 1028 | // // setRed 1029 | // // setGreen 1030 | // // setBlue 1031 | // // these MUST NOT be written to! 1032 | // // 1033 | // // 1034 | // // DO NOT USE delay 1035 | // // timings can be accomplished using millis() 1036 | // } 1037 | // return ""; // IF the effect code controls brightness return "BR" 1038 | // break; 1039 | // case INITIALIZE: 1040 | // // Initialize any variables to start the effect here. 1041 | // return "Ok"; 1042 | // break; 1043 | // case GET_NAME: 1044 | // return "My Awesome Effect"; // return your effect's name here 1045 | // } 1046 | // } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PinkyLEDs 2 | 3 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/pinkywafer/PinkyLEDs)](https://github.com/pinkywafer/PinkyLEDs/releases) 4 | ![GitHub Release Date](https://img.shields.io/github/release-date/pinkywafer/PinkyLEDs) 5 | [![GitHub](https://img.shields.io/github/license/pinkywafer/PinkyLEDs)](LICENSE) 6 | 7 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-Yes-brightgreen.svg)](https://github.com/pinkywafer/PinkyLEDs/graphs/commit-activity) 8 | [![GitHub issues](https://img.shields.io/github/issues/pinkywafer/PinkyLEDs)](https://github.com/pinkywafer/PinkyLEDs/issues) 9 | 10 | [![Buy me a coffee](https://img.shields.io/static/v1.svg?label=Buy%20me%20a%20coffee&logo=buy%20me%20a%20coffee&logoColor=white&labelColor=ff69b4&message=donate&color=Black)](https://www.buymeacoffee.com/V3q9id4) 11 | 12 | ## MQTT and E1.31 pixel driver for ESP8266 and ESP32 13 | 14 | For setup information and more, please see [the wiki](https://github.com/pinkywafer/PinkyLEDs/wiki) 15 | 16 | Version 0.11.0 is a major refactor and breaks out all (except a few core effects) to a seperate .ino file. An effect template is given at the end of the file to enable you to easily contribute your own effects. 17 | 18 | ## Features 19 | 20 | ### Fade now implemented on power on/off and all effect changes. 21 | 22 | ### New from v0.10.0 Audio reactive effects 23 | 24 | ### New from v0.9.0 Rotary Encoder Support for Brightness and effect Speed 25 | 26 | * LED driver 27 | * MQTT control and status using Json messages 28 | * MQTT discovery for Home Assistant for easy set up 29 | * MQTT group topic to allow control of multiple boards with single MQTT message 30 | * Last will and testament MQTT messages to show online status 31 | * Support for Home Assistant `flash` function 32 | * OTA updates 33 | * Status indication at boot... red - powered up, amber - wifi connected, green MQTT connected 34 | 35 | ## Optional 36 | 37 | * Home Assistant "white value" slider can be used to control effect speed 38 | * Physical button control 39 | * Rotary encoder control for Brightness and Speed 40 | * Original sketch effects and DrZzs custom effects [(from this video)](https://www.youtube.com/watch?v=6Y6jUM1OaYM&t=365s) included. 41 | * E1.31 Unicast control (ie Xlights) - (note can ONLY be selected via MQTT) 42 | * Audio reactive effects using Microphone Audio amplifier module 43 | 44 | Thanks to the original authors, Bruh, Library authors, DrZzs for additional effects, [http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/index.html](http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/index.html) for color palettes, @bbacskay and anyone else I've missed! 45 | -------------------------------------------------------------------------------- /example.yaml: -------------------------------------------------------------------------------- 1 | light: 2 | - platform: mqtt 3 | schema: json 4 | name: "PinkyLED1" 5 | command_topic: "cmnd/PinkyLED1" 6 | state_topic: "stat/PinkyLED1" 7 | availability_topic: "LWT/PinkyLED1" 8 | payload_available: "Online" 9 | payload_not_available: "Offline" 10 | white_value: true 11 | optimistic: false 12 | rgb: true 13 | flash_time_short: 1 14 | flash_time_long: 5 15 | brightness: true 16 | effect: true 17 | effect_list: 18 | - "Confetti Color" 19 | - "Glitter Color" 20 | - "Juggle Color" 21 | - "Sinelon Color" 22 | - "Solid Color" 23 | - "Christmas" 24 | - "Candy Cane" 25 | - "Holly Jolly" 26 | - "Valentine" 27 | - "Lovey Day" 28 | - "St Patty" 29 | - "Easter" 30 | - "USA" 31 | - "Independence" 32 | - "Go Blue" 33 | - "Hail" 34 | - "Touchdown" 35 | - "Halloween" 36 | - "Punkin" 37 | - "Thanksgiving" 38 | - "Turkey Day" 39 | - "BPM" 40 | - "Cyclon Rainbow" 41 | - "Dots" 42 | - "Fire" 43 | - "Lightning" 44 | - "Police All" 45 | - "Police One" 46 | - "Rainbow" 47 | - "Glitter Rainbow" 48 | - "Ripple" 49 | - "Twinkle" 50 | - "Audio Color" 51 | - "Audio Level Rainbow" 52 | - "E131" 53 | --------------------------------------------------------------------------------