├── .gitattributes ├── .gitignore ├── Griswold-LED-Controller.ino ├── README.md ├── colormodes.h ├── data-moved_aside ├── graphs.htm └── graphs.js.gz ├── data ├── edit.htm.gz ├── favicon.ico ├── index.htm ├── jscolor.js ├── materialize.min.js └── palettes │ ├── 35_blue_waves.bin │ ├── 37_waves.bin │ ├── Green_White_Red.bin │ ├── Lindaa07.bin │ ├── beading.bin │ ├── bhw1_29.bin │ ├── bhw1_greenie.bin │ ├── bhw1_purpgreen.bin │ ├── bhw1_purplered.bin │ ├── bhw1_sunconure.bin │ ├── bhw2_xmas.bin │ ├── blueeyedgal.bin │ ├── brightsong2.bin │ ├── bud2.bin │ ├── christmas-candy.bin │ ├── faewing3.bin │ ├── goddess-moon.bin │ ├── lkmtch00.bin │ ├── patriot.bin │ ├── plasma.bin │ ├── prism.bin │ ├── sls.bin │ ├── sorcery-2.bin │ ├── starrynite.bin │ ├── twilight.bin │ ├── usa1.bin │ ├── water1.bin │ └── wintercolors.bin ├── definitions.h ├── eepromsettings.h ├── palette_convert ├── .gitignore └── palette_convert.py ├── palettes.h ├── request_handlers.h ├── spiffs_webserver.h └── upload these via file mgr to ESP8266 └── color-selector.htm /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /Griswold-LED-Controller.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 @jake-b, @russp81, @toblum 2 | // Griswold LED Lighting Controller 3 | 4 | // Griswold is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as 6 | // published by the Free Software Foundation, either version 3 of 7 | // the License, or (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with this program. If not, see . 16 | 17 | // Griswold is a fork of the LEDLAMP project at 18 | // https://github.com/russp81/LEDLAMP_FASTLEDs 19 | 20 | // The LEDLAMP project is a fork of the McLighting Project at 21 | // https://github.com/toblum/McLighting 22 | 23 | // *************************************************************************** 24 | // Load libraries for: WebServer / WiFiManager / WebSockets 25 | // *************************************************************************** 26 | #include //https://github.com/esp8266/Arduino 27 | 28 | // needed for library WiFiManager 29 | #include 30 | #include 31 | #include //https://github.com/tzapu/WiFiManager 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | #include "RemoteDebug.h" //https://github.com/JoaoLopesF/RemoteDebug 40 | 41 | #include //https://github.com/Links2004/arduinoWebSockets 42 | #include 43 | 44 | // *************************************************************************** 45 | // Sub-modules of this application 46 | // *************************************************************************** 47 | #include "definitions.h" 48 | #include "eepromsettings.h" 49 | #include "palettes.h" 50 | #include "colormodes.h" 51 | 52 | // *************************************************************************** 53 | // Instanciate HTTP(80) / WebSockets(81) Server 54 | // *************************************************************************** 55 | ESP8266WebServer server(80); 56 | WebSocketsServer webSocket = WebSocketsServer(81); 57 | 58 | // *************************************************************************** 59 | // Load library "ticker" for blinking status led 60 | // *************************************************************************** 61 | Ticker ticker; 62 | 63 | void tick() { 64 | // toggle state 65 | int state = digitalRead(BUILTIN_LED); // get the current state of GPIO1 pin 66 | digitalWrite(BUILTIN_LED, !state); // set pin to the opposite state 67 | } 68 | 69 | // *************************************************************************** 70 | // Callback for WiFiManager library when config mode is entered 71 | // *************************************************************************** 72 | // gets called when WiFiManager enters configuration mode 73 | void configModeCallback(WiFiManager *myWiFiManager) { 74 | DBG_OUTPUT_PORT.println("Entered config mode"); 75 | DBG_OUTPUT_PORT.println(WiFi.softAPIP()); 76 | // if you used auto generated SSID, print it 77 | DBG_OUTPUT_PORT.println(myWiFiManager->getConfigPortalSSID()); 78 | // entered config mode, make led toggle faster 79 | ticker.attach(0.2, tick); 80 | } 81 | 82 | // *************************************************************************** 83 | // Include: Webserver & Request Handlers 84 | // *************************************************************************** 85 | #include "spiffs_webserver.h" // must be included after the 'server' object 86 | #include "request_handlers.h" // is declared. 87 | 88 | // *************************************************************************** 89 | // MAIN 90 | // *************************************************************************** 91 | void setup() { 92 | 93 | // Generate a pseduo-unique hostname 94 | char hostname[strlen(HOSTNAME_PREFIX)+6]; 95 | uint16_t chipid = ESP.getChipId() & 0xFFFF; 96 | sprintf(hostname, "%s-%04x",HOSTNAME_PREFIX, chipid); 97 | 98 | #ifdef REMOTE_DEBUG 99 | Debug.begin(hostname); // Initiaze the telnet server - hostname is the used 100 | // in MDNS.begin 101 | Debug.setResetCmdEnabled(true); // Enable the reset command 102 | #endif 103 | 104 | // *************************************************************************** 105 | // Setup: EEPROM 106 | // *************************************************************************** 107 | initSettings(); // setting loaded from EEPROM or defaults if fail 108 | printSettings(); 109 | 110 | ///*** Random Seed*** 111 | randomSeed(analogRead(0)); 112 | 113 | //********color palette setup stuff**************** 114 | currentPalette = RainbowColors_p; 115 | loadPaletteFromFile(settings.palette_ndx, &targetPalette); 116 | currentBlending = LINEARBLEND; 117 | //************************************************** 118 | 119 | #ifndef REMOTE_DEBUG 120 | DBG_OUTPUT_PORT.begin(115200); 121 | #endif 122 | DBG_OUTPUT_PORT.printf("system_get_cpu_freq: %d\n", system_get_cpu_freq()); 123 | 124 | // set builtin led pin as output 125 | pinMode(BUILTIN_LED, OUTPUT); 126 | // start ticker with 0.5 because we start in AP mode and try to connect 127 | ticker.attach(0.6, tick); 128 | 129 | // *************************************************************************** 130 | // Setup: WiFiManager 131 | // *************************************************************************** 132 | // Local intialization. Once its business is done, there is no need to keep it 133 | // around 134 | WiFiManager wifiManager; 135 | // reset settings - for testing 136 | // wifiManager.resetSettings(); 137 | 138 | // set callback that gets called when connecting to previous WiFi fails, and 139 | // enters Access Point mode 140 | wifiManager.setAPCallback(configModeCallback); 141 | 142 | // fetches ssid and pass and tries to connect 143 | // if it does not connect it starts an access point with the specified name 144 | // here "AutoConnectAP" 145 | // and goes into a blocking loop awaiting configuration 146 | if (!wifiManager.autoConnect(hostname)) { 147 | DBG_OUTPUT_PORT.println("failed to connect and hit timeout"); 148 | // reset and try again, or maybe put it to deep sleep 149 | ESP.reset(); 150 | delay(1000); 151 | } 152 | 153 | // if you get here you have connected to the WiFi 154 | DBG_OUTPUT_PORT.println("connected...yeey :)"); 155 | ticker.detach(); 156 | // keep LED on 157 | digitalWrite(BUILTIN_LED, LOW); 158 | 159 | // *************************************************************************** 160 | // Setup: WiFiManager 161 | // *************************************************************************** 162 | ArduinoOTA.setHostname(hostname); 163 | ArduinoOTA.onStart([]() { 164 | String type; 165 | if (ArduinoOTA.getCommand() == U_FLASH) 166 | type = "sketch"; 167 | else 168 | type = "filesystem"; 169 | 170 | SPIFFS.end(); // unmount SPIFFS for update. 171 | DBG_OUTPUT_PORT.println("Start updating " + type); 172 | }); 173 | ArduinoOTA.onEnd([]() { 174 | DBG_OUTPUT_PORT.println("\nEnd... remounting SPIFFS"); 175 | SPIFFS.begin(); 176 | paletteCount = getPaletteCount(); 177 | }); 178 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 179 | DBG_OUTPUT_PORT.printf("Progress: %u%%\r", (progress / (total / 100))); 180 | }); 181 | ArduinoOTA.onError([](ota_error_t error) { 182 | DBG_OUTPUT_PORT.printf("Error[%u]: ", error); 183 | if (error == OTA_AUTH_ERROR) 184 | DBG_OUTPUT_PORT.println("Auth Failed"); 185 | else if (error == OTA_BEGIN_ERROR) 186 | DBG_OUTPUT_PORT.println("Begin Failed"); 187 | else if (error == OTA_CONNECT_ERROR) 188 | DBG_OUTPUT_PORT.println("Connect Failed"); 189 | else if (error == OTA_RECEIVE_ERROR) 190 | DBG_OUTPUT_PORT.println("Receive Failed"); 191 | else if (error == OTA_END_ERROR) 192 | DBG_OUTPUT_PORT.println("End Failed"); 193 | }); 194 | 195 | ArduinoOTA.begin(); 196 | DBG_OUTPUT_PORT.println("OTA Ready"); 197 | DBG_OUTPUT_PORT.print("IP address: "); 198 | DBG_OUTPUT_PORT.println(WiFi.localIP()); 199 | 200 | // *************************************************************************** 201 | // Setup: FASTLED 202 | // *************************************************************************** 203 | delay(3000); // 3 second delay for recovery 204 | 205 | // tell FastLED about the LED strip configuration 206 | FastLED.addLeds(leds, NUM_LEDS) 207 | .setCorrection(TypicalLEDStrip); 208 | 209 | // FastLED.addLeds(leds, 210 | // NUM_LEDS).setCorrection(TypicalLEDStrip); 211 | // set master brightness control 212 | FastLED.setBrightness(settings.overall_brightness); 213 | 214 | // *************************************************************************** 215 | // Setup: MDNS responder 216 | // *************************************************************************** 217 | MDNS.begin(hostname); 218 | DBG_OUTPUT_PORT.print("Open http://"); 219 | DBG_OUTPUT_PORT.print(hostname); 220 | DBG_OUTPUT_PORT.println(".local/edit to see the file browser"); 221 | 222 | // *************************************************************************** 223 | // Setup: WebSocket server 224 | // *************************************************************************** 225 | webSocket.begin(); 226 | webSocket.onEvent(webSocketEvent); 227 | 228 | // *************************************************************************** 229 | // Setup: SPIFFS 230 | // *************************************************************************** 231 | SPIFFS.begin(); 232 | { 233 | Dir dir = SPIFFS.openDir("/"); 234 | while (dir.next()) { 235 | String fileName = dir.fileName(); 236 | size_t fileSize = dir.fileSize(); 237 | DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), 238 | formatBytes(fileSize).c_str()); 239 | } 240 | DBG_OUTPUT_PORT.printf("\n"); 241 | } 242 | 243 | // *************************************************************************** 244 | // Setup: SPIFFS Webserver handler 245 | // *************************************************************************** 246 | // list directory 247 | server.on("/list", HTTP_GET, handleFileList); 248 | // load editor 249 | server.on("/edit", HTTP_GET, []() { 250 | if (!handleFileRead("/edit.htm")) 251 | server.send(404, "text/plain", "FileNotFound"); 252 | }); 253 | // create file 254 | server.on("/edit", HTTP_PUT, handleFileCreate); 255 | // delete file 256 | server.on("/edit", HTTP_DELETE, handleFileDelete); 257 | // first callback is called after the request has ended with all parsed 258 | // arguments 259 | // second callback handles file uploads at that location 260 | server.on("/edit", HTTP_POST, []() { server.send(200, "text/plain", ""); }, 261 | handleFileUpload); 262 | // get heap status, analog input value and all GPIO statuses in one json call 263 | server.on("/esp_status", HTTP_GET, []() { 264 | String json = "{"; 265 | json += "\"heap\":" + String(ESP.getFreeHeap()); 266 | json += ", \"analog\":" + String(analogRead(A0)); 267 | json += ", \"gpio\":" + 268 | String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16))); 269 | json += "}"; 270 | server.send(200, "text/json", json); 271 | json = String(); 272 | }); 273 | 274 | // called when the url is not defined here 275 | // use it to load content from SPIFFS 276 | server.onNotFound([]() { 277 | if (!handleFileRead(server.uri())) handleNotFound(); 278 | }); 279 | 280 | server.on("/upload", handleMinimalUpload); 281 | 282 | server.on("/restart", []() { 283 | DBG_OUTPUT_PORT.printf("/restart:\n"); 284 | server.send(200, "text/plain", "restarting..."); 285 | ESP.restart(); 286 | }); 287 | 288 | server.on("/reset_wlan", []() { 289 | DBG_OUTPUT_PORT.printf("/reset_wlan:\n"); 290 | server.send(200, "text/plain", "Resetting WLAN and restarting..."); 291 | WiFiManager wifiManager; 292 | wifiManager.resetSettings(); 293 | ESP.restart(); 294 | }); 295 | 296 | // *************************************************************************** 297 | // Setup: SPIFFS Webserver handler 298 | // *************************************************************************** 299 | server.on("/set_brightness", []() { 300 | if (server.arg("c").toInt() > 0) { 301 | settings.overall_brightness = (int)server.arg("c").toInt() * 2.55; 302 | } else { 303 | settings.overall_brightness = server.arg("p").toInt(); 304 | } 305 | if (settings.overall_brightness > 255) { 306 | settings.overall_brightness = 255; 307 | } 308 | if (settings.overall_brightness < 0) { 309 | settings.overall_brightness = 0; 310 | } 311 | FastLED.setBrightness(settings.overall_brightness); 312 | 313 | if (settings.mode == HOLD) { 314 | settings.mode = ALL; 315 | } 316 | 317 | getStatusJSON(); 318 | }); 319 | 320 | server.on("/get_brightness", []() { 321 | String str_brightness = String((int)(settings.overall_brightness / 2.55)); 322 | server.send(200, "text/plain", str_brightness); 323 | DBG_OUTPUT_PORT.print("/get_brightness: "); 324 | DBG_OUTPUT_PORT.println(str_brightness); 325 | }); 326 | 327 | server.on("/get_switch", []() { 328 | server.send(200, "text/plain", (settings.mode == OFF) ? "0" : "1"); 329 | DBG_OUTPUT_PORT.printf("/get_switch: %s\n", 330 | (settings.mode == OFF) ? "0" : "1"); 331 | }); 332 | 333 | server.on("/get_color", []() { 334 | String rgbcolor = String(settings.main_color.red, HEX) + 335 | String(settings.main_color.green, HEX) + 336 | String(settings.main_color.blue, HEX); 337 | server.send(200, "text/plain", rgbcolor); 338 | DBG_OUTPUT_PORT.print("/get_color: "); 339 | DBG_OUTPUT_PORT.println(rgbcolor); 340 | }); 341 | 342 | server.on("/status", []() { getStatusJSON(); }); 343 | 344 | server.on("/off", []() { 345 | //exit_func = true; 346 | settings.mode = OFF; 347 | getArgs(); 348 | getStatusJSON(); 349 | }); 350 | 351 | server.on("/all", []() { 352 | //exit_func = true; 353 | settings.mode = ALL; 354 | getArgs(); 355 | getStatusJSON(); 356 | }); 357 | 358 | server.on("/rainbow", []() { 359 | //exit_func = true; 360 | settings.mode = RAINBOW; 361 | getArgs(); 362 | getStatusJSON(); 363 | }); 364 | 365 | server.on("/confetti", []() { 366 | //exit_func = true; 367 | settings.mode = CONFETTI; 368 | getArgs(); 369 | getStatusJSON(); 370 | }); 371 | 372 | server.on("/sinelon", []() { 373 | //exit_func = true; 374 | settings.mode = SINELON; 375 | getArgs(); 376 | getStatusJSON(); 377 | }); 378 | 379 | server.on("/juggle", []() { 380 | //exit_func = true; 381 | settings.mode = JUGGLE; 382 | getArgs(); 383 | getStatusJSON(); 384 | }); 385 | 386 | server.on("/bpm", []() { 387 | //exit_func = true; 388 | settings.mode = BPM; 389 | getArgs(); 390 | getStatusJSON(); 391 | }); 392 | 393 | server.on("/ripple", []() { 394 | //exit_func = true; 395 | settings.mode = RIPPLE; 396 | getArgs(); 397 | getStatusJSON(); 398 | }); 399 | 400 | server.on("/comet", []() { 401 | //exit_func = true; 402 | settings.mode = COMET; 403 | getArgs(); 404 | getStatusJSON(); 405 | }); 406 | 407 | server.on("/wipe", []() { 408 | settings.mode = WIPE; 409 | getArgs(); 410 | getStatusJSON(); 411 | }); 412 | 413 | server.on("/tv", []() { 414 | settings.mode = TV; 415 | getArgs(); 416 | getStatusJSON(); 417 | }); 418 | 419 | 420 | server.on("/palette_anims", []() { 421 | settings.mode = PALETTE_ANIMS; 422 | if (server.arg("p") != "") { 423 | uint8_t pal = (uint8_t) strtol(server.arg("p").c_str(), NULL, 10); 424 | if (pal > paletteCount) 425 | pal = paletteCount; 426 | 427 | settings.palette_ndx = pal; 428 | loadPaletteFromFile(settings.palette_ndx, &targetPalette); 429 | currentPalette = targetPalette; //PaletteCollection[settings.palette_ndx]; 430 | DBG_OUTPUT_PORT.printf("Palette is: %d", pal); 431 | } 432 | getStatusJSON(); 433 | }); 434 | 435 | server.begin(); 436 | 437 | paletteCount = getPaletteCount(); 438 | } 439 | 440 | void loop() { 441 | EVERY_N_MILLISECONDS(int(float(1000 / settings.fps))) { 442 | gHue++; // slowly cycle the "base color" through the rainbow 443 | } 444 | 445 | // Simple statemachine that handles the different modes 446 | switch (settings.mode) { 447 | default: 448 | case OFF: 449 | fill_solid(leds, NUM_LEDS, CRGB(0,0,0)); 450 | break; 451 | 452 | case ALL: 453 | fill_solid(leds, NUM_LEDS, CRGB(settings.main_color.red, settings.main_color.green, 454 | settings.main_color.blue)); 455 | break; 456 | 457 | case MIXEDSHOW: 458 | { 459 | gPatterns[gCurrentPatternNumber](); 460 | 461 | // send the 'leds' array out to the actual LED strip 462 | int showlength_Millis = settings.show_length * 1000; 463 | 464 | // DBG_OUTPUT_PORT.println("showlengthmillis = " + 465 | // String(showlength_Millis)); 466 | if (((millis()) - (lastMillis)) >= showlength_Millis) { 467 | nextPattern(); 468 | DBG_OUTPUT_PORT.println( "void nextPattern was called at " + String(millis()) + 469 | " and the current show length set to " + String(showlength_Millis)); 470 | } 471 | } 472 | break; 473 | 474 | case RAINBOW: 475 | rainbow(); 476 | break; 477 | 478 | case CONFETTI: 479 | confetti(); 480 | break; 481 | 482 | case SINELON: 483 | sinelon(); 484 | break; 485 | 486 | case JUGGLE: 487 | juggle(); 488 | break; 489 | 490 | case BPM: 491 | bpm(); 492 | break; 493 | 494 | case PALETTE_ANIMS: 495 | palette_anims(); 496 | break; 497 | 498 | case RIPPLE: 499 | ripple(); 500 | break; 501 | 502 | case COMET: 503 | comet(); 504 | break; 505 | 506 | case THEATERCHASE: 507 | theaterChase(); 508 | break; 509 | 510 | case WIPE: 511 | colorWipe(); 512 | break; 513 | 514 | case TV: 515 | tv(); 516 | break; 517 | } 518 | 519 | // Add glitter if necessary 520 | if (settings.glitter_on == true) { 521 | addGlitter(settings.glitter_density); 522 | } 523 | 524 | // Get the current time 525 | unsigned long continueTime = millis() + int(float(1000 / settings.fps)); 526 | // Do our main loop functions, until we hit our wait time 527 | do { 528 | //long int now = micros(); 529 | FastLED.show(); // Display whats rendered. 530 | //long int later = micros(); 531 | //DBG_OUTPUT_PORT.printf("Show time is %ld\n", later-now); 532 | server.handleClient(); // Handle requests to the web server 533 | webSocket.loop(); // Handle websocket traffic 534 | ArduinoOTA.handle(); // Handle OTA requests. 535 | #ifdef REMOTE_DEBUG 536 | Debug.handle(); // Handle telent server 537 | #endif 538 | yield(); // Yield for ESP8266 stuff 539 | 540 | if (WiFi.status() != WL_CONNECTED) { 541 | // Blink the LED quickly to indicate WiFi connection lost. 542 | EVERY_N_MILLISECONDS(250) { 543 | int state = digitalRead(BUILTIN_LED); // get the current state of GPIO1 pin 544 | digitalWrite(BUILTIN_LED, !state); 545 | } 546 | } else { 547 | // Light on-steady indicating WiFi is connected. 548 | digitalWrite(BUILTIN_LED, false); 549 | } 550 | 551 | } while (millis() < continueTime); 552 | 553 | 554 | } 555 | 556 | void nextPattern() { 557 | // add one to the current pattern number, and wrap around at the end 558 | // gCurrentPatternNumber = (gCurrentPatternNumber + random(0, 559 | // ARRAY_SIZE(gPatterns))) % ARRAY_SIZE( gPatterns); 560 | gCurrentPatternNumber = random(0, ARRAY_SIZE(gPatterns)); 561 | lastMillis = millis(); 562 | } 563 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jake's "Griswold" LED controller for Christmas Lights. 2 | 3 | I bought 1000 WS2811 nodes for my outdoor Christmas light installation this year. 4 | Based on the "Russell's FASTLEDs" project by @russp81, which is in turn based on the "McLighting" project by @toblum 5 | 6 | It seemed necessary to name the thing after Clark Griswold, but really just to differentiate this fork from the originals. 7 | 8 | ![Clark Griswold](http://i.giphy.com/gB9wIPXav2Ryg.gif) 9 | 10 | @russp81 mixed the work of @toblum with the @FastLED (FastLED library 3.1.3 as of this writing), the colorjs colorpicker, color spectrums created via FastLED Palette Knife, and some additional strip animations. 11 | 12 | # License 13 | 14 | As per the original [McLighting](https://github.com/toblum/McLighting) project, this project is released under the GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007. 15 | 16 | Griswold is free software: you can redistribute it and/or modify 17 | it under the terms of the GNU Lesser General Public License as 18 | published by the Free Software Foundation, either version 3 of 19 | the License, or (at your option) any later version. 20 | 21 | This program is distributed in the hope that it will be useful, 22 | but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | GNU General Public License for more details. 25 | 26 | You should have received a copy of the GNU Lesser General Public License 27 | along with this program. If not, see . 28 | 29 | # Improvements 30 | 31 | - Palettes stored as binary files on SPIFFS. See below for more information on this. 32 | - Display name of the current palette file in the web interface. 33 | - Added ArduinoOTA support so I can update the firmware over WiFi, which will be important when its installed outside. 34 | - Added the ability to store the settings in EEPROM and restore on boot. 35 | - Merged the jscolor interface into the original McLighting interface 36 | - Updated the McLighting interface to retrieve the current settings from the device, and update the UI with the current settings, rather than always default to the defaults. 37 | - General code formatting clean-up. 38 | - Added “RemoteDebug” library for serial console over telnet. (Optional #define) 39 | - Fixed divide-by-zero error that occurs when fps=0 by preventing fps=0 from the UI. 40 | - Updates to the “animated palette” function, now you can select a single palette, or the existing randomized palette after time delay. 41 | - Rearchitected things a bit, now the colormodes.h functions render one single frame, and do not block the main thread. 42 | - Added back the wipe and tv animations from the original McLighting project (removed in LEDLAMP_FASTLEDs) 43 | - Modified TV animation to add some flicker (I like it better) 44 | - Added “effect brightness” setting to allow you to dim the main effect independently of glitter. 45 | 46 | Russell's FASTLEDs: 47 | https://github.com/russp81/LEDLAMP_FASTLEDs 48 | 49 | FastLED 3.1.3 library: 50 | https://github.com/FastLED/FastLED 51 | 52 | McLighting library: 53 | https://github.com/toblum/McLighting 54 | 55 | jscolor Color Picker: 56 | http://jscolor.com/ 57 | 58 | FastLED Palette Knife: 59 | http://fastled.io/tools/paletteknife/ 60 | 61 | RemoteDebug: 62 | https://github.com/JoaoLopesF/RemoteDebug 63 | 64 | # Palettes on SPIFFS 65 | 66 | Normally, you use [PaletteKnife](http://fastled.io/tools/paletteknife/) to generate arrays with the palette info. You then compile this data into your project. I wanted to be able to update the palettes without recompiling, so I moved them to files in SPIFFS (/palettes directory). There is a little python program that basically takes the logic from PaletteKnife and outputs a binary file with the palette data instead. Load these binary files to SPIFFS using the [Arduino ESP8266 filesystem uploader](https://github.com/esp8266/arduino-esp8266fs-plugin) or manually. 67 | 68 | # Portions of @russp81's original README 69 | 70 | If you aren't familiar with how to setup your ESP8266, see the readme on McLighting's git. It's well written and should get you up and running. 71 | 72 | In short you will: 73 | 74 | 1. Configure the Arduino IDE to communicate with the ESP8266 75 | 2. Upload the sketch (from this repo) The sketch is setup for a 240 pixel WS2812B GRB LED Strip. 76 | (change the applicable options in "definitions.h" to your desire) 77 | 3. On first launch, the ESP8266 will advertise it's own WiFi network for you to connect to, once you connect to it, launch your browser 78 | and the web interface is self explanatory. (If the interface doesn't load, type in "192.168.4.1" into your browser and hit go) 79 | 4. Once the ESP is on your wifi network, you can then upload the required files for the web interface by typing the in IP address 80 | of the ESP followed by "/edit" (i.e. 192.168.1.20/edit). Then upload the files from the folder labeled "upload these" from this repo. 81 | 5. Once you have finished uploading, type in the IP of the ESP into your browser and you should be up and running! 82 | 83 | 84 | My work was all on adding FastLED (and other tweaks / animations) into the McLighting sketch instead of using Adafruit NeoPixel. 85 | 86 | I am a self taught coder who learns by a few books, google, and looking at other's code, 87 | and I just liked the things you can do in FastLED better, so I decided to tackle the 88 | idea of integrating FastLED into the already awesome work of @toblum. 89 | 90 | I have a limited grasp on the h/w and s/w relationships (do's and don't do's, etc). 91 | I edited clockless_esp8266.h (in the FastLED platforms folder) and 92 | kept getting flickering until I incremented the WAIT_TIME up to 18us. 93 | (also I did "#define FASTLED_INTERRUPT_RETRY_COUNT 3" inside my sketch). 94 | 95 | If I disabled interrupts altogether "#define FASTLED_ALLOW_INTERRUPTS 0", the strip would stop flickering but I would get 96 | what I believe to be "watchdog resets" every 5 to 20 minutes depending on what animation was running, wifi traffic, etc... 97 | 98 | For reference, I learned more about the interrupts issue from here: https://github.com/FastLED/FastLED/issues/306 99 | 100 | If anyone can shed more light on this I am all ears! I'm not sure exactly what the implications are 101 | for setting the WAIT_TIME = 18us?? Everything appears to function properly, and so far I have not seen 102 | a reset in a few hours. 103 | 104 | Also, I added a separate color picker from (http://jscolor.com/). My idea with this is to eventually create 105 | spectrums using multiple color pickers via the web interface (instead of grinding out coding in the Arduino IDE) 106 | and eventually animate them as well. When I am finished with this project, I (we) will hopefully be able to build those 107 | spectrums, save them to the ESP8266 flash memory, and have a universal routine in the Arduino Sketch that can handle 108 | the input / output of the spectrums to the strip (even running animations with them). I also might even try making a web interface 109 | to create custom animations from, but that seems like a pretty decent challenge from what I can tell. (I am just now finding my way 110 | around in html/css/js so I have A LOT of learning to do!) 111 | 112 | I will say again, I'm a rookie who has tinkered around in a little of each major language with no formal education, so 113 | if you see something that doesn't look right, it probably isn't! I am very open to suggestions / learning anything 114 | anyone is willing to share. 115 | 116 | -------------------------------------------------------------------------------- /colormodes.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 @jake-b, @russp81, @toblum 2 | // Griswold LED Lighting Controller 3 | 4 | // Griswold is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as 6 | // published by the Free Software Foundation, either version 3 of 7 | // the License, or (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with this program. If not, see . 16 | 17 | // Griswold is a fork of the LEDLAMP project at 18 | // https://github.com/russp81/LEDLAMP_FASTLEDs 19 | 20 | // The LEDLAMP project is a fork of the McLighting Project at 21 | // https://github.com/toblum/McLighting 22 | 23 | // *************************************************************************** 24 | // Color modes 25 | // *************************************************************************** 26 | //#include "definitions.h" 27 | 28 | char* listStatusJSON(); 29 | 30 | extern WebSocketsServer webSocket; 31 | 32 | // These functions originally displayed the color using a call to FastLed.show() 33 | // This has been refactored out, theser functions now simply render into the 34 | // leds[] array. The FastLed.show() call happens in the main loop now. 35 | // Furthermore, the 'add glitter' option also refactored out to the main loop. 36 | 37 | void addGlitter(fract8 chanceOfGlitter) { 38 | if (random8() < chanceOfGlitter) { 39 | leds[random16(NUM_LEDS)] += 40 | CRGB(settings.glitter_color.red, settings.glitter_color.green, 41 | settings.glitter_color.blue); 42 | } 43 | } 44 | 45 | void rainbow() { 46 | // FastLED's built-in rainbow generator 47 | fill_rainbow(leds, NUM_LEDS, gHue, 7); 48 | 49 | // if (settings.glitter_on == true){addGlitter(settings.glitter_density);} 50 | // frame has been created, now show it 51 | // FastLED.show(); 52 | // insert a delay to keep the framerate modest 53 | // FastLED.delay(int(float(500/settings.fps))); 54 | } 55 | 56 | void confetti() { 57 | // random colored speckles that blink in and fade smoothly 58 | fadeToBlackBy(leds, NUM_LEDS, settings.ftb_speed); 59 | for (int x=0; x endingLEDIndex) return; //stop condition 124 | 125 | // leds[i] = ColorFromPalette( currentPalette, colorIndex + sin8(i*16), 126 | // brightness); 127 | leds[i] = ColorFromPalette(palette, colorIndex, 128 | settings.effect_brightness); 129 | if (anim_direction == FORWARD) { 130 | colorIndex += 3; 131 | } 132 | if (anim_direction == BACK) { 133 | colorIndex -= 3; 134 | } 135 | } 136 | } 137 | 138 | void ChangePalettePeriodically(bool forceNow) { 139 | if (forceNow || millis() - paletteMillis > (settings.show_length * 1000)) { 140 | paletteMillis = millis(); 141 | 142 | targetPaletteIndex = random(0, paletteCount); 143 | 144 | currentPalette = targetPalette; 145 | 146 | anim_direction = (DIRECTION)!anim_direction; // DIRECTION enum allows flipping by boolean not. 147 | 148 | loadPaletteFromFile(targetPaletteIndex, &targetPalette); 149 | 150 | DBG_OUTPUT_PORT.printf("New pallet index: %d\n", targetPaletteIndex); 151 | 152 | if (settings.glitter_wipe_on) { 153 | DBG_OUTPUT_PORT.println("Begin glitter wipe"); 154 | wipeInProgress = true; 155 | } 156 | } 157 | } 158 | 159 | void colorWipe() { 160 | static CRGB prevColor = CHSV(gHue, 255, settings.effect_brightness); 161 | static CRGB currentColor = CHSV(gHue+60, 255, settings.effect_brightness); 162 | 163 | // Wrap around if necessary 164 | if (wipePos >= NUM_LEDS) { 165 | wipePos = 0; 166 | prevColor = currentColor; 167 | gHue += 60; 168 | currentColor = CHSV(gHue, 255, settings.effect_brightness); 169 | } 170 | 171 | // Render the first half of the wipe 172 | for (int x=0; x= 0 && speckle < NUM_LEDS) { 185 | leds[speckle] += CRGB(settings.glitter_color.red, settings.glitter_color.green, 186 | settings.glitter_color.blue); 187 | } 188 | } 189 | } 190 | 191 | // Advance for next frame 192 | wipePos+=WIPE_SPEED; 193 | } 194 | 195 | void palette_anims() { 196 | currentBlending = LINEARBLEND; 197 | 198 | if (settings.palette_ndx == -1) ChangePalettePeriodically(false); 199 | 200 | if (!settings.glitter_wipe_on) { 201 | uint8_t maxChanges = int(float(settings.fps / 2)); 202 | nblendPaletteTowardPalette(currentPalette, targetPalette, maxChanges); 203 | 204 | // Update the current palette if necessary-- and send to any connected clients. 205 | if (currentPaletteIndex != targetPaletteIndex) { 206 | currentPaletteIndex = targetPaletteIndex; 207 | 208 | // Send current palette name to the UI. 209 | String name = getPaletteNameWithIndex(currentPaletteIndex); 210 | webSocket.broadcastTXT("p"+name); 211 | } 212 | } 213 | 214 | static uint8_t startIndex = 0; 215 | 216 | /* motion speed */ 217 | startIndex = startIndex + 3; 218 | 219 | FillLEDsFromPaletteColors(currentPalette,startIndex); 220 | 221 | if (settings.glitter_wipe_on && wipeInProgress) { 222 | if (wipePos >= NUM_LEDS) { 223 | DBG_OUTPUT_PORT.println("End glitter wipe"); 224 | wipeInProgress = false; 225 | wipePos = 0; 226 | currentPalette = targetPalette; 227 | currentPaletteIndex = targetPaletteIndex; 228 | 229 | // Send current palette name to the UI. 230 | String name = getPaletteNameWithIndex(currentPaletteIndex); 231 | webSocket.broadcastTXT("p"+name); 232 | FillLEDsFromPaletteColors(targetPalette,startIndex); 233 | } else { 234 | FillLEDsFromPaletteColors(targetPalette,startIndex, wipePos); 235 | for (int x=0; x < 3; x++) { 236 | int speckle = wipePos + random(-SPARKLE_SPREAD,SPARKLE_SPREAD); 237 | if (speckle >= 0 && speckle < NUM_LEDS) { 238 | leds[speckle] += CRGB(settings.glitter_color.red, settings.glitter_color.green, 239 | settings.glitter_color.blue); 240 | } 241 | } 242 | wipePos+=WIPE_SPEED; 243 | } 244 | } 245 | 246 | 247 | } 248 | 249 | //*****************LED 250 | //RIPPLE***************************************************** 251 | 252 | void one_color_allHSV(int ahue, 253 | int abright) { // SET ALL LEDS TO ONE COLOR (HSV) 254 | for (int i = 0; i < NUM_LEDS; i++) { 255 | leds[i] = CHSV(ahue, 255, abright); 256 | } 257 | } 258 | 259 | int wrap(int step) { 260 | if (step < 0) return NUM_LEDS + step; 261 | if (step > NUM_LEDS - 1) return step - NUM_LEDS; 262 | return step; 263 | } 264 | 265 | void ripple() { 266 | if (currentBg == nextBg) { 267 | nextBg = random(256); 268 | } else if (nextBg > currentBg) { 269 | currentBg++; 270 | } else { 271 | currentBg--; 272 | } 273 | for (uint16_t l = 0; l < NUM_LEDS; l++) { 274 | leds[l] = CHSV(currentBg, 255, 275 | settings.effect_brightness); // strip.setPixelColor(l, 276 | // Wheel(currentBg, 0.1)); 277 | } 278 | 279 | if (step == -1) { 280 | center = random(NUM_LEDS); 281 | color = random(256); 282 | step = 0; 283 | } 284 | 285 | if (step == 0) { 286 | leds[center] = CHSV( 287 | color, 255, settings.effect_brightness); // strip.setPixelColor(center, 288 | // Wheel(color, 1)); 289 | step++; 290 | } else { 291 | if (step < maxSteps) { 292 | //Serial.println(pow(fadeRate, step)); 293 | 294 | leds[wrap(center + step)] = 295 | CHSV(color, 255, 296 | pow(fadeRate, step) * 255); // strip.setPixelColor(wrap(center 297 | // + step), Wheel(color, 298 | // pow(fadeRate, step))); 299 | leds[wrap(center - step)] = 300 | CHSV(color, 255, 301 | pow(fadeRate, step) * 255); // strip.setPixelColor(wrap(center 302 | // - step), Wheel(color, 303 | // pow(fadeRate, step))); 304 | if (step > 3) { 305 | leds[wrap(center + step - 3)] = 306 | CHSV(color, 255, pow(fadeRate, step - 2) * 307 | 255); // strip.setPixelColor(wrap(center + 308 | // step - 3), Wheel(color, 309 | // pow(fadeRate, step - 2))); 310 | leds[wrap(center - step + 3)] = 311 | CHSV(color, 255, pow(fadeRate, step - 2) * 312 | 255); // strip.setPixelColor(wrap(center - 313 | // step + 3), Wheel(color, 314 | // pow(fadeRate, step - 2))); 315 | } 316 | step++; 317 | } else { 318 | step = -1; 319 | } 320 | } 321 | // if (settings.glitter_on == true){addGlitter(settings.glitter_density);} 322 | 323 | // frame has been created, now show it 324 | // FastLED.show(); 325 | // insert a delay to keep the framerate modest 326 | // FastLED.delay(int(float(1000/settings.fps))); 327 | } 328 | 329 | //***************************END LED 330 | //RIPPLE***************************************************** 331 | 332 | void comet() { 333 | fadeToBlackBy(leds, NUM_LEDS, settings.ftb_speed); 334 | lead_dot = beatsin16(int(float(settings.fps / 3)), 0, NUM_LEDS); 335 | leds[lead_dot] = CHSV(dothue, 200, 255); 336 | dothue += 8; 337 | // if (settings.glitter_on == true){addGlitter(settings.glitter_density);} 338 | // FastLED.show(); 339 | } 340 | 341 | // Theatre-style crawling lights. 342 | void theaterChase() { 343 | static int8_t frame = 0; 344 | 345 | // turn off the previous frame's led 346 | for (int i = 0; i < NUM_LEDS; i = i + 3) { 347 | if (i + frame < NUM_LEDS) { 348 | leds[i + frame] = CRGB(0, 0, 0); // turn every third pixel off 349 | } 350 | } 351 | 352 | // advance the frame 353 | frame++; 354 | if (frame > 2) frame = 0; 355 | 356 | // turn on the current frame's leds 357 | for (int i = 0; i < NUM_LEDS; i = i + 3) { 358 | if (i + frame < NUM_LEDS) { 359 | leds[i + frame] = 360 | CRGB(settings.main_color.red, settings.main_color.green, 361 | settings.main_color.blue); // turn every third pixel on 362 | } 363 | } 364 | } 365 | 366 | 367 | //***********TV 368 | int dipInterval = 10; 369 | int darkTime = 250; 370 | unsigned long currentDipTime; 371 | unsigned long dipStartTime; 372 | unsigned long currentMillis; 373 | int ledState = LOW; 374 | long previousMillis = 0; 375 | int ledBrightness[NUM_LEDS]; 376 | uint16_t ledHue[NUM_LEDS]; 377 | int led = 5; 378 | int interval = 2000; 379 | int twitch = 50; 380 | int dipCount = 0; 381 | int analogLevel = 100; 382 | boolean timeToDip = false; 383 | 384 | CRGB hsb2rgbAN1(uint16_t index, uint8_t sat, uint8_t bright) { 385 | // Source: https://blog.adafruit.com/2012/03/14/constant-brightness-hsb-to-rgb-algorithm/ 386 | uint8_t temp[5], n = (index >> 8) % 3; 387 | temp[0] = temp[3] = (uint8_t)(( (sat ^ 255) * bright) / 255); 388 | temp[1] = temp[4] = (uint8_t)((((( (index & 255) * sat) / 255) + (sat ^ 255)) * bright) / 255); 389 | temp[2] = (uint8_t)(((((((index & 255) ^ 255) * sat) / 255) + (sat ^ 255)) * bright) / 255); 390 | 391 | return CRGB(temp[n + 2], temp[n + 1], temp[n]); 392 | } 393 | 394 | void _tvUpdateLed (int led, int brightness) { 395 | ledBrightness[led] = brightness; 396 | for (int i=0; i interval) { 407 | previousMillis = currentMillis; 408 | interval = random(750,4001);//Adjusts the interval for more/less frequent random light changes 409 | twitch = random(40,100);// Twitch provides motion effect but can be a bit much if too high 410 | dipCount++; 411 | } 412 | if (currentMillis-previousMillis dipInterval) { 421 | //DBG_OUTPUT_PORT.println("dip"); 422 | timeToDip = true; 423 | dipCount = 0; 424 | dipStartTime = millis(); 425 | darkTime = random(50,150); 426 | dipInterval = random(5,250);// cycles of flicker 427 | } 428 | } 429 | } else { 430 | //DBG_OUTPUT_PORT.println("Dip Time"); 431 | currentDipTime = millis(); 432 | if (currentDipTime - dipStartTime < darkTime) { 433 | for (int i=3; i> 8) % 3, 200, ledBrightness[i]); 453 | 454 | leds[i] = hsb2rgbAN1(ledHue[i], sat, ledBrightness[i]).nscale8_video(flicker); 455 | } 456 | } 457 | 458 | 459 | //*******************************ARRAY OF SHOW ANIMATIONS FOR MIXED SHOW 460 | //MODE*********************** 461 | typedef void (*SimplePatternList[])(); 462 | SimplePatternList gPatterns = {rainbow, confetti, sinelon, juggle, 463 | bpm, palette_anims, ripple, comet}; 464 | //************************************************************************************************** 465 | -------------------------------------------------------------------------------- /data-moved_aside/graphs.htm: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | ESP Monitor 23 | 24 | 85 | 86 | 87 |
88 | 89 | 90 | 91 | 92 |
93 |
94 |
95 |
96 | 97 | -------------------------------------------------------------------------------- /data-moved_aside/graphs.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data-moved_aside/graphs.js.gz -------------------------------------------------------------------------------- /data/edit.htm.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/edit.htm.gz -------------------------------------------------------------------------------- /data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/favicon.ico -------------------------------------------------------------------------------- /data/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 166 | 167 | 168 | 169 |
170 | 187 | 188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
Error on websocket connect.
208 |
209 |
210 |
211 |
212 | 213 |
214 |
215 |
216 |
217 |
218 | 219 |
220 |
221 |
222 |
223 |
pick a color
224 |
225 |
226 |
227 |
228 |
229 |
230 | 231 |
232 |
233 |

Show Mode

234 |
235 |
236 | 239 |
240 | 241 | 246 | 247 | 252 | 253 | 258 | 259 | 264 | 265 | 270 | 271 | 276 | 277 | 282 | 287 | 292 | 293 | 298 | 303 | 308 | 313 |
314 |
315 |
316 |

Palette Animations

317 | 318 |
319 | 324 | 329 | 334 |
335 |
336 |
337 |

Glitter Mode

338 |
339 | 344 | 349 |
350 | 356 |
357 |
358 |
359 |
360 |

Save Settings

361 | 378 |
379 |
380 |

Parameters

381 |
382 |
383 |
384 |
385 |
386 |

387 |
388 |
389 |
390 |

391 |
392 |
393 |
394 |

395 |
396 |
397 | 398 |
399 |
400 |
401 |

402 |
403 |
404 |
405 |

406 |
407 |
408 |
409 |

410 |
411 |
412 | 413 |
414 |
415 |
416 |

417 |
418 |
419 | 420 |
421 |
422 |
423 |

424 |
425 |
426 | 427 |
428 |
429 |
430 |

431 |
432 |
433 |
434 |
435 |
436 |

437 |
438 |
439 |
440 |
441 |
442 |

443 |
444 |
445 |
446 |
447 |
448 |

449 |
450 |
451 |
452 |
453 |
454 |

455 |
456 |
457 |
458 |
459 |
460 |
461 | 462 |
463 |
464 |
465 | toHEXString = 466 |
467 | toRGBString = 468 |
469 | R, G, B = 470 |
471 | H, S, V = 472 |
473 | 474 |
475 |
476 | 477 | 488 | 489 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 959 | 960 | 961 | -------------------------------------------------------------------------------- /data/jscolor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jscolor - JavaScript Color Picker 3 | * 4 | * @link http://jscolor.com 5 | * @license For open source use: GPLv3 6 | * For commercial use: JSColor Commercial License 7 | * @author Jan Odvarko 8 | * @version 2.0.4 9 | * 10 | * See usage examples at http://jscolor.com/examples/ 11 | */ 12 | 13 | 14 | "use strict"; 15 | 16 | 17 | if (!window.jscolor) { window.jscolor = (function () { 18 | 19 | 20 | var jsc = { 21 | 22 | 23 | register : function () { 24 | jsc.attachDOMReadyEvent(jsc.init); 25 | jsc.attachEvent(document, 'mousedown', jsc.onDocumentMouseDown); 26 | jsc.attachEvent(document, 'touchstart', jsc.onDocumentTouchStart); 27 | jsc.attachEvent(window, 'resize', jsc.onWindowResize); 28 | }, 29 | 30 | 31 | init : function () { 32 | if (jsc.jscolor.lookupClass) { 33 | jsc.jscolor.installByClassName(jsc.jscolor.lookupClass); 34 | } 35 | }, 36 | 37 | 38 | tryInstallOnElements : function (elms, className) { 39 | var matchClass = new RegExp('(^|\\s)(' + className + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i'); 40 | 41 | for (var i = 0; i < elms.length; i += 1) { 42 | if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color') { 43 | if (jsc.isColorAttrSupported) { 44 | // skip inputs of type 'color' if supported by the browser 45 | continue; 46 | } 47 | } 48 | var m; 49 | if (!elms[i].jscolor && elms[i].className && (m = elms[i].className.match(matchClass))) { 50 | var targetElm = elms[i]; 51 | var optsStr = null; 52 | 53 | var dataOptions = jsc.getDataAttr(targetElm, 'jscolor'); 54 | if (dataOptions !== null) { 55 | optsStr = dataOptions; 56 | } else if (m[4]) { 57 | optsStr = m[4]; 58 | } 59 | 60 | var opts = {}; 61 | if (optsStr) { 62 | try { 63 | opts = (new Function ('return (' + optsStr + ')'))(); 64 | } catch(eParseError) { 65 | jsc.warn('Error parsing jscolor options: ' + eParseError + ':\n' + optsStr); 66 | } 67 | } 68 | targetElm.jscolor = new jsc.jscolor(targetElm, opts); 69 | } 70 | } 71 | }, 72 | 73 | 74 | isColorAttrSupported : (function () { 75 | var elm = document.createElement('input'); 76 | if (elm.setAttribute) { 77 | elm.setAttribute('type', 'color'); 78 | if (elm.type.toLowerCase() == 'color') { 79 | return true; 80 | } 81 | } 82 | return false; 83 | })(), 84 | 85 | 86 | isCanvasSupported : (function () { 87 | var elm = document.createElement('canvas'); 88 | return !!(elm.getContext && elm.getContext('2d')); 89 | })(), 90 | 91 | 92 | fetchElement : function (mixed) { 93 | return typeof mixed === 'string' ? document.getElementById(mixed) : mixed; 94 | }, 95 | 96 | 97 | isElementType : function (elm, type) { 98 | return elm.nodeName.toLowerCase() === type.toLowerCase(); 99 | }, 100 | 101 | 102 | getDataAttr : function (el, name) { 103 | var attrName = 'data-' + name; 104 | var attrValue = el.getAttribute(attrName); 105 | if (attrValue !== null) { 106 | return attrValue; 107 | } 108 | return null; 109 | }, 110 | 111 | 112 | attachEvent : function (el, evnt, func) { 113 | if (el.addEventListener) { 114 | el.addEventListener(evnt, func, false); 115 | } else if (el.attachEvent) { 116 | el.attachEvent('on' + evnt, func); 117 | } 118 | }, 119 | 120 | 121 | detachEvent : function (el, evnt, func) { 122 | if (el.removeEventListener) { 123 | el.removeEventListener(evnt, func, false); 124 | } else if (el.detachEvent) { 125 | el.detachEvent('on' + evnt, func); 126 | } 127 | }, 128 | 129 | 130 | _attachedGroupEvents : {}, 131 | 132 | 133 | attachGroupEvent : function (groupName, el, evnt, func) { 134 | if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) { 135 | jsc._attachedGroupEvents[groupName] = []; 136 | } 137 | jsc._attachedGroupEvents[groupName].push([el, evnt, func]); 138 | jsc.attachEvent(el, evnt, func); 139 | }, 140 | 141 | 142 | detachGroupEvents : function (groupName) { 143 | if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) { 144 | for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) { 145 | var evt = jsc._attachedGroupEvents[groupName][i]; 146 | jsc.detachEvent(evt[0], evt[1], evt[2]); 147 | } 148 | delete jsc._attachedGroupEvents[groupName]; 149 | } 150 | }, 151 | 152 | 153 | attachDOMReadyEvent : function (func) { 154 | var fired = false; 155 | var fireOnce = function () { 156 | if (!fired) { 157 | fired = true; 158 | func(); 159 | } 160 | }; 161 | 162 | if (document.readyState === 'complete') { 163 | setTimeout(fireOnce, 1); // async 164 | return; 165 | } 166 | 167 | if (document.addEventListener) { 168 | document.addEventListener('DOMContentLoaded', fireOnce, false); 169 | 170 | // Fallback 171 | window.addEventListener('load', fireOnce, false); 172 | 173 | } else if (document.attachEvent) { 174 | // IE 175 | document.attachEvent('onreadystatechange', function () { 176 | if (document.readyState === 'complete') { 177 | document.detachEvent('onreadystatechange', arguments.callee); 178 | fireOnce(); 179 | } 180 | }) 181 | 182 | // Fallback 183 | window.attachEvent('onload', fireOnce); 184 | 185 | // IE7/8 186 | if (document.documentElement.doScroll && window == window.top) { 187 | var tryScroll = function () { 188 | if (!document.body) { return; } 189 | try { 190 | document.documentElement.doScroll('left'); 191 | fireOnce(); 192 | } catch (e) { 193 | setTimeout(tryScroll, 1); 194 | } 195 | }; 196 | tryScroll(); 197 | } 198 | } 199 | }, 200 | 201 | 202 | warn : function (msg) { 203 | if (window.console && window.console.warn) { 204 | window.console.warn(msg); 205 | } 206 | }, 207 | 208 | 209 | preventDefault : function (e) { 210 | if (e.preventDefault) { e.preventDefault(); } 211 | e.returnValue = false; 212 | }, 213 | 214 | 215 | captureTarget : function (target) { 216 | // IE 217 | if (target.setCapture) { 218 | jsc._capturedTarget = target; 219 | jsc._capturedTarget.setCapture(); 220 | } 221 | }, 222 | 223 | 224 | releaseTarget : function () { 225 | // IE 226 | if (jsc._capturedTarget) { 227 | jsc._capturedTarget.releaseCapture(); 228 | jsc._capturedTarget = null; 229 | } 230 | }, 231 | 232 | 233 | fireEvent : function (el, evnt) { 234 | if (!el) { 235 | return; 236 | } 237 | if (document.createEvent) { 238 | var ev = document.createEvent('HTMLEvents'); 239 | ev.initEvent(evnt, true, true); 240 | el.dispatchEvent(ev); 241 | } else if (document.createEventObject) { 242 | var ev = document.createEventObject(); 243 | el.fireEvent('on' + evnt, ev); 244 | } else if (el['on' + evnt]) { // alternatively use the traditional event model 245 | el['on' + evnt](); 246 | } 247 | }, 248 | 249 | 250 | classNameToList : function (className) { 251 | return className.replace(/^\s+|\s+$/g, '').split(/\s+/); 252 | }, 253 | 254 | 255 | // The className parameter (str) can only contain a single class name 256 | hasClass : function (elm, className) { 257 | if (!className) { 258 | return false; 259 | } 260 | return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' '); 261 | }, 262 | 263 | 264 | // The className parameter (str) can contain multiple class names separated by whitespace 265 | setClass : function (elm, className) { 266 | var classList = jsc.classNameToList(className); 267 | for (var i = 0; i < classList.length; i += 1) { 268 | if (!jsc.hasClass(elm, classList[i])) { 269 | elm.className += (elm.className ? ' ' : '') + classList[i]; 270 | } 271 | } 272 | }, 273 | 274 | 275 | // The className parameter (str) can contain multiple class names separated by whitespace 276 | unsetClass : function (elm, className) { 277 | var classList = jsc.classNameToList(className); 278 | for (var i = 0; i < classList.length; i += 1) { 279 | var repl = new RegExp( 280 | '^\\s*' + classList[i] + '\\s*|' + 281 | '\\s*' + classList[i] + '\\s*$|' + 282 | '\\s+' + classList[i] + '(\\s+)', 283 | 'g' 284 | ); 285 | elm.className = elm.className.replace(repl, '$1'); 286 | } 287 | }, 288 | 289 | 290 | getStyle : function (elm) { 291 | return window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle; 292 | }, 293 | 294 | 295 | setStyle : (function () { 296 | var helper = document.createElement('div'); 297 | var getSupportedProp = function (names) { 298 | for (var i = 0; i < names.length; i += 1) { 299 | if (names[i] in helper.style) { 300 | return names[i]; 301 | } 302 | } 303 | }; 304 | var props = { 305 | borderRadius: getSupportedProp(['borderRadius', 'MozBorderRadius', 'webkitBorderRadius']), 306 | boxShadow: getSupportedProp(['boxShadow', 'MozBoxShadow', 'webkitBoxShadow']) 307 | }; 308 | return function (elm, prop, value) { 309 | switch (prop.toLowerCase()) { 310 | case 'opacity': 311 | var alphaOpacity = Math.round(parseFloat(value) * 100); 312 | elm.style.opacity = value; 313 | elm.style.filter = 'alpha(opacity=' + alphaOpacity + ')'; 314 | break; 315 | default: 316 | elm.style[props[prop]] = value; 317 | break; 318 | } 319 | }; 320 | })(), 321 | 322 | 323 | setBorderRadius : function (elm, value) { 324 | jsc.setStyle(elm, 'borderRadius', value || '0'); 325 | }, 326 | 327 | 328 | setBoxShadow : function (elm, value) { 329 | jsc.setStyle(elm, 'boxShadow', value || 'none'); 330 | }, 331 | 332 | 333 | getElementPos : function (e, relativeToViewport) { 334 | var x=0, y=0; 335 | var rect = e.getBoundingClientRect(); 336 | x = rect.left; 337 | y = rect.top; 338 | if (!relativeToViewport) { 339 | var viewPos = jsc.getViewPos(); 340 | x += viewPos[0]; 341 | y += viewPos[1]; 342 | } 343 | return [x, y]; 344 | }, 345 | 346 | 347 | getElementSize : function (e) { 348 | return [e.offsetWidth, e.offsetHeight]; 349 | }, 350 | 351 | 352 | // get pointer's X/Y coordinates relative to viewport 353 | getAbsPointerPos : function (e) { 354 | if (!e) { e = window.event; } 355 | var x = 0, y = 0; 356 | if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { 357 | // touch devices 358 | x = e.changedTouches[0].clientX; 359 | y = e.changedTouches[0].clientY; 360 | } else if (typeof e.clientX === 'number') { 361 | x = e.clientX; 362 | y = e.clientY; 363 | } 364 | return { x: x, y: y }; 365 | }, 366 | 367 | 368 | // get pointer's X/Y coordinates relative to target element 369 | getRelPointerPos : function (e) { 370 | if (!e) { e = window.event; } 371 | var target = e.target || e.srcElement; 372 | var targetRect = target.getBoundingClientRect(); 373 | 374 | var x = 0, y = 0; 375 | 376 | var clientX = 0, clientY = 0; 377 | if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { 378 | // touch devices 379 | clientX = e.changedTouches[0].clientX; 380 | clientY = e.changedTouches[0].clientY; 381 | } else if (typeof e.clientX === 'number') { 382 | clientX = e.clientX; 383 | clientY = e.clientY; 384 | } 385 | 386 | x = clientX - targetRect.left; 387 | y = clientY - targetRect.top; 388 | return { x: x, y: y }; 389 | }, 390 | 391 | 392 | getViewPos : function () { 393 | var doc = document.documentElement; 394 | return [ 395 | (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0), 396 | (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0) 397 | ]; 398 | }, 399 | 400 | 401 | getViewSize : function () { 402 | var doc = document.documentElement; 403 | return [ 404 | (window.innerWidth || doc.clientWidth), 405 | (window.innerHeight || doc.clientHeight), 406 | ]; 407 | }, 408 | 409 | 410 | redrawPosition : function () { 411 | 412 | if (jsc.picker && jsc.picker.owner) { 413 | var thisObj = jsc.picker.owner; 414 | 415 | var tp, vp; 416 | 417 | if (thisObj.fixed) { 418 | // Fixed elements are positioned relative to viewport, 419 | // therefore we can ignore the scroll offset 420 | tp = jsc.getElementPos(thisObj.targetElement, true); // target pos 421 | vp = [0, 0]; // view pos 422 | } else { 423 | tp = jsc.getElementPos(thisObj.targetElement); // target pos 424 | vp = jsc.getViewPos(); // view pos 425 | } 426 | 427 | var ts = jsc.getElementSize(thisObj.targetElement); // target size 428 | var vs = jsc.getViewSize(); // view size 429 | var ps = jsc.getPickerOuterDims(thisObj); // picker size 430 | var a, b, c; 431 | switch (thisObj.position.toLowerCase()) { 432 | case 'left': a=1; b=0; c=-1; break; 433 | case 'right':a=1; b=0; c=1; break; 434 | case 'top': a=0; b=1; c=-1; break; 435 | default: a=0; b=1; c=1; break; 436 | } 437 | var l = (ts[b]+ps[b])/2; 438 | 439 | // compute picker position 440 | if (!thisObj.smartPosition) { 441 | var pp = [ 442 | tp[a], 443 | tp[b]+ts[b]-l+l*c 444 | ]; 445 | } else { 446 | var pp = [ 447 | -vp[a]+tp[a]+ps[a] > vs[a] ? 448 | (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) : 449 | tp[a], 450 | -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ? 451 | (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) : 452 | (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c) 453 | ]; 454 | } 455 | 456 | var x = pp[a]; 457 | var y = pp[b]; 458 | var positionValue = thisObj.fixed ? 'fixed' : 'absolute'; 459 | var contractShadow = 460 | (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) && 461 | (pp[1] + ps[1] < tp[1] + ts[1]); 462 | 463 | jsc._drawPosition(thisObj, x, y, positionValue, contractShadow); 464 | } 465 | }, 466 | 467 | 468 | _drawPosition : function (thisObj, x, y, positionValue, contractShadow) { 469 | var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px 470 | 471 | jsc.picker.wrap.style.position = positionValue; 472 | jsc.picker.wrap.style.left = x + 'px'; 473 | jsc.picker.wrap.style.top = y + 'px'; 474 | 475 | jsc.setBoxShadow( 476 | jsc.picker.boxS, 477 | thisObj.shadow ? 478 | new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) : 479 | null); 480 | }, 481 | 482 | 483 | getPickerDims : function (thisObj) { 484 | var displaySlider = !!jsc.getSliderComponent(thisObj); 485 | var dims = [ 486 | 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.width + 487 | (displaySlider ? 2 * thisObj.insetWidth + jsc.getPadToSliderPadding(thisObj) + thisObj.sliderSize : 0), 488 | 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.height + 489 | (thisObj.closable ? 2 * thisObj.insetWidth + thisObj.padding + thisObj.buttonHeight : 0) 490 | ]; 491 | return dims; 492 | }, 493 | 494 | 495 | getPickerOuterDims : function (thisObj) { 496 | var dims = jsc.getPickerDims(thisObj); 497 | return [ 498 | dims[0] + 2 * thisObj.borderWidth, 499 | dims[1] + 2 * thisObj.borderWidth 500 | ]; 501 | }, 502 | 503 | 504 | getPadToSliderPadding : function (thisObj) { 505 | return Math.max(thisObj.padding, 1.5 * (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness)); 506 | }, 507 | 508 | 509 | getPadYComponent : function (thisObj) { 510 | switch (thisObj.mode.charAt(1).toLowerCase()) { 511 | case 'v': return 'v'; break; 512 | } 513 | return 's'; 514 | }, 515 | 516 | 517 | getSliderComponent : function (thisObj) { 518 | if (thisObj.mode.length > 2) { 519 | switch (thisObj.mode.charAt(2).toLowerCase()) { 520 | case 's': return 's'; break; 521 | case 'v': return 'v'; break; 522 | } 523 | } 524 | return null; 525 | }, 526 | 527 | 528 | onDocumentMouseDown : function (e) { 529 | if (!e) { e = window.event; } 530 | var target = e.target || e.srcElement; 531 | 532 | if (target._jscLinkedInstance) { 533 | if (target._jscLinkedInstance.showOnClick) { 534 | target._jscLinkedInstance.show(); 535 | } 536 | } else if (target._jscControlName) { 537 | jsc.onControlPointerStart(e, target, target._jscControlName, 'mouse'); 538 | } else { 539 | // Mouse is outside the picker controls -> hide the color picker! 540 | if (jsc.picker && jsc.picker.owner) { 541 | jsc.picker.owner.hide(); 542 | } 543 | } 544 | }, 545 | 546 | 547 | onDocumentTouchStart : function (e) { 548 | if (!e) { e = window.event; } 549 | var target = e.target || e.srcElement; 550 | 551 | if (target._jscLinkedInstance) { 552 | if (target._jscLinkedInstance.showOnClick) { 553 | target._jscLinkedInstance.show(); 554 | } 555 | } else if (target._jscControlName) { 556 | jsc.onControlPointerStart(e, target, target._jscControlName, 'touch'); 557 | } else { 558 | if (jsc.picker && jsc.picker.owner) { 559 | jsc.picker.owner.hide(); 560 | } 561 | } 562 | }, 563 | 564 | 565 | onWindowResize : function (e) { 566 | jsc.redrawPosition(); 567 | }, 568 | 569 | 570 | onParentScroll : function (e) { 571 | // hide the picker when one of the parent elements is scrolled 572 | if (jsc.picker && jsc.picker.owner) { 573 | jsc.picker.owner.hide(); 574 | } 575 | }, 576 | 577 | 578 | _pointerMoveEvent : { 579 | mouse: 'mousemove', 580 | touch: 'touchmove' 581 | }, 582 | _pointerEndEvent : { 583 | mouse: 'mouseup', 584 | touch: 'touchend' 585 | }, 586 | 587 | 588 | _pointerOrigin : null, 589 | _capturedTarget : null, 590 | 591 | 592 | onControlPointerStart : function (e, target, controlName, pointerType) { 593 | var thisObj = target._jscInstance; 594 | 595 | jsc.preventDefault(e); 596 | jsc.captureTarget(target); 597 | 598 | var registerDragEvents = function (doc, offset) { 599 | jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType], 600 | jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset)); 601 | jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType], 602 | jsc.onDocumentPointerEnd(e, target, controlName, pointerType)); 603 | }; 604 | 605 | registerDragEvents(document, [0, 0]); 606 | 607 | if (window.parent && window.frameElement) { 608 | var rect = window.frameElement.getBoundingClientRect(); 609 | var ofs = [-rect.left, -rect.top]; 610 | registerDragEvents(window.parent.window.document, ofs); 611 | } 612 | 613 | var abs = jsc.getAbsPointerPos(e); 614 | var rel = jsc.getRelPointerPos(e); 615 | jsc._pointerOrigin = { 616 | x: abs.x - rel.x, 617 | y: abs.y - rel.y 618 | }; 619 | 620 | switch (controlName) { 621 | case 'pad': 622 | // if the slider is at the bottom, move it up 623 | switch (jsc.getSliderComponent(thisObj)) { 624 | case 's': if (thisObj.hsv[1] === 0) { thisObj.fromHSV(null, 100, null); }; break; 625 | case 'v': if (thisObj.hsv[2] === 0) { thisObj.fromHSV(null, null, 100); }; break; 626 | } 627 | jsc.setPad(thisObj, e, 0, 0); 628 | break; 629 | 630 | case 'sld': 631 | jsc.setSld(thisObj, e, 0); 632 | break; 633 | } 634 | 635 | jsc.dispatchFineChange(thisObj); 636 | }, 637 | 638 | 639 | onDocumentPointerMove : function (e, target, controlName, pointerType, offset) { 640 | return function (e) { 641 | var thisObj = target._jscInstance; 642 | switch (controlName) { 643 | case 'pad': 644 | if (!e) { e = window.event; } 645 | jsc.setPad(thisObj, e, offset[0], offset[1]); 646 | jsc.dispatchFineChange(thisObj); 647 | break; 648 | 649 | case 'sld': 650 | if (!e) { e = window.event; } 651 | jsc.setSld(thisObj, e, offset[1]); 652 | jsc.dispatchFineChange(thisObj); 653 | break; 654 | } 655 | } 656 | }, 657 | 658 | 659 | onDocumentPointerEnd : function (e, target, controlName, pointerType) { 660 | return function (e) { 661 | var thisObj = target._jscInstance; 662 | jsc.detachGroupEvents('drag'); 663 | jsc.releaseTarget(); 664 | // Always dispatch changes after detaching outstanding mouse handlers, 665 | // in case some user interaction will occur in user's onchange callback 666 | // that would intrude with current mouse events 667 | jsc.dispatchChange(thisObj); 668 | }; 669 | }, 670 | 671 | 672 | dispatchChange : function (thisObj) { 673 | if (thisObj.valueElement) { 674 | if (jsc.isElementType(thisObj.valueElement, 'input')) { 675 | jsc.fireEvent(thisObj.valueElement, 'change'); 676 | } 677 | } 678 | }, 679 | 680 | 681 | dispatchFineChange : function (thisObj) { 682 | if (thisObj.onFineChange) { 683 | var callback; 684 | if (typeof thisObj.onFineChange === 'string') { 685 | callback = new Function (thisObj.onFineChange); 686 | } else { 687 | callback = thisObj.onFineChange; 688 | } 689 | callback.call(thisObj); 690 | } 691 | }, 692 | 693 | 694 | setPad : function (thisObj, e, ofsX, ofsY) { 695 | var pointerAbs = jsc.getAbsPointerPos(e); 696 | var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.insetWidth; 697 | var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; 698 | 699 | var xVal = x * (360 / (thisObj.width - 1)); 700 | var yVal = 100 - (y * (100 / (thisObj.height - 1))); 701 | 702 | switch (jsc.getPadYComponent(thisObj)) { 703 | case 's': thisObj.fromHSV(xVal, yVal, null, jsc.leaveSld); break; 704 | case 'v': thisObj.fromHSV(xVal, null, yVal, jsc.leaveSld); break; 705 | } 706 | }, 707 | 708 | 709 | setSld : function (thisObj, e, ofsY) { 710 | var pointerAbs = jsc.getAbsPointerPos(e); 711 | var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; 712 | 713 | var yVal = 100 - (y * (100 / (thisObj.height - 1))); 714 | 715 | switch (jsc.getSliderComponent(thisObj)) { 716 | case 's': thisObj.fromHSV(null, yVal, null, jsc.leavePad); break; 717 | case 'v': thisObj.fromHSV(null, null, yVal, jsc.leavePad); break; 718 | } 719 | }, 720 | 721 | 722 | _vmlNS : 'jsc_vml_', 723 | _vmlCSS : 'jsc_vml_css_', 724 | _vmlReady : false, 725 | 726 | 727 | initVML : function () { 728 | if (!jsc._vmlReady) { 729 | // init VML namespace 730 | var doc = document; 731 | if (!doc.namespaces[jsc._vmlNS]) { 732 | doc.namespaces.add(jsc._vmlNS, 'urn:schemas-microsoft-com:vml'); 733 | } 734 | if (!doc.styleSheets[jsc._vmlCSS]) { 735 | var tags = ['shape', 'shapetype', 'group', 'background', 'path', 'formulas', 'handles', 'fill', 'stroke', 'shadow', 'textbox', 'textpath', 'imagedata', 'line', 'polyline', 'curve', 'rect', 'roundrect', 'oval', 'arc', 'image']; 736 | var ss = doc.createStyleSheet(); 737 | ss.owningElement.id = jsc._vmlCSS; 738 | for (var i = 0; i < tags.length; i += 1) { 739 | ss.addRule(jsc._vmlNS + '\\:' + tags[i], 'behavior:url(#default#VML);'); 740 | } 741 | } 742 | jsc._vmlReady = true; 743 | } 744 | }, 745 | 746 | 747 | createPalette : function () { 748 | 749 | var paletteObj = { 750 | elm: null, 751 | draw: null 752 | }; 753 | 754 | if (jsc.isCanvasSupported) { 755 | // Canvas implementation for modern browsers 756 | 757 | var canvas = document.createElement('canvas'); 758 | var ctx = canvas.getContext('2d'); 759 | 760 | var drawFunc = function (width, height, type) { 761 | canvas.width = width; 762 | canvas.height = height; 763 | 764 | ctx.clearRect(0, 0, canvas.width, canvas.height); 765 | 766 | var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0); 767 | hGrad.addColorStop(0 / 6, '#F00'); 768 | hGrad.addColorStop(1 / 6, '#FF0'); 769 | hGrad.addColorStop(2 / 6, '#0F0'); 770 | hGrad.addColorStop(3 / 6, '#0FF'); 771 | hGrad.addColorStop(4 / 6, '#00F'); 772 | hGrad.addColorStop(5 / 6, '#F0F'); 773 | hGrad.addColorStop(6 / 6, '#F00'); 774 | 775 | ctx.fillStyle = hGrad; 776 | ctx.fillRect(0, 0, canvas.width, canvas.height); 777 | 778 | var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height); 779 | switch (type.toLowerCase()) { 780 | case 's': 781 | vGrad.addColorStop(0, 'rgba(255,255,255,0)'); 782 | vGrad.addColorStop(1, 'rgba(255,255,255,1)'); 783 | break; 784 | case 'v': 785 | vGrad.addColorStop(0, 'rgba(0,0,0,0)'); 786 | vGrad.addColorStop(1, 'rgba(0,0,0,1)'); 787 | break; 788 | } 789 | ctx.fillStyle = vGrad; 790 | ctx.fillRect(0, 0, canvas.width, canvas.height); 791 | }; 792 | 793 | paletteObj.elm = canvas; 794 | paletteObj.draw = drawFunc; 795 | 796 | } else { 797 | // VML fallback for IE 7 and 8 798 | 799 | jsc.initVML(); 800 | 801 | var vmlContainer = document.createElement('div'); 802 | vmlContainer.style.position = 'relative'; 803 | vmlContainer.style.overflow = 'hidden'; 804 | 805 | var hGrad = document.createElement(jsc._vmlNS + ':fill'); 806 | hGrad.type = 'gradient'; 807 | hGrad.method = 'linear'; 808 | hGrad.angle = '90'; 809 | hGrad.colors = '16.67% #F0F, 33.33% #00F, 50% #0FF, 66.67% #0F0, 83.33% #FF0' 810 | 811 | var hRect = document.createElement(jsc._vmlNS + ':rect'); 812 | hRect.style.position = 'absolute'; 813 | hRect.style.left = -1 + 'px'; 814 | hRect.style.top = -1 + 'px'; 815 | hRect.stroked = false; 816 | hRect.appendChild(hGrad); 817 | vmlContainer.appendChild(hRect); 818 | 819 | var vGrad = document.createElement(jsc._vmlNS + ':fill'); 820 | vGrad.type = 'gradient'; 821 | vGrad.method = 'linear'; 822 | vGrad.angle = '180'; 823 | vGrad.opacity = '0'; 824 | 825 | var vRect = document.createElement(jsc._vmlNS + ':rect'); 826 | vRect.style.position = 'absolute'; 827 | vRect.style.left = -1 + 'px'; 828 | vRect.style.top = -1 + 'px'; 829 | vRect.stroked = false; 830 | vRect.appendChild(vGrad); 831 | vmlContainer.appendChild(vRect); 832 | 833 | var drawFunc = function (width, height, type) { 834 | vmlContainer.style.width = width + 'px'; 835 | vmlContainer.style.height = height + 'px'; 836 | 837 | hRect.style.width = 838 | vRect.style.width = 839 | (width + 1) + 'px'; 840 | hRect.style.height = 841 | vRect.style.height = 842 | (height + 1) + 'px'; 843 | 844 | // Colors must be specified during every redraw, otherwise IE won't display 845 | // a full gradient during a subsequential redraw 846 | hGrad.color = '#F00'; 847 | hGrad.color2 = '#F00'; 848 | 849 | switch (type.toLowerCase()) { 850 | case 's': 851 | vGrad.color = vGrad.color2 = '#FFF'; 852 | break; 853 | case 'v': 854 | vGrad.color = vGrad.color2 = '#000'; 855 | break; 856 | } 857 | }; 858 | 859 | paletteObj.elm = vmlContainer; 860 | paletteObj.draw = drawFunc; 861 | } 862 | 863 | return paletteObj; 864 | }, 865 | 866 | 867 | createSliderGradient : function () { 868 | 869 | var sliderObj = { 870 | elm: null, 871 | draw: null 872 | }; 873 | 874 | if (jsc.isCanvasSupported) { 875 | // Canvas implementation for modern browsers 876 | 877 | var canvas = document.createElement('canvas'); 878 | var ctx = canvas.getContext('2d'); 879 | 880 | var drawFunc = function (width, height, color1, color2) { 881 | canvas.width = width; 882 | canvas.height = height; 883 | 884 | ctx.clearRect(0, 0, canvas.width, canvas.height); 885 | 886 | var grad = ctx.createLinearGradient(0, 0, 0, canvas.height); 887 | grad.addColorStop(0, color1); 888 | grad.addColorStop(1, color2); 889 | 890 | ctx.fillStyle = grad; 891 | ctx.fillRect(0, 0, canvas.width, canvas.height); 892 | }; 893 | 894 | sliderObj.elm = canvas; 895 | sliderObj.draw = drawFunc; 896 | 897 | } else { 898 | // VML fallback for IE 7 and 8 899 | 900 | jsc.initVML(); 901 | 902 | var vmlContainer = document.createElement('div'); 903 | vmlContainer.style.position = 'relative'; 904 | vmlContainer.style.overflow = 'hidden'; 905 | 906 | var grad = document.createElement(jsc._vmlNS + ':fill'); 907 | grad.type = 'gradient'; 908 | grad.method = 'linear'; 909 | grad.angle = '180'; 910 | 911 | var rect = document.createElement(jsc._vmlNS + ':rect'); 912 | rect.style.position = 'absolute'; 913 | rect.style.left = -1 + 'px'; 914 | rect.style.top = -1 + 'px'; 915 | rect.stroked = false; 916 | rect.appendChild(grad); 917 | vmlContainer.appendChild(rect); 918 | 919 | var drawFunc = function (width, height, color1, color2) { 920 | vmlContainer.style.width = width + 'px'; 921 | vmlContainer.style.height = height + 'px'; 922 | 923 | rect.style.width = (width + 1) + 'px'; 924 | rect.style.height = (height + 1) + 'px'; 925 | 926 | grad.color = color1; 927 | grad.color2 = color2; 928 | }; 929 | 930 | sliderObj.elm = vmlContainer; 931 | sliderObj.draw = drawFunc; 932 | } 933 | 934 | return sliderObj; 935 | }, 936 | 937 | 938 | leaveValue : 1<<0, 939 | leaveStyle : 1<<1, 940 | leavePad : 1<<2, 941 | leaveSld : 1<<3, 942 | 943 | 944 | BoxShadow : (function () { 945 | var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) { 946 | this.hShadow = hShadow; 947 | this.vShadow = vShadow; 948 | this.blur = blur; 949 | this.spread = spread; 950 | this.color = color; 951 | this.inset = !!inset; 952 | }; 953 | 954 | BoxShadow.prototype.toString = function () { 955 | var vals = [ 956 | Math.round(this.hShadow) + 'px', 957 | Math.round(this.vShadow) + 'px', 958 | Math.round(this.blur) + 'px', 959 | Math.round(this.spread) + 'px', 960 | this.color 961 | ]; 962 | if (this.inset) { 963 | vals.push('inset'); 964 | } 965 | return vals.join(' '); 966 | }; 967 | 968 | return BoxShadow; 969 | })(), 970 | 971 | 972 | // 973 | // Usage: 974 | // var myColor = new jscolor( [, ]) 975 | // 976 | 977 | jscolor : function (targetElement, options) { 978 | 979 | // General options 980 | // 981 | this.value = null; // initial HEX color. To change it later, use methods fromString(), fromHSV() and fromRGB() 982 | this.valueElement = targetElement; // element that will be used to display and input the color code 983 | this.styleElement = targetElement; // element that will preview the picked color using CSS backgroundColor 984 | this.required = true; // whether the associated text can be left empty 985 | this.refine = true; // whether to refine the entered color code (e.g. uppercase it and remove whitespace) 986 | this.hash = false; // whether to prefix the HEX color code with # symbol 987 | this.uppercase = true; // whether to uppercase the color code 988 | this.onFineChange = null; // called instantly every time the color changes (value can be either a function or a string with javascript code) 989 | this.activeClass = 'jscolor-active'; // class to be set to the target element when a picker window is open on it 990 | this.minS = 0; // min allowed saturation (0 - 100) 991 | this.maxS = 100; // max allowed saturation (0 - 100) 992 | this.minV = 0; // min allowed value (brightness) (0 - 100) 993 | this.maxV = 100; // max allowed value (brightness) (0 - 100) 994 | 995 | // Accessing the picked color 996 | // 997 | this.hsv = [0, 0, 100]; // read-only [0-360, 0-100, 0-100] 998 | this.rgb = [255, 255, 255]; // read-only [0-255, 0-255, 0-255] 999 | 1000 | // Color Picker options 1001 | // 1002 | this.width = 181; // width of color palette (in px) 1003 | this.height = 101; // height of color palette (in px) 1004 | this.showOnClick = true; // whether to display the color picker when user clicks on its target element 1005 | this.mode = 'HSV'; // HSV | HVS | HS | HV - layout of the color picker controls 1006 | this.position = 'bottom'; // left | right | top | bottom - position relative to the target element 1007 | this.smartPosition = true; // automatically change picker position when there is not enough space for it 1008 | this.sliderSize = 16; // px 1009 | this.crossSize = 8; // px 1010 | this.closable = false; // whether to display the Close button 1011 | this.closeText = 'Close'; 1012 | this.buttonColor = '#000000'; // CSS color 1013 | this.buttonHeight = 18; // px 1014 | this.padding = 12; // px 1015 | this.backgroundColor = '#FFFFFF'; // CSS color 1016 | this.borderWidth = 1; // px 1017 | this.borderColor = '#BBBBBB'; // CSS color 1018 | this.borderRadius = 8; // px 1019 | this.insetWidth = 1; // px 1020 | this.insetColor = '#BBBBBB'; // CSS color 1021 | this.shadow = true; // whether to display shadow 1022 | this.shadowBlur = 15; // px 1023 | this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color 1024 | this.pointerColor = '#4C4C4C'; // px 1025 | this.pointerBorderColor = '#FFFFFF'; // px 1026 | this.pointerBorderWidth = 1; // px 1027 | this.pointerThickness = 2; // px 1028 | this.zIndex = 1000; 1029 | this.container = null; // where to append the color picker (BODY element by default) 1030 | 1031 | 1032 | for (var opt in options) { 1033 | if (options.hasOwnProperty(opt)) { 1034 | this[opt] = options[opt]; 1035 | } 1036 | } 1037 | 1038 | 1039 | this.hide = function () { 1040 | if (isPickerOwner()) { 1041 | detachPicker(); 1042 | } 1043 | }; 1044 | 1045 | 1046 | this.show = function () { 1047 | drawPicker(); 1048 | }; 1049 | 1050 | 1051 | this.redraw = function () { 1052 | if (isPickerOwner()) { 1053 | drawPicker(); 1054 | } 1055 | }; 1056 | 1057 | 1058 | this.importColor = function () { 1059 | if (!this.valueElement) { 1060 | this.exportColor(); 1061 | } else { 1062 | if (jsc.isElementType(this.valueElement, 'input')) { 1063 | if (!this.refine) { 1064 | if (!this.fromString(this.valueElement.value, jsc.leaveValue)) { 1065 | if (this.styleElement) { 1066 | this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; 1067 | this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; 1068 | this.styleElement.style.color = this.styleElement._jscOrigStyle.color; 1069 | } 1070 | this.exportColor(jsc.leaveValue | jsc.leaveStyle); 1071 | } 1072 | } else if (!this.required && /^\s*$/.test(this.valueElement.value)) { 1073 | this.valueElement.value = ''; 1074 | if (this.styleElement) { 1075 | this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; 1076 | this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; 1077 | this.styleElement.style.color = this.styleElement._jscOrigStyle.color; 1078 | } 1079 | this.exportColor(jsc.leaveValue | jsc.leaveStyle); 1080 | 1081 | } else if (this.fromString(this.valueElement.value)) { 1082 | // managed to import color successfully from the value -> OK, don't do anything 1083 | } else { 1084 | this.exportColor(); 1085 | } 1086 | } else { 1087 | // not an input element -> doesn't have any value 1088 | this.exportColor(); 1089 | } 1090 | } 1091 | }; 1092 | 1093 | 1094 | this.exportColor = function (flags) { 1095 | if (!(flags & jsc.leaveValue) && this.valueElement) { 1096 | var value = this.toString(); 1097 | if (this.uppercase) { value = value.toUpperCase(); } 1098 | if (this.hash) { value = '#' + value; } 1099 | 1100 | if (jsc.isElementType(this.valueElement, 'input')) { 1101 | this.valueElement.value = value; 1102 | } else { 1103 | this.valueElement.innerHTML = value; 1104 | } 1105 | } 1106 | if (!(flags & jsc.leaveStyle)) { 1107 | if (this.styleElement) { 1108 | this.styleElement.style.backgroundImage = 'none'; 1109 | this.styleElement.style.backgroundColor = '#' + this.toString(); 1110 | this.styleElement.style.color = this.isLight() ? '#000' : '#FFF'; 1111 | } 1112 | } 1113 | if (!(flags & jsc.leavePad) && isPickerOwner()) { 1114 | redrawPad(); 1115 | } 1116 | if (!(flags & jsc.leaveSld) && isPickerOwner()) { 1117 | redrawSld(); 1118 | } 1119 | }; 1120 | 1121 | 1122 | // h: 0-360 1123 | // s: 0-100 1124 | // v: 0-100 1125 | // 1126 | this.fromHSV = function (h, s, v, flags) { // null = don't change 1127 | if (h !== null) { 1128 | if (isNaN(h)) { return false; } 1129 | h = Math.max(0, Math.min(360, h)); 1130 | } 1131 | if (s !== null) { 1132 | if (isNaN(s)) { return false; } 1133 | s = Math.max(0, Math.min(100, this.maxS, s), this.minS); 1134 | } 1135 | if (v !== null) { 1136 | if (isNaN(v)) { return false; } 1137 | v = Math.max(0, Math.min(100, this.maxV, v), this.minV); 1138 | } 1139 | 1140 | this.rgb = HSV_RGB( 1141 | h===null ? this.hsv[0] : (this.hsv[0]=h), 1142 | s===null ? this.hsv[1] : (this.hsv[1]=s), 1143 | v===null ? this.hsv[2] : (this.hsv[2]=v) 1144 | ); 1145 | 1146 | this.exportColor(flags); 1147 | }; 1148 | 1149 | 1150 | // r: 0-255 1151 | // g: 0-255 1152 | // b: 0-255 1153 | // 1154 | this.fromRGB = function (r, g, b, flags) { // null = don't change 1155 | if (r !== null) { 1156 | if (isNaN(r)) { return false; } 1157 | r = Math.max(0, Math.min(255, r)); 1158 | } 1159 | if (g !== null) { 1160 | if (isNaN(g)) { return false; } 1161 | g = Math.max(0, Math.min(255, g)); 1162 | } 1163 | if (b !== null) { 1164 | if (isNaN(b)) { return false; } 1165 | b = Math.max(0, Math.min(255, b)); 1166 | } 1167 | 1168 | var hsv = RGB_HSV( 1169 | r===null ? this.rgb[0] : r, 1170 | g===null ? this.rgb[1] : g, 1171 | b===null ? this.rgb[2] : b 1172 | ); 1173 | if (hsv[0] !== null) { 1174 | this.hsv[0] = Math.max(0, Math.min(360, hsv[0])); 1175 | } 1176 | if (hsv[2] !== 0) { 1177 | this.hsv[1] = hsv[1]===null ? null : Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1])); 1178 | } 1179 | this.hsv[2] = hsv[2]===null ? null : Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2])); 1180 | 1181 | // update RGB according to final HSV, as some values might be trimmed 1182 | var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]); 1183 | this.rgb[0] = rgb[0]; 1184 | this.rgb[1] = rgb[1]; 1185 | this.rgb[2] = rgb[2]; 1186 | 1187 | this.exportColor(flags); 1188 | }; 1189 | 1190 | 1191 | this.fromString = function (str, flags) { 1192 | var m; 1193 | if (m = str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)) { 1194 | // HEX notation 1195 | // 1196 | 1197 | if (m[1].length === 6) { 1198 | // 6-char notation 1199 | this.fromRGB( 1200 | parseInt(m[1].substr(0,2),16), 1201 | parseInt(m[1].substr(2,2),16), 1202 | parseInt(m[1].substr(4,2),16), 1203 | flags 1204 | ); 1205 | } else { 1206 | // 3-char notation 1207 | this.fromRGB( 1208 | parseInt(m[1].charAt(0) + m[1].charAt(0),16), 1209 | parseInt(m[1].charAt(1) + m[1].charAt(1),16), 1210 | parseInt(m[1].charAt(2) + m[1].charAt(2),16), 1211 | flags 1212 | ); 1213 | } 1214 | return true; 1215 | 1216 | } else if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) { 1217 | var params = m[1].split(','); 1218 | var re = /^\s*(\d*)(\.\d+)?\s*$/; 1219 | var mR, mG, mB; 1220 | if ( 1221 | params.length >= 3 && 1222 | (mR = params[0].match(re)) && 1223 | (mG = params[1].match(re)) && 1224 | (mB = params[2].match(re)) 1225 | ) { 1226 | var r = parseFloat((mR[1] || '0') + (mR[2] || '')); 1227 | var g = parseFloat((mG[1] || '0') + (mG[2] || '')); 1228 | var b = parseFloat((mB[1] || '0') + (mB[2] || '')); 1229 | this.fromRGB(r, g, b, flags); 1230 | return true; 1231 | } 1232 | } 1233 | return false; 1234 | }; 1235 | 1236 | 1237 | this.toString = function () { 1238 | return ( 1239 | (0x100 | Math.round(this.rgb[0])).toString(16).substr(1) + 1240 | (0x100 | Math.round(this.rgb[1])).toString(16).substr(1) + 1241 | (0x100 | Math.round(this.rgb[2])).toString(16).substr(1) 1242 | ); 1243 | }; 1244 | 1245 | 1246 | this.toHEXString = function () { 1247 | return '#' + this.toString().toUpperCase(); 1248 | }; 1249 | 1250 | 1251 | this.toRGBString = function () { 1252 | return ('rgb(' + 1253 | Math.round(this.rgb[0]) + ',' + 1254 | Math.round(this.rgb[1]) + ',' + 1255 | Math.round(this.rgb[2]) + ')' 1256 | ); 1257 | }; 1258 | 1259 | 1260 | this.isLight = function () { 1261 | return ( 1262 | 0.213 * this.rgb[0] + 1263 | 0.715 * this.rgb[1] + 1264 | 0.072 * this.rgb[2] > 1265 | 255 / 2 1266 | ); 1267 | }; 1268 | 1269 | 1270 | this._processParentElementsInDOM = function () { 1271 | if (this._linkedElementsProcessed) { return; } 1272 | this._linkedElementsProcessed = true; 1273 | 1274 | var elm = this.targetElement; 1275 | do { 1276 | // If the target element or one of its parent nodes has fixed position, 1277 | // then use fixed positioning instead 1278 | // 1279 | // Note: In Firefox, getComputedStyle returns null in a hidden iframe, 1280 | // that's why we need to check if the returned style object is non-empty 1281 | var currStyle = jsc.getStyle(elm); 1282 | if (currStyle && currStyle.position.toLowerCase() === 'fixed') { 1283 | this.fixed = true; 1284 | } 1285 | 1286 | if (elm !== this.targetElement) { 1287 | // Ensure to attach onParentScroll only once to each parent element 1288 | // (multiple targetElements can share the same parent nodes) 1289 | // 1290 | // Note: It's not just offsetParents that can be scrollable, 1291 | // that's why we loop through all parent nodes 1292 | if (!elm._jscEventsAttached) { 1293 | jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); 1294 | elm._jscEventsAttached = true; 1295 | } 1296 | } 1297 | } while ((elm = elm.parentNode) && !jsc.isElementType(elm, 'body')); 1298 | }; 1299 | 1300 | 1301 | // r: 0-255 1302 | // g: 0-255 1303 | // b: 0-255 1304 | // 1305 | // returns: [ 0-360, 0-100, 0-100 ] 1306 | // 1307 | function RGB_HSV (r, g, b) { 1308 | r /= 255; 1309 | g /= 255; 1310 | b /= 255; 1311 | var n = Math.min(Math.min(r,g),b); 1312 | var v = Math.max(Math.max(r,g),b); 1313 | var m = v - n; 1314 | if (m === 0) { return [ null, 0, 100 * v ]; } 1315 | var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); 1316 | return [ 1317 | 60 * (h===6?0:h), 1318 | 100 * (m/v), 1319 | 100 * v 1320 | ]; 1321 | } 1322 | 1323 | 1324 | // h: 0-360 1325 | // s: 0-100 1326 | // v: 0-100 1327 | // 1328 | // returns: [ 0-255, 0-255, 0-255 ] 1329 | // 1330 | function HSV_RGB (h, s, v) { 1331 | var u = 255 * (v / 100); 1332 | 1333 | if (h === null) { 1334 | return [ u, u, u ]; 1335 | } 1336 | 1337 | h /= 60; 1338 | s /= 100; 1339 | 1340 | var i = Math.floor(h); 1341 | var f = i%2 ? h-i : 1-(h-i); 1342 | var m = u * (1 - s); 1343 | var n = u * (1 - s * f); 1344 | switch (i) { 1345 | case 6: 1346 | case 0: return [u,n,m]; 1347 | case 1: return [n,u,m]; 1348 | case 2: return [m,u,n]; 1349 | case 3: return [m,n,u]; 1350 | case 4: return [n,m,u]; 1351 | case 5: return [u,m,n]; 1352 | } 1353 | } 1354 | 1355 | 1356 | function detachPicker () { 1357 | jsc.unsetClass(THIS.targetElement, THIS.activeClass); 1358 | jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap); 1359 | delete jsc.picker.owner; 1360 | } 1361 | 1362 | 1363 | function drawPicker () { 1364 | 1365 | // At this point, when drawing the picker, we know what the parent elements are 1366 | // and we can do all related DOM operations, such as registering events on them 1367 | // or checking their positioning 1368 | THIS._processParentElementsInDOM(); 1369 | 1370 | if (!jsc.picker) { 1371 | jsc.picker = { 1372 | owner: null, 1373 | wrap : document.createElement('div'), 1374 | box : document.createElement('div'), 1375 | boxS : document.createElement('div'), // shadow area 1376 | boxB : document.createElement('div'), // border 1377 | pad : document.createElement('div'), 1378 | padB : document.createElement('div'), // border 1379 | padM : document.createElement('div'), // mouse/touch area 1380 | padPal : jsc.createPalette(), 1381 | cross : document.createElement('div'), 1382 | crossBY : document.createElement('div'), // border Y 1383 | crossBX : document.createElement('div'), // border X 1384 | crossLY : document.createElement('div'), // line Y 1385 | crossLX : document.createElement('div'), // line X 1386 | sld : document.createElement('div'), 1387 | sldB : document.createElement('div'), // border 1388 | sldM : document.createElement('div'), // mouse/touch area 1389 | sldGrad : jsc.createSliderGradient(), 1390 | sldPtrS : document.createElement('div'), // slider pointer spacer 1391 | sldPtrIB : document.createElement('div'), // slider pointer inner border 1392 | sldPtrMB : document.createElement('div'), // slider pointer middle border 1393 | sldPtrOB : document.createElement('div'), // slider pointer outer border 1394 | btn : document.createElement('div'), 1395 | btnT : document.createElement('span') // text 1396 | }; 1397 | 1398 | jsc.picker.pad.appendChild(jsc.picker.padPal.elm); 1399 | jsc.picker.padB.appendChild(jsc.picker.pad); 1400 | jsc.picker.cross.appendChild(jsc.picker.crossBY); 1401 | jsc.picker.cross.appendChild(jsc.picker.crossBX); 1402 | jsc.picker.cross.appendChild(jsc.picker.crossLY); 1403 | jsc.picker.cross.appendChild(jsc.picker.crossLX); 1404 | jsc.picker.padB.appendChild(jsc.picker.cross); 1405 | jsc.picker.box.appendChild(jsc.picker.padB); 1406 | jsc.picker.box.appendChild(jsc.picker.padM); 1407 | 1408 | jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm); 1409 | jsc.picker.sldB.appendChild(jsc.picker.sld); 1410 | jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB); 1411 | jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB); 1412 | jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB); 1413 | jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS); 1414 | jsc.picker.box.appendChild(jsc.picker.sldB); 1415 | jsc.picker.box.appendChild(jsc.picker.sldM); 1416 | 1417 | jsc.picker.btn.appendChild(jsc.picker.btnT); 1418 | jsc.picker.box.appendChild(jsc.picker.btn); 1419 | 1420 | jsc.picker.boxB.appendChild(jsc.picker.box); 1421 | jsc.picker.wrap.appendChild(jsc.picker.boxS); 1422 | jsc.picker.wrap.appendChild(jsc.picker.boxB); 1423 | } 1424 | 1425 | var p = jsc.picker; 1426 | 1427 | var displaySlider = !!jsc.getSliderComponent(THIS); 1428 | var dims = jsc.getPickerDims(THIS); 1429 | var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); 1430 | var padToSliderPadding = jsc.getPadToSliderPadding(THIS); 1431 | var borderRadius = Math.min( 1432 | THIS.borderRadius, 1433 | Math.round(THIS.padding * Math.PI)); // px 1434 | var padCursor = 'crosshair'; 1435 | 1436 | // wrap 1437 | p.wrap.style.clear = 'both'; 1438 | p.wrap.style.width = (dims[0] + 2 * THIS.borderWidth) + 'px'; 1439 | p.wrap.style.height = (dims[1] + 2 * THIS.borderWidth) + 'px'; 1440 | p.wrap.style.zIndex = THIS.zIndex; 1441 | 1442 | // picker 1443 | p.box.style.width = dims[0] + 'px'; 1444 | p.box.style.height = dims[1] + 'px'; 1445 | 1446 | p.boxS.style.position = 'absolute'; 1447 | p.boxS.style.left = '0'; 1448 | p.boxS.style.top = '0'; 1449 | p.boxS.style.width = '100%'; 1450 | p.boxS.style.height = '100%'; 1451 | jsc.setBorderRadius(p.boxS, borderRadius + 'px'); 1452 | 1453 | // picker border 1454 | p.boxB.style.position = 'relative'; 1455 | p.boxB.style.border = THIS.borderWidth + 'px solid'; 1456 | p.boxB.style.borderColor = THIS.borderColor; 1457 | p.boxB.style.background = THIS.backgroundColor; 1458 | jsc.setBorderRadius(p.boxB, borderRadius + 'px'); 1459 | 1460 | // IE hack: 1461 | // If the element is transparent, IE will trigger the event on the elements under it, 1462 | // e.g. on Canvas or on elements with border 1463 | p.padM.style.background = 1464 | p.sldM.style.background = 1465 | '#FFF'; 1466 | jsc.setStyle(p.padM, 'opacity', '0'); 1467 | jsc.setStyle(p.sldM, 'opacity', '0'); 1468 | 1469 | // pad 1470 | p.pad.style.position = 'relative'; 1471 | p.pad.style.width = THIS.width + 'px'; 1472 | p.pad.style.height = THIS.height + 'px'; 1473 | 1474 | // pad palettes (HSV and HVS) 1475 | p.padPal.draw(THIS.width, THIS.height, jsc.getPadYComponent(THIS)); 1476 | 1477 | // pad border 1478 | p.padB.style.position = 'absolute'; 1479 | p.padB.style.left = THIS.padding + 'px'; 1480 | p.padB.style.top = THIS.padding + 'px'; 1481 | p.padB.style.border = THIS.insetWidth + 'px solid'; 1482 | p.padB.style.borderColor = THIS.insetColor; 1483 | 1484 | // pad mouse area 1485 | p.padM._jscInstance = THIS; 1486 | p.padM._jscControlName = 'pad'; 1487 | p.padM.style.position = 'absolute'; 1488 | p.padM.style.left = '0'; 1489 | p.padM.style.top = '0'; 1490 | p.padM.style.width = (THIS.padding + 2 * THIS.insetWidth + THIS.width + padToSliderPadding / 2) + 'px'; 1491 | p.padM.style.height = dims[1] + 'px'; 1492 | p.padM.style.cursor = padCursor; 1493 | 1494 | // pad cross 1495 | p.cross.style.position = 'absolute'; 1496 | p.cross.style.left = 1497 | p.cross.style.top = 1498 | '0'; 1499 | p.cross.style.width = 1500 | p.cross.style.height = 1501 | crossOuterSize + 'px'; 1502 | 1503 | // pad cross border Y and X 1504 | p.crossBY.style.position = 1505 | p.crossBX.style.position = 1506 | 'absolute'; 1507 | p.crossBY.style.background = 1508 | p.crossBX.style.background = 1509 | THIS.pointerBorderColor; 1510 | p.crossBY.style.width = 1511 | p.crossBX.style.height = 1512 | (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; 1513 | p.crossBY.style.height = 1514 | p.crossBX.style.width = 1515 | crossOuterSize + 'px'; 1516 | p.crossBY.style.left = 1517 | p.crossBX.style.top = 1518 | (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px'; 1519 | p.crossBY.style.top = 1520 | p.crossBX.style.left = 1521 | '0'; 1522 | 1523 | // pad cross line Y and X 1524 | p.crossLY.style.position = 1525 | p.crossLX.style.position = 1526 | 'absolute'; 1527 | p.crossLY.style.background = 1528 | p.crossLX.style.background = 1529 | THIS.pointerColor; 1530 | p.crossLY.style.height = 1531 | p.crossLX.style.width = 1532 | (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px'; 1533 | p.crossLY.style.width = 1534 | p.crossLX.style.height = 1535 | THIS.pointerThickness + 'px'; 1536 | p.crossLY.style.left = 1537 | p.crossLX.style.top = 1538 | (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px'; 1539 | p.crossLY.style.top = 1540 | p.crossLX.style.left = 1541 | THIS.pointerBorderWidth + 'px'; 1542 | 1543 | // slider 1544 | p.sld.style.overflow = 'hidden'; 1545 | p.sld.style.width = THIS.sliderSize + 'px'; 1546 | p.sld.style.height = THIS.height + 'px'; 1547 | 1548 | // slider gradient 1549 | p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000'); 1550 | 1551 | // slider border 1552 | p.sldB.style.display = displaySlider ? 'block' : 'none'; 1553 | p.sldB.style.position = 'absolute'; 1554 | p.sldB.style.right = THIS.padding + 'px'; 1555 | p.sldB.style.top = THIS.padding + 'px'; 1556 | p.sldB.style.border = THIS.insetWidth + 'px solid'; 1557 | p.sldB.style.borderColor = THIS.insetColor; 1558 | 1559 | // slider mouse area 1560 | p.sldM._jscInstance = THIS; 1561 | p.sldM._jscControlName = 'sld'; 1562 | p.sldM.style.display = displaySlider ? 'block' : 'none'; 1563 | p.sldM.style.position = 'absolute'; 1564 | p.sldM.style.right = '0'; 1565 | p.sldM.style.top = '0'; 1566 | p.sldM.style.width = (THIS.sliderSize + padToSliderPadding / 2 + THIS.padding + 2 * THIS.insetWidth) + 'px'; 1567 | p.sldM.style.height = dims[1] + 'px'; 1568 | p.sldM.style.cursor = 'default'; 1569 | 1570 | // slider pointer inner and outer border 1571 | p.sldPtrIB.style.border = 1572 | p.sldPtrOB.style.border = 1573 | THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor; 1574 | 1575 | // slider pointer outer border 1576 | p.sldPtrOB.style.position = 'absolute'; 1577 | p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; 1578 | p.sldPtrOB.style.top = '0'; 1579 | 1580 | // slider pointer middle border 1581 | p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor; 1582 | 1583 | // slider pointer spacer 1584 | p.sldPtrS.style.width = THIS.sliderSize + 'px'; 1585 | p.sldPtrS.style.height = sliderPtrSpace + 'px'; 1586 | 1587 | // the Close button 1588 | function setBtnBorder () { 1589 | var insetColors = THIS.insetColor.split(/\s+/); 1590 | var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1]; 1591 | p.btn.style.borderColor = outsetColor; 1592 | } 1593 | p.btn.style.display = THIS.closable ? 'block' : 'none'; 1594 | p.btn.style.position = 'absolute'; 1595 | p.btn.style.left = THIS.padding + 'px'; 1596 | p.btn.style.bottom = THIS.padding + 'px'; 1597 | p.btn.style.padding = '0 15px'; 1598 | p.btn.style.height = THIS.buttonHeight + 'px'; 1599 | p.btn.style.border = THIS.insetWidth + 'px solid'; 1600 | setBtnBorder(); 1601 | p.btn.style.color = THIS.buttonColor; 1602 | p.btn.style.font = '12px sans-serif'; 1603 | p.btn.style.textAlign = 'center'; 1604 | try { 1605 | p.btn.style.cursor = 'pointer'; 1606 | } catch(eOldIE) { 1607 | p.btn.style.cursor = 'hand'; 1608 | } 1609 | p.btn.onmousedown = function () { 1610 | THIS.hide(); 1611 | }; 1612 | p.btnT.style.lineHeight = THIS.buttonHeight + 'px'; 1613 | p.btnT.innerHTML = ''; 1614 | p.btnT.appendChild(document.createTextNode(THIS.closeText)); 1615 | 1616 | // place pointers 1617 | redrawPad(); 1618 | redrawSld(); 1619 | 1620 | // If we are changing the owner without first closing the picker, 1621 | // make sure to first deal with the old owner 1622 | if (jsc.picker.owner && jsc.picker.owner !== THIS) { 1623 | jsc.unsetClass(jsc.picker.owner.targetElement, THIS.activeClass); 1624 | } 1625 | 1626 | // Set the new picker owner 1627 | jsc.picker.owner = THIS; 1628 | 1629 | // The redrawPosition() method needs picker.owner to be set, that's why we call it here, 1630 | // after setting the owner 1631 | if (jsc.isElementType(container, 'body')) { 1632 | jsc.redrawPosition(); 1633 | } else { 1634 | jsc._drawPosition(THIS, 0, 0, 'relative', false); 1635 | } 1636 | 1637 | if (p.wrap.parentNode != container) { 1638 | container.appendChild(p.wrap); 1639 | } 1640 | 1641 | jsc.setClass(THIS.targetElement, THIS.activeClass); 1642 | } 1643 | 1644 | 1645 | function redrawPad () { 1646 | // redraw the pad pointer 1647 | switch (jsc.getPadYComponent(THIS)) { 1648 | case 's': var yComponent = 1; break; 1649 | case 'v': var yComponent = 2; break; 1650 | } 1651 | var x = Math.round((THIS.hsv[0] / 360) * (THIS.width - 1)); 1652 | var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); 1653 | var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); 1654 | var ofs = -Math.floor(crossOuterSize / 2); 1655 | jsc.picker.cross.style.left = (x + ofs) + 'px'; 1656 | jsc.picker.cross.style.top = (y + ofs) + 'px'; 1657 | 1658 | // redraw the slider 1659 | switch (jsc.getSliderComponent(THIS)) { 1660 | case 's': 1661 | var rgb1 = HSV_RGB(THIS.hsv[0], 100, THIS.hsv[2]); 1662 | var rgb2 = HSV_RGB(THIS.hsv[0], 0, THIS.hsv[2]); 1663 | var color1 = 'rgb(' + 1664 | Math.round(rgb1[0]) + ',' + 1665 | Math.round(rgb1[1]) + ',' + 1666 | Math.round(rgb1[2]) + ')'; 1667 | var color2 = 'rgb(' + 1668 | Math.round(rgb2[0]) + ',' + 1669 | Math.round(rgb2[1]) + ',' + 1670 | Math.round(rgb2[2]) + ')'; 1671 | jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); 1672 | break; 1673 | case 'v': 1674 | var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 100); 1675 | var color1 = 'rgb(' + 1676 | Math.round(rgb[0]) + ',' + 1677 | Math.round(rgb[1]) + ',' + 1678 | Math.round(rgb[2]) + ')'; 1679 | var color2 = '#000'; 1680 | jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); 1681 | break; 1682 | } 1683 | } 1684 | 1685 | 1686 | function redrawSld () { 1687 | var sldComponent = jsc.getSliderComponent(THIS); 1688 | if (sldComponent) { 1689 | // redraw the slider pointer 1690 | switch (sldComponent) { 1691 | case 's': var yComponent = 1; break; 1692 | case 'v': var yComponent = 2; break; 1693 | } 1694 | var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); 1695 | jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(sliderPtrSpace / 2)) + 'px'; 1696 | } 1697 | } 1698 | 1699 | 1700 | function isPickerOwner () { 1701 | return jsc.picker && jsc.picker.owner === THIS; 1702 | } 1703 | 1704 | 1705 | function blurValue () { 1706 | THIS.importColor(); 1707 | } 1708 | 1709 | 1710 | // Find the target element 1711 | if (typeof targetElement === 'string') { 1712 | var id = targetElement; 1713 | var elm = document.getElementById(id); 1714 | if (elm) { 1715 | this.targetElement = elm; 1716 | } else { 1717 | jsc.warn('Could not find target element with ID \'' + id + '\''); 1718 | } 1719 | } else if (targetElement) { 1720 | this.targetElement = targetElement; 1721 | } else { 1722 | jsc.warn('Invalid target element: \'' + targetElement + '\''); 1723 | } 1724 | 1725 | if (this.targetElement._jscLinkedInstance) { 1726 | jsc.warn('Cannot link jscolor twice to the same element. Skipping.'); 1727 | return; 1728 | } 1729 | this.targetElement._jscLinkedInstance = this; 1730 | 1731 | // Find the value element 1732 | this.valueElement = jsc.fetchElement(this.valueElement); 1733 | // Find the style element 1734 | this.styleElement = jsc.fetchElement(this.styleElement); 1735 | 1736 | var THIS = this; 1737 | var container = 1738 | this.container ? 1739 | jsc.fetchElement(this.container) : 1740 | document.getElementsByTagName('body')[0]; 1741 | var sliderPtrSpace = 3; // px 1742 | 1743 | // For BUTTON elements it's important to stop them from sending the form when clicked 1744 | // (e.g. in Safari) 1745 | if (jsc.isElementType(this.targetElement, 'button')) { 1746 | if (this.targetElement.onclick) { 1747 | var origCallback = this.targetElement.onclick; 1748 | this.targetElement.onclick = function (evt) { 1749 | origCallback.call(this, evt); 1750 | return false; 1751 | }; 1752 | } else { 1753 | this.targetElement.onclick = function () { return false; }; 1754 | } 1755 | } 1756 | 1757 | /* 1758 | var elm = this.targetElement; 1759 | do { 1760 | // If the target element or one of its offsetParents has fixed position, 1761 | // then use fixed positioning instead 1762 | // 1763 | // Note: In Firefox, getComputedStyle returns null in a hidden iframe, 1764 | // that's why we need to check if the returned style object is non-empty 1765 | var currStyle = jsc.getStyle(elm); 1766 | if (currStyle && currStyle.position.toLowerCase() === 'fixed') { 1767 | this.fixed = true; 1768 | } 1769 | 1770 | if (elm !== this.targetElement) { 1771 | // attach onParentScroll so that we can recompute the picker position 1772 | // when one of the offsetParents is scrolled 1773 | if (!elm._jscEventsAttached) { 1774 | jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); 1775 | elm._jscEventsAttached = true; 1776 | } 1777 | } 1778 | } while ((elm = elm.offsetParent) && !jsc.isElementType(elm, 'body')); 1779 | */ 1780 | 1781 | // valueElement 1782 | if (this.valueElement) { 1783 | if (jsc.isElementType(this.valueElement, 'input')) { 1784 | var updateField = function () { 1785 | THIS.fromString(THIS.valueElement.value, jsc.leaveValue); 1786 | jsc.dispatchFineChange(THIS); 1787 | }; 1788 | jsc.attachEvent(this.valueElement, 'keyup', updateField); 1789 | jsc.attachEvent(this.valueElement, 'input', updateField); 1790 | jsc.attachEvent(this.valueElement, 'blur', blurValue); 1791 | this.valueElement.setAttribute('autocomplete', 'off'); 1792 | } 1793 | } 1794 | 1795 | // styleElement 1796 | if (this.styleElement) { 1797 | this.styleElement._jscOrigStyle = { 1798 | backgroundImage : this.styleElement.style.backgroundImage, 1799 | backgroundColor : this.styleElement.style.backgroundColor, 1800 | color : this.styleElement.style.color 1801 | }; 1802 | } 1803 | 1804 | if (this.value) { 1805 | // Try to set the color from the .value option and if unsuccessful, 1806 | // export the current color 1807 | this.fromString(this.value) || this.exportColor(); 1808 | } else { 1809 | this.importColor(); 1810 | } 1811 | } 1812 | 1813 | }; 1814 | 1815 | 1816 | //================================ 1817 | // Public properties and methods 1818 | //================================ 1819 | 1820 | 1821 | // By default, search for all elements with class="jscolor" and install a color picker on them. 1822 | // 1823 | // You can change what class name will be looked for by setting the property jscolor.lookupClass 1824 | // anywhere in your HTML document. To completely disable the automatic lookup, set it to null. 1825 | // 1826 | jsc.jscolor.lookupClass = 'jscolor'; 1827 | 1828 | 1829 | jsc.jscolor.installByClassName = function (className) { 1830 | var inputElms = document.getElementsByTagName('input'); 1831 | var buttonElms = document.getElementsByTagName('button'); 1832 | 1833 | jsc.tryInstallOnElements(inputElms, className); 1834 | jsc.tryInstallOnElements(buttonElms, className); 1835 | }; 1836 | 1837 | 1838 | jsc.register(); 1839 | 1840 | 1841 | return jsc.jscolor; 1842 | 1843 | 1844 | })(); } 1845 | -------------------------------------------------------------------------------- /data/palettes/35_blue_waves.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/35_blue_waves.bin -------------------------------------------------------------------------------- /data/palettes/37_waves.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/37_waves.bin -------------------------------------------------------------------------------- /data/palettes/Green_White_Red.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/Green_White_Red.bin -------------------------------------------------------------------------------- /data/palettes/Lindaa07.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/Lindaa07.bin -------------------------------------------------------------------------------- /data/palettes/beading.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/beading.bin -------------------------------------------------------------------------------- /data/palettes/bhw1_29.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/bhw1_29.bin -------------------------------------------------------------------------------- /data/palettes/bhw1_greenie.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/bhw1_greenie.bin -------------------------------------------------------------------------------- /data/palettes/bhw1_purpgreen.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/bhw1_purpgreen.bin -------------------------------------------------------------------------------- /data/palettes/bhw1_purplered.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/bhw1_purplered.bin -------------------------------------------------------------------------------- /data/palettes/bhw1_sunconure.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/bhw1_sunconure.bin -------------------------------------------------------------------------------- /data/palettes/bhw2_xmas.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/bhw2_xmas.bin -------------------------------------------------------------------------------- /data/palettes/blueeyedgal.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/blueeyedgal.bin -------------------------------------------------------------------------------- /data/palettes/brightsong2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/brightsong2.bin -------------------------------------------------------------------------------- /data/palettes/bud2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/bud2.bin -------------------------------------------------------------------------------- /data/palettes/christmas-candy.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/christmas-candy.bin -------------------------------------------------------------------------------- /data/palettes/faewing3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/faewing3.bin -------------------------------------------------------------------------------- /data/palettes/goddess-moon.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/goddess-moon.bin -------------------------------------------------------------------------------- /data/palettes/lkmtch00.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/lkmtch00.bin -------------------------------------------------------------------------------- /data/palettes/patriot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/patriot.bin -------------------------------------------------------------------------------- /data/palettes/plasma.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/plasma.bin -------------------------------------------------------------------------------- /data/palettes/prism.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/prism.bin -------------------------------------------------------------------------------- /data/palettes/sls.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/sls.bin -------------------------------------------------------------------------------- /data/palettes/sorcery-2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/sorcery-2.bin -------------------------------------------------------------------------------- /data/palettes/starrynite.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/starrynite.bin -------------------------------------------------------------------------------- /data/palettes/twilight.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/twilight.bin -------------------------------------------------------------------------------- /data/palettes/usa1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/usa1.bin -------------------------------------------------------------------------------- /data/palettes/water1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/water1.bin -------------------------------------------------------------------------------- /data/palettes/wintercolors.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jake-b/Griswold-LED-Controller/a3fb7aded07585b7fa32ba41d62667cf6a8d382d/data/palettes/wintercolors.bin -------------------------------------------------------------------------------- /definitions.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 @jake-b, @russp81, @toblum 2 | // Griswold LED Lighting Controller 3 | 4 | // Griswold is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as 6 | // published by the Free Software Foundation, either version 3 of 7 | // the License, or (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with this program. If not, see . 16 | 17 | // Griswold is a fork of the LEDLAMP project at 18 | // https://github.com/russp81/LEDLAMP_FASTLEDs 19 | 20 | // The LEDLAMP project is a fork of the McLighting Project at 21 | // https://github.com/toblum/McLighting 22 | 23 | //#define FASTLED_ALLOW_INTERRUPTS 0 24 | 25 | 26 | #define FASTLED_USE_PROGMEM 1 27 | // Note, you need to patch FastLEDs in order to use this. You'll get an 28 | // error related to . Saves more than 3k given the palettes 29 | // 30 | // Simply edit and update the include (Line ~29): 31 | // #if FASTLED_INCLUDE_PGMSPACE == 1 32 | // #if (defined(__AVR__)) 33 | // #include 34 | // #else 35 | // #include 36 | // #endif 37 | // #endif 38 | 39 | #define FASTLED_INTERRUPT_RETRY_COUNT 3 40 | #include "FastLED.h" 41 | #if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3001000) 42 | #warning "Requires FastLED 3.1 or later; check github for latest code." 43 | #endif 44 | 45 | #define HOSTNAME_PREFIX "GRISWOLD" 46 | 47 | //#define REMOTE_DEBUG 48 | 49 | #define DATA_PIN 5 50 | //#define CLK_PIN 4 51 | #define LED_TYPE WS2811 52 | #define COLOR_ORDER RGB 53 | #define NUM_LEDS 800 54 | CRGB leds[NUM_LEDS]; 55 | 56 | #define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) 57 | 58 | enum MODE { HOLD, 59 | OFF, 60 | ALL, 61 | MIXEDSHOW, 62 | RAINBOW, 63 | CONFETTI, 64 | SINELON, 65 | JUGGLE, 66 | BPM, 67 | PALETTE_ANIMS, 68 | RIPPLE, 69 | COMET, 70 | THEATERCHASE, 71 | WIPE, 72 | TV }; 73 | 74 | enum DIRECTION { 75 | BACK = 0, 76 | FORWARD = 1, }; 77 | 78 | // These globals moved to the settings struct 79 | //MODE mode = OFF; // Standard mode that is active when software starts 80 | //uint8_t FPS = 50; // Global variable for storing the frames per second 81 | //uint8_t brightness = 255; // Global variable for storing the brightness (255 == 100%) 82 | //uint8_t show_length = 15; // Global variable for storing the show_time (in seconds) 83 | //uint8_t ftb_speed = 50; // Global variable for fade to black speed 84 | //uint8_t glitter_density = 50; // Global variable for glitter density 85 | long lastMillis = 0; // Global variable for timechecking last show cycle time 86 | long theaterMillis = 0; 87 | long paletteMillis = 0; // Global variable for timechecking color palette shifts 88 | //bool exit_func = false; // Global helper variable to get out of the color modes when mode changes 89 | //bool GLITTER_ON = false; // Global to add / remove glitter to any animation 90 | 91 | //******Palette Animation Globals******************************************* 92 | uint8_t targetPaletteIndex; 93 | uint8_t currentPaletteIndex; 94 | uint8_t colorIndex; 95 | DIRECTION anim_direction = FORWARD; 96 | CRGBPalette16 currentPalette; 97 | CRGBPalette16 targetPalette; 98 | TBlendType currentBlending; 99 | 100 | //*************************************************************************** 101 | 102 | //***************RIPPLE****************************************************** 103 | int color; 104 | int center = 0; 105 | int step = -1; 106 | int maxSteps = 32; 107 | float fadeRate = 0.8; 108 | int diff; 109 | 110 | //background color 111 | uint32_t currentBg = random(256); 112 | uint32_t nextBg = currentBg; 113 | //****************************************************************************** 114 | 115 | byte dothue = 0; 116 | int lead_dot = 0; 117 | 118 | uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current 119 | uint8_t gHue = 0; // rotating "base color" used by many of the patterns 120 | 121 | struct ledstate // Data structure to store a state of a single led 122 | { 123 | uint8_t red; 124 | uint8_t green; 125 | uint8_t blue; 126 | }; 127 | 128 | typedef struct ledstate LEDState; // Define the datatype LEDState 129 | LEDState ledstates[NUM_LEDS]; // Get an array of led states to store the state of the whole strip 130 | //LEDState main_color; // Store the "main color" of the strip used in single color modes 131 | //LEDState glitter_color; // Store the "glitter color" of the strip for glitter mode 132 | 133 | // Supporting the "Glitter Wipe" effect 134 | #define SPARKLE_SPREAD (_max(NUM_LEDS/80,3)) 135 | #define WIPE_SPEED (_max(NUM_LEDS/120,1)) 136 | int16_t wipePos = 0; 137 | 138 | #ifdef REMOTE_DEBUG 139 | RemoteDebug Debug; 140 | #define DBG_OUTPUT_PORT Debug 141 | #else 142 | #define DBG_OUTPUT_PORT Serial 143 | #endif 144 | -------------------------------------------------------------------------------- /eepromsettings.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 @jake-b, @russp81, @toblum 2 | // Griswold LED Lighting Controller 3 | 4 | // Griswold is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as 6 | // published by the Free Software Foundation, either version 3 of 7 | // the License, or (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with this program. If not, see . 16 | 17 | // Griswold is a fork of the LEDLAMP project at 18 | // https://github.com/russp81/LEDLAMP_FASTLEDs 19 | 20 | // The LEDLAMP project is a fork of the McLighting Project at 21 | // https://github.com/toblum/McLighting 22 | 23 | #include 24 | 25 | // EEPROM stuff 26 | // adapted from https://github.com/esp8266/Arduino/issues/1090 27 | 28 | #pragma pack(push) // push current alignment to stack 29 | #pragma pack(1) // set alignment to 1 byte boundary 30 | typedef struct { 31 | MODE mode; 32 | uint8_t fps = 50; // Global variable for storing the frames per second 33 | uint8_t overall_brightness = 255; // Global variable for storing the brightness (255 == 100%) 34 | uint8_t show_length = 15; // Global variable for storing the show_time (in seconds) 35 | uint8_t ftb_speed = 50; // Global variable for fade to black speed 36 | uint8_t glitter_density = 50; // Global variable for glitter density 37 | bool glitter_on = false; // Global to add / remove glitter to any animation 38 | LEDState main_color; // Store the "main color" of the strip used in single 39 | // color modes 40 | LEDState glitter_color; // Store the "glitter color" of the strip for glitter mode 41 | uint8_t effect_brightness = 255; // Brightness used for effect animations 42 | int8_t palette_ndx = -1; // Palette to use for PALETTE_ANIMS. -1 is change periodically 43 | uint8_t confetti_dens = 1; // Density for the confetti effect. More confetti needed for longer strings. 44 | bool glitter_wipe_on = false; 45 | uint8_t filler[46]; // in case adding data in config avoiding loosing current conf by bad crc 46 | uint16_t crc; 47 | } EEPROMSettings; 48 | #pragma pack(pop) 49 | 50 | EEPROMSettings settings; 51 | 52 | uint16_t crc16Update(uint16_t crc, uint8_t a) { 53 | int i; 54 | crc ^= a; 55 | for (i = 0; i < 8; ++i) { 56 | if (crc & 1) 57 | crc = (crc >> 1) ^ 0xA001; 58 | else 59 | crc = (crc >> 1); 60 | } 61 | return crc; 62 | } 63 | 64 | void loadDefaults() { 65 | settings.mode = OFF; 66 | settings.fps = 50; // Global variable for storing the frames per second 67 | settings.overall_brightness = 255; // Global variable for storing the brightness (255 == 100%) 68 | settings.effect_brightness = 128; // Global variable for storing the palette brightness (255 == 100%) 69 | settings.show_length = 15; // Global variable for storing the show_time (in seconds) 70 | settings.ftb_speed = 50; // Global variable for fade to black speed 71 | settings.glitter_density = 50; // Global variable for glitter density 72 | settings.glitter_on = false; // Global to add / remove glitter to any animation 73 | settings.main_color = { 128, 128, 128}; // Store the "main color" of the strip used in single color modes 74 | settings.glitter_color = {128, 128, 128}; 75 | settings.palette_ndx = -1; 76 | settings.confetti_dens = 1; 77 | settings.glitter_wipe_on = false; 78 | } 79 | 80 | bool readSettings(bool clear_on_error) { 81 | uint16_t crc = ~0; 82 | uint8_t* pconfig = (uint8_t*)&settings; 83 | uint8_t data; 84 | 85 | // For whole size of config structure 86 | for (uint16_t i = 0; i < sizeof(EEPROMSettings); ++i) { 87 | // read data 88 | data = EEPROM.read(i); 89 | 90 | // save into struct 91 | *pconfig++ = data; 92 | 93 | // calc CRC 94 | crc = crc16Update(crc, data); 95 | } 96 | 97 | // CRC Error ? 98 | if (crc != 0) { 99 | DBG_OUTPUT_PORT.println("Settings CRC failed on read from EEPROM"); 100 | // Clear config if wanted 101 | if (clear_on_error) { 102 | memset(&settings, 0, sizeof(EEPROMSettings)); 103 | 104 | // Set defaults 105 | loadDefaults(); 106 | return false; 107 | } 108 | } 109 | 110 | DBG_OUTPUT_PORT.println("Settings successfully read from EERPOM"); 111 | return true; 112 | } 113 | 114 | bool saveSettings(void) { 115 | uint8_t* pconfig; 116 | bool ret_code; 117 | 118 | // Init pointer 119 | pconfig = (uint8_t*)&settings; 120 | 121 | // Init CRC 122 | settings.crc = ~0; 123 | 124 | // For whole size of config structure, pre-calculate CRC 125 | for (uint16_t i = 0; i < sizeof(EEPROMSettings) - 2; ++i) 126 | settings.crc = crc16Update(settings.crc, *pconfig++); 127 | 128 | // Re init pointer 129 | pconfig = (uint8_t*)&settings; 130 | 131 | // For whole size of config structure, write to EEP 132 | for (uint16_t i = 0; i < sizeof(EEPROMSettings); ++i) 133 | EEPROM.write(i, *pconfig++); 134 | 135 | // Physically save 136 | EEPROM.commit(); 137 | 138 | // Read Again to see if saved ok, but do 139 | // not clear if error this avoid clearing 140 | // default config and breaks OTA 141 | ret_code = readSettings(false); 142 | 143 | DBG_OUTPUT_PORT.print("Write settings to EEPROM: "); 144 | 145 | if (ret_code) 146 | DBG_OUTPUT_PORT.println(F("OK!")); 147 | else 148 | DBG_OUTPUT_PORT.println(F("Error!")); 149 | 150 | // return result 151 | return (ret_code); 152 | } 153 | 154 | void printSettings() { 155 | DBG_OUTPUT_PORT.println("Current settings in RAM:"); 156 | 157 | DBG_OUTPUT_PORT.printf("mode: %d\n", 158 | settings.mode); 159 | DBG_OUTPUT_PORT.printf("fps: %d\n", 160 | settings.fps); // Global variable for storing the frames per second 161 | DBG_OUTPUT_PORT.printf("overall_brightness: %d\n", 162 | settings.overall_brightness); // Global variable for storing the brightness (255 == 100%) 163 | DBG_OUTPUT_PORT.printf("effect_brightness: %d\n", 164 | settings.effect_brightness); // Global variable for storing the brightnes (255 == 100%) 165 | DBG_OUTPUT_PORT.printf("show_length: %d\n", 166 | settings.show_length); // Global variable for storing 167 | // the show_time (in seconds) 168 | DBG_OUTPUT_PORT.printf("ftb_speed: %d\n", 169 | settings.ftb_speed); // Global variable for fade to black speed 170 | DBG_OUTPUT_PORT.printf("glitter_density: %d\n", 171 | settings.glitter_density); // Global variable for glitter density 172 | DBG_OUTPUT_PORT.printf("glitter_on: %d\n", 173 | settings.glitter_on); // Global to add / remove glitter to any animation 174 | DBG_OUTPUT_PORT.printf("main_color: %d,%d,%d\n", 175 | settings.main_color.red, settings.main_color.green, 176 | settings.main_color.blue); // Store the "main color" of the strip used in single color modes 177 | DBG_OUTPUT_PORT.printf("glitter_color: %d,%d,%d\n", settings.glitter_color.red, 178 | settings.glitter_color.green, settings.glitter_color.blue); 179 | DBG_OUTPUT_PORT.printf("palette_ndx: %d\n", settings.palette_ndx); // selected palette 180 | DBG_OUTPUT_PORT.printf("confetti_dens: %d\n", settings.confetti_dens); // selected palette 181 | } 182 | 183 | void initSettings() { 184 | EEPROM.begin(sizeof(EEPROMSettings)); 185 | if (readSettings(true)) { 186 | DBG_OUTPUT_PORT.println("Successfully read settings from EEPROM"); 187 | } else { 188 | DBG_OUTPUT_PORT.println( 189 | "Failed read settings from EEPROM, defaults loaded."); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /palette_convert/.gitignore: -------------------------------------------------------------------------------- 1 | *.c3g 2 | *.bin -------------------------------------------------------------------------------- /palette_convert/palette_convert.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | 3 | # Griswold is a fork of the LEDLAMP project at 4 | # https://github.com/russp81/LEDLAMP_FASTLEDs 5 | 6 | # The LEDLAMP project is a fork of the McLighting Project at 7 | # https://github.com/toblum/McLighting 8 | 9 | # PaletteKnife was released under the MIT License (MIT), and hence this is as well. 10 | 11 | # Copyright (c) 2016 @jake-b 12 | 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 14 | # this software and associated documentation files (the "Software"), to deal in 15 | # the Software without restriction, including without limitation the rights to 16 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 17 | # the Software, and to permit persons to whom the Software is furnished to do so, 18 | # subject to the following conditions: 19 | 20 | # The above copyright notice and this permission notice shall be included in all 21 | # copies or substantial portions of the Software. 22 | 23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 25 | # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 26 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 27 | # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 28 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | # This is a rudamentary clone of PaletteKnife, designed to output the palette to a 31 | # file in binary format. 32 | 33 | import sys 34 | import re 35 | import math 36 | import os 37 | 38 | def adjustGamma(orig, gamma): 39 | o = orig / 255.0; 40 | adj = math.pow( o, gamma); 41 | res = math.floor( adj * 255.0); 42 | if ((orig != 0) and (res == 0)): 43 | res = 1; 44 | return int(res); 45 | 46 | 47 | infile = sys.argv[1] 48 | outfile = os.path.splitext(infile)[0] + ".bin" 49 | 50 | print "Processing file: " + infile 51 | 52 | with open(infile) as f: 53 | content = f.read() 54 | output_bytes = [] 55 | 56 | regex = re.compile('.*\(\s*([0-9]+), *([0-9]+), *([0-9]+)\)\s+([0-9.]+)') 57 | 58 | # RGBA Warning 59 | if content.find("rgba(") != -1: 60 | print("WARNING: TRANSPARENCY not supported."); 61 | 62 | count = 0 63 | for line in content.split('\n'): 64 | match = regex.match(line) 65 | 66 | if match: 67 | #print len(match) 68 | r = int(match.group(1)) 69 | g = int(match.group(2)) 70 | b = int(match.group(3)) 71 | pct = float(match.group(4)) 72 | ndx = int(math.floor( (pct * 255.0) / 100.0 )) 73 | 74 | output_bytes.append(ndx) 75 | output_bytes.append(adjustGamma(r, 2.6)) 76 | output_bytes.append(adjustGamma(g, 2.2)) 77 | output_bytes.append(adjustGamma(b, 2.5)) 78 | 79 | f.close() 80 | 81 | newFileByteArray = bytearray(output_bytes) 82 | with open(outfile,'wb') as newFile: 83 | newFile.write(newFileByteArray) 84 | newFile.close() 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /palettes.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 @jake-b, @russp81, @toblum 2 | // Griswold LED Lighting Controller 3 | 4 | // Griswold is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as 6 | // published by the Free Software Foundation, either version 3 of 7 | // the License, or (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with this program. If not, see . 16 | 17 | // Griswold is a fork of the LEDLAMP project at 18 | // https://github.com/russp81/LEDLAMP_FASTLEDs 19 | 20 | // The LEDLAMP project is a fork of the McLighting Project at 21 | // https://github.com/toblum/McLighting 22 | 23 | int paletteCount = 0; 24 | 25 | int getPaletteCount() { 26 | Dir dir = SPIFFS.openDir("/palettes"); 27 | int palette_count = 0; 28 | while (dir.next()) { 29 | palette_count++; 30 | } 31 | DBG_OUTPUT_PORT.printf("Palette count: %d\n", palette_count); 32 | return palette_count; 33 | } 34 | 35 | bool openPaletteFileWithIndex(int index, File* file) { 36 | Dir dir = SPIFFS.openDir("/palettes"); 37 | 38 | int palette_count = 0; 39 | while (dir.next()) { 40 | if (palette_count == index) break; 41 | palette_count++; 42 | } 43 | 44 | if (palette_count != index) { 45 | DBG_OUTPUT_PORT.println("Error, unable to open palette"); 46 | return false; 47 | } 48 | 49 | *file = dir.openFile("r"); 50 | return true; //success 51 | } 52 | 53 | bool loadPaletteFromFile(int index, CRGBPalette16* palette) { 54 | File paletteFile; 55 | if (!openPaletteFileWithIndex(index, &paletteFile)) { 56 | DBG_OUTPUT_PORT.printf("Error loading paletteFile at index %d\n", index); 57 | return false; 58 | } 59 | 60 | int paletteFileSize = paletteFile.size(); 61 | uint8_t* bytes = new uint8_t[paletteFileSize]; 62 | if (!bytes) { 63 | DBG_OUTPUT_PORT.println("Unable to allocate memory for palette"); 64 | return false; 65 | } 66 | 67 | paletteFile.readBytes((char*)bytes, paletteFileSize); 68 | paletteFile.close(); 69 | 70 | DBG_OUTPUT_PORT.printf("Load palette named %s (%d bytes)\n", paletteFile.name(), paletteFileSize); 71 | 72 | palette->loadDynamicGradientPalette(bytes); 73 | 74 | delete[] bytes; 75 | return true; 76 | } 77 | 78 | 79 | String getPaletteNameWithIndex(int index) { 80 | Dir dir = SPIFFS.openDir("/palettes"); 81 | 82 | int ndx = 0; 83 | while (dir.next()) { 84 | if (ndx == index) return dir.fileName(); 85 | ndx++; 86 | } 87 | return "[unknown]"; 88 | } 89 | -------------------------------------------------------------------------------- /request_handlers.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 @jake-b, @russp81, @toblum 2 | // Griswold LED Lighting Controller 3 | 4 | // Griswold is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as 6 | // published by the Free Software Foundation, either version 3 of 7 | // the License, or (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with this program. If not, see . 16 | 17 | // Griswold is a fork of the LEDLAMP project at 18 | // https://github.com/russp81/LEDLAMP_FASTLEDs 19 | 20 | // The LEDLAMP project is a fork of the McLighting Project at 21 | // https://github.com/toblum/McLighting 22 | 23 | // *************************************************************************** 24 | // Request handlers 25 | // *************************************************************************** 26 | void getArgs() { 27 | if (server.arg("rgb") != "") { 28 | uint32_t rgb = (uint32_t) strtol(server.arg("rgb").c_str(), NULL, 16); 29 | settings.main_color.red = ((rgb >> 16) & 0xFF); 30 | settings.main_color.green = ((rgb >> 8) & 0xFF); 31 | settings.main_color.blue = ((rgb >> 0) & 0xFF); 32 | } else { 33 | settings.main_color.red = server.arg("r").toInt(); 34 | settings.main_color.green = server.arg("g").toInt(); 35 | settings.main_color.blue = server.arg("b").toInt(); 36 | } 37 | if (server.arg("d") != "") { 38 | settings.fps = server.arg("d").toInt(); 39 | if (settings.fps == 0) settings.fps = 40; // prevent divide by zero! 40 | } 41 | if (settings.main_color.red > 255) { 42 | settings.main_color.red = 255; 43 | } 44 | if (settings.main_color.green > 255) { 45 | settings.main_color.green = 255; 46 | } 47 | if (settings.main_color.blue > 255) { 48 | settings.main_color.blue = 255; 49 | } 50 | 51 | if (settings.main_color.red < 0) { 52 | settings.main_color.red = 0; 53 | } 54 | if (settings.main_color.green < 0) { 55 | settings.main_color.green = 0; 56 | } 57 | if (settings.main_color.blue < 0) { 58 | settings.main_color.blue = 0; 59 | } 60 | 61 | DBG_OUTPUT_PORT.print("Mode: "); 62 | DBG_OUTPUT_PORT.print(settings.mode); 63 | DBG_OUTPUT_PORT.print(", Color: "); 64 | DBG_OUTPUT_PORT.print(settings.main_color.red); 65 | DBG_OUTPUT_PORT.print(", "); 66 | DBG_OUTPUT_PORT.print(settings.main_color.green); 67 | DBG_OUTPUT_PORT.print(", "); 68 | DBG_OUTPUT_PORT.print(settings.main_color.blue); 69 | DBG_OUTPUT_PORT.print(", Delay:"); 70 | DBG_OUTPUT_PORT.print(settings.fps); 71 | DBG_OUTPUT_PORT.print(", Brightness:"); 72 | DBG_OUTPUT_PORT.println(settings.overall_brightness); 73 | DBG_OUTPUT_PORT.print(", show_length:"); 74 | DBG_OUTPUT_PORT.println(settings.show_length); 75 | } 76 | 77 | void handleMinimalUpload() { 78 | char temp[1500]; 79 | int sec = millis() / 1000; 80 | int min = sec / 60; 81 | int hr = min / 60; 82 | 83 | snprintf_P ( temp, 1500, 84 | PSTR("\ 85 | \ 86 | \ 87 | ESP8266 Upload\ 88 | \ 89 | \ 90 | \ 91 | \ 92 | \ 93 |
\ 94 | \ 95 | \ 96 | \ 97 |
\ 98 | \ 99 | "), 100 | hr, min % 60, sec % 60 101 | ); 102 | server.send ( 200, "text/html", temp ); 103 | } 104 | 105 | void handleNotFound() { 106 | String message = "File Not Found\n\n"; 107 | message += "URI: "; 108 | message += server.uri(); 109 | message += "\nMethod: "; 110 | message += ( server.method() == HTTP_GET ) ? "GET" : "POST"; 111 | message += "\nArguments: "; 112 | message += server.args(); 113 | message += "\n"; 114 | for ( uint8_t i = 0; i < server.args(); i++ ) { 115 | message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n"; 116 | } 117 | server.send ( 404, "text/plain", message ); 118 | } 119 | 120 | char* listStatusJSON() { 121 | char json[512]; 122 | File file; 123 | openPaletteFileWithIndex(currentPaletteIndex, &file); 124 | snprintf_P(json, sizeof(json), PSTR("{\"mode\":%d, \"FPS\":%d,\"show_length\":%d, \"ftb_speed\":%d, \"overall_brightness\":%d, \"effect_brightness\":%d, \"color\":[%d, %d, %d], \"glitter_color\":[%d,%d,%d], \"glitter_density\":%d, \"glitter_on\":%d, \"confetti_density\":%d, \"palette_name\": \"%s\", \"glitter_wipe_on\": %d}"), settings.mode, settings.fps, settings.show_length, settings.ftb_speed, settings.overall_brightness, settings.effect_brightness, settings.main_color.red, settings.main_color.green, settings.main_color.blue, settings.glitter_color.red, settings.glitter_color.green, settings.glitter_color.blue, settings.glitter_density, settings.glitter_on, settings.confetti_dens, file.name(), settings.glitter_wipe_on); 125 | file.close(); 126 | return json; 127 | } 128 | 129 | 130 | void getStatusJSON() { 131 | server.send ( 200, "application/json", listStatusJSON() ); 132 | } 133 | 134 | void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { 135 | switch (type) { 136 | case WStype_DISCONNECTED: 137 | DBG_OUTPUT_PORT.printf("WS: [%u] Disconnected!\n", num); 138 | break; 139 | 140 | case WStype_CONNECTED: { 141 | IPAddress ip = webSocket.remoteIP(num); 142 | DBG_OUTPUT_PORT.printf("WS: [%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); 143 | 144 | // send message to client 145 | webSocket.sendTXT(num, "Connected"); 146 | } 147 | break; 148 | 149 | case WStype_TEXT: 150 | DBG_OUTPUT_PORT.printf("WS: [%u] get Text: %s\n", num, payload); 151 | 152 | // # ==> Set main color 153 | if (payload[0] == '#') { 154 | // decode rgb data 155 | uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16); 156 | settings.main_color.red = ((rgb >> 16) & 0xFF); 157 | settings.main_color.green = ((rgb >> 8) & 0xFF); 158 | settings.main_color.blue = ((rgb >> 0) & 0xFF); 159 | DBG_OUTPUT_PORT.printf("Set main color to: [%u] [%u] [%u]\n", settings.main_color.red, settings.main_color.green, settings.main_color.blue); 160 | webSocket.sendTXT(num, "OK"); 161 | } 162 | 163 | // # ==> Set glitter color 164 | if (payload[0] == 'G') { 165 | // decode rgb data 166 | uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16); 167 | settings.glitter_color.red = ((rgb >> 16) & 0xFF); 168 | settings.glitter_color.green = ((rgb >> 8) & 0xFF); 169 | settings.glitter_color.blue = ((rgb >> 0) & 0xFF); 170 | DBG_OUTPUT_PORT.printf("Set glitter color to: [%u] [%u] [%u]\n", settings.glitter_color.red, settings.glitter_color.green, settings.glitter_color.blue); 171 | webSocket.sendTXT(num, "OK"); 172 | } 173 | 174 | // # ==> Set delay 175 | if (payload[0] == '?') { 176 | // decode delay data 177 | uint8_t d = (uint8_t) strtol((const char *) &payload[1], NULL, 10); 178 | settings.fps = ((d >> 0) & 0xFF); 179 | if (settings.fps == 0) settings.fps = 1; // Prevent divide by zero. 180 | DBG_OUTPUT_PORT.printf("WS: Set FPS: [%u]\n", settings.fps); 181 | webSocket.sendTXT(num, "OK"); 182 | } 183 | 184 | // # ==> Set brightness 185 | if (payload[0] == '%') { 186 | uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); 187 | settings.overall_brightness = ((b >> 0) & 0xFF); 188 | DBG_OUTPUT_PORT.printf("WS: Set brightness to: [%u]\n", settings.overall_brightness); 189 | FastLED.setBrightness(settings.overall_brightness); 190 | webSocket.sendTXT(num, "OK"); 191 | } 192 | 193 | // e ==> Set effect brightness 194 | if (payload[0] == 'e') { 195 | uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); 196 | settings.effect_brightness = ((b >> 0) & 0xFF); 197 | DBG_OUTPUT_PORT.printf("WS: Set effect brightness to: [%u]\n", settings.effect_brightness); 198 | webSocket.sendTXT(num, "OK"); 199 | } 200 | 201 | // # ==> Set show_length 202 | if (payload[0] == '^') { 203 | uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); 204 | settings.show_length = ((b >> 0) & 0xFF); 205 | DBG_OUTPUT_PORT.printf("WS: Set show_length to: [%u]\n", settings.show_length); 206 | webSocket.sendTXT(num, "OK"); 207 | } 208 | 209 | // # ==> Set fade to black speed 210 | if (payload[0] == '_') { 211 | uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); 212 | settings.ftb_speed = ((b >> 0) & 0xFF); 213 | DBG_OUTPUT_PORT.printf("WS: Set fade to black speed to: [%u]\n", settings.ftb_speed); 214 | webSocket.sendTXT(num, "OK"); 215 | } 216 | 217 | // # ==> Set fade glitter density 218 | if (payload[0] == '+') { 219 | uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); 220 | settings.glitter_density = ((b >> 0) & 0xFF); 221 | DBG_OUTPUT_PORT.printf("WS: Set fade to glitter density to: [%u]\n", settings.glitter_density); 222 | webSocket.sendTXT(num, "OK"); 223 | } 224 | 225 | 226 | // * ==> Set main color and light all LEDs (Shortcut) 227 | if (payload[0] == '*') { 228 | // decode rgb data 229 | uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16); 230 | 231 | settings.main_color.red = ((rgb >> 16) & 0xFF); 232 | settings.main_color.green = ((rgb >> 8) & 0xFF); 233 | settings.main_color.blue = ((rgb >> 0) & 0xFF); 234 | 235 | for (int i = 0; i < NUM_LEDS; i++) { 236 | leds[i] = CRGB(settings.main_color.red, settings.main_color.green, settings.main_color.blue); 237 | } 238 | FastLED.show(); 239 | DBG_OUTPUT_PORT.printf("WS: Set all leds to main color: [%u] [%u] [%u]\n", settings.main_color.red, settings.main_color.green, settings.main_color.blue); 240 | //exit_func = true; 241 | settings.mode = ALL; 242 | webSocket.sendTXT(num, "OK"); 243 | } 244 | 245 | // ! ==> Set single LED in given color 246 | if (payload[0] == '!') { 247 | // decode led index 248 | uint64_t rgb = (uint64_t) strtol((const char *) &payload[1], NULL, 16); 249 | 250 | uint8_t led = ((rgb >> 24) & 0xFF); 251 | if (led < NUM_LEDS) { 252 | ledstates[led].red = ((rgb >> 16) & 0xFF); 253 | ledstates[led].green = ((rgb >> 8) & 0xFF); 254 | ledstates[led].blue = ((rgb >> 0) & 0xFF); 255 | DBG_OUTPUT_PORT.printf("WS: Set single led [%u] to [%u] [%u] [%u]!\n", led, ledstates[led].red, ledstates[led].green, ledstates[led].blue); 256 | 257 | for (uint8_t i = 0; i < NUM_LEDS; i++) { 258 | leds[i] = CRGB(ledstates[i].red, ledstates[i].green, ledstates[i].blue); 259 | 260 | } 261 | FastLED.show(); 262 | } 263 | //exit_func = true; 264 | settings.mode = ALL; 265 | webSocket.sendTXT(num, "OK"); 266 | } 267 | 268 | // ! ==> Activate mode 269 | if (payload[0] == '=') { 270 | // we get mode data 271 | String str_mode = String((char *) &payload[0]); 272 | 273 | //exit_func = true; 274 | 275 | if (str_mode.startsWith("=off")) { 276 | settings.mode = OFF; 277 | } 278 | if (str_mode.startsWith("=all")) { 279 | settings.mode = ALL; 280 | } 281 | if (str_mode.startsWith("=mixedshow")) { 282 | settings.mode = MIXEDSHOW; 283 | } 284 | if (str_mode.startsWith("=rainbow")) { 285 | settings.mode = RAINBOW; 286 | } 287 | if (str_mode.startsWith("=confetti")) { 288 | settings.mode = CONFETTI; 289 | } 290 | if (str_mode.startsWith("=sinelon")) { 291 | settings.mode = SINELON; 292 | } 293 | if (str_mode.startsWith("=juggle")) { 294 | settings.mode = JUGGLE; 295 | } 296 | if (str_mode.startsWith("=bpm")) { 297 | settings.mode = BPM; 298 | } 299 | if (str_mode.startsWith("=palette_anims")) { 300 | if (settings.palette_ndx != -1) { 301 | currentPaletteIndex = settings.palette_ndx; 302 | loadPaletteFromFile(settings.palette_ndx, &targetPalette); 303 | } 304 | settings.mode = PALETTE_ANIMS; 305 | } 306 | if (str_mode.startsWith("=ripple")) { 307 | settings.mode = RIPPLE; 308 | } 309 | if (str_mode.startsWith("=comet")) { 310 | settings.mode = COMET; 311 | } 312 | if (str_mode.startsWith("=theaterchase")) { 313 | settings.mode = THEATERCHASE; 314 | } 315 | if (str_mode.startsWith("=add_glitter")) { 316 | settings.glitter_on = true; 317 | } 318 | if (str_mode.startsWith("=stop_glitter")) { 319 | settings.glitter_on = false; 320 | } 321 | if (str_mode.startsWith("=start_glitter_wipe")) { 322 | settings.glitter_wipe_on = true; 323 | } 324 | if (str_mode.startsWith("=stop_glitter_wipe")) { 325 | settings.glitter_wipe_on = false; 326 | } 327 | if (str_mode.startsWith("=wipe")) { 328 | settings.mode = WIPE; 329 | } 330 | if (str_mode.startsWith("=tv")) { 331 | settings.mode = TV; 332 | } 333 | DBG_OUTPUT_PORT.printf("Activated mode [%u]!\n", settings.mode); 334 | webSocket.sendTXT(num, "OK"); 335 | } 336 | 337 | // $ ==> Get status Info. 338 | if (payload[0] == '$') { 339 | DBG_OUTPUT_PORT.printf("Get status info."); 340 | 341 | String json = listStatusJSON(); 342 | DBG_OUTPUT_PORT.println(json); 343 | webSocket.sendTXT(num, json); 344 | } 345 | 346 | // ` ==> Restore defaults. 347 | if (payload[0] == '`') { 348 | DBG_OUTPUT_PORT.printf("Restore defaults."); 349 | loadDefaults(); 350 | String json = listStatusJSON(); 351 | DBG_OUTPUT_PORT.println(json); 352 | webSocket.sendTXT(num, json); 353 | } 354 | 355 | // | ==> Save settings. 356 | if (payload[0] == '|') { 357 | DBG_OUTPUT_PORT.printf("Save settings."); 358 | saveSettings(); 359 | webSocket.sendTXT(num, "OK"); 360 | } 361 | 362 | // \ ==> Load settings. 363 | if (payload[0] == '\\') { 364 | DBG_OUTPUT_PORT.printf("Load settings."); 365 | readSettings(false); 366 | 367 | String json = listStatusJSON(); 368 | DBG_OUTPUT_PORT.println(json); 369 | webSocket.sendTXT(num, json); 370 | } 371 | 372 | // { ==> Change palette 373 | if (payload[0] == '{') { 374 | if (length == 2) { 375 | if (payload[1] == '+') { 376 | DBG_OUTPUT_PORT.printf("Current pallet_ndx=%d\n", settings.palette_ndx); 377 | settings.palette_ndx++; 378 | if (settings.palette_ndx >= paletteCount) { 379 | settings.palette_ndx = 0; 380 | } 381 | 382 | targetPaletteIndex = settings.palette_ndx; 383 | loadPaletteFromFile(settings.palette_ndx, &targetPalette); 384 | 385 | if (settings.glitter_wipe_on) { 386 | wipeInProgress = true; 387 | } 388 | 389 | DBG_OUTPUT_PORT.printf("Next palette: %d\n", settings.palette_ndx); 390 | 391 | } else if (payload[1] == 'r') { 392 | DBG_OUTPUT_PORT.printf("Randomize palette.\n"); 393 | settings.palette_ndx = -1; 394 | ChangePalettePeriodically(true); 395 | } else if (payload[1] == 'd') { 396 | DBG_OUTPUT_PORT.printf("Change direction: %d\n", anim_direction); 397 | anim_direction = (DIRECTION)!anim_direction; 398 | } 399 | } 400 | String json = listStatusJSON(); 401 | DBG_OUTPUT_PORT.println(json); 402 | webSocket.sendTXT(num, json); 403 | } 404 | 405 | // " ==> Confetti Density 406 | if (payload[0] == '"') { 407 | uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); 408 | settings.confetti_dens = ((b >> 0) & 0xFF); 409 | DBG_OUTPUT_PORT.printf("WS: Set confetti density to: [%u]\n", settings.confetti_dens); 410 | webSocket.sendTXT(num, "OK"); 411 | } 412 | break; 413 | } 414 | } 415 | 416 | void checkForRequests() { 417 | webSocket.loop(); 418 | server.handleClient(); 419 | } 420 | -------------------------------------------------------------------------------- /spiffs_webserver.h: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // SPIFFS Webserver 3 | // Source: https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer/examples/FSBrowser 4 | // *************************************************************************** 5 | 6 | /* 7 | FSWebServer - Example WebServer with SPIFFS backend for esp8266 8 | Copyright (c) 2015 Hristo Gochkov. All rights reserved. 9 | This file is part of the ESP8266WebServer library for Arduino environment. 10 | 11 | This library is free software; you can redistribute it and/or 12 | modify it under the terms of the GNU Lesser General Public 13 | License as published by the Free Software Foundation; either 14 | version 2.1 of the License, or (at your option) any later version. 15 | This library is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | Lesser General Public License for more details. 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | 23 | upload the contents of the data folder with MkSPIFFS Tool ("ESP8266 Sketch Data Upload" in Tools menu in Arduino IDE) 24 | or you can upload the contents of a folder if you CD in that folder and run the following command: 25 | for file in `ls -A1`; do curl -F "file=@$PWD/$file" esp8266fs.local/edit; done 26 | 27 | access the sample web page at http://esp8266fs.local 28 | edit the page by going to http://esp8266fs.local/edit 29 | */ 30 | 31 | File fsUploadFile; 32 | 33 | //format bytes 34 | String formatBytes(size_t bytes) { 35 | if (bytes < 1024) { 36 | return String(bytes) + "B"; 37 | } else if (bytes < (1024 * 1024)) { 38 | return String(bytes / 1024.0) + "KB"; 39 | } else if (bytes < (1024 * 1024 * 1024)) { 40 | return String(bytes / 1024.0 / 1024.0) + "MB"; 41 | } else { 42 | return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; 43 | } 44 | } 45 | 46 | String getContentType(String filename) { 47 | if (server.hasArg("download")) return "application/octet-stream"; 48 | else if (filename.endsWith(".htm")) return "text/html"; 49 | else if (filename.endsWith(".html")) return "text/html"; 50 | else if (filename.endsWith(".css")) return "text/css"; 51 | else if (filename.endsWith(".js")) return "application/javascript"; 52 | else if (filename.endsWith(".png")) return "image/png"; 53 | else if (filename.endsWith(".gif")) return "image/gif"; 54 | else if (filename.endsWith(".jpg")) return "image/jpeg"; 55 | else if (filename.endsWith(".ico")) return "image/x-icon"; 56 | else if (filename.endsWith(".xml")) return "text/xml"; 57 | else if (filename.endsWith(".pdf")) return "application/x-pdf"; 58 | else if (filename.endsWith(".zip")) return "application/x-zip"; 59 | else if (filename.endsWith(".gz")) return "application/x-gzip"; 60 | return "text/plain"; 61 | } 62 | 63 | bool handleFileRead(String path) { 64 | DBG_OUTPUT_PORT.println("handleFileRead: " + path); 65 | if (path.endsWith("/")) path += "index.htm"; 66 | String contentType = getContentType(path); 67 | String pathWithGz = path + ".gz"; 68 | if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { 69 | if (SPIFFS.exists(pathWithGz)) 70 | path += ".gz"; 71 | File file = SPIFFS.open(path, "r"); 72 | size_t sent = server.streamFile(file, contentType); 73 | file.close(); 74 | return true; 75 | } 76 | return false; 77 | } 78 | 79 | void handleFileUpload() { 80 | if (server.uri() != "/edit") return; 81 | HTTPUpload& upload = server.upload(); 82 | if (upload.status == UPLOAD_FILE_START) { 83 | String filename = upload.filename; 84 | if (!filename.startsWith("/")) filename = "/" + filename; 85 | DBG_OUTPUT_PORT.print("handleFileUpload Name: "); 86 | DBG_OUTPUT_PORT.println(filename); 87 | fsUploadFile = SPIFFS.open(filename, "w"); 88 | filename = String(); 89 | } else if (upload.status == UPLOAD_FILE_WRITE) { 90 | //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize); 91 | if (fsUploadFile) 92 | fsUploadFile.write(upload.buf, upload.currentSize); 93 | } else if (upload.status == UPLOAD_FILE_END) { 94 | if (fsUploadFile) 95 | fsUploadFile.close(); 96 | DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); 97 | } 98 | 99 | // Update the paletteCount in case someone uploaded one. 100 | paletteCount = getPaletteCount(); 101 | 102 | } 103 | 104 | void handleFileDelete() { 105 | if (server.args() == 0) return server.send(500, "text/plain", "BAD ARGS"); 106 | String path = server.arg(0); 107 | DBG_OUTPUT_PORT.println("handleFileDelete: " + path); 108 | if (path == "/") 109 | return server.send(500, "text/plain", "BAD PATH"); 110 | if (!SPIFFS.exists(path)) 111 | return server.send(404, "text/plain", "FileNotFound"); 112 | SPIFFS.remove(path); 113 | server.send(200, "text/plain", ""); 114 | path = String(); 115 | 116 | // Update the paletteCount in case someone deleted one. 117 | paletteCount = getPaletteCount(); 118 | 119 | } 120 | 121 | void handleFileCreate() { 122 | if (server.args() == 0) 123 | return server.send(500, "text/plain", "BAD ARGS"); 124 | String path = server.arg(0); 125 | DBG_OUTPUT_PORT.println("handleFileCreate: " + path); 126 | if (path == "/") 127 | return server.send(500, "text/plain", "BAD PATH"); 128 | if (SPIFFS.exists(path)) 129 | return server.send(500, "text/plain", "FILE EXISTS"); 130 | File file = SPIFFS.open(path, "w"); 131 | if (file) 132 | file.close(); 133 | else 134 | return server.send(500, "text/plain", "CREATE FAILED"); 135 | server.send(200, "text/plain", ""); 136 | path = String(); 137 | 138 | // Update the paletteCount in case someone created one. 139 | paletteCount = getPaletteCount(); 140 | } 141 | 142 | void handleFileList() { 143 | if (!server.hasArg("dir")) { 144 | server.send(500, "text/plain", "BAD ARGS"); 145 | return; 146 | } 147 | 148 | String path = server.arg("dir"); 149 | DBG_OUTPUT_PORT.println("handleFileList: " + path); 150 | Dir dir = SPIFFS.openDir(path); 151 | path = String(); 152 | 153 | String output = "["; 154 | while (dir.next()) { 155 | File entry = dir.openFile("r"); 156 | if (output != "[") output += ','; 157 | bool isDir = false; 158 | output += "{\"type\":\""; 159 | output += (isDir) ? "dir" : "file"; 160 | output += "\",\"name\":\""; 161 | output += String(entry.name()).substring(1); 162 | output += "\"}"; 163 | entry.close(); 164 | } 165 | 166 | output += "]"; 167 | server.send(200, "text/json", output); 168 | } 169 | 170 | -------------------------------------------------------------------------------- /upload these via file mgr to ESP8266/color-selector.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Getting colors 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 130 | 131 |
132 | 133 |
134 | toHEXString =
135 | toRGBString =
136 | R, G, B =
137 | H, S, V = 138 |
139 | 140 | 141 | 143 | 144 | 145 | 146 | --------------------------------------------------------------------------------