Updated with latest changes from Tree v2
Added support for dynamic fields, updated web app, etc.
This commit is contained in:
parent
f1d2c4dd94
commit
d8149e2fd3
|
@ -0,0 +1,125 @@
|
|||
//holds the current upload
|
||||
File fsUploadFile;
|
||||
|
||||
//format bytes
|
||||
String formatBytes(size_t bytes){
|
||||
if (bytes < 1024){
|
||||
return String(bytes)+"B";
|
||||
} else if(bytes < (1024 * 1024)){
|
||||
return String(bytes/1024.0)+"KB";
|
||||
} else if(bytes < (1024 * 1024 * 1024)){
|
||||
return String(bytes/1024.0/1024.0)+"MB";
|
||||
} else {
|
||||
return String(bytes/1024.0/1024.0/1024.0)+"GB";
|
||||
}
|
||||
}
|
||||
|
||||
String getContentType(String filename){
|
||||
if(webServer.hasArg("download")) return "application/octet-stream";
|
||||
else if(filename.endsWith(".htm")) return "text/html";
|
||||
else if(filename.endsWith(".html")) return "text/html";
|
||||
else if(filename.endsWith(".css")) return "text/css";
|
||||
else if(filename.endsWith(".js")) return "application/javascript";
|
||||
else if(filename.endsWith(".png")) return "image/png";
|
||||
else if(filename.endsWith(".gif")) return "image/gif";
|
||||
else if(filename.endsWith(".jpg")) return "image/jpeg";
|
||||
else if(filename.endsWith(".ico")) return "image/x-icon";
|
||||
else if(filename.endsWith(".xml")) return "text/xml";
|
||||
else if(filename.endsWith(".pdf")) return "application/x-pdf";
|
||||
else if(filename.endsWith(".zip")) return "application/x-zip";
|
||||
else if(filename.endsWith(".gz")) return "application/x-gzip";
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
bool handleFileRead(String path){
|
||||
Serial.println("handleFileRead: " + path);
|
||||
if(path.endsWith("/")) path += "index.htm";
|
||||
String contentType = getContentType(path);
|
||||
String pathWithGz = path + ".gz";
|
||||
if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){
|
||||
if(SPIFFS.exists(pathWithGz))
|
||||
path += ".gz";
|
||||
File file = SPIFFS.open(path, "r");
|
||||
size_t sent = webServer.streamFile(file, contentType);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleFileUpload(){
|
||||
if(webServer.uri() != "/edit") return;
|
||||
HTTPUpload& upload = webServer.upload();
|
||||
if(upload.status == UPLOAD_FILE_START){
|
||||
String filename = upload.filename;
|
||||
if(!filename.startsWith("/")) filename = "/"+filename;
|
||||
Serial.print("handleFileUpload Name: "); Serial.println(filename);
|
||||
fsUploadFile = SPIFFS.open(filename, "w");
|
||||
filename = String();
|
||||
} else if(upload.status == UPLOAD_FILE_WRITE){
|
||||
//Serial.print("handleFileUpload Data: "); Serial.println(upload.currentSize);
|
||||
if(fsUploadFile)
|
||||
fsUploadFile.write(upload.buf, upload.currentSize);
|
||||
} else if(upload.status == UPLOAD_FILE_END){
|
||||
if(fsUploadFile)
|
||||
fsUploadFile.close();
|
||||
Serial.print("handleFileUpload Size: "); Serial.println(upload.totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
void handleFileDelete(){
|
||||
if(webServer.args() == 0) return webServer.send(500, "text/plain", "BAD ARGS");
|
||||
String path = webServer.arg(0);
|
||||
Serial.println("handleFileDelete: " + path);
|
||||
if(path == "/")
|
||||
return webServer.send(500, "text/plain", "BAD PATH");
|
||||
if(!SPIFFS.exists(path))
|
||||
return webServer.send(404, "text/plain", "FileNotFound");
|
||||
SPIFFS.remove(path);
|
||||
webServer.send(200, "text/plain", "");
|
||||
path = String();
|
||||
}
|
||||
|
||||
void handleFileCreate(){
|
||||
if(webServer.args() == 0)
|
||||
return webServer.send(500, "text/plain", "BAD ARGS");
|
||||
String path = webServer.arg(0);
|
||||
Serial.println("handleFileCreate: " + path);
|
||||
if(path == "/")
|
||||
return webServer.send(500, "text/plain", "BAD PATH");
|
||||
if(SPIFFS.exists(path))
|
||||
return webServer.send(500, "text/plain", "FILE EXISTS");
|
||||
File file = SPIFFS.open(path, "w");
|
||||
if(file)
|
||||
file.close();
|
||||
else
|
||||
return webServer.send(500, "text/plain", "CREATE FAILED");
|
||||
webServer.send(200, "text/plain", "");
|
||||
path = String();
|
||||
}
|
||||
|
||||
void handleFileList() {
|
||||
if(!webServer.hasArg("dir")) {webServer.send(500, "text/plain", "BAD ARGS"); return;}
|
||||
|
||||
String path = webServer.arg("dir");
|
||||
Serial.println("handleFileList: " + path);
|
||||
Dir dir = SPIFFS.openDir(path);
|
||||
path = String();
|
||||
|
||||
String output = "[";
|
||||
while(dir.next()){
|
||||
File entry = dir.openFile("r");
|
||||
if (output != "[") output += ',';
|
||||
bool isDir = false;
|
||||
output += "{\"type\":\"";
|
||||
output += (isDir)?"dir":"file";
|
||||
output += "\",\"name\":\"";
|
||||
output += String(entry.name()).substring(1);
|
||||
output += "\"}";
|
||||
entry.close();
|
||||
}
|
||||
|
||||
output += "]";
|
||||
webServer.send(200, "text/json", output);
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
ESP8266 + FastLED + IR Remote: https://github.com/jasoncoon/esp8266-fastled-webserver
|
||||
Copyright (C) 2016 Jason Coon
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
typedef String (*FieldSetter)(String);
|
||||
typedef String (*FieldGetter)();
|
||||
|
||||
const String NumberFieldType = "Number";
|
||||
const String BooleanFieldType = "Boolean";
|
||||
const String SelectFieldType = "Select";
|
||||
const String ColorFieldType = "Color";
|
||||
const String SectionFieldType = "Section";
|
||||
|
||||
typedef struct Field {
|
||||
String name;
|
||||
String label;
|
||||
String type;
|
||||
uint8_t min;
|
||||
uint8_t max;
|
||||
FieldGetter getValue;
|
||||
FieldGetter getOptions;
|
||||
FieldSetter setValue;
|
||||
};
|
||||
|
||||
typedef Field FieldList[];
|
||||
|
||||
Field getField(String name, FieldList fields, uint8_t count) {
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
Field field = fields[i];
|
||||
if (field.name == name) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return Field();
|
||||
}
|
||||
|
||||
String getFieldValue(String name, FieldList fields, uint8_t count) {
|
||||
Field field = getField(name, fields, count);
|
||||
if (field.getValue) {
|
||||
return field.getValue();
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
String setFieldValue(String name, String value, FieldList fields, uint8_t count) {
|
||||
Field field = getField(name, fields, count);
|
||||
if (field.setValue) {
|
||||
return field.setValue(value);
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
String getFieldsJson(FieldList fields, uint8_t count) {
|
||||
String json = "[";
|
||||
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
Field field = fields[i];
|
||||
|
||||
json += "{\"name\":\"" + field.name + "\",\"label\":\"" + field.label + "\",\"type\":\"" + field.type + "\"";
|
||||
|
||||
if(field.getValue) {
|
||||
if (field.type == ColorFieldType || field.type == "String") {
|
||||
json += ",\"value\":\"" + field.getValue() + "\"";
|
||||
}
|
||||
else {
|
||||
json += ",\"value\":" + field.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type == NumberFieldType) {
|
||||
json += ",\"min\":" + String(field.min);
|
||||
json += ",\"max\":" + String(field.max);
|
||||
}
|
||||
|
||||
if (field.getOptions) {
|
||||
json += ",\"options\":[";
|
||||
json += field.getOptions();
|
||||
json += "]";
|
||||
}
|
||||
|
||||
json += "}";
|
||||
|
||||
if (i < count - 1)
|
||||
json += ",";
|
||||
}
|
||||
|
||||
json += "]";
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/*
|
||||
String json = "[";
|
||||
|
||||
json += "{\"name\":\"power\",\"label\":\"Power\",\"type\":\"Boolean\",\"value\":" + String(power) + "},";
|
||||
json += "{\"name\":\"brightness\",\"label\":\"Brightness\",\"type\":\"Number\",\"value\":" + String(brightness) + "},";
|
||||
|
||||
json += "{\"name\":\"pattern\",\"label\":\"Pattern\",\"type\":\"Select\",\"value\":" + String(currentPatternIndex) + ",\"options\":[";
|
||||
for (uint8_t i = 0; i < patternCount; i++)
|
||||
{
|
||||
json += "\"" + patterns[i].name + "\"";
|
||||
if (i < patternCount - 1)
|
||||
json += ",";
|
||||
}
|
||||
json += "]},";
|
||||
|
||||
json += "{\"name\":\"autoplay\",\"label\":\"Autoplay\",\"type\":\"Boolean\",\"value\":" + String(autoplay) + "},";
|
||||
json += "{\"name\":\"autoplayDuration\",\"label\":\"Autoplay Duration\",\"type\":\"Number\",\"value\":" + String(autoplayDuration) + "},";
|
||||
|
||||
json += "{\"name\":\"solidColor\",\"label\":\"Color\",\"type\":\"Color\",\"value\":\"" + String(solidColor.r) + "," + String(solidColor.g) + "," + String(solidColor.b) +"\"},";
|
||||
|
||||
json += "{\"name\":\"cooling\",\"label\":\"Cooling\",\"type\":\"Number\",\"value\":" + String(cooling) + "},";
|
||||
json += "{\"name\":\"sparking\",\"label\":\"Sparking\",\"type\":\"Number\",\"value\":" + String(sparking) + "}";
|
||||
|
||||
json += "]";
|
||||
*/
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
ESP8266 + FastLED + IR Remote: https://github.com/jasoncoon/esp8266-fastled-webserver
|
||||
Copyright (C) 2016 Jason Coon
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
uint8_t power = 1;
|
||||
uint8_t brightness = brightnessMap[brightnessIndex];
|
||||
|
||||
//String setPower(String value) {
|
||||
// power = value.toInt();
|
||||
// if(power < 0) power = 0;
|
||||
// else if (power > 1) power = 1;
|
||||
// return String(power);
|
||||
//}
|
||||
|
||||
String getPower() {
|
||||
return String(power);
|
||||
}
|
||||
|
||||
//String setBrightness(String value) {
|
||||
// brightness = value.toInt();
|
||||
// if(brightness < 0) brightness = 0;
|
||||
// else if (brightness > 255) brightness = 255;
|
||||
// return String(brightness);
|
||||
//}
|
||||
|
||||
String getBrightness() {
|
||||
return String(brightness);
|
||||
}
|
||||
|
||||
String getPattern() {
|
||||
return String(currentPatternIndex);
|
||||
}
|
||||
|
||||
String getPatterns() {
|
||||
String json = "";
|
||||
|
||||
for (uint8_t i = 0; i < patternCount; i++) {
|
||||
json += "\"" + patterns[i].name + "\"";
|
||||
if (i < patternCount - 1)
|
||||
json += ",";
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
String getAutoplay() {
|
||||
return String(autoplay);
|
||||
}
|
||||
|
||||
String getAutoplayDuration() {
|
||||
return String(autoplayDuration);
|
||||
}
|
||||
|
||||
String getSolidColor() {
|
||||
return String(solidColor.r) + "," + String(solidColor.g) + "," + String(solidColor.b);
|
||||
}
|
||||
|
||||
String getCooling() {
|
||||
return String(cooling);
|
||||
}
|
||||
|
||||
String getSparking() {
|
||||
return String(sparking);
|
||||
}
|
||||
|
||||
String getSpeed() {
|
||||
return String(speed);
|
||||
}
|
||||
|
||||
String getTwinkleSpeed() {
|
||||
return String(twinkleSpeed);
|
||||
}
|
||||
|
||||
String getTwinkleDensity() {
|
||||
return String(twinkleDensity);
|
||||
}
|
||||
|
||||
FieldList fields = {
|
||||
{ "power", "Power", BooleanFieldType, 0, 1, getPower },
|
||||
{ "brightness", "Brightness", NumberFieldType, 1, 255, getBrightness },
|
||||
{ "pattern", "Pattern", SelectFieldType, 0, patternCount, getPattern, getPatterns },
|
||||
{ "speed", "Speed", NumberFieldType, 1, 255, getSpeed },
|
||||
{ "autoplay", "Autoplay", SectionFieldType },
|
||||
{ "autoplay", "Autoplay", BooleanFieldType, 0, 1, getAutoplay },
|
||||
{ "autoplayDuration", "Autoplay Duration", NumberFieldType, 0, 255, getAutoplayDuration },
|
||||
{ "solidColor", "Solid Color", SectionFieldType },
|
||||
{ "solidColor", "Color", ColorFieldType, 0, 255, getSolidColor },
|
||||
{ "fire", "Fire & Water", SectionFieldType },
|
||||
{ "cooling", "Cooling", NumberFieldType, 0, 255, getCooling },
|
||||
{ "sparking", "Sparking", NumberFieldType, 0, 255, getSparking },
|
||||
{ "twinkles", "Twinkles", SectionFieldType },
|
||||
{ "twinkleSpeed", "Twinkle Speed", NumberFieldType, 0, 8, getTwinkleSpeed },
|
||||
{ "twinkleDensity", "Twinkle Density", NumberFieldType, 0, 8, getTwinkleDensity },
|
||||
};
|
||||
|
||||
uint8_t fieldCount = ARRAY_SIZE(fields);
|
|
@ -0,0 +1,390 @@
|
|||
// TwinkleFOX by Mark Kriegsman: https://gist.github.com/kriegsman/756ea6dcae8e30845b5a
|
||||
//
|
||||
// TwinkleFOX: Twinkling 'holiday' lights that fade in and out.
|
||||
// Colors are chosen from a palette; a few palettes are provided.
|
||||
//
|
||||
// This December 2015 implementation improves on the December 2014 version
|
||||
// in several ways:
|
||||
// - smoother fading, compatible with any colors and any palettes
|
||||
// - easier control of twinkle speed and twinkle density
|
||||
// - supports an optional 'background color'
|
||||
// - takes even less RAM: zero RAM overhead per pixel
|
||||
// - illustrates a couple of interesting techniques (uh oh...)
|
||||
//
|
||||
// The idea behind this (new) implementation is that there's one
|
||||
// basic, repeating pattern that each pixel follows like a waveform:
|
||||
// The brightness rises from 0..255 and then falls back down to 0.
|
||||
// The brightness at any given point in time can be determined as
|
||||
// as a function of time, for example:
|
||||
// brightness = sine( time ); // a sine wave of brightness over time
|
||||
//
|
||||
// So the way this implementation works is that every pixel follows
|
||||
// the exact same wave function over time. In this particular case,
|
||||
// I chose a sawtooth triangle wave (triwave8) rather than a sine wave,
|
||||
// but the idea is the same: brightness = triwave8( time ).
|
||||
//
|
||||
// Of course, if all the pixels used the exact same wave form, and
|
||||
// if they all used the exact same 'clock' for their 'time base', all
|
||||
// the pixels would brighten and dim at once -- which does not look
|
||||
// like twinkling at all.
|
||||
//
|
||||
// So to achieve random-looking twinkling, each pixel is given a
|
||||
// slightly different 'clock' signal. Some of the clocks run faster,
|
||||
// some run slower, and each 'clock' also has a random offset from zero.
|
||||
// The net result is that the 'clocks' for all the pixels are always out
|
||||
// of sync from each other, producing a nice random distribution
|
||||
// of twinkles.
|
||||
//
|
||||
// The 'clock speed adjustment' and 'time offset' for each pixel
|
||||
// are generated randomly. One (normal) approach to implementing that
|
||||
// would be to randomly generate the clock parameters for each pixel
|
||||
// at startup, and store them in some arrays. However, that consumes
|
||||
// a great deal of precious RAM, and it turns out to be totally
|
||||
// unnessary! If the random number generate is 'seeded' with the
|
||||
// same starting value every time, it will generate the same sequence
|
||||
// of values every time. So the clock adjustment parameters for each
|
||||
// pixel are 'stored' in a pseudo-random number generator! The PRNG
|
||||
// is reset, and then the first numbers out of it are the clock
|
||||
// adjustment parameters for the first pixel, the second numbers out
|
||||
// of it are the parameters for the second pixel, and so on.
|
||||
// In this way, we can 'store' a stable sequence of thousands of
|
||||
// random clock adjustment parameters in literally two bytes of RAM.
|
||||
//
|
||||
// There's a little bit of fixed-point math involved in applying the
|
||||
// clock speed adjustments, which are expressed in eighths. Each pixel's
|
||||
// clock speed ranges from 8/8ths of the system clock (i.e. 1x) to
|
||||
// 23/8ths of the system clock (i.e. nearly 3x).
|
||||
//
|
||||
// On a basic Arduino Uno or Leonardo, this code can twinkle 300+ pixels
|
||||
// smoothly at over 50 updates per seond.
|
||||
//
|
||||
// -Mark Kriegsman, December 2015
|
||||
|
||||
// Overall twinkle speed.
|
||||
// 0 (VERY slow) to 8 (VERY fast).
|
||||
// 4, 5, and 6 are recommended, default is 4.
|
||||
uint8_t twinkleSpeed = 4;
|
||||
|
||||
// Overall twinkle density.
|
||||
// 0 (NONE lit) to 8 (ALL lit at once).
|
||||
// Default is 5.
|
||||
uint8_t twinkleDensity = 5;
|
||||
|
||||
// Background color for 'unlit' pixels
|
||||
// Can be set to CRGB::Black if desired.
|
||||
CRGB gBackgroundColor = CRGB::Black;
|
||||
// Example of dim incandescent fairy light background color
|
||||
// CRGB gBackgroundColor = CRGB(CRGB::FairyLight).nscale8_video(16);
|
||||
|
||||
// If AUTO_SELECT_BACKGROUND_COLOR is set to 1,
|
||||
// then for any palette where the first two entries
|
||||
// are the same, a dimmed version of that color will
|
||||
// automatically be used as the background color.
|
||||
#define AUTO_SELECT_BACKGROUND_COLOR 0
|
||||
|
||||
// If COOL_LIKE_INCANDESCENT is set to 1, colors will
|
||||
// fade out slighted 'reddened', similar to how
|
||||
// incandescent bulbs change color as they get dim down.
|
||||
#define COOL_LIKE_INCANDESCENT 1
|
||||
|
||||
CRGBPalette16 twinkleFoxPalette;
|
||||
|
||||
// This function is like 'triwave8', which produces a
|
||||
// symmetrical up-and-down triangle sawtooth waveform, except that this
|
||||
// function produces a triangle wave with a faster attack and a slower decay:
|
||||
//
|
||||
// / \
|
||||
// / \
|
||||
// / \
|
||||
// / \
|
||||
//
|
||||
|
||||
uint8_t attackDecayWave8( uint8_t i)
|
||||
{
|
||||
if( i < 86) {
|
||||
return i * 3;
|
||||
} else {
|
||||
i -= 86;
|
||||
return 255 - (i + (i/2));
|
||||
}
|
||||
}
|
||||
|
||||
// This function takes a pixel, and if its in the 'fading down'
|
||||
// part of the cycle, it adjusts the color a little bit like the
|
||||
// way that incandescent bulbs fade toward 'red' as they dim.
|
||||
void coolLikeIncandescent( CRGB& c, uint8_t phase)
|
||||
{
|
||||
if( phase < 128) return;
|
||||
|
||||
uint8_t cooling = (phase - 128) >> 4;
|
||||
c.g = qsub8( c.g, cooling);
|
||||
c.b = qsub8( c.b, cooling * 2);
|
||||
}
|
||||
|
||||
// This function takes a time in pseudo-milliseconds,
|
||||
// figures out brightness = f( time ), and also hue = f( time )
|
||||
// The 'low digits' of the millisecond time are used as
|
||||
// input to the brightness wave function.
|
||||
// The 'high digits' are used to select a color, so that the color
|
||||
// does not change over the course of the fade-in, fade-out
|
||||
// of one cycle of the brightness wave function.
|
||||
// The 'high digits' are also used to determine whether this pixel
|
||||
// should light at all during this cycle, based on the twinkleDensity.
|
||||
CRGB computeOneTwinkle( uint32_t ms, uint8_t salt)
|
||||
{
|
||||
uint16_t ticks = ms >> (8-twinkleSpeed);
|
||||
uint8_t fastcycle8 = ticks;
|
||||
uint16_t slowcycle16 = (ticks >> 8) + salt;
|
||||
slowcycle16 += sin8( slowcycle16);
|
||||
slowcycle16 = (slowcycle16 * 2053) + 1384;
|
||||
uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8);
|
||||
|
||||
uint8_t bright = 0;
|
||||
if( ((slowcycle8 & 0x0E)/2) < twinkleDensity) {
|
||||
bright = attackDecayWave8( fastcycle8);
|
||||
}
|
||||
|
||||
uint8_t hue = slowcycle8 - salt;
|
||||
CRGB c;
|
||||
if( bright > 0) {
|
||||
c = ColorFromPalette( twinkleFoxPalette, hue, bright, NOBLEND);
|
||||
if( COOL_LIKE_INCANDESCENT == 1 ) {
|
||||
coolLikeIncandescent( c, fastcycle8);
|
||||
}
|
||||
} else {
|
||||
c = CRGB::Black;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// This function loops over each pixel, calculates the
|
||||
// adjusted 'clock' that this pixel should use, and calls
|
||||
// "CalculateOneTwinkle" on each pixel. It then displays
|
||||
// either the twinkle color of the background color,
|
||||
// whichever is brighter.
|
||||
void drawTwinkles()
|
||||
{
|
||||
// "PRNG16" is the pseudorandom number generator
|
||||
// It MUST be reset to the same starting value each time
|
||||
// this function is called, so that the sequence of 'random'
|
||||
// numbers that it generates is (paradoxically) stable.
|
||||
uint16_t PRNG16 = 11337;
|
||||
|
||||
uint32_t clock32 = millis();
|
||||
|
||||
// Set up the background color, "bg".
|
||||
// if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of
|
||||
// the current palette are identical, then a deeply faded version of
|
||||
// that color is used for the background color
|
||||
CRGB bg;
|
||||
if( (AUTO_SELECT_BACKGROUND_COLOR == 1) &&
|
||||
(twinkleFoxPalette[0] == twinkleFoxPalette[1] )) {
|
||||
bg = twinkleFoxPalette[0];
|
||||
uint8_t bglight = bg.getAverageLight();
|
||||
if( bglight > 64) {
|
||||
bg.nscale8_video( 16); // very bright, so scale to 1/16th
|
||||
} else if( bglight > 16) {
|
||||
bg.nscale8_video( 64); // not that bright, so scale to 1/4th
|
||||
} else {
|
||||
bg.nscale8_video( 86); // dim, scale to 1/3rd.
|
||||
}
|
||||
} else {
|
||||
bg = gBackgroundColor; // just use the explicitly defined background color
|
||||
}
|
||||
|
||||
uint8_t backgroundBrightness = bg.getAverageLight();
|
||||
|
||||
for(uint8_t i = 0; i < NUM_LEDS; i++) {
|
||||
CRGB& pixel = leds[i];
|
||||
|
||||
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number
|
||||
uint16_t myclockoffset16= PRNG16; // use that number as clock offset
|
||||
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number
|
||||
// use that number as clock speed adjustment factor (in 8ths, from 8/8ths to 23/8ths)
|
||||
uint8_t myspeedmultiplierQ5_3 = ((((PRNG16 & 0xFF)>>4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08;
|
||||
uint32_t myclock30 = (uint32_t)((clock32 * myspeedmultiplierQ5_3) >> 3) + myclockoffset16;
|
||||
uint8_t myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel
|
||||
|
||||
// We now have the adjusted 'clock' for this pixel, now we call
|
||||
// the function that computes what color the pixel should be based
|
||||
// on the "brightness = f( time )" idea.
|
||||
CRGB c = computeOneTwinkle( myclock30, myunique8);
|
||||
|
||||
uint8_t cbright = c.getAverageLight();
|
||||
int16_t deltabright = cbright - backgroundBrightness;
|
||||
if( deltabright >= 32 || (!bg)) {
|
||||
// If the new pixel is significantly brighter than the background color,
|
||||
// use the new color.
|
||||
pixel = c;
|
||||
} else if( deltabright > 0 ) {
|
||||
// If the new pixel is just slightly brighter than the background color,
|
||||
// mix a blend of the new color and the background color
|
||||
pixel = blend( bg, c, deltabright * 8);
|
||||
} else {
|
||||
// if the new pixel is not at all brighter than the background color,
|
||||
// just use the background color.
|
||||
pixel = bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A mostly red palette with green accents and white trim.
|
||||
// "CRGB::Gray" is used as white to keep the brightness more uniform.
|
||||
const TProgmemRGBPalette16 RedGreenWhite_p FL_PROGMEM =
|
||||
{ CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
|
||||
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
|
||||
CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray,
|
||||
CRGB::Green, CRGB::Green, CRGB::Green, CRGB::Green };
|
||||
|
||||
// A mostly (dark) green palette with red berries.
|
||||
#define Holly_Green 0x00580c
|
||||
#define Holly_Red 0xB00402
|
||||
const TProgmemRGBPalette16 Holly_p FL_PROGMEM =
|
||||
{ Holly_Green, Holly_Green, Holly_Green, Holly_Green,
|
||||
Holly_Green, Holly_Green, Holly_Green, Holly_Green,
|
||||
Holly_Green, Holly_Green, Holly_Green, Holly_Green,
|
||||
Holly_Green, Holly_Green, Holly_Green, Holly_Red
|
||||
};
|
||||
|
||||
// A red and white striped palette
|
||||
// "CRGB::Gray" is used as white to keep the brightness more uniform.
|
||||
const TProgmemRGBPalette16 RedWhite_p FL_PROGMEM =
|
||||
{ CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray,
|
||||
CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray,
|
||||
CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray,
|
||||
CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray };
|
||||
|
||||
// A mostly blue palette with white accents.
|
||||
// "CRGB::Gray" is used as white to keep the brightness more uniform.
|
||||
const TProgmemRGBPalette16 BlueWhite_p FL_PROGMEM =
|
||||
{ CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
|
||||
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
|
||||
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
|
||||
CRGB::Blue, CRGB::Gray, CRGB::Gray, CRGB::Gray };
|
||||
|
||||
// A pure "fairy light" palette with some brightness variations
|
||||
#define HALFFAIRY ((CRGB::FairyLight & 0xFEFEFE) / 2)
|
||||
#define QUARTERFAIRY ((CRGB::FairyLight & 0xFCFCFC) / 4)
|
||||
const TProgmemRGBPalette16 FairyLight_p FL_PROGMEM =
|
||||
{ CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight,
|
||||
HALFFAIRY, HALFFAIRY, CRGB::FairyLight, CRGB::FairyLight,
|
||||
QUARTERFAIRY, QUARTERFAIRY, CRGB::FairyLight, CRGB::FairyLight,
|
||||
CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight };
|
||||
|
||||
// A palette of soft snowflakes with the occasional bright one
|
||||
const TProgmemRGBPalette16 Snow_p FL_PROGMEM =
|
||||
{ 0x304048, 0x304048, 0x304048, 0x304048,
|
||||
0x304048, 0x304048, 0x304048, 0x304048,
|
||||
0x304048, 0x304048, 0x304048, 0x304048,
|
||||
0x304048, 0x304048, 0x304048, 0xE0F0FF };
|
||||
|
||||
// A palette reminiscent of large 'old-school' C9-size tree lights
|
||||
// in the five classic colors: red, orange, green, blue, and white.
|
||||
#define C9_Red 0xB80400
|
||||
#define C9_Orange 0x902C02
|
||||
#define C9_Green 0x046002
|
||||
#define C9_Blue 0x070758
|
||||
#define C9_White 0x606820
|
||||
const TProgmemRGBPalette16 RetroC9_p FL_PROGMEM =
|
||||
{ C9_Red, C9_Orange, C9_Red, C9_Orange,
|
||||
C9_Orange, C9_Red, C9_Orange, C9_Red,
|
||||
C9_Green, C9_Green, C9_Green, C9_Green,
|
||||
C9_Blue, C9_Blue, C9_Blue,
|
||||
C9_White
|
||||
};
|
||||
|
||||
// A cold, icy pale blue palette
|
||||
#define Ice_Blue1 0x0C1040
|
||||
#define Ice_Blue2 0x182080
|
||||
#define Ice_Blue3 0x5080C0
|
||||
const TProgmemRGBPalette16 Ice_p FL_PROGMEM =
|
||||
{
|
||||
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
|
||||
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
|
||||
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
|
||||
Ice_Blue2, Ice_Blue2, Ice_Blue2, Ice_Blue3
|
||||
};
|
||||
|
||||
void redGreenWhiteTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = RedGreenWhite_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void hollyTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = Holly_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void redWhiteTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = RedWhite_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void blueWhiteTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = BlueWhite_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void fairyLightTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = FairyLight_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void snow2Twinkles()
|
||||
{
|
||||
twinkleFoxPalette = Snow_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void iceTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = Ice_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void retroC9Twinkles()
|
||||
{
|
||||
twinkleFoxPalette = RetroC9_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void partyTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = PartyColors_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void forestTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = ForestColors_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void lavaTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = LavaColors_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void fireTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = HeatColors_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void cloud2Twinkles()
|
||||
{
|
||||
twinkleFoxPalette = CloudColors_p;
|
||||
drawTwinkles();
|
||||
}
|
||||
|
||||
void oceanTwinkles()
|
||||
{
|
||||
twinkleFoxPalette = OceanColors_p;
|
||||
drawTwinkles();
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// based on ColorTwinkles by Mark Kriegsman: https://gist.github.com/kriegsman/5408ecd397744ba0393e
|
||||
|
||||
#define STARTING_BRIGHTNESS 64
|
||||
#define FADE_IN_SPEED 32
|
||||
#define FADE_OUT_SPEED 20
|
||||
#define DENSITY 255
|
||||
|
||||
enum { GETTING_DARKER = 0, GETTING_BRIGHTER = 1 };
|
||||
|
||||
CRGB makeBrighter( const CRGB& color, fract8 howMuchBrighter)
|
||||
{
|
||||
CRGB incrementalColor = color;
|
||||
incrementalColor.nscale8( howMuchBrighter);
|
||||
return color + incrementalColor;
|
||||
}
|
||||
|
||||
CRGB makeDarker( const CRGB& color, fract8 howMuchDarker)
|
||||
{
|
||||
CRGB newcolor = color;
|
||||
newcolor.nscale8( 255 - howMuchDarker);
|
||||
return newcolor;
|
||||
}
|
||||
|
||||
// Compact implementation of
|
||||
// the directionFlags array, using just one BIT of RAM
|
||||
// per pixel. This requires a bunch of bit wrangling,
|
||||
// but conserves precious RAM. The cost is a few
|
||||
// cycles and about 100 bytes of flash program memory.
|
||||
uint8_t directionFlags[ (NUM_LEDS + 7) / 8];
|
||||
|
||||
bool getPixelDirection( uint16_t i)
|
||||
{
|
||||
uint16_t index = i / 8;
|
||||
uint8_t bitNum = i & 0x07;
|
||||
|
||||
uint8_t andMask = 1 << bitNum;
|
||||
return (directionFlags[index] & andMask) != 0;
|
||||
}
|
||||
|
||||
void setPixelDirection( uint16_t i, bool dir)
|
||||
{
|
||||
uint16_t index = i / 8;
|
||||
uint8_t bitNum = i & 0x07;
|
||||
|
||||
uint8_t orMask = 1 << bitNum;
|
||||
uint8_t andMask = 255 - orMask;
|
||||
uint8_t value = directionFlags[index] & andMask;
|
||||
if ( dir ) {
|
||||
value += orMask;
|
||||
}
|
||||
directionFlags[index] = value;
|
||||
}
|
||||
|
||||
void brightenOrDarkenEachPixel( fract8 fadeUpAmount, fract8 fadeDownAmount)
|
||||
{
|
||||
for ( uint16_t i = 0; i < NUM_LEDS; i++) {
|
||||
if ( getPixelDirection(i) == GETTING_DARKER) {
|
||||
// This pixel is getting darker
|
||||
leds[i] = makeDarker( leds[i], fadeDownAmount);
|
||||
} else {
|
||||
// This pixel is getting brighter
|
||||
leds[i] = makeBrighter( leds[i], fadeUpAmount);
|
||||
// now check to see if we've maxxed out the brightness
|
||||
if ( leds[i].r == 255 || leds[i].g == 255 || leds[i].b == 255) {
|
||||
// if so, turn around and start getting darker
|
||||
setPixelDirection(i, GETTING_DARKER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void colortwinkles()
|
||||
{
|
||||
EVERY_N_MILLIS(30)
|
||||
{
|
||||
// Make each pixel brighter or darker, depending on
|
||||
// its 'direction' flag.
|
||||
brightenOrDarkenEachPixel( FADE_IN_SPEED, FADE_OUT_SPEED);
|
||||
|
||||
// Now consider adding a new random twinkle
|
||||
if ( random8() < DENSITY ) {
|
||||
int pos = random16(NUM_LEDS);
|
||||
if ( !leds[pos]) {
|
||||
leds[pos] = ColorFromPalette( gCurrentPalette, random8(), STARTING_BRIGHTNESS, NOBLEND);
|
||||
setPixelDirection(pos, GETTING_BRIGHTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cloudTwinkles()
|
||||
{
|
||||
gCurrentPalette = CloudColors_p; // Blues and whites!
|
||||
colortwinkles();
|
||||
}
|
||||
|
||||
void rainbowTwinkles()
|
||||
{
|
||||
gCurrentPalette = RainbowColors_p;
|
||||
colortwinkles();
|
||||
}
|
||||
|
||||
void snowTwinkles()
|
||||
{
|
||||
CRGB w(85, 85, 85), W(CRGB::White);
|
||||
|
||||
gCurrentPalette = CRGBPalette16( W, W, W, W, w, w, w, w, w, w, w, w, w, w, w, w );
|
||||
colortwinkles();
|
||||
}
|
||||
|
||||
void incandescentTwinkles()
|
||||
{
|
||||
CRGB l(0xE1A024);
|
||||
|
||||
gCurrentPalette = CRGBPalette16( l, l, l, l, l, l, l, l, l, l, l, l, l, l, l, l );
|
||||
colortwinkles();
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,18 @@
|
|||
/*body {
|
||||
padding-bottom: 70px;
|
||||
}*/
|
||||
|
||||
.grid-item-color {
|
||||
width: 4%;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.grid-item-pattern {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 5px;
|
||||
padding: 6px;
|
||||
white-space: normal;
|
||||
cursor: pointer;
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
270
data/index.htm
270
data/index.htm
|
@ -5,110 +5,228 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ESP8266 + FastLED</title>
|
||||
<title>ESP8266 + FastLED by Evil Genius Labs</title>
|
||||
|
||||
<!-- request CSS from internet CDN -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-minicolors/2.2.4/jquery.minicolors.min.css" integrity="sha256-4wnSkPYU5B4yngAlx/rEb8LdfMah4teUth4AfhGEuaY=" crossorigin="anonymous" />
|
||||
|
||||
<!-- request CSS from the ESP8266 web server -->
|
||||
<!-- <link rel="stylesheet" href="css/bootstrap.min.css"> -->
|
||||
<!-- <link rel="stylesheet" href="css/jquery.minicolors.min.css"> -->
|
||||
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
|
||||
<link rel="icon" href="images/atom196.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="navbar navbar-default navbar-static-top" id="top" role="banner">
|
||||
|
||||
<nav class="navbar navbar-default navbar-static-top" id="top" role="banner">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="/">ESP8266 + FastLED</a>
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse-1" aria-expanded="false">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="https://www.evilgeniuslabs.org" target="_blank"><img src="https://evilgeniuslabs.org/images/atom.svg" style="width: 24px; height: 24px;" /></a>
|
||||
<a class="navbar-brand" href="https://www.evilgeniuslabs.org" target="_blank">Evil Genius Labs</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="/">Tree v2 <span class="sr-only">(current)</span></a></li>
|
||||
<li><a href="/simple.htm" target="_blank" title="Simple Mode">Simple</a></li>
|
||||
<li><a href="/edit.htm" target="_blank" title="Edit Files">Files</a></li>
|
||||
<li><a href="/update" target="_blank" title="Update Firmware">Firmware</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<img style="height: 16px;" src="https://assets-cdn.github.com/favicon.ico">
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-1 col-sm-offset-2">
|
||||
<button type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-refresh" id="btnRefresh"></span>
|
||||
</nav>
|
||||
|
||||
<div id="container" class="container">
|
||||
|
||||
<form class="form-horizontal" id="form">
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="templates" style="display: none">
|
||||
|
||||
<div id="sectionTemplate" class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<hr style="margin-bottom: 5px;margin-top: 5px;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="numberTemplate" class="form-group">
|
||||
<label class="col-sm-2 control-label"></label>
|
||||
<div class="col-sm-2">
|
||||
<input class="form-control input" type="number" step="1" min="0" max="255" />
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-control slider" type="range" step="1" min="0" max="255" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="booleanTemplate" class="form-group">
|
||||
<label class="col-sm-2 control-label"></label>
|
||||
<div class="col-sm-10">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default" id="btnOn">On</button>
|
||||
<button type="button" class="btn btn-default" id="btnOff">Off</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="selectTemplate" class="form-group">
|
||||
<label class="col-sm-2 control-label"></label>
|
||||
<div class="col-sm-8">
|
||||
<select class="form-control"><select>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<button type="button" class="btn btn-default btn-previous"
|
||||
aria-label="Previous" title="Previous">
|
||||
<span class="glyphicon glyphicon-chevron-left"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-next"
|
||||
aria-label="Next" title="Next">
|
||||
<span class="glyphicon glyphicon-chevron-right"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<p id="status" class="form-control-static">Status</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Power</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="btn-group" role="group" aria-label="Power">
|
||||
<button type="button" class="btn btn-default" id="btnPowerOn">On</button>
|
||||
<button type="button" class="btn btn-default" id="btnPowerOff">Off</button>
|
||||
</div>
|
||||
|
||||
<div id="colorPaletteTemplate" class="form-group">
|
||||
<label class="col-sm-2 control-label color-label"></label>
|
||||
<div class="col-sm-10">
|
||||
<div class="btn-group btn-group-justified" role="group">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FF0000;" title="Red"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FF8000;" title="Orange"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FFFF00;" title="Yellow"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #80FF00;" title="Chartreuse"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #00FF00;" title="Green"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #00FF80;" title="Spring Green"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #00FFFF;" title="Cyan"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #0080FF;" title="Azure"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #0000FF;" title="Blue"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #8000FF;" title="Violet"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FF00FF;" title="Magenta"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FF0080;" title="Rose"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FFFFFF;" title="White"> </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="colorTemplate">
|
||||
<div class="form-group">
|
||||
<label for="inputBrightness" class="col-sm-2 control-label">Brightness</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" id="spanBrightness">128</span>
|
||||
<input class="form-control" id="inputBrightness" type="range" step="1" min="0" max="255" />
|
||||
</div>
|
||||
<!-- <label class="col-sm-2 control-label color-label"></label> -->
|
||||
<div class="col-sm-12 col-sm-offset-2">
|
||||
<input type="text" class="form-control minicolors" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputPattern" class="col-sm-2 control-label">Pattern</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-control" id="inputPattern">
|
||||
<option>Rainbow</option>
|
||||
<option>Sinelon</option>
|
||||
<option>Juggle</option>
|
||||
</select>
|
||||
<label class="col-sm-2 control-label">Red</label>
|
||||
<div class="col-sm-2">
|
||||
<input class="form-control color-red-input" type="number" step="1" min="0" max="255" />
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-control color-red-slider" type="range" step="1" min="0" max="255" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputColor" class="col-sm-2 control-label">Color</label>
|
||||
<div class="col-sm-6">
|
||||
<input id="inputColor" type="text" class="form-control">
|
||||
<label class="col-sm-2 control-label">Green</label>
|
||||
<div class="col-sm-2">
|
||||
<input class="form-control color-green-input" type="number" step="1" min="0" max="255" />
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-control color-green-slider" type="range" step="1" min="0" max="255" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-6 col-sm-offset-2">
|
||||
<div class="btn-group btn-group-justified" role="group">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FF0000;" title="Red"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FF8000;" title="Orange"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FFFF00;" title="Yellow"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #80FF00;" title="Chartreuse"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #00FF00;" title="Green"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #00FF80;" title="Spring Green"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #00FFFF;" title="Cyan"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #0080FF;" title="Azure"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #0000FF;" title="Blue"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #8000FF;" title="Violet"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FF00FF;" title="Magenta"> </button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-color" style="background: #FF0080;" title="Rose"> </button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="col-sm-2 control-label">Blue</label>
|
||||
<div class="col-sm-2">
|
||||
<input class="form-control color-blue-input" type="number" step="1" min="0" max="255" />
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-control color-blue-slider" type="range" step="1" min="0" max="255" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="js/scripts.js"></script>
|
||||
|
||||
<nav class="navbar navbar-default navbar-fixed-bottom">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse-2" aria-expanded="false">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="navbar-collapse-2">
|
||||
<ul class="nav navbar-nav">
|
||||
<li>
|
||||
<a href="/" aria-label="Refresh" title="Refresh">
|
||||
<span class="glyphicon glyphicon-refresh" id="btnRefresh"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li><p class="navbar-text" id="status">Loading, please wait...</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- request js from internet CDN -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-minicolors/2.2.4/jquery.minicolors.min.js" integrity="sha256-XAFQ9dZ6hy8p/GRhU8h/8pMvM1etymiJLZW1CiHV3bQ=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.min.js" integrity="sha256-A4JwlcDvqO4JXpvEtvWY1RH8JAEMu5W21wP8GUXLUNs=" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- request js from the ESP8266 web server -->
|
||||
<!-- <script src="js/jquery-3.1.1.min.js"></script> -->
|
||||
<!-- <script src="js/bootstrap.min.js"></script> -->
|
||||
<!-- <script src="js/jquery.minicolors.min.js"></script> -->
|
||||
<!-- <script src="js/r-websocket.min.js"></script> -->
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,462 @@
|
|||
// used when hosting the site on the ESP8266
|
||||
var address = location.hostname;
|
||||
var urlBase = "";
|
||||
|
||||
// used when hosting the site somewhere other than the ESP8266 (handy for testing without waiting forever to upload to SPIFFS)
|
||||
// var address = "esp8266-1920f7.local";
|
||||
// var urlBase = "http://" + address + "/";
|
||||
|
||||
var postColorTimer = {};
|
||||
var postValueTimer = {};
|
||||
|
||||
var ignoreColorChange = false;
|
||||
|
||||
var ws = new ReconnectingWebSocket('ws://' + address + ':81/', ['arduino']);
|
||||
ws.debug = true;
|
||||
|
||||
ws.onmessage = function(evt) {
|
||||
if(evt.data != null)
|
||||
{
|
||||
var data = JSON.parse(evt.data);
|
||||
if(data == null) return;
|
||||
updateFieldValue(data.name, data.value);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#status").html("Connecting, please wait...");
|
||||
|
||||
$.get(urlBase + "all", function(data) {
|
||||
$("#status").html("Loading, please wait...");
|
||||
|
||||
$.each(data, function(index, field) {
|
||||
if (field.type == "Number") {
|
||||
addNumberField(field);
|
||||
} else if (field.type == "Boolean") {
|
||||
addBooleanField(field);
|
||||
} else if (field.type == "Select") {
|
||||
addSelectField(field);
|
||||
} else if (field.type == "Color") {
|
||||
addColorFieldPalette(field);
|
||||
addColorFieldPicker(field);
|
||||
} else if (field.type == "Section") {
|
||||
addSectionField(field);
|
||||
}
|
||||
});
|
||||
|
||||
$(".minicolors").minicolors({
|
||||
theme: "bootstrap",
|
||||
changeDelay: 200,
|
||||
control: "wheel",
|
||||
format: "rgb",
|
||||
inline: true
|
||||
});
|
||||
|
||||
$("#status").html("Ready");
|
||||
})
|
||||
.fail(function(errorThrown) {
|
||||
console.log("error: " + errorThrown);
|
||||
});
|
||||
});
|
||||
|
||||
function addNumberField(field) {
|
||||
var template = $("#numberTemplate").clone();
|
||||
|
||||
template.attr("id", "form-group-" + field.name);
|
||||
template.attr("data-field-type", field.type);
|
||||
|
||||
var label = template.find(".control-label");
|
||||
label.attr("for", "input-" + field.name);
|
||||
label.text(field.label);
|
||||
|
||||
var input = template.find(".input");
|
||||
var slider = template.find(".slider");
|
||||
slider.attr("id", "input-" + field.name);
|
||||
if (field.min) {
|
||||
input.attr("min", field.min);
|
||||
slider.attr("min", field.min);
|
||||
}
|
||||
if (field.max) {
|
||||
input.attr("max", field.max);
|
||||
slider.attr("max", field.max);
|
||||
}
|
||||
if (field.step) {
|
||||
input.attr("step", field.step);
|
||||
slider.attr("step", field.step);
|
||||
}
|
||||
input.val(field.value);
|
||||
slider.val(field.value);
|
||||
|
||||
slider.on("change mousemove", function() {
|
||||
input.val($(this).val());
|
||||
});
|
||||
|
||||
slider.on("change", function() {
|
||||
var value = $(this).val();
|
||||
input.val(value);
|
||||
field.value = value;
|
||||
delayPostValue(field.name, value);
|
||||
});
|
||||
|
||||
input.on("change", function() {
|
||||
var value = $(this).val();
|
||||
slider.val(value);
|
||||
field.value = value;
|
||||
delayPostValue(field.name, value);
|
||||
});
|
||||
|
||||
$("#form").append(template);
|
||||
}
|
||||
|
||||
function addBooleanField(field) {
|
||||
var template = $("#booleanTemplate").clone();
|
||||
|
||||
template.attr("id", "form-group-" + field.name);
|
||||
template.attr("data-field-type", field.type);
|
||||
|
||||
var label = template.find(".control-label");
|
||||
label.attr("for", "btn-group-" + field.name);
|
||||
label.text(field.label);
|
||||
|
||||
var btngroup = template.find(".btn-group");
|
||||
btngroup.attr("id", "btn-group-" + field.name);
|
||||
|
||||
var btnOn = template.find("#btnOn");
|
||||
var btnOff = template.find("#btnOff");
|
||||
|
||||
btnOn.attr("id", "btnOn" + field.name);
|
||||
btnOff.attr("id", "btnOff" + field.name);
|
||||
|
||||
btnOn.attr("class", field.value ? "btn btn-primary" : "btn btn-default");
|
||||
btnOff.attr("class", !field.value ? "btn btn-primary" : "btn btn-default");
|
||||
|
||||
btnOn.click(function() {
|
||||
setBooleanFieldValue(field, btnOn, btnOff, 1)
|
||||
});
|
||||
btnOff.click(function() {
|
||||
setBooleanFieldValue(field, btnOn, btnOff, 0)
|
||||
});
|
||||
|
||||
$("#form").append(template);
|
||||
}
|
||||
|
||||
function addSelectField(field) {
|
||||
var template = $("#selectTemplate").clone();
|
||||
|
||||
template.attr("id", "form-group-" + field.name);
|
||||
template.attr("data-field-type", field.type);
|
||||
|
||||
var id = "input-" + field.name;
|
||||
|
||||
var label = template.find(".control-label");
|
||||
label.attr("for", id);
|
||||
label.text(field.label);
|
||||
|
||||
var select = template.find(".form-control");
|
||||
select.attr("id", id);
|
||||
|
||||
for (var i = 0; i < field.options.length; i++) {
|
||||
var optionText = field.options[i];
|
||||
var option = $("<option></option>");
|
||||
option.text(optionText);
|
||||
option.attr("value", i);
|
||||
select.append(option);
|
||||
}
|
||||
|
||||
select.val(field.value);
|
||||
|
||||
select.change(function() {
|
||||
var value = template.find("#" + id + " option:selected").index();
|
||||
postValue(field.name, value);
|
||||
});
|
||||
|
||||
var previousButton = template.find(".btn-previous");
|
||||
var nextButton = template.find(".btn-next");
|
||||
|
||||
previousButton.click(function() {
|
||||
var value = template.find("#" + id + " option:selected").index();
|
||||
var count = select.find("option").length;
|
||||
value--;
|
||||
if(value < 0)
|
||||
value = count - 1;
|
||||
select.val(value);
|
||||
postValue(field.name, value);
|
||||
});
|
||||
|
||||
nextButton.click(function() {
|
||||
var value = template.find("#" + id + " option:selected").index();
|
||||
var count = select.find("option").length;
|
||||
value++;
|
||||
if(value >= count)
|
||||
value = 0;
|
||||
select.val(value);
|
||||
postValue(field.name, value);
|
||||
});
|
||||
|
||||
$("#form").append(template);
|
||||
}
|
||||
|
||||
function addColorFieldPicker(field) {
|
||||
var template = $("#colorTemplate").clone();
|
||||
|
||||
template.attr("id", "form-group-" + field.name);
|
||||
template.attr("data-field-type", field.type);
|
||||
|
||||
var id = "input-" + field.name;
|
||||
|
||||
var input = template.find(".minicolors");
|
||||
input.attr("id", id);
|
||||
|
||||
if(!field.value.startsWith("rgb("))
|
||||
field.value = "rgb(" + field.value;
|
||||
|
||||
if(!field.value.endsWith(")"))
|
||||
field.value += ")";
|
||||
|
||||
input.val(field.value);
|
||||
|
||||
var components = rgbToComponents(field.value);
|
||||
|
||||
var redInput = template.find(".color-red-input");
|
||||
var greenInput = template.find(".color-green-input");
|
||||
var blueInput = template.find(".color-blue-input");
|
||||
|
||||
var redSlider = template.find(".color-red-slider");
|
||||
var greenSlider = template.find(".color-green-slider");
|
||||
var blueSlider = template.find(".color-blue-slider");
|
||||
|
||||
redInput.attr("id", id + "-red");
|
||||
greenInput.attr("id", id + "-green");
|
||||
blueInput.attr("id", id + "-blue");
|
||||
|
||||
redSlider.attr("id", id + "-red-slider");
|
||||
greenSlider.attr("id", id + "-green-slider");
|
||||
blueSlider.attr("id", id + "-blue-slider");
|
||||
|
||||
redInput.val(components.r);
|
||||
greenInput.val(components.g);
|
||||
blueInput.val(components.b);
|
||||
|
||||
redSlider.val(components.r);
|
||||
greenSlider.val(components.g);
|
||||
blueSlider.val(components.b);
|
||||
|
||||
redInput.on("change", function() {
|
||||
var value = $("#" + id).val();
|
||||
var r = $(this).val();
|
||||
var components = rgbToComponents(value);
|
||||
field.value = r + "," + components.g + "," + components.b;
|
||||
$("#" + id).minicolors("value", "rgb(" + field.value + ")");
|
||||
redSlider.val(r);
|
||||
});
|
||||
|
||||
greenInput.on("change", function() {
|
||||
var value = $("#" + id).val();
|
||||
var g = $(this).val();
|
||||
var components = rgbToComponents(value);
|
||||
field.value = components.r + "," + g + "," + components.b;
|
||||
$("#" + id).minicolors("value", "rgb(" + field.value + ")");
|
||||
greenSlider.val(g);
|
||||
});
|
||||
|
||||
blueInput.on("change", function() {
|
||||
var value = $("#" + id).val();
|
||||
var b = $(this).val();
|
||||
var components = rgbToComponents(value);
|
||||
field.value = components.r + "," + components.g + "," + b;
|
||||
$("#" + id).minicolors("value", "rgb(" + field.value + ")");
|
||||
blueSlider.val(b);
|
||||
});
|
||||
|
||||
redSlider.on("change", function() {
|
||||
var value = $("#" + id).val();
|
||||
var r = $(this).val();
|
||||
var components = rgbToComponents(value);
|
||||
field.value = r + "," + components.g + "," + components.b;
|
||||
$("#" + id).minicolors("value", "rgb(" + field.value + ")");
|
||||
redInput.val(r);
|
||||
});
|
||||
|
||||
greenSlider.on("change", function() {
|
||||
var value = $("#" + id).val();
|
||||
var g = $(this).val();
|
||||
var components = rgbToComponents(value);
|
||||
field.value = components.r + "," + g + "," + components.b;
|
||||
$("#" + id).minicolors("value", "rgb(" + field.value + ")");
|
||||
greenInput.val(g);
|
||||
});
|
||||
|
||||
blueSlider.on("change", function() {
|
||||
var value = $("#" + id).val();
|
||||
var b = $(this).val();
|
||||
var components = rgbToComponents(value);
|
||||
field.value = components.r + "," + components.g + "," + b;
|
||||
$("#" + id).minicolors("value", "rgb(" + field.value + ")");
|
||||
blueInput.val(b);
|
||||
});
|
||||
|
||||
redSlider.on("change mousemove", function() {
|
||||
redInput.val($(this).val());
|
||||
});
|
||||
|
||||
greenSlider.on("change mousemove", function() {
|
||||
greenInput.val($(this).val());
|
||||
});
|
||||
|
||||
blueSlider.on("change mousemove", function() {
|
||||
blueInput.val($(this).val());
|
||||
});
|
||||
|
||||
input.on("change", function() {
|
||||
if (ignoreColorChange) return;
|
||||
|
||||
var value = $(this).val();
|
||||
var components = rgbToComponents(value);
|
||||
|
||||
redInput.val(components.r);
|
||||
greenInput.val(components.g);
|
||||
blueInput.val(components.b);
|
||||
|
||||
redSlider.val(components.r);
|
||||
greenSlider.val(components.g);
|
||||
blueSlider.val(components.b);
|
||||
|
||||
field.value = components.r + "," + components.g + "," + components.b;
|
||||
delayPostColor(field.name, components);
|
||||
});
|
||||
|
||||
$("#form").append(template);
|
||||
}
|
||||
|
||||
function addColorFieldPalette(field) {
|
||||
var template = $("#colorPaletteTemplate").clone();
|
||||
|
||||
var buttons = template.find(".btn-color");
|
||||
|
||||
var label = template.find(".control-label");
|
||||
label.text(field.label);
|
||||
|
||||
buttons.each(function(index, button) {
|
||||
$(button).click(function() {
|
||||
var rgb = $(this).css('backgroundColor');
|
||||
var components = rgbToComponents(rgb);
|
||||
|
||||
field.value = components.r + "," + components.g + "," + components.b;
|
||||
postColor(field.name, components);
|
||||
|
||||
ignoreColorChange = true;
|
||||
var id = "#input-" + field.name;
|
||||
$(id).minicolors("value", "rgb(" + field.value + ")");
|
||||
$(id + "-red").val(components.r);
|
||||
$(id + "-green").val(components.g);
|
||||
$(id + "-blue").val(components.b);
|
||||
$(id + "-red-slider").val(components.r);
|
||||
$(id + "-green-slider").val(components.g);
|
||||
$(id + "-blue-slider").val(components.b);
|
||||
ignoreColorChange = false;
|
||||
});
|
||||
});
|
||||
|
||||
$("#form").append(template);
|
||||
}
|
||||
|
||||
function addSectionField(field) {
|
||||
var template = $("#sectionTemplate").clone();
|
||||
|
||||
template.attr("id", "form-group-section-" + field.name);
|
||||
template.attr("data-field-type", field.type);
|
||||
|
||||
$("#form").append(template);
|
||||
}
|
||||
|
||||
function updateFieldValue(name, value) {
|
||||
var group = $("#form-group-" + name);
|
||||
|
||||
var type = group.attr("data-field-type");
|
||||
|
||||
if (type == "Number") {
|
||||
var input = group.find(".form-control");
|
||||
input.val(value);
|
||||
} else if (type == "Boolean") {
|
||||
var btnOn = group.find("#btnOn" + name);
|
||||
var btnOff = group.find("#btnOff" + name);
|
||||
|
||||
btnOn.attr("class", value ? "btn btn-primary" : "btn btn-default");
|
||||
btnOff.attr("class", !value ? "btn btn-primary" : "btn btn-default");
|
||||
|
||||
} else if (type == "Select") {
|
||||
var select = group.find(".form-control");
|
||||
select.val(value);
|
||||
} else if (type == "Color") {
|
||||
var input = group.find(".form-control");
|
||||
input.val("rgb(" + value + ")");
|
||||
}
|
||||
};
|
||||
|
||||
function setBooleanFieldValue(field, btnOn, btnOff, value) {
|
||||
field.value = value;
|
||||
|
||||
btnOn.attr("class", field.value ? "btn btn-primary" : "btn btn-default");
|
||||
btnOff.attr("class", !field.value ? "btn btn-primary" : "btn btn-default");
|
||||
|
||||
postValue(field.name, field.value);
|
||||
}
|
||||
|
||||
function postValue(name, value) {
|
||||
$("#status").html("Setting " + name + ": " + value + ", please wait...");
|
||||
|
||||
var body = { name: name, value: value };
|
||||
|
||||
$.post(urlBase + name + "?value=" + value, body, function(data) {
|
||||
if (data.name != null) {
|
||||
$("#status").html("Set " + name + ": " + data.name);
|
||||
} else {
|
||||
$("#status").html("Set " + name + ": " + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function delayPostValue(name, value) {
|
||||
clearTimeout(postValueTimer);
|
||||
postValueTimer = setTimeout(function() {
|
||||
postValue(name, value);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function postColor(name, value) {
|
||||
$("#status").html("Setting " + name + ": " + value.r + "," + value.g + "," + value.b + ", please wait...");
|
||||
|
||||
var body = { name: name, r: value.r, g: value.g, b: value.b };
|
||||
|
||||
$.post(urlBase + name + "?r=" + value.r + "&g=" + value.g + "&b=" + value.b, body, function(data) {
|
||||
$("#status").html("Set " + name + ": " + data);
|
||||
})
|
||||
.fail(function(textStatus, errorThrown) { $("#status").html("Fail: " + textStatus + " " + errorThrown); });
|
||||
}
|
||||
|
||||
function delayPostColor(name, value) {
|
||||
clearTimeout(postColorTimer);
|
||||
postColorTimer = setTimeout(function() {
|
||||
postColor(name, value);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function componentToHex(c) {
|
||||
var hex = c.toString(16);
|
||||
return hex.length == 1 ? "0" + hex : hex;
|
||||
}
|
||||
|
||||
function rgbToHex(r, g, b) {
|
||||
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
|
||||
}
|
||||
|
||||
function rgbToComponents(rgb) {
|
||||
var components = {};
|
||||
|
||||
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||
components.r = parseInt(rgb[1]);
|
||||
components.g = parseInt(rgb[2]);
|
||||
components.b = parseInt(rgb[3]);
|
||||
|
||||
return components;
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,239 @@
|
|||
// used when hosting the site on the ESP8266
|
||||
var address = location.hostname;
|
||||
var urlBase = "";
|
||||
|
||||
// used when hosting the site somewhere other than the ESP8266 (handy for testing without waiting forever to upload to SPIFFS)
|
||||
// var address = "192.168.1.13";
|
||||
// var urlBase = "http://" + address + "/";
|
||||
|
||||
var postColorTimer = {};
|
||||
var postValueTimer = {};
|
||||
|
||||
var ignoreColorChange = false;
|
||||
|
||||
var patterns = [
|
||||
"Pride",
|
||||
"Color Waves",
|
||||
|
||||
"Rainbow Twinkles",
|
||||
"Snow Twinkles",
|
||||
"Cloud Twinkles",
|
||||
"Incandescent Twinkles",
|
||||
|
||||
"Retro C9 Twinkles",
|
||||
"Red & White Twinkles",
|
||||
"Blue & White Twinkles",
|
||||
"Red, Green & White Twinkles",
|
||||
"Fairy Light Twinkles",
|
||||
"Snow 2 Twinkles",
|
||||
"Holly Twinkles",
|
||||
"Ice Twinkles",
|
||||
"Party Twinkles",
|
||||
"Forest Twinkles",
|
||||
"Lava Twinkles",
|
||||
"Fire Twinkles",
|
||||
"Cloud 2 Twinkles",
|
||||
"Ocean Twinkles",
|
||||
|
||||
"Rainbow",
|
||||
"Rainbow With Glitter",
|
||||
"Solid Rainbow",
|
||||
"Confetti",
|
||||
"Sinelon",
|
||||
"Beat",
|
||||
"Juggle",
|
||||
"Fire",
|
||||
"Water"
|
||||
];
|
||||
|
||||
var ws = new ReconnectingWebSocket('ws://' + address + ':81/', ['arduino']);
|
||||
ws.debug = true;
|
||||
|
||||
ws.onmessage = function(evt) {
|
||||
if(evt.data != null)
|
||||
{
|
||||
var data = JSON.parse(evt.data);
|
||||
if(data == null) return;
|
||||
switch(data.name) {
|
||||
case "power":
|
||||
if(data.value == 1) {
|
||||
$("#btnOn").attr("class", "btn btn-primary");
|
||||
$("#btnOff").attr("class", "btn btn-default");
|
||||
} else {
|
||||
$("#btnOff").attr("class", "btn btn-primary");
|
||||
$("#btnOn").attr("class", "btn btn-default");
|
||||
}
|
||||
break;
|
||||
|
||||
case "pattern":
|
||||
$(".grid-item-pattern").attr("class", "grid-item-pattern btn btn-default");
|
||||
$("#pattern-button-" + data.value).attr("class", "grid-item-pattern btn btn-primary");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#status").html("Connecting, please wait...");
|
||||
|
||||
$.get(urlBase + "all", function(data) {
|
||||
$("#status").html("Loading, please wait...");
|
||||
|
||||
$.each(data, function(index, field) {
|
||||
switch (field.name) {
|
||||
case "power":
|
||||
if(field.value == 1) {
|
||||
$("#btnOn").attr("class", "btn btn-primary");
|
||||
} else {
|
||||
$("#btnOff").attr("class", "btn btn-primary");
|
||||
}
|
||||
break;
|
||||
|
||||
case "pattern":
|
||||
addPatternButtons(field);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
addColorButtons();
|
||||
|
||||
$("#btnOn").click(function() {
|
||||
postValue("power", 1);
|
||||
$("#btnOn").attr("class", "btn btn-primary");
|
||||
$("#btnOff").attr("class", "btn btn-default");
|
||||
});
|
||||
|
||||
$("#btnOff").click(function() {
|
||||
postValue("power", 0);
|
||||
$("#btnOff").attr("class", "btn btn-primary");
|
||||
$("#btnOn").attr("class", "btn btn-default");
|
||||
});
|
||||
|
||||
$("#status").html("Ready");
|
||||
});
|
||||
|
||||
function addColorButtons() {
|
||||
var hues = 25;
|
||||
var hueStep = 360 / hues;
|
||||
|
||||
var levels = 10;
|
||||
var levelStep = 60 / levels;
|
||||
|
||||
for(var l = 20; l < 80; l += levelStep) {
|
||||
for(var h = 0; h < hues; h++) {
|
||||
addColorButton(h * hueStep, 100, l);
|
||||
}
|
||||
}
|
||||
|
||||
$('.grid-color').isotope({
|
||||
itemSelector: '.grid-item-color',
|
||||
layoutMode: 'fitRows'
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
var colorButtonIndex = 0;
|
||||
|
||||
function addColorButton(h, s, l) {
|
||||
var color = "hsla(" + h + ", " + s + "%, " + l + "%, 1)"
|
||||
var template = $("#colorButtonTemplate").clone();
|
||||
template.attr("id", "color-button-" + colorButtonIndex++);
|
||||
template.css("background-color", color);
|
||||
template.click(function() {
|
||||
var rgb = $(this).css('backgroundColor');
|
||||
var components = rgbToComponents(rgb);
|
||||
|
||||
$(".grid-item-color").css("border", "none");
|
||||
$(this).css("border", "1px solid");
|
||||
|
||||
postColor("solidColor", components);
|
||||
});
|
||||
|
||||
$("#colorButtonsRow").append(template);
|
||||
}
|
||||
|
||||
function addPatternButtons(patternField) {
|
||||
$.each(patternField.options, function(index, pattern) {
|
||||
if($.inArray(pattern, patterns) == -1)
|
||||
return;
|
||||
|
||||
var template = $("#patternButtonTemplate").clone();
|
||||
template.attr("id", "pattern-button-" + index);
|
||||
template.text(pattern);
|
||||
template.click(function() {
|
||||
postValue("patternName", pattern);
|
||||
$(".grid-item-color").css("border", "none");
|
||||
$(".grid-item-pattern").attr("class", "grid-item-pattern btn btn-default");
|
||||
$(this).attr("class", "grid-item-pattern btn btn-primary");
|
||||
});
|
||||
|
||||
$("#patternGrid").append(template);
|
||||
});
|
||||
|
||||
$('.grid-pattern').isotope({
|
||||
itemSelector: '.grid-item-pattern',
|
||||
layoutMode: 'fitRows'
|
||||
});
|
||||
|
||||
$("#pattern-button-" + patternField.value).attr("class", "grid-item-pattern btn btn-primary");
|
||||
}
|
||||
|
||||
function postValue(name, value) {
|
||||
$("#status").html("Setting " + name + ": " + value + ", please wait...");
|
||||
|
||||
var body = { name: name, value: value };
|
||||
|
||||
$.post(urlBase + name, body, function(data) {
|
||||
if (data.name != null) {
|
||||
$("#status").html("Set " + name + ": " + data.name);
|
||||
} else {
|
||||
$("#status").html("Set " + name + ": " + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function delayPostValue(name, value) {
|
||||
clearTimeout(postValueTimer);
|
||||
postValueTimer = setTimeout(function() {
|
||||
postValue(name, value);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function postColor(name, value) {
|
||||
$("#status").html("Setting " + name + ": " + value.r + "," + value.g + "," + value.b + ", please wait...");
|
||||
|
||||
var body = { name: name, r: value.r, g: value.g, b: value.b };
|
||||
|
||||
$.post(urlBase + name + "?r=" + value.r + "&g=" + value.g + "&b=" + value.b, body, function(data) {
|
||||
$("#status").html("Set " + name + ": " + data);
|
||||
})
|
||||
.fail(function(textStatus, errorThrown) { $("#status").html("Fail: " + textStatus + " " + errorThrown); });
|
||||
}
|
||||
|
||||
function delayPostColor(name, value) {
|
||||
clearTimeout(postColorTimer);
|
||||
postColorTimer = setTimeout(function() {
|
||||
postColor(name, value);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function componentToHex(c) {
|
||||
var hex = c.toString(16);
|
||||
return hex.length == 1 ? "0" + hex : hex;
|
||||
}
|
||||
|
||||
function rgbToHex(r, g, b) {
|
||||
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
|
||||
}
|
||||
|
||||
function rgbToComponents(rgb) {
|
||||
var components = {};
|
||||
|
||||
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||
components.r = parseInt(rgb[1]);
|
||||
components.g = parseInt(rgb[2]);
|
||||
components.b = parseInt(rgb[3]);
|
||||
|
||||
return components;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Tree v2 by Evil Genius Labs</title>
|
||||
|
||||
<!-- request CSS from internet CDN -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
|
||||
<!-- request CSS from the ESP8266 web server -->
|
||||
<!-- <link rel="stylesheet" href="css/bootstrap.min.css"> -->
|
||||
|
||||
<link rel="stylesheet" href="css/simple.css">
|
||||
|
||||
<link rel="icon" href="images/atom196.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="container" class="container">
|
||||
|
||||
<div style="margin: 5px;">
|
||||
<button type="button" class="btn btn-default" id="btnOn">On</button>
|
||||
<button type="button" class="btn btn-default" id="btnOff">Off</button>
|
||||
</div>
|
||||
|
||||
<div id="patternGrid" class="grid-pattern"></div>
|
||||
|
||||
<div id="colorButtonsRow" class="grid-color"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="templates" style="display: none">
|
||||
|
||||
<div id="colorButtonTemplate" class="grid-item-color"></div>
|
||||
|
||||
<button id="patternButtonTemplate" class="grid-item-pattern btn btn-default"></button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- request js from internet CDN -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.min.js" integrity="sha256-A4JwlcDvqO4JXpvEtvWY1RH8JAEMu5W21wP8GUXLUNs=" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- request js from the ESP8266 web server -->
|
||||
<!-- <script src="js/jquery-3.1.1.min.js"></script> -->
|
||||
<!-- <script src="js/bootstrap.min.js"></script> -->
|
||||
|
||||
<script src="js/simple.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
Loading…
Reference in New Issue