In a previous project, i had connected an RGB LED to an ESP8266-01 to be able to change the colour over WiFi. This time i am going to use an 8-pixel Neopixel RGB strip, which only requires 1 digital pin to control it, and the Adafruit Neopixel library works on ESP8266 as listed here.

ASSEMBLY instructions: buffalolabs-lantern-assembly-guide-v1

WORKSHOP package (slides, assembly, code): 20161123lantern-package

I used a laser cutter to make a box for it (9cm x 9cm x 12cm, 3mm thickness), using the Tabbed Box Maker extension of Inkscape. I added some simple stars to the design and this is the result, powered by a USB powerbank.

IMG_20160907_092854664

I started the project on a breadboard as below. I wanted to power the lantern from a standard USB powerbank, so i needed to include a voltage regulator down to 3.3V (i used an MCP1826 that can do up to 1A, smaller is fine too). I wanted a switch with 2 options:

  1. WiFi mode: start an access point (default SSID: Lantern, no password) to allow any device (phone, tablet, laptop) to connect to the ESP8266 web server, to display the below web page (http://esp) that allows to pick a colour (same javascript page as my previous project, inspired by the lights at DSL)
  2. Show mode: no WiFi, just an RGB strip rainbow animation loop based on the Neopixel ‘strandtest’ sketch

img_20161123_090505749_hdr

I used a switch with 3 positions: on/off/on. The challenge was to use this switch to enter one of the 2 above modes, by using a GPIO pin of the ESP8266, in this case GPIO3 (which also acts as Serial RX but that is not a problem). I used a 4K3 pull-down resistor on this pin, and when the switch brings a HIGH voltage to it, the voltage continues to the Vcc pin of the ESP8266 through a diode, to power the module. Without the diode, GPIO3 would also be HIGH when the switch connects power to the Vcc pin. I used a diode with a low forward voltage (IN5819) so that GPIO3 is not much higher than Vcc. And Vcc still gets around 3V in this mode.

The other components are a 430 Ohm resistor on the pin going to the Neopixel strip, GPIO2, as recommended by Adafruit, and a 220uF capacitor on the power rail. I tried using the 5V to power the Neopixel strip, as it mentions 4-7V, but in that caused the wrong lights/colours from time to time, maybe because the 3.3V on the data pin is too low vs the 5V power, so i connected the 3.3V to power the Neopixel strip and that works well.

Below the simple schematics. Vcc is the USB 5V. I ended up using a 0.1uF on the 3.3V side instead of the 1uF in the schematics, and an IN5819 diode which has a lower forward voltage than the 1N4148. On the Neopixel strip, connect DIN (data in) to the 470 resistor.

lantern-schematics-v1

With my breadboard prototype working, i was ready to solder it on some perf board. I used a 4×2 female header to be able to take out the ESP8266-01 module for re-programming. I cut an old USB cable and used the black and red power wires to connect to the voltage regulator. The switch sits after the voltage regulator; energy efficiency is not a priority in this project.

Next i used hot glue to fix the Neopixel strip inside the lantern, and the perf board to the bottom. I also made a knot in the USB cable to prevent it from ripping out the connections.

IMG_20160905_095617694

Below videos of the 2 modes:



gabby isa juno1 juno2 juno3 img_20160915_202121494

img_20161026_085039402

screenshot-2016-11-02-at-16-25-34

This is the PCB i made for this project:

img_20161101_120404040

We had a few good workshops at DSL Nov-Dec 2016 making this lantern.

15170875_10211072792924173_5855002594036932548_n-edited

img_20161202_203549189

And my sketch (tested on Arduino IDE 1.6.5 with added ESP8266 support):

/* Neopixel web server with ESP8266-01
 * with switch to rainbow animation
 *
 * Tom Tobback Sep 2016
 * BuffaloLabs www.cassiopeia.hk/kids
LICENSE:
Copyright (c) 2016 Tom Tobback

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 * web page esp/rgb returns POST request with 3 RGB parameters
 * web page inspired by https://github.com/dimsumlabs/nodemcu-httpd
 *
 * voltage regulator from 5V to 3.3V
 * neopixel strip 8 LEDs on GPIO2, and Vcc on 3.3V otherwise the data goes wrong sometimes
 *
 * ESP8266 ESP-01 connections
 * Vcc 3.3V
 * GND GND
 * GPIO2 neopixel data pin with 430ohm resistor
 * GPIO3 switch between animation/webserver (only at startup!) with 4K3 pull-down resistor and IN5819 diode to Vcc
 * CH_PD 3.3V (actually Vcc)
 * RST 3.3V (actually Vcc)
 *
 * 3 way switch on 3.3V to choose between webserver and animation
 * GPIO3=RX is connected to switch: HIGH= animation LOW= webserver
 * in HIGH state the power goes from GPIO3 to Vcc via a diode IN5819
 * in LOW state the Vcc power cannot reach GPIO3
 */

#include <Adafruit_NeoPixel.h>

Adafruit_NeoPixel strip = Adafruit_NeoPixel(8, 2, NEO_GRB + NEO_KHZ800);

#include <ESP8266WiFi.h>
 #include <DNSServer.h>
 #include <ESP8266WebServer.h>

const char *ssid = "RGBlights";
 // const char *password = "87654321";

const byte DNS_PORT = 53;
 IPAddress apIP(192, 168, 1, 1);
 DNSServer dnsServer;
 ESP8266WebServer webServer(80);

boolean animation;
 const byte brightness = 250;

String webpageRGB = ""
 "<!DOCTYPE html><html><head><title>Neopixel control</title><meta name='mobile-web-app-capable' content='yes' />"
 "<meta name='viewport' content='width=device-width' /></head><body style='margin: 0px; padding: 0px;'>"
 "<canvas id='colorspace'></canvas></body>"
 "<script type='text/javascript'>"
 "(function () {"
 " var canvas = document.getElementById('colorspace');"
 " var ctx = canvas.getContext('2d');"
 " function drawCanvas() {"
 " var colours = ctx.createLinearGradient(0, 0, window.innerWidth, 0);"
 " for(var i=0; i <= 360; i+=10) {"
 " colours.addColorStop(i/360, 'hsl(' + i + ', 100%, 50%)');"
 " }"
 " ctx.fillStyle = colours;"
 " ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);"
 " var luminance = ctx.createLinearGradient(0, 0, 0, ctx.canvas.height);"
 " luminance.addColorStop(0, '#ffffff');"
 " luminance.addColorStop(0.05, '#ffffff');"
 " luminance.addColorStop(0.5, 'rgba(0,0,0,0)');"
 " luminance.addColorStop(0.95, '#000000');"
 " luminance.addColorStop(1, '#000000');"
 " ctx.fillStyle = luminance;"
 " ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);"
 " }"
 " var eventLocked = false;"
 " function handleEvent(clientX, clientY) {"
 " if(eventLocked) {"
 " return;"
 " }"
 " function colourCorrect(v) {"
 " return Math.round(1023-(v*v)/64);"
 " }"
 " var data = ctx.getImageData(clientX, clientY, 1, 1).data;"
 " var params = ["
 " 'r=' + colourCorrect(data[0]),"
 " 'g=' + colourCorrect(data[1]),"
 " 'b=' + colourCorrect(data[2])"
 " ].join('&');"
 " var req = new XMLHttpRequest();"
 " req.open('POST', '?' + params, true);"
 " req.send();"
 " eventLocked = true;"
 " req.onreadystatechange = function() {"
 " if(req.readyState == 4) {"
 " eventLocked = false;"
 " }"
 " }"
 " }"
 " canvas.addEventListener('click', function(event) {"
 " handleEvent(event.clientX, event.clientY, true);"
 " }, false);"
 " canvas.addEventListener('touchmove', function(event){"
 " handleEvent(event.touches[0].clientX, event.touches[0].clientY);"
 "}, false);"
 " function resizeCanvas() {"
 " canvas.width = window.innerWidth;"
 " canvas.height = window.innerHeight;"
 " drawCanvas();"
 " }"
 " window.addEventListener('resize', resizeCanvas, false);"
 " resizeCanvas();"
 " drawCanvas();"
 " document.ontouchmove = function(e) {e.preventDefault()};"
 " })();"
 "</script></html>";
 //////////////////////////////////////////////////////////////////////////////////////////////////

void handleRGB() {
 // Serial.println("handle RGB..");

webServer.send(200, "text/html", webpageRGB);

String red = webServer.arg(0); // read RGB arguments
 String green = webServer.arg(1);
 String blue = webServer.arg(2);

allRGB(255 - red.toInt() / 4, 255 - green.toInt() / 4, 255 - blue.toInt() / 4);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////////////

void setup() {

// Serial.begin(9600);

strip.begin();
 strip.show(); // Initialize all pixels to 'off'
 strip.setBrightness(brightness); // 0-255 range

pinMode(3, INPUT);
 animation = digitalRead(3);

if (!animation) { // start webserver
 allRGB(0,0,0);
 delay(1000);
 // Serial.println("Starting webserver...");

WiFi.mode(WIFI_AP);
 WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
 WiFi.softAP(ssid);

// if DNSServer is started with "*" for domain name, it will reply with provided IP to all DNS request
 dnsServer.start(DNS_PORT, "esp", apIP);

webServer.on("/", handleRGB);

webServer.begin();

introRed(); // knightrider
 strip.setPixelColor(0, 255, 0, 0);
 strip.setPixelColor(3, 255, 0, 0);
 strip.setPixelColor(4, 255, 0, 0);
 strip.setPixelColor(7, 255, 0, 0);
 strip.show();
 }
 else {
 WiFi.disconnect();
 WiFi.softAPdisconnect(true);
 WiFi.mode(WIFI_STA);
 // Serial.println("Starting animation...");
 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////

void loop() {

if (animation) {
 rainbow(20);
 rainbowCycle(20);
 theaterChaseRainbow(50);
 }
 else {
 dnsServer.processNextRequest();
 webServer.handleClient();
 }
 }
 //////////////////////////////////////////////////////////////////////////////////////////////////
 /////////////////////// NEOPIXEL FUNCTIONS //////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////////////

// Fill the dots one after the other with a color
 void colorWipe(uint32_t c, uint8_t wait) {
 for (uint16_t i = 0; i < strip.numPixels(); i++) {
 strip.setPixelColor(i, c);
 strip.show();
 delay(wait);
 }
 }

void rainbow(uint8_t wait) {
 uint16_t i, j;

for (j = 0; j < 256; j++) {
 for (i = 0; i < strip.numPixels(); i++) {
 strip.setPixelColor(i, Wheel((i + j) & 255));
 }
 strip.show();
 delay(wait);
 }
 }

// Slightly different, this makes the rainbow equally distributed throughout
 void rainbowCycle(uint8_t wait) {
 uint16_t i, j;

for (j = 0; j < 256 * 5; j++) { // 5 cycles of all colors on wheel
 for (i = 0; i < strip.numPixels(); i++) {
 strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
 }
 strip.show();
 delay(wait);
 }
 }

//Theatre-style crawling lights.
 void theaterChase(uint32_t c, uint8_t wait) {
 for (int j = 0; j < 10; j++) { //do 10 cycles of chasing
 for (int q = 0; q < 3; q++) {
 for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
 strip.setPixelColor(i + q, c); //turn every third pixel on
 }
 strip.show();

delay(wait);

for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
 strip.setPixelColor(i + q, 0); //turn every third pixel off
 }
 }
 }
 }

//Theatre-style crawling lights with rainbow effect
 void theaterChaseRainbow(uint8_t wait) {
 for (int j = 0; j < 256; j++) { // cycle all 256 colors in the wheel
 for (int q = 0; q < 3; q++) {
 for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
 strip.setPixelColor(i + q, Wheel( (i + j) % 255)); //turn every third pixel on
 }
 strip.show();

delay(wait);

for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
 strip.setPixelColor(i + q, 0); //turn every third pixel off
 }
 }
 }
 }

