├── Clock.h ├── Commands.h ├── FSBrowser.h ├── Field.h ├── Fields.h ├── GradientPalettes.h ├── LICENSE ├── Pacifica.h ├── Ping.h ├── README.md ├── TwinkleFOX.h ├── Twinkles.h ├── build.sh ├── data ├── css │ ├── bootstrap.min.css │ ├── jquery.minicolors.min.css │ ├── simple.css │ └── styles.css ├── edit.htm ├── favicon.ico ├── fonts │ ├── glyphicons.eot │ ├── glyphicons.svg │ ├── glyphicons.ttf │ ├── glyphicons.woff │ └── glyphicons.woff2 ├── images │ └── atom196.png ├── index.htm ├── js │ ├── app.js │ ├── bootstrap.min.js │ ├── jquery-3.1.1.min.js │ ├── jquery.minicolors.min.js │ ├── r-websocket.min.js │ └── simple.js └── simple.htm ├── deployapp.sh ├── deployfirmware.sh ├── eclipse-v2.ino ├── power.sh └── uploadfile.sh /Clock.h: -------------------------------------------------------------------------------- 1 | uint8_t flipClock = 0; 2 | int timeZone = -6; 3 | 4 | unsigned long lastTimeSync = millis(); 5 | 6 | int oldSecTime = 0; 7 | int oldSec = 0; 8 | 9 | IPAddress timeServerIP; // time.nist.gov NTP server address 10 | const char* ntpServerName = "time.nist.gov"; 11 | 12 | const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message 13 | 14 | byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets 15 | 16 | // send an NTP request to the time server at the given address 17 | void sendNTPpacket() 18 | { 19 | //get a random server from the pool 20 | WiFi.hostByName(ntpServerName, timeServerIP); 21 | 22 | // set all bytes in the buffer to 0 23 | memset(packetBuffer, 0, NTP_PACKET_SIZE); 24 | // Initialize values needed to form NTP request 25 | // (see URL above for details on the packets) 26 | packetBuffer[0] = 0b11100011; // LI, Version, Mode 27 | packetBuffer[1] = 0; // Stratum, or type of clock 28 | packetBuffer[2] = 6; // Polling Interval 29 | packetBuffer[3] = 0xEC; // Peer Clock Precision 30 | // 8 bytes of zero for Root Delay & Root Dispersion 31 | packetBuffer[12] = 49; 32 | packetBuffer[13] = 0x4E; 33 | packetBuffer[14] = 49; 34 | packetBuffer[15] = 52; 35 | // all NTP fields have been given values, now 36 | // you can send a packet requesting a timestamp: 37 | udp.beginPacket(timeServerIP, 123); //NTP requests are to port 123 38 | udp.write(packetBuffer, NTP_PACKET_SIZE); 39 | udp.endPacket(); 40 | } 41 | 42 | time_t getNtpTime() 43 | { 44 | while (udp.parsePacket() > 0) ; // discard any previously received packets 45 | Serial.println("Transmit NTP Request"); 46 | sendNTPpacket(); 47 | uint32_t beginWait = millis(); 48 | while (millis() - beginWait < 1500) { 49 | int size = udp.parsePacket(); 50 | if (size >= NTP_PACKET_SIZE) { 51 | Serial.println("Receive NTP Response"); 52 | udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer 53 | unsigned long secsSince1900; 54 | // convert four bytes starting at location 40 to a long integer 55 | secsSince1900 = (unsigned long)packetBuffer[40] << 24; 56 | secsSince1900 |= (unsigned long)packetBuffer[41] << 16; 57 | secsSince1900 |= (unsigned long)packetBuffer[42] << 8; 58 | secsSince1900 |= (unsigned long)packetBuffer[43]; 59 | return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; 60 | } 61 | } 62 | Serial.println("No NTP Response :-("); 63 | return 0; // return 0 if unable to get the time 64 | } 65 | 66 | void drawAnalogClock(byte seconds, byte minutes, byte hours, boolean drawMillis, boolean drawSecond) 67 | { 68 | if (timeStatus() == timeSet) { 69 | setSyncInterval(300); 70 | } 71 | else { 72 | setSyncInterval(30); 73 | } 74 | 75 | if (second() != oldSec) { 76 | oldSecTime = millis(); 77 | oldSec = second(); 78 | } 79 | 80 | if (hours > 12) hours -= 12; 81 | 82 | int millisecond = millis() - oldSecTime; 83 | 84 | int secondIndex = map(seconds, 0, 59, 0, NUM_LEDS); 85 | int minuteIndex = map(minutes, 0, 59, 0, NUM_LEDS); 86 | int hourIndex = map(hours * 5, 5, 60, 0, NUM_LEDS); 87 | int millisecondIndex = map(secondIndex + millisecond * .06, 0, 60, 0, NUM_LEDS); 88 | 89 | if (millisecondIndex >= NUM_LEDS) 90 | millisecondIndex -= NUM_LEDS; 91 | 92 | hourIndex += minuteIndex / 12; 93 | 94 | if (hourIndex >= NUM_LEDS) 95 | hourIndex -= NUM_LEDS; 96 | 97 | // see if we need to reverse the order of the LEDS 98 | if (flipClock == 1) { 99 | int max = NUM_LEDS - 1; 100 | secondIndex = max - secondIndex; 101 | minuteIndex = max - minuteIndex; 102 | hourIndex = max - hourIndex; 103 | millisecondIndex = max - millisecondIndex; 104 | } 105 | 106 | if (secondIndex >= NUM_LEDS) 107 | secondIndex = NUM_LEDS - 1; 108 | else if (secondIndex < 0) 109 | secondIndex = 0; 110 | 111 | if (minuteIndex >= NUM_LEDS) 112 | minuteIndex = NUM_LEDS - 1; 113 | else if (minuteIndex < 0) 114 | minuteIndex = 0; 115 | 116 | if (hourIndex >= NUM_LEDS) 117 | hourIndex = NUM_LEDS - 1; 118 | else if (hourIndex < 0) 119 | hourIndex = 0; 120 | 121 | if (millisecondIndex >= NUM_LEDS) 122 | millisecondIndex = NUM_LEDS - 1; 123 | else if (millisecondIndex < 0) 124 | millisecondIndex = 0; 125 | 126 | if (drawMillis) 127 | leds[millisecondIndex] += CRGB(0, 0, 127); // Blue 128 | 129 | if (drawSecond) 130 | leds[secondIndex] += CRGB(0, 0, 127); // Blue 131 | 132 | leds[minuteIndex] += CRGB::Green; 133 | leds[hourIndex] += CRGB::Red; 134 | } 135 | 136 | void analogClock() { 137 | dimAll(220); 138 | 139 | drawAnalogClock(second(), minute(), hour(), false, true); 140 | } 141 | 142 | void analogClockWithMillis() { 143 | dimAll(220); 144 | 145 | drawAnalogClock(second(), minute(), hour(), true, true); 146 | } 147 | 148 | -------------------------------------------------------------------------------- /Commands.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ESP8266 + FastLED + IR Remote + MSGEQ7: https://github.com/jasoncoon/esp8266-fastled-webserver 3 | * Copyright (C) 2015 Jason Coon 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | enum class InputCommand { 20 | None, 21 | Up, 22 | Down, 23 | Left, 24 | Right, 25 | Select, 26 | Brightness, 27 | PlayMode, 28 | Palette, 29 | Power, 30 | BrightnessUp, 31 | BrightnessDown, 32 | 33 | Pattern1, 34 | Pattern2, 35 | Pattern3, 36 | Pattern4, 37 | Pattern5, 38 | Pattern6, 39 | Pattern7, 40 | Pattern8, 41 | Pattern9, 42 | Pattern10, 43 | Pattern11, 44 | Pattern12, 45 | 46 | RedUp, 47 | RedDown, 48 | GreenUp, 49 | GreenDown, 50 | BlueUp, 51 | BlueDown, 52 | 53 | Red, 54 | RedOrange, 55 | Orange, 56 | YellowOrange, 57 | Yellow, 58 | 59 | Green, 60 | Lime, 61 | Aqua, 62 | Teal, 63 | Navy, 64 | 65 | Blue, 66 | RoyalBlue, 67 | Purple, 68 | Indigo, 69 | Magenta, 70 | 71 | White, 72 | Pink, 73 | LightPink, 74 | BabyBlue, 75 | LightBlue, 76 | }; 77 | 78 | // IR Raw Key Codes for SparkFun remote 79 | #define IRCODE_SPARKFUN_POWER 0x10EFD827 // 284153895 80 | #define IRCODE_SPARKFUN_A 0x10EFF807 // 81 | #define IRCODE_SPARKFUN_B 0x10EF7887 82 | #define IRCODE_SPARKFUN_C 0x10EF58A7 83 | #define IRCODE_SPARKFUN_UP 0x10EFA05F // 284139615 84 | #define IRCODE_SPARKFUN_LEFT 0x10EF10EF 85 | #define IRCODE_SPARKFUN_SELECT 0x10EF20DF 86 | #define IRCODE_SPARKFUN_RIGHT 0x10EF807F 87 | #define IRCODE_SPARKFUN_DOWN 0x10EF00FF 88 | #define IRCODE_SPARKFUN_HELD 0xFFFFFFFF 89 | 90 | // IR Raw Key Codes for Adafruit remote 91 | #define IRCODE_ADAFRUIT_HELD 0x7FFFFFFF // 4294967295 92 | #define IRCODE_ADAFRUIT_VOLUME_UP 0x00FD40BF // 16597183 93 | #define IRCODE_ADAFRUIT_PLAY_PAUSE 0x00FD807F // 16613503 94 | #define IRCODE_ADAFRUIT_VOLUME_DOWN 0x00FD00FF // 16580863 95 | #define IRCODE_ADAFRUIT_SETUP 0x00FD20DF // 16589023 96 | #define IRCODE_ADAFRUIT_UP 0x00FDA05F // 16621663 97 | #define IRCODE_ADAFRUIT_STOP_MODE 0x00FD609F // 16605343 98 | #define IRCODE_ADAFRUIT_LEFT 0x00FD10EF // 16584943 99 | #define IRCODE_ADAFRUIT_ENTER_SAVE 0x00FD906F // 16617583 100 | #define IRCODE_ADAFRUIT_RIGHT 0x00FD50AF // 16601263 101 | #define IRCODE_ADAFRUIT_0_10_PLUS 0x00FD30CF // 16593103 102 | #define IRCODE_ADAFRUIT_DOWN 0x00FDB04F // 16625743 103 | #define IRCODE_ADAFRUIT_BACK 0x00FD708F // 16609423 104 | #define IRCODE_ADAFRUIT_1 0x00FD08F7 // 16582903 105 | #define IRCODE_ADAFRUIT_2 0x00FD8877 // 16615543 106 | #define IRCODE_ADAFRUIT_3 0x00FD48B7 // 16599223 107 | #define IRCODE_ADAFRUIT_4 0x00FD28D7 // 16591063 108 | #define IRCODE_ADAFRUIT_5 0x00FDA857 // 16623703 109 | #define IRCODE_ADAFRUIT_6 0x00FD6897 // 16607383 110 | #define IRCODE_ADAFRUIT_7 0x00FD18E7 // 16586983 111 | #define IRCODE_ADAFRUIT_8 0x00FD9867 // 16619623 112 | #define IRCODE_ADAFRUIT_9 0x00FD58A7 // 16603303 113 | 114 | // IR Raw Key Codes for eTopxizu 44Key IR Remote Controller for 5050 3528 RGB LED Light Strip 115 | #define IRCODE_ETOPXIZU_HELD 0x7FFFFFFF // 4294967295 116 | #define IRCODE_ETOPXIZU_POWER 16712445 117 | #define IRCODE_ETOPXIZU_PLAY_PAUSE 16745085 118 | #define IRCODE_ETOPXIZU_BRIGHTNESS_UP 16726725 119 | #define IRCODE_ETOPXIZU_BRIGHTNESS_DOWN 16759365 120 | 121 | #define IRCODE_ETOPXIZU_DIY1 16724175 122 | #define IRCODE_ETOPXIZU_DIY2 16756815 123 | #define IRCODE_ETOPXIZU_DIY3 16740495 124 | #define IRCODE_ETOPXIZU_DIY4 16716015 125 | #define IRCODE_ETOPXIZU_DIY5 16748655 126 | #define IRCODE_ETOPXIZU_DIY6 16732335 127 | 128 | #define IRCODE_ETOPXIZU_JUMP3 16720095 129 | #define IRCODE_ETOPXIZU_JUMP7 16752735 130 | #define IRCODE_ETOPXIZU_FADE3 16736415 131 | #define IRCODE_ETOPXIZU_FADE7 16769055 132 | #define IRCODE_ETOPXIZU_FLASH 16764975 133 | #define IRCODE_ETOPXIZU_AUTO 16773135 134 | 135 | #define IRCODE_ETOPXIZU_QUICK 16771095 136 | #define IRCODE_ETOPXIZU_SLOW 16762935 137 | 138 | #define IRCODE_ETOPXIZU_RED_UP 16722135 139 | #define IRCODE_ETOPXIZU_RED_DOWN 16713975 140 | 141 | #define IRCODE_ETOPXIZU_GREEN_UP 16754775 142 | #define IRCODE_ETOPXIZU_GREEN_DOWN 16746615 143 | 144 | #define IRCODE_ETOPXIZU_BLUE_UP 16738455 145 | #define IRCODE_ETOPXIZU_BLUE_DOWN 16730295 146 | 147 | #define IRCODE_ETOPXIZU_RED 16718565 148 | #define IRCODE_ETOPXIZU_RED_ORANGE 16722645 149 | #define IRCODE_ETOPXIZU_ORANGE 16714485 150 | #define IRCODE_ETOPXIZU_YELLOW_ORANGE 16726215 151 | #define IRCODE_ETOPXIZU_YELLOW 16718055 152 | 153 | #define IRCODE_ETOPXIZU_GREEN 16751205 154 | #define IRCODE_ETOPXIZU_LIME 16755285 155 | #define IRCODE_ETOPXIZU_AQUA 16747125 156 | #define IRCODE_ETOPXIZU_TEAL 16758855 157 | #define IRCODE_ETOPXIZU_NAVY 16750695 158 | 159 | #define IRCODE_ETOPXIZU_BLUE 16753245 160 | #define IRCODE_ETOPXIZU_ROYAL_BLUE 16749165 161 | #define IRCODE_ETOPXIZU_PURPLE 16757325 162 | #define IRCODE_ETOPXIZU_INDIGO 16742535 163 | #define IRCODE_ETOPXIZU_MAGENTA 16734375 164 | 165 | #define IRCODE_ETOPXIZU_WHITE 16720605 166 | #define IRCODE_ETOPXIZU_PINK 16716525 167 | #define IRCODE_ETOPXIZU_LIGHT_PINK 16724685 168 | #define IRCODE_ETOPXIZU_BABY_BLUE 16775175 169 | #define IRCODE_ETOPXIZU_LIGHT_BLUE 16767015 170 | 171 | bool sparkfunRemoteEnabled = true; 172 | bool adafruitRemoteEnabled = true; 173 | bool etopxizuRemoteEnabled = true; 174 | 175 | // Low level IR code reading function 176 | // Function will return 0 if no IR code available 177 | unsigned long decodeIRCode() { 178 | 179 | decode_results results; 180 | 181 | results.value = 0; 182 | 183 | // Attempt to read an IR code ? 184 | if (irReceiver.decode(&results)) { 185 | delay(20); 186 | 187 | if (results.value != 0) 188 | Serial.println(results.value); 189 | 190 | // Prepare to receive the next IR code 191 | irReceiver.resume(); 192 | } 193 | 194 | return results.value; 195 | } 196 | 197 | // Read an IR code 198 | // Function will return 0 if no IR code available 199 | unsigned long readIRCode() { 200 | 201 | // Is there an IR code to read ? 202 | unsigned long code = decodeIRCode(); 203 | if (code == 0) { 204 | // No code so return 0 205 | return 0; 206 | } 207 | 208 | // Keep reading until code changes 209 | while (decodeIRCode() == code) { 210 | ; 211 | } 212 | // Serial.println(code); 213 | return code; 214 | } 215 | 216 | unsigned long lastIrCode = 0; 217 | 218 | unsigned int holdStartTime = 0; 219 | unsigned int defaultHoldDelay = 500; 220 | bool isHolding = false; 221 | 222 | unsigned int zeroStartTime = 0; 223 | unsigned int zeroDelay = 120; 224 | 225 | unsigned long readIRCode(unsigned int holdDelay) { 226 | // read the raw code from the sensor 227 | unsigned long irCode = readIRCode(); 228 | 229 | //Serial.print(millis()); 230 | //Serial.print("\t"); 231 | //Serial.println(irCode); 232 | 233 | // don't return a short click until we know it's not a long hold 234 | // we'll have to wait for holdDelay ms to pass before returning a non-zero IR code 235 | // then, after that delay, as long as the button is held, we can keep returning the code 236 | // every time until it's released 237 | 238 | // the ir remote only sends codes every 107 ms or so (avg 106.875, max 111, min 102), 239 | // so the ir sensor will return 0 even if a button is held 240 | // so we have to wait longer than that before returning a non-zero code 241 | // in order to detect that a button has been released and is no longer held 242 | 243 | // only reset after we've gotten 0 back for more than the ir remote send interval 244 | unsigned int zeroTime = 0; 245 | 246 | if (irCode == 0) { 247 | zeroTime = millis() - zeroStartTime; 248 | if (zeroTime >= zeroDelay && lastIrCode != 0) { 249 | //Serial.println(F("zero delay has elapsed, returning last ir code")); 250 | // the button has been released for longer than the zero delay 251 | // start over delays over and return the last code 252 | irCode = lastIrCode; 253 | lastIrCode = 0; 254 | return irCode; 255 | } 256 | 257 | return 0; 258 | } 259 | 260 | // reset the zero timer every time a non-zero code is read 261 | zeroStartTime = millis(); 262 | 263 | unsigned int heldTime = 0; 264 | 265 | if (irCode == IRCODE_SPARKFUN_HELD || irCode == IRCODE_ADAFRUIT_HELD) { 266 | // has the hold delay passed? 267 | heldTime = millis() - holdStartTime; 268 | if (heldTime >= holdDelay) { 269 | isHolding = true; 270 | //Serial.println(F("hold delay has elapsed, returning last ir code")); 271 | return lastIrCode; 272 | } 273 | else if (holdStartTime == 0) { 274 | isHolding = false; 275 | holdStartTime = millis(); 276 | } 277 | } 278 | else { 279 | // not zero, not IRCODE_SPARKFUN_HELD 280 | // store it for use later, until the hold and zero delays have elapsed 281 | holdStartTime = millis(); 282 | isHolding = false; 283 | lastIrCode = irCode; 284 | return 0; 285 | } 286 | 287 | return 0; 288 | } 289 | 290 | void heldButtonHasBeenHandled() { 291 | lastIrCode = 0; 292 | isHolding = false; 293 | holdStartTime = 0; 294 | } 295 | 296 | unsigned long waitForIRCode() { 297 | 298 | unsigned long irCode = readIRCode(); 299 | while ((irCode == 0) || (irCode == 0xFFFFFFFF)) { 300 | delay(200); 301 | irCode = readIRCode(); 302 | } 303 | return irCode; 304 | } 305 | 306 | InputCommand getCommand(unsigned long input) { 307 | if (adafruitRemoteEnabled) { 308 | switch (input) { 309 | case IRCODE_ADAFRUIT_UP: 310 | return InputCommand::Up; 311 | 312 | case IRCODE_ADAFRUIT_DOWN: 313 | return InputCommand::Down; 314 | 315 | case IRCODE_ADAFRUIT_LEFT: 316 | return InputCommand::Left; 317 | 318 | case IRCODE_ADAFRUIT_RIGHT: 319 | return InputCommand::Right; 320 | 321 | case IRCODE_ADAFRUIT_ENTER_SAVE: 322 | return InputCommand::Select; 323 | 324 | case IRCODE_ADAFRUIT_STOP_MODE: 325 | case IRCODE_ADAFRUIT_1: 326 | return InputCommand::PlayMode; 327 | 328 | case IRCODE_ADAFRUIT_2: 329 | return InputCommand::Palette; 330 | 331 | case IRCODE_ADAFRUIT_PLAY_PAUSE: 332 | return InputCommand::Power; 333 | 334 | case IRCODE_ADAFRUIT_VOLUME_UP: 335 | return InputCommand::BrightnessUp; 336 | 337 | case IRCODE_ADAFRUIT_VOLUME_DOWN: 338 | return InputCommand::BrightnessDown; 339 | } 340 | } 341 | 342 | if (sparkfunRemoteEnabled) { 343 | switch (input) { 344 | case IRCODE_SPARKFUN_UP: 345 | return InputCommand::Up; 346 | 347 | case IRCODE_SPARKFUN_DOWN: 348 | return InputCommand::Down; 349 | 350 | case IRCODE_SPARKFUN_LEFT: 351 | return InputCommand::Left; 352 | 353 | case IRCODE_SPARKFUN_RIGHT: 354 | return InputCommand::Right; 355 | 356 | case IRCODE_SPARKFUN_SELECT: 357 | return InputCommand::Select; 358 | 359 | case IRCODE_SPARKFUN_POWER: 360 | return InputCommand::Brightness; 361 | 362 | case IRCODE_SPARKFUN_A: 363 | return InputCommand::PlayMode; 364 | 365 | case IRCODE_SPARKFUN_B: 366 | return InputCommand::Palette; 367 | } 368 | } 369 | 370 | if (etopxizuRemoteEnabled) { 371 | switch (input) { 372 | case IRCODE_ETOPXIZU_QUICK: 373 | return InputCommand::Up; 374 | 375 | case IRCODE_ETOPXIZU_SLOW: 376 | return InputCommand::Down; 377 | 378 | case IRCODE_ETOPXIZU_PLAY_PAUSE: 379 | return InputCommand::PlayMode; 380 | 381 | case IRCODE_ETOPXIZU_POWER: 382 | return InputCommand::Power; 383 | 384 | case IRCODE_ETOPXIZU_BRIGHTNESS_UP: 385 | return InputCommand::BrightnessUp; 386 | case IRCODE_ETOPXIZU_BRIGHTNESS_DOWN: 387 | return InputCommand::BrightnessDown; 388 | 389 | case IRCODE_ETOPXIZU_DIY1: 390 | return InputCommand::Pattern1; 391 | case IRCODE_ETOPXIZU_DIY2: 392 | return InputCommand::Pattern2; 393 | case IRCODE_ETOPXIZU_DIY3: 394 | return InputCommand::Pattern3; 395 | case IRCODE_ETOPXIZU_DIY4: 396 | return InputCommand::Pattern4; 397 | case IRCODE_ETOPXIZU_DIY5: 398 | return InputCommand::Pattern5; 399 | case IRCODE_ETOPXIZU_DIY6: 400 | return InputCommand::Pattern6; 401 | case IRCODE_ETOPXIZU_JUMP3: 402 | return InputCommand::Pattern7; 403 | case IRCODE_ETOPXIZU_JUMP7: 404 | return InputCommand::Pattern8; 405 | case IRCODE_ETOPXIZU_FADE3: 406 | return InputCommand::Pattern9; 407 | case IRCODE_ETOPXIZU_FADE7: 408 | return InputCommand::Pattern10; 409 | case IRCODE_ETOPXIZU_FLASH: 410 | return InputCommand::Pattern11; 411 | case IRCODE_ETOPXIZU_AUTO: 412 | return InputCommand::Pattern12; 413 | 414 | case IRCODE_ETOPXIZU_RED_UP: 415 | return InputCommand::RedUp; 416 | case IRCODE_ETOPXIZU_RED_DOWN: 417 | return InputCommand::RedDown; 418 | 419 | case IRCODE_ETOPXIZU_GREEN_UP: 420 | return InputCommand::GreenUp; 421 | case IRCODE_ETOPXIZU_GREEN_DOWN: 422 | return InputCommand::GreenDown; 423 | 424 | case IRCODE_ETOPXIZU_BLUE_UP: 425 | return InputCommand::BlueUp; 426 | case IRCODE_ETOPXIZU_BLUE_DOWN: 427 | return InputCommand::BlueDown; 428 | 429 | case IRCODE_ETOPXIZU_RED: 430 | return InputCommand::Red; 431 | case IRCODE_ETOPXIZU_RED_ORANGE: 432 | return InputCommand::RedOrange; 433 | case IRCODE_ETOPXIZU_ORANGE: 434 | return InputCommand::Orange; 435 | case IRCODE_ETOPXIZU_YELLOW_ORANGE: 436 | return InputCommand::YellowOrange; 437 | case IRCODE_ETOPXIZU_YELLOW: 438 | return InputCommand::Yellow; 439 | 440 | case IRCODE_ETOPXIZU_GREEN: 441 | return InputCommand::Green; 442 | case IRCODE_ETOPXIZU_LIME: 443 | return InputCommand::Lime; 444 | case IRCODE_ETOPXIZU_AQUA: 445 | return InputCommand::Aqua; 446 | case IRCODE_ETOPXIZU_TEAL: 447 | return InputCommand::Teal; 448 | case IRCODE_ETOPXIZU_NAVY: 449 | return InputCommand::Navy; 450 | 451 | case IRCODE_ETOPXIZU_BLUE: 452 | return InputCommand::Blue; 453 | case IRCODE_ETOPXIZU_ROYAL_BLUE: 454 | return InputCommand::RoyalBlue; 455 | case IRCODE_ETOPXIZU_PURPLE: 456 | return InputCommand::Purple; 457 | case IRCODE_ETOPXIZU_INDIGO: 458 | return InputCommand::Indigo; 459 | case IRCODE_ETOPXIZU_MAGENTA: 460 | return InputCommand::Magenta; 461 | 462 | case IRCODE_ETOPXIZU_WHITE: 463 | return InputCommand::White; 464 | case IRCODE_ETOPXIZU_PINK: 465 | return InputCommand::Pink; 466 | case IRCODE_ETOPXIZU_LIGHT_PINK: 467 | return InputCommand::LightPink; 468 | case IRCODE_ETOPXIZU_BABY_BLUE: 469 | return InputCommand::BabyBlue; 470 | case IRCODE_ETOPXIZU_LIGHT_BLUE: 471 | return InputCommand::LightBlue; 472 | } 473 | } 474 | 475 | return InputCommand::None; 476 | } 477 | 478 | InputCommand readCommand() { 479 | return getCommand(readIRCode()); 480 | } 481 | 482 | InputCommand readCommand(unsigned int holdDelay) { 483 | return getCommand(readIRCode(holdDelay)); 484 | } 485 | -------------------------------------------------------------------------------- /FSBrowser.h: -------------------------------------------------------------------------------- 1 | //holds the current upload 2 | File fsUploadFile; 3 | 4 | //format bytes 5 | String formatBytes(size_t bytes){ 6 | if (bytes < 1024){ 7 | return String(bytes)+"B"; 8 | } else if(bytes < (1024 * 1024)){ 9 | return String(bytes/1024.0)+"KB"; 10 | } else if(bytes < (1024 * 1024 * 1024)){ 11 | return String(bytes/1024.0/1024.0)+"MB"; 12 | } else { 13 | return String(bytes/1024.0/1024.0/1024.0)+"GB"; 14 | } 15 | } 16 | 17 | String getContentType(String filename){ 18 | if(webServer.hasArg("download")) return "application/octet-stream"; 19 | else if(filename.endsWith(".htm")) return "text/html"; 20 | else if(filename.endsWith(".html")) return "text/html"; 21 | else if(filename.endsWith(".css")) return "text/css"; 22 | else if(filename.endsWith(".js")) return "application/javascript"; 23 | else if(filename.endsWith(".png")) return "image/png"; 24 | else if(filename.endsWith(".gif")) return "image/gif"; 25 | else if(filename.endsWith(".jpg")) return "image/jpeg"; 26 | else if(filename.endsWith(".ico")) return "image/x-icon"; 27 | else if(filename.endsWith(".xml")) return "text/xml"; 28 | else if(filename.endsWith(".pdf")) return "application/x-pdf"; 29 | else if(filename.endsWith(".zip")) return "application/x-zip"; 30 | else if(filename.endsWith(".gz")) return "application/x-gzip"; 31 | return "text/plain"; 32 | } 33 | 34 | bool handleFileRead(String path){ 35 | Serial.println("handleFileRead: " + path); 36 | if(path.endsWith("/")) path += "index.htm"; 37 | String contentType = getContentType(path); 38 | String pathWithGz = path + ".gz"; 39 | if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){ 40 | if(SPIFFS.exists(pathWithGz)) 41 | path += ".gz"; 42 | File file = SPIFFS.open(path, "r"); 43 | size_t sent = webServer.streamFile(file, contentType); 44 | file.close(); 45 | return true; 46 | } 47 | return false; 48 | } 49 | 50 | void handleFileUpload(){ 51 | if(webServer.uri() != "/edit") return; 52 | HTTPUpload& upload = webServer.upload(); 53 | if(upload.status == UPLOAD_FILE_START){ 54 | String filename = upload.filename; 55 | if(!filename.startsWith("/")) filename = "/"+filename; 56 | Serial.print("handleFileUpload Name: "); Serial.println(filename); 57 | fsUploadFile = SPIFFS.open(filename, "w"); 58 | filename = String(); 59 | } else if(upload.status == UPLOAD_FILE_WRITE){ 60 | //Serial.print("handleFileUpload Data: "); Serial.println(upload.currentSize); 61 | if(fsUploadFile) 62 | fsUploadFile.write(upload.buf, upload.currentSize); 63 | } else if(upload.status == UPLOAD_FILE_END){ 64 | if(fsUploadFile) 65 | fsUploadFile.close(); 66 | Serial.print("handleFileUpload Size: "); Serial.println(upload.totalSize); 67 | } 68 | } 69 | 70 | void handleFileDelete(){ 71 | if(webServer.args() == 0) return webServer.send(500, "text/plain", "BAD ARGS"); 72 | String path = webServer.arg(0); 73 | Serial.println("handleFileDelete: " + path); 74 | if(path == "/") 75 | return webServer.send(500, "text/plain", "BAD PATH"); 76 | if(!SPIFFS.exists(path)) 77 | return webServer.send(404, "text/plain", "FileNotFound"); 78 | SPIFFS.remove(path); 79 | webServer.send(200, "text/plain", ""); 80 | path = String(); 81 | } 82 | 83 | void handleFileCreate(){ 84 | if(webServer.args() == 0) 85 | return webServer.send(500, "text/plain", "BAD ARGS"); 86 | String path = webServer.arg(0); 87 | Serial.println("handleFileCreate: " + path); 88 | if(path == "/") 89 | return webServer.send(500, "text/plain", "BAD PATH"); 90 | if(SPIFFS.exists(path)) 91 | return webServer.send(500, "text/plain", "FILE EXISTS"); 92 | File file = SPIFFS.open(path, "w"); 93 | if(file) 94 | file.close(); 95 | else 96 | return webServer.send(500, "text/plain", "CREATE FAILED"); 97 | webServer.send(200, "text/plain", ""); 98 | path = String(); 99 | } 100 | 101 | void handleFileList() { 102 | if(!webServer.hasArg("dir")) {webServer.send(500, "text/plain", "BAD ARGS"); return;} 103 | 104 | String path = webServer.arg("dir"); 105 | Serial.println("handleFileList: " + path); 106 | Dir dir = SPIFFS.openDir(path); 107 | path = String(); 108 | 109 | String output = "["; 110 | while(dir.next()){ 111 | File entry = dir.openFile("r"); 112 | if (output != "[") output += ','; 113 | bool isDir = false; 114 | output += "{\"type\":\""; 115 | output += (isDir)?"dir":"file"; 116 | output += "\",\"name\":\""; 117 | output += String(entry.name()).substring(1); 118 | output += "\"}"; 119 | entry.close(); 120 | } 121 | 122 | output += "]"; 123 | webServer.send(200, "text/json", output); 124 | } 125 | 126 | -------------------------------------------------------------------------------- /Field.h: -------------------------------------------------------------------------------- 1 | /* 2 | ESP8266 + FastLED + IR Remote: https://github.com/jasoncoon/esp8266-fastled-webserver 3 | Copyright (C) 2016 Jason Coon 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | typedef String (*FieldSetter)(String); 20 | typedef String (*FieldGetter)(); 21 | 22 | const String NumberFieldType = "Number"; 23 | const String BooleanFieldType = "Boolean"; 24 | const String SelectFieldType = "Select"; 25 | const String ColorFieldType = "Color"; 26 | const String SectionFieldType = "Section"; 27 | const String LabelFieldType = "Label"; 28 | 29 | typedef struct Field { 30 | String name; 31 | String label; 32 | String type; 33 | uint8_t min; 34 | uint8_t max; 35 | FieldGetter getValue; 36 | FieldGetter getOptions; 37 | FieldSetter setValue; 38 | }; 39 | 40 | typedef Field FieldList[]; 41 | 42 | Field getField(String name, FieldList fields, uint8_t count) { 43 | for (uint8_t i = 0; i < count; i++) { 44 | Field field = fields[i]; 45 | if (field.name == name) { 46 | return field; 47 | } 48 | } 49 | return Field(); 50 | } 51 | 52 | String getFieldValue(String name, FieldList fields, uint8_t count) { 53 | Field field = getField(name, fields, count); 54 | if (field.getValue) { 55 | return field.getValue(); 56 | } 57 | return String(); 58 | } 59 | 60 | String setFieldValue(String name, String value, FieldList fields, uint8_t count) { 61 | Field field = getField(name, fields, count); 62 | if (field.setValue) { 63 | return field.setValue(value); 64 | } 65 | return String(); 66 | } 67 | 68 | String getFieldsJson(FieldList fields, uint8_t count) { 69 | String json = "["; 70 | 71 | for (uint8_t i = 0; i < count; i++) { 72 | Field field = fields[i]; 73 | 74 | json += "{\"name\":\"" + field.name + "\",\"label\":\"" + field.label + "\",\"type\":\"" + field.type + "\""; 75 | 76 | if(field.getValue) { 77 | if (field.type == ColorFieldType || field.type == "String" || field.type == "Label") { 78 | json += ",\"value\":\"" + field.getValue() + "\""; 79 | } 80 | else { 81 | json += ",\"value\":" + field.getValue(); 82 | } 83 | } 84 | 85 | if (field.type == NumberFieldType) { 86 | json += ",\"min\":" + String(field.min); 87 | json += ",\"max\":" + String(field.max); 88 | } 89 | 90 | if (field.getOptions) { 91 | json += ",\"options\":["; 92 | json += field.getOptions(); 93 | json += "]"; 94 | } 95 | 96 | json += "}"; 97 | 98 | if (i < count - 1) 99 | json += ","; 100 | } 101 | 102 | json += "]"; 103 | 104 | return json; 105 | } 106 | 107 | /* 108 | String json = "["; 109 | 110 | json += "{\"name\":\"power\",\"label\":\"Power\",\"type\":\"Boolean\",\"value\":" + String(power) + "},"; 111 | json += "{\"name\":\"brightness\",\"label\":\"Brightness\",\"type\":\"Number\",\"value\":" + String(brightness) + "},"; 112 | 113 | json += "{\"name\":\"pattern\",\"label\":\"Pattern\",\"type\":\"Select\",\"value\":" + String(currentPatternIndex) + ",\"options\":["; 114 | for (uint8_t i = 0; i < patternCount; i++) 115 | { 116 | json += "\"" + patterns[i].name + "\""; 117 | if (i < patternCount - 1) 118 | json += ","; 119 | } 120 | json += "]},"; 121 | 122 | json += "{\"name\":\"autoplay\",\"label\":\"Autoplay\",\"type\":\"Boolean\",\"value\":" + String(autoplay) + "},"; 123 | json += "{\"name\":\"autoplayDuration\",\"label\":\"Autoplay Duration\",\"type\":\"Number\",\"value\":" + String(autoplayDuration) + "},"; 124 | 125 | json += "{\"name\":\"solidColor\",\"label\":\"Color\",\"type\":\"Color\",\"value\":\"" + String(solidColor.r) + "," + String(solidColor.g) + "," + String(solidColor.b) +"\"},"; 126 | 127 | json += "{\"name\":\"cooling\",\"label\":\"Cooling\",\"type\":\"Number\",\"value\":" + String(cooling) + "},"; 128 | json += "{\"name\":\"sparking\",\"label\":\"Sparking\",\"type\":\"Number\",\"value\":" + String(sparking) + "}"; 129 | 130 | json += "]"; 131 | */ 132 | -------------------------------------------------------------------------------- /Fields.h: -------------------------------------------------------------------------------- 1 | /* 2 | ESP8266 + FastLED + IR Remote: https://github.com/jasoncoon/esp8266-fastled-webserver 3 | Copyright (C) 2016 Jason Coon 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | uint8_t power = 1; 20 | uint8_t brightness = brightnessMap[brightnessIndex]; 21 | 22 | //String setPower(String value) { 23 | // power = value.toInt(); 24 | // if(power < 0) power = 0; 25 | // else if (power > 1) power = 1; 26 | // return String(power); 27 | //} 28 | 29 | String getPower() { 30 | return String(power); 31 | } 32 | 33 | //String setBrightness(String value) { 34 | // brightness = value.toInt(); 35 | // if(brightness < 0) brightness = 0; 36 | // else if (brightness > 255) brightness = 255; 37 | // return String(brightness); 38 | //} 39 | 40 | String getBrightness() { 41 | return String(brightness); 42 | } 43 | 44 | String getPattern() { 45 | return String(currentPatternIndex); 46 | } 47 | 48 | String getPatterns() { 49 | String json = ""; 50 | 51 | for (uint8_t i = 0; i < patternCount; i++) { 52 | json += "\"" + patterns[i].name + "\""; 53 | if (i < patternCount - 1) 54 | json += ","; 55 | } 56 | 57 | return json; 58 | } 59 | 60 | String getAutoplay() { 61 | return String(autoplay); 62 | } 63 | 64 | String getAutoplayDuration() { 65 | return String(autoplayDuration); 66 | } 67 | 68 | String getSolidColor() { 69 | return String(solidColor.r) + "," + String(solidColor.g) + "," + String(solidColor.b); 70 | } 71 | 72 | String getCooling() { 73 | return String(cooling); 74 | } 75 | 76 | String getSparking() { 77 | return String(sparking); 78 | } 79 | 80 | String getSpeed() { 81 | return String(speed); 82 | } 83 | 84 | String getTwinkleSpeed() { 85 | return String(twinkleSpeed); 86 | } 87 | 88 | String getTwinkleDensity() { 89 | return String(twinkleDensity); 90 | } 91 | 92 | String getName() { 93 | return nameString; 94 | } 95 | 96 | FieldList fields = { 97 | { "name", "Name", LabelFieldType, 0, 0, getName }, 98 | { "power", "Power", BooleanFieldType, 0, 1, getPower }, 99 | { "brightness", "Brightness", NumberFieldType, 1, 255, getBrightness }, 100 | { "pattern", "Pattern", SelectFieldType, 0, patternCount, getPattern, getPatterns }, 101 | { "speed", "Speed", NumberFieldType, 1, 255, getSpeed }, 102 | { "autoplay", "Autoplay", SectionFieldType }, 103 | { "autoplay", "Autoplay", BooleanFieldType, 0, 1, getAutoplay }, 104 | { "autoplayDuration", "Autoplay Duration", NumberFieldType, 0, 255, getAutoplayDuration }, 105 | { "solidColor", "Solid Color", SectionFieldType }, 106 | { "solidColor", "Color", ColorFieldType, 0, 255, getSolidColor }, 107 | { "fire", "Fire & Water", SectionFieldType }, 108 | { "cooling", "Cooling", NumberFieldType, 0, 255, getCooling }, 109 | { "sparking", "Sparking", NumberFieldType, 0, 255, getSparking }, 110 | { "twinkles", "Twinkles", SectionFieldType }, 111 | { "twinkleSpeed", "Twinkle Speed", NumberFieldType, 0, 8, getTwinkleSpeed }, 112 | { "twinkleDensity", "Twinkle Density", NumberFieldType, 0, 8, getTwinkleDensity }, 113 | }; 114 | 115 | uint8_t fieldCount = ARRAY_SIZE(fields); 116 | -------------------------------------------------------------------------------- /GradientPalettes.h: -------------------------------------------------------------------------------- 1 | // From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb 2 | 3 | // Gradient Color Palette definitions for 33 different cpt-city color palettes. 4 | // 956 bytes of PROGMEM for all of the palettes together, 5 | // +618 bytes of PROGMEM for gradient palette code (AVR). 6 | // 1,494 bytes total for all 34 color palettes and associated code. 7 | 8 | // Gradient palette "ib_jul01_gp", originally from 9 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/xmas/tn/ib_jul01.png.index.html 10 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 11 | // Size: 16 bytes of program space. 12 | 13 | DEFINE_GRADIENT_PALETTE( ib_jul01_gp ) { 14 | 0, 194, 1, 1, 15 | 94, 1, 29, 18, 16 | 132, 57,131, 28, 17 | 255, 113, 1, 1}; 18 | 19 | // Gradient palette "es_vintage_57_gp", originally from 20 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_57.png.index.html 21 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 22 | // Size: 20 bytes of program space. 23 | 24 | DEFINE_GRADIENT_PALETTE( es_vintage_57_gp ) { 25 | 0, 2, 1, 1, 26 | 53, 18, 1, 0, 27 | 104, 69, 29, 1, 28 | 153, 167,135, 10, 29 | 255, 46, 56, 4}; 30 | 31 | // Gradient palette "es_vintage_01_gp", originally from 32 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_01.png.index.html 33 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 34 | // Size: 32 bytes of program space. 35 | 36 | DEFINE_GRADIENT_PALETTE( es_vintage_01_gp ) { 37 | 0, 4, 1, 1, 38 | 51, 16, 0, 1, 39 | 76, 97,104, 3, 40 | 101, 255,131, 19, 41 | 127, 67, 9, 4, 42 | 153, 16, 0, 1, 43 | 229, 4, 1, 1, 44 | 255, 4, 1, 1}; 45 | 46 | // Gradient palette "es_rivendell_15_gp", originally from 47 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/rivendell/tn/es_rivendell_15.png.index.html 48 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 49 | // Size: 20 bytes of program space. 50 | 51 | DEFINE_GRADIENT_PALETTE( es_rivendell_15_gp ) { 52 | 0, 1, 14, 5, 53 | 101, 16, 36, 14, 54 | 165, 56, 68, 30, 55 | 242, 150,156, 99, 56 | 255, 150,156, 99}; 57 | 58 | // Gradient palette "rgi_15_gp", originally from 59 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/rgi/tn/rgi_15.png.index.html 60 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 61 | // Size: 36 bytes of program space. 62 | 63 | DEFINE_GRADIENT_PALETTE( rgi_15_gp ) { 64 | 0, 4, 1, 31, 65 | 31, 55, 1, 16, 66 | 63, 197, 3, 7, 67 | 95, 59, 2, 17, 68 | 127, 6, 2, 34, 69 | 159, 39, 6, 33, 70 | 191, 112, 13, 32, 71 | 223, 56, 9, 35, 72 | 255, 22, 6, 38}; 73 | 74 | // Gradient palette "retro2_16_gp", originally from 75 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/retro2/tn/retro2_16.png.index.html 76 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 77 | // Size: 8 bytes of program space. 78 | 79 | DEFINE_GRADIENT_PALETTE( retro2_16_gp ) { 80 | 0, 188,135, 1, 81 | 255, 46, 7, 1}; 82 | 83 | // Gradient palette "Analogous_1_gp", originally from 84 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/red/tn/Analogous_1.png.index.html 85 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 86 | // Size: 20 bytes of program space. 87 | 88 | DEFINE_GRADIENT_PALETTE( Analogous_1_gp ) { 89 | 0, 3, 0,255, 90 | 63, 23, 0,255, 91 | 127, 67, 0,255, 92 | 191, 142, 0, 45, 93 | 255, 255, 0, 0}; 94 | 95 | // Gradient palette "es_pinksplash_08_gp", originally from 96 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_08.png.index.html 97 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 98 | // Size: 20 bytes of program space. 99 | 100 | DEFINE_GRADIENT_PALETTE( es_pinksplash_08_gp ) { 101 | 0, 126, 11,255, 102 | 127, 197, 1, 22, 103 | 175, 210,157,172, 104 | 221, 157, 3,112, 105 | 255, 157, 3,112}; 106 | 107 | // Gradient palette "es_pinksplash_07_gp", originally from 108 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_07.png.index.html 109 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 110 | // Size: 28 bytes of program space. 111 | 112 | DEFINE_GRADIENT_PALETTE( es_pinksplash_07_gp ) { 113 | 0, 229, 1, 1, 114 | 61, 242, 4, 63, 115 | 101, 255, 12,255, 116 | 127, 249, 81,252, 117 | 153, 255, 11,235, 118 | 193, 244, 5, 68, 119 | 255, 232, 1, 5}; 120 | 121 | // Gradient palette "Coral_reef_gp", originally from 122 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/other/tn/Coral_reef.png.index.html 123 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 124 | // Size: 24 bytes of program space. 125 | 126 | DEFINE_GRADIENT_PALETTE( Coral_reef_gp ) { 127 | 0, 40,199,197, 128 | 50, 10,152,155, 129 | 96, 1,111,120, 130 | 96, 43,127,162, 131 | 139, 10, 73,111, 132 | 255, 1, 34, 71}; 133 | 134 | // Gradient palette "es_ocean_breeze_068_gp", originally from 135 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_068.png.index.html 136 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 137 | // Size: 24 bytes of program space. 138 | 139 | DEFINE_GRADIENT_PALETTE( es_ocean_breeze_068_gp ) { 140 | 0, 100,156,153, 141 | 51, 1, 99,137, 142 | 101, 1, 68, 84, 143 | 104, 35,142,168, 144 | 178, 0, 63,117, 145 | 255, 1, 10, 10}; 146 | 147 | // Gradient palette "es_ocean_breeze_036_gp", originally from 148 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_036.png.index.html 149 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 150 | // Size: 16 bytes of program space. 151 | 152 | DEFINE_GRADIENT_PALETTE( es_ocean_breeze_036_gp ) { 153 | 0, 1, 6, 7, 154 | 89, 1, 99,111, 155 | 153, 144,209,255, 156 | 255, 0, 73, 82}; 157 | 158 | // Gradient palette "departure_gp", originally from 159 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/mjf/tn/departure.png.index.html 160 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 161 | // Size: 88 bytes of program space. 162 | 163 | DEFINE_GRADIENT_PALETTE( departure_gp ) { 164 | 0, 8, 3, 0, 165 | 42, 23, 7, 0, 166 | 63, 75, 38, 6, 167 | 84, 169, 99, 38, 168 | 106, 213,169,119, 169 | 116, 255,255,255, 170 | 138, 135,255,138, 171 | 148, 22,255, 24, 172 | 170, 0,255, 0, 173 | 191, 0,136, 0, 174 | 212, 0, 55, 0, 175 | 255, 0, 55, 0}; 176 | 177 | // Gradient palette "es_landscape_64_gp", originally from 178 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_64.png.index.html 179 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 180 | // Size: 36 bytes of program space. 181 | 182 | DEFINE_GRADIENT_PALETTE( es_landscape_64_gp ) { 183 | 0, 0, 0, 0, 184 | 37, 2, 25, 1, 185 | 76, 15,115, 5, 186 | 127, 79,213, 1, 187 | 128, 126,211, 47, 188 | 130, 188,209,247, 189 | 153, 144,182,205, 190 | 204, 59,117,250, 191 | 255, 1, 37,192}; 192 | 193 | // Gradient palette "es_landscape_33_gp", originally from 194 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_33.png.index.html 195 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 196 | // Size: 24 bytes of program space. 197 | 198 | DEFINE_GRADIENT_PALETTE( es_landscape_33_gp ) { 199 | 0, 1, 5, 0, 200 | 19, 32, 23, 1, 201 | 38, 161, 55, 1, 202 | 63, 229,144, 1, 203 | 66, 39,142, 74, 204 | 255, 1, 4, 1}; 205 | 206 | // Gradient palette "rainbowsherbet_gp", originally from 207 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/icecream/tn/rainbowsherbet.png.index.html 208 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 209 | // Size: 28 bytes of program space. 210 | 211 | DEFINE_GRADIENT_PALETTE( rainbowsherbet_gp ) { 212 | 0, 255, 33, 4, 213 | 43, 255, 68, 25, 214 | 86, 255, 7, 25, 215 | 127, 255, 82,103, 216 | 170, 255,255,242, 217 | 209, 42,255, 22, 218 | 255, 87,255, 65}; 219 | 220 | // Gradient palette "gr65_hult_gp", originally from 221 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr65_hult.png.index.html 222 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 223 | // Size: 24 bytes of program space. 224 | 225 | DEFINE_GRADIENT_PALETTE( gr65_hult_gp ) { 226 | 0, 247,176,247, 227 | 48, 255,136,255, 228 | 89, 220, 29,226, 229 | 160, 7, 82,178, 230 | 216, 1,124,109, 231 | 255, 1,124,109}; 232 | 233 | // Gradient palette "gr64_hult_gp", originally from 234 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr64_hult.png.index.html 235 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 236 | // Size: 32 bytes of program space. 237 | 238 | DEFINE_GRADIENT_PALETTE( gr64_hult_gp ) { 239 | 0, 1,124,109, 240 | 66, 1, 93, 79, 241 | 104, 52, 65, 1, 242 | 130, 115,127, 1, 243 | 150, 52, 65, 1, 244 | 201, 1, 86, 72, 245 | 239, 0, 55, 45, 246 | 255, 0, 55, 45}; 247 | 248 | // Gradient palette "GMT_drywet_gp", originally from 249 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/gmt/tn/GMT_drywet.png.index.html 250 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 251 | // Size: 28 bytes of program space. 252 | 253 | DEFINE_GRADIENT_PALETTE( GMT_drywet_gp ) { 254 | 0, 47, 30, 2, 255 | 42, 213,147, 24, 256 | 84, 103,219, 52, 257 | 127, 3,219,207, 258 | 170, 1, 48,214, 259 | 212, 1, 1,111, 260 | 255, 1, 7, 33}; 261 | 262 | // Gradient palette "ib15_gp", originally from 263 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/general/tn/ib15.png.index.html 264 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 265 | // Size: 24 bytes of program space. 266 | 267 | DEFINE_GRADIENT_PALETTE( ib15_gp ) { 268 | 0, 113, 91,147, 269 | 72, 157, 88, 78, 270 | 89, 208, 85, 33, 271 | 107, 255, 29, 11, 272 | 141, 137, 31, 39, 273 | 255, 59, 33, 89}; 274 | 275 | // Gradient palette "Fuschia_7_gp", originally from 276 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/fuschia/tn/Fuschia-7.png.index.html 277 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 278 | // Size: 20 bytes of program space. 279 | 280 | DEFINE_GRADIENT_PALETTE( Fuschia_7_gp ) { 281 | 0, 43, 3,153, 282 | 63, 100, 4,103, 283 | 127, 188, 5, 66, 284 | 191, 161, 11,115, 285 | 255, 135, 20,182}; 286 | 287 | // Gradient palette "es_emerald_dragon_08_gp", originally from 288 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/emerald_dragon/tn/es_emerald_dragon_08.png.index.html 289 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 290 | // Size: 16 bytes of program space. 291 | 292 | DEFINE_GRADIENT_PALETTE( es_emerald_dragon_08_gp ) { 293 | 0, 97,255, 1, 294 | 101, 47,133, 1, 295 | 178, 13, 43, 1, 296 | 255, 2, 10, 1}; 297 | 298 | // Gradient palette "lava_gp", originally from 299 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/lava.png.index.html 300 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 301 | // Size: 52 bytes of program space. 302 | 303 | DEFINE_GRADIENT_PALETTE( lava_gp ) { 304 | 0, 0, 0, 0, 305 | 46, 18, 0, 0, 306 | 96, 113, 0, 0, 307 | 108, 142, 3, 1, 308 | 119, 175, 17, 1, 309 | 146, 213, 44, 2, 310 | 174, 255, 82, 4, 311 | 188, 255,115, 4, 312 | 202, 255,156, 4, 313 | 218, 255,203, 4, 314 | 234, 255,255, 4, 315 | 244, 255,255, 71, 316 | 255, 255,255,255}; 317 | 318 | // Gradient palette "fire_gp", originally from 319 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fire.png.index.html 320 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 321 | // Size: 28 bytes of program space. 322 | 323 | DEFINE_GRADIENT_PALETTE( fire_gp ) { 324 | 0, 1, 1, 0, 325 | 76, 32, 5, 0, 326 | 146, 192, 24, 0, 327 | 197, 220,105, 5, 328 | 240, 252,255, 31, 329 | 250, 252,255,111, 330 | 255, 255,255,255}; 331 | 332 | // Gradient palette "Colorfull_gp", originally from 333 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Colorfull.png.index.html 334 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 335 | // Size: 44 bytes of program space. 336 | 337 | DEFINE_GRADIENT_PALETTE( Colorfull_gp ) { 338 | 0, 10, 85, 5, 339 | 25, 29,109, 18, 340 | 60, 59,138, 42, 341 | 93, 83, 99, 52, 342 | 106, 110, 66, 64, 343 | 109, 123, 49, 65, 344 | 113, 139, 35, 66, 345 | 116, 192,117, 98, 346 | 124, 255,255,137, 347 | 168, 100,180,155, 348 | 255, 22,121,174}; 349 | 350 | // Gradient palette "Magenta_Evening_gp", originally from 351 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Magenta_Evening.png.index.html 352 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 353 | // Size: 28 bytes of program space. 354 | 355 | DEFINE_GRADIENT_PALETTE( Magenta_Evening_gp ) { 356 | 0, 71, 27, 39, 357 | 31, 130, 11, 51, 358 | 63, 213, 2, 64, 359 | 70, 232, 1, 66, 360 | 76, 252, 1, 69, 361 | 108, 123, 2, 51, 362 | 255, 46, 9, 35}; 363 | 364 | // Gradient palette "Pink_Purple_gp", originally from 365 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Pink_Purple.png.index.html 366 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 367 | // Size: 44 bytes of program space. 368 | 369 | DEFINE_GRADIENT_PALETTE( Pink_Purple_gp ) { 370 | 0, 19, 2, 39, 371 | 25, 26, 4, 45, 372 | 51, 33, 6, 52, 373 | 76, 68, 62,125, 374 | 102, 118,187,240, 375 | 109, 163,215,247, 376 | 114, 217,244,255, 377 | 122, 159,149,221, 378 | 149, 113, 78,188, 379 | 183, 128, 57,155, 380 | 255, 146, 40,123}; 381 | 382 | // Gradient palette "Sunset_Real_gp", originally from 383 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png.index.html 384 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 385 | // Size: 28 bytes of program space. 386 | 387 | DEFINE_GRADIENT_PALETTE( Sunset_Real_gp ) { 388 | 0, 120, 0, 0, 389 | 22, 179, 22, 0, 390 | 51, 255,104, 0, 391 | 85, 167, 22, 18, 392 | 135, 100, 0,103, 393 | 198, 16, 0,130, 394 | 255, 0, 0,160}; 395 | 396 | // Gradient palette "es_autumn_19_gp", originally from 397 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_19.png.index.html 398 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 399 | // Size: 52 bytes of program space. 400 | 401 | DEFINE_GRADIENT_PALETTE( es_autumn_19_gp ) { 402 | 0, 26, 1, 1, 403 | 51, 67, 4, 1, 404 | 84, 118, 14, 1, 405 | 104, 137,152, 52, 406 | 112, 113, 65, 1, 407 | 122, 133,149, 59, 408 | 124, 137,152, 52, 409 | 135, 113, 65, 1, 410 | 142, 139,154, 46, 411 | 163, 113, 13, 1, 412 | 204, 55, 3, 1, 413 | 249, 17, 1, 1, 414 | 255, 17, 1, 1}; 415 | 416 | // Gradient palette "BlacK_Blue_Magenta_White_gp", originally from 417 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Blue_Magenta_White.png.index.html 418 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 419 | // Size: 28 bytes of program space. 420 | 421 | DEFINE_GRADIENT_PALETTE( BlacK_Blue_Magenta_White_gp ) { 422 | 0, 0, 0, 0, 423 | 42, 0, 0, 45, 424 | 84, 0, 0,255, 425 | 127, 42, 0,255, 426 | 170, 255, 0,255, 427 | 212, 255, 55,255, 428 | 255, 255,255,255}; 429 | 430 | // Gradient palette "BlacK_Magenta_Red_gp", originally from 431 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Magenta_Red.png.index.html 432 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 433 | // Size: 20 bytes of program space. 434 | 435 | DEFINE_GRADIENT_PALETTE( BlacK_Magenta_Red_gp ) { 436 | 0, 0, 0, 0, 437 | 63, 42, 0, 45, 438 | 127, 255, 0,255, 439 | 191, 255, 0, 45, 440 | 255, 255, 0, 0}; 441 | 442 | // Gradient palette "BlacK_Red_Magenta_Yellow_gp", originally from 443 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Red_Magenta_Yellow.png.index.html 444 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 445 | // Size: 28 bytes of program space. 446 | 447 | DEFINE_GRADIENT_PALETTE( BlacK_Red_Magenta_Yellow_gp ) { 448 | 0, 0, 0, 0, 449 | 42, 42, 0, 0, 450 | 84, 255, 0, 0, 451 | 127, 255, 0, 45, 452 | 170, 255, 0,255, 453 | 212, 255, 55, 45, 454 | 255, 255,255, 0}; 455 | 456 | // Gradient palette "Blue_Cyan_Yellow_gp", originally from 457 | // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Blue_Cyan_Yellow.png.index.html 458 | // converted for FastLED with gammas (2.6, 2.2, 2.5) 459 | // Size: 20 bytes of program space. 460 | 461 | DEFINE_GRADIENT_PALETTE( Blue_Cyan_Yellow_gp ) { 462 | 0, 0, 0,255, 463 | 63, 0, 55,255, 464 | 127, 0,255,255, 465 | 191, 42,255, 45, 466 | 255, 255,255, 0}; 467 | 468 | 469 | // Single array of defined cpt-city color palettes. 470 | // This will let us programmatically choose one based on 471 | // a number, rather than having to activate each explicitly 472 | // by name every time. 473 | // Since it is const, this array could also be moved 474 | // into PROGMEM to save SRAM, but for simplicity of illustration 475 | // we'll keep it in a regular SRAM array. 476 | // 477 | // This list of color palettes acts as a "playlist"; you can 478 | // add or delete, or re-arrange as you wish. 479 | const TProgmemRGBGradientPalettePtr gGradientPalettes[] = { 480 | Sunset_Real_gp, 481 | es_rivendell_15_gp, 482 | es_ocean_breeze_036_gp, 483 | rgi_15_gp, 484 | retro2_16_gp, 485 | Analogous_1_gp, 486 | es_pinksplash_08_gp, 487 | Coral_reef_gp, 488 | es_ocean_breeze_068_gp, 489 | es_pinksplash_07_gp, 490 | es_vintage_01_gp, 491 | departure_gp, 492 | es_landscape_64_gp, 493 | es_landscape_33_gp, 494 | rainbowsherbet_gp, 495 | gr65_hult_gp, 496 | gr64_hult_gp, 497 | GMT_drywet_gp, 498 | ib_jul01_gp, 499 | es_vintage_57_gp, 500 | ib15_gp, 501 | Fuschia_7_gp, 502 | es_emerald_dragon_08_gp, 503 | lava_gp, 504 | fire_gp, 505 | Colorfull_gp, 506 | Magenta_Evening_gp, 507 | Pink_Purple_gp, 508 | es_autumn_19_gp, 509 | BlacK_Blue_Magenta_White_gp, 510 | BlacK_Magenta_Red_gp, 511 | BlacK_Red_Magenta_Yellow_gp, 512 | Blue_Cyan_Yellow_gp }; 513 | 514 | 515 | // Count of how many cpt-city gradients are defined: 516 | const uint8_t gGradientPaletteCount = 517 | sizeof( gGradientPalettes) / sizeof( TProgmemRGBGradientPalettePtr ); 518 | 519 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Pacifica.h: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////// 2 | // 3 | // The code for this animation is more complicated than other examples, and 4 | // while it is "ready to run", and documented in general, it is probably not 5 | // the best starting point for learning. Nevertheless, it does illustrate some 6 | // useful techniques. 7 | // 8 | ////////////////////////////////////////////////////////////////////////// 9 | // 10 | // In this animation, there are four "layers" of waves of light. 11 | // 12 | // Each layer moves independently, and each is scaled separately. 13 | // 14 | // All four wave layers are added together on top of each other, and then 15 | // another filter is applied that adds "whitecaps" of brightness where the 16 | // waves line up with each other more. Finally, another pass is taken 17 | // over the led array to 'deepen' (dim) the blues and greens. 18 | // 19 | // The speed and scale and motion each layer varies slowly within independent 20 | // hand-chosen ranges, which is why the code has a lot of low-speed 'beatsin8' functions 21 | // with a lot of oddly specific numeric ranges. 22 | // 23 | // These three custom blue-green color palettes were inspired by the colors found in 24 | // the waters off the southern coast of California, https://goo.gl/maps/QQgd97jjHesHZVxQ7 25 | // 26 | CRGBPalette16 pacifica_palette_1 = 27 | { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117, 28 | 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x14554B, 0x28AA50 }; 29 | CRGBPalette16 pacifica_palette_2 = 30 | { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117, 31 | 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x0C5F52, 0x19BE5F }; 32 | CRGBPalette16 pacifica_palette_3 = 33 | { 0x000208, 0x00030E, 0x000514, 0x00061A, 0x000820, 0x000927, 0x000B2D, 0x000C33, 34 | 0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF }; 35 | 36 | // Add one layer of waves into the led array 37 | void pacifica_one_layer( CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) 38 | { 39 | uint16_t ci = cistart; 40 | uint16_t waveangle = ioff; 41 | uint16_t wavescale_half = (wavescale / 2) + 20; 42 | for( uint16_t i = 0; i < NUM_LEDS; i++) { 43 | waveangle += 250; 44 | uint16_t s16 = sin16( waveangle ) + 32768; 45 | uint16_t cs = scale16( s16 , wavescale_half ) + wavescale_half; 46 | ci += cs; 47 | uint16_t sindex16 = sin16( ci) + 32768; 48 | uint8_t sindex8 = scale16( sindex16, 240); 49 | CRGB c = ColorFromPalette( p, sindex8, bri, LINEARBLEND); 50 | leds[i] += c; 51 | } 52 | } 53 | 54 | // Add extra 'white' to areas where the four layers of light have lined up brightly 55 | void pacifica_add_whitecaps() 56 | { 57 | uint8_t basethreshold = beatsin8( 9, 55, 65); 58 | uint8_t wave = beat8( 7 ); 59 | 60 | for( uint16_t i = 0; i < NUM_LEDS; i++) { 61 | uint8_t threshold = scale8( sin8( wave), 20) + basethreshold; 62 | wave += 7; 63 | uint8_t l = leds[i].getAverageLight(); 64 | if( l > threshold) { 65 | uint8_t overage = l - threshold; 66 | uint8_t overage2 = qadd8( overage, overage); 67 | leds[i] += CRGB( overage, overage2, qadd8( overage2, overage2)); 68 | } 69 | } 70 | } 71 | 72 | // Deepen the blues and greens 73 | void pacifica_deepen_colors() 74 | { 75 | for( uint16_t i = 0; i < NUM_LEDS; i++) { 76 | leds[i].blue = scale8( leds[i].blue, 145); 77 | leds[i].green= scale8( leds[i].green, 200); 78 | leds[i] |= CRGB( 2, 5, 7); 79 | } 80 | } 81 | 82 | void pacifica() 83 | { 84 | // Increment the four "color index start" counters, one for each wave layer. 85 | // Each is incremented at a different speed, and the speeds vary over time. 86 | static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; 87 | static uint32_t sLastms = 0; 88 | uint32_t ms = GET_MILLIS(); 89 | uint32_t deltams = ms - sLastms; 90 | sLastms = ms; 91 | uint16_t speedfactor1 = beatsin16(3, 179, 269); 92 | uint16_t speedfactor2 = beatsin16(4, 179, 269); 93 | uint32_t deltams1 = (deltams * speedfactor1) / 256; 94 | uint32_t deltams2 = (deltams * speedfactor2) / 256; 95 | uint32_t deltams21 = (deltams1 + deltams2) / 2; 96 | sCIStart1 += (deltams1 * beatsin88(1011,10,13)); 97 | sCIStart2 -= (deltams21 * beatsin88(777,8,11)); 98 | sCIStart3 -= (deltams1 * beatsin88(501,5,7)); 99 | sCIStart4 -= (deltams2 * beatsin88(257,4,6)); 100 | 101 | // Clear out the LED array to a dim background blue-green 102 | fill_solid( leds, NUM_LEDS, CRGB( 2, 6, 10)); 103 | 104 | // Render each of four layers, with different scales and speeds, that vary over time 105 | pacifica_one_layer( pacifica_palette_1, sCIStart1, beatsin16( 3, 11 * 256, 14 * 256), beatsin8( 10, 70, 130), 0-beat16( 301) ); 106 | pacifica_one_layer( pacifica_palette_2, sCIStart2, beatsin16( 4, 6 * 256, 9 * 256), beatsin8( 17, 40, 80), beat16( 401) ); 107 | pacifica_one_layer( pacifica_palette_3, sCIStart3, 6 * 256, beatsin8( 9, 10,38), 0-beat16(503)); 108 | pacifica_one_layer( pacifica_palette_3, sCIStart4, 5 * 256, beatsin8( 8, 10,28), beat16(601)); 109 | 110 | // Add brighter 'whitecaps' where the waves lines up more 111 | pacifica_add_whitecaps(); 112 | 113 | // Deepen the blues and greens a bit 114 | pacifica_deepen_colors(); 115 | } 116 | -------------------------------------------------------------------------------- /Ping.h: -------------------------------------------------------------------------------- 1 | /* 2 | ESP8266 + FastLED: https://github.com/jasoncoon/esp8266-fastled-webserver 3 | Copyright (C) 2015-2020 Jason Coon 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | // Discovery enables you to find your internet-connected ESP8266 devices on your network. 20 | // When enabled, the ESP8266 will "ping" a server every 10 minutes, by default. 21 | // Then, you can go to https://discover.evilgeniuslabs.org to view all of the 22 | // ESP8266 devices at your current location. 23 | 24 | // Data submitted to the server: device name, local IP address, MAC address, and current millis (uptime). 25 | // The server logs this, along with the public request IP address. 26 | // When you visit https://discover.evilgeniuslabs.org, the server returns a list of devices 27 | // with the same public request IP address. 28 | 29 | // Discovery is "opt-in". It is disabled by default. Enable it if you want, if you find it useful. 30 | // Evil Genius Labs, despite the name, is not actually evil, and will never sell or share 31 | // this data to any 3rd parties, ever. 32 | 33 | // This was a feature I needed for my own devices, of which there are dozens. :) 34 | 35 | const bool discovery = true; 36 | const String serverName = "https://ping.evilgeniuslabs.org"; // address of server to ping 37 | const String fingerPrint = "AD 1F CB D9 A0 BC 17 D5 5B F2 E1 BF 98 D1 06 CD AC 3F B8 33"; // server SSL cert fingerprint 38 | 39 | void checkPingTimer() 40 | { 41 | if (!discovery) 42 | return; 43 | 44 | const unsigned long pingDelay = 600000; /// 60 * 10 * 1000; // 10 minutes 45 | static unsigned long lastPingTime = pingDelay; 46 | 47 | if ((millis() - lastPingTime) > pingDelay) 48 | { 49 | // Serial.println("Time to ping"); 50 | 51 | // Check WiFi connection status 52 | if (WiFi.status() == WL_CONNECTED) 53 | { 54 | Serial.println("Connected, ready to ping"); 55 | HTTPClient http; 56 | 57 | http.begin(serverName, fingerPrint); 58 | http.addHeader("Content-Type", "application/json"); 59 | String deviceName = "\"deviceName\":\"" + nameString; 60 | String localIp = WiFi.localIP().toString(); 61 | String macAddress = WiFi.macAddress(); 62 | String body = "{" + 63 | deviceName + 64 | "\",\"localIp\":\"" + localIp + 65 | "\",\"macAddress\":\"" + macAddress + 66 | "\",\"millis\":" + String(millis()) + 67 | "}"; 68 | Serial.print("Pinging "); 69 | Serial.print(serverName); 70 | Serial.print(" with: "); 71 | Serial.println(body); 72 | Serial.print("Millis: "); 73 | Serial.println(millis()); 74 | int httpResponseCode = http.POST(body); 75 | Serial.print("Millis: "); 76 | Serial.println(millis()); 77 | Serial.print("Ping response code: "); 78 | Serial.println(httpResponseCode); 79 | lastPingTime = millis(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Eclipse v2 2 | ========= 3 | 4 | [Demo Videos](https://youtu.be/OM4Ad-Sr1Ro?list=PLUYGVM-2vDxLnqGd-mG_AMGEECwHlfd5e): 5 | 6 | [![Demo Videos](http://img.youtube.com/vi/dCE2dl4IW_E/0.jpg)](https://youtu.be/OM4Ad-Sr1Ro?list=PLUYGVM-2vDxLnqGd-mG_AMGEECwHlfd5e) 7 | 8 | Small (5.25" / 133.35mm) clock and art display built with 60 RGB LEDs, controlled with an ESP8266 using the FastLED library. 9 | 10 | Fully open-source, and programmable over Wi-Fi or micro USB cable. 11 | 12 | Controlled via a web app over Wi-Fi, and/or a wireless infrared remote control. 13 | 14 | Parts list: 15 | 16 | * 4 [3D printed quarters](https://www.thingiverse.com/thing:2873557) (10mm or 12mm to match your LED strip) 17 | * [ESP8266 Level Shifter Shield](https://www.tindie.com/products/jasoncoon/wemos-d1-mini-esp8266-led-and-level-shifter-shield/) 18 | * [Wood Circle](http://www.michaels.com/circle-shape-by-artminds/10298974.html) or [Round Mirror](http://www.michaels.com/round-mirror-by-artminds/M10025203.html) 19 | * [1m, 144 RGB LED Strip](https://www.adafruit.com/product/1507) 20 | * [ESP8266](https://www.aliexpress.com/item/WEMOS-D1-mini-Pro-16M-bytes-external-antenna-connector-ESP8266-WIFI-Internet-of-Things-development-board/32724692514.html) 21 | * [SN74HCT245N Level Shifter](http://www.digikey.com/product-detail/en/texas-instruments/SN74HCT245N/296-1612-5-ND/277258) 22 | * [1000µF Capacitor](http://www.digikey.com/product-detail/en/panasonic-electronic-components/ECA-1EM102/P5156-ND/245015) 23 | * [0.10µF Capacitor](https://www.digikey.com/product-detail/en/kemet/C320C104M5R5TA/399-9776-ND/3726028) 24 | * [470 ohm resistors](https://www.digikey.com/product-detail/en/stackpole-electronics-inc/CF14JT470R/CF14JT470RCT-ND/1830342) 25 | 26 | Controller assembly instructions: https://www.evilgeniuslabs.org/wifi-led-controller 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /TwinkleFOX.h: -------------------------------------------------------------------------------- 1 | // TwinkleFOX by Mark Kriegsman: https://gist.github.com/kriegsman/756ea6dcae8e30845b5a 2 | // 3 | // TwinkleFOX: Twinkling 'holiday' lights that fade in and out. 4 | // Colors are chosen from a palette; a few palettes are provided. 5 | // 6 | // This December 2015 implementation improves on the December 2014 version 7 | // in several ways: 8 | // - smoother fading, compatible with any colors and any palettes 9 | // - easier control of twinkle speed and twinkle density 10 | // - supports an optional 'background color' 11 | // - takes even less RAM: zero RAM overhead per pixel 12 | // - illustrates a couple of interesting techniques (uh oh...) 13 | // 14 | // The idea behind this (new) implementation is that there's one 15 | // basic, repeating pattern that each pixel follows like a waveform: 16 | // The brightness rises from 0..255 and then falls back down to 0. 17 | // The brightness at any given point in time can be determined as 18 | // as a function of time, for example: 19 | // brightness = sine( time ); // a sine wave of brightness over time 20 | // 21 | // So the way this implementation works is that every pixel follows 22 | // the exact same wave function over time. In this particular case, 23 | // I chose a sawtooth triangle wave (triwave8) rather than a sine wave, 24 | // but the idea is the same: brightness = triwave8( time ). 25 | // 26 | // Of course, if all the pixels used the exact same wave form, and 27 | // if they all used the exact same 'clock' for their 'time base', all 28 | // the pixels would brighten and dim at once -- which does not look 29 | // like twinkling at all. 30 | // 31 | // So to achieve random-looking twinkling, each pixel is given a 32 | // slightly different 'clock' signal. Some of the clocks run faster, 33 | // some run slower, and each 'clock' also has a random offset from zero. 34 | // The net result is that the 'clocks' for all the pixels are always out 35 | // of sync from each other, producing a nice random distribution 36 | // of twinkles. 37 | // 38 | // The 'clock speed adjustment' and 'time offset' for each pixel 39 | // are generated randomly. One (normal) approach to implementing that 40 | // would be to randomly generate the clock parameters for each pixel 41 | // at startup, and store them in some arrays. However, that consumes 42 | // a great deal of precious RAM, and it turns out to be totally 43 | // unnessary! If the random number generate is 'seeded' with the 44 | // same starting value every time, it will generate the same sequence 45 | // of values every time. So the clock adjustment parameters for each 46 | // pixel are 'stored' in a pseudo-random number generator! The PRNG 47 | // is reset, and then the first numbers out of it are the clock 48 | // adjustment parameters for the first pixel, the second numbers out 49 | // of it are the parameters for the second pixel, and so on. 50 | // In this way, we can 'store' a stable sequence of thousands of 51 | // random clock adjustment parameters in literally two bytes of RAM. 52 | // 53 | // There's a little bit of fixed-point math involved in applying the 54 | // clock speed adjustments, which are expressed in eighths. Each pixel's 55 | // clock speed ranges from 8/8ths of the system clock (i.e. 1x) to 56 | // 23/8ths of the system clock (i.e. nearly 3x). 57 | // 58 | // On a basic Arduino Uno or Leonardo, this code can twinkle 300+ pixels 59 | // smoothly at over 50 updates per seond. 60 | // 61 | // -Mark Kriegsman, December 2015 62 | 63 | // Overall twinkle speed. 64 | // 0 (VERY slow) to 8 (VERY fast). 65 | // 4, 5, and 6 are recommended, default is 4. 66 | uint8_t twinkleSpeed = 4; 67 | 68 | // Overall twinkle density. 69 | // 0 (NONE lit) to 8 (ALL lit at once). 70 | // Default is 5. 71 | uint8_t twinkleDensity = 5; 72 | 73 | // Background color for 'unlit' pixels 74 | // Can be set to CRGB::Black if desired. 75 | CRGB gBackgroundColor = CRGB::Black; 76 | // Example of dim incandescent fairy light background color 77 | // CRGB gBackgroundColor = CRGB(CRGB::FairyLight).nscale8_video(16); 78 | 79 | // If AUTO_SELECT_BACKGROUND_COLOR is set to 1, 80 | // then for any palette where the first two entries 81 | // are the same, a dimmed version of that color will 82 | // automatically be used as the background color. 83 | #define AUTO_SELECT_BACKGROUND_COLOR 0 84 | 85 | // If COOL_LIKE_INCANDESCENT is set to 1, colors will 86 | // fade out slighted 'reddened', similar to how 87 | // incandescent bulbs change color as they get dim down. 88 | #define COOL_LIKE_INCANDESCENT 1 89 | 90 | CRGBPalette16 twinkleFoxPalette; 91 | 92 | // This function is like 'triwave8', which produces a 93 | // symmetrical up-and-down triangle sawtooth waveform, except that this 94 | // function produces a triangle wave with a faster attack and a slower decay: 95 | // 96 | // / \ 97 | // / \ 98 | // / \ 99 | // / \ 100 | // 101 | 102 | uint8_t attackDecayWave8( uint8_t i) 103 | { 104 | if( i < 86) { 105 | return i * 3; 106 | } else { 107 | i -= 86; 108 | return 255 - (i + (i/2)); 109 | } 110 | } 111 | 112 | // This function takes a pixel, and if its in the 'fading down' 113 | // part of the cycle, it adjusts the color a little bit like the 114 | // way that incandescent bulbs fade toward 'red' as they dim. 115 | void coolLikeIncandescent( CRGB& c, uint8_t phase) 116 | { 117 | if( phase < 128) return; 118 | 119 | uint8_t cooling = (phase - 128) >> 4; 120 | c.g = qsub8( c.g, cooling); 121 | c.b = qsub8( c.b, cooling * 2); 122 | } 123 | 124 | // This function takes a time in pseudo-milliseconds, 125 | // figures out brightness = f( time ), and also hue = f( time ) 126 | // The 'low digits' of the millisecond time are used as 127 | // input to the brightness wave function. 128 | // The 'high digits' are used to select a color, so that the color 129 | // does not change over the course of the fade-in, fade-out 130 | // of one cycle of the brightness wave function. 131 | // The 'high digits' are also used to determine whether this pixel 132 | // should light at all during this cycle, based on the twinkleDensity. 133 | CRGB computeOneTwinkle( uint32_t ms, uint8_t salt) 134 | { 135 | uint16_t ticks = ms >> (8-twinkleSpeed); 136 | uint8_t fastcycle8 = ticks; 137 | uint16_t slowcycle16 = (ticks >> 8) + salt; 138 | slowcycle16 += sin8( slowcycle16); 139 | slowcycle16 = (slowcycle16 * 2053) + 1384; 140 | uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); 141 | 142 | uint8_t bright = 0; 143 | if( ((slowcycle8 & 0x0E)/2) < twinkleDensity) { 144 | bright = attackDecayWave8( fastcycle8); 145 | } 146 | 147 | uint8_t hue = slowcycle8 - salt; 148 | CRGB c; 149 | if( bright > 0) { 150 | c = ColorFromPalette( twinkleFoxPalette, hue, bright, NOBLEND); 151 | if( COOL_LIKE_INCANDESCENT == 1 ) { 152 | coolLikeIncandescent( c, fastcycle8); 153 | } 154 | } else { 155 | c = CRGB::Black; 156 | } 157 | return c; 158 | } 159 | 160 | // This function loops over each pixel, calculates the 161 | // adjusted 'clock' that this pixel should use, and calls 162 | // "CalculateOneTwinkle" on each pixel. It then displays 163 | // either the twinkle color of the background color, 164 | // whichever is brighter. 165 | void drawTwinkles() 166 | { 167 | // "PRNG16" is the pseudorandom number generator 168 | // It MUST be reset to the same starting value each time 169 | // this function is called, so that the sequence of 'random' 170 | // numbers that it generates is (paradoxically) stable. 171 | uint16_t PRNG16 = 11337; 172 | 173 | uint32_t clock32 = millis(); 174 | 175 | // Set up the background color, "bg". 176 | // if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of 177 | // the current palette are identical, then a deeply faded version of 178 | // that color is used for the background color 179 | CRGB bg; 180 | if( (AUTO_SELECT_BACKGROUND_COLOR == 1) && 181 | (twinkleFoxPalette[0] == twinkleFoxPalette[1] )) { 182 | bg = twinkleFoxPalette[0]; 183 | uint8_t bglight = bg.getAverageLight(); 184 | if( bglight > 64) { 185 | bg.nscale8_video( 16); // very bright, so scale to 1/16th 186 | } else if( bglight > 16) { 187 | bg.nscale8_video( 64); // not that bright, so scale to 1/4th 188 | } else { 189 | bg.nscale8_video( 86); // dim, scale to 1/3rd. 190 | } 191 | } else { 192 | bg = gBackgroundColor; // just use the explicitly defined background color 193 | } 194 | 195 | uint8_t backgroundBrightness = bg.getAverageLight(); 196 | 197 | for(uint8_t i = 0; i < NUM_LEDS; i++) { 198 | CRGB& pixel = leds[i]; 199 | 200 | PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number 201 | uint16_t myclockoffset16= PRNG16; // use that number as clock offset 202 | PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number 203 | // use that number as clock speed adjustment factor (in 8ths, from 8/8ths to 23/8ths) 204 | uint8_t myspeedmultiplierQ5_3 = ((((PRNG16 & 0xFF)>>4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08; 205 | uint32_t myclock30 = (uint32_t)((clock32 * myspeedmultiplierQ5_3) >> 3) + myclockoffset16; 206 | uint8_t myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel 207 | 208 | // We now have the adjusted 'clock' for this pixel, now we call 209 | // the function that computes what color the pixel should be based 210 | // on the "brightness = f( time )" idea. 211 | CRGB c = computeOneTwinkle( myclock30, myunique8); 212 | 213 | uint8_t cbright = c.getAverageLight(); 214 | int16_t deltabright = cbright - backgroundBrightness; 215 | if( deltabright >= 32 || (!bg)) { 216 | // If the new pixel is significantly brighter than the background color, 217 | // use the new color. 218 | pixel = c; 219 | } else if( deltabright > 0 ) { 220 | // If the new pixel is just slightly brighter than the background color, 221 | // mix a blend of the new color and the background color 222 | pixel = blend( bg, c, deltabright * 8); 223 | } else { 224 | // if the new pixel is not at all brighter than the background color, 225 | // just use the background color. 226 | pixel = bg; 227 | } 228 | } 229 | } 230 | 231 | // A mostly red palette with green accents and white trim. 232 | // "CRGB::Gray" is used as white to keep the brightness more uniform. 233 | const TProgmemRGBPalette16 RedGreenWhite_p FL_PROGMEM = 234 | { CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, 235 | CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, 236 | CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray, 237 | CRGB::Green, CRGB::Green, CRGB::Green, CRGB::Green }; 238 | 239 | // A mostly (dark) green palette with red berries. 240 | #define Holly_Green 0x00580c 241 | #define Holly_Red 0xB00402 242 | const TProgmemRGBPalette16 Holly_p FL_PROGMEM = 243 | { Holly_Green, Holly_Green, Holly_Green, Holly_Green, 244 | Holly_Green, Holly_Green, Holly_Green, Holly_Green, 245 | Holly_Green, Holly_Green, Holly_Green, Holly_Green, 246 | Holly_Green, Holly_Green, Holly_Green, Holly_Red 247 | }; 248 | 249 | // A red and white striped palette 250 | // "CRGB::Gray" is used as white to keep the brightness more uniform. 251 | const TProgmemRGBPalette16 RedWhite_p FL_PROGMEM = 252 | { CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray, 253 | CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray, 254 | CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray, 255 | CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray }; 256 | 257 | // A mostly blue palette with white accents. 258 | // "CRGB::Gray" is used as white to keep the brightness more uniform. 259 | const TProgmemRGBPalette16 BlueWhite_p FL_PROGMEM = 260 | { CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, 261 | CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, 262 | CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, 263 | CRGB::Blue, CRGB::Gray, CRGB::Gray, CRGB::Gray }; 264 | 265 | // A pure "fairy light" palette with some brightness variations 266 | #define HALFFAIRY ((CRGB::FairyLight & 0xFEFEFE) / 2) 267 | #define QUARTERFAIRY ((CRGB::FairyLight & 0xFCFCFC) / 4) 268 | const TProgmemRGBPalette16 FairyLight_p FL_PROGMEM = 269 | { CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, 270 | HALFFAIRY, HALFFAIRY, CRGB::FairyLight, CRGB::FairyLight, 271 | QUARTERFAIRY, QUARTERFAIRY, CRGB::FairyLight, CRGB::FairyLight, 272 | CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight }; 273 | 274 | // A palette of soft snowflakes with the occasional bright one 275 | const TProgmemRGBPalette16 Snow_p FL_PROGMEM = 276 | { 0x304048, 0x304048, 0x304048, 0x304048, 277 | 0x304048, 0x304048, 0x304048, 0x304048, 278 | 0x304048, 0x304048, 0x304048, 0x304048, 279 | 0x304048, 0x304048, 0x304048, 0xE0F0FF }; 280 | 281 | // A palette reminiscent of large 'old-school' C9-size tree lights 282 | // in the five classic colors: red, orange, green, blue, and white. 283 | #define C9_Red 0xB80400 284 | #define C9_Orange 0x902C02 285 | #define C9_Green 0x046002 286 | #define C9_Blue 0x070758 287 | #define C9_White 0x606820 288 | const TProgmemRGBPalette16 RetroC9_p FL_PROGMEM = 289 | { C9_Red, C9_Orange, C9_Red, C9_Orange, 290 | C9_Orange, C9_Red, C9_Orange, C9_Red, 291 | C9_Green, C9_Green, C9_Green, C9_Green, 292 | C9_Blue, C9_Blue, C9_Blue, 293 | C9_White 294 | }; 295 | 296 | // A cold, icy pale blue palette 297 | #define Ice_Blue1 0x0C1040 298 | #define Ice_Blue2 0x182080 299 | #define Ice_Blue3 0x5080C0 300 | const TProgmemRGBPalette16 Ice_p FL_PROGMEM = 301 | { 302 | Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1, 303 | Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1, 304 | Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1, 305 | Ice_Blue2, Ice_Blue2, Ice_Blue2, Ice_Blue3 306 | }; 307 | 308 | void redGreenWhiteTwinkles() 309 | { 310 | twinkleFoxPalette = RedGreenWhite_p; 311 | drawTwinkles(); 312 | } 313 | 314 | void hollyTwinkles() 315 | { 316 | twinkleFoxPalette = Holly_p; 317 | drawTwinkles(); 318 | } 319 | 320 | void redWhiteTwinkles() 321 | { 322 | twinkleFoxPalette = RedWhite_p; 323 | drawTwinkles(); 324 | } 325 | 326 | void blueWhiteTwinkles() 327 | { 328 | twinkleFoxPalette = BlueWhite_p; 329 | drawTwinkles(); 330 | } 331 | 332 | void fairyLightTwinkles() 333 | { 334 | twinkleFoxPalette = FairyLight_p; 335 | drawTwinkles(); 336 | } 337 | 338 | void snow2Twinkles() 339 | { 340 | twinkleFoxPalette = Snow_p; 341 | drawTwinkles(); 342 | } 343 | 344 | void iceTwinkles() 345 | { 346 | twinkleFoxPalette = Ice_p; 347 | drawTwinkles(); 348 | } 349 | 350 | void retroC9Twinkles() 351 | { 352 | twinkleFoxPalette = RetroC9_p; 353 | drawTwinkles(); 354 | } 355 | 356 | void partyTwinkles() 357 | { 358 | twinkleFoxPalette = PartyColors_p; 359 | drawTwinkles(); 360 | } 361 | 362 | void forestTwinkles() 363 | { 364 | twinkleFoxPalette = ForestColors_p; 365 | drawTwinkles(); 366 | } 367 | 368 | void lavaTwinkles() 369 | { 370 | twinkleFoxPalette = LavaColors_p; 371 | drawTwinkles(); 372 | } 373 | 374 | void fireTwinkles() 375 | { 376 | twinkleFoxPalette = HeatColors_p; 377 | drawTwinkles(); 378 | } 379 | 380 | void cloud2Twinkles() 381 | { 382 | twinkleFoxPalette = CloudColors_p; 383 | drawTwinkles(); 384 | } 385 | 386 | void oceanTwinkles() 387 | { 388 | twinkleFoxPalette = OceanColors_p; 389 | drawTwinkles(); 390 | } 391 | -------------------------------------------------------------------------------- /Twinkles.h: -------------------------------------------------------------------------------- 1 | // based on ColorTwinkles by Mark Kriegsman: https://gist.github.com/kriegsman/5408ecd397744ba0393e 2 | 3 | #define STARTING_BRIGHTNESS 64 4 | #define FADE_IN_SPEED 32 5 | #define FADE_OUT_SPEED 20 6 | #define DENSITY 255 7 | 8 | enum { GETTING_DARKER = 0, GETTING_BRIGHTER = 1 }; 9 | 10 | CRGB makeBrighter( const CRGB& color, fract8 howMuchBrighter) 11 | { 12 | CRGB incrementalColor = color; 13 | incrementalColor.nscale8( howMuchBrighter); 14 | return color + incrementalColor; 15 | } 16 | 17 | CRGB makeDarker( const CRGB& color, fract8 howMuchDarker) 18 | { 19 | CRGB newcolor = color; 20 | newcolor.nscale8( 255 - howMuchDarker); 21 | return newcolor; 22 | } 23 | 24 | // Compact implementation of 25 | // the directionFlags array, using just one BIT of RAM 26 | // per pixel. This requires a bunch of bit wrangling, 27 | // but conserves precious RAM. The cost is a few 28 | // cycles and about 100 bytes of flash program memory. 29 | uint8_t directionFlags[ (NUM_LEDS + 7) / 8]; 30 | 31 | bool getPixelDirection( uint16_t i) 32 | { 33 | uint16_t index = i / 8; 34 | uint8_t bitNum = i & 0x07; 35 | 36 | uint8_t andMask = 1 << bitNum; 37 | return (directionFlags[index] & andMask) != 0; 38 | } 39 | 40 | void setPixelDirection( uint16_t i, bool dir) 41 | { 42 | uint16_t index = i / 8; 43 | uint8_t bitNum = i & 0x07; 44 | 45 | uint8_t orMask = 1 << bitNum; 46 | uint8_t andMask = 255 - orMask; 47 | uint8_t value = directionFlags[index] & andMask; 48 | if ( dir ) { 49 | value += orMask; 50 | } 51 | directionFlags[index] = value; 52 | } 53 | 54 | void brightenOrDarkenEachPixel( fract8 fadeUpAmount, fract8 fadeDownAmount) 55 | { 56 | for ( uint16_t i = 0; i < NUM_LEDS; i++) { 57 | if ( getPixelDirection(i) == GETTING_DARKER) { 58 | // This pixel is getting darker 59 | leds[i] = makeDarker( leds[i], fadeDownAmount); 60 | } else { 61 | // This pixel is getting brighter 62 | leds[i] = makeBrighter( leds[i], fadeUpAmount); 63 | // now check to see if we've maxxed out the brightness 64 | if ( leds[i].r == 255 || leds[i].g == 255 || leds[i].b == 255) { 65 | // if so, turn around and start getting darker 66 | setPixelDirection(i, GETTING_DARKER); 67 | } 68 | } 69 | } 70 | } 71 | 72 | void colortwinkles() 73 | { 74 | EVERY_N_MILLIS(30) 75 | { 76 | // Make each pixel brighter or darker, depending on 77 | // its 'direction' flag. 78 | brightenOrDarkenEachPixel( FADE_IN_SPEED, FADE_OUT_SPEED); 79 | 80 | // Now consider adding a new random twinkle 81 | if ( random8() < DENSITY ) { 82 | int pos = random16(NUM_LEDS); 83 | if ( !leds[pos]) { 84 | leds[pos] = ColorFromPalette( gCurrentPalette, random8(), STARTING_BRIGHTNESS, NOBLEND); 85 | setPixelDirection(pos, GETTING_BRIGHTER); 86 | } 87 | } 88 | } 89 | } 90 | 91 | void cloudTwinkles() 92 | { 93 | gCurrentPalette = CloudColors_p; // Blues and whites! 94 | colortwinkles(); 95 | } 96 | 97 | void rainbowTwinkles() 98 | { 99 | gCurrentPalette = RainbowColors_p; 100 | colortwinkles(); 101 | } 102 | 103 | void snowTwinkles() 104 | { 105 | CRGB w(85, 85, 85), W(CRGB::White); 106 | 107 | gCurrentPalette = CRGBPalette16( W, W, W, W, w, w, w, w, w, w, w, w, w, w, w, w ); 108 | colortwinkles(); 109 | } 110 | 111 | void incandescentTwinkles() 112 | { 113 | CRGB l(0xE1A024); 114 | 115 | gCurrentPalette = CRGBPalette16( l, l, l, l, l, l, l, l, l, l, l, l, l, l, l, l ); 116 | colortwinkles(); 117 | } 118 | 119 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | outputDir=build 2 | arduinoDir=C:/Users/Jason/dev/arduino-1.6.12 3 | arduinoDataDir=C:/Users/Jason/AppData/Local/Arduino15 4 | arduinoSketchBookDir=C:/Users/Jason/Documents/Arduino 5 | 6 | mkdir -p $outputDir/ 7 | 8 | $arduinoDir/arduino-builder -compile -logger=machine -hardware $arduinoDir/hardware -hardware $arduinoDataDir/packages -hardware $arduinoSketchBookDir/hardware -tools $arduinoDir/tools-builder -tools $arduinoDir/hardware/tools/avr -tools $arduinoDataDir/packages -built-in-libraries $arduinoDir/libraries -libraries $arduinoSketchBookDir/libraries -fqbn=esp8266:esp8266:d1_mini:CpuFrequency=160,UploadSpeed=921600,FlashSize=4M3M -ide-version=10612 -build-path $outputDir/ -warnings=none -prefs=build.warn_data_percentage=75 -prefs=runtime.tools.esptool.path=$arduinoDataDir/packages/esp8266/tools/esptool/0.4.9 -prefs=runtime.tools.xtensa-lx106-elf-gcc.path=$arduinoDataDir/packages/esp8266/tools/xtensa-lx106-elf-gcc/1.20.0-26-gb404fb9-2 -prefs=runtime.tools.mkspiffs.path=$arduinoDataDir/packages/esp8266/tools/mkspiffs/0.1.2 -verbose tree-v2.ino 9 | -------------------------------------------------------------------------------- /data/css/simple.css: -------------------------------------------------------------------------------- 1 | /*body { 2 | padding-bottom: 70px; 3 | }*/ 4 | 5 | .grid-item-color { 6 | width: 4%; 7 | height: 32px; 8 | cursor: pointer; 9 | } 10 | 11 | .grid-item-pattern { 12 | width: 100px; 13 | height: 100px; 14 | margin: 5px; 15 | padding: 6px; 16 | white-space: normal; 17 | cursor: pointer; 18 | } 19 | -------------------------------------------------------------------------------- /data/css/styles.css: -------------------------------------------------------------------------------- 1 | body { padding-bottom: 70px; } 2 | -------------------------------------------------------------------------------- /data/edit.htm: -------------------------------------------------------------------------------- 1 | ESP Editor
4 | -------------------------------------------------------------------------------- /data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilgeniuslabs/eclipse-v2/dbeb696b232de5c152bb03bfafe816a5ed9fe7f4/data/favicon.ico -------------------------------------------------------------------------------- /data/fonts/glyphicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilgeniuslabs/eclipse-v2/dbeb696b232de5c152bb03bfafe816a5ed9fe7f4/data/fonts/glyphicons.eot -------------------------------------------------------------------------------- /data/fonts/glyphicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilgeniuslabs/eclipse-v2/dbeb696b232de5c152bb03bfafe816a5ed9fe7f4/data/fonts/glyphicons.ttf -------------------------------------------------------------------------------- /data/fonts/glyphicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilgeniuslabs/eclipse-v2/dbeb696b232de5c152bb03bfafe816a5ed9fe7f4/data/fonts/glyphicons.woff -------------------------------------------------------------------------------- /data/fonts/glyphicons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilgeniuslabs/eclipse-v2/dbeb696b232de5c152bb03bfafe816a5ed9fe7f4/data/fonts/glyphicons.woff2 -------------------------------------------------------------------------------- /data/images/atom196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilgeniuslabs/eclipse-v2/dbeb696b232de5c152bb03bfafe816a5ed9fe7f4/data/images/atom196.png -------------------------------------------------------------------------------- /data/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Eclipse v2 by Evil Genius Labs 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 54 | 55 |
56 | 57 |
58 | 59 |
60 | 61 |
62 | 63 | 208 | 209 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /data/js/app.js: -------------------------------------------------------------------------------- 1 | // used when hosting the site on the ESP8266 2 | var address = location.hostname; 3 | var urlBase = ""; 4 | 5 | // used when hosting the site somewhere other than the ESP8266 (handy for testing without waiting forever to upload to SPIFFS) 6 | // var address = "esp8266-1920f7.local"; 7 | // var urlBase = "http://" + address + "/"; 8 | 9 | var postColorTimer = {}; 10 | var postValueTimer = {}; 11 | 12 | var ignoreColorChange = false; 13 | 14 | // var ws = new ReconnectingWebSocket("ws://" + address + ":81/", ["arduino"]); 15 | // ws.debug = true; 16 | 17 | // ws.onmessage = function (evt) { 18 | // if (evt.data != null) { 19 | // var data = JSON.parse(evt.data); 20 | // if (data == null) return; 21 | // updateFieldValue(data.name, data.value); 22 | // } 23 | // }; 24 | 25 | $(document).ready(function () { 26 | $("#status").html("Connecting, please wait..."); 27 | 28 | $.get(urlBase + "all", function (data) { 29 | $("#status").html("Loading, please wait..."); 30 | 31 | $.each(data, function (index, field) { 32 | if (field.type == "Number") { 33 | addNumberField(field); 34 | } else if (field.type == "Boolean") { 35 | addBooleanField(field); 36 | } else if (field.type == "Select") { 37 | addSelectField(field); 38 | } else if (field.type == "Color") { 39 | addColorFieldPalette(field); 40 | addColorFieldPicker(field); 41 | } else if (field.type == "Section") { 42 | addSectionField(field); 43 | } else if (field.type == "String") { 44 | addStringField(field, false); 45 | } else if (field.type == "Label") { 46 | addStringField(field, true); 47 | } 48 | }); 49 | 50 | $(".minicolors").minicolors({ 51 | theme: "bootstrap", 52 | changeDelay: 200, 53 | control: "wheel", 54 | format: "rgb", 55 | inline: true, 56 | }); 57 | 58 | $("#status").html("Ready"); 59 | }).fail(function (errorThrown) { 60 | console.log({ errorThrown }); 61 | }); 62 | }); 63 | 64 | function addNumberField(field) { 65 | var template = $("#numberTemplate").clone(); 66 | 67 | template.attr("id", "form-group-" + field.name); 68 | template.attr("data-field-type", field.type); 69 | 70 | var label = template.find(".control-label"); 71 | label.attr("for", "input-" + field.name); 72 | label.text(field.label); 73 | 74 | var input = template.find(".input"); 75 | var slider = template.find(".slider"); 76 | slider.attr("id", "input-" + field.name); 77 | if (field.min) { 78 | input.attr("min", field.min); 79 | slider.attr("min", field.min); 80 | } 81 | if (field.max) { 82 | input.attr("max", field.max); 83 | slider.attr("max", field.max); 84 | } 85 | if (field.step) { 86 | input.attr("step", field.step); 87 | slider.attr("step", field.step); 88 | } 89 | input.val(field.value); 90 | slider.val(field.value); 91 | 92 | slider.on("change mousemove", function () { 93 | input.val($(this).val()); 94 | }); 95 | 96 | slider.on("change", function () { 97 | var value = $(this).val(); 98 | input.val(value); 99 | field.value = value; 100 | delayPostValue(field.name, value); 101 | }); 102 | 103 | input.on("change", function () { 104 | var value = $(this).val(); 105 | slider.val(value); 106 | field.value = value; 107 | delayPostValue(field.name, value); 108 | }); 109 | 110 | $("#form").append(template); 111 | } 112 | 113 | function addBooleanField(field) { 114 | var template = $("#booleanTemplate").clone(); 115 | 116 | template.attr("id", "form-group-" + field.name); 117 | template.attr("data-field-type", field.type); 118 | 119 | var label = template.find(".control-label"); 120 | label.attr("for", "btn-group-" + field.name); 121 | label.text(field.label); 122 | 123 | var btngroup = template.find(".btn-group"); 124 | btngroup.attr("id", "btn-group-" + field.name); 125 | 126 | var btnOn = template.find("#btnOn"); 127 | var btnOff = template.find("#btnOff"); 128 | 129 | btnOn.attr("id", "btnOn" + field.name); 130 | btnOff.attr("id", "btnOff" + field.name); 131 | 132 | btnOn.attr("class", field.value ? "btn btn-primary" : "btn btn-default"); 133 | btnOff.attr("class", !field.value ? "btn btn-primary" : "btn btn-default"); 134 | 135 | btnOn.click(function () { 136 | setBooleanFieldValue(field, btnOn, btnOff, 1); 137 | }); 138 | btnOff.click(function () { 139 | setBooleanFieldValue(field, btnOn, btnOff, 0); 140 | }); 141 | 142 | $("#form").append(template); 143 | } 144 | 145 | function addSelectField(field) { 146 | var template = $("#selectTemplate").clone(); 147 | 148 | template.attr("id", "form-group-" + field.name); 149 | template.attr("data-field-type", field.type); 150 | 151 | var id = "input-" + field.name; 152 | 153 | var label = template.find(".control-label"); 154 | label.attr("for", id); 155 | label.text(field.label); 156 | 157 | var select = template.find(".form-control"); 158 | select.attr("id", id); 159 | 160 | for (var i = 0; i < field.options.length; i++) { 161 | var optionText = field.options[i]; 162 | var option = $(""); 163 | option.text(optionText); 164 | option.attr("value", i); 165 | select.append(option); 166 | } 167 | 168 | select.val(field.value); 169 | 170 | select.change(function () { 171 | var value = template.find("#" + id + " option:selected").index(); 172 | postValue(field.name, value); 173 | }); 174 | 175 | var previousButton = template.find(".btn-previous"); 176 | var nextButton = template.find(".btn-next"); 177 | 178 | previousButton.click(function () { 179 | var value = template.find("#" + id + " option:selected").index(); 180 | var count = select.find("option").length; 181 | value--; 182 | if (value < 0) value = count - 1; 183 | select.val(value); 184 | postValue(field.name, value); 185 | }); 186 | 187 | nextButton.click(function () { 188 | var value = template.find("#" + id + " option:selected").index(); 189 | var count = select.find("option").length; 190 | value++; 191 | if (value >= count) value = 0; 192 | select.val(value); 193 | postValue(field.name, value); 194 | }); 195 | 196 | $("#form").append(template); 197 | } 198 | 199 | function addColorFieldPicker(field) { 200 | var template = $("#colorTemplate").clone(); 201 | 202 | template.attr("id", "form-group-" + field.name); 203 | template.attr("data-field-type", field.type); 204 | 205 | var id = "input-" + field.name; 206 | 207 | var input = template.find(".minicolors"); 208 | input.attr("id", id); 209 | 210 | if (!field.value.startsWith("rgb(")) field.value = "rgb(" + field.value; 211 | 212 | if (!field.value.endsWith(")")) field.value += ")"; 213 | 214 | input.val(field.value); 215 | 216 | var components = rgbToComponents(field.value); 217 | 218 | var redInput = template.find(".color-red-input"); 219 | var greenInput = template.find(".color-green-input"); 220 | var blueInput = template.find(".color-blue-input"); 221 | 222 | var redSlider = template.find(".color-red-slider"); 223 | var greenSlider = template.find(".color-green-slider"); 224 | var blueSlider = template.find(".color-blue-slider"); 225 | 226 | redInput.attr("id", id + "-red"); 227 | greenInput.attr("id", id + "-green"); 228 | blueInput.attr("id", id + "-blue"); 229 | 230 | redSlider.attr("id", id + "-red-slider"); 231 | greenSlider.attr("id", id + "-green-slider"); 232 | blueSlider.attr("id", id + "-blue-slider"); 233 | 234 | redInput.val(components.r); 235 | greenInput.val(components.g); 236 | blueInput.val(components.b); 237 | 238 | redSlider.val(components.r); 239 | greenSlider.val(components.g); 240 | blueSlider.val(components.b); 241 | 242 | redInput.on("change", function () { 243 | var value = $("#" + id).val(); 244 | var r = $(this).val(); 245 | var components = rgbToComponents(value); 246 | field.value = r + "," + components.g + "," + components.b; 247 | $("#" + id).minicolors("value", "rgb(" + field.value + ")"); 248 | redSlider.val(r); 249 | }); 250 | 251 | greenInput.on("change", function () { 252 | var value = $("#" + id).val(); 253 | var g = $(this).val(); 254 | var components = rgbToComponents(value); 255 | field.value = components.r + "," + g + "," + components.b; 256 | $("#" + id).minicolors("value", "rgb(" + field.value + ")"); 257 | greenSlider.val(g); 258 | }); 259 | 260 | blueInput.on("change", function () { 261 | var value = $("#" + id).val(); 262 | var b = $(this).val(); 263 | var components = rgbToComponents(value); 264 | field.value = components.r + "," + components.g + "," + b; 265 | $("#" + id).minicolors("value", "rgb(" + field.value + ")"); 266 | blueSlider.val(b); 267 | }); 268 | 269 | redSlider.on("change", function () { 270 | var value = $("#" + id).val(); 271 | var r = $(this).val(); 272 | var components = rgbToComponents(value); 273 | field.value = r + "," + components.g + "," + components.b; 274 | $("#" + id).minicolors("value", "rgb(" + field.value + ")"); 275 | redInput.val(r); 276 | }); 277 | 278 | greenSlider.on("change", function () { 279 | var value = $("#" + id).val(); 280 | var g = $(this).val(); 281 | var components = rgbToComponents(value); 282 | field.value = components.r + "," + g + "," + components.b; 283 | $("#" + id).minicolors("value", "rgb(" + field.value + ")"); 284 | greenInput.val(g); 285 | }); 286 | 287 | blueSlider.on("change", function () { 288 | var value = $("#" + id).val(); 289 | var b = $(this).val(); 290 | var components = rgbToComponents(value); 291 | field.value = components.r + "," + components.g + "," + b; 292 | $("#" + id).minicolors("value", "rgb(" + field.value + ")"); 293 | blueInput.val(b); 294 | }); 295 | 296 | redSlider.on("change mousemove", function () { 297 | redInput.val($(this).val()); 298 | }); 299 | 300 | greenSlider.on("change mousemove", function () { 301 | greenInput.val($(this).val()); 302 | }); 303 | 304 | blueSlider.on("change mousemove", function () { 305 | blueInput.val($(this).val()); 306 | }); 307 | 308 | input.on("change", function () { 309 | if (ignoreColorChange) return; 310 | 311 | var value = $(this).val(); 312 | var components = rgbToComponents(value); 313 | 314 | redInput.val(components.r); 315 | greenInput.val(components.g); 316 | blueInput.val(components.b); 317 | 318 | redSlider.val(components.r); 319 | greenSlider.val(components.g); 320 | blueSlider.val(components.b); 321 | 322 | field.value = components.r + "," + components.g + "," + components.b; 323 | delayPostColor(field.name, components); 324 | }); 325 | 326 | $("#form").append(template); 327 | } 328 | 329 | function addColorFieldPalette(field) { 330 | var template = $("#colorPaletteTemplate").clone(); 331 | 332 | var buttons = template.find(".btn-color"); 333 | 334 | var label = template.find(".control-label"); 335 | label.text(field.label); 336 | 337 | buttons.each(function (index, button) { 338 | $(button).click(function () { 339 | var rgb = $(this).css("backgroundColor"); 340 | var components = rgbToComponents(rgb); 341 | 342 | field.value = components.r + "," + components.g + "," + components.b; 343 | postColor(field.name, components); 344 | 345 | ignoreColorChange = true; 346 | var id = "#input-" + field.name; 347 | $(id).minicolors("value", "rgb(" + field.value + ")"); 348 | $(id + "-red").val(components.r); 349 | $(id + "-green").val(components.g); 350 | $(id + "-blue").val(components.b); 351 | $(id + "-red-slider").val(components.r); 352 | $(id + "-green-slider").val(components.g); 353 | $(id + "-blue-slider").val(components.b); 354 | ignoreColorChange = false; 355 | }); 356 | }); 357 | 358 | $("#form").append(template); 359 | } 360 | 361 | function addSectionField(field) { 362 | var template = $("#sectionTemplate").clone(); 363 | 364 | template.attr("id", "form-group-section-" + field.name); 365 | template.attr("data-field-type", field.type); 366 | 367 | $("#form").append(template); 368 | } 369 | 370 | function addStringField(field, readonly) { 371 | var template; 372 | 373 | if (readonly) { 374 | template = $("#labelTemplate").clone(); 375 | } else { 376 | template = $("#stringTemplate").clone(); 377 | } 378 | 379 | template.attr("id", "form-group-" + field.name); 380 | template.attr("data-field-type", field.type); 381 | 382 | var label = template.find(".control-label"); 383 | label.attr("for", "input-" + field.name); 384 | label.text(field.label); 385 | 386 | var input = template.find(".input"); 387 | input.val(field.value); 388 | 389 | if (!readonly) { 390 | input.on("change", function () { 391 | var value = $(this).val(); 392 | field.value = value; 393 | delayPostValue(field.name, value); 394 | }); 395 | } 396 | 397 | $("#form").append(template); 398 | } 399 | 400 | function updateFieldValue(name, value) { 401 | var group = $("#form-group-" + name); 402 | 403 | var type = group.attr("data-field-type"); 404 | 405 | if (type == "Number") { 406 | var input = group.find(".form-control"); 407 | input.val(value); 408 | } else if (type == "Boolean") { 409 | var btnOn = group.find("#btnOn" + name); 410 | var btnOff = group.find("#btnOff" + name); 411 | 412 | btnOn.attr("class", value ? "btn btn-primary" : "btn btn-default"); 413 | btnOff.attr("class", !value ? "btn btn-primary" : "btn btn-default"); 414 | } else if (type == "Select") { 415 | var select = group.find(".form-control"); 416 | select.val(value); 417 | } else if (type == "Color") { 418 | var input = group.find(".form-control"); 419 | input.val("rgb(" + value + ")"); 420 | } 421 | } 422 | 423 | function setBooleanFieldValue(field, btnOn, btnOff, value) { 424 | field.value = value; 425 | 426 | btnOn.attr("class", field.value ? "btn btn-primary" : "btn btn-default"); 427 | btnOff.attr("class", !field.value ? "btn btn-primary" : "btn btn-default"); 428 | 429 | postValue(field.name, field.value); 430 | } 431 | 432 | function postValue(name, value) { 433 | $("#status").html("Setting " + name + ": " + value + ", please wait..."); 434 | 435 | var body = { name: name, value: value }; 436 | 437 | $.post(urlBase + name + "?value=" + value, body, function (data) { 438 | if (data.name != null) { 439 | $("#status").html("Set " + name + ": " + data.name); 440 | } else { 441 | $("#status").html("Set " + name + ": " + data); 442 | } 443 | }); 444 | } 445 | 446 | function delayPostValue(name, value) { 447 | clearTimeout(postValueTimer); 448 | postValueTimer = setTimeout(function () { 449 | postValue(name, value); 450 | }, 300); 451 | } 452 | 453 | function postColor(name, value) { 454 | $("#status").html( 455 | "Setting " + 456 | name + 457 | ": " + 458 | value.r + 459 | "," + 460 | value.g + 461 | "," + 462 | value.b + 463 | ", please wait..." 464 | ); 465 | 466 | var body = { name: name, r: value.r, g: value.g, b: value.b }; 467 | 468 | $.post( 469 | urlBase + name + "?r=" + value.r + "&g=" + value.g + "&b=" + value.b, 470 | body, 471 | function (data) { 472 | $("#status").html("Set " + name + ": " + data); 473 | } 474 | ).fail(function (textStatus, errorThrown) { 475 | $("#status").html("Fail: " + textStatus + " " + errorThrown); 476 | }); 477 | } 478 | 479 | function delayPostColor(name, value) { 480 | clearTimeout(postColorTimer); 481 | postColorTimer = setTimeout(function () { 482 | postColor(name, value); 483 | }, 300); 484 | } 485 | 486 | function componentToHex(c) { 487 | var hex = c.toString(16); 488 | return hex.length == 1 ? "0" + hex : hex; 489 | } 490 | 491 | function rgbToHex(r, g, b) { 492 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 493 | } 494 | 495 | function rgbToComponents(rgb) { 496 | var components = {}; 497 | 498 | rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); 499 | components.r = parseInt(rgb[1]); 500 | components.g = parseInt(rgb[2]); 501 | components.b = parseInt(rgb[3]); 502 | 503 | return components; 504 | } 505 | -------------------------------------------------------------------------------- /data/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /data/js/jquery.minicolors.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery MiniColors: A tiny color picker built on jQuery 3 | * 4 | * Copyright: Cory LaViska for A Beautiful Site, LLC: http://www.abeautifulsite.net/ 5 | * 6 | * Contribute: https://github.com/claviska/jquery-minicolors 7 | * 8 | * @license: http://opensource.org/licenses/MIT 9 | * 10 | */ 11 | !function(i){"function"==typeof define&&define.amd?define(["jquery"],i):"object"==typeof exports?module.exports=i(require("jquery")):i(jQuery)}(function($){"use strict";function i(i,t){var o=$('
'),s=$.minicolors.defaults,a,n,r,c,l;if(!i.data("minicolors-initialized")){if(t=$.extend(!0,{},s,t),o.addClass("minicolors-theme-"+t.theme).toggleClass("minicolors-with-opacity",t.opacity).toggleClass("minicolors-no-data-uris",t.dataUris!==!0),void 0!==t.position&&$.each(t.position.split(" "),function(){o.addClass("minicolors-position-"+this)}),a="rgb"===t.format?t.opacity?"25":"20":t.keywords?"11":"7",i.addClass("minicolors-input").data("minicolors-initialized",!1).data("minicolors-settings",t).prop("size",a).wrap(o).after('
'),t.inline||(i.after(''),i.next(".minicolors-input-swatch").on("click",function(t){t.preventDefault(),i.focus()})),c=i.parent().find(".minicolors-panel"),c.on("selectstart",function(){return!1}).end(),t.swatches&&0!==t.swatches.length)for(t.swatches.length>7&&(t.swatches.length=7),c.addClass("minicolors-with-swatches"),n=$('
    ').appendTo(c),l=0;l').appendTo(n).data("swatch-color",t.swatches[l]).find(".minicolors-swatch-color").css({backgroundColor:y(r),opacity:r.a}),t.swatches[l]=r;t.inline&&i.parent().addClass("minicolors-inline"),e(i,!1),i.data("minicolors-initialized",!0)}}function t(i){var t=i.parent();i.removeData("minicolors-initialized").removeData("minicolors-settings").removeProp("size").removeClass("minicolors-input"),t.before(i).remove()}function o(i){var t=i.parent(),o=t.find(".minicolors-panel"),a=i.data("minicolors-settings");!i.data("minicolors-initialized")||i.prop("disabled")||t.hasClass("minicolors-inline")||t.hasClass("minicolors-focus")||(s(),t.addClass("minicolors-focus"),o.stop(!0,!0).fadeIn(a.showSpeed,function(){a.show&&a.show.call(i.get(0))}))}function s(){$(".minicolors-focus").each(function(){var i=$(this),t=i.find(".minicolors-input"),o=i.find(".minicolors-panel"),s=t.data("minicolors-settings");o.fadeOut(s.hideSpeed,function(){s.hide&&s.hide.call(t.get(0)),i.removeClass("minicolors-focus")})})}function a(i,t,o){var s=i.parents(".minicolors").find(".minicolors-input"),a=s.data("minicolors-settings"),r=i.find("[class$=-picker]"),e=i.offset().left,c=i.offset().top,l=Math.round(t.pageX-e),h=Math.round(t.pageY-c),d=o?a.animationSpeed:0,p,u,g,m;t.originalEvent.changedTouches&&(l=t.originalEvent.changedTouches[0].pageX-e,h=t.originalEvent.changedTouches[0].pageY-c),0>l&&(l=0),0>h&&(h=0),l>i.width()&&(l=i.width()),h>i.height()&&(h=i.height()),i.parent().is(".minicolors-slider-wheel")&&r.parent().is(".minicolors-grid")&&(p=75-l,u=75-h,g=Math.sqrt(p*p+u*u),m=Math.atan2(u,p),0>m&&(m+=2*Math.PI),g>75&&(g=75,l=75-75*Math.cos(m),h=75-75*Math.sin(m)),l=Math.round(l),h=Math.round(h)),i.is(".minicolors-grid")?r.stop(!0).animate({top:h+"px",left:l+"px"},d,a.animationEasing,function(){n(s,i)}):r.stop(!0).animate({top:h+"px"},d,a.animationEasing,function(){n(s,i)})}function n(i,t){function o(i,t){var o,s;return i.length&&t?(o=i.offset().left,s=i.offset().top,{x:o-t.offset().left+i.outerWidth()/2,y:s-t.offset().top+i.outerHeight()/2}):null}var s,a,n,e,l,h,d,p=i.val(),u=i.attr("data-opacity"),g=i.parent(),f=i.data("minicolors-settings"),v=g.find(".minicolors-input-swatch"),b=g.find(".minicolors-grid"),w=g.find(".minicolors-slider"),y=g.find(".minicolors-opacity-slider"),k=b.find("[class$=-picker]"),M=w.find("[class$=-picker]"),x=y.find("[class$=-picker]"),I=o(k,b),S=o(M,w),z=o(x,y);if(t.is(".minicolors-grid, .minicolors-slider, .minicolors-opacity-slider")){switch(f.control){case"wheel":e=b.width()/2-I.x,l=b.height()/2-I.y,h=Math.sqrt(e*e+l*l),d=Math.atan2(l,e),0>d&&(d+=2*Math.PI),h>75&&(h=75,I.x=69-75*Math.cos(d),I.y=69-75*Math.sin(d)),a=m(h/.75,0,100),s=m(180*d/Math.PI,0,360),n=m(100-Math.floor(S.y*(100/w.height())),0,100),p=C({h:s,s:a,b:n}),w.css("backgroundColor",C({h:s,s:a,b:100}));break;case"saturation":s=m(parseInt(I.x*(360/b.width()),10),0,360),a=m(100-Math.floor(S.y*(100/w.height())),0,100),n=m(100-Math.floor(I.y*(100/b.height())),0,100),p=C({h:s,s:a,b:n}),w.css("backgroundColor",C({h:s,s:100,b:n})),g.find(".minicolors-grid-inner").css("opacity",a/100);break;case"brightness":s=m(parseInt(I.x*(360/b.width()),10),0,360),a=m(100-Math.floor(I.y*(100/b.height())),0,100),n=m(100-Math.floor(S.y*(100/w.height())),0,100),p=C({h:s,s:a,b:n}),w.css("backgroundColor",C({h:s,s:a,b:100})),g.find(".minicolors-grid-inner").css("opacity",1-n/100);break;default:s=m(360-parseInt(S.y*(360/w.height()),10),0,360),a=m(Math.floor(I.x*(100/b.width())),0,100),n=m(100-Math.floor(I.y*(100/b.height())),0,100),p=C({h:s,s:a,b:n}),b.css("backgroundColor",C({h:s,s:100,b:100}))}u=f.opacity?parseFloat(1-z.y/y.height()).toFixed(2):1,r(i,p,u)}else v.find("span").css({backgroundColor:p,opacity:u}),c(i,p,u)}function r(i,t,o){var s,a=i.parent(),n=i.data("minicolors-settings"),r=a.find(".minicolors-input-swatch");n.opacity&&i.attr("data-opacity",o),"rgb"===n.format?(s=f(t)?u(t,!0):x(p(t,!0)),o=""===i.attr("data-opacity")?1:m(parseFloat(i.attr("data-opacity")).toFixed(2),0,1),(isNaN(o)||!n.opacity)&&(o=1),t=i.minicolors("rgbObject").a<=1&&s&&n.opacity?"rgba("+s.r+", "+s.g+", "+s.b+", "+parseFloat(o)+")":"rgb("+s.r+", "+s.g+", "+s.b+")"):(f(t)&&(t=w(t)),t=d(t,n.letterCase)),i.val(t),r.find("span").css({backgroundColor:t,opacity:o}),c(i,t,o)}function e(i,t){var o,s,a,n,r,e,l,h,b,y,M=i.parent(),x=i.data("minicolors-settings"),I=M.find(".minicolors-input-swatch"),S=M.find(".minicolors-grid"),z=M.find(".minicolors-slider"),F=M.find(".minicolors-opacity-slider"),D=S.find("[class$=-picker]"),T=z.find("[class$=-picker]"),j=F.find("[class$=-picker]");switch(f(i.val())?(o=w(i.val()),r=m(parseFloat(v(i.val())).toFixed(2),0,1),r&&i.attr("data-opacity",r)):o=d(p(i.val(),!0),x.letterCase),o||(o=d(g(x.defaultValue,!0),x.letterCase)),s=k(o),n=x.keywords?$.map(x.keywords.split(","),function(i){return $.trim(i.toLowerCase())}):[],e=""!==i.val()&&$.inArray(i.val().toLowerCase(),n)>-1?d(i.val()):f(i.val())?u(i.val()):o,t||i.val(e),x.opacity&&(a=""===i.attr("data-opacity")?1:m(parseFloat(i.attr("data-opacity")).toFixed(2),0,1),isNaN(a)&&(a=1),i.attr("data-opacity",a),I.find("span").css("opacity",a),h=m(F.height()-F.height()*a,0,F.height()),j.css("top",h+"px")),"transparent"===i.val().toLowerCase()&&I.find("span").css("opacity",0),I.find("span").css("backgroundColor",o),x.control){case"wheel":b=m(Math.ceil(.75*s.s),0,S.height()/2),y=s.h*Math.PI/180,l=m(75-Math.cos(y)*b,0,S.width()),h=m(75-Math.sin(y)*b,0,S.height()),D.css({top:h+"px",left:l+"px"}),h=150-s.b/(100/S.height()),""===o&&(h=0),T.css("top",h+"px"),z.css("backgroundColor",C({h:s.h,s:s.s,b:100}));break;case"saturation":l=m(5*s.h/12,0,150),h=m(S.height()-Math.ceil(s.b/(100/S.height())),0,S.height()),D.css({top:h+"px",left:l+"px"}),h=m(z.height()-s.s*(z.height()/100),0,z.height()),T.css("top",h+"px"),z.css("backgroundColor",C({h:s.h,s:100,b:s.b})),M.find(".minicolors-grid-inner").css("opacity",s.s/100);break;case"brightness":l=m(5*s.h/12,0,150),h=m(S.height()-Math.ceil(s.s/(100/S.height())),0,S.height()),D.css({top:h+"px",left:l+"px"}),h=m(z.height()-s.b*(z.height()/100),0,z.height()),T.css("top",h+"px"),z.css("backgroundColor",C({h:s.h,s:s.s,b:100})),M.find(".minicolors-grid-inner").css("opacity",1-s.b/100);break;default:l=m(Math.ceil(s.s/(100/S.width())),0,S.width()),h=m(S.height()-Math.ceil(s.b/(100/S.height())),0,S.height()),D.css({top:h+"px",left:l+"px"}),h=m(z.height()-s.h/(360/z.height()),0,z.height()),T.css("top",h+"px"),S.css("backgroundColor",C({h:s.h,s:100,b:100}))}i.data("minicolors-initialized")&&c(i,e,a)}function c(i,t,o){var s=i.data("minicolors-settings"),a=i.data("minicolors-lastChange"),n,r,e;if(!a||a.value!==t||a.opacity!==o){if(i.data("minicolors-lastChange",{value:t,opacity:o}),s.swatches&&0!==s.swatches.length){for(n=f(t)?u(t,!0):x(t),r=-1,e=0;ei&&(i=t),i>o&&(i=o),i}function f(i){var t=i.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);return t&&4===t.length?!0:!1}function v(i){return i=i.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+(\.\d{1,2})?|\.\d{1,2})[\s+]?/i),i&&6===i.length?i[4]:"1"}function b(i){var t={},o=Math.round(i.h),s=Math.round(255*i.s/100),a=Math.round(255*i.b/100);if(0===s)t.r=t.g=t.b=a;else{var n=a,r=(255-s)*a/255,e=(n-r)*(o%60)/60;360===o&&(o=0),60>o?(t.r=n,t.b=r,t.g=r+e):120>o?(t.g=n,t.b=r,t.r=n-e):180>o?(t.g=n,t.r=r,t.b=r+e):240>o?(t.b=n,t.r=r,t.g=n-e):300>o?(t.b=n,t.g=r,t.r=r+e):360>o?(t.r=n,t.g=r,t.b=n-e):(t.r=0,t.g=0,t.b=0)}return{r:Math.round(t.r),g:Math.round(t.g),b:Math.round(t.b)}}function w(i){return i=i.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i),i&&4===i.length?"#"+("0"+parseInt(i[1],10).toString(16)).slice(-2)+("0"+parseInt(i[2],10).toString(16)).slice(-2)+("0"+parseInt(i[3],10).toString(16)).slice(-2):""}function y(i){var t=[i.r.toString(16),i.g.toString(16),i.b.toString(16)];return $.each(t,function(i,o){1===o.length&&(t[i]="0"+o)}),"#"+t.join("")}function C(i){return y(b(i))}function k(i){var t=M(x(i));return 0===t.s&&(t.h=360),t}function M(i){var t={h:0,s:0,b:0},o=Math.min(i.r,i.g,i.b),s=Math.max(i.r,i.g,i.b),a=s-o;return t.b=s,t.s=0!==s?255*a/s:0,0!==t.s?i.r===s?t.h=(i.g-i.b)/a:i.g===s?t.h=2+(i.b-i.r)/a:t.h=4+(i.r-i.g)/a:t.h=-1,t.h*=60,t.h<0&&(t.h+=360),t.s*=100/255,t.b*=100/255,t}function x(i){return i=parseInt(i.indexOf("#")>-1?i.substring(1):i,16),{r:i>>16,g:(65280&i)>>8,b:255&i}}$.minicolors={defaults:{animationSpeed:50,animationEasing:"swing",change:null,changeDelay:0,control:"hue",dataUris:!0,defaultValue:"",format:"hex",hide:null,hideSpeed:100,inline:!1,keywords:"",letterCase:"lowercase",opacity:!1,position:"bottom left",show:null,showSpeed:100,theme:"default",swatches:[]}},$.extend($.fn,{minicolors:function(a,n){switch(a){case"destroy":return $(this).each(function(){t($(this))}),$(this);case"hide":return s(),$(this);case"opacity":return void 0===n?$(this).attr("data-opacity"):($(this).each(function(){e($(this).attr("data-opacity",n))}),$(this));case"rgbObject":return l($(this),"rgbaObject"===a);case"rgbString":case"rgbaString":return h($(this),"rgbaString"===a);case"settings":return void 0===n?$(this).data("minicolors-settings"):($(this).each(function(){var i=$(this).data("minicolors-settings")||{};t($(this)),$(this).minicolors($.extend(!0,i,n))}),$(this));case"show":return o($(this).eq(0)),$(this);case"value":return void 0===n?$(this).val():($(this).each(function(){"object"==typeof n&&null!==typeof n?(n.opacity&&$(this).attr("data-opacity",m(n.opacity,0,1)),n.color&&$(this).val(n.color)):$(this).val(n),e($(this))}),$(this));default:return"create"!==a&&(n=a),$(this).each(function(){i($(this),n)}),$(this)}}}),$(document).on("mousedown.minicolors touchstart.minicolors",function(i){$(i.target).parents().add(i.target).hasClass("minicolors")||s()}).on("mousedown.minicolors touchstart.minicolors",".minicolors-grid, .minicolors-slider, .minicolors-opacity-slider",function(i){var t=$(this);i.preventDefault(),$(document).data("minicolors-target",t),a(t,i,!0)}).on("mousemove.minicolors touchmove.minicolors",function(i){var t=$(document).data("minicolors-target");t&&a(t,i)}).on("mouseup.minicolors touchend.minicolors",function(){$(this).removeData("minicolors-target")}).on("click.minicolors",".minicolors-swatches li",function(i){i.preventDefault();var t=$(this),o=t.parents(".minicolors").find(".minicolors-input"),s=t.data("swatch-color");r(o,s,v(s)),e(o)}).on("mousedown.minicolors touchstart.minicolors",".minicolors-input-swatch",function(i){var t=$(this).parent().find(".minicolors-input");i.preventDefault(),o(t)}).on("focus.minicolors",".minicolors-input",function(){var i=$(this);i.data("minicolors-initialized")&&o(i)}).on("blur.minicolors",".minicolors-input",function(){var i=$(this),t=i.data("minicolors-settings"),o,s,a,n,r;i.data("minicolors-initialized")&&(o=t.keywords?$.map(t.keywords.split(","),function(i){return $.trim(i.toLowerCase())}):[],""!==i.val()&&$.inArray(i.val().toLowerCase(),o)>-1?r=i.val():(f(i.val())?a=u(i.val(),!0):(s=p(i.val(),!0),a=s?x(s):null),r=null===a?t.defaultValue:"rgb"===t.format?u(t.opacity?"rgba("+a.r+","+a.g+","+a.b+","+i.attr("data-opacity")+")":"rgb("+a.r+","+a.g+","+a.b+")"):y(a)),n=t.opacity?i.attr("data-opacity"):1,"transparent"===r.toLowerCase()&&(n=0),i.closest(".minicolors").find(".minicolors-input-swatch > span").css("opacity",n),i.val(r),""===i.val()&&i.val(g(t.defaultValue,!0)),i.val(d(i.val(),t.letterCase)))}).on("keydown.minicolors",".minicolors-input",function(i){var t=$(this);if(t.data("minicolors-initialized"))switch(i.keyCode){case 9:s();break;case 13:case 27:s(),t.blur()}}).on("keyup.minicolors",".minicolors-input",function(){var i=$(this);i.data("minicolors-initialized")&&e(i,!0)}).on("paste.minicolors",".minicolors-input",function(){var i=$(this);i.data("minicolors-initialized")&&setTimeout(function(){e(i,!0)},1)})}); -------------------------------------------------------------------------------- /data/js/r-websocket.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a}); 2 | -------------------------------------------------------------------------------- /data/js/simple.js: -------------------------------------------------------------------------------- 1 | // used when hosting the site on the ESP8266 2 | var address = location.hostname; 3 | var urlBase = ""; 4 | 5 | // used when hosting the site somewhere other than the ESP8266 (handy for testing without waiting forever to upload to SPIFFS) 6 | // var address = "192.168.1.13"; 7 | // var urlBase = "http://" + address + "/"; 8 | 9 | var postColorTimer = {}; 10 | var postValueTimer = {}; 11 | 12 | var ignoreColorChange = false; 13 | 14 | var patterns = [ 15 | "Pride", 16 | "Color Waves", 17 | 18 | "Rainbow Twinkles", 19 | "Snow Twinkles", 20 | "Cloud Twinkles", 21 | "Incandescent Twinkles", 22 | 23 | "Retro C9 Twinkles", 24 | "Red & White Twinkles", 25 | "Blue & White Twinkles", 26 | "Red, Green & White Twinkles", 27 | "Fairy Light Twinkles", 28 | "Snow 2 Twinkles", 29 | "Holly Twinkles", 30 | "Ice Twinkles", 31 | "Party Twinkles", 32 | "Forest Twinkles", 33 | "Lava Twinkles", 34 | "Fire Twinkles", 35 | "Cloud 2 Twinkles", 36 | "Ocean Twinkles", 37 | 38 | "Rainbow", 39 | "Rainbow With Glitter", 40 | "Solid Rainbow", 41 | "Confetti", 42 | "Sinelon", 43 | "Beat", 44 | "Juggle", 45 | "Fire", 46 | "Water" 47 | ]; 48 | 49 | var ws = new ReconnectingWebSocket('ws://' + address + ':81/', ['arduino']); 50 | ws.debug = true; 51 | 52 | ws.onmessage = function(evt) { 53 | if(evt.data != null) 54 | { 55 | var data = JSON.parse(evt.data); 56 | if(data == null) return; 57 | switch(data.name) { 58 | case "power": 59 | if(data.value == 1) { 60 | $("#btnOn").attr("class", "btn btn-primary"); 61 | $("#btnOff").attr("class", "btn btn-default"); 62 | } else { 63 | $("#btnOff").attr("class", "btn btn-primary"); 64 | $("#btnOn").attr("class", "btn btn-default"); 65 | } 66 | break; 67 | 68 | case "pattern": 69 | $(".grid-item-pattern").attr("class", "grid-item-pattern btn btn-default"); 70 | $("#pattern-button-" + data.value).attr("class", "grid-item-pattern btn btn-primary"); 71 | break; 72 | } 73 | } 74 | } 75 | 76 | $(document).ready(function() { 77 | $("#status").html("Connecting, please wait..."); 78 | 79 | $.get(urlBase + "all", function(data) { 80 | $("#status").html("Loading, please wait..."); 81 | 82 | $.each(data, function(index, field) { 83 | switch (field.name) { 84 | case "power": 85 | if(field.value == 1) { 86 | $("#btnOn").attr("class", "btn btn-primary"); 87 | } else { 88 | $("#btnOff").attr("class", "btn btn-primary"); 89 | } 90 | break; 91 | 92 | case "pattern": 93 | addPatternButtons(field); 94 | break; 95 | } 96 | }); 97 | }); 98 | 99 | addColorButtons(); 100 | 101 | $("#btnOn").click(function() { 102 | postValue("power", 1); 103 | $("#btnOn").attr("class", "btn btn-primary"); 104 | $("#btnOff").attr("class", "btn btn-default"); 105 | }); 106 | 107 | $("#btnOff").click(function() { 108 | postValue("power", 0); 109 | $("#btnOff").attr("class", "btn btn-primary"); 110 | $("#btnOn").attr("class", "btn btn-default"); 111 | }); 112 | 113 | $("#status").html("Ready"); 114 | }); 115 | 116 | function addColorButtons() { 117 | var hues = 25; 118 | var hueStep = 360 / hues; 119 | 120 | var levels = 10; 121 | var levelStep = 60 / levels; 122 | 123 | for(var l = 20; l < 80; l += levelStep) { 124 | for(var h = 0; h < hues; h++) { 125 | addColorButton(h * hueStep, 100, l); 126 | } 127 | } 128 | 129 | $('.grid-color').isotope({ 130 | itemSelector: '.grid-item-color', 131 | layoutMode: 'fitRows' 132 | }); 133 | 134 | } 135 | 136 | var colorButtonIndex = 0; 137 | 138 | function addColorButton(h, s, l) { 139 | var color = "hsla(" + h + ", " + s + "%, " + l + "%, 1)" 140 | var template = $("#colorButtonTemplate").clone(); 141 | template.attr("id", "color-button-" + colorButtonIndex++); 142 | template.css("background-color", color); 143 | template.click(function() { 144 | var rgb = $(this).css('backgroundColor'); 145 | var components = rgbToComponents(rgb); 146 | 147 | $(".grid-item-color").css("border", "none"); 148 | $(this).css("border", "1px solid"); 149 | 150 | postColor("solidColor", components); 151 | }); 152 | 153 | $("#colorButtonsRow").append(template); 154 | } 155 | 156 | function addPatternButtons(patternField) { 157 | $.each(patternField.options, function(index, pattern) { 158 | if($.inArray(pattern, patterns) == -1) 159 | return; 160 | 161 | var template = $("#patternButtonTemplate").clone(); 162 | template.attr("id", "pattern-button-" + index); 163 | template.text(pattern); 164 | template.click(function() { 165 | postValue("patternName", pattern); 166 | $(".grid-item-color").css("border", "none"); 167 | $(".grid-item-pattern").attr("class", "grid-item-pattern btn btn-default"); 168 | $(this).attr("class", "grid-item-pattern btn btn-primary"); 169 | }); 170 | 171 | $("#patternGrid").append(template); 172 | }); 173 | 174 | $('.grid-pattern').isotope({ 175 | itemSelector: '.grid-item-pattern', 176 | layoutMode: 'fitRows' 177 | }); 178 | 179 | $("#pattern-button-" + patternField.value).attr("class", "grid-item-pattern btn btn-primary"); 180 | } 181 | 182 | function postValue(name, value) { 183 | $("#status").html("Setting " + name + ": " + value + ", please wait..."); 184 | 185 | var body = { name: name, value: value }; 186 | 187 | $.post(urlBase + name, body, function(data) { 188 | if (data.name != null) { 189 | $("#status").html("Set " + name + ": " + data.name); 190 | } else { 191 | $("#status").html("Set " + name + ": " + data); 192 | } 193 | }); 194 | } 195 | 196 | function delayPostValue(name, value) { 197 | clearTimeout(postValueTimer); 198 | postValueTimer = setTimeout(function() { 199 | postValue(name, value); 200 | }, 300); 201 | } 202 | 203 | function postColor(name, value) { 204 | $("#status").html("Setting " + name + ": " + value.r + "," + value.g + "," + value.b + ", please wait..."); 205 | 206 | var body = { name: name, r: value.r, g: value.g, b: value.b }; 207 | 208 | $.post(urlBase + name + "?r=" + value.r + "&g=" + value.g + "&b=" + value.b, body, function(data) { 209 | $("#status").html("Set " + name + ": " + data); 210 | }) 211 | .fail(function(textStatus, errorThrown) { $("#status").html("Fail: " + textStatus + " " + errorThrown); }); 212 | } 213 | 214 | function delayPostColor(name, value) { 215 | clearTimeout(postColorTimer); 216 | postColorTimer = setTimeout(function() { 217 | postColor(name, value); 218 | }, 300); 219 | } 220 | 221 | function componentToHex(c) { 222 | var hex = c.toString(16); 223 | return hex.length == 1 ? "0" + hex : hex; 224 | } 225 | 226 | function rgbToHex(r, g, b) { 227 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 228 | } 229 | 230 | function rgbToComponents(rgb) { 231 | var components = {}; 232 | 233 | rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); 234 | components.r = parseInt(rgb[1]); 235 | components.g = parseInt(rgb[2]); 236 | components.b = parseInt(rgb[3]); 237 | 238 | return components; 239 | } 240 | -------------------------------------------------------------------------------- /data/simple.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Eclipse v2 by Evil Genius Labs 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    24 | 25 |
    26 | 27 | 28 |
    29 | 30 |
    31 | 32 |
    33 | 34 |
    35 | 36 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /deployapp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # upload the web app to the board 3 | 4 | # investigate using a single file to bring in the defaults 5 | # ip=$(< ip.txt) 6 | 7 | ip=${1:-"192.168.0.100"} 8 | url="http://$ip/edit" 9 | 10 | declare -a filenames=("css/styles.css" 11 | "js/app.js" 12 | "index.htm" 13 | "css/simple.css" 14 | "js/simple.js" 15 | "simple.htm") 16 | 17 | # "images/atom196.png" 18 | # "favicon.ico" 19 | 20 | for filename in "${filenames[@]}" 21 | do 22 | # add --trace-ascii curl.log for logging 23 | 24 | gzip -kf data/$filename 25 | 26 | echo $filename 27 | curl --form "file=@data/$filename.gz;filename=$filename.gz" $url 28 | 29 | rm -f data/$filename.gz 30 | done 31 | -------------------------------------------------------------------------------- /deployfirmware.sh: -------------------------------------------------------------------------------- 1 | outputDir=build 2 | binFilename=tree-v2.ino.bin 3 | ip=${1:-"192.168.0.100"} 4 | url="http://$ip/update" 5 | 6 | curl -v --form "file=@$outputDir/$binFilename;filename=$binFilename" $url 7 | -------------------------------------------------------------------------------- /power.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: ./power.sh 192.168.1.243 [on|off] 3 | 4 | ip=${1:-"192.168.0.100"} 5 | value=${2:-"off"} 6 | 7 | if [ $value = "off" ]; then 8 | curl -X POST http://$ip/power?value=0 9 | else 10 | curl -X POST http://$ip/power?value=1 11 | fi 12 | -------------------------------------------------------------------------------- /uploadfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # upload the web app to the board 3 | 4 | # investigate using a single file to bring in the defaults 5 | # ip=$(< ip.txt) 6 | 7 | ip=${1:-"192.168.0.100"} 8 | url="http://$ip/edit" 9 | filename=$2 10 | 11 | # add --trace-ascii curl.log for logging 12 | 13 | gzip -kf data/$filename 14 | 15 | echo $filename 16 | curl --form "file=@data/$filename.gz;filename=$filename.gz" $url 17 | 18 | rm -f data/$filename.gz 19 | --------------------------------------------------------------------------------