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
125
FSBrowser.h
Normal file
125
FSBrowser.h
Normal file
@ -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);
|
||||
}
|
||||
|
130
Field.h
Normal file
130
Field.h
Normal file
@ -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 += "]";
|
||||
*/
|
110
Fields.h
Normal file
110
Fields.h
Normal file
@ -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);
|
390
TwinkleFOX.h
Normal file
390
TwinkleFOX.h
Normal file
@ -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();
|
||||
}
|
118
Twinkles.h
Normal file
118
Twinkles.h
Normal file
@ -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();
|
||||
}
|
||||
|
6
data/css/bootstrap.min.css
vendored
Normal file
6
data/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
data/css/jquery.minicolors.min.css
vendored
Normal file
1
data/css/jquery.minicolors.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
18
data/css/simple.css
Normal file
18
data/css/simple.css
Normal file
@ -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
3
data/edit.htm
Normal file
3
data/edit.htm
Normal file
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>
|
||||
|
462
data/js/app.js
Normal file
462
data/js/app.js
Normal file
@ -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;
|
||||
}
|
7
data/js/bootstrap.min.js
vendored
Normal file
7
data/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
data/js/jquery-3.1.1.min.js
vendored
Normal file
4
data/js/jquery-3.1.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
data/js/jquery.minicolors.min.js
vendored
Normal file
11
data/js/jquery.minicolors.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
data/js/r-websocket.min.js
vendored
Normal file
1
data/js/r-websocket.min.js
vendored
Normal file
@ -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
239
data/js/simple.js
Normal file
239
data/js/simple.js
Normal file
@ -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;
|
||||
}
|
58
data/simple.htm
Normal file
58
data/simple.htm
Normal file
@ -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
BIN
web-app-simple-thumb.png
Normal file
BIN
web-app-simple-thumb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
web-app-simple.png
Normal file
BIN
web-app-simple.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
BIN
web-app-thumb.png
Normal file
BIN
web-app-thumb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
web-app.png
Normal file
BIN
web-app.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
Loading…
Reference in New Issue
Block a user