// Input a value 0 to 255 to get a color value.
 // The colours are a transition r - g - b - back to r.
 uint32_t Wheel(byte WheelPos) {
 WheelPos = 255 - WheelPos;
 if (WheelPos < 85) {
 return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
 }
 if (WheelPos < 170) {
 WheelPos -= 85;
 return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
 }
 WheelPos -= 170;
 return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
 }

//////////////////////////////// TOM NEOPIXEL //////////////////////////////////////////////////

void introRed() {
 for (int u = 0; u < 3; u++) {
 for (int i = 0; i < strip.numPixels(); i++) {
 oneRed(i);
 delay(100);
 }
 for (int i = strip.numPixels() - 2; i > 0 ; i--) {
 oneRed(i);
 delay(100);
 }
 }
 oneRed(strip.numPixels()); // switch all off (switch on only 8, but beyond range)
 }

void oneRed(int u) {
 for (int i = 0; i < strip.numPixels(); i++) {
 strip.setPixelColor(i, 0, 0, 0);
 }
 strip.setPixelColor(u, 255, 0, 0);
 strip.show();
 }

void allRGB(int r, int g, int b) {
 for (int i = 0; i < strip.numPixels(); i++) {
 strip.setPixelColor(i, r, g, b);
 }
 strip.show();
 }

 

2 thoughts on “WiFi lantern with RGB strip (ESP8266)

Leave a Reply

Your email address will not be published. Required fields are marked *