DougalPlummer
Published © GPL3+

IoT or Standalone ESP8266 NeoPixel Controller

An upgrade for my deck / Christmas lights controller.

BeginnerWork in progress1.5 hours2,971
IoT or Standalone ESP8266 NeoPixel Controller

Things used in this project

Hardware components

ESP8266 Wemos D1 R2
×1
WS2812 Neopixel strip
×1
NodeMCU with OLED Display
×1
ESP8266 ESP-12E
Espressif ESP8266 ESP-12E
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Deck Lights

Only one neopixel shown but I'm sure you will get the drift

Code

Deck Lights Mk 2

C/C++
Not pretty but it works -bits of code scavenged from many places.
/* NeoPixel Stick - Examples
 * Adafruit NeoPixel Stick consists of 8x 5050 RGB WS2812B LEDs
 * http://adafruit.com/products/1426
 *
 * Connections:
 * D3 --- DIN
 *     -- 5VDC  power supplied externally
 * G ---- GND (either)
 *
 * Adafruit recommendations:
 * - add a 1000uF capacitor across the power lines 5VDC and GND.
 * - add a 300-500 Ohm resistor on first pixel's data input DIN.
 * - avoid connecting on a live circuit... if you must, connect GND first.
 *
 * Dependencies:
 * https://github.com/adafruit/Adafruit_NeoPixel
 */
#include <ESP8266WiFi.h>
#include <WiFiUDP.h> 
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266httpUpdate.h>
//#include <DNSServer.h>
#include <TimeLib.h>
#include <EEPROM.h>
#include <stdio.h>
#include <DNSServer.h>
#include <Wire.h>
#include <Adafruit_NeoPixel.h>
#include "SSD1306.h"
#include "SH1106.h"
#include "SH1106Wire.h"
#include "ds3231.h"

//SSD1306 display(0x3c, 5, 4);   // GPIO 5 = D1, GPIO 4 = D2  ---  Built in 0.96" display
SH1106Wire display(0x3c, 4, 5);   // arse about ??? GPIO 5 = D1, GPIO 4 = D2  --- External 1.3" Display


#define MAX_MEM_PATTERN 24

typedef struct __attribute__((__packed__)) {            // volitile stuff
  char      Description[20] ; // Pattern Name 
  bool      bOnOff ;          // on off status
  long      iloopCount  ;      // number of loops in this pattern 
  long      iSubloopCount  ;   // sub loops of pattern
  long      paramA ;
  long      paramB ;
  long      paramC ;
  long      lDelay   ;        // delay in ms if used  
} sub_pattern_t ;               // bytes

typedef struct __attribute__((__packed__)) {            // volitile stuff
bool           bRun     ;  // run pattern sequencer
bool           bOnOff   ;  // light on off
bool           bWhite ;    // display white    
bool           bBBQ ;      // display last white    
int            iBright ;   // brightness
int            iPattern ;
int            iMinPattern ;
int            iMaxPattern ;
int            iloopCount ;
int            iSubloopCount ;
int            iBBQMin ;
int            iBBQMax ;
sub_pattern_t  pattern[MAX_MEM_PATTERN] ;
} patterns_t ;       // bytes

patterns_t lp ;      // light patterns

#define BUFF_MAX 32

const byte SETPMODE_PIN = D0 ; 
const byte FLASH_BTN = D3 ;    // GPIO 0 = FLASH BUTTON 
const byte SCOPE_PIN = D5 ;
const byte FACTORY_RESET = D6 ;
const byte LED = BUILTIN_LED ;  // = D4 ;

char buff[BUFF_MAX]; 
/* Set these to your desired credentials. */
const char *ssid = "WS2812_LEDS";
//const char *password = "password";
const char *host = "WS2812_LEDS";

char nssid[16] ;
char npassword[16] ;

IPAddress MyIP(192,168,2,110) ;
IPAddress RCIP(192,168,2,255) ;

char timeServer[24] = {"au.pool.ntp.org\0"};
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

byte rtc_sec ;
byte rtc_min ;
byte rtc_hour ;
byte rtc_fert_hour ;
float rtc_temp ;
unsigned int localPort = 2390;      // local port to listen for NTP UDP packets
bool bConfig = false ;
bool bDoTimeUpdate = false ;
bool hasRTC = false ;
int lTimeZone;            
int iDoSave ;
uint8_t rtc_status ;
struct ts tc;            
long lScanCtr = 0 ;
long lScanLast = 0 ;
int  iSamplePixel = 10 ;

#define MAX_PATTERN 15

WiFiUDP ntpudp;
ESP8266WebServer server(80);
ESP8266WebServer OTAWebServer(81);
ESP8266HTTPUpdateServer OTAWebUpdater;

// NeoPixel stick DIN pin
#define DIN_PIN D3

// How many NeoPixels on the stick?
#define NUM_PIXELS 600 // 64

// Third parameter:
//   NEO_RGB     Pixels are wired for RGB bitstream
//   NEO_GRB     Pixels are wired for GRB bitstream (NeoPixel Stick)
//   NEO_KHZ400  400 KHz bitstream for FLORA pixels
//   NEO_KHZ800  800 KHz bitstream for High Density LED strip (NeoPixel Stick)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_PIXELS, DIN_PIN, NEO_GRB + NEO_KHZ800);

int pause = 1000;

#define NUM_STATES  7  // number of patterns to cycle through

// system timer, incremented by one every time through the main loop
//unsigned int loopCount = 0;
//unsigned int ploopCount = 2000 ;

unsigned int seed = 0;  // used to initialize random number generator

