Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
|
My deck / Christmas lights which I put up for my 50th birthday looked a bit sad, so I thought an upgrade was in order. In particular, I wanted to be able to change and update the software from my laptop in the house via WiFi. My daughter also requested to be able to "play" with the lights from her iPad so the job was on.
So the project broke down into two parts. A quick web interface and then into converting the existing code to be compatible with the Adafruit neopixel library which runs on the ESP8266 without modification. The original light controller was a modified Pololu sample running on an arduino mega. This didn't seem to want to run on the ESP so a conversion to a working library seemed the path of least resistance. So after a few evenings of effort. I had the original code running on the new library. Lots of subtle nuances in this conversion from one library to another and I'm sure I even tripped over a compiler issue alone the way.
I ended up writing a "pixel scope" page so I could see the state / colour of all the pixels in the memory buffer. This had many benefits as I didn't have 600 neopixels by the television where I code at night and even I did I prolly set fire to the sofa testing it all out. I used a 64 pixel pcb for ground truthing the code before plugging it all into the existing control box. I also did all the development on a nodemcu with oled display. This made life easy as only 3 wires to the neopixel board where required and the display allowed addition feedback as to what was going on.
So why did I use the D1 R2 board for the final deployment ? Well the D1 R2 board has a nice inbuilt switch mode power supply that is very 12V compatible. When I tried the NodeMCU at deployment I needed to power from 5V, the only power available in the control box was 12V from the main power pack or 5V from the first of three 12 to 5 Volt converters that power the 600 leds. Needless to say 600 neopixels all banging away drawing heaps of amps does not bode well for a quite cpu grade supply. Worked OK at 25% brightness but not at full tilt. So again path of least resistance, plug in the Wemos board where the Mega had been and problem solved.
The OLED display uses I2C to talk. As usual I have included the I2C scan page to diagnose issues with bus. You can see the two types of display I have been buying (SD1306 and SH1106), they have interchangeable ish declarations.
If your wondering about the BBQ pixels? This is so a selectable number of pixels that are directly over my BBQ can be made go 100% white while the pattern runs around them. No longer do I have to turn food over in the dark or spill my red wine cos I can't see the glass.
At the bottom of the main screen is a link so you can upload new code to the controller, this is prolly the simplest of the three ways to do this. There is a web page that explains all this, it only takes 5 lines of code to implement ! I have used this several times and I don't have to move off the sofa to change the lightning pattern program when required.
The program will join an existing WiFi network or it will form it's own, perfect for both stand alone and household applications. The current IP address is shown on the display mind you doesn't take much to work out the stand alone address as you get an addition WiFi AP showing on your device. MAC address etc are in the node config page so you can fix the IP address in the DHCP of you router to make it easier to find (for kids etc).
My daughter is happy that she can now turn on the lights and "play" with them via iPad so at the end of the day there are briefly multiple happy people in our household.
And finally big thanks to the Adafruit and Pololu people without which this project would have been several months not days in the making. Please visit and support them.
/* 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.
Comments