├── Bindicator.ino ├── DebugMacros.h ├── GoogleScript.gs ├── HTTPSRedirect.cpp ├── HTTPSRedirect.h ├── LinkedList.h ├── README.md ├── stl ├── Base.stl ├── Bin Body.stl ├── Lid (no supports).stl ├── README.md ├── TrashCan-axle.stl └── TrashCan-wheel.stl └── ws2812fx ├── WS2812FX.cpp ├── WS2812FX.h └── readme.md /Bindicator.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "HTTPSRedirect.h" 12 | #include "LinkedList.h" 13 | #include "DebugMacros.h" 14 | 15 | #define SECOND 1000 16 | #define MINUTE SECOND * 60 17 | #define HOUR MINUTE * 60 18 | 19 | //#define DISPLAY // Comment out if your ESP8266 doesn't have a display 20 | #define POWER_LED true // Illuminate the built-in LED when power supplied 21 | #define VERSION "v3.10b" // Version information 22 | #define PORTAL_SSID "Bindicator" // SSID for web portal 23 | #define SCROLLING true // If false, use the button to change page 24 | #define LED_PIN 15 // NeoPixel data pin 25 | #define TOUCH_PIN 13 // Capacitive touch data pin 26 | #define RESET_PIN 16 // Display reset 27 | #define CLOCK_PIN 5 // Display clock 28 | #define DATA_PIN 4 // Display data 29 | #define LED_COUNT 12 // Number of pixels 30 | #define BRIGHTNESS 255 // NeoPixel brightness (0 - 255) 31 | #define SPEED 1000 // Animation speed (lower is faster) 32 | #define WIFI_TIMEOUT 5 * SECOND // Delay in seconds before WiFi connection times out 33 | #define WATCHDOG_TIMEOUT 60 * SECOND // Delay in seconds before watchdog reboots device 34 | #define CONFIG_DELAY 3 * SECOND // Delay in seconds before enabling WiFi config at boot 35 | #define LED_INTERVAL 2 * SECOND // Interval in second between event colour changing 36 | #define REFRESH_INTERVAL 10 * MINUTE // Data refresh interval 37 | #define NIGHT_BRT 150 // Night light brightness (0 - 255) 38 | #define HALOGEN_HUE 5643 // Night light colour temperature 39 | #define HALOGEN_SAT 80 // Night light saturation 40 | 41 | int modes[3] = { FX_MODE_STATIC, FX_MODE_RAINBOW, FX_MODE_CUSTOM }; 42 | int modeIndex = FX_MODE_STATIC; 43 | 44 | struct Event { // All events contain a title and associated colour 45 | const char* title; 46 | uint32_t color; 47 | }; 48 | 49 | LinkedList eventList; // Downloaded calendar events go here 50 | 51 | Adafruit_NeoPixel pixel = Adafruit_NeoPixel(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); 52 | WS2812FX ws2812fx = WS2812FX(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); 53 | 54 | #ifdef DISPLAY 55 | U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, RESET_PIN, CLOCK_PIN, DATA_PIN); 56 | int page; // Incremented to change page when button touched and no collection scheduled 57 | #endif 58 | 59 | const char* host = "script.google.com"; // Base URL for Google Apps 60 | const int httpsPort = 443; // Default HTTPS port 61 | char *GScriptId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // Default script ID 62 | 63 | String eventsUrl = String("/macros/s/") + GScriptId + "/exec"; // URL to retrieve calendar entries [GET] 64 | String clearUrl = String("/macros/s/") + GScriptId + "/exec?clear"; // URL to clear active events [POST] 65 | 66 | HTTPSRedirect* client = nullptr; // This client handles the redirection used by Google 67 | WiFiManagerParameter custom_gScriptId( // Custom parameter if Google Script ID is changed 68 | "gscriptid", "Google App ID", GScriptId, 56); 69 | 70 | void neopixelCallback(); // Task scheduler function for updating the NeoPixel and display 71 | void getEventsCallback(); // Task scheduler function for refreshing event data 72 | void cancelEventsCallback(); // Task scheduler function for cancelling upcoming events 73 | void wiFiWatchdogCallback(); // Task scheduler function for monitoring WiFi connection 74 | 75 | Scheduler ts; // Responsible for all repeating functions 76 | Task tShowEventColor(LED_INTERVAL, TASK_FOREVER, &neopixelCallback); // Task to convert events to NeoPixel colours 77 | Task tUpdateData(REFRESH_INTERVAL, TASK_FOREVER, &getEventsCallback); // Task to update the list of calendar events 78 | Task tCancelEvents(SECOND / 2, TASK_FOREVER, &cancelEventsCallback); // Task to poll the capacitive button 79 | Task tWiFiWatchdog(SECOND, TASK_FOREVER, &wiFiWatchdogCallback); // Task to monitor WiFi and reconnect if necessary 80 | 81 | 82 | void setup() { 83 | pinMode(TOUCH_PIN, INPUT); // Set the pin connected to the touch sensor to input 84 | if (POWER_LED) { // Turn on the built-in LED if enabled above 85 | pinMode(LED_BUILTIN, OUTPUT); 86 | digitalWrite(LED_BUILTIN, LOW); 87 | } 88 | 89 | delay(1000); 90 | 91 | #ifdef DEBUG 92 | Serial.begin(115200); 93 | Serial.flush(); 94 | DPRINTLN("Serial Transmission Begin"); 95 | #endif 96 | 97 | initialiseNeoPixel(); // NeoPixel won't work without initialisation 98 | 99 | #ifdef DISPLAY 100 | u8g2.begin(); 101 | u8g2.setFontMode(0); // Enable transparent mode, which is faster 102 | u8g2.clearBuffer(); // Clear the internal memory 103 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 104 | u8g2.drawStr(20, 14, "Bindicator"); 105 | u8g2.setFont(u8g2_font_helvR14_tr); 106 | u8g2.drawStr(38, 32, VERSION); 107 | u8g2.sendBuffer(); // Transfer internal memory to the display 108 | #endif 109 | 110 | DPRINTLN("Mounting FS..."); 111 | if (SPIFFS.begin()) { // If file system is valid get configuration data 112 | DPRINTLN("Mounted file system"); 113 | if (SPIFFS.exists("/config.json")) { // File exists, reading and loading 114 | 115 | DPRINTLN("Reading config file"); 116 | File configFile = SPIFFS.open("/config.json", "r"); 117 | if (configFile) { 118 | DPRINTLN("Opened config file"); 119 | size_t size = configFile.size(); 120 | std::unique_ptr buf(new char[size]); // Allocate a buffer to store contents of the file 121 | configFile.readBytes(buf.get(), size); 122 | 123 | DynamicJsonDocument doc(size); // Deserialise JSON data 124 | deserializeJson(doc, buf.get()); 125 | DeserializationError err = deserializeJson(doc, buf.get()); 126 | 127 | if (!err) { 128 | JsonObject root = doc[0]; // If the config file is valid, copy the script ID 129 | strcpy(GScriptId, root["GScriptId"]); 130 | } 131 | configFile.close(); 132 | } 133 | } 134 | } else { 135 | DPRINTLN("Failed to mount FS"); 136 | } 137 | 138 | unsigned long startTime = millis(); 139 | ws2812fx.setMode(FX_MODE_COLOR_WIPE); 140 | ws2812fx.setColor(MAGENTA); 141 | while (digitalRead(TOUCH_PIN) == HIGH) // If so wait until approx 5 seconds have passed 142 | { // then start WiFi Configuration 143 | DPRINTLN("Touch pin high"); 144 | if (millis() > startTime + (CONFIG_DELAY)) 145 | { 146 | configWiFi(); 147 | return; 148 | } 149 | ws2812fx.service(); 150 | } 151 | 152 | DPRINTLN(); // Debugging info 153 | DPRINT("Connecting to WiFi: "); 154 | 155 | WiFi.softAPdisconnect(true); // Stop broadcasting SSID from setup mode (ESP8266 bug) 156 | WiFi.begin(); 157 | 158 | ws2812fx.setColor(WHITE); 159 | ws2812fx.setMode(FX_MODE_FADE); 160 | 161 | startTime = millis(); 162 | while (WiFi.status() != WL_CONNECTED) { 163 | ws2812fx.service(); 164 | DPRINT("."); 165 | if (millis() > startTime + (WIFI_TIMEOUT)) 166 | { // It's trivial to change this to start WiFi configuration 167 | ws2812fx.fill(ws2812fx.ColorHSV(RED)); // at this point. 168 | ws2812fx.show(); 169 | DPRINTLN(); 170 | DPRINTLN("WiFi Timeout"); 171 | while (true) 172 | { 173 | if (millis() < WATCHDOG_TIMEOUT) // Wait 60 seconds then reboot in attempt to recover WiFi 174 | ESP.wdtFeed(); 175 | } 176 | #ifdef DISPLAY 177 | u8g2.clearBuffer(); // Clear the internal memory 178 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 179 | u8g2.drawStr(0, 14, "Error joining"); // Write title to the buffer 180 | u8g2.drawStr(0, 32, "WiFi network"); 181 | u8g2.sendBuffer(); // Transfer internal memory to the display 182 | #endif 183 | return; 184 | } 185 | } 186 | DPRINTLN(""); // Debugging info 187 | DPRINTLN("WiFi connected"); 188 | DPRINTLN("IP address: "); 189 | DPRINTLN(WiFi.localIP()); 190 | 191 | ts.init(); // Task Scheduler initialisation 192 | ts.addTask(tShowEventColor); // Add tasks 193 | ts.addTask(tUpdateData); // ... 194 | ts.addTask(tCancelEvents); // ... 195 | ts.addTask(tWiFiWatchdog); // .... 196 | tUpdateData.enable(); // Connected and ready to start getting event data 197 | tCancelEvents.enable(); // Allow use of capacitive button 198 | tWiFiWatchdog.enable(); 199 | } 200 | 201 | void loop() { 202 | ts.execute(); // Task scheduler handles all repeating functions 203 | ws2812fx.service(); // WS2812 animation 204 | } 205 | 206 | void neopixelCallback() { 207 | DPRINTLN(ESP.getFreeHeap()); 208 | if (eventList.getLength() > 0) // If items to show... 209 | { 210 | switch (eventList.getLength()) 211 | { 212 | case 1: 213 | if (ws2812fx.getMode() != FX_MODE_BREATH) 214 | ws2812fx.setMode(FX_MODE_BREATH); 215 | break; 216 | 217 | case 2: 218 | if (ws2812fx.getMode() != FX_MODE_STATIC) 219 | ws2812fx.setMode(FX_MODE_STATIC); 220 | break; 221 | 222 | default: 223 | if (ws2812fx.getMode() != FX_MODE_STATIC) 224 | ws2812fx.setMode(FX_MODE_STATIC); 225 | } 226 | 227 | struct Event e = eventList.getCurrent(); 228 | ws2812fx.setColor(e.color); 229 | eventList.loop(); // Move to the next item 230 | #ifdef DISPLAY 231 | u8g2.clearBuffer(); // Clear the internal memory 232 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 233 | u8g2.drawStr(0, 14, "Next collection"); // Write title to the buffer 234 | u8g2.drawStr(0, 32, e.title); 235 | DPRINTLN(e.title); 236 | u8g2.sendBuffer(); // Transfer internal memory to the display 237 | #endif 238 | } else 239 | { 240 | tidyUp(); 241 | 242 | #ifdef DISPLAY 243 | switch (page % 3) 244 | { 245 | case 0: // Page 1 246 | u8g2.clearBuffer(); // Clear the internal memory 247 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 248 | u8g2.drawStr(0, 14, "Free heap"); // Write the free heap to the buffer 249 | char buf[32]; 250 | itoa(ESP.getFreeHeap(), buf, 10); 251 | u8g2.drawStr(0, 32, buf); 252 | u8g2.drawStr(53, 32, "kb"); 253 | u8g2.sendBuffer(); // Transfer internal memory to the display 254 | break; 255 | 256 | case 1: // Page 2 257 | u8g2.clearBuffer(); // Clear the internal memory 258 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 259 | u8g2.drawStr(0, 14, "IP address"); // Write the title to the buffer 260 | u8g2.drawStr(0, 32, 261 | WiFi.localIP().toString().c_str()); // Write the IP address to the buffer 262 | u8g2.sendBuffer(); // Transfer internal memory to the display 263 | break; 264 | 265 | case 2: // Page 3 266 | long t = 267 | ts.timeUntilNextIteration(tUpdateData); // Time until task runs again in millis 268 | u8g2.clearBuffer(); // Clear the internal memory 269 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 270 | if (t == 0) { 271 | u8g2.drawStr(0, 14, "Next check"); 272 | u8g2.drawStr(0, 32, "in progress."); 273 | } 274 | if (t == -1) { 275 | u8g2.drawStr(0, 14, "Calendar sync"); 276 | u8g2.drawStr(0, 32, "is disabled."); 277 | } 278 | else { 279 | const char* text = showTimeFormatted(t); 280 | int mins = t / 60000; 281 | DPRINTLN(text); 282 | u8g2.drawStr(0, 14, "Next update"); 283 | u8g2.drawStr(22, 32, "mins"); 284 | u8g2.setCursor(0, 32); 285 | u8g2.print(mins); 286 | } 287 | u8g2.sendBuffer(); 288 | } 289 | } 290 | #endif 291 | } 292 | 293 | void cancelEventsCallback() { 294 | if (digitalRead(TOUCH_PIN) == HIGH && eventList.getLength() > 0) // Only run if the button is held down 295 | { // and there are events to cancel 296 | #ifdef DISPLAY 297 | u8g2.clearBuffer(); // Clear the internal memory 298 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 299 | u8g2.drawStr(0, 14, "Cancelling"); // Write title to the buffer 300 | u8g2.drawStr(0, 32, "reminder..."); 301 | u8g2.sendBuffer(); // Transfer internal memory to the display 302 | #endif 303 | ws2812fx.setColor(MAGENTA); 304 | ws2812fx.setMode(FX_MODE_STATIC); 305 | ws2812fx.service(); 306 | 307 | // Use HTTPSRedirect class to create a new TLS connection 308 | client = new HTTPSRedirect(httpsPort); 309 | client->setInsecure(); 310 | client->setPrintResponseBody(true); 311 | client->setContentTypeHeader("application/json"); 312 | 313 | DPRINT("Connecting to "); 314 | DPRINTLN(host); 315 | 316 | // Try to connect for a maximum of 5 times 317 | bool flag = false; 318 | for (int i = 0; i < 5; i++) { 319 | int retval = client->connect(host, httpsPort); 320 | if (retval == 1) { 321 | flag = true; 322 | break; 323 | } 324 | else 325 | DPRINTLN("Connection failed. Retrying..."); 326 | } 327 | 328 | if (!flag) { 329 | DPRINT("Could not connect to server: "); 330 | DPRINTLN(host); 331 | DPRINTLN("Exiting..."); 332 | return; 333 | } 334 | 335 | client->POST(clearUrl, host, "", false); // Send empty POST string 336 | 337 | delete client; // Delete HTTPSRedirect object 338 | client = nullptr; // COMMENT THIS LINE IF PROGRAM CRASHES 339 | 340 | tidyUp(); 341 | 342 | eventList.Clear(); // Empty the event list 343 | getEventsCallback(); // Update events list to make sure they were 344 | return; // successfully cancelled 345 | } 346 | 347 | #ifdef DISPLAY 348 | if (!SCROLLING && digitalRead(TOUCH_PIN) == HIGH) 349 | page++; // Increment page number (modulo) 350 | else 351 | page++; 352 | #endif 353 | 354 | unsigned long startTime = millis(); 355 | if (digitalRead(TOUCH_PIN) == HIGH && eventList.getLength() == 0) 356 | { 357 | modeIndex++; // Cycle nightlight modes 358 | modeIndex = modeIndex % 3; 359 | ws2812fx.setMode(modes[modeIndex]); 360 | tidyUp(); 361 | DPRINT("Mode: "); 362 | DPRINTLN(modeIndex); 363 | } 364 | } 365 | 366 | void getEventsCallback() 367 | { 368 | DPRINTLN(ESP.getFreeHeap()); 369 | 370 | client = new HTTPSRedirect(httpsPort); 371 | client->setInsecure(); 372 | client->setPrintResponseBody(true); 373 | client->setContentTypeHeader("application/json"); // Expecting a JSON response 374 | 375 | DPRINTLN("Connecting to "); 376 | DPRINTLN(host); 377 | 378 | // Try to connect for a maximum of 5 times 379 | bool flag = false; 380 | for (int i = 0; i < 5; i++) { 381 | int retval = client->connect(host, httpsPort); 382 | if (retval == 1) { 383 | flag = true; 384 | break; 385 | } 386 | else 387 | DPRINTLN("Connection failed. Retrying..."); 388 | } 389 | 390 | if (!flag) { 391 | DPRINTLN("Could not connect to server: "); 392 | DPRINTLN(host); 393 | DPRINTLN("Exiting..."); 394 | return; 395 | } 396 | 397 | if (client->GET(eventsUrl, host)) 398 | if (client->getStatusCode() == 200) // Valid response 399 | { 400 | DPRINTLN(ESP.getFreeHeap()); 401 | DPRINTLN("Response Body: "); 402 | parseJson(client->getResponseBody()); 403 | DPRINT(client->getResponseBody()); 404 | } 405 | 406 | if (client->getStatusCode() == 404) // 404 - script ID probably wrong 407 | { 408 | DPRINTLN("404 - confirm Gscript ID"); 409 | pixel.fill(pixel.ColorHSV(RED)); 410 | pixel.show(); 411 | #ifdef DISPLAY 412 | u8g2.clearBuffer(); // Clear the internal memory 413 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 414 | u8g2.drawStr(0, 14, "Update failed"); // Write title to the buffer 415 | u8g2.drawStr(0, 32, "Check API ID"); 416 | u8g2.sendBuffer(); // Transfer internal memory to the display 417 | #endif 418 | } 419 | 420 | // Delete HTTPSRedirect object 421 | delete client; 422 | client = nullptr; 423 | tShowEventColor.enableDelayed(SECOND * 3); // Brief delay so errors visible to user 424 | } 425 | 426 | void parseJson(String json) { 427 | 428 | const size_t capacity = JSON_ARRAY_SIZE(3) + 3 * JSON_OBJECT_SIZE(2) + 1024; // Additional bytes for string duplication 429 | DynamicJsonDocument doc(capacity); 430 | 431 | deserializeJson(doc, json); 432 | DeserializationError err = deserializeJson(doc, json); 433 | 434 | if (err) { 435 | DPRINTLN(F("deserializeJson() failed with code ")); 436 | DPRINTLN(err.c_str()); 437 | return; 438 | } 439 | 440 | JsonObject root; // This object contains an array of JSON events 441 | eventList.Clear(); 442 | for (int i = 0; i < doc.size(); i++) 443 | { 444 | DPRINTLN(ESP.getFreeHeap()); 445 | struct Event e; // For each event, create a struct and populate data 446 | root = doc[i]; 447 | e.title = root["title"]; 448 | e.color = eventColor2RealColor(atoi(root["color"])); 449 | eventList.Append(e); // Add event to list 450 | } 451 | } 452 | 453 | /******************** WIFI FUNCTIONS ************************/ 454 | 455 | void configWiFi() { 456 | #ifdef DISPLAY 457 | u8g2.clearBuffer(); // Clear the internal memory 458 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 459 | u8g2.drawStr(24, 23, "WiFi Setup"); // Write title to the buffer 460 | u8g2.sendBuffer(); // Transfer internal memory to the display 461 | #endif 462 | 463 | WiFiManager wifiManager; // Handles WiFi configuration via captive portal 464 | wifiManager.setSaveConfigCallback(saveConfigCallback); // This function called if data updated 465 | wifiManager.addParameter(&custom_gScriptId); // Add a custom parameter for the Google Apps ID 466 | wifiManager.startConfigPortal(PORTAL_SSID); // Start an open access point 467 | DPRINTLN("Wifi Configuration started"); 468 | } 469 | 470 | void saveConfigCallback() 471 | { 472 | strcpy(GScriptId, custom_gScriptId.getValue()); // Read updated parameters 473 | 474 | DPRINTLN("Saving config"); 475 | StaticJsonDocument<128> doc; 476 | doc["GScriptId"] = GScriptId; 477 | 478 | File configFile = SPIFFS.open("/config.json", "w"); 479 | if (!configFile) { 480 | DPRINTLN("Failed to open config file for writing"); 481 | } 482 | 483 | if (serializeJson(doc, configFile) == 0) { 484 | DPRINTLN(F("Failed to write to file")); 485 | } 486 | configFile.close(); 487 | 488 | DPRINTLN("Restarting in 5 seconds"); 489 | #ifdef DISPLAY 490 | u8g2.clearBuffer(); // Clear the internal memory 491 | u8g2.setFont(u8g2_font_helvR14_tr); // Choose a suitable font 492 | u8g2.drawStr(0, 14, "Restarting"); // Write title to the buffer 493 | u8g2.drawStr(0, 32, "in 5 seconds"); 494 | u8g2.sendBuffer(); // Transfer internal memory to the display 495 | #endif 496 | delay(5000); 497 | ESP.restart(); 498 | } 499 | 500 | void wiFiWatchdogCallback() { 501 | unsigned long startTime = millis(); 502 | 503 | while (WiFi.status() != WL_CONNECTED) 504 | { 505 | if (millis() > startTime + WATCHDOG_TIMEOUT) 506 | ESP.restart(); 507 | ws2812fx.setColor(RED); 508 | ws2812fx.setBrightness(BRIGHTNESS); 509 | ws2812fx.setMode(FX_MODE_BLINK); 510 | ws2812fx.service(); 511 | WiFi.begin(); 512 | } 513 | } 514 | 515 | /********************** NEOPIXEL HELPER FUNCTIONS ***************************/ 516 | 517 | // Needed to use the WS2812 LEDs 518 | // MUST BE CALLED 519 | void initialiseNeoPixel() 520 | { 521 | ws2812fx.init(); 522 | ws2812fx.setBrightness(BRIGHTNESS); 523 | ws2812fx.setSpeed(SPEED); 524 | ws2812fx.setColor(0x007BFF); 525 | ws2812fx.setMode(FX_MODE_STATIC); 526 | ws2812fx.setCustomMode(customEffect); 527 | ws2812fx.start(); 528 | } 529 | 530 | uint32_t eventColor2RealColor(int eventColor) 531 | { 532 | switch (eventColor) 533 | { 534 | case 0: 535 | return PINK; 536 | break; 537 | 538 | case 1: 539 | return CYAN; 540 | break; 541 | 542 | case 2: 543 | return ws2812fx.Color(75, 255, 120); 544 | break; 545 | 546 | case 3: 547 | return ws2812fx.Color(190, 175, 255); 548 | break; 549 | 550 | case 4: 551 | return ws2812fx.Color(255, 50, 50); 552 | break; 553 | 554 | case 5: 555 | return YELLOW; 556 | break; 557 | 558 | case 6: 559 | return ORANGE; 560 | break; 561 | 562 | case 7: 563 | return CYAN; 564 | break; 565 | 566 | case 8: 567 | return WHITE; 568 | break; 569 | 570 | case 9: 571 | return BLUE; 572 | break; 573 | 574 | case 10: 575 | return GREEN; 576 | break; 577 | 578 | case 11: 579 | return RED; 580 | break; 581 | 582 | default: 583 | return WHITE; 584 | } 585 | } 586 | 587 | void tidyUp() { 588 | if (ws2812fx.getMode() != modes[modeIndex]) 589 | ws2812fx.setMode(modes[modeIndex]); 590 | // if (ws2812fx.getMode() == FX_MODE_STATIC) 591 | // ws2812fx.setColor(ws2812fx.ColorHSV(0, 0, NIGHT_BRT)); 592 | if (ws2812fx.getMode() == FX_MODE_STATIC) 593 | ws2812fx.setColor(ws2812fx.gamma32(ws2812fx.ColorHSV(HALOGEN_HUE, HALOGEN_SAT, NIGHT_BRT))); 594 | if (ws2812fx.getMode() == FX_MODE_FIRE_FLICKER_SOFT) 595 | ws2812fx.setColor(0xE25822); 596 | if (ws2812fx.getMode() == FX_MODE_COMET || ws2812fx.getMode() == FX_MODE_LARSON_SCANNER) 597 | ws2812fx.setColor(0x0000FF); 598 | } 599 | 600 | uint16_t customEffect(void) { 601 | WS2812FX::Segment* seg = ws2812fx.getSegment(); 602 | ws2812fx.clear(); 603 | return seg->speed; // Return the delay until the next animation step (in ms) 604 | } 605 | 606 | const char* showTimeFormatted(long ms) { 607 | DPRINTLN(ms); 608 | long hours = 0; 609 | long mins = 0; 610 | long secs = 0; 611 | 612 | String minsText = " minutes"; 613 | secs = ms / 1000; 614 | mins = secs / 60; 615 | hours = mins / 60; 616 | secs = secs - (mins * 60); // Subtract the coverted seconds to minutes in order to display 59 secs max 617 | mins = mins - (hours * 60); // Subtract the coverted minutes to hours in order to display 59 minutes max 618 | 619 | // return days + hours_o + hours + mins_o + mins + secs_o + secs; 620 | String r; 621 | if (mins > 1) 622 | r = mins + minsText; 623 | else 624 | r = "one minute"; 625 | DPRINTLN(r.c_str()); 626 | return r.c_str(); 627 | 628 | } 629 | -------------------------------------------------------------------------------- /DebugMacros.h: -------------------------------------------------------------------------------- 1 | // Variadic macros used to print information in de-bugging mode 2 | // from LarryD, Arduino forum 3 | 4 | #pragma once 5 | // un-comment this line to print the debugging statements 6 | //#define DEBUG 7 | 8 | #ifdef DEBUG 9 | #define DPRINT(...) Serial.print(__VA_ARGS__) 10 | #define DPRINTLN(...) Serial.println(__VA_ARGS__) 11 | #else 12 | // define blank line 13 | #define DPRINT(...) 14 | #define DPRINTLN(...) 15 | #endif 16 | -------------------------------------------------------------------------------- /GoogleScript.gs: -------------------------------------------------------------------------------- 1 | /******************************************** 2 | * * 3 | * USER DEFINED SETTINGS * 4 | * * 5 | ********************************************/ 6 | 7 | // Maximum number of calendar events to download. 8 | // Valid range 1 - 3 (assumes max three collections on one day) 9 | // Default [3] 10 | 11 | var maxEvents = 3; 12 | 13 | 14 | // The time at which the collection is assumed to have taken place 15 | // after which the reminder will be automatically dismissed. 16 | // Valid range 0:01am - 11:59pm (0001 - 2359 also acceptable) 17 | // Default [8:00am] 18 | 19 | var cutoffTime = "8:00am" 20 | 21 | //=============================================================// 22 | //=============================================================// 23 | 24 | /******************************************** 25 | * * 26 | * PROGRAM VARIABLES AND FUNCTIONS * 27 | * -- EDIT WITH CAUTION -- * 28 | * * 29 | ********************************************/ 30 | 31 | var dismissed = "dismissed"; 32 | var dateRange = 1; 33 | 34 | function test() 35 | { 36 | var e = { 37 | "parameter": { 38 | "clear": "" 39 | } 40 | }; 41 | 42 | doPost(e); 43 | } 44 | 45 | function doGet() 46 | { 47 | return ContentService.createTextOutput(getEventsJson()); 48 | } 49 | 50 | function doPost(e) 51 | { 52 | var clear = e.parameter.clear; 53 | if (clear === undefined) 54 | { 55 | Logger.log("Clear parameter undefined"); 56 | return; 57 | } 58 | 59 | Logger.log("task === clear"); 60 | var cal = CalendarApp.getCalendarsByName('Bins')[0]; 61 | 62 | var now = new Date(); 63 | now.setDate(new Date().getDate()); 64 | var tomorrow = new Date(); 65 | tomorrow.setDate(new Date().getDate()+1); 66 | 67 | var events = cal.getEventsForDay(tomorrow); 68 | var i; 69 | for (i = 0; i < events.length; i++) 70 | { 71 | events[i].setTag(dismissed, "TRUE"); 72 | Logger.log("Dismissed: " + events[i].getTag(dismissed)); 73 | } 74 | 75 | } 76 | 77 | function getEventsJson() 78 | { 79 | var cal = CalendarApp.getCalendarsByName('Bins')[0]; 80 | var now = new Date(); 81 | now.setDate(new Date().getDate()); 82 | var nextWeek = new Date(); 83 | nextWeek.setDate(new Date().getDate() + dateRange); 84 | 85 | var events = cal.getEvents(now, nextWeek) 86 | if (events.length > 0) 87 | { 88 | var event = []; 89 | var i; 90 | var j = events.length; 91 | if (events.length >= maxEvents) 92 | j = maxEvents; 93 | for (i = 0; i < j; i++) 94 | { 95 | if (events[i].getTag(dismissed) !== "TRUE") 96 | event.push({"title": events[i].getTitle(), "color": events[i].getColor()}); 97 | } 98 | var payload = JSON.stringify(event); 99 | Logger.log(payload); 100 | return payload; 101 | } 102 | 103 | var event = []; 104 | var payload = JSON.stringify(event); 105 | Logger.log(payload); 106 | return payload; 107 | } 108 | 109 | function parseTime(t) { 110 | var d = new Date(); 111 | d.setDate(new Date().getDate()); 112 | var time = t.match( /(\d+)(?::(\d\d))?\s*(p?)/ ); 113 | d.setHours( parseInt( time[1]) + (time[3] ? 12 : 0) ); 114 | d.setMinutes( parseInt( time[2]) || 0 ); 115 | return d; 116 | } 117 | 118 | /******************************************** 119 | * * 120 | * DEBUGGING * 121 | * -- CALL FROM RUN MENU -- * 122 | * * 123 | ********************************************/ 124 | 125 | function resetEventTags() 126 | { 127 | var Cal = CalendarApp.getCalendarsByName('Bins')[0]; 128 | 129 | var now = new Date(); 130 | now.setDate(new Date().getDate()); 131 | var future = new Date(); 132 | future.setDate(new Date().getDate()+31); 133 | 134 | var events = Cal.getEvents(now, future); 135 | var i; 136 | for (i = 0; i < events.length; i++) 137 | { 138 | events[i].setTag(dismissed, "FALSE"); 139 | Logger.log("Dismissed: " + events[i].getTag(dismissed)); 140 | } 141 | } 142 | 143 | 144 | -------------------------------------------------------------------------------- /HTTPSRedirect.cpp: -------------------------------------------------------------------------------- 1 | /* HTTPS on ESP8266 with follow redirects, chunked encoding support 2 | * Version 2.1 3 | * Author: Sujay Phadke 4 | * Github: @electronicsguy 5 | * Copyright (C) 2017 Sujay Phadke 6 | * All rights reserved. 7 | * 8 | */ 9 | 10 | #include "HTTPSRedirect.h" 11 | #include "DebugMacros.h" 12 | 13 | HTTPSRedirect::HTTPSRedirect(void) : _httpsPort(443){ 14 | Init(); 15 | } 16 | 17 | HTTPSRedirect::HTTPSRedirect(const int p) : _httpsPort(p){ 18 | Init(); 19 | } 20 | 21 | HTTPSRedirect::~HTTPSRedirect(){ 22 | } 23 | 24 | void HTTPSRedirect::Init(void){ 25 | _keepAlive = true; 26 | _printResponseBody = false; 27 | _maxRedirects = 10; 28 | _contentTypeHeader = "application/x-www-form-urlencoded"; 29 | } 30 | 31 | // This is the main function which is similar to the method 32 | // print() from WifiClient or WifiClientSecure 33 | bool HTTPSRedirect::printRedir(void){ 34 | unsigned int httpStatus; 35 | 36 | // Check if connection to host is alive 37 | if (!connected()){ 38 | Serial.println("Error! Not connected to host."); 39 | return false; 40 | } 41 | 42 | // Clear the input stream of any junk data before making the request 43 | while(available()) 44 | read(); 45 | 46 | // Create HTTP/1.1 compliant request string 47 | // HTTP/1.1 complaint request packet must exist 48 | 49 | DPRINTLN(_Request); 50 | 51 | // Make the actual HTTPS request using the method 52 | // print() from the WifiClientSecure class 53 | // Make sure the input stream is cleared (as above) before making the call 54 | print(_Request); 55 | 56 | // Read HTTP Response Status lines 57 | while (connected()) { 58 | 59 | httpStatus = getResponseStatus(); 60 | 61 | // Only some HTTP response codes are checked for 62 | // http://www.restapitutorial.com/httpstatuscodes.html 63 | switch (httpStatus){ 64 | // Success. Fetch final response body 65 | case 200: 66 | case 201: 67 | { 68 | // final header is discarded 69 | fetchHeader(); 70 | 71 | #ifdef EXTRA_FNS 72 | printHeaderFields(); 73 | #endif 74 | 75 | if (_hF.transferEncoding == "chunked") 76 | fetchBodyChunked(); 77 | else 78 | fetchBodyUnChunked(_hF.contentLength); 79 | 80 | return true; 81 | } 82 | break; 83 | 84 | case 301: 85 | case 302: 86 | { 87 | // Get re-direction URL from the 'Location' field in the header 88 | if (getLocationURL()){ 89 | //stop(); // may not be required 90 | 91 | _myResponse.redirected = true; 92 | 93 | // Make a new connection to the re-direction server 94 | if (!connect(_redirHost.c_str(), _httpsPort)) { 95 | Serial.println("Connection to re-directed URL failed!"); 96 | return false; 97 | } 98 | 99 | // Recursive call to the requested URL on the server 100 | return printRedir(); 101 | 102 | } 103 | else{ 104 | Serial.println("Unable to retrieve redirection URL!"); 105 | return false; 106 | 107 | } 108 | } 109 | break; 110 | 111 | default: 112 | Serial.print("Error with request. Response status code: "); 113 | Serial.println(httpStatus); 114 | return false; 115 | break; 116 | } // end of switch 117 | 118 | } // end of while 119 | 120 | return false; 121 | 122 | } 123 | 124 | // Create a HTTP GET request packet 125 | // GET headers must be terminated with a "\r\n\r\n" 126 | // http://stackoverflow.com/questions/6686261/what-at-the-bare-minimum-is-required-for-an-http-request 127 | void HTTPSRedirect::createGetRequest(const String& url, const char* host){ 128 | _Request = String("GET ") + url + " HTTP/1.1\r\n" + 129 | "Host: " + host + "\r\n" + 130 | "User-Agent: ESP8266\r\n" + 131 | (_keepAlive ? "" : "Connection: close\r\n") + 132 | "\r\n\r\n"; 133 | 134 | return; 135 | } 136 | 137 | // Create a HTTP POST request packet 138 | // POST headers must be terminated with a "\r\n\r\n" 139 | // POST requests have 1 single blank like between the end of the header fields and the body payload 140 | void HTTPSRedirect::createPostRequest(const String& url, const char* host, const String& payload){ 141 | // Content-Length is mandatory in POST requests 142 | // Body content will include payload and a newline character 143 | unsigned int len = payload.length() + 1; 144 | 145 | _Request = String("POST ") + url + " HTTP/1.1\r\n" + 146 | "Host: " + host + "\r\n" + 147 | "User-Agent: ESP8266\r\n" + 148 | (_keepAlive ? "" : "Connection: close\r\n") + 149 | "Content-Type: " + _contentTypeHeader + "\r\n" + 150 | "Content-Length: " + len + "\r\n" + 151 | "\r\n" + 152 | payload + 153 | "\r\n\r\n"; 154 | 155 | return; 156 | } 157 | 158 | 159 | bool HTTPSRedirect::getLocationURL(void){ 160 | 161 | bool flag; 162 | 163 | // Keep reading from the input stream till we get to 164 | // the location field in the header 165 | flag = find("Location: "); 166 | 167 | if (flag){ 168 | // Skip URI protocol (http, https, etc. till '//') 169 | // This assumes that the location field will be containing 170 | // a URL of the form: http:/// 171 | readStringUntil('/'); 172 | readStringUntil('/'); 173 | // get hostname 174 | _redirHost = readStringUntil('/'); 175 | // get remaining url 176 | _redirUrl = String('/') + readStringUntil('\n'); 177 | } 178 | else{ 179 | DPRINT("No valid 'Location' field found in header!"); 180 | } 181 | 182 | // Create a GET request for the new location 183 | createGetRequest(_redirUrl, _redirHost.c_str()); 184 | 185 | DPRINT("_redirHost: "); 186 | DPRINTLN(_redirHost); 187 | DPRINT("_redirUrl: "); 188 | DPRINTLN(_redirUrl); 189 | 190 | return flag; 191 | } 192 | 193 | void HTTPSRedirect::fetchHeader(void){ 194 | String line = ""; 195 | int pos = -1; 196 | int pos2 = -1; 197 | int pos3 = -1; 198 | 199 | _hF.transferEncoding = ""; 200 | _hF.contentLength = 0; 201 | 202 | #ifdef EXTRA_FNS 203 | _hF.contentType = ""; 204 | #endif 205 | 206 | while (connected()) { 207 | line = readStringUntil('\n'); 208 | 209 | DPRINTLN(line); 210 | 211 | // HTTP headers are terminated by a CRLF ('\r\n') 212 | // Hence the final line will contain only '\r' 213 | // since we have already till the end ('\n') 214 | if (line == "\r") 215 | break; 216 | 217 | if (pos < 0){ 218 | pos = line.indexOf("Transfer-Encoding: "); 219 | if (!pos) 220 | // get string & remove trailing '\r' character to facilitate string comparisons 221 | _hF.transferEncoding = line.substring(19, line.length()-1); 222 | } 223 | if (pos2 < 0){ 224 | pos2 = line.indexOf("Content-Length: "); 225 | if (!pos2) 226 | _hF.contentLength = line.substring(16).toInt(); 227 | } 228 | #ifdef EXTRA_FNS 229 | if (pos3 < 0){ 230 | pos3 = line.indexOf("Content-Type: "); 231 | if (!pos3) 232 | // get string & remove trailing '\r' character to facilitate string comparisons 233 | _hF.contentType = line.substring(14, line.length()-1); 234 | } 235 | #endif 236 | 237 | } 238 | 239 | return; 240 | } 241 | 242 | void HTTPSRedirect::fetchBodyUnChunked(unsigned len){ 243 | String line; 244 | DPRINTLN("Body:"); 245 | 246 | while ((connected()) && (len > 0)) { 247 | line = readStringUntil('\n'); 248 | len -= line.length(); 249 | // Content length will include all '\n' terminating characters 250 | // Decrement once more to account for the '\n' line ending character 251 | --len; 252 | 253 | if (_printResponseBody) 254 | Serial.println(line); 255 | 256 | _myResponse.body += line; 257 | _myResponse.body += '\n'; 258 | 259 | } 260 | } 261 | 262 | // Ref: http://mihai.ibanescu.net/chunked-encoding-and-python-requests 263 | // http://fssnip.net/2t 264 | void HTTPSRedirect::fetchBodyChunked(void){ 265 | String line; 266 | int chunkSize; 267 | 268 | while (connected()){ 269 | line = readStringUntil('\n'); 270 | 271 | // Skip any empty lines 272 | if (line == "\r") 273 | continue; 274 | 275 | // Chunk sizes are in hexadecimal so convert to integer 276 | chunkSize = (uint32_t) strtol((const char *) line.c_str(), NULL, 16); 277 | DPRINT("Chunk Size: "); 278 | DPRINTLN(chunkSize); 279 | 280 | // Terminating chunk is of size 0 281 | if (chunkSize == 0) 282 | break; 283 | 284 | while (chunkSize > 0){ 285 | line = readStringUntil('\n'); 286 | if (_printResponseBody) 287 | Serial.println(line); 288 | 289 | _myResponse.body += line; 290 | _myResponse.body += '\n'; 291 | 292 | chunkSize -= line.length(); 293 | // The line above includes the '\r' character 294 | // which is not part of chunk size, so account for it 295 | --chunkSize; 296 | } 297 | 298 | // Skip over chunk trailer 299 | 300 | } 301 | 302 | return; 303 | 304 | } 305 | 306 | unsigned int HTTPSRedirect::getResponseStatus(void){ 307 | // Read response status line 308 | // ref: https://www.tutorialspoint.com/http/http_responses.htm 309 | 310 | unsigned int statusCode; 311 | String reasonPhrase; 312 | String line; 313 | 314 | unsigned int pos = -1; 315 | unsigned int pos2 = -1; 316 | 317 | // Skip any empty lines 318 | do{ 319 | line = readStringUntil('\n'); 320 | }while(line.length() == 0); 321 | 322 | pos = line.indexOf("HTTP/1.1 "); 323 | pos2 = line.indexOf(" ", 9); 324 | 325 | if (!pos){ 326 | statusCode = line.substring(9, pos2).toInt(); 327 | reasonPhrase = line.substring(pos2+1, line.length()-1); 328 | } 329 | else{ 330 | DPRINTLN("Error! No valid Status Code found in HTTP Response."); 331 | statusCode = 0; 332 | reasonPhrase = ""; 333 | } 334 | 335 | _myResponse.statusCode = statusCode; 336 | _myResponse.reasonPhrase = reasonPhrase; 337 | 338 | DPRINT("Status code: "); 339 | DPRINTLN(statusCode); 340 | DPRINT("Reason phrase: "); 341 | DPRINTLN(reasonPhrase); 342 | 343 | return statusCode; 344 | } 345 | 346 | bool HTTPSRedirect::GET(const String& url, const char* host){ 347 | return GET(url, host, _printResponseBody); 348 | } 349 | 350 | bool HTTPSRedirect::GET(const String& url, const char* host, const bool& disp){ 351 | bool retval; 352 | bool oldval; 353 | 354 | // set _printResponseBody temporarily to argument passed 355 | oldval = _printResponseBody; 356 | _printResponseBody = disp; 357 | 358 | // redirected Host and Url need to be initialized in case a 359 | // reConnectFinalEndpoint() request is made after an initial request 360 | // which did not have redirection 361 | _redirHost = host; 362 | _redirUrl = url; 363 | 364 | InitResponse(); 365 | 366 | // Create request packet 367 | createGetRequest(url, host); 368 | 369 | // Calll request handler 370 | retval = printRedir(); 371 | 372 | _printResponseBody = oldval; 373 | return retval; 374 | } 375 | 376 | bool HTTPSRedirect::POST(const String& url, const char* host, const String& payload){ 377 | return POST(url, host, payload, _printResponseBody); 378 | } 379 | 380 | bool HTTPSRedirect::POST(const String& url, const char* host, const String& payload, const bool& disp){ 381 | bool retval; 382 | bool oldval; 383 | 384 | // set _printResponseBody temporarily to argument passed 385 | oldval = _printResponseBody; 386 | _printResponseBody = disp; 387 | 388 | // redirected Host and Url need to be initialized in case a 389 | // reConnectFinalEndpoint() request is made after an initial request 390 | // which did not have redirection 391 | _redirHost = host; 392 | _redirUrl = url; 393 | 394 | InitResponse(); 395 | 396 | // Create request packet 397 | createPostRequest(url, host, payload); 398 | 399 | // Call request handler 400 | retval = printRedir(); 401 | 402 | _printResponseBody = oldval; 403 | return retval; 404 | } 405 | 406 | void HTTPSRedirect::InitResponse(void){ 407 | // Init response data 408 | _myResponse.body = ""; 409 | _myResponse.statusCode = 0; 410 | _myResponse.reasonPhrase = ""; 411 | _myResponse.redirected = false; 412 | } 413 | 414 | int HTTPSRedirect::getStatusCode(void){ 415 | return _myResponse.statusCode; 416 | } 417 | 418 | String HTTPSRedirect::getReasonPhrase(void){ 419 | return _myResponse.reasonPhrase; 420 | } 421 | 422 | String HTTPSRedirect::getResponseBody(void){ 423 | return _myResponse.body; 424 | } 425 | 426 | void HTTPSRedirect::setPrintResponseBody(bool disp){ 427 | _printResponseBody = disp; 428 | } 429 | 430 | void HTTPSRedirect::setMaxRedirects(const unsigned int n){ 431 | _maxRedirects = n; // to-do: use this in code above 432 | } 433 | 434 | void HTTPSRedirect::setContentTypeHeader(const char *type){ 435 | _contentTypeHeader = type; 436 | } 437 | 438 | #ifdef OPTIMIZE_SPEED 439 | bool HTTPSRedirect::reConnectFinalEndpoint(void){ 440 | // disconnect if connection already exists 441 | if (connected()) 442 | stop(); 443 | 444 | DPRINT("_redirHost: "); 445 | DPRINTLN(_redirHost); 446 | DPRINT("_redirUrl: "); 447 | DPRINTLN(_redirUrl); 448 | 449 | // Connect to stored final endpoint 450 | if (!connect(_redirHost.c_str(), _httpsPort)) { 451 | DPRINTLN("Connection to final URL failed!"); 452 | return false; 453 | } 454 | 455 | // Valid request packed must already be 456 | // present at this point in the member variable _Request 457 | // from the previous GET() or POST() request 458 | 459 | // Make call to final endpoint 460 | return printRedir(); 461 | } 462 | #endif 463 | 464 | #ifdef EXTRA_FNS 465 | void HTTPSRedirect::fetchBodyRaw(void){ 466 | String line; 467 | 468 | while (connected()){ 469 | line = readStringUntil('\n'); 470 | if (_printResponseBody) 471 | Serial.println(line); 472 | 473 | _myResponse.body += line; 474 | _myResponse.body += '\n'; 475 | } 476 | } 477 | 478 | void HTTPSRedirect::printHeaderFields(void){ 479 | DPRINT("Transfer Encoding: "); 480 | DPRINTLN(_hF.transferEncoding); 481 | DPRINT("Content Length: "); 482 | DPRINTLN(_hF.contentLength); 483 | DPRINT("Content Type: "); 484 | DPRINTLN(_hF.contentType); 485 | } 486 | #endif 487 | 488 | -------------------------------------------------------------------------------- /HTTPSRedirect.h: -------------------------------------------------------------------------------- 1 | /* HTTPS on ESP8266 with follow redirects, chunked encoding support 2 | * Version 2.1 3 | * Author: Sujay Phadke 4 | * Github: @electronicsguy 5 | * Copyright (C) 2017 Sujay Phadke 6 | * All rights reserved. 7 | * 8 | */ 9 | #pragma once 10 | #include 11 | 12 | // Un-comment for extra functionality 13 | //#define EXTRA_FNS 14 | #define OPTIMIZE_SPEED 15 | 16 | class HTTPSRedirect : public WiFiClientSecure { 17 | private: 18 | const int _httpsPort; 19 | bool _keepAlive; 20 | String _redirUrl; 21 | String _redirHost; 22 | unsigned int _maxRedirects; // to-do 23 | const char* _contentTypeHeader; 24 | 25 | struct headerFields{ 26 | String transferEncoding; 27 | unsigned int contentLength; 28 | #ifdef EXTRA_FNS 29 | String contentType; 30 | #endif 31 | }; 32 | 33 | headerFields _hF; 34 | 35 | String _Request; 36 | 37 | struct Response{ 38 | int statusCode; 39 | String reasonPhrase; 40 | bool redirected; 41 | String body; 42 | }; 43 | 44 | Response _myResponse; 45 | bool _printResponseBody; 46 | 47 | void Init(void); 48 | bool printRedir(void); 49 | void fetchHeader(void); 50 | bool getLocationURL(void); 51 | void fetchBodyUnChunked(unsigned); 52 | void fetchBodyChunked(void); 53 | unsigned int getResponseStatus(void); 54 | void InitResponse(void); 55 | void createGetRequest(const String&, const char*); 56 | void createPostRequest(const String&, const char*, const String&); 57 | 58 | #ifdef EXTRA_FNS 59 | void fetchBodyRaw(void); 60 | void printHeaderFields(void); 61 | #endif 62 | 63 | public: 64 | 65 | HTTPSRedirect(void); 66 | HTTPSRedirect(const int); 67 | ~HTTPSRedirect(); 68 | 69 | bool GET(const String&, const char*); 70 | bool GET(const String&, const char*, const bool&); 71 | bool POST(const String&, const char*, const String&); 72 | bool POST(const String&, const char*, const String&, const bool&); 73 | 74 | int getStatusCode(void); 75 | String getReasonPhrase(void); 76 | String getResponseBody(void); 77 | 78 | void setPrintResponseBody(bool); 79 | void setMaxRedirects(const unsigned int); 80 | 81 | void setContentTypeHeader(const char *); 82 | #ifdef OPTIMIZE_SPEED 83 | bool reConnectFinalEndpoint(void); 84 | #endif 85 | 86 | }; 87 | -------------------------------------------------------------------------------- /LinkedList.h: -------------------------------------------------------------------------------- 1 | /* Linked List 2 | * Version 1.0 3 | * Author: Multiple, David Horn 4 | * 5 | * Updated: 2020/01/18 6 | * Changes: Added loop() function, fixed exception in Clear() 7 | */ 8 | 9 | #ifndef LinkedList_hpp 10 | #define LinkedList_hpp 11 | 12 | template 13 | class ListNode { 14 | public: 15 | T element; 16 | ListNode* next; 17 | ListNode* prev; 18 | 19 | ListNode(T element, ListNode* prev, ListNode* next) : element(element) 20 | { 21 | this->next = next; 22 | this->prev = prev; 23 | }; 24 | }; 25 | 26 | template 27 | class LinkedList { 28 | private: 29 | int length; 30 | ListNode* head; 31 | ListNode* tail; 32 | ListNode* curr; 33 | public: 34 | LinkedList(); 35 | LinkedList(const LinkedList&); 36 | ~LinkedList(); 37 | T& getCurrent(); 38 | T& First() const; 39 | T& Last() const; 40 | int getLength(); 41 | void Append(T); 42 | void DeleteLast(); 43 | void DeleteFirst(); 44 | void DeleteCurrent(); 45 | bool next(); 46 | bool loop(); 47 | bool moveToStart(); 48 | bool prev(); 49 | void Delete(T&); 50 | bool Search(T); 51 | void Clear(); 52 | void PutFirstToLast(); 53 | void Update(T elem); 54 | LinkedList& operator = (const LinkedList&); 55 | }; 56 | 57 | template 58 | LinkedList::LinkedList() { 59 | length = 0; 60 | head = nullptr; 61 | tail = nullptr; 62 | curr = nullptr; 63 | } 64 | 65 | template 66 | LinkedList::LinkedList(const LinkedList & list) { 67 | length = 0; 68 | head = nullptr; 69 | tail = nullptr; 70 | curr = nullptr; 71 | 72 | ListNode * temp = list.head; 73 | 74 | while (temp != nullptr) 75 | { 76 | Append(temp->element); 77 | temp = temp->next; 78 | } 79 | } 80 | 81 | template 82 | LinkedList & LinkedList::operator=(const LinkedList & list) 83 | { 84 | Clear(); 85 | 86 | ListNode * temp = list.head; 87 | 88 | while (temp != nullptr) 89 | { 90 | Append(temp->element); 91 | temp = temp->next; 92 | } 93 | 94 | return *this; 95 | } 96 | 97 | template 98 | LinkedList::~LinkedList() { 99 | Clear(); 100 | } 101 | 102 | template 103 | T& LinkedList::getCurrent() 104 | { 105 | return curr->element; 106 | } 107 | 108 | template 109 | T& LinkedList::First() const 110 | { 111 | return head->element; 112 | } 113 | 114 | template 115 | T& LinkedList::Last() const 116 | { 117 | return tail->element; 118 | } 119 | 120 | template 121 | int LinkedList::getLength() 122 | { 123 | return length; 124 | } 125 | 126 | template 127 | void LinkedList::Append(T element) 128 | { 129 | ListNode * node = new ListNode(element, tail, nullptr); 130 | malloc(sizeof(node)); 131 | 132 | if (length == 0) 133 | curr = tail = head = node; 134 | else { 135 | tail->next = node; 136 | tail = node; 137 | } 138 | 139 | length++; 140 | 141 | } 142 | 143 | template 144 | void LinkedList::DeleteLast() 145 | { 146 | if (length == 0) 147 | return; 148 | curr = tail; 149 | DeleteCurrent(); 150 | } 151 | 152 | template 153 | void LinkedList::DeleteFirst() 154 | { 155 | if (length == 0) 156 | return; 157 | curr = head; 158 | DeleteCurrent(); 159 | } 160 | 161 | template 162 | bool LinkedList::next() 163 | { 164 | if (length == 0) 165 | return false; 166 | 167 | if (curr->next == nullptr) 168 | return false; 169 | 170 | curr = curr->next; 171 | return true; 172 | } 173 | 174 | template 175 | bool LinkedList::loop() 176 | { 177 | if (length == 0) 178 | return false; 179 | 180 | if (curr->next == nullptr) 181 | { 182 | curr = head; 183 | return true; 184 | } 185 | 186 | curr = curr->next; 187 | return true; 188 | } 189 | 190 | template 191 | bool LinkedList::moveToStart() 192 | { 193 | curr = head; 194 | return length != 0; 195 | } 196 | 197 | template 198 | bool LinkedList::prev() 199 | { 200 | if (length == 0) 201 | return false; 202 | 203 | if (curr->prev != nullptr) 204 | return false; 205 | 206 | curr = curr->prev; 207 | return true; 208 | } 209 | 210 | template 211 | void LinkedList::Delete(T & elem) 212 | { 213 | if (Search(elem)) 214 | DeleteCurrent(); 215 | } 216 | 217 | template 218 | void LinkedList::DeleteCurrent() 219 | { 220 | if (length == 0) 221 | return; 222 | length--; 223 | ListNode * temp = curr; 224 | 225 | if (temp->prev != nullptr) 226 | temp->prev->next = temp->next; 227 | if (temp->next != nullptr) 228 | temp->next->prev = temp->prev; 229 | 230 | if (length == 0) 231 | head = curr = tail = nullptr; 232 | else if (curr == head) 233 | curr = head = head->next; 234 | else if (curr == tail) 235 | curr = tail = tail->prev; 236 | else 237 | curr = curr->prev; 238 | 239 | delete temp; 240 | } 241 | 242 | template 243 | bool LinkedList::Search(T elem) 244 | { 245 | if (length == 0) 246 | return false; 247 | if (moveToStart()) 248 | do { 249 | if (curr->element == elem) 250 | return true; 251 | } while (next()); 252 | return false; 253 | } 254 | 255 | template 256 | void LinkedList::PutFirstToLast() 257 | { 258 | if (length < 2) 259 | return; 260 | ListNode* temp = head->next; 261 | head->next->prev = nullptr; 262 | head->next = nullptr; 263 | head->prev = tail; 264 | tail->next = head; 265 | tail = head; 266 | head = temp; 267 | } 268 | 269 | template 270 | void LinkedList::Update(T elem) 271 | { 272 | if (Search(elem)) 273 | curr->element = elem; 274 | } 275 | 276 | template 277 | void LinkedList::Clear() 278 | { 279 | if (length == 0) 280 | return; 281 | ListNode * temp = head; 282 | 283 | while (temp != nullptr) 284 | { 285 | head = head->next; 286 | delete temp; 287 | temp = head; 288 | length = 0; 289 | } 290 | 291 | head = curr = tail = nullptr; 292 | 293 | } 294 | 295 | 296 | #endif 297 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bindicator 2 | ## Overview 3 | A bin collection notifier partly inspired by [this Twitter post](https://twitter.com/tarbard/status/1002464120447397888?lang=en) by Darren Tabard. Significant changes include collection schedule download from a Google Calendar, OLED display support, use of two NeoPixel Rings for greater brightness, and a capacitive touch sensor to cancel the reminder. 4 | 5 | ### Click on images to see videos 6 | [![Watch the video](https://img.youtube.com/vi/CMq2k3gbSQc/maxresdefault.jpg)](https://youtu.be/CMq2k3gbSQc) 7 | 8 | ## How it works 9 | This is a project in two parts - simple Arduino code running on an ESP8266 handles the WiFi connection, drives the NeoPixel, and regularly downloads calendar events in JSON format from a Google Calendar. On first start it will launch a captive portal access point (SSID "Bindicator"), where you must supply a Google Scripts App ID for your calendar. 10 | 11 | Once connected any collections will cause the Bindicator to light up 24 hours before. Tapping the top of the "bin" will cancel the reminder. Multiple collections (three maximum) on the same day will show as the colour cycling. Colour is determined by the colour chosen for the event in Google Calendar. If the reminder is ignored, it will auto-cancel at 8.00am on the day of collection. 12 | 13 | Calendar data is refreshed every 20 minutes. 14 | 15 | [![Watch the video](https://img.youtube.com/vi/yja5Ed7apo8/maxresdefault.jpg)](https://youtu.be/yja5Ed7apo8) 16 | 17 | ## Make your own 18 | ### Parts list 19 | 1. [ESP8266 development board with OLED (any ESP8266 will work)](https://www.amazon.co.uk/gp/product/B076S8S6HL/ref=ox_sc_act_title_1?smid=A1QGN06QN25C35&psc=1) OR [Makerfire D1 Mini or clone](https://www.amazon.co.uk/Makerfire-NodeMcu-ESP8266-Internet-Development/dp/B07KYFZD17/ref=sr_1_1_sspa?crid=2KMR2ZYY6J68T) 20 | 2. [NeoPixel 7-LED ring (or generic equivalent) * 2](https://www.amazon.co.uk/gp/product/B07L82MSC9/ref=ox_sc_act_title_2?smid=A3TQ6TJY5HYALR&psc=1) 21 | 3. [Generic capacitive touch sensor](https://www.amazon.co.uk/ARCELI-2-5-5-5V-Capacitive-Self-Lock-Arduino/dp/B07BVN4CNH/) 22 | 23 | ### Arduino code 24 | The Arduino code is plug-and-play. Further information on customising the program is available below. 25 | 26 | > If you're using a plain ESP8266 board you should comment out the DISPLAY definition on line 18. 27 | 28 | ### Google script 29 | Instead of trying to download and parse an entire calendar, the heavy lifting is handled by a Google Script. Go to [Google Scripts](https://script.google.com/home) and create a new project. Copy and paste the code from the GoogleScript.gs file, save it, and publish it. 30 | 31 | **You need to save the API ID at this point**. This is the section of the URL from /macros/s through to /exec (see below in bold): 32 | > https://script.google.com/macros/s/**AKfycbzcUsdfsdfsdltDtBHlbgde_9fXQvYMuddsvGhHFIGcSl3wr_5k**/exec 33 | 34 | You'll need to supply this ID when you set up the Bindicator for the first time, otherwise it'll just show you my bin collection calendar. 35 | 36 | ### Soldering 37 | Minimal soldering required. You'll need to solder wires to the NeoPixels and touch sensor. The NeoPixels support loop-in-and-out for data, saving a pin on the ESP8266. Dupont jumper wires can be used to connect to the board. I removed the plastic connector and replaced it with thin heat shrink tube to allow more flexibility when squashing everything into the base. 38 | 39 | NeoPixels and the button are happy on either 3.3 or 5V. I would not recommend running both from 3.3V as the on-board regulator will struggle, but the 5V side should be fine, as is a mixture of both. 40 | 41 | Important: The D1 Mini only has one ground. Either loop through both NeoPixels and then on to the button, or solder a couple of header pins horizontally to the top of the USB port (common ground). 42 | 43 | ### 3D print 44 | I've modified a [fantastic model of a wheelie bin / trash can](https://www.thingiverse.com/thing:1935572) by DrLex. You'll need to download the wheels and axles from Thingiverse because I've not edited them. I've changed the bin model to include a mounting ring for the NeoPixel and a housing for the capacitive touch switch. I'd recommend printing with extra solid layers both on the sides and the top. I've used transparent PLA and white. White is best. Grey definitely doesn't work. :-) 45 | 46 | If you use transparent PLA you may wish to print a diffuser which drops on top of the LED ring. This makes the light much easier to see in daylight, and prevents colour fringing effects from the layer lines. 47 | 48 | I hot-glued the button and NeoPixel to the lid. 49 | 50 | [Here's the Thingiverse remix](https://www.thingiverse.com/thing:4147342) 51 | 52 | [![Watch the video](https://img.youtube.com/vi/6T-8IIQMk4Q/maxresdefault.jpg)](https://youtu.be/6T-8IIQMk4Q) 53 | 54 | ## LED colours 55 | * **Pulsing White**: Connecting to WiFi 56 | * **Solid White**: Downloading calendar data 57 | * **Magenta (at startup)**: WiFi portal running 58 | * **Magenta**: Reminder cancelled 59 | * **Red**: Unable to connect to WiFi 60 | 61 | ## Troubleshooting 62 | **Can't connect to WiFi.** 63 | The ESP8266 will only connect to 2.4GHz networks. If the captive portal isn't working properly, you can easily hard-code the SSID / password / Google Script ID into the Arduino program. 64 | 65 | **I can connect but then see a single red flash** 66 | Check your Google Script ID. This is a response to a 404, which Google returns if the script ID is wrong. See above. 67 | 68 | **Corrupted names on the OLED display.** 69 | I think this is a memory leak from the Linked List which handles events. The reality is that I'm not sure why it's happening. If you don't like it just reboot the Bindicator. 70 | 71 | **Colours aren't distinctive.** 72 | I'd recommend sticking with strong primary colours (green / blue / orange etc) instead of the paler equivalents. The brightness variable may help here, as would implementing the gamma correction function available from Adafruit. If you don't like them, they're easily corrected (8 bit RGB values). 73 | 74 | **Generic "it's not working!"** 75 | The ESP8266 is quite particular over pins used for GPIO, especially at boot. Check the various pin-outs on Google for recommendations. The numbers silk-screened onto the board do not correspond with the Arduino IDE assignment. 76 | 77 | You could also uncomment the DEBUG flag in the DebugMacros.h file which will provide more information through the serial monitor. 78 | 79 | ## Improvements 80 | Reducing the reliance on hot-glue would be a start. I wasn't aware of NeoPixel linear boards when I built this, which could be used to create some very interesting effects if mounted on the inside back wall of the model. 81 | 82 | ## Thanks 83 | * Darren Tabard [for the inspiration](https://twitter.com/tarbard/status/1002464120447397888?lang=en). 84 | * DrLex for the [incredible 3D model](https://www.thingiverse.com/thing:1935572). 85 | * Adafruit for their NeoPixel Library. 86 | * Arkhipenko for the [TaskScheduler Library](https://github.com/arkhipenko/TaskScheduler) library. 87 | * ElectronicsGuy for his [HTTPSRedirect library](https://github.com/electronicsguy/ESP8266/tree/master/HTTPSRedirect), without which this wouldn't be possible. 88 | * Olikraus's [u8g2 display library and fonts](https://github.com/olikraus/u8g2). 89 | * [ArduinoJSON](https://arduinojson.org/). 90 | -------------------------------------------------------------------------------- /stl/Base.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furstyferret-dev/bindicator/61330731fb617c8fbe3db16a320b821270070026/stl/Base.stl -------------------------------------------------------------------------------- /stl/Bin Body.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furstyferret-dev/bindicator/61330731fb617c8fbe3db16a320b821270070026/stl/Bin Body.stl -------------------------------------------------------------------------------- /stl/Lid (no supports).stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furstyferret-dev/bindicator/61330731fb617c8fbe3db16a320b821270070026/stl/Lid (no supports).stl -------------------------------------------------------------------------------- /stl/README.md: -------------------------------------------------------------------------------- 1 | ### 3D model remixed from original Thingiverse design: 2 | https://www.thingiverse.com/thing:1935572 3 | 4 | ### Thingiverse link: 5 | https://www.thingiverse.com/thing:4147342 6 | 7 | ### Changes made: 8 | * Base added 9 | * Cutout in bottom for wiring / header pins 10 | * Neopixel holders 11 | * Lid modified to hold capacitive touch sensor 12 | * Cable routing 13 | 14 | ### Print settings: 15 | Printed on a Flashforge Finder using white PLA, temperature 190C. 0.12mm layer thickness with four perimeter walls and supports under the wheel arches. 16 | 17 | Infil is 3D at 10% on the body, and default hexagon on the base. 18 | -------------------------------------------------------------------------------- /stl/TrashCan-axle.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furstyferret-dev/bindicator/61330731fb617c8fbe3db16a320b821270070026/stl/TrashCan-axle.stl -------------------------------------------------------------------------------- /stl/TrashCan-wheel.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furstyferret-dev/bindicator/61330731fb617c8fbe3db16a320b821270070026/stl/TrashCan-wheel.stl -------------------------------------------------------------------------------- /ws2812fx/WS2812FX.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | WS2812FX.cpp - Library for WS2812 LED effects. 3 | 4 | Harm Aldick - 2016 5 | www.aldick.org 6 | 7 | 8 | FEATURES 9 | * A lot of blinken modes and counting 10 | * WS2812FX can be used as drop-in replacement for Adafruit NeoPixel Library 11 | 12 | NOTES 13 | * Uses the Adafruit NeoPixel library. Get it here: 14 | https://github.com/adafruit/Adafruit_NeoPixel 15 | 16 | 17 | 18 | LICENSE 19 | 20 | The MIT License (MIT) 21 | 22 | Copyright (c) 2016 Harm Aldick 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy 25 | of this software and associated documentation files (the "Software"), to deal 26 | in the Software without restriction, including without limitation the rights 27 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | copies of the Software, and to permit persons to whom the Software is 29 | furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in 32 | all copies or substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 40 | THE SOFTWARE. 41 | 42 | 43 | CHANGELOG 44 | 45 | 2016-05-28 Initial beta release 46 | 2016-06-03 Code cleanup, minor improvements, new modes 47 | 2016-06-04 2 new fx, fixed setColor (now also resets _mode_color) 48 | 2017-02-02 added external trigger functionality (e.g. for sound-to-light) 49 | 2017-02-02 removed "blackout" on mode, speed or color-change 50 | 2017-09-26 implemented segment and reverse features 51 | 2017-11-16 changed speed calc, reduced memory footprint 52 | 2018-02-24 added hooks for user created custom effects 53 | */ 54 | 55 | #include "WS2812FX.h" 56 | 57 | void WS2812FX::init() { 58 | resetSegmentRuntimes(); 59 | Adafruit_NeoPixel::begin(); 60 | } 61 | 62 | // void WS2812FX::timer() { 63 | // for (int j=0; j < 1000; j++) { 64 | // uint16_t delay = (this->*_mode[SEGMENT.mode])(); 65 | // } 66 | // } 67 | 68 | void WS2812FX::service() { 69 | if(_running || _triggered) { 70 | unsigned long now = millis(); // Be aware, millis() rolls over every 49 days 71 | bool doShow = false; 72 | for(uint8_t i=0; i < _num_segments; i++) { 73 | _segment_index = i; 74 | CLR_FRAME; 75 | if(now > SEGMENT_RUNTIME.next_time || _triggered) { 76 | SET_FRAME; 77 | doShow = true; 78 | uint16_t delay = (this->*_mode[SEGMENT.mode])(); 79 | SEGMENT_RUNTIME.next_time = now + max(delay, SPEED_MIN); 80 | SEGMENT_RUNTIME.counter_mode_call++; 81 | } 82 | } 83 | if(doShow) { 84 | delay(1); // for ESP32 (see https://forums.adafruit.com/viewtopic.php?f=47&t=117327) 85 | show(); 86 | } 87 | _triggered = false; 88 | } 89 | } 90 | 91 | // overload setPixelColor() functions so we can use gamma correction 92 | // (see https://learn.adafruit.com/led-tricks-gamma-correction/the-issue) 93 | void WS2812FX::setPixelColor(uint16_t n, uint32_t c) { 94 | if(IS_GAMMA) { 95 | uint8_t w = (c >> 24) & 0xFF; 96 | uint8_t r = (c >> 16) & 0xFF; 97 | uint8_t g = (c >> 8) & 0xFF; 98 | uint8_t b = c & 0xFF; 99 | Adafruit_NeoPixel::setPixelColor(n, gamma8(r), gamma8(g), gamma8(b), gamma8(w)); 100 | } else { 101 | Adafruit_NeoPixel::setPixelColor(n, c); 102 | } 103 | } 104 | 105 | void WS2812FX::setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b) { 106 | if(IS_GAMMA) { 107 | Adafruit_NeoPixel::setPixelColor(n, gamma8(r), gamma8(g), gamma8(b)); 108 | } else { 109 | Adafruit_NeoPixel::setPixelColor(n, r, g, b); 110 | } 111 | } 112 | 113 | void WS2812FX::setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { 114 | if(IS_GAMMA) { 115 | Adafruit_NeoPixel::setPixelColor(n, gamma8(r), gamma8(g), gamma8(b), gamma8(w)); 116 | } else { 117 | Adafruit_NeoPixel::setPixelColor(n, r, g, b, w); 118 | } 119 | } 120 | 121 | void WS2812FX::copyPixels(uint16_t dest, uint16_t src, uint16_t count) { 122 | uint8_t *pixels = getPixels(); 123 | uint8_t bytesPerPixel = getNumBytesPerPixel(); // 3=RGB, 4=RGBW 124 | 125 | memmove(pixels + (dest * bytesPerPixel), pixels + (src * bytesPerPixel), count * bytesPerPixel); 126 | } 127 | 128 | // overload show() functions so we can use custom show() 129 | void WS2812FX::show(void) { 130 | if(customShow == NULL) { 131 | Adafruit_NeoPixel::show(); 132 | } else { 133 | customShow(); 134 | } 135 | } 136 | 137 | void WS2812FX::start() { 138 | resetSegmentRuntimes(); 139 | _running = true; 140 | } 141 | 142 | void WS2812FX::stop() { 143 | _running = false; 144 | strip_off(); 145 | } 146 | 147 | void WS2812FX::pause() { 148 | _running = false; 149 | } 150 | 151 | void WS2812FX::resume() { 152 | _running = true; 153 | } 154 | 155 | void WS2812FX::trigger() { 156 | _triggered = true; 157 | } 158 | 159 | void WS2812FX::setMode(uint8_t m) { 160 | setMode(0, m); 161 | } 162 | 163 | void WS2812FX::setMode(uint8_t seg, uint8_t m) { 164 | resetSegmentRuntime(seg); 165 | _segments[seg].mode = constrain(m, 0, MODE_COUNT - 1); 166 | } 167 | 168 | void WS2812FX::setOptions(uint8_t seg, uint8_t o) { 169 | _segments[seg].options = o; 170 | } 171 | 172 | void WS2812FX::setSpeed(uint16_t s) { 173 | setSpeed(0, s); 174 | } 175 | 176 | void WS2812FX::setSpeed(uint8_t seg, uint16_t s) { 177 | // resetSegmentRuntime(seg); 178 | _segments[seg].speed = constrain(s, SPEED_MIN, SPEED_MAX); 179 | } 180 | 181 | void WS2812FX::increaseSpeed(uint8_t s) { 182 | uint16_t newSpeed = constrain(SEGMENT.speed + s, SPEED_MIN, SPEED_MAX); 183 | setSpeed(newSpeed); 184 | } 185 | 186 | void WS2812FX::decreaseSpeed(uint8_t s) { 187 | uint16_t newSpeed = constrain(SEGMENT.speed - s, SPEED_MIN, SPEED_MAX); 188 | setSpeed(newSpeed); 189 | } 190 | 191 | void WS2812FX::setColor(uint8_t r, uint8_t g, uint8_t b) { 192 | setColor(((uint32_t)r << 16) | ((uint32_t)g << 8) | b); 193 | } 194 | 195 | void WS2812FX::setColor(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { 196 | setColor((((uint32_t)w << 24)| ((uint32_t)r << 16) | ((uint32_t)g << 8)| ((uint32_t)b))); 197 | } 198 | 199 | void WS2812FX::setColor(uint32_t c) { 200 | setColor(0, c); 201 | } 202 | 203 | void WS2812FX::setColor(uint8_t seg, uint32_t c) { 204 | // resetSegmentRuntime(seg); 205 | _segments[seg].colors[0] = c; 206 | } 207 | 208 | void WS2812FX::setColors(uint8_t seg, uint32_t* c) { 209 | // resetSegmentRuntime(seg); 210 | for(uint8_t i=0; i 1); 240 | 241 | _segments[0].start = 0; 242 | _segments[0].stop = Adafruit_NeoPixel::numLEDs - 1; 243 | } 244 | 245 | void WS2812FX::increaseLength(uint16_t s) { 246 | s = _segments[0].stop - _segments[0].start + 1 + s; 247 | setLength(s); 248 | } 249 | 250 | void WS2812FX::decreaseLength(uint16_t s) { 251 | if (s > _segments[0].stop - _segments[0].start + 1) s = 1; 252 | s = _segments[0].stop - _segments[0].start + 1 - s; 253 | 254 | for(uint16_t i=_segments[0].start + s; i <= (_segments[0].stop - _segments[0].start + 1); i++) { 255 | setPixelColor(i, 0); 256 | } 257 | show(); 258 | 259 | setLength(s); 260 | } 261 | 262 | boolean WS2812FX::isRunning() { 263 | return _running; 264 | } 265 | 266 | boolean WS2812FX::isTriggered() { 267 | return _triggered; 268 | } 269 | 270 | boolean WS2812FX::isFrame() { 271 | return isFrame(0); 272 | } 273 | 274 | boolean WS2812FX::isFrame(uint8_t segIndex) { 275 | return (_segment_runtimes[segIndex].aux_param2 & FRAME); 276 | } 277 | 278 | boolean WS2812FX::isCycle() { 279 | return isCycle(0); 280 | } 281 | 282 | boolean WS2812FX::isCycle(uint8_t segIndex) { 283 | return (_segment_runtimes[segIndex].aux_param2 & CYCLE); 284 | } 285 | 286 | uint8_t WS2812FX::getMode(void) { 287 | return getMode(0); 288 | } 289 | 290 | uint8_t WS2812FX::getMode(uint8_t seg) { 291 | return _segments[seg].mode; 292 | } 293 | 294 | uint16_t WS2812FX::getSpeed(void) { 295 | return getSpeed(0); 296 | } 297 | 298 | uint16_t WS2812FX::getSpeed(uint8_t seg) { 299 | return _segments[seg].speed; 300 | } 301 | 302 | 303 | uint8_t WS2812FX::getOptions(uint8_t seg) { 304 | return _segments[seg].options; 305 | } 306 | 307 | uint16_t WS2812FX::getLength(void) { 308 | return numPixels(); 309 | } 310 | 311 | uint16_t WS2812FX::getNumBytes(void) { 312 | return numBytes; 313 | } 314 | 315 | uint8_t WS2812FX::getNumBytesPerPixel(void) { 316 | return (wOffset == rOffset) ? 3 : 4; // 3=RGB, 4=RGBW 317 | } 318 | 319 | uint8_t WS2812FX::getModeCount(void) { 320 | return MODE_COUNT; 321 | } 322 | 323 | uint8_t WS2812FX::getNumSegments(void) { 324 | return _num_segments; 325 | } 326 | 327 | void WS2812FX::setNumSegments(uint8_t n) { 328 | _num_segments = n; 329 | } 330 | 331 | uint32_t WS2812FX::getColor(void) { 332 | return getColor(0); 333 | } 334 | 335 | uint32_t WS2812FX::getColor(uint8_t seg) { 336 | return _segments[seg].colors[0]; 337 | } 338 | 339 | uint32_t* WS2812FX::getColors(uint8_t seg) { 340 | return _segments[seg].colors; 341 | } 342 | 343 | WS2812FX::Segment* WS2812FX::getSegment(void) { 344 | return &_segments[_segment_index]; 345 | } 346 | 347 | WS2812FX::Segment* WS2812FX::getSegment(uint8_t seg) { 348 | return &_segments[seg]; 349 | } 350 | 351 | WS2812FX::Segment* WS2812FX::getSegments(void) { 352 | return _segments; 353 | } 354 | 355 | WS2812FX::Segment_runtime* WS2812FX::getSegmentRuntime(void) { 356 | return &_segment_runtimes[_segment_index]; 357 | } 358 | 359 | WS2812FX::Segment_runtime* WS2812FX::getSegmentRuntime(uint8_t seg) { 360 | return &_segment_runtimes[seg]; 361 | } 362 | 363 | WS2812FX::Segment_runtime* WS2812FX::getSegmentRuntimes(void) { 364 | return _segment_runtimes; 365 | } 366 | 367 | const __FlashStringHelper* WS2812FX::getModeName(uint8_t m) { 368 | if(m < MODE_COUNT) { 369 | return _names[m]; 370 | } else { 371 | return F(""); 372 | } 373 | } 374 | 375 | void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint16_t speed, bool reverse) { 376 | uint32_t colors[] = {color, 0, 0}; 377 | setSegment(n, start, stop, mode, colors, speed, reverse); 378 | } 379 | 380 | void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint16_t speed, uint8_t options) { 381 | uint32_t colors[] = {color, 0, 0}; 382 | setSegment(n, start, stop, mode, colors, speed, options); 383 | } 384 | 385 | void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint16_t speed, bool reverse) { 386 | setSegment(n, start, stop, mode, colors, speed, (uint8_t)(reverse ? REVERSE : NO_OPTIONS)); 387 | } 388 | 389 | void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint16_t speed, uint8_t options) { 390 | if(n < (sizeof(_segments) / sizeof(_segments[0]))) { 391 | if(n + 1 > _num_segments) _num_segments = n + 1; 392 | _segments[n].start = start; 393 | _segments[n].stop = stop; 394 | _segments[n].mode = mode; 395 | _segments[n].speed = speed; 396 | _segments[n].options = options; 397 | 398 | for(uint8_t i=0; i g -> b -> back to r 438 | * Inspired by the Adafruit examples. 439 | */ 440 | uint32_t WS2812FX::color_wheel(uint8_t pos) { 441 | pos = 255 - pos; 442 | if(pos < 85) { 443 | return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3); 444 | } else if(pos < 170) { 445 | pos -= 85; 446 | return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3); 447 | } else { 448 | pos -= 170; 449 | return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0); 450 | } 451 | } 452 | 453 | 454 | /* 455 | * Returns a new, random wheel index with a minimum distance of 42 from pos. 456 | */ 457 | uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { 458 | uint8_t r = 0; 459 | uint8_t x = 0; 460 | uint8_t y = 0; 461 | uint8_t d = 0; 462 | 463 | while(d < 42) { 464 | r = random8(); 465 | x = abs(pos - r); 466 | y = 255 - x; 467 | d = min(x, y); 468 | } 469 | 470 | return r; 471 | } 472 | 473 | // fast 8-bit random number generator shamelessly borrowed from FastLED 474 | uint8_t WS2812FX::random8() { 475 | _rand16seed = (_rand16seed * 2053) + 13849; 476 | return (uint8_t)((_rand16seed + (_rand16seed >> 8)) & 0xFF); 477 | } 478 | 479 | // note random8(lim) generates numbers in the range 0 to (lim -1) 480 | uint8_t WS2812FX::random8(uint8_t lim) { 481 | uint8_t r = random8(); 482 | r = (r * lim) >> 8; 483 | return r; 484 | } 485 | 486 | uint16_t WS2812FX::random16() { 487 | return (uint16_t)random8() * 256 + random8(); 488 | } 489 | 490 | // note random16(lim) generates numbers in the range 0 to (lim - 1) 491 | uint16_t WS2812FX::random16(uint16_t lim) { 492 | uint16_t r = random16(); 493 | r = ((uint32_t)r * lim) >> 16; 494 | return r; 495 | } 496 | 497 | // Return the sum of all LED intensities (can be used for 498 | // rudimentary power calculations) 499 | uint32_t WS2812FX::intensitySum() { 500 | uint8_t *pixels = getPixels(); 501 | uint32_t sum = 0; 502 | for(uint16_t i=0; i 255) lum = 511 - lum; // lum = 15 -> 255 -> 15 714 | 715 | uint16_t delay; 716 | if(lum == 15) delay = 970; // 970 pause before each breath 717 | else if(lum <= 25) delay = 38; // 19 718 | else if(lum <= 50) delay = 36; // 18 719 | else if(lum <= 75) delay = 28; // 14 720 | else if(lum <= 100) delay = 20; // 10 721 | else if(lum <= 125) delay = 14; // 7 722 | else if(lum <= 150) delay = 11; // 5 723 | else delay = 10; // 4 724 | 725 | uint32_t color = SEGMENT.colors[0]; 726 | uint8_t w = (color >> 24 & 0xFF) * lum / 256; 727 | uint8_t r = (color >> 16 & 0xFF) * lum / 256; 728 | uint8_t g = (color >> 8 & 0xFF) * lum / 256; 729 | uint8_t b = (color & 0xFF) * lum / 256; 730 | for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { 731 | setPixelColor(i, r, g, b, w); 732 | } 733 | 734 | SEGMENT_RUNTIME.counter_mode_step += 2; 735 | if(SEGMENT_RUNTIME.counter_mode_step > (512-15)) SEGMENT_RUNTIME.counter_mode_step = 15; 736 | return delay; 737 | } 738 | 739 | 740 | /* 741 | * Fades the LEDs between two colors 742 | */ 743 | uint16_t WS2812FX::mode_fade(void) { 744 | int lum = SEGMENT_RUNTIME.counter_mode_step; 745 | if(lum > 255) lum = 511 - lum; // lum = 0 -> 255 -> 0 746 | 747 | uint32_t color = color_blend(SEGMENT.colors[0], SEGMENT.colors[1], lum); 748 | for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { 749 | setPixelColor(i, color); 750 | } 751 | 752 | SEGMENT_RUNTIME.counter_mode_step += 4; 753 | if(SEGMENT_RUNTIME.counter_mode_step > 511) SEGMENT_RUNTIME.counter_mode_step = 0; 754 | return (SEGMENT.speed / 128); 755 | } 756 | 757 | 758 | /* 759 | * scan function - runs a block of pixels back and forth. 760 | */ 761 | uint16_t WS2812FX::scan(uint32_t color1, uint32_t color2, bool dual) { 762 | int8_t dir = SEGMENT_RUNTIME.aux_param ? -1 : 1; 763 | uint8_t size = 1 << SIZE_OPTION; 764 | 765 | for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { 766 | setPixelColor(i, color2); 767 | } 768 | 769 | for(uint8_t i = 0; i < size; i++) { 770 | if(IS_REVERSE || dual) { 771 | setPixelColor(SEGMENT.stop - SEGMENT_RUNTIME.counter_mode_step - i, color1); 772 | } 773 | if(!IS_REVERSE || dual) { 774 | setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step + i, color1); 775 | } 776 | } 777 | 778 | SEGMENT_RUNTIME.counter_mode_step += dir; 779 | if(SEGMENT_RUNTIME.counter_mode_step == 0) SEGMENT_RUNTIME.aux_param = 0; 780 | if(SEGMENT_RUNTIME.counter_mode_step >= (uint16_t)(SEGMENT_LENGTH - size)) SEGMENT_RUNTIME.aux_param = 1; 781 | 782 | return (SEGMENT.speed / (SEGMENT_LENGTH * 2)); 783 | } 784 | 785 | 786 | /* 787 | * Runs a block of pixels back and forth. 788 | */ 789 | uint16_t WS2812FX::mode_scan(void) { 790 | return scan(SEGMENT.colors[0], SEGMENT.colors[1], false); 791 | } 792 | 793 | 794 | /* 795 | * Runs two blocks of pixels back and forth in opposite directions. 796 | */ 797 | uint16_t WS2812FX::mode_dual_scan(void) { 798 | return scan(SEGMENT.colors[0], SEGMENT.colors[1], true); 799 | } 800 | 801 | 802 | /* 803 | * Cycles all LEDs at once through a rainbow. 804 | */ 805 | uint16_t WS2812FX::mode_rainbow(void) { 806 | uint32_t color = color_wheel(SEGMENT_RUNTIME.counter_mode_step); 807 | for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { 808 | setPixelColor(i, color); 809 | } 810 | 811 | SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF; 812 | return (SEGMENT.speed / 32); 813 | } 814 | 815 | 816 | /* 817 | * Cycles a rainbow over the entire string of LEDs. 818 | */ 819 | uint16_t WS2812FX::mode_rainbow_cycle(void) { 820 | for(uint16_t i=0; i < SEGMENT_LENGTH; i++) { 821 | uint32_t color = color_wheel(((i * 256 / SEGMENT_LENGTH) + SEGMENT_RUNTIME.counter_mode_step) & 0xFF); 822 | setPixelColor(SEGMENT.start + i, color); 823 | } 824 | 825 | SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF; 826 | return (SEGMENT.speed / 256); 827 | } 828 | 829 | 830 | /* 831 | * Theatre-style crawling lights. 832 | * Inspired by the Adafruit examples. 833 | */ 834 | uint16_t WS2812FX::mode_theater_chase(void) { 835 | return tricolor_chase(SEGMENT.colors[0], SEGMENT.colors[1], SEGMENT.colors[1]); 836 | } 837 | 838 | 839 | /* 840 | * Theatre-style crawling lights with rainbow effect. 841 | * Inspired by the Adafruit examples. 842 | */ 843 | uint16_t WS2812FX::mode_theater_chase_rainbow(void) { 844 | SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF; 845 | uint32_t color = color_wheel(SEGMENT_RUNTIME.counter_mode_step); 846 | return tricolor_chase(color, SEGMENT.colors[1], SEGMENT.colors[1]); 847 | } 848 | 849 | 850 | /* 851 | * Running lights effect with smooth sine transition. 852 | */ 853 | uint16_t WS2812FX::mode_running_lights(void) { 854 | uint8_t w = ((SEGMENT.colors[0] >> 24) & 0xFF); 855 | uint8_t r = ((SEGMENT.colors[0] >> 16) & 0xFF); 856 | uint8_t g = ((SEGMENT.colors[0] >> 8) & 0xFF); 857 | uint8_t b = (SEGMENT.colors[0] & 0xFF); 858 | 859 | uint8_t size = 1 << SIZE_OPTION; 860 | uint8_t sineIncr = max(1, (256 / SEGMENT_LENGTH) * size); 861 | for(uint16_t i=0; i < SEGMENT_LENGTH; i++) { 862 | int lum = (int)sine8(((i + SEGMENT_RUNTIME.counter_mode_step) * sineIncr)); 863 | if(IS_REVERSE) { 864 | setPixelColor(SEGMENT.start + i, (r * lum) / 256, (g * lum) / 256, (b * lum) / 256, (w * lum) / 256); 865 | } else { 866 | setPixelColor(SEGMENT.stop - i, (r * lum) / 256, (g * lum) / 256, (b * lum) / 256, (w * lum) / 256); 867 | } 868 | } 869 | SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) % 256; 870 | return (SEGMENT.speed / SEGMENT_LENGTH); 871 | } 872 | 873 | 874 | /* 875 | * twinkle function 876 | */ 877 | uint16_t WS2812FX::twinkle(uint32_t color1, uint32_t color2) { 878 | if(SEGMENT_RUNTIME.counter_mode_step == 0) { 879 | for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { 880 | setPixelColor(i, color2); 881 | } 882 | uint16_t min_leds = max(1, SEGMENT_LENGTH / 5); // make sure, at least one LED is on 883 | uint16_t max_leds = max(1, SEGMENT_LENGTH / 2); // make sure, at least one LED is on 884 | SEGMENT_RUNTIME.counter_mode_step = random(min_leds, max_leds); 885 | } 886 | 887 | setPixelColor(SEGMENT.start + random16(SEGMENT_LENGTH), color1); 888 | 889 | SEGMENT_RUNTIME.counter_mode_step--; 890 | return (SEGMENT.speed / SEGMENT_LENGTH); 891 | } 892 | 893 | /* 894 | * Blink several LEDs on, reset, repeat. 895 | * Inspired by www.tweaking4all.com/hardware/arduino/arduino-led-strip-effects/ 896 | */ 897 | uint16_t WS2812FX::mode_twinkle(void) { 898 | return twinkle(SEGMENT.colors[0], SEGMENT.colors[1]); 899 | } 900 | 901 | /* 902 | * Blink several LEDs in random colors on, reset, repeat. 903 | * Inspired by www.tweaking4all.com/hardware/arduino/arduino-led-strip-effects/ 904 | */ 905 | uint16_t WS2812FX::mode_twinkle_random(void) { 906 | return twinkle(color_wheel(random8()), SEGMENT.colors[1]); 907 | } 908 | 909 | 910 | /* 911 | * fade out functions 912 | */ 913 | void WS2812FX::fade_out() { 914 | return fade_out(SEGMENT.colors[1]); 915 | } 916 | 917 | void WS2812FX::fade_out(uint32_t targetColor) { 918 | static const uint8_t rateMapH[] = {0, 1, 1, 1, 2, 3, 4, 6}; 919 | static const uint8_t rateMapL[] = {0, 2, 3, 8, 8, 8, 8, 8}; 920 | 921 | uint8_t rate = FADE_RATE; 922 | uint8_t rateH = rateMapH[rate]; 923 | uint8_t rateL = rateMapL[rate]; 924 | 925 | uint32_t color = targetColor; 926 | int w2 = (color >> 24) & 0xff; 927 | int r2 = (color >> 16) & 0xff; 928 | int g2 = (color >> 8) & 0xff; 929 | int b2 = color & 0xff; 930 | 931 | for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { 932 | color = getPixelColor(i); // current color 933 | if(rate == 0) { // old fade-to-black algorithm 934 | setPixelColor(i, (color >> 1) & 0x7F7F7F7F); 935 | } else { // new fade-to-color algorithm 936 | int w1 = (color >> 24) & 0xff; 937 | int r1 = (color >> 16) & 0xff; 938 | int g1 = (color >> 8) & 0xff; 939 | int b1 = color & 0xff; 940 | 941 | // calculate the color differences between the current and target colors 942 | int wdelta = w2 - w1; 943 | int rdelta = r2 - r1; 944 | int gdelta = g2 - g1; 945 | int bdelta = b2 - b1; 946 | 947 | // if the current and target colors are almost the same, jump right to the target 948 | // color, otherwise calculate an intermediate color. (fixes rounding issues) 949 | wdelta = abs(wdelta) < 3 ? wdelta : (wdelta >> rateH) + (wdelta >> rateL); 950 | rdelta = abs(rdelta) < 3 ? rdelta : (rdelta >> rateH) + (rdelta >> rateL); 951 | gdelta = abs(gdelta) < 3 ? gdelta : (gdelta >> rateH) + (gdelta >> rateL); 952 | bdelta = abs(bdelta) < 3 ? bdelta : (bdelta >> rateH) + (bdelta >> rateL); 953 | 954 | setPixelColor(i, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); 955 | } 956 | } 957 | } 958 | 959 | 960 | /* 961 | * color blend function 962 | */ 963 | uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { 964 | if(blend == 0) return color1; 965 | if(blend == 255) return color2; 966 | 967 | uint8_t w1 = (color1 >> 24) & 0xff; 968 | uint8_t r1 = (color1 >> 16) & 0xff; 969 | uint8_t g1 = (color1 >> 8) & 0xff; 970 | uint8_t b1 = color1 & 0xff; 971 | 972 | uint8_t w2 = (color2 >> 24) & 0xff; 973 | uint8_t r2 = (color2 >> 16) & 0xff; 974 | uint8_t g2 = (color2 >> 8) & 0xff; 975 | uint8_t b2 = color2 & 0xff; 976 | 977 | uint32_t w3 = ((w2 * blend) + (w1 * (255U - blend))) / 256U; 978 | uint32_t r3 = ((r2 * blend) + (r1 * (255U - blend))) / 256U; 979 | uint32_t g3 = ((g2 * blend) + (g1 * (255U - blend))) / 256U; 980 | uint32_t b3 = ((b2 * blend) + (b1 * (255U - blend))) / 256U; 981 | 982 | return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3)); 983 | } 984 | 985 | 986 | /* 987 | * twinkle_fade function 988 | */ 989 | uint16_t WS2812FX::twinkle_fade(uint32_t color) { 990 | fade_out(); 991 | 992 | if(random8(3) == 0) { 993 | uint8_t size = 1 << SIZE_OPTION; 994 | uint16_t index = SEGMENT.start + random16(SEGMENT_LENGTH - size); 995 | for(uint8_t i=0; i= (uint16_t)((SEGMENT_LENGTH * 2) - 2)) { 1388 | SEGMENT_RUNTIME.counter_mode_step = 0; 1389 | } 1390 | 1391 | return (SEGMENT.speed / (SEGMENT_LENGTH * 2)); 1392 | } 1393 | 1394 | 1395 | /* 1396 | * Firing comets from one end. 1397 | */ 1398 | uint16_t WS2812FX::mode_comet(void) { 1399 | fade_out(); 1400 | 1401 | if(IS_REVERSE) { 1402 | setPixelColor(SEGMENT.stop - SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); 1403 | } else { 1404 | setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); 1405 | } 1406 | 1407 | SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) % SEGMENT_LENGTH; 1408 | return (SEGMENT.speed / SEGMENT_LENGTH); 1409 | } 1410 | 1411 | 1412 | /* 1413 | * Fireworks function. 1414 | */ 1415 | uint16_t WS2812FX::fireworks(uint32_t color) { 1416 | fade_out(); 1417 | 1418 | // for better performance, manipulate the Adafruit_NeoPixels pixels[] array directly 1419 | uint8_t *pixels = getPixels(); 1420 | uint8_t bytesPerPixel = getNumBytesPerPixel(); // 3=RGB, 4=RGBW 1421 | uint16_t startPixel = SEGMENT.start * bytesPerPixel + bytesPerPixel; 1422 | uint16_t stopPixel = SEGMENT.stop * bytesPerPixel ; 1423 | for(uint16_t i=startPixel; i > 2) + 1425 | pixels[i] + 1426 | (pixels[i + bytesPerPixel] >> 2); 1427 | pixels[i] = tmpPixel > 255 ? 255 : tmpPixel; 1428 | } 1429 | 1430 | uint8_t size = 2 << SIZE_OPTION; 1431 | if(!_triggered) { 1432 | for(uint16_t i=0; i> 24) & 0xFF; 1475 | byte r = (SEGMENT.colors[0] >> 16) & 0xFF; 1476 | byte g = (SEGMENT.colors[0] >> 8) & 0xFF; 1477 | byte b = (SEGMENT.colors[0] & 0xFF); 1478 | byte lum = max(w, max(r, max(g, b))) / rev_intensity; 1479 | for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { 1480 | int flicker = random8(lum); 1481 | setPixelColor(i, max(r - flicker, 0), max(g - flicker, 0), max(b - flicker, 0), max(w - flicker, 0)); 1482 | } 1483 | return (SEGMENT.speed / SEGMENT_LENGTH); 1484 | } 1485 | 1486 | /* 1487 | * Random flickering. 1488 | */ 1489 | uint16_t WS2812FX::mode_fire_flicker(void) { 1490 | return fire_flicker(3); 1491 | } 1492 | 1493 | /* 1494 | * Random flickering, less intensity. 1495 | */ 1496 | uint16_t WS2812FX::mode_fire_flicker_soft(void) { 1497 | return fire_flicker(6); 1498 | } 1499 | 1500 | /* 1501 | * Random flickering, more intensity. 1502 | */ 1503 | uint16_t WS2812FX::mode_fire_flicker_intense(void) { 1504 | return fire_flicker(1.7); 1505 | } 1506 | 1507 | 1508 | /* 1509 | * Tricolor chase function 1510 | */ 1511 | uint16_t WS2812FX::tricolor_chase(uint32_t color1, uint32_t color2, uint32_t color3) { 1512 | uint8_t sizeCnt = 1 << SIZE_OPTION; 1513 | uint16_t index = SEGMENT_RUNTIME.counter_mode_call % (sizeCnt * 3); 1514 | for(uint16_t i=0; i < SEGMENT_LENGTH; i++, index++) { 1515 | index = index % (sizeCnt * 3); 1516 | 1517 | uint32_t color = color3; 1518 | if(index < sizeCnt) color = color1; 1519 | else if(index < (sizeCnt * 2)) color = color2; 1520 | 1521 | if(IS_REVERSE) { 1522 | setPixelColor(SEGMENT.start + i, color); 1523 | } else { 1524 | setPixelColor(SEGMENT.stop - i, color); 1525 | } 1526 | } 1527 | 1528 | return (SEGMENT.speed / SEGMENT_LENGTH); 1529 | } 1530 | 1531 | 1532 | /* 1533 | * Tricolor chase mode 1534 | */ 1535 | uint16_t WS2812FX::mode_tricolor_chase(void) { 1536 | return tricolor_chase(SEGMENT.colors[0], SEGMENT.colors[1], SEGMENT.colors[2]); 1537 | } 1538 | 1539 | 1540 | /* 1541 | * Alternating white/red/black pixels running. 1542 | */ 1543 | uint16_t WS2812FX::mode_circus_combustus(void) { 1544 | return tricolor_chase(RED, WHITE, BLACK); 1545 | } 1546 | 1547 | /* 1548 | * ICU mode 1549 | */ 1550 | uint16_t WS2812FX::mode_icu(void) { 1551 | uint16_t dest = SEGMENT_RUNTIME.counter_mode_step & 0xFFFF; 1552 | 1553 | setPixelColor(SEGMENT.start + dest, SEGMENT.colors[0]); 1554 | setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, SEGMENT.colors[0]); 1555 | 1556 | if(SEGMENT_RUNTIME.aux_param3 == dest) { // pause between eye movements 1557 | if(random8(6) == 0) { // blink once in a while 1558 | setPixelColor(SEGMENT.start + dest, BLACK); 1559 | setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, BLACK); 1560 | return 200; 1561 | } 1562 | SEGMENT_RUNTIME.aux_param3 = random16(SEGMENT_LENGTH/2); 1563 | return 1000 + random16(2000); 1564 | } 1565 | 1566 | setPixelColor(SEGMENT.start + dest, BLACK); 1567 | setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, BLACK); 1568 | 1569 | if(SEGMENT_RUNTIME.aux_param3 > SEGMENT_RUNTIME.counter_mode_step) { 1570 | SEGMENT_RUNTIME.counter_mode_step++; 1571 | dest++; 1572 | } else if (SEGMENT_RUNTIME.aux_param3 < SEGMENT_RUNTIME.counter_mode_step) { 1573 | SEGMENT_RUNTIME.counter_mode_step--; 1574 | dest--; 1575 | } 1576 | 1577 | setPixelColor(SEGMENT.start + dest, SEGMENT.colors[0]); 1578 | setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, SEGMENT.colors[0]); 1579 | 1580 | return (SEGMENT.speed / SEGMENT_LENGTH); 1581 | } 1582 | 1583 | /* 1584 | * Custom modes 1585 | */ 1586 | uint16_t WS2812FX::mode_custom_0() { 1587 | return customModes[0](); 1588 | } 1589 | uint16_t WS2812FX::mode_custom_1() { 1590 | return customModes[1](); 1591 | } 1592 | uint16_t WS2812FX::mode_custom_2() { 1593 | return customModes[2](); 1594 | } 1595 | uint16_t WS2812FX::mode_custom_3() { 1596 | return customModes[3](); 1597 | } 1598 | 1599 | /* 1600 | * Custom mode helpers 1601 | */ 1602 | void WS2812FX::setCustomMode(uint16_t (*p)()) { 1603 | customModes[0] = p; 1604 | } 1605 | 1606 | uint8_t WS2812FX::setCustomMode(const __FlashStringHelper* name, uint16_t (*p)()) { 1607 | static uint8_t custom_mode_index = 0; 1608 | return setCustomMode(custom_mode_index++, name, p); 1609 | } 1610 | 1611 | uint8_t WS2812FX::setCustomMode(uint8_t index, const __FlashStringHelper* name, uint16_t (*p)()) { 1612 | if((uint8_t)(FX_MODE_CUSTOM_0 + index) < MODE_COUNT) { 1613 | _names[FX_MODE_CUSTOM_0 + index] = name; // store the custom mode name 1614 | customModes[index] = p; // store the custom mode 1615 | 1616 | return (FX_MODE_CUSTOM_0 + index); 1617 | } 1618 | return 0; 1619 | } 1620 | 1621 | /* 1622 | * Custom show helper 1623 | */ 1624 | void WS2812FX::setCustomShow(void (*p)()) { 1625 | customShow = p; 1626 | } 1627 | -------------------------------------------------------------------------------- /ws2812fx/WS2812FX.h: -------------------------------------------------------------------------------- 1 | /* 2 | WS2812FX.h - Library for WS2812 LED effects. 3 | 4 | Harm Aldick - 2016 5 | www.aldick.org 6 | FEATURES 7 | * A lot of blinken modes and counting 8 | * WS2812FX can be used as drop-in replacement for Adafruit NeoPixel Library 9 | NOTES 10 | * Uses the Adafruit NeoPixel library. Get it here: 11 | https://github.com/adafruit/Adafruit_NeoPixel 12 | LICENSE 13 | The MIT License (MIT) 14 | Copyright (c) 2016 Harm Aldick 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | THE SOFTWARE. 30 | CHANGELOG 31 | 2016-05-28 Initial beta release 32 | 2016-06-03 Code cleanup, minor improvements, new modes 33 | 2016-06-04 2 new fx, fixed setColor (now also resets _mode_color) 34 | 2017-02-02 added external trigger functionality (e.g. for sound-to-light) 35 | */ 36 | 37 | #ifndef WS2812FX_h 38 | #define WS2812FX_h 39 | 40 | #define FSH(x) (__FlashStringHelper*)(x) 41 | 42 | #include 43 | 44 | #define DEFAULT_BRIGHTNESS (uint8_t)50 45 | #define DEFAULT_MODE (uint8_t)0 46 | #define DEFAULT_SPEED (uint16_t)1000 47 | #define DEFAULT_COLOR (uint32_t)0xFF0000 48 | 49 | #if defined(ESP8266) || defined(ESP32) 50 | //#pragma message("Compiling for ESP") 51 | #define SPEED_MIN (uint16_t)2 52 | #else 53 | //#pragma message("Compiling for Arduino") 54 | #define SPEED_MIN (uint16_t)10 55 | #endif 56 | #define SPEED_MAX (uint16_t)65535 57 | 58 | #define BRIGHTNESS_MIN (uint8_t)0 59 | #define BRIGHTNESS_MAX (uint8_t)255 60 | 61 | /* each segment uses 36 bytes of SRAM memory, so if you're application fails because of 62 | insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ 63 | #define MAX_NUM_SEGMENTS 10 64 | #define NUM_COLORS 3 /* number of colors per segment */ 65 | #define MAX_CUSTOM_MODES 4 66 | #define SEGMENT _segments[_segment_index] 67 | #define SEGMENT_RUNTIME _segment_runtimes[_segment_index] 68 | #define SEGMENT_LENGTH (uint16_t)(SEGMENT.stop - SEGMENT.start + 1) 69 | 70 | // some common colors 71 | #define RED (uint32_t)0xFF0000 72 | #define GREEN (uint32_t)0x00FF00 73 | #define BLUE (uint32_t)0x0000FF 74 | #define WHITE (uint32_t)0xFFFFFF 75 | #define BLACK (uint32_t)0x000000 76 | #define YELLOW (uint32_t)0xFFFF00 77 | #define CYAN (uint32_t)0x00FFFF 78 | #define MAGENTA (uint32_t)0xFF00FF 79 | #define PURPLE (uint32_t)0x400080 80 | #define ORANGE (uint32_t)0xFF3000 81 | #define PINK (uint32_t)0xFF1493 82 | #define ULTRAWHITE (uint32_t)0xFFFFFFFF 83 | #define DARK(c) (uint32_t)((c >> 4) & 0x0f0f0f0f) 84 | 85 | // segment options 86 | // bit 7: reverse animation 87 | // bits 4-6: fade rate (0-7) 88 | // bit 3: gamma correction 89 | // bits 1-2: size 90 | // bits 0: TBD 91 | #define NO_OPTIONS (uint8_t)B00000000 92 | #define REVERSE (uint8_t)B10000000 93 | #define IS_REVERSE ((SEGMENT.options & REVERSE) == REVERSE) 94 | #define FADE_XFAST (uint8_t)B00010000 95 | #define FADE_FAST (uint8_t)B00100000 96 | #define FADE_MEDIUM (uint8_t)B00110000 97 | #define FADE_SLOW (uint8_t)B01000000 98 | #define FADE_XSLOW (uint8_t)B01010000 99 | #define FADE_XXSLOW (uint8_t)B01100000 100 | #define FADE_GLACIAL (uint8_t)B01110000 101 | #define FADE_RATE ((SEGMENT.options >> 4) & 7) 102 | #define GAMMA (uint8_t)B00001000 103 | #define IS_GAMMA ((SEGMENT.options & GAMMA) == GAMMA) 104 | #define SIZE_SMALL (uint8_t)B00000000 105 | #define SIZE_MEDIUM (uint8_t)B00000010 106 | #define SIZE_LARGE (uint8_t)B00000100 107 | #define SIZE_XLARGE (uint8_t)B00000110 108 | #define SIZE_OPTION ((SEGMENT.options >> 1) & 3) 109 | 110 | // segment runtime options (aux_param2) 111 | #define FRAME (uint8_t)B10000000 112 | #define SET_FRAME (SEGMENT_RUNTIME.aux_param2 |= FRAME) 113 | #define CLR_FRAME (SEGMENT_RUNTIME.aux_param2 &= ~FRAME) 114 | #define CYCLE (uint8_t)B01000000 115 | #define SET_CYCLE (SEGMENT_RUNTIME.aux_param2 |= CYCLE) 116 | #define CLR_CYCLE (SEGMENT_RUNTIME.aux_param2 &= ~CYCLE) 117 | 118 | #define MODE_COUNT (sizeof(_names)/sizeof(_names[0])) 119 | 120 | #define FX_MODE_STATIC 0 121 | #define FX_MODE_BLINK 1 122 | #define FX_MODE_BREATH 2 123 | #define FX_MODE_COLOR_WIPE 3 124 | #define FX_MODE_COLOR_WIPE_INV 4 125 | #define FX_MODE_COLOR_WIPE_REV 5 126 | #define FX_MODE_COLOR_WIPE_REV_INV 6 127 | #define FX_MODE_COLOR_WIPE_RANDOM 7 128 | #define FX_MODE_RANDOM_COLOR 8 129 | #define FX_MODE_SINGLE_DYNAMIC 9 130 | #define FX_MODE_MULTI_DYNAMIC 10 131 | #define FX_MODE_RAINBOW 11 132 | #define FX_MODE_RAINBOW_CYCLE 12 133 | #define FX_MODE_SCAN 13 134 | #define FX_MODE_DUAL_SCAN 14 135 | #define FX_MODE_FADE 15 136 | #define FX_MODE_THEATER_CHASE 16 137 | #define FX_MODE_THEATER_CHASE_RAINBOW 17 138 | #define FX_MODE_RUNNING_LIGHTS 18 139 | #define FX_MODE_TWINKLE 19 140 | #define FX_MODE_TWINKLE_RANDOM 20 141 | #define FX_MODE_TWINKLE_FADE 21 142 | #define FX_MODE_TWINKLE_FADE_RANDOM 22 143 | #define FX_MODE_SPARKLE 23 144 | #define FX_MODE_FLASH_SPARKLE 24 145 | #define FX_MODE_HYPER_SPARKLE 25 146 | #define FX_MODE_STROBE 26 147 | #define FX_MODE_STROBE_RAINBOW 27 148 | #define FX_MODE_MULTI_STROBE 28 149 | #define FX_MODE_BLINK_RAINBOW 29 150 | #define FX_MODE_CHASE_WHITE 30 151 | #define FX_MODE_CHASE_COLOR 31 152 | #define FX_MODE_CHASE_RANDOM 32 153 | #define FX_MODE_CHASE_RAINBOW 33 154 | #define FX_MODE_CHASE_FLASH 34 155 | #define FX_MODE_CHASE_FLASH_RANDOM 35 156 | #define FX_MODE_CHASE_RAINBOW_WHITE 36 157 | #define FX_MODE_CHASE_BLACKOUT 37 158 | #define FX_MODE_CHASE_BLACKOUT_RAINBOW 38 159 | #define FX_MODE_COLOR_SWEEP_RANDOM 39 160 | #define FX_MODE_RUNNING_COLOR 40 161 | #define FX_MODE_RUNNING_RED_BLUE 41 162 | #define FX_MODE_RUNNING_RANDOM 42 163 | #define FX_MODE_LARSON_SCANNER 43 164 | #define FX_MODE_COMET 44 165 | #define FX_MODE_FIREWORKS 45 166 | #define FX_MODE_FIREWORKS_RANDOM 46 167 | #define FX_MODE_MERRY_CHRISTMAS 47 168 | #define FX_MODE_FIRE_FLICKER 48 169 | #define FX_MODE_FIRE_FLICKER_SOFT 49 170 | #define FX_MODE_FIRE_FLICKER_INTENSE 50 171 | #define FX_MODE_CIRCUS_COMBUSTUS 51 172 | #define FX_MODE_HALLOWEEN 52 173 | #define FX_MODE_BICOLOR_CHASE 53 174 | #define FX_MODE_TRICOLOR_CHASE 54 175 | #define FX_MODE_ICU 55 176 | #define FX_MODE_CUSTOM 56 // keep this for backward compatiblity 177 | #define FX_MODE_CUSTOM_0 56 // custom modes need to go at the end 178 | #define FX_MODE_CUSTOM_1 57 179 | #define FX_MODE_CUSTOM_2 58 180 | #define FX_MODE_CUSTOM_3 59 181 | 182 | // create GLOBAL names to allow WS2812FX to compile with sketches and other libs that store strings 183 | // in PROGMEM (get rid of the "section type conflict with __c" errors once and for all. Amen.) 184 | const char name_0[] PROGMEM = "Static"; 185 | const char name_1[] PROGMEM = "Blink"; 186 | const char name_2[] PROGMEM = "Breath"; 187 | const char name_3[] PROGMEM = "Color Wipe"; 188 | const char name_4[] PROGMEM = "Color Wipe Inverse"; 189 | const char name_5[] PROGMEM = "Color Wipe Reverse"; 190 | const char name_6[] PROGMEM = "Color Wipe Reverse Inverse"; 191 | const char name_7[] PROGMEM = "Color Wipe Random"; 192 | const char name_8[] PROGMEM = "Random Color"; 193 | const char name_9[] PROGMEM = "Single Dynamic"; 194 | const char name_10[] PROGMEM = "Multi Dynamic"; 195 | const char name_11[] PROGMEM = "Rainbow"; 196 | const char name_12[] PROGMEM = "Rainbow Cycle"; 197 | const char name_13[] PROGMEM = "Scan"; 198 | const char name_14[] PROGMEM = "Dual Scan"; 199 | const char name_15[] PROGMEM = "Fade"; 200 | const char name_16[] PROGMEM = "Theater Chase"; 201 | const char name_17[] PROGMEM = "Theater Chase Rainbow"; 202 | const char name_18[] PROGMEM = "Running Lights"; 203 | const char name_19[] PROGMEM = "Twinkle"; 204 | const char name_20[] PROGMEM = "Twinkle Random"; 205 | const char name_21[] PROGMEM = "Twinkle Fade"; 206 | const char name_22[] PROGMEM = "Twinkle Fade Random"; 207 | const char name_23[] PROGMEM = "Sparkle"; 208 | const char name_24[] PROGMEM = "Flash Sparkle"; 209 | const char name_25[] PROGMEM = "Hyper Sparkle"; 210 | const char name_26[] PROGMEM = "Strobe"; 211 | const char name_27[] PROGMEM = "Strobe Rainbow"; 212 | const char name_28[] PROGMEM = "Multi Strobe"; 213 | const char name_29[] PROGMEM = "Blink Rainbow"; 214 | const char name_30[] PROGMEM = "Chase White"; 215 | const char name_31[] PROGMEM = "Chase Color"; 216 | const char name_32[] PROGMEM = "Chase Random"; 217 | const char name_33[] PROGMEM = "Chase Rainbow"; 218 | const char name_34[] PROGMEM = "Chase Flash"; 219 | const char name_35[] PROGMEM = "Chase Flash Random"; 220 | const char name_36[] PROGMEM = "Chase Rainbow White"; 221 | const char name_37[] PROGMEM = "Chase Blackout"; 222 | const char name_38[] PROGMEM = "Chase Blackout Rainbow"; 223 | const char name_39[] PROGMEM = "Color Sweep Random"; 224 | const char name_40[] PROGMEM = "Running Color"; 225 | const char name_41[] PROGMEM = "Running Red Blue"; 226 | const char name_42[] PROGMEM = "Running Random"; 227 | const char name_43[] PROGMEM = "Larson Scanner"; 228 | const char name_44[] PROGMEM = "Comet"; 229 | const char name_45[] PROGMEM = "Fireworks"; 230 | const char name_46[] PROGMEM = "Fireworks Random"; 231 | const char name_47[] PROGMEM = "Merry Christmas"; 232 | const char name_48[] PROGMEM = "Fire Flicker"; 233 | const char name_49[] PROGMEM = "Fire Flicker (soft)"; 234 | const char name_50[] PROGMEM = "Fire Flicker (intense)"; 235 | const char name_51[] PROGMEM = "Circus Combustus"; 236 | const char name_52[] PROGMEM = "Halloween"; 237 | const char name_53[] PROGMEM = "Bicolor Chase"; 238 | const char name_54[] PROGMEM = "Tricolor Chase"; 239 | const char name_55[] PROGMEM = "ICU"; 240 | const char name_56[] PROGMEM = "Custom 0"; // custom modes need to go at the end 241 | const char name_57[] PROGMEM = "Custom 1"; 242 | const char name_58[] PROGMEM = "Custom 2"; 243 | const char name_59[] PROGMEM = "Custom 3"; 244 | 245 | static const __FlashStringHelper* _names[] = { 246 | FSH(name_0), 247 | FSH(name_1), 248 | FSH(name_2), 249 | FSH(name_3), 250 | FSH(name_4), 251 | FSH(name_5), 252 | FSH(name_6), 253 | FSH(name_7), 254 | FSH(name_8), 255 | FSH(name_9), 256 | FSH(name_10), 257 | FSH(name_11), 258 | FSH(name_12), 259 | FSH(name_13), 260 | FSH(name_14), 261 | FSH(name_15), 262 | FSH(name_16), 263 | FSH(name_17), 264 | FSH(name_18), 265 | FSH(name_19), 266 | FSH(name_20), 267 | FSH(name_21), 268 | FSH(name_22), 269 | FSH(name_23), 270 | FSH(name_24), 271 | FSH(name_25), 272 | FSH(name_26), 273 | FSH(name_27), 274 | FSH(name_28), 275 | FSH(name_29), 276 | FSH(name_30), 277 | FSH(name_31), 278 | FSH(name_32), 279 | FSH(name_33), 280 | FSH(name_34), 281 | FSH(name_35), 282 | FSH(name_36), 283 | FSH(name_37), 284 | FSH(name_38), 285 | FSH(name_39), 286 | FSH(name_40), 287 | FSH(name_41), 288 | FSH(name_42), 289 | FSH(name_43), 290 | FSH(name_44), 291 | FSH(name_45), 292 | FSH(name_46), 293 | FSH(name_47), 294 | FSH(name_48), 295 | FSH(name_49), 296 | FSH(name_50), 297 | FSH(name_51), 298 | FSH(name_52), 299 | FSH(name_53), 300 | FSH(name_54), 301 | FSH(name_55), 302 | FSH(name_56), 303 | FSH(name_57), 304 | FSH(name_58), 305 | FSH(name_59) 306 | }; 307 | 308 | class WS2812FX : public Adafruit_NeoPixel { 309 | 310 | typedef uint16_t (WS2812FX::*mode_ptr)(void); 311 | 312 | // segment parameters 313 | public: 314 | typedef struct Segment { // 20 bytes 315 | uint16_t start; 316 | uint16_t stop; 317 | uint16_t speed; 318 | uint8_t mode; 319 | uint8_t options; 320 | uint32_t colors[NUM_COLORS]; 321 | } segment; 322 | 323 | // segment runtime parameters 324 | typedef struct Segment_runtime { // 16 bytes 325 | unsigned long next_time; 326 | uint32_t counter_mode_step; 327 | uint32_t counter_mode_call; 328 | uint8_t aux_param; // auxilary param (usually stores a color_wheel index) 329 | uint8_t aux_param2; // auxilary param (usually stores bitwise options) 330 | uint16_t aux_param3; // auxilary param (usually stores a segment index) 331 | } segment_runtime; 332 | 333 | WS2812FX(uint16_t n, uint8_t p, neoPixelType t) : Adafruit_NeoPixel(n, p, t) { 334 | _mode[FX_MODE_STATIC] = &WS2812FX::mode_static; 335 | _mode[FX_MODE_BLINK] = &WS2812FX::mode_blink; 336 | _mode[FX_MODE_COLOR_WIPE] = &WS2812FX::mode_color_wipe; 337 | _mode[FX_MODE_COLOR_WIPE_INV] = &WS2812FX::mode_color_wipe_inv; 338 | _mode[FX_MODE_COLOR_WIPE_REV] = &WS2812FX::mode_color_wipe_rev; 339 | _mode[FX_MODE_COLOR_WIPE_REV_INV] = &WS2812FX::mode_color_wipe_rev_inv; 340 | _mode[FX_MODE_COLOR_WIPE_RANDOM] = &WS2812FX::mode_color_wipe_random; 341 | _mode[FX_MODE_RANDOM_COLOR] = &WS2812FX::mode_random_color; 342 | _mode[FX_MODE_SINGLE_DYNAMIC] = &WS2812FX::mode_single_dynamic; 343 | _mode[FX_MODE_MULTI_DYNAMIC] = &WS2812FX::mode_multi_dynamic; 344 | _mode[FX_MODE_RAINBOW] = &WS2812FX::mode_rainbow; 345 | _mode[FX_MODE_RAINBOW_CYCLE] = &WS2812FX::mode_rainbow_cycle; 346 | _mode[FX_MODE_SCAN] = &WS2812FX::mode_scan; 347 | _mode[FX_MODE_DUAL_SCAN] = &WS2812FX::mode_dual_scan; 348 | _mode[FX_MODE_FADE] = &WS2812FX::mode_fade; 349 | _mode[FX_MODE_THEATER_CHASE] = &WS2812FX::mode_theater_chase; 350 | _mode[FX_MODE_THEATER_CHASE_RAINBOW] = &WS2812FX::mode_theater_chase_rainbow; 351 | _mode[FX_MODE_TWINKLE] = &WS2812FX::mode_twinkle; 352 | _mode[FX_MODE_TWINKLE_RANDOM] = &WS2812FX::mode_twinkle_random; 353 | _mode[FX_MODE_TWINKLE_FADE] = &WS2812FX::mode_twinkle_fade; 354 | _mode[FX_MODE_TWINKLE_FADE_RANDOM] = &WS2812FX::mode_twinkle_fade_random; 355 | _mode[FX_MODE_SPARKLE] = &WS2812FX::mode_sparkle; 356 | _mode[FX_MODE_FLASH_SPARKLE] = &WS2812FX::mode_flash_sparkle; 357 | _mode[FX_MODE_HYPER_SPARKLE] = &WS2812FX::mode_hyper_sparkle; 358 | _mode[FX_MODE_STROBE] = &WS2812FX::mode_strobe; 359 | _mode[FX_MODE_STROBE_RAINBOW] = &WS2812FX::mode_strobe_rainbow; 360 | _mode[FX_MODE_MULTI_STROBE] = &WS2812FX::mode_multi_strobe; 361 | _mode[FX_MODE_BLINK_RAINBOW] = &WS2812FX::mode_blink_rainbow; 362 | _mode[FX_MODE_CHASE_WHITE] = &WS2812FX::mode_chase_white; 363 | _mode[FX_MODE_CHASE_COLOR] = &WS2812FX::mode_chase_color; 364 | _mode[FX_MODE_CHASE_RANDOM] = &WS2812FX::mode_chase_random; 365 | _mode[FX_MODE_CHASE_RAINBOW] = &WS2812FX::mode_chase_rainbow; 366 | _mode[FX_MODE_CHASE_FLASH] = &WS2812FX::mode_chase_flash; 367 | _mode[FX_MODE_CHASE_FLASH_RANDOM] = &WS2812FX::mode_chase_flash_random; 368 | _mode[FX_MODE_CHASE_RAINBOW_WHITE] = &WS2812FX::mode_chase_rainbow_white; 369 | _mode[FX_MODE_CHASE_BLACKOUT] = &WS2812FX::mode_chase_blackout; 370 | _mode[FX_MODE_CHASE_BLACKOUT_RAINBOW] = &WS2812FX::mode_chase_blackout_rainbow; 371 | _mode[FX_MODE_COLOR_SWEEP_RANDOM] = &WS2812FX::mode_color_sweep_random; 372 | _mode[FX_MODE_RUNNING_COLOR] = &WS2812FX::mode_running_color; 373 | _mode[FX_MODE_RUNNING_RED_BLUE] = &WS2812FX::mode_running_red_blue; 374 | _mode[FX_MODE_RUNNING_RANDOM] = &WS2812FX::mode_running_random; 375 | _mode[FX_MODE_LARSON_SCANNER] = &WS2812FX::mode_larson_scanner; 376 | _mode[FX_MODE_COMET] = &WS2812FX::mode_comet; 377 | _mode[FX_MODE_FIREWORKS] = &WS2812FX::mode_fireworks; 378 | _mode[FX_MODE_FIREWORKS_RANDOM] = &WS2812FX::mode_fireworks_random; 379 | _mode[FX_MODE_MERRY_CHRISTMAS] = &WS2812FX::mode_merry_christmas; 380 | _mode[FX_MODE_FIRE_FLICKER] = &WS2812FX::mode_fire_flicker; 381 | _mode[FX_MODE_FIRE_FLICKER_SOFT] = &WS2812FX::mode_fire_flicker_soft; 382 | _mode[FX_MODE_FIRE_FLICKER_INTENSE] = &WS2812FX::mode_fire_flicker_intense; 383 | _mode[FX_MODE_CIRCUS_COMBUSTUS] = &WS2812FX::mode_circus_combustus; 384 | _mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween; 385 | _mode[FX_MODE_BICOLOR_CHASE] = &WS2812FX::mode_bicolor_chase; 386 | _mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase; 387 | // if flash memory is constrained (I'm looking at you Arduino Nano), replace modes 388 | // that use a lot of flash with mode_static (reduces flash footprint by about 2100 bytes) 389 | #ifdef REDUCED_MODES 390 | _mode[FX_MODE_BREATH] = &WS2812FX::mode_static; 391 | _mode[FX_MODE_RUNNING_LIGHTS] = &WS2812FX::mode_static; 392 | _mode[FX_MODE_ICU] = &WS2812FX::mode_static; 393 | #else 394 | _mode[FX_MODE_BREATH] = &WS2812FX::mode_breath; 395 | _mode[FX_MODE_RUNNING_LIGHTS] = &WS2812FX::mode_running_lights; 396 | _mode[FX_MODE_ICU] = &WS2812FX::mode_icu; 397 | #endif 398 | _mode[FX_MODE_CUSTOM_0] = &WS2812FX::mode_custom_0; 399 | _mode[FX_MODE_CUSTOM_1] = &WS2812FX::mode_custom_1; 400 | _mode[FX_MODE_CUSTOM_2] = &WS2812FX::mode_custom_2; 401 | _mode[FX_MODE_CUSTOM_3] = &WS2812FX::mode_custom_3; 402 | 403 | brightness = DEFAULT_BRIGHTNESS + 1; // Adafruit_NeoPixel internally offsets brightness by 1 404 | _running = false; 405 | _num_segments = 1; 406 | _segments[0].mode = DEFAULT_MODE; 407 | _segments[0].colors[0] = DEFAULT_COLOR; 408 | _segments[0].start = 0; 409 | _segments[0].stop = n - 1; 410 | _segments[0].speed = DEFAULT_SPEED; 411 | resetSegmentRuntimes(); 412 | } 413 | 414 | void 415 | // timer(void), 416 | init(void), 417 | service(void), 418 | start(void), 419 | stop(void), 420 | pause(void), 421 | resume(void), 422 | strip_off(void), 423 | fade_out(void), 424 | fade_out(uint32_t), 425 | setMode(uint8_t m), 426 | setMode(uint8_t seg, uint8_t m), 427 | setOptions(uint8_t seg, uint8_t o), 428 | setCustomMode(uint16_t (*p)()), 429 | setCustomShow(void (*p)()), 430 | setSpeed(uint16_t s), 431 | setSpeed(uint8_t seg, uint16_t s), 432 | increaseSpeed(uint8_t s), 433 | decreaseSpeed(uint8_t s), 434 | setColor(uint8_t r, uint8_t g, uint8_t b), 435 | setColor(uint8_t r, uint8_t g, uint8_t b, uint8_t w), 436 | setColor(uint32_t c), 437 | setColor(uint8_t seg, uint32_t c), 438 | setColors(uint8_t seg, uint32_t* c), 439 | setBrightness(uint8_t b), 440 | increaseBrightness(uint8_t s), 441 | decreaseBrightness(uint8_t s), 442 | setLength(uint16_t b), 443 | increaseLength(uint16_t s), 444 | decreaseLength(uint16_t s), 445 | trigger(void), 446 | setNumSegments(uint8_t n), 447 | setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint16_t speed, bool reverse), 448 | setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint16_t speed, uint8_t options), 449 | setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint16_t speed, bool reverse), 450 | setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint16_t speed, uint8_t options), 451 | resetSegments(), 452 | resetSegmentRuntimes(), 453 | resetSegmentRuntime(uint8_t), 454 | setPixelColor(uint16_t n, uint32_t c), 455 | setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b), 456 | setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w), 457 | copyPixels(uint16_t d, uint16_t s, uint16_t c), 458 | show(void); 459 | 460 | boolean 461 | isRunning(void), 462 | isTriggered(void), 463 | isFrame(void), 464 | isFrame(uint8_t), 465 | isCycle(void), 466 | isCycle(uint8_t); 467 | 468 | uint8_t 469 | random8(void), 470 | random8(uint8_t), 471 | getMode(void), 472 | getMode(uint8_t), 473 | getModeCount(void), 474 | setCustomMode(const __FlashStringHelper* name, uint16_t (*p)()), 475 | setCustomMode(uint8_t i, const __FlashStringHelper* name, uint16_t (*p)()), 476 | getNumSegments(void), 477 | get_random_wheel_index(uint8_t), 478 | getOptions(uint8_t), 479 | getNumBytesPerPixel(void); 480 | 481 | uint16_t 482 | random16(void), 483 | random16(uint16_t), 484 | getSpeed(void), 485 | getSpeed(uint8_t), 486 | getLength(void), 487 | getNumBytes(void); 488 | 489 | uint32_t 490 | color_wheel(uint8_t), 491 | getColor(void), 492 | getColor(uint8_t), 493 | intensitySum(void); 494 | 495 | uint32_t* getColors(uint8_t); 496 | uint32_t* intensitySums(void); 497 | 498 | const __FlashStringHelper* getModeName(uint8_t m); 499 | 500 | WS2812FX::Segment* getSegment(void); 501 | 502 | WS2812FX::Segment* getSegment(uint8_t); 503 | 504 | WS2812FX::Segment* getSegments(void); 505 | 506 | WS2812FX::Segment_runtime* getSegmentRuntime(void); 507 | 508 | WS2812FX::Segment_runtime* getSegmentRuntime(uint8_t); 509 | 510 | WS2812FX::Segment_runtime* getSegmentRuntimes(void); 511 | 512 | // mode helper functions 513 | uint16_t 514 | blink(uint32_t, uint32_t, bool strobe), 515 | color_wipe(uint32_t, uint32_t, bool), 516 | twinkle(uint32_t, uint32_t), 517 | twinkle_fade(uint32_t), 518 | chase(uint32_t, uint32_t, uint32_t), 519 | running(uint32_t, uint32_t), 520 | fireworks(uint32_t), 521 | fire_flicker(int), 522 | tricolor_chase(uint32_t, uint32_t, uint32_t), 523 | scan(uint32_t, uint32_t, bool); 524 | uint32_t 525 | color_blend(uint32_t, uint32_t, uint8_t); 526 | 527 | // builtin modes 528 | uint16_t 529 | mode_static(void), 530 | mode_blink(void), 531 | mode_blink_rainbow(void), 532 | mode_strobe(void), 533 | mode_strobe_rainbow(void), 534 | mode_color_wipe(void), 535 | mode_color_wipe_inv(void), 536 | mode_color_wipe_rev(void), 537 | mode_color_wipe_rev_inv(void), 538 | mode_color_wipe_random(void), 539 | mode_color_sweep_random(void), 540 | mode_random_color(void), 541 | mode_single_dynamic(void), 542 | mode_multi_dynamic(void), 543 | mode_breath(void), 544 | mode_fade(void), 545 | mode_scan(void), 546 | mode_dual_scan(void), 547 | mode_theater_chase(void), 548 | mode_theater_chase_rainbow(void), 549 | mode_rainbow(void), 550 | mode_rainbow_cycle(void), 551 | mode_running_lights(void), 552 | mode_twinkle(void), 553 | mode_twinkle_random(void), 554 | mode_twinkle_fade(void), 555 | mode_twinkle_fade_random(void), 556 | mode_sparkle(void), 557 | mode_flash_sparkle(void), 558 | mode_hyper_sparkle(void), 559 | mode_multi_strobe(void), 560 | mode_chase_white(void), 561 | mode_chase_color(void), 562 | mode_chase_random(void), 563 | mode_chase_rainbow(void), 564 | mode_chase_flash(void), 565 | mode_chase_flash_random(void), 566 | mode_chase_rainbow_white(void), 567 | mode_chase_blackout(void), 568 | mode_chase_blackout_rainbow(void), 569 | mode_running_color(void), 570 | mode_running_red_blue(void), 571 | mode_running_random(void), 572 | mode_larson_scanner(void), 573 | mode_comet(void), 574 | mode_fireworks(void), 575 | mode_fireworks_random(void), 576 | mode_merry_christmas(void), 577 | mode_halloween(void), 578 | mode_fire_flicker(void), 579 | mode_fire_flicker_soft(void), 580 | mode_fire_flicker_intense(void), 581 | mode_circus_combustus(void), 582 | mode_bicolor_chase(void), 583 | mode_tricolor_chase(void), 584 | mode_icu(void), 585 | mode_custom_0(void), 586 | mode_custom_1(void), 587 | mode_custom_2(void), 588 | mode_custom_3(void); 589 | 590 | private: 591 | uint16_t _rand16seed; 592 | uint16_t (*customModes[MAX_CUSTOM_MODES])(void) { 593 | []{ return (uint16_t)1000; }, 594 | []{ return (uint16_t)1000; }, 595 | []{ return (uint16_t)1000; }, 596 | []{ return (uint16_t)1000; } 597 | }; 598 | void (*customShow)(void) = NULL; 599 | 600 | boolean 601 | _running, 602 | _triggered; 603 | 604 | mode_ptr _mode[MODE_COUNT]; // SRAM footprint: 4 bytes per element 605 | 606 | uint8_t _segment_index = 0; 607 | uint8_t _num_segments = 1; 608 | segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 20 bytes per element 609 | // start, stop, speed, mode, options, color[] 610 | { 0, 7, DEFAULT_SPEED, FX_MODE_STATIC, NO_OPTIONS, {DEFAULT_COLOR}} 611 | }; 612 | segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 16 bytes per element 613 | }; 614 | 615 | #endif 616 | -------------------------------------------------------------------------------- /ws2812fx/readme.md: -------------------------------------------------------------------------------- 1 | Slightly modified WS2812FX.cpp library file with rainbow cycle speeds tweaked. 2 | 3 | Your Bindicator will work fine with the standard distribution and I'd encourage you to keep using that. 4 | --------------------------------------------------------------------------------