void BackIntheBoxMemory(){
int i  ;  
  sprintf(nssid,"******\0") ;   // your default credentials go here
  sprintf(npassword,"*****\0") ;   // your password
  lTimeZone = 10 ;
  
  lp.bRun = false ;
  lp.iPattern = 1 ;
  lp.iloopCount = 0 ;
  lp.iSubloopCount = 0 ;
  lp.iMaxPattern = 8  ;
  lp.iMinPattern = 1  ;
  lp.bOnOff = false  ;  // light on off
  lp.bWhite = false  ;  // display white    
  lp.bBBQ = false ;     // bbq lights last 120  
  lp.iBright = 255 ;   // brightness
  lp.iBBQMin = 539 ; // 480 ;  // start of white zone  56
  lp.iBBQMax = 599 ; // 599 ;  // end of hite zone  64
  
  for ( i = 0 ; i < MAX_MEM_PATTERN ; i++ ) {
    lp.pattern[i].bOnOff = true ;          // on off status
    lp.pattern[i].iloopCount = 3000 ;      // number of loops in this pattern 
    lp.pattern[i].iSubloopCount = 0  ;   // sub loops of pattern
    lp.pattern[i].paramA = 0 ;
    lp.pattern[i].paramB = 0 ;
    lp.pattern[i].paramC = 0 ;
    lp.pattern[i].lDelay = 50 ;        // delay in ms if used  
    switch(i){
      case 0:
        String("Warm White Shimmer\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].lDelay = 5 ; 
      break;
      case 1:
        String("Bright Twinkle Gr\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].paramA = 2 ;
        lp.pattern[i].paramB = 0 ;
        lp.pattern[i].paramC = 30 ;
      break;
      case 2:
        String("Bright Twinkle Rd\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].paramA = 1 ;
        lp.pattern[i].paramB = 0 ;
        lp.pattern[i].paramC = 30 ;
      break;
      case 3:
        String("Bright Twinkle Wh\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].paramA = 0 ;
        lp.pattern[i].paramB = 0 ;
        lp.pattern[i].paramC = 30 ;
      break;
      case 4:
        String("Bright Twinkle GRW\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].paramA = 0 ;
        lp.pattern[i].paramB = 3 ;
        lp.pattern[i].paramC = 30 ;
      break;
      case 5:
        String("Collision\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].lDelay = 15 ; 
        lp.pattern[i].bOnOff = false ;
      break;
      case 6:
        String("Traditionaln Colors\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].lDelay = 2 ; 
      break;
      case 7:
        String("RGW Gradient\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
      break;
      case 8:
        String("Color Explosion\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
      break;
      case 9:
        String("Rainbow\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
      break;
      case 10:
        String("Random Colors\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].bOnOff = false ;
      break;
      case 13:
        lp.pattern[i].paramA = 255 ;
        lp.pattern[i].paramB = 0 ;
        lp.pattern[i].paramC = 0 ;
        String("Theater chase Red\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
      break;
      case 12:
        lp.pattern[i].paramA = 0 ;
        lp.pattern[i].paramB = 255 ;
        lp.pattern[i].paramC = 0 ;
        String("Theater chase Green\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
      break;
      case 11:
        lp.pattern[i].paramA = 0 ;
        lp.pattern[i].paramB = 0 ;
        lp.pattern[i].paramC = 255 ;
        String("Theater chase Blue\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
      break;
      case 14:
        String("Rainbow Chase\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
      break;
      case 15:
        String("Rainbow Mk2\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
      break;
      case 23:
        String("Single Pixel Chase\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].paramA = 255 ;
        lp.pattern[i].paramB = 255 ;
        lp.pattern[i].paramC = 255 ;
      break;
      default:
        String("\0").toCharArray(lp.pattern[i].Description,sizeof(lp.pattern[i].Description))  ; // Pattern Name
        lp.pattern[i].bOnOff = false ;
      break;
    }
  }
}

unsigned long sendNTPpacket(char* address)
{
byte packetBuffer[ NTP_PACKET_SIZE];     //buffer to hold incoming and outgoing packets  
//  Serial.println("sending NTP packet...");
                    
  memset(packetBuffer, 0, NTP_PACKET_SIZE);    // set all bytes in the buffer to 0
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  ntpudp.beginPacket(address, 123); //NTP requests are to port 123
  ntpudp.write(packetBuffer, NTP_PACKET_SIZE);
  ntpudp.endPacket();
  Serial.println(F("Get Time"));  
}

unsigned long processNTPpacket(void){
    ntpudp.read(packetBuffer, NTP_PACKET_SIZE);                                         // the timestamp starts at byte 40 of the received packet and is four bytes, or two words, long. First, esxtract the two words:
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);                  // combine the four bytes (two words) into a long integer
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    unsigned long secsSince1900 = highWord << 16 | lowWord;                             // this is NTP time (seconds since Jan 1 1900):
    const unsigned long seventyYears = 2208988800UL;                                    // now convert NTP time into everyday time:     Unix time starts on Jan 1 1970. In seconds, that's 2208988800:   
    unsigned long epoch = secsSince1900 - seventyYears + (lTimeZone * SECS_PER_HOUR);   // subtract seventy years:
    setTime((time_t)epoch);                                                             // update the clock
    Serial.print(F("Unix time = "));
    Serial.println(epoch);                                                              // print Unix time:
}


void handleRoot() {
  boolean currentLineIsBlank = true;
  tmElements_t tm;
  long  i = 0 ;
  int ii  ;
  int iProgNum = 0;
  int j ;
  int k , kk , iTmp ;
  boolean bExtraValve = false ;
  uint8_t iPage = 1 ;
  boolean bDefault = true ;
//  int td[6];
  long lTmp ; 
  String MyCheck , MyColor , MyNum  ;
  byte mac[6];
  String message ;
  uint32_t pcol ;
  uint32_t xcol ;
  uint8_t r, g, b ;
  
  Serial.println(F("Process WEB request"));  
  
  for (uint8_t j=0; j<server.args(); j++){
    i = String(server.argName(j)).indexOf("command");
    if (i != -1){  // 
      switch (String(server.arg(j)).toInt()){
        case 1:  // load values
          LoadParamsFromEEPROM(true);
        break;
        case 2: // Save values
          LoadParamsFromEEPROM(false);
        break;
        case 3: // 
        break;
        case 4: // 
        break;
        case 5: // 
        break;
        case 8: //  Cold Reboot
          ESP.reset() ;
        break;
        case 9: //  Warm Reboot
          ESP.restart() ;
        break;
        case 667: // wipe the memory to factory default
          BackIntheBoxMemory();
        break;
        case 665:
          sendNTPpacket(timeServer); // send an NTP packet to a time server  once and hour  
        break;
        case 668:
        break;
      }  
    }
    
    i = String(server.argName(j)).indexOf("nssid");    // ssid setup
    if (i != -1){  
      String(server.arg(j)).toCharArray(nssid,sizeof(nssid));
    }        
    i = String(server.argName(j)).indexOf("npass");   // password setup
    if (i != -1){  // have a request to set the time zone
      String(server.arg(j)).toCharArray(npassword,sizeof(npassword));
    }        

    i = String(server.argName(j)).indexOf("naddr");   // ip address setup
    if (i != -1){  // have a request to set the time zone
      MyIP[0] = String(server.arg(j)).substring(0,3).toInt() ;
      MyIP[1] =String(server.arg(j)).substring(4,7).toInt() ;
      MyIP[2] = String(server.arg(j)).substring(8,11).toInt() ;
      MyIP[3] =String(server.arg(j)).substring(12,15).toInt() ;
    }        

    i = String(server.argName(j)).indexOf("stime");
    if (i != -1){  // 
      tm.Year = (String(server.arg(j)).substring(0,4).toInt()-1970) ;
      tm.Month =(String(server.arg(j)).substring(5,7).toInt()) ;
      tm.Day = (String(server.arg(j)).substring(8,10).toInt()) ;
      tm.Hour =(String(server.arg(j)).substring(11,13).toInt()) ;
      tm.Minute = (String(server.arg(j)).substring(14,16).toInt()) ;
      tm.Second = 0 ;
      setTime(makeTime(tm));    
    }        

    i = String(server.argName(j)).indexOf("tzone");
    if (i != -1){  // 
      lTimeZone = String(server.arg(j)).toInt() ;
      constrain(lTimeZone,-12,12);
      bDoTimeUpdate = true ; // trigger and update to fix the time
    }
    i = String(server.argName(j)).indexOf("bmi");
    if (i != -1){  // 
      lp.iBBQMin = String(server.arg(j)).toInt() ;
      constrain(lp.iBBQMin,0, strip.numPixels());
    }
    i = String(server.argName(j)).indexOf("bmx");
    if (i != -1){  // 
      lp.iBBQMax = String(server.arg(j)).toInt() ;
      constrain(lp.iBBQMax,0, strip.numPixels());
    }

    i = String(server.argName(j)).indexOf("max");
    if (i != -1){  // 
      lp.iMaxPattern = String(server.arg(j)).toInt() ;
      constrain(lp.iMaxPattern,0,MAX_PATTERN);
    }
    i = String(server.argName(j)).indexOf("min");
    if (i != -1){  // 
      lp.iMinPattern = String(server.arg(j)).toInt() ;
      constrain(lp.iMinPattern,0,MAX_PATTERN);
    }
    i = String(server.argName(j)).indexOf("bri");
    if (i != -1){  // 
      lp.iBright = String(server.arg(j)).toInt() ;
      constrain(lp.iBright,0,255);
    }
    i = String(server.argName(j)).indexOf("sam");
    if (i != -1){  // 
      iSamplePixel = String(server.arg(j)).toInt() ;
      constrain(iSamplePixel,0,strip.numPixels());
    }
    
    i = String(server.argName(j)).indexOf("cur");
    if (i != -1){  // 
      lp.iPattern = String(server.arg(j)).toInt() ;
      constrain(lp.iPattern,0,MAX_PATTERN);
    }

    i = String(server.argName(k)).indexOf("run");
    if (i != -1){  // 
      lp.bRun = String(server.arg(j)).toInt() ;           
    }

    i = String(server.argName(k)).indexOf("onf");
    if (i != -1){  // 
      lp.bOnOff = String(server.arg(j)).toInt() ;           
    }

    i = String(server.argName(k)).indexOf("bwh");
    if (i != -1){  // 
      lp.bWhite = String(server.arg(j)).toInt() ;           
    }

    i = String(server.argName(k)).indexOf("bbq");
    if (i != -1){  // 
      lp.bBBQ = String(server.arg(j)).toInt() ;           
    }

    for ( ii = 0 ; ii < MAX_MEM_PATTERN ; ii++){                 // 
      MyNum = String(ii) ;
      if ( MyNum.length() == 1 ) {
        MyNum = "0" + MyNum ;
      }
      i = String(server.argName(j)).indexOf("num" + MyNum );  // bit of a parser cheat
      if (i != -1){  // 
        lp.pattern[ii].bOnOff = false ; 
      }        
      i = String(server.argName(j)).indexOf("en" + MyNum);  // this is the on signal
      if (i != -1){  // 
        if ( String(server.arg(j)).length() == 2 ){ // only put back what we find
          lp.pattern[ii].bOnOff = true ; 
        }  
      }        
      i = String(server.argName(j)).indexOf("ds" + MyNum);  // description
      if (i != -1){  // 
        String(server.arg(j)).toCharArray( lp.pattern[ii].Description , sizeof(lp.pattern[ii].Description)) ;        
      }        
      i = String(server.argName(j)).indexOf("lc" + MyNum);  // loop count
      if (i != -1){  // 
        lp.pattern[ii].iloopCount = String(server.arg(j)).toInt() ; 
      }        
      i = String(server.argName(j)).indexOf("sl" + MyNum);  // sub loop count
      if (i != -1){  // 
        lp.pattern[ii].iSubloopCount = String(server.arg(j)).toInt() ; 
      }        
      i = String(server.argName(j)).indexOf("dl" + MyNum);  // delay  ms
      if (i != -1){  // 
        lp.pattern[ii].lDelay = String(server.arg(j)).toInt() ; 
      }        
      i = String(server.argName(j)).indexOf("pa" + MyNum);  // parameter a
      if (i != -1){  // 
        lp.pattern[ii].paramA = String(server.arg(j)).toInt() ; 
      }        
      i = String(server.argName(j)).indexOf("pb" + MyNum);  // parameter b
      if (i != -1){  // 
        lp.pattern[ii].paramB = String(server.arg(j)).toInt() ; 
      }        
      i = String(server.argName(j)).indexOf("pc" + MyNum);  // parameter c
      if (i != -1){  // 
        lp.pattern[ii].paramC = String(server.arg(j)).toInt() ; 
      }        

    }
                  
  }          

  SendHTTPHeader();
  
  server.sendContent(F("<a href='/?command=2'>Save Parameters to EEPROM</a><br>")) ;         

  snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", year(), month(), day() , hour(), minute(), second());
  server.sendContent("<b>"+ String(buff)) ; 
  if ( year() < 2000 ) {
    server.sendContent(F("--- CLOCK NOT SET ---")) ;
  }
  server.sendContent(F("</b><br>")) ;  

  if (String(server.uri()).indexOf("stime")>0) {  // setup of the time
    bDefault = false ;
    snprintf(buff, BUFF_MAX, "%04d/%02d/%02d %02d:%02d", year(), month(), day() , hour(), minute());
    server.sendContent("<br><br><form method=get action=" + server.uri() + "><br>Set Current Time: <input type='text' name='stime' value='"+ String(buff) + "' size=12>");
    server.sendContent(F("<input type='submit' value='SET'><br><br></form>"));
  }

  if (String(server.uri()).indexOf("setup")>0) {  // setup of the node
    server.sendContent("<form method=get action=" + server.uri() + "><table border=1 title='WiFi Node Settings'>");
    server.sendContent(F("<tr><th>Parameter</th><th>Value</th><th><input type='submit' value='SET'></th></tr>"));

    server.sendContent(F("<tr><td>Time Zone</td><td align=center>")) ; 
    server.sendContent("<input type='text' name='tzone' value='" + String(lTimeZone) + "' size=12></td><td></td></tr>");
  
    server.sendContent(F("<tr><td>Local UDP Port NTP</td><td align=center>")) ; 
    server.sendContent("<input type='text' name='lpntp' value='" + String(localPort) + "' size=12></td><td></td></tr>");
  
    server.sendContent(F("<tr><td>SSID</td><td align=center>")) ; 
//    snprintf(buff, BUFF_MAX, "%s" , nssid );
    server.sendContent("<input type='text' name='nssid' value='" + String(nssid) + "' maxlength=15 size=12></td><td></td></tr>");

//    Serial.println(String(nssid));
//    Serial.println(String(npassword));
  
    server.sendContent(F("<tr><td>Password</td><td align=center>")) ; 
    server.sendContent("<input type='text' name='npass' value='" + String(npassword) + "' maxlength=15 size=12></td><td></td></tr>");
  
    server.sendContent("<tr><td>ESP ID</td><td align=center>0x" + String(ESP.getChipId(), HEX) + "</td><td align=center>"+String(ESP.getChipId())+"</td></tr>" ) ; 
    WiFi.macAddress(mac);      
    snprintf(buff, BUFF_MAX, "%02X:%02X:%02X:%02X:%02X:%02X", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);
    server.sendContent("<tr><td>MAC Address</td><td align=center>" + String(buff) + "</td><td align=center>.</td></tr>" ) ; 
    server.sendContent("<tr><td>WiFi RSSI</td><td align=center>" + String(WiFi.RSSI()) + "</td><td>(dBm)</td></tr>" ) ; 
    snprintf(buff, BUFF_MAX, "%03u.%03u.%03u.%03u", MyIP[0],MyIP[1],MyIP[2],MyIP[3]);
    server.sendContent("<tr><td>Node IP Address</td><td align=center>" + String(buff) + "</td><td>.</td></tr>" ) ; 
    server.sendContent("<tr><td>Last Scan Speed</td><td align=center>" + String(lScanCtr) + "</td><td>(per second)</td></tr>" ) ;    
    if( hasRTC ){
      rtc_status = DS3231_get_sreg();
      if (( rtc_status & 0x80 ) != 0 ){
        server.sendContent(F("<tr><td>RTC Battery</td><td align=center bgcolor='red'>DEPLETED</td><td></td></tr>")) ;            
      }else{
        server.sendContent(F("<tr><td>RTC Battery</td><td align=center bgcolor='green'>-- OK --</td><td></td></tr>")) ;                    
      }
      server.sendContent("<tr><td>RTC Temperature</td><td align=center>"+String(rtc_temp,1)+"</td><td>(C)</td></tr>") ;                    
    }
    server.sendContent("<tr><td>ESP Core Version</td><td align=center>" + String(ESP.getCoreVersion()) + "</td><td>.</td></tr>" ) ;    
    server.sendContent("<tr><td>ESP Full Version</td><td align=center>" + String(ESP.getFullVersion()) + "</td><td>.</td></tr>" ) ;    
    server.sendContent("<tr><td>SDK Version</td><td align=center>" + String(ESP.getSdkVersion()) + "</td><td>.</td></tr>" ) ;    
    server.sendContent("<tr><td>CPU Volts</td><td align=center>" + String(ESP.getVcc()) + "</td><td>(V)</td></tr>" ) ;    
    server.sendContent("<tr><td>CPU Frequecy</td><td align=center>" + String(ESP.getCpuFreqMHz()) + "</td><td>(MHz)</td></tr>" ) ;    
    server.sendContent("<tr><td>Get Rest Reason</td><td align=center>" + String(ESP.getResetReason()) + "</td><td></td></tr>" ) ;    
    server.sendContent("<tr><td>Get Reset Into</td><td align=center>" + String(ESP.getResetInfo()) + "</td><td></td></tr>" ) ;    
    server.sendContent("<tr><td>Get Sketch Size</td><td align=center>" + String(ESP.getSketchSize()) + "</td><td>(Bytes)</td></tr>" ) ;    
    server.sendContent("<tr><td>Free Sketch Space</td><td align=center>" + String(ESP.getFreeSketchSpace()) + "</td><td>(Bytes)</td></tr>" ) ;    
    
    server.sendContent(F("</form></table>"));
    bDefault = false ;
  }
     
  if (bDefault) {     // default valve control and setup
    server.sendContent(F("<table border=1>")) ;
    server.sendContent(F("<tr><th>Parameter</th><th colspan=2>Value</th><th>.</th></tr>")) ;
    if ( lp.bRun == 0 ) {
      MyColor = F("bgcolor=red") ;
      MyCheck = F("Automation ON")  ;    
    }else{
      MyColor = F("bgcolor=green") ;
      MyCheck = F("Automation OFF")  ;    
    } 
    server.sendContent("<tr><form method=get action=" + server.uri() + "><input type='hidden' name='run' value='1'><td align=center "+String(MyColor)+">"+String(MyCheck)+"</td><td align=center><input type='submit' value='ON'></form></td><td align=center><form method=get action=" + server.uri() + "><input type='hidden' name='run' value='0'><input type='submit' value='OFF'></td></form><td></td></tr>") ;   
    if ( lp.bOnOff == 0 ) {
      MyColor = F("bgcolor=red") ;
      MyCheck = F("Light OFF")  ;    
    }else{
      MyColor = F("bgcolor=green") ;
      MyCheck = F("Light ON")  ;    
    } 
    server.sendContent("<tr><form method=get action=" + server.uri() + "><input type='hidden' name='onf' value='1'><td align=center "+String(MyColor)+">"+String(MyCheck)+"</td><td align=center><input type='submit' value='ON'></form></td><td align=center><form method=get action=" + server.uri() + "><input type='hidden' name='onf' value='0'><input type='submit' value='OFF'></td></form><td></td></tr>") ;   
    if ( lp.bWhite == 0 ) {
      MyColor = F("bgcolor=red") ;
      MyCheck = F("All White OFF")  ;    
    }else{
      MyColor = F("bgcolor=green") ;
      MyCheck = F("All White ON")  ;    
    } 
    server.sendContent("<tr><form method=get action=" + server.uri() + "><input type='hidden' name='bwh' value='1'><td align=center "+String(MyColor)+">"+String(MyCheck)+"</td><td align=center><input type='submit' value='ON'></form></td><td align=center><form method=get action=" + server.uri() + "><input type='hidden' name='bwh' value='0'><input type='submit' value='OFF'></td></form><td></td></tr>") ;   
    if ( lp.bBBQ == 0 ) {
      MyColor = F("bgcolor=red") ;
      MyCheck = F("BBQ Light OFF")  ;    
    }else{
      MyColor = F("bgcolor=green") ;
      MyCheck = F("BBQ Ligh ON")  ;    
    } 
    server.sendContent("<tr><form method=get action=" + server.uri() + "><input type='hidden' name='bbq' value='1'><td align=center "+String(MyColor)+">"+String(MyCheck)+"</td><td align=center><input type='submit' value='ON'></form></td><td align=center><form method=get action=" + server.uri() + "><input type='hidden' name='bbq' value='0'><input type='submit' value='OFF'></td></form><td></td></tr>") ;   
    server.sendContent("<tr><form method=get action=" + server.uri() + "><td>Current Pattern</td><td colspan=2><select name='cur'>") ;
    for ( i = 0 ; i < MAX_MEM_PATTERN ; i++ ) {
      server.sendContent(F("<option value='")); 
      server.sendContent(String(i)) ;
      if ( lp.iPattern == i ){
         server.sendContent(F("' SELECTED>"));
      }else{
         server.sendContent(F("'>"));                    
      }
      server.sendContent(String(i) + " - " + String(lp.pattern[i].Description)) ;
    }
    server.sendContent(F("</select></td><td><input type='submit' value='SET'></td></form></tr>"));
    server.sendContent("<tr><form method=get action=" + server.uri() + "><td>Min Pattern</td><td colspan=2><select name='min'>") ;
    for ( i = 0 ; i < MAX_MEM_PATTERN ; i++ ) {
      server.sendContent(F("<option value='")); 
      server.sendContent(String(i)) ;
      if ( lp.iMinPattern == i ){
         server.sendContent(F("' SELECTED>"));
      }else{
         server.sendContent(F("'>"));                    
      }
      server.sendContent(String(i) + " - " + String(lp.pattern[i].Description)) ;
    }
    server.sendContent(F("</select></td><td><input type='submit' value='SET'></td></form></tr>"));
    server.sendContent("<tr><form method=get action=" + server.uri() + "><td>Max Pattern</td><td colspan=2><select name='max'>") ;
    for ( i = 0 ; i < MAX_MEM_PATTERN ; i++ ) {
      server.sendContent(F("<option value='")); 
      server.sendContent(String(i)) ;
      if ( lp.iMaxPattern == i ){
         server.sendContent(F("' SELECTED>"));
      }else{
         server.sendContent(F("'>"));                    
      }
      server.sendContent(String(i) + " - " + String(lp.pattern[i].Description)) ;
    }
    server.sendContent(F("</select></td><td><input type='submit' value='SET'></td></form></tr>"));   
    server.sendContent("<tr><form method=get action=" + server.uri() + "><td>Brightness</td><td colspan=2><input type='text' name='bri'  maxlength=3 size=5  value='"+String(lp.iBright)+"'></td><td><input type='submit' value='SET'></td></form></tr>") ;
    server.sendContent("<tr><form method=get action=" + server.uri() + "><td>BBQ min pixel</td><td colspan=2><input type='text' name='bmi'  maxlength=3 size=5  value='"+String(lp.iBBQMin)+"'></td><td><input type='submit' value='SET'></td></form></tr>") ;
    server.sendContent("<tr><form method=get action=" + server.uri() + "><td>BBQ max pixel</td><td colspan=2><input type='text' name='bmx'  maxlength=3 size=5  value='"+String(lp.iBBQMax)+"'></td><td><input type='submit' value='SET'></td></form></tr>") ;
    pcol = strip.getPixelColor(iSamplePixel) ;
    xcol = pcol ^ 0xffffff ;
    r = ( pcol >> 16 ) & 0xff ;
    g = ( pcol >> 8 ) & 0xff ;
    b = ( pcol  ) & 0xff ;
    snprintf(buff, BUFF_MAX, " 0x%02X%02X%02X", r, g ,b );    
    server.sendContent("<tr><form method=get action=" + server.uri() + "><td>Sample pixel</td><td colspan=2><input type='text' name='sam'  maxlength=3 size=5  value='"+String(iSamplePixel)+"'>"+String(buff)+"</td><td><input type='submit' value='SET'></td></form></tr>") ;
    server.sendContent("<tr><td>Loop Counter</td><td align=center colspan=2>"+String(lp.iloopCount)+"</td><td>.</td></tr>") ;
    server.sendContent("<tr><td>Sub Loop Counter</td><td align=center colspan=2>"+String(lp.iSubloopCount)+"</td><td>.</td></tr>") ;
    server.sendContent("<tr><td>Number of pixels</td><td align=center colspan=2>"+String(strip.numPixels())+"</td><td>.</td></tr>") ;
    server.sendContent("<tr><td>Cycles per second</td><td align=center colspan=2>"+String(lScanLast)+"</td><td>.</td></tr>") ;
    
    server.sendContent(F("</table><br>")) ;
    server.sendContent(F("<table title='Patterns' border=1><tr><th>No</th><th>Enabled</th><th>Name</th><th>Cycles</th><th>Sub Cycles</th><th>Delay</th><th>Param A<br>Red</th><th>Param B<br>Green</th><th>Param C<br>Blue</th><th>.</th></tr>")) ;
    for ( i = 0 ; i < MAX_MEM_PATTERN ; i++ ) {
      MyNum = String(i) ;
      if ( MyNum.length() == 1 ) {
        MyNum = "0" + MyNum ;
      }
      if ( lp.iPattern == i ) {
        MyColor = F("bgcolor='yellow'") ;
      }else{
        MyColor = F("") ;
      }
      server.sendContent("<tr><form method=get action=" + server.uri() + "><input type='hidden' name='num" + MyNum + "' value='1'><td "+String(MyColor)+ " align=center><a href='/?cur="+String(i)+"'>"+String(i)+"</a></td>") ;
      if ( lp.pattern[i].bOnOff == 0 ) {
        MyColor = F("bgcolor=red") ;
        MyCheck = F("")  ;    
      }else{
        MyColor = F("bgcolor=green") ;
        MyCheck = F("CHECKED")  ;    
      }
      server.sendContent("<td align=center "+String(MyColor)+"><input type='checkbox' name='en" + MyNum + "' "+String(MyCheck)+"></td><td>");
      server.sendContent("<input type='text' name='ds" + MyNum + "' maxlength=20 size=20 value='" + String(lp.pattern[i].Description) +"'");
      server.sendContent(F("</td><td>")) ;
      server.sendContent("<input type='text' name='lc" + MyNum + "' maxlength=6 size=6 value='" + String(lp.pattern[i].iloopCount) +"'");
      server.sendContent(F("</td><td>")) ;
      server.sendContent("<input type='text' name='sl" + MyNum + "' maxlength=6 size=6 value='" + String(lp.pattern[i].iSubloopCount) +"'");
      server.sendContent(F("</td><td>")) ;
      server.sendContent("<input type='text' name='dl" + MyNum + "' maxlength=6 size=6 value='" + String(lp.pattern[i].lDelay) +"'");
      server.sendContent(F("</td><td>")) ;
      server.sendContent("<input type='text' name='pa" + MyNum + "' maxlength=6 size=6 value='" + String(lp.pattern[i].paramA) +"'");
      server.sendContent(F("</td><td>")) ;
      server.sendContent("<input type='text' name='pb" + MyNum + "' maxlength=6 size=6 value='" + String(lp.pattern[i].paramB) +"'");
      server.sendContent(F("</td><td>")) ;
      server.sendContent("<input type='text' name='pc" + MyNum + "' maxlength=6 size=6 value='" + String(lp.pattern[i].paramC) +"'");
      server.sendContent(F("</td><td><input type='submit' value='SET'></td></form></tr>")) ;
    }
    server.sendContent(F("</table>")) ;
  }
  server.sendContent(F("<br><br><a href='/?command=1'>Load Parameters from EEPROM</a><br><br><a href='/?command=667'>Reset Memory to Factory Default</a><br><a href='/?command=665'>Sync UTP Time</a><br><a href='/stime'>Manual Time Set</a><br><a href='/scan'>I2C Scan</a><br>")) ;         
  server.sendContent(F("<a href='/setup'>WiFi Node Setup</a><br><a href='/scope'>Pixel Scope</a><br>"));
  snprintf(buff, BUFF_MAX, "%03u.%03u.%03u.%03u", MyIP[0],MyIP[1],MyIP[2],MyIP[3]);
  server.sendContent("<a href='http://" + String(buff) + ":81/update'>OTA Firmware Update</a><br>");
  server.sendContent(F("</body></html>\r\n"));
  
}

void handleNotFound(){
  String message = F("Seriously - No way DUDE\n\n");
  message += F("URI: ");
  message += server.uri();
  message += F("\nMethod: ");
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += F("\nArguments: ");
  message += server.args();
  message += F("\n");
  for (uint8_t i=0; i<server.args(); i++){
    message += " NAME:"+server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
  }
  server.send(404, F("text/plain"), message);
//  Serial.print(message);
}
void SaveLongToEEPROM(int address,long val){
long tmp ;  
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}
int LoadIntFromEEPROM(int address,int minval,int maxval, int defaultval){
int dummy1 = 0 ; // belt and braces ... dont know which way the stack works
int tmp ;  
int dummy2 = 0 ; // yep write this one as well ... maybe
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}
void SaveIntToEEPROM(int address,int val){
int dummy1 = 0 ;
int tmp ;  
int dummy2 = 0 ;
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}

int LoadLongFromEEPROM(int address,long minval,long maxval, long defaultval){
long tmp ;
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}
void LoadParamsFromEEPROM(bool bLoad){
long lTmp ;  
int i ;
int j ;
int bofs ,ofs ;
int eeAddress ;
  if ( bLoad ) {
    lTimeZone = LoadLongFromEEPROM(2,-12,12,10);              // Time Zone
    localPort = LoadIntFromEEPROM(3,1,65535,2390);            // Local NTP UDP port

  //  LoadIPFromEEPROM(8,&RCIP);
    eeAddress = 8 * sizeof(float) ;
    EEPROM.get(eeAddress,RCIP) ; 
    
    eeAddress = 14 * sizeof(float) ;
    EEPROM.get(eeAddress,nssid) ; 
    eeAddress = 18 * sizeof(float) ;
    EEPROM.get(eeAddress,npassword) ; 
    eeAddress += sizeof(npassword) ;

    EEPROM.get(eeAddress,lp) ; 
    eeAddress += sizeof(lp) ;
    
  }else{
    SaveLongToEEPROM(2,lTimeZone );
    SaveIntToEEPROM(3,localPort );

    eeAddress = 7 * sizeof(float) ;
    EEPROM.put(eeAddress,MyIP);
    eeAddress = 8 * sizeof(float) ;
    EEPROM.put(eeAddress,RCIP);
    eeAddress = 14 * sizeof(float) ;
    EEPROM.put(eeAddress,nssid);
    eeAddress = 18 * sizeof(float) ;
    EEPROM.put(eeAddress,npassword);
    eeAddress += sizeof(npassword) ;

    EEPROM.put(eeAddress,lp);
    eeAddress += sizeof(lp) ;

    EEPROM.commit();                                                       // save changes in one go ???
  }
}


// enumerate the possible patterns in the order they will cycle
enum Pattern {
  WarmWhiteShimmer = 0,
  RandomColorWalk = 1,
  TraditionalColors = 2,
  ColorExplosion = 3,
  Gradient = 4,
  BrightTwinkle = 5,
  Collision = 6,
  AllOff = 255
};
unsigned char pattern = AllOff;
unsigned int maxLoops;  // go to next state when loopCount >= maxLoops


//#####################################  SETUP  #####################################################
void setup() {
int i , j = 0 ; 
String host ;
  
  Serial.begin(115200);
  Serial.setDebugOutput(true);  
  Serial.println(String(ESP.getResetReason()));
  Serial.println(String(ESP.getResetInfo()));

  display.init();
  display.flipScreenVertically();

  /* show start screen */
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  display.setFont(ArialMT_Plain_16);
  display.drawString(64, 0, "ESP => WS2812");
  display.drawString(64, 16, "Deck Lights");
  display.setFont(ArialMT_Plain_10);
  display.drawString(64, 40, "Circa 2018");
  display.drawString(64, 50, "Dougal Plummer");
  display.display();
  
  strip.begin();
  strip.setPixelColor(0,127,0,0);
  strip.setPixelColor(1,0,127,0);
  strip.setPixelColor(2,0,0,127);
  strip.setPixelColor(strip.numPixels()-3,127,0,0);
  strip.setPixelColor(strip.numPixels()-2,0,127,0);
  strip.setPixelColor(strip.numPixels()-1,0,0,127);
  strip.show(); // Start with all pixels off
  
  EEPROM.begin(2048);
  LoadParamsFromEEPROM(true);
  
  if ((digitalRead(SETPMODE_PIN) == HIGH) && false ){
    bConfig = true ;
    IPAddress localIp(192, 168, 5 +(ESP.getChipId() & 0x7f ) , 1);
    IPAddress MaskIp(255, 255, 255 , 0);
    WiFi.softAPConfig(localIp,localIp,MaskIp);
    
//    WiFi.softAP(ssid, password); // configure mode
    WiFi.softAP(ssid); // configure mode no password
    MyIP = WiFi.softAPIP();
    Serial.print("Soft AP IP address: ");
    Serial.println(MyIP);
  }else{
    bConfig = false ;   // are we in factory configuratin mode
    Serial.println(String(nssid));
    Serial.println(String(npassword));
    if ( npassword[0] == 0 ){
      WiFi.begin((char*)nssid);                    // connect to unencrypted access point      
    }else{
      WiFi.begin((char*)nssid, (char*)npassword);  // connect to access point with encryption
    }
    while (( WiFi.status() != WL_CONNECTED ) && ( j < 30 )) {
     j = j + 1 ;
     delay(500);
     strip.setPixelColor(10+j,0,0,255);
     strip.show();
    } 
    if ( j >= 30 ) {
       bConfig = true ;
       WiFi.disconnect();
       IPAddress localIp(192, 168, 5 +(ESP.getChipId() & 0x7f ) , 1);
       IPAddress MaskIp(255, 255, 255 , 0);
       WiFi.softAPConfig(localIp,localIp,MaskIp);
       WiFi.softAP(ssid); // configure mode no password
       MyIP = WiFi.softAPIP();
       Serial.print("Soft AP IP address: ");
       Serial.println(MyIP);
    }else{
       Serial.println("");
       Serial.println("WiFi connected");  
       Serial.print("IP address: ");
       MyIP =  WiFi.localIP() ;
       Serial.println(MyIP) ;
    }

    Serial.println("Starting UDP");
    ntpudp.begin(localPort);                      // this is the recieve on NTP port
    Serial.print("NTP Local UDP port: ");
    Serial.println(ntpudp.localPort());
  }
  host = String("decklights") ;
  String(host).replace(" ","_");
  String(host).toCharArray(buff,sizeof(buff));
  if (MDNS.begin(buff)) {
    MDNS.addService("http", "tcp", 80);
    Serial.println("MDNS responder started");
    Serial.print("You can now connect to http://");
    Serial.print(host);
    Serial.println(".local");
  }

  server.on("/", handleRoot);
  server.on("/setup", handleRoot);
  server.on("/scan", i2cScan);
  server.on("/scope" , pixelscope );
  server.on("/stime", handleRoot);
  server.onNotFound(handleNotFound);  
  server.begin();
  
  delay(pause);
  pinMode(BUILTIN_LED,OUTPUT);
  
  strip.setBrightness(lp.iBright);

  tc.mon = 0 ;
  tc.wday = 0 ;
  DS3231_init(DS3231_INTCN); // look for a rtc
  DS3231_get(&tc);
  rtc_status = DS3231_get_sreg();
  if (((tc.mon < 1 )|| (tc.mon > 12 ))&& (tc.wday>8)){  // no rtc to load off
    Serial.println("NO RTC ?");
  }else{
    setTime((int)tc.hour,(int)tc.min,(int)tc.sec,(int)tc.mday,(int)tc.mon,(int)tc.year ) ; // set the internal RTC
    hasRTC = true ;
    Serial.println("Has RTC ?");
    rtc_temp = DS3231_get_treg(); 
    DS3231_get(&tc);
  }
  rtc_min = minute();
  rtc_sec = second(); 
//  BackIntheBoxMemory();
//  LoadParamsFromEEPROM(false);

  OTAWebUpdater.setup(&OTAWebServer);
  OTAWebServer.begin();  
}

//#######################################################################  LOOP  #####################################################
void loop() {
long lTime ;  
int j , k ;
unsigned char r, g, b ;
uint32_t pcol ;

  lTime = millis() ;
  lScanCtr++;
  
  if (rtc_sec != second() ){
    lScanLast = lScanCtr ;
    lScanCtr = 0 ;
    
    display.clear();
//      display.drawLine(minRow, 63, maxRow, 63);
    display.setTextAlignment(TEXT_ALIGN_LEFT);
    snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", year(), month(), day() , hour(), minute(), second());
    display.drawString(0 , 0, String(buff) );
    display.setTextAlignment(TEXT_ALIGN_RIGHT);
    display.drawString(128 , 0, String(WiFi.RSSI()));
    display.setTextAlignment(TEXT_ALIGN_LEFT);

    display.setTextAlignment(TEXT_ALIGN_CENTER);
    if ( bConfig ){
      snprintf(buff, BUFF_MAX, ">> IP %03u.%03u.%03u.%03u <<", MyIP[0],MyIP[1],MyIP[2],MyIP[3]);
    }else{
      snprintf(buff, BUFF_MAX, "IP %03u.%03u.%03u.%03u", MyIP[0],MyIP[1],MyIP[2],MyIP[3]);      
    }
    display.drawString(64 , 54 ,  String(buff) );

    display.setTextAlignment(TEXT_ALIGN_RIGHT);
    display.drawString(128 , 11, String(lp.iPattern) );

    display.setTextAlignment(TEXT_ALIGN_CENTER);
    display.drawString(64 ,11, String(lp.iloopCount) );

    display.setTextAlignment(TEXT_ALIGN_LEFT);
    display.drawString(0 ,11, String(lScanLast) );

...

This file has been truncated, please download it to see its full contents.

Credits

DougalPlummer

DougalPlummer

14 projects • 89 followers
I write code for a living, on everything from PLC's to web and accounting systems. I'm also very talented at farming prickles :)

Comments