├── Circuit Construction Notes.txt ├── ESP8266_WiFi_RGB_Lamp_FINAL.ino ├── README.md └── html_pages.h /Circuit Construction Notes.txt: -------------------------------------------------------------------------------- 1 | Circuit Connstruction Notes 2 | ============================ 3 | 4 | // The LED strip that was used for this development was a 60 LED RGB WS2812 5 | // The strip was wrapped around a 1.5 inch dia. tube from top to bottom. This gave good rainbow effects in rainbow mode. 6 | // The whole thing was then placed inside an opaque glass jar. This obfuscates the individual LED's to give a uniform light. 7 | // For candle mode, the bottom 30 LED's were used to illuminated. This gives the impression of a small candle or tea light. -------------------------------------------------------------------------------- /ESP8266_WiFi_RGB_Lamp_FINAL.ino: -------------------------------------------------------------------------------- 1 | /*ESP8266WiFiWebServer 2 | * Copyright (c) 2015, Majenko Technologies 3 | * All rights reserved. 4 | */ 5 | 6 | /*The following project leans heavily on the NeoPixel library from Adafruit */ 7 | /* Many thanks to Adafruit for the NeoPixel library and NeoCandle code. */ 8 | /* https://www.adafruit.com/ */ 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "html_pages.h" 18 | 19 | #define PIN 0 20 | #define NUMPIXELS 60 // Number of LED's in your strip 21 | 22 | // START OF CANDLE MODE RELATED STUFF //////////////////////////////////////////////// 23 | #define CANDLEPIXELS 30 //This is how may led's will represent your 'wick'. With a 60 LED strip, 30 gives the effect of a tealight or small candle when in an opaque container. 24 | // END OF CANDLE MODE RELATED STUFF ////////////////////////////////////////////////// 25 | 26 | 27 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); 28 | 29 | // START OF CANDLE MODE RELATED STUFF //////////////////////////////////////////////// 30 | Adafruit_NeoPixel *wick; 31 | byte state; 32 | unsigned long flicker_msecs; 33 | unsigned long flicker_start; 34 | byte index_start; 35 | byte index_end; 36 | // END OF CANDLE MODE RELATED STUFF //////////////////////////////////////////////////// 37 | 38 | const char *ssid = " (B)) ? (A) : (B)) 93 | 94 | // END OF CANDLE MODE RELATED STUFF //////////////////////////////////////////////////// 95 | 96 | void handleIndex(); 97 | void handleNotFound(); 98 | void handleSwitchOn(); 99 | void handleSwitchOff(); 100 | void handleSetColour(); 101 | void handleColour(); 102 | void handleSetBrightness(); 103 | void handleBrightness(); 104 | void handleSelectMode(); 105 | void handle_mode1(); 106 | void handle_mode2(); 107 | void handle_mode3(); 108 | void handle_mode4(); 109 | void set_color(byte); 110 | void light_up_all(); 111 | void turn_off_all(); 112 | uint32_t Wheel(byte); 113 | 114 | void setup ( void ) { 115 | Serial.begin ( 115200 ); 116 | WiFi.begin ( ssid, password ); 117 | Serial.println ( "" ); 118 | 119 | // Wait for connection 120 | while ( WiFi.status() != WL_CONNECTED ) { 121 | delay ( 500 ); 122 | Serial.print ( "." ); 123 | } 124 | 125 | EEPROM.begin(5); // set up EEPROM storage space for config values 126 | 127 | Serial.println ( "" ); 128 | Serial.print ( "Connected to " ); 129 | Serial.println ( ssid ); 130 | Serial.print ( "IP address: " ); 131 | Serial.println ( WiFi.localIP() ); 132 | 133 | if ( mdns.begin ( "esp8266", WiFi.localIP() ) ) { 134 | Serial.println ( "MDNS responder started" ); 135 | } 136 | 137 | Serial.println ( "HTTP server started" ); 138 | 139 | server.on ( "/", handleIndex ); 140 | server.onNotFound ( handleNotFound ); 141 | 142 | server.on ( "/switch_on", handleSwitchOn); 143 | server.on ( "/switch_off", handleSwitchOff); 144 | server.on ( "/set_colour", handleSetColour); 145 | server.on ( "/set_colour_hash", handleColour ); 146 | server.on ( "/set_brightness", handleSetBrightness); 147 | server.on ( "/set_bright_val", handleBrightness); 148 | server.on ( "/select_mode", handleSelectMode); 149 | server.on ( "/set_mode1", handle_mode1); 150 | server.on ( "/set_mode2", handle_mode2); 151 | server.on ( "/set_mode3", handle_mode3); 152 | server.on ( "/set_mode4", handle_mode4); 153 | 154 | //EEPROM Memory// 155 | //Mem Location ||--0--||--1--||--2--||--3--||--4--||--5--||--6--|| 156 | // red green blue bright mode 157 | 158 | red_int = EEPROM.read(0); //restore colour to last used value. Ensures RGB lamp is same colour as when last switched off 159 | green_int = EEPROM.read(1); 160 | blue_int = EEPROM.read(2); 161 | brightness = EEPROM.read(3); 162 | mode_flag = EEPROM.read(4); 163 | 164 | server.begin(); 165 | strip.begin(); 166 | 167 | 168 | // START OF CANDLE MODE RELATED STUFF //////////////////////////////////////////////// 169 | // There is no good source of entropy to seed the random number generator, 170 | // so we'll just read the analog value of an unconnected pin. This won't be 171 | // very random either, but there's really nothing else we can do. 172 | // 173 | // True randomness isn't strictly necessary, we just don't want a whole 174 | // string of these things to do exactly the same thing at the same time if 175 | // they're all powered on simultaneously. 176 | randomSeed(analogRead(UNCONNECTED_PIN)); 177 | 178 | 179 | wick = new Adafruit_NeoPixel(CANDLEPIXELS, WICK_PIN, NEO_GRB + NEO_KHZ800); 180 | wick->begin(); 181 | wick->show(); 182 | 183 | set_color(255); 184 | index_start = 255; 185 | index_end = 255; 186 | state = BRIGHT; 187 | // END OF CANDLE MODE RELATED STUFF //////////////////////////////////////////////////// 188 | 189 | handleSwitchOn(); 190 | } 191 | 192 | void loop ( void ) { 193 | mdns.update(); 194 | server.handleClient(); 195 | 196 | 197 | // START OF CANDLE MODE RELATED STUFF //////////////////////////////////////////////// 198 | // The candle mode related stuff is here to ensure it has the fastest response time 199 | // All the other modes have their own functions outside of the main loop 200 | 201 | if (mode_flag==4){ 202 | 203 | unsigned long current_time; 204 | current_time = millis(); 205 | 206 | //Serial.println("In Loop"); 207 | //Serial.println(state); 208 | 209 | switch (state) 210 | { 211 | case BRIGHT: 212 | // Serial.println("Bright"); 213 | mdns.update(); 214 | server.handleClient(); 215 | flicker_msecs = random(DOWN_MAX_MSECS - DOWN_MIN_MSECS) + DOWN_MIN_MSECS; 216 | flicker_start = current_time; 217 | index_start = index_end; 218 | if ((index_start > INDEX_BOTTOM) && 219 | (random(100) < INDEX_BOTTOM_PERCENT)) 220 | index_end = random(index_start - INDEX_BOTTOM) + INDEX_BOTTOM; 221 | else 222 | index_end = random(index_start - INDEX_MIN) + INDEX_MIN; 223 | 224 | state = DOWN; 225 | break; 226 | 227 | case DIM: 228 | // Serial.println("Dim"); 229 | mdns.update(); 230 | server.handleClient(); 231 | flicker_msecs = random(UP_MAX_MSECS - UP_MIN_MSECS) + UP_MIN_MSECS; 232 | flicker_start = current_time; 233 | index_start = index_end; 234 | index_end = random(INDEX_MAX - index_start) + INDEX_MIN; 235 | state = UP; 236 | break; 237 | 238 | case BRIGHT_HOLD: 239 | case DIM_HOLD: 240 | // Serial.println("DIM Hold"); 241 | mdns.update(); 242 | server.handleClient(); 243 | if (current_time >= (flicker_start + flicker_msecs)) 244 | state = (state == BRIGHT_HOLD) ? BRIGHT : DIM; 245 | break; 246 | 247 | case UP: 248 | case DOWN: 249 | // Serial.println("Down"); 250 | mdns.update(); 251 | server.handleClient(); 252 | if (current_time < (flicker_start + flicker_msecs)) 253 | set_color(index_start + ((index_end - index_start) * (((current_time - flicker_start) * 1.0) / flicker_msecs))); 254 | else 255 | { 256 | set_color(index_end); 257 | 258 | if (state == DOWN) 259 | { 260 | if (random(100) < DIM_HOLD_PERCENT) 261 | { 262 | flicker_start = current_time; 263 | flicker_msecs = random(DIM_HOLD_MAX_MSECS - DIM_HOLD_MIN_MSECS) + DIM_HOLD_MIN_MSECS; 264 | state = DIM_HOLD; 265 | } 266 | else 267 | state = DIM; 268 | } 269 | else 270 | { 271 | if (random(100) < BRIGHT_HOLD_PERCENT) 272 | { 273 | flicker_start = current_time; 274 | flicker_msecs = random(BRIGHT_HOLD_MAX_MSECS - BRIGHT_HOLD_MIN_MSECS) + BRIGHT_HOLD_MIN_MSECS; 275 | state = BRIGHT_HOLD; 276 | } 277 | else 278 | state = BRIGHT; 279 | } 280 | } 281 | 282 | break; 283 | } } 284 | // END OF CANDLE MODE RELATED STUFF //////////////////////////////////////////////////// 285 | 286 | return; 287 | } 288 | 289 | void handleIndex(){ 290 | Serial.println ( "Request for index page received" ); 291 | server.send ( 200, "text/html", page_contents); 292 | } 293 | 294 | void handleNotFound() { 295 | String message = "File Not Found\n\n"; 296 | message += "URI: "; 297 | message += server.uri(); 298 | message += "\nMethod: "; 299 | message += ( server.method() == HTTP_GET ) ? "GET" : "POST"; 300 | message += "\nArguments: "; 301 | message += server.args(); 302 | message += "\n"; 303 | 304 | for ( uint8_t i = 0; i < server.args(); i++ ) { 305 | message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n"; 306 | } 307 | 308 | server.send ( 404, "text/plain", message ); 309 | } 310 | 311 | void handleSwitchOn() { 312 | mode_flag = EEPROM.read(4); // start-up in last saved mode 313 | delay(100); 314 | switch(mode_flag){ 315 | case 1:handle_mode1(); 316 | break; 317 | case 2:handle_mode2(); 318 | break; 319 | case 3:handle_mode3(); 320 | break; 321 | case 4:handle_mode4(); 322 | break; 323 | default: 324 | light_up_all(); //Default to fixed colour should the EEProm become corrupted 325 | break; 326 | } 327 | server.send ( 200, "text/html", "" ); 328 | }; 329 | 330 | void handleSwitchOff() { 331 | mode_flag=1; //go to default fixed color mode and turn off all pixels 332 | delay(100); 333 | turn_off_all(); 334 | server.send ( 200, "text/html", "" ); 335 | } 336 | 337 | void handleSetColour() { 338 | server.send ( 200, "text/html", colour_picker); 339 | } 340 | 341 | 342 | void handleSetBrightness(){ 343 | server.send ( 200, "text/html", bright_set); 344 | } 345 | 346 | 347 | void handleSelectMode(){ 348 | server.send ( 200, "text/html", mode_page ); 349 | // Serial.println ( "Mode select page" ); 350 | } 351 | 352 | 353 | void handleColour(){ 354 | char buf_red[3]; //char buffers to hold 'String' value converted to char array 355 | char buf_green[3]; 356 | char buf_blue[3]; 357 | String message = server.arg(0); //get the 1st argument from the url which is the hex rgb value from the colour picker ie. #rrggbb (actually %23rrggbb) 358 | rgb_now = message; 359 | rgb_now.replace("%23", "#"); // change %23 to # as we need this in one of html pages 360 | String red_val = rgb_now.substring(1,3); //extract the rgb values 361 | String green_val = rgb_now.substring(3,5); 362 | String blue_val = rgb_now.substring(5,7); 363 | 364 | mode_flag=1; //get to fixed colour mode if not already 365 | 366 | red_val.toCharArray(buf_red,3); //convert hex 'String' to Char[] for use in strtol() 367 | green_val.toCharArray(buf_green,3); 368 | blue_val.toCharArray(buf_blue,3); 369 | 370 | red_int = gamma_adjust[strtol( buf_red, NULL, 16)]; //convert hex chars to ints and apply gamma adjust 371 | green_int = gamma_adjust[strtol( buf_green, NULL, 16)]; 372 | blue_int = gamma_adjust[strtol( buf_blue, NULL, 16)]; 373 | 374 | EEPROM.write(0,red_int); //write the colour values to EEPROM to be restored on start-up 375 | EEPROM.write(1,green_int); 376 | EEPROM.write(2,blue_int); 377 | EEPROM.commit(); 378 | 379 | light_up_all(); 380 | String java_redirect = ""; 383 | server.send ( 200, "text/html", java_redirect ); // all done! - take user back to the colour picking page 384 | } 385 | 386 | void handleBrightness(){ 387 | String message = server.arg(0); //get the 1st argument from the url which is the brightness level set by the slider 388 | String bright_val = message.substring(0,3); //extract the brightness value from the end of the argument in the URL 389 | brightness = bright_val.toInt(); 390 | EEPROM.write(3,brightness); //write the brightness value to EEPROM to be restored on start-up 391 | EEPROM.commit(); 392 | 393 | String java_redirect = ""; 396 | server.send ( 200, "text/html", java_redirect); // all done! - take user back to the brightness selection page 397 | } 398 | 399 | void light_up_all(){ 400 | for(int i=0;iwindow.location='/';"); 417 | 418 | while(mode_flag==1){ // Check the mode hasn't been changed whilst we wait, if so - leave immediately 419 | light_up_all(); //set mode to default state - all led's on, fixed colour. This loop will service any brightness changes 420 | loop(); // Not much to do except service the main loop 421 | }return; 422 | } 423 | 424 | 425 | void handle_mode2(){ //colour fade mode 426 | mode_flag = 2; 427 | EEPROM.write(4,mode_flag); //write mode to EEProm so can be restored on start-up 428 | EEPROM.commit(); 429 | server.send ( 200, "text/html",""); 430 | uint16_t i, j, k; 431 | int wait = 10; //DON'T ever set this more than '10'. Use the 'k' value in the loop below to increase delays. This prevents the watchdog timer timing out on the ESP8266 432 | 433 | while(mode_flag==2){ 434 | for(j=0; j<256; j++) { 435 | loop(); 436 | for(i=0; iwindow.location='/';"); 454 | uint16_t i, j, k; 455 | int wait = 10; //DON'T ever set this more than '10'. Use the 'k' value in the loop below to increase delays. This prevents the watchdog timer timing out on the ESP8266 456 | 457 | while(mode_flag==3){ // do this indefenitely or until mode changes 458 | for(j=0; j < 256*5; j++) { // 5 cycles of all colors on wheel 459 | loop(); 460 | for(i=0; i < NUMPIXELS; i++) { 461 | loop(); 462 | strip.setPixelColor(i,Wheel(((i * 256 / NUMPIXELS) + j) & 255)); 463 | if(mode_flag!=3){return;} //the mode has been changed - get outta here! 464 | } 465 | strip.show(); 466 | 467 | for(k=0; k < 50; k++){ // Do ten loops of the 'wait' and service loop routine inbetween. Total wait = 10 x 'wait'. This prevents sluggishness in the browser html front end menu. 468 | delay(wait); 469 | loop(); 470 | } 471 | }}return; 472 | } 473 | 474 | 475 | uint32_t Wheel(byte WheelPos) { 476 | WheelPos = 255 - WheelPos; 477 | if(WheelPos < 85) { 478 | loop(); 479 | return strip.Color(brightness*(255 - WheelPos * 3)/255, 0, brightness*(WheelPos * 3)/255); //scale the output values by a factor of global 'brightness' so that the brightness remains as set 480 | } 481 | if(WheelPos < 170) { 482 | WheelPos -= 85; 483 | loop(); 484 | return strip.Color(0,brightness*(WheelPos * 3)/255, brightness*(255 - WheelPos * 3)/255); 485 | } 486 | WheelPos -= 170; 487 | loop(); 488 | return strip.Color(brightness*(WheelPos * 3)/255, brightness*(255 - WheelPos * 3)/255, 0); 489 | } 490 | 491 | 492 | void handle_mode4(){ //Candle mode 493 | mode_flag = 4; 494 | EEPROM.write(4,mode_flag); //write mode to EEProm so can be restored on start-up 495 | EEPROM.commit(); 496 | server.send ( 200, "text/html",""); 497 | turn_off_all(); 498 | mode_flag = 4; 499 | loop(); 500 | } 501 | 502 | 503 | 504 | void set_color(byte index) 505 | { 506 | int i; 507 | index = MAXVAL(MINVAL(index, INDEX_MAX), INDEX_BOTTOM); 508 | if (index >= INDEX_MIN){ 509 | for(i=0;isetPixelColor(i, index, (index * 3) / 8, 0); 511 | }} 512 | else if (index < INDEX_MIN){ 513 | for(i=0;isetPixelColor(i, index, (index * 3.25) / 8, 0); 515 | }} 516 | 517 | wick->show(); 518 | return; 519 | } 520 | 521 | 522 | 523 | 524 | 525 | 526 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino-ESP8266-RGB-Web-Server 2 | A Wi-Fi web server for the ESP8266 platform driving a WS2812B addressable RGB Led strip - Arduino sketch 3 | 4 | Description: 5 | An Arduino sketch for running a Wi-Fi web server on the ESP8266-12F. Designed to drive the WS2812B addressable RGB Led strips using the Adafruit Neopixel Arduino library. 6 | 7 | Construction Notes: 8 | The software has been written to give the best effects with a 60 LED WS2812 RGB LED strip but will work with any. The number of LED's in your strip can be set in code. 9 | 10 | To get the best effects (especially in candle mode) wrap the strip around a tube approx. 1.5 inches dia. to form a cylinder of light. Place this in an opaque container to obfuscate the individual LED's to give an overall 'glow'. 11 | 12 | This ensures that the rainbow mode is effective and also the candle mode appears to be a small tea light or candle at the bottom of the container. 13 | 14 | Implements the following: 15 | 16 | Adafruit Neopixel Library: 17 | https://learn.adafruit.com/adafruit-neopixel-uberguide?view=all#arduino-library 18 | 19 | 20 | ESP8266 Web Server: 21 | https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino 22 | 23 | 24 | Useful Information: 25 | 26 | ESP8266 Setup Guide: 27 | https://learn.sparkfun.com/tutorials/esp8266-thing-hookup-guide?_ga=1.192693098.1720005138.1446065268 28 | 29 | Future improvements I would like too add. 30 | 31 | 1) Additional operation mode that allows the user to set a 'sunrise' wake-up alarm. The lamp will switch on at a certain time at zero brightness and then gradually get brighter and brighter over a user definable period of time. The color setting should also be user definable. Perhaps implementing an internet time look up to update an on-board clock. 32 | 33 | 2) Test all this on a standard ESP8266-12F (around $5) rather than using the 'Sparkfun ESP8266 Thing'. - UPDATE: Done, it works Ok. 34 | 35 | 3) Allow the user to set the transition time (via the html menu system), between colors for the color fade mode and rainbow mode. 36 | 37 | 4) Update 21/07/2016 - Candle mode added. Experiment with '#define CANDLEPIXELS XX' (default 30) to get the best effect for your set up. Use more or less pixels to suit how bright you want the candle to be. Tweak the code if you want to try and make a better candle effect. Brightness and Colour set mode do not function when in candle mode because colour and brightness level are fundamental parts of the candle effect. 38 | 39 | 5) Add more modes - Strobe, Disco, Breathing 40 | 41 | 6) Start the RGB Lamp in AP Mode first so that a user can connect directly and determine the I.P. Address it occupies on the local network. Maybe give 20 seconds for an incoming AP connection, then switch to server mode / normal operation. 42 | 43 | The only way to determine the IP address of the lamp at the moment is to either connect to the serial port and monitor the output on start-up or log into your router. You can either tell the router to allocate an IP address based on the MAC address of your ESP8266 or just look to see what is connected and on what IP in your router logs. 44 | -------------------------------------------------------------------------------- /html_pages.h: -------------------------------------------------------------------------------- 1 | const uint16_t gamma_adjust[] { 2 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 4 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 5 | 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 6 | 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 7 | 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 8 | 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 9 | 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 10 | 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, 11 | 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 12 | 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 13 | 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, 14 | 115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, 15 | 144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, 16 | 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, 17 | 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; 18 | 19 | 20 | const char page_contents[] PROGMEM = R"=====( 21 | 22 | 23 | 24 | 25 |

RGB Lamp 1

26 | 27 |

28 | 29 |



30 | 31 |



32 | 33 |



34 | 35 |



36 | 37 | )====="; 38 | 39 | 40 | const char colour_picker[] PROGMEM = R"=====( 41 | 42 | 43 | 44 | 45 |


Touch below to select a colour



46 | 47 | 48 |

49 | 50 |

51 | 52 | 53 | 76 | 77 | 78 | )====="; 79 | 80 | 81 | const char bright_set[] PROGMEM = R"=====( 82 | 83 | 84 | 85 | 86 |


Move The Slider to Set Brightness



87 | 88 | 89 |

90 | 91 |

92 | 93 | 94 | 117 | 118 | 119 | )====="; 120 | 121 | 122 | const char mode_page[] PROGMEM = R"=====( 123 |

Select Mode



124 | 125 |



126 | 127 |



128 | 129 |



130 | 131 |



132 | 133 |

134 | 135 | )====="; 136 | 137 | 138 | 139 | 140 | --------------------------------------------------------------------------------