Added JSON state API

This commit is contained in:
cschwinne 2019-03-06 01:20:38 +01:00
parent 3f9b37aa7f
commit 66c224c954
6 changed files with 248 additions and 258 deletions

View File

@ -35,7 +35,7 @@
*/
uint16_t WS2812FX::mode_static(void) {
fill(SEGMENT.colors[0]);
return ((SEGMENT.options >> 7) & 0x01) ? 20 : 500; //update faster if in transition
return (SEGMENT.getOption(7)) ? 20 : 500; //update faster if in transition
}

View File

@ -42,7 +42,7 @@
/* each segment uses 37 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
#define MAX_NUM_SEGMENTS 10
#define MAX_NUM_SEGMENTS 1
#define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT _segments[_segment_index]
#define SEGMENT_RUNTIME _segment_runtimes[_segment_index]
@ -174,8 +174,26 @@ class WS2812FX {
uint8_t intensity;
uint8_t palette;
uint8_t mode;
uint8_t options; //bit pattern: msb first: cloning3 cloning2 cloning1 cloning0 tbd tbd reverse selected
uint8_t options; //bit pattern: msb first: transitional tbd tbd tbd tbd paused reverse selected
uint32_t colors[NUM_COLORS];
//member functions
uint32_t color(uint8_t n)
{
return colors[n];
}
void setOption(uint8_t n, bool val)
{
if (val) {
options |= 0x01 << n;
} else
{
options &= ~(0x01 << n);
}
}
bool getOption(uint8_t n)
{
return ((options >> n) & 0x01);
}
} segment;
// segment runtime parameters
@ -185,6 +203,7 @@ class WS2812FX {
uint32_t counter_mode_call;
uint16_t aux_param;
uint16_t aux_param2;
void reset(){next_time = 0; counter_mode_step = 0; counter_mode_call = 0; aux_param = 0; aux_param2 = 0;};
} segment_runtime;
WS2812FX() {
@ -318,10 +337,7 @@ class WS2812FX {
unlockAll(void),
setTransitionMode(bool t),
trigger(void),
setNumSegments(uint8_t n),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint8_t speed, uint8_t intensity, bool reverse),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint8_t speed, uint8_t intensity, bool reverse),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint8_t speed, uint8_t intensity, uint8_t options),
setSegment(uint8_t n, uint16_t start, uint16_t stop),
resetSegments(),
setPixelColor(uint16_t n, uint32_t c),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
@ -340,6 +356,7 @@ class WS2812FX {
getNumSegments(void),
getModeCount(void),
getPaletteCount(void),
getMaxSegments(void),
get_random_wheel_index(uint8_t);
uint32_t
@ -349,8 +366,8 @@ class WS2812FX {
getPixelColor(uint16_t),
getColor(void);
WS2812FX::Segment
getSegment(void);
WS2812FX::Segment&
getSegment(uint8_t n);
WS2812FX::Segment_runtime
getSegmentRuntime(void);
@ -495,144 +512,31 @@ class WS2812FX {
uint8_t _num_segments = 1;
segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 21 bytes per element
// start, stop, speed, intensity, palette, mode, options, color[]
{ 0, 7, DEFAULT_SPEED, 128, 0, FX_MODE_STATIC, NO_OPTIONS, {DEFAULT_COLOR}}
{ 0, 7, DEFAULT_SPEED, 128, 0, DEFAULT_MODE, NO_OPTIONS, {DEFAULT_COLOR}}
};
segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 16 bytes per element
};
const char JSON_mode_names[] PROGMEM = R"=====({"effects":[
"Solid",
"Blink",
"Breathe",
"Wipe",
"Wipe Random",
"Random Colors",
"Sweep",
"Dynamic",
"Colorloop",
"Rainbow",
"Scan",
"Dual Scan",
"Fade",
"Chase",
"Chase Rainbow",
"Running",
"Saw",
"Twinkle",
"Dissolve",
"Dissolve Rnd",
"Sparkle",
"Dark Sparkle",
"Sparkle+",
"Strobe",
"Strobe Rainbow",
"Mega Strobe",
"Blink Rainbow",
"Android",
"Chase",
"Chase Random",
"Chase Rainbow",
"Chase Flash",
"Chase Flash Rnd",
"Rainbow Runner",
"Colorful",
"Traffic Light",
"Sweep Random",
"Running 2",
"Red & Blue",
"Stream",
"Scanner",
"Lighthouse",
"Fireworks",
"Rain",
"Merry Christmas",
"Fire Flicker",
"Gradient",
"Loading",
"In Out",
"In In",
"Out Out",
"Out In",
"Circus",
"Halloween",
"Tri Chase",
"Tri Wipe",
"Tri Fade",
"Lightning",
"ICU",
"Multi Comet",
"Dual Scanner",
"Stream 2",
"Oscillate",
"Pride 2015",
"Juggle",
"Palette",
"Fire 2012",
"Colorwaves",
"BPM",
"Fill Noise",
"Noise 1",
"Noise 2",
"Noise 3",
"Noise 4",
"Colortwinkle",
"Lake",
"Meteor",
"Smooth Meteor",
"Railway",
"Ripple"
]})=====";
//10 names per line
const char JSON_mode_names[] PROGMEM = R"=====([
"Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow",
"Scan","Dual Scan","Fade","Chase","Chase Rainbow","Running","Saw","Twinkle","Dissolve","Dissolve Rnd",
"Sparkle","Dark Sparkle","Sparkle+","Strobe","Strobe Rainbow","Mega Strobe","Blink Rainbow","Android","Chase","Chase Random",
"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Red & Blue","Stream",
"Scanner","Lighthouse","Fireworks","Rain","Merry Christmas","Fire Flicker","Gradient","Loading","In Out","In In",
"Out Out","Out In","Circus","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet",
"Dual Scanner","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","BPM","Fill Noise","Noise 1",
"Noise 2","Noise 3","Noise 4","Colortwinkle","Lake","Meteor","Smooth Meteor","Railway","Ripple"
])=====";
const char JSON_palette_names[] PROGMEM = R"=====({"palettes":[
"Default",
"Random Cycle",
"Primary Color",
"Based on Primary",
"Set Colors",
"Based on Set",
"Party",
"Cloud",
"Lava",
"Ocean",
"Forest",
"Rainbow",
"Rainbow Bands",
"Sunset",
"Rivendell",
"Breeze",
"Red & Blue",
"Yellowout",
"Analogous",
"Splash",
"Pastel",
"Sunset 2",
"Beech",
"Vintage",
"Departure",
"Landscape",
"Beach",
"Sherbet",
"Hult",
"Hult 64",
"Drywet",
"Jul",
"Grintage",
"Rewhi",
"Tertiary",
"Fire",
"Icefire",
"Cyane",
"Light Pink",
"Autumn",
"Magenta",
"Magred",
"Yelmag",
"Yelblu",
"Orange & Teal",
"Tiamat",
"April Night"
]})=====";
const char JSON_palette_names[] PROGMEM = R"=====([
"Default","Random Cycle","Primary Color","Based on Primary","Set Colors","Based on Set","Party","Cloud","Lava","Ocean",
"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash",
"Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64",
"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn",
"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night"
])=====";
#endif

View File

@ -336,8 +336,8 @@ uint8_t WS2812FX::getNumSegments(void) {
return _num_segments;
}
void WS2812FX::setNumSegments(uint8_t n) {
_num_segments = n;
uint8_t WS2812FX::getMaxSegments(void) {
return MAX_NUM_SEGMENTS;
}
uint32_t WS2812FX::getColor(void) {
@ -379,8 +379,9 @@ uint32_t WS2812FX::getPixelColor(uint16_t i)
return ( (lColor.W << 24) | (r << 16) | (g << 8) | (b) );
}
WS2812FX::Segment WS2812FX::getSegment(void) {
return _segments[0];
WS2812FX::Segment& WS2812FX::getSegment(uint8_t id) {
if (id >= MAX_NUM_SEGMENTS) return _segments[0];
return _segments[id];
}
WS2812FX::Segment_runtime WS2812FX::getSegmentRuntime(void) {
@ -391,29 +392,14 @@ WS2812FX::Segment* WS2812FX::getSegments(void) {
return _segments;
}
void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint8_t speed, uint8_t intensity, bool reverse) {
uint32_t colors[] = {color, 0, 0};
setSegment(n, start, stop, mode, colors, speed, intensity, reverse);
}
void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint8_t speed, uint8_t intensity, bool reverse) {
setSegment(n, start, stop, mode, colors, speed, intensity, (uint8_t)(reverse ? REVERSE : NO_OPTIONS));
}
void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint8_t speed, uint8_t intensity, uint8_t options) {
if(n < (sizeof(_segments) / sizeof(_segments[0]))) {
if(n + 1 > _num_segments) _num_segments = n + 1;
_segments[n].start = start;
_segments[n].stop = stop;
_segments[n].mode = mode;
_segments[n].speed = speed;
_segments[n].intensity = intensity;
_segments[n].options = options;
for(uint8_t i=0; i<NUM_COLORS; i++) {
_segments[n].colors[i] = colors[i];
}
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2) {
if (n >= MAX_NUM_SEGMENTS) return;
Segment& seg = _segments[n];
if (seg.start == i1 && seg.stop == i2) return;
if (i1 < _length) seg.start = i1;
seg.stop = i2;
if (i2 > _length) seg.stop = _length;
_segment_runtimes[n].reset();
}
void WS2812FX::resetSegments() {
@ -421,7 +407,11 @@ void WS2812FX::resetSegments() {
memset(_segment_runtimes, 0, sizeof(_segment_runtimes));
_segment_index = 0;
_num_segments = 1;
setSegment(0, 0, 7, FX_MODE_STATIC, (const uint32_t[]){DEFAULT_COLOR, 0, 0}, DEFAULT_SPEED, 128, NO_OPTIONS);
_segments[0].mode = DEFAULT_MODE;
_segments[0].colors[0] = DEFAULT_COLOR;
_segments[0].start = 0;
_segments[0].speed = DEFAULT_SPEED;
_segments[0].stop = _length;
}
void WS2812FX::setIndividual(uint16_t i, uint32_t col)
@ -483,13 +473,8 @@ void WS2812FX::unlockAll()
void WS2812FX::setTransitionMode(bool t)
{
if (t) {
SEGMENT.options |= 0x01 << 7;
} else
{
SEGMENT.options &= ~(0x01 << 7);
return;
}
SEGMENT.setOption(7,t);
if (!t) return;
unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
if (SEGMENT.mode == FX_MODE_STATIC && SEGMENT_RUNTIME.next_time > waitMax) SEGMENT_RUNTIME.next_time = waitMax;
}

View File

@ -89,7 +89,7 @@
//version code in format yymmddb (b = daily build)
#define VERSION 1903051
#define VERSION 1903055
char versionString[] = "0.8.4-dev";
@ -467,7 +467,6 @@ const byte gamma8[] = {
//function prototypes
void serveMessage(AsyncWebServerRequest*,uint16_t,String,String,byte);
//turns all LEDs off and restarts ESP
void reset()
{

View File

@ -64,25 +64,17 @@ void initServer()
doReboot = true;
});
server.on("/json/state", HTTP_GET, [](AsyncWebServerRequest *request){
serveJsonState(request);
});
server.on("/json", HTTP_GET, [](AsyncWebServerRequest *request){
serveJson(request);
});
server.on("/json/effects", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "application/json", JSON_mode_names);
});
server.on("/json/palettes", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "application/json", JSON_palette_names);
});
server.on("/json/info", HTTP_GET, [](AsyncWebServerRequest *request){
serveJsonInfo(request);
});
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest *request){
request->send(500, "application/json", "{\"error\":\"Not implemented\"}");
});
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request, JsonVariant &json) {
JsonObject& root = json.as<JsonObject>();
if (!root.success()){request->send(500, "application/json", "{\"error\":\"Parsing failed\"}"); return;}
deserializeState(root);
request->send(200, "application/json", "{\"success\":true}");
});
server.addHandler(handler);
//*******DEPRECATED*******
server.on("/version", HTTP_GET, [](AsyncWebServerRequest *request){
@ -96,10 +88,6 @@ void initServer()
server.on("/freeheap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", (String)ESP.getFreeHeap());
});
server.on("/build", HTTP_GET, [](AsyncWebServerRequest *request){
serveJsonInfo(request);
});
server.on("/power", HTTP_GET, [](AsyncWebServerRequest *request){
String val = "";
@ -312,7 +300,7 @@ String settingsProcessor(const String& var)
void serveSettings(AsyncWebServerRequest* request)
{
byte subPage = 0;
String url = request->url();
const String& url = request->url();
if (url.indexOf("sett") >= 0)
{
if (url.indexOf("wifi") > 0) subPage = 1;

View File

@ -2,36 +2,114 @@
* JSON API (De)serialization
*/
void serveJsonState(AsyncWebServerRequest* request)
void deserializeState(JsonObject& root)
{
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& doc = response->getRoot();
doc["on"] = (bri > 0);
doc["bri"] = briLast;
doc["transition"] = transitionDelay/100; //in 100ms
bool on = root["on"] | (bri > 0);
if (!on != !bri) toggleOnOff();
JsonObject& nl = doc.createNestedObject("nl");
briLast = root["bri"] | briLast;
if (bri > 0) bri = briLast;
if (root.containsKey("transition"))
{
transitionDelay = root["transition"];
transitionDelay *= 100;
}
JsonObject& nl = root["nl"];
nightlightActive = nl["on"] | nightlightActive;
nightlightDelayMins = nl["dur"] | nightlightDelayMins;
nightlightFade = nl["fade"] | nightlightFade;
nightlightTargetBri = nl["tbri"] | nightlightTargetBri;
JsonObject& udpn = root["udpn"];
notifyDirect = udpn["send"] | notifyDirect;
receiveNotifications = udpn["recv"] | receiveNotifications;
bool noNotification = udpn["nn"]; //send no notification just for this request
int it = 0;
JsonArray& segs = root["seg"];
for (JsonObject& elem : segs)
{
byte id = elem["id"] | it;
if (id < strip.getMaxSegments())
{
WS2812FX::Segment& seg = strip.getSegment(id);
uint16_t start = elem["start"] | seg.start;
int stop = elem["stop"] | -1;
if (stop < 0) {
uint16_t len = elem["len"];
stop = (len > 0) ? start + len : seg.stop;
}
strip.setSegment(id, start, stop);
JsonArray& colarr = elem["col"];
if (colarr.success())
{
for (uint8_t i = 0; i < 3; i++)
{
JsonArray& colX = colarr[i];
if (!colX.success()) break;
byte sz = colX.size();
if (sz > 0 && sz < 5)
{
int rgbw[] = {0,0,0,0};
memset(rgbw, 0, 4);
byte cp = colX.copyTo(rgbw);
seg.colors[i] = ((rgbw[3] << 24) | ((rgbw[0]&0xFF) << 16) | ((rgbw[1]&0xFF) << 8) | ((rgbw[2]&0xFF)));
if (cp == 1 && rgbw[0] == 0) seg.colors[i] = 0;
//temporary
if (i == 0) {col[0] = rgbw[0]; col[1] = rgbw[1]; col[2] = rgbw[2]; col[3] = rgbw[3];}
if (i == 1) {colSec[0] = rgbw[0]; colSec[1] = rgbw[1]; colSec[2] = rgbw[2]; colSec[3] = rgbw[3];}
}
}
}
byte fx = elem["fx"] | seg.mode;
if (fx != seg.mode && fx < strip.getModeCount()) strip.setMode(fx);
seg.speed = elem["sx"] | seg.speed;
seg.intensity = elem["ix"] | seg.intensity;
byte pal = elem["pal"] | seg.palette;
if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal);
seg.setOption(0, elem["sel"] | seg.getOption(0));
seg.setOption(1, elem["rev"] | seg.getOption(1));
//int cln = seg_0["cln"];
//temporary
effectCurrent = seg.mode;
effectSpeed = seg.speed;
effectIntensity = seg.intensity;
effectPalette = seg.palette;
}
it++;
}
colorUpdated(noNotification ? 5:1);
}
void serializeState(JsonObject& root)
{
root["on"] = (bri > 0);
root["bri"] = briLast;
root["transition"] = transitionDelay/100; //in 100ms
JsonObject& nl = root.createNestedObject("nl");
nl["on"] = nightlightActive;
nl["dur"] = nightlightDelayMins;
nl["fade"] = nightlightFade;
nl["tbri"] = nightlightTargetBri;
JsonObject& udpn = doc.createNestedObject("udpn");
JsonObject& udpn = root.createNestedObject("udpn");
udpn["send"] = notifyDirect;
udpn["recv"] = receiveNotifications;
JsonArray& seg = doc.createNestedArray("seg");
JsonArray& seg = root.createNestedArray("seg");
JsonObject& seg0 = seg.createNestedObject();
serializeSegment(seg0);
response->setLength();
request->send(response);
}
JsonObject& serializeSegment(JsonObject& root)
void serializeSegment(JsonObject& root)
{
WS2812FX::Segment seg = strip.getSegment();
WS2812FX::Segment seg = strip.getSegment(0);
//root["i"] = i;
root["start"] = seg.start;
@ -54,21 +132,17 @@ JsonObject& serializeSegment(JsonObject& root)
root["sx"] = seg.speed;
root["ix"] = seg.intensity;
root["pal"] = seg.palette;
root["sel"] = ((seg.options & 0x01) == 0x01);
root["rev"] = ((seg.options & 0x02) == 0x02);
root["sel"] = seg.getOption(0);
root["rev"] = seg.getOption(1);
root["cln"] = -1;
}
//fill string buffer with build info
void serveJsonInfo(AsyncWebServerRequest* request)
void serializeInfo(JsonObject& root)
{
AsyncJsonResponse* response = new AsyncJsonResponse();
JsonObject& doc = response->getRoot();
root["ver"] = versionString;
root["vid"] = VERSION;
doc["ver"] = versionString;
doc["vid"] = VERSION;
JsonObject& leds = doc.createNestedObject("leds");
JsonObject& leds = root.createNestedObject("leds");
leds["count"] = ledCount;
leds["rgbw"] = useRGBW;
JsonArray& leds_pin = leds.createNestedArray("pin");
@ -76,55 +150,95 @@ void serveJsonInfo(AsyncWebServerRequest* request)
leds["pwr"] = strip.currentMilliamps;
leds["maxpwr"] = strip.ablMilliampsMax;
leds["maxseg"] = 1;
doc["name"] = serverDescription;
doc["udpport"] = udpPort;
doc["live"] = realtimeActive;
doc["fxcount"] = strip.getModeCount();
doc["palcount"] = strip.getPaletteCount();
#ifdef ARDUINO_ARCH_ESP32
doc["arch"] = "esp32";
doc["core"] = ESP.getSdkVersion();
//doc["maxalloc"] = ESP.getMaxAllocHeap();
#else
doc["arch"] = "esp8266";
doc["core"] = ESP.getCoreVersion();
//doc["maxalloc"] = ESP.getMaxFreeBlockSize();
#endif
doc["freeheap"] = ESP.getFreeHeap();
doc["uptime"] = millis()/1000;
leds["maxseg"] = strip.getMaxSegments();
JsonArray& opt = doc.createNestedArray("opt");
root["name"] = serverDescription;
root["udpport"] = udpPort;
root["live"] = realtimeActive;
root["fxcount"] = strip.getModeCount();
root["palcount"] = strip.getPaletteCount();
#ifdef ARDUINO_ARCH_ESP32
root["arch"] = "esp32";
root["core"] = ESP.getSdkVersion();
//root["maxalloc"] = ESP.getMaxAllocHeap();
#else
root["arch"] = "esp8266";
root["core"] = ESP.getCoreVersion();
//root["maxalloc"] = ESP.getMaxFreeBlockSize();
#endif
root["freeheap"] = ESP.getFreeHeap();
root["uptime"] = millis()/1000;
byte os = 0;
#ifdef WLED_DEBUG
os = 0x80;
#endif
#ifndef WLED_DISABLE_ALEXA
opt.add("alexa");
os += 0x40;
#endif
#ifndef WLED_DISABLE_BLYNK
opt.add("blynk");
os += 0x20;
#endif
#ifndef WLED_DISABLE_CRONIXIE
opt.add("cronixie");
#endif
#ifdef WLED_DEBUG
opt.add("debug");
os += 0x10;
#endif
#ifdef USEFS
opt.add("fs");
os += 0x08;
#endif
#ifndef WLED_DISABLE_HUESYNC
opt.add("huesync");
os += 0x04;
#endif
#ifndef WLED_DISABLE_MOBILE_UI
opt.add("mobile-ui");
os += 0x02;
#endif
#ifndef WLED_DISABLE_OTA
opt.add("ota");
os += 0x01;
#endif
root["opt"] = os;
doc["brand"] = "wled";
doc["product"] = "DIY light";
doc["btype"] = "dev";
doc["mac"] = escapedMac;
root["brand"] = "WLED";
root["product"] = "DIY light";
root["btype"] = "dev";
root["mac"] = escapedMac;
}
void serveJson(AsyncWebServerRequest* request)
{
byte subJson = 0;
const String& url = request->url();
if (url.indexOf("state") > 0) subJson = 1;
else if (url.indexOf("info") > 0) subJson = 2;
else if (url.indexOf("eff") > 0) {
request->send_P(200, "application/json", JSON_mode_names);
return;
}
else if (url.indexOf("pal") > 0) {
request->send_P(200, "application/json", JSON_palette_names);
return;
}
else if (url.length() > 6) { //not just /json
request->send( 500, "application/json", "{\"error\":\"Not implemented\"}");
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse();
JsonObject& doc = response->getRoot();
switch (subJson)
{
case 1: //state
serializeState(doc); break;
case 2: //info
serializeInfo(doc); break;
default: //all
JsonObject& state = doc.createNestedObject("state");
serializeState(state);
JsonObject& info = doc.createNestedObject("info");
serializeInfo(info);
doc["effects"] = RawJson(JSON_mode_names);
doc["palettes"] = RawJson(JSON_palette_names);
}
response->setLength();
request->send(response);
}