Merge branch 'master' into dev

Minor tweaks.
This commit is contained in:
Blaz Kristan 2021-07-03 21:27:06 +02:00
commit 3066a142b8
26 changed files with 2678 additions and 2559 deletions

View File

@ -2,6 +2,25 @@
### Builds after release 0.12.0
#### Build 2107021
- Added WebSockets support to UI
#### Build 2107020
- Send websockets on every state change
- Improved Aurora effect
#### Build 2107011
- Added MQTT button feedback option (PR #2011)
#### Build 2107010
- Added JSON IR codes (PR #1941)
- Adjusted the width of WiFi and LED settings input fields
- Fixed a minor visual issue with slider trail not reaching thumb on low values
#### Build 2106302
- Fixed settings page broken by using "%" in input fields

View File

@ -185,18 +185,18 @@ upload_speed = 115200
# ------------------------------------------------------------------------------
lib_compat_mode = strict
lib_deps =
fastled/FastLED @ 3.3.2
NeoPixelBus @ ^2.6.0
fastled/FastLED @ 3.4.0
NeoPixelBus @ 2.6.4
ESPAsyncTCP @ 1.2.0
ESPAsyncUDP
AsyncTCP @ 1.0.3
IRremoteESP8266 @ 2.7.3
IRremoteESP8266 @ 2.7.18
https://github.com/lorol/LITTLEFS.git
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.2
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
#TFT_eSPI
#For use SSD1306 OLED display uncomment following
U8g2@~2.27.2
U8g2@~2.28.8
#For Dallas sensor uncomment following 2 lines
OneWire@~2.3.5
; milesburton/DallasTemperature@^3.9.0

View File

@ -193,16 +193,16 @@ public:
*/
void setup()
{
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (!pinManager.allocatePin(PIRsensorPin,false)) {
PIRsensorPin = -1; // allocation failed
enabled = false;
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
} else {
// PIR Sensor mode INPUT_PULLUP
pinMode(PIRsensorPin, INPUT_PULLUP);
if (enabled) {
if (enabled) {
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin,false)) {
// PIR Sensor mode INPUT_PULLUP
pinMode(PIRsensorPin, INPUT_PULLUP);
sensorPinState = digitalRead(PIRsensorPin);
} else {
if (PIRsensorPin >= 0) DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
PIRsensorPin = -1; // allocation failed
enabled = false;
}
}
initDone = true;
@ -221,8 +221,8 @@ public:
*/
void loop()
{
// only check sensors 10x/s
if (millis() - lastLoop < 100 || strip.isUpdating()) return;
// only check sensors 4x/s
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
lastLoop = millis();
if (!updatePIRsensorState()) {

View File

@ -107,19 +107,20 @@ class UsermodTemperature : public Usermod {
void setup() {
int retries = 10;
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (!pinManager.allocatePin(temperaturePin,false)) {
temperaturePin = -1; // allocation failed
enabled = false;
DEBUG_PRINTLN(F("Temperature pin allocation failed."));
} else {
if (enabled) {
// config says we are enabled
if (enabled) {
// config says we are enabled
DEBUG_PRINTLN(F("Allocating temperature pin..."));
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin)) {
oneWire = new OneWire(temperaturePin);
if (!oneWire->reset())
enabled = false; // resetting 1-Wire bus yielded an error
else
while ((enabled=findSensor()) && retries--) delay(25); // try to find sensor
} else {
if (temperaturePin >= 0) DEBUG_PRINTLN(F("Temperature pin allocation failed."));
temperaturePin = -1; // allocation failed
enabled = false;
}
}
initDone = true;

View File

@ -133,6 +133,7 @@ class FourLineDisplayUsermod : public Usermod {
if (type == NONE) return;
if (!pinManager.allocatePin(sclPin)) { sclPin = -1; type = NONE; return;}
if (!pinManager.allocatePin(sdaPin)) { pinManager.deallocatePin(sclPin); sclPin = sdaPin = -1; type = NONE; return; }
DEBUG_PRINTLN(F("Allocating display."));
switch (type) {
case SSD1306:
#ifdef ESP8266
@ -184,12 +185,19 @@ class FourLineDisplayUsermod : public Usermod {
type = NONE;
return;
}
(static_cast<U8X8*>(u8x8))->begin();
initDone = true;
if (u8x8 != nullptr) {
DEBUG_PRINTLN(F("Starting display."));
(static_cast<U8X8*>(u8x8))->begin();
} else {
DEBUG_PRINTLN(F("Display init failed."));
type = NONE;
return;
}
setFlipMode(flip);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
setPowerSave(0);
drawString(0, 0, "Loading...");
initDone = true;
}
// gets called every time WiFi is (re-)connected. Initialize own network
@ -648,6 +656,7 @@ class FourLineDisplayUsermod : public Usermod {
type = newType;
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
if (sclPin!=newScl || sdaPin!=newSda || type!=newType) {
if (type != NONE) delete (static_cast<U8X8*>(u8x8));
@ -665,7 +674,6 @@ class FourLineDisplayUsermod : public Usermod {
setContrast(contrast);
setFlipMode(flip);
if (needsRedraw && !wakeDisplay()) redraw(true);
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;

View File

@ -3926,7 +3926,6 @@ uint16_t WS2812FX::mode_tv_simulator(void) {
*/
//CONFIG
#define BACKLIGHT 5
#define W_MAX_COUNT 20 //Number of simultaneous waves
#define W_MAX_SPEED 6 //Higher number, higher speed
#define W_WIDTH_FACTOR 6 //Higher number, smaller waves
@ -4051,9 +4050,13 @@ uint16_t WS2812FX::mode_aurora(void) {
}
}
uint8_t backlight = 1; //dimmer backlight if less active colors
if (SEGCOLOR(0)) backlight++;
if (SEGCOLOR(1)) backlight++;
if (SEGCOLOR(2)) backlight++;
//Loop through LEDs to determine color
for(int i = 0; i < SEGLEN; i++) {
CRGB mixedRgb = CRGB(BACKLIGHT, BACKLIGHT, BACKLIGHT);
CRGB mixedRgb = CRGB(backlight, backlight, backlight);
//For each LED we must check each wave if it is "active" at this position.
//If there are multiple waves active on a LED we multiply their values.
@ -4065,7 +4068,7 @@ uint16_t WS2812FX::mode_aurora(void) {
}
}
setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2], BACKLIGHT);
setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2]);
}
return FRAMETIME;

View File

@ -319,6 +319,27 @@ class WS2812FX {
vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED
return vLength;
}
uint8_t differs(Segment& b) {
uint8_t d = 0;
if (start != b.start) d |= SEG_DIFFERS_BOUNDS;
if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
if (offset != b.offset) d |= SEG_DIFFERS_GSO;
if (grouping != b.grouping) d |= SEG_DIFFERS_GSO;
if (spacing != b.spacing) d |= SEG_DIFFERS_GSO;
if (opacity != b.opacity) d |= SEG_DIFFERS_BRI;
if (mode != b.mode) d |= SEG_DIFFERS_FX;
if (speed != b.speed) d |= SEG_DIFFERS_FX;
if (intensity != b.intensity) d |= SEG_DIFFERS_FX;
if (palette != b.palette) d |= SEG_DIFFERS_FX;
if ((options & 0b00101111) != (b.options & 0b00101111)) d |= SEG_DIFFERS_OPT;
for (uint8_t i = 0; i < NUM_COLORS; i++)
{
if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL;
}
return d;
}
} segment;
// segment runtime parameters
@ -619,7 +640,6 @@ class WS2812FX {
gammaCorrectBri = false,
gammaCorrectCol = true,
applyToAllSelected = true,
segmentsAreIdentical(Segment* a, Segment* b),
setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p),
// return true if the strip is being sent pixel updates
isUpdating(void);

View File

@ -1018,23 +1018,6 @@ uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8
return crgb_to_col(fastled_col);
}
//@returns `true` if color, mode, speed, intensity and palette match
bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b)
{
//if (a->start != b->start) return false;
//if (a->stop != b->stop) return false;
for (uint8_t i = 0; i < NUM_COLORS; i++)
{
if (a->colors[i] != b->colors[i]) return false;
}
if (a->mode != b->mode) return false;
if (a->speed != b->speed) return false;
if (a->intensity != b->intensity) return false;
if (a->palette != b->palette) return false;
//if (a->getOption(SEG_OPTION_REVERSED) != b->getOption(SEG_OPTION_REVERSED)) return false;
return true;
}
//load custom mapping table from JSON file
void WS2812FX::deserializeMap(uint8_t n) {

View File

@ -19,7 +19,7 @@ void shortPressAction(uint8_t b)
}
// publish MQTT message
if (WLED_MQTT_CONNECTED) {
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "short");
@ -74,7 +74,7 @@ void handleSwitch(uint8_t b)
}
// publish MQTT message
if (WLED_MQTT_CONNECTED) {
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b);
else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
@ -96,7 +96,7 @@ void handleAnalog(uint8_t b)
if (buttonType[b] == BTN_TYPE_ANALOG_INVERTED) aRead = 255 - aRead;
// remove noise & reduce frequency of UI updates (3*13mV)
// remove noise & reduce frequency of UI updates
aRead &= 0xFC;
if (oldRead[b] == aRead) return; // no change in reading
@ -199,7 +199,7 @@ void handleButton()
else _setRandomColor(false,true);
// publish MQTT message
if (WLED_MQTT_CONNECTED) {
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "long");
@ -227,7 +227,7 @@ void handleButton()
applyPreset(macroDoublePress[b]);
// publish MQTT message
if (WLED_MQTT_CONNECTED) {
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "double");

View File

@ -121,7 +121,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (lC > ledCount) ledCount = lC; // fix incorrect total length (honour analog setup)
// read multiple button configuration
JsonArray hw_btn_ins = hw[F("btn")]["ins"];
JsonObject btn_obj = hw["btn"];
JsonArray hw_btn_ins = btn_obj[F("ins")];
if (!hw_btn_ins.isNull()) {
uint8_t s = 0;
for (JsonObject btn : hw_btn_ins) {
@ -133,7 +134,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
} else {
btnPin[s] = -1;
}
JsonArray hw_btn_ins_0_macros = btn[F("macros")];
JsonArray hw_btn_ins_0_macros = btn["macros"];
CJSON(macroButton[s], hw_btn_ins_0_macros[0]);
CJSON(macroLongPress[s],hw_btn_ins_0_macros[1]);
CJSON(macroDoublePress[s], hw_btn_ins_0_macros[2]);
@ -158,7 +159,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
macroDoublePress[s] = 0;
}
}
CJSON(touchThreshold,hw[F("btn")][F("tt")]);
CJSON(touchThreshold,btn_obj[F("tt")]);
CJSON(buttonPublishMqtt,btn_obj["mqtt"]);
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
if (hw_ir_pin > -2) {
@ -190,8 +192,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(briMultiplier, light[F("scale-bri")]);
CJSON(strip.paletteBlend, light[F("pal-mode")]);
float light_gc_bri = light[F("gc")]["bri"];
float light_gc_col = light[F("gc")]["col"]; // 2.8
float light_gc_bri = light["gc"]["bri"];
float light_gc_col = light["gc"]["col"]; // 2.8
if (light_gc_bri > 1.5) strip.gammaCorrectBri = true;
else if (light_gc_bri > 0.5) strip.gammaCorrectBri = false;
if (light_gc_col > 1.5) strip.gammaCorrectCol = true;
@ -210,7 +212,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (nightlightDelayMinsDefault != prev) nightlightDelayMins = nightlightDelayMinsDefault;
CJSON(nightlightTargetBri, light_nl[F("tbri")]);
CJSON(macroNl, light_nl[F("macro")]);
CJSON(macroNl, light_nl["macro"]);
JsonObject def = doc[F("def")];
CJSON(bootPreset, def[F("ps")]);
@ -234,10 +236,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
prev = notifyDirectDefault;
CJSON(notifyDirectDefault, if_sync_send[F("dir")]);
if (notifyDirectDefault != prev) notifyDirect = notifyDirectDefault;
CJSON(notifyButton, if_sync_send[F("btn")]);
CJSON(notifyAlexa, if_sync_send[F("va")]);
CJSON(notifyHue, if_sync_send[F("hue")]);
CJSON(notifyMacro, if_sync_send[F("macro")]);
CJSON(notifyButton, if_sync_send["btn"]);
CJSON(notifyAlexa, if_sync_send["va"]);
CJSON(notifyHue, if_sync_send["hue"]);
CJSON(notifyMacro, if_sync_send["macro"]);
CJSON(notifyTwice, if_sync_send[F("twice")]);
JsonObject if_nodes = interfaces["nodes"];
@ -261,10 +263,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false
CJSON(arlsOffset, if_live[F("offset")]); // 0
CJSON(alexaEnabled, interfaces[F("va")][F("alexa")]); // false
CJSON(alexaEnabled, interfaces["va"][F("alexa")]); // false
CJSON(macroAlexaOn, interfaces[F("va")][F("macros")][0]);
CJSON(macroAlexaOff, interfaces[F("va")][F("macros")][1]);
CJSON(macroAlexaOn, interfaces["va"]["macros"][0]);
CJSON(macroAlexaOff, interfaces["va"]["macros"][1]);
#ifndef WLED_DISABLE_BLYNK
const char* apikey = interfaces["blynk"][F("token")] | "Hidden";
@ -291,7 +293,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
#endif
#ifndef WLED_DISABLE_HUESYNC
JsonObject if_hue = interfaces[F("hue")];
JsonObject if_hue = interfaces["hue"];
CJSON(huePollingEnabled, if_hue["en"]);
CJSON(huePollLightId, if_hue["id"]);
tdd = if_hue[F("iv")] | -1;
@ -339,7 +341,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(countdownHour, cntdwn_goal[3]);
CJSON(countdownMin, cntdwn_goal[4]);
CJSON(countdownSec, cntdwn_goal[5]);
CJSON(macroCountdown, cntdwn[F("macro")]);
CJSON(macroCountdown, cntdwn["macro"]);
setCountdown();
JsonArray timers = tm["ins"];
@ -349,7 +351,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (it<8 && timer[F("hour")]==255) it=8; // hour==255 -> sunrise/sunset
CJSON(timerHours[it], timer[F("hour")]);
CJSON(timerMinutes[it], timer["min"]);
CJSON(timerMacro[it], timer[F("macro")]);
CJSON(timerMacro[it], timer["macro"]);
byte dowPrev = timerWeekday[it];
//note: act is currently only 0 or 1.
@ -538,6 +540,7 @@ void serializeConfig() {
hw_btn_ins_0_macros.add(macroDoublePress[i]);
}
hw_btn[F("tt")] = touchThreshold;
hw_btn["mqtt"] = buttonPublishMqtt;
JsonObject hw_ir = hw.createNestedObject("ir");
hw_ir["pin"] = irPin;
@ -567,7 +570,7 @@ void serializeConfig() {
light_nl[F("mode")] = nightlightMode;
light_nl["dur"] = nightlightDelayMinsDefault;
light_nl[F("tbri")] = nightlightTargetBri;
light_nl[F("macro")] = macroNl;
light_nl["macro"] = macroNl;
JsonObject def = doc.createNestedObject("def");
def[F("ps")] = bootPreset;
@ -587,10 +590,10 @@ void serializeConfig() {
JsonObject if_sync_send = if_sync.createNestedObject("send");
if_sync_send[F("dir")] = notifyDirect;
if_sync_send[F("btn")] = notifyButton;
if_sync_send[F("va")] = notifyAlexa;
if_sync_send[F("hue")] = notifyHue;
if_sync_send[F("macro")] = notifyMacro;
if_sync_send["btn"] = notifyButton;
if_sync_send["va"] = notifyAlexa;
if_sync_send["hue"] = notifyHue;
if_sync_send["macro"] = notifyMacro;
if_sync_send[F("twice")] = notifyTwice;
JsonObject if_nodes = interfaces.createNestedObject("nodes");
@ -682,7 +685,7 @@ void serializeConfig() {
JsonArray goal = cntdwn.createNestedArray(F("goal"));
goal.add(countdownYear); goal.add(countdownMonth); goal.add(countdownDay);
goal.add(countdownHour); goal.add(countdownMin); goal.add(countdownSec);
cntdwn[F("macro")] = macroCountdown;
cntdwn["macro"] = macroCountdown;
JsonArray timers_ins = timers.createNestedArray("ins");
@ -692,7 +695,7 @@ void serializeConfig() {
timers_ins0["en"] = (timerWeekday[i] & 0x01);
timers_ins0[F("hour")] = timerHours[i];
timers_ins0["min"] = timerMinutes[i];
timers_ins0[F("macro")] = timerMacro[i];
timers_ins0["macro"] = timerMacro[i];
timers_ins0[F("dow")] = timerWeekday[i] >> 1;
}
@ -752,7 +755,7 @@ bool deserializeConfigSec() {
#endif
#ifndef WLED_DISABLE_HUESYNC
getStringFromJson(hueApiKey, interfaces[F("hue")][F("key")], 47);
getStringFromJson(hueApiKey, interfaces["hue"][F("key")], 47);
#endif
JsonObject ota = doc["ota"];

View File

@ -77,6 +77,7 @@
#define NOTIFIER_CALL_MODE_PRESET_CYCLE 8
#define NOTIFIER_CALL_MODE_BLYNK 9
#define NOTIFIER_CALL_MODE_ALEXA 10
#define NOTIFIER_CALL_MODE_WS_SEND 11 //special call mode, not for notifier, updates websocket only
//RGB to RGBW conversion mode
#define RGBW_MODE_MANUAL_ONLY 0 //No automatic white channel calculation. Manual white channel slider
@ -195,6 +196,14 @@
#define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed
#define SEG_OPTION_TRANSITIONAL 7
//Segment differs return byte
#define SEG_DIFFERS_BRI 0x01
#define SEG_DIFFERS_OPT 0x02
#define SEG_DIFFERS_COL 0x04
#define SEG_DIFFERS_FX 0x08
#define SEG_DIFFERS_BOUNDS 0x10
#define SEG_DIFFERS_GSO 0x20
//Playlist option byte
#define PL_OPTION_SHUFFLE 0x01

View File

@ -140,6 +140,10 @@ button {
padding: 0;
vertical-align: middle;
}
.segt TD.h {
font-size: 13px;
padding: 2px 0 0;
}
.keytd {
text-align: left;
@ -573,6 +577,8 @@ input[type=range]::-moz-range-thumb {
transition-duration: 0.5s;
-webkit-backface-visibility: hidden;
-webkit-transform:translate3d(0,0,0);
overflow: clip;
text-overflow: clip;
}
.btn-s {
@ -595,9 +601,11 @@ input[type=range]::-moz-range-thumb {
}
.btn-xs, .btn-pl-del, .btn-pl-add {
width: 42px;
height: 42px;
}
.btn-pl-del, .btn-pl-add {
margin: 0;
white-space: nowrap;
}
#qcs-w {
margin-top: 10px;
@ -975,13 +983,13 @@ input[type=number]::-webkit-outer-spin-button {
z-index: 1;
}
#selectPalette .lstI.selected {
top: 80px;
bottom: 0;
#pallist .lstI.selected {
top: 27px;
bottom: -11px;
}
#selectPalette .lstI.sticky {
top: 40px;
#pallist .lstI.sticky {
top: -11px;
}
.lstI.sticky {

View File

@ -102,11 +102,11 @@
<p class="labels"><i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i> Color palette</p>
<div class="il">
<div class="staytop fnd">
<input type="text" class="fnd" placeholder="Search" oninput="search(this,'selectPalette')" onfocus="search(this)" />
<input type="text" class="fnd" placeholder="Search" oninput="search(this,'pallist')" onfocus="search(this)" />
<span onclick="clean(this)" class="icons">&#xe38f;</span>
<div class="icons"><svg xmlns='http://www.w3.org/2000/svg' class='fndIcn'><circle cx='8' cy='8' r='6' /><line x1='12' y1='12' x2='24' y2='12' transform='rotate(45,12,12)' /></svg></div>
</div>
<div id="selectPalette" class="list">
<div id="pallist" class="list">
<div class="lstI" data-id="0">
<label class="radio schkl">
&nbsp;

View File

@ -7,7 +7,7 @@ var selColors;
var expanded = [false];
var powered = [true];
var nlDur = 60, nlTar = 0;
var nlFade = false;
var nlMode = false;
var selectedFx = 0;
var selectedPal = 0;
var csel = 0;
@ -16,19 +16,18 @@ var lastUpdate = 0;
var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0;
var pcMode = false, pcModeA = false, lastw = 0;
var tr = 7;
var pNum = 0;
var d = document;
const ranges = RangeTouch.setup('input[type="range"]', {});
var palettesData;
var pJson = {}, eJson = {}, lJson = {};
var pN = "", pI = 0;
var pN = "", pI = 0, pNum = 0;
var pmt = 1, pmtLS = 0, pmtLast = 0;
var lastinfo = {};
var ws, noWS = false;
var cfg = {
theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, labels:true, pcmbot:false, pid:true, seglen:false}
};
var myWS, noWS = false;
var cpick = new iro.ColorPicker("#picker", {
width: 260,
@ -257,16 +256,6 @@ function onLoad()
sl.addEventListener('touchstart', toggleBubble);
sl.addEventListener('touchend', toggleBubble);
}
// Create UI update WS handler
myWS = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws');
// myWS.onopen = function () {
// myWS.send("{'v':true}");
// }
myWS.onmessage = function(event) {
var json = JSON.parse(event.data);
if (handleJson(json.state)) updateUI(true);
}
}
function updateTablinks(tabI)
@ -557,6 +546,7 @@ function loadInfo(callback=null)
return res.json();
})
.then(json => {
clearErrorToast();
lastinfo = json;
var name = json.name;
gId('namelabel').innerHTML = name;
@ -569,8 +559,11 @@ function loadInfo(callback=null)
syncTglRecv = json.str;
maxSeg = json.leds.maxseg;
pmt = json.fs.pmt;
gId('buttonNodes').style.display = showNodes() ? "block":"none";
showNodes();
populateInfo(json);
// Create UI update WS handler
if (!ws && json.ws > -1) setTimeout(makeWS,1000);
reqsLegal = true;
if (callback) callback();
})
.catch(function (error) {
@ -813,10 +806,10 @@ function populatePalettes()
"name": "Default",
});
var paletteHtml = "";
var html = "";
for (let i = 0; i < palettes.length; i++) {
let previewCss = genPalPrevCss(palettes[i].id);
paletteHtml += generateListItemHtml(
html += generateListItemHtml(
'palette',
palettes[i].id,
palettes[i].name,
@ -825,12 +818,12 @@ function populatePalettes()
);
}
gId('selectPalette').innerHTML=paletteHtml;
gId('pallist').innerHTML=html;
}
function redrawPalPrev()
{
let palettes = d.querySelectorAll('#selectPalette .lstI');
let palettes = d.querySelectorAll('#pallist .lstI');
for (let i = 0; i < palettes.length; i++) {
let id = palettes[i].dataset.id;
let lstPrev = palettes[i].querySelector('.lstIprev');
@ -899,10 +892,10 @@ function genPalPrevCss(id)
function generateListItemHtml(listName, id, name, clickAction, extraHtml = '')
{
return `<div class="lstI${id==0?' sticky':''}" data-id="${id}">
return `<div class="lstI${id==0?' sticky':''}" data-id="${id}" onClick="${clickAction}()">
<label class="radio schkl">
&nbsp;
<input type="radio" value="${id}" name="${listName}" onChange="${clickAction}()">
<input type="radio" value="${id}" name="${listName}">
<span class="radiomark schk"></span>
</label>
<div class="lstIcontent" onClick="${clickAction}(${id})">
@ -918,8 +911,9 @@ function updateTrail(e, slidercol)
{
if (e==null) return;
var max = e.hasAttribute('max') ? e.attributes.max.value : 255;
var progress = e.value * 100 / max;
progress = parseInt(progress);
var perc = e.value * 100 / max;
perc = parseInt(perc);
if (perc < 50) perc += 2;
var scol;
switch (slidercol) {
case 1: scol = "#f00"; break;
@ -927,7 +921,7 @@ function updateTrail(e, slidercol)
case 3: scol = "#00f"; break;
default: scol = "var(--c-f)";
}
var val = `linear-gradient(90deg, ${scol} ${progress}%, var(--c-4) ${progress}%)`;
var val = `linear-gradient(90deg, ${scol} ${perc}%, var(--c-4) ${perc}%)`;
e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val;
var bubble = e.parentNode.parentNode.getElementsByTagName('output')[0];
if (bubble) bubble.innerHTML = e.value;
@ -993,12 +987,12 @@ function updatePA(scrollto=false)
function updateUI(scrollto=false)
{
noWS = (!myWS || myWS.readyState === WebSocket.CLOSED);
noWS = (!ws || ws.readyState === WebSocket.CLOSED);
gId('buttonPower').className = (isOn) ? "active":"";
gId('buttonNl').className = (nlA) ? "active":"";
gId('buttonSync').className = (syncSend) ? "active":"";
gId('buttonNodes').style.display = showNodes() ? "block":"none";
showNodes();
updateSelectedPalette(scrollto);
updateSelectedFx(scrollto);
@ -1016,7 +1010,7 @@ function updateUI(scrollto=false)
function updateSelectedPalette(scrollto=false)
{
var parent = gId('selectPalette');
var parent = gId('pallist');
var selPaletteInput = parent.querySelector(`input[name="palette"][value="${selectedPal}"]`);
if (selPaletteInput) selPaletteInput.checked = true;
@ -1030,7 +1024,6 @@ function updateSelectedPalette(scrollto=false)
function updateSelectedFx(scrollto=false)
{
var parent = gId('fxlist');
var selEffectInput = parent.querySelector(`input[name="fx"][value="${selectedFx}"]`);
if (selEffectInput) selEffectInput.checked = true;
@ -1055,7 +1048,34 @@ function cmpP(a, b)
return a[1].n.localeCompare(b[1].n,undefined, {numeric: true});
}
function handleJson(s)
function makeWS() {
if (ws) return;
ws = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws');
ws.onmessage = function(event) {
clearTimeout(jsonTimeout);
jsonTimeout = null;
clearErrorToast();
gId('connind').style.backgroundColor = "#079";
var json = JSON.parse(event.data);
var info = json.info;
if (info) {
lastinfo = info;
showNodes();
if (isInfo) {
populateInfo(info);
}
}
var s = json.state ? json.state : json;
displayRover(info, s);
readState(s);
};
ws.onclose = function(event) {
gId('connind').style.backgroundColor = "#831";
ws = null;
}
}
function readState(s,command=false)
{
if (!s) return false;
@ -1076,8 +1096,12 @@ function handleJson(s)
if(s.seg[i].sel) {selc = ind; break;} ind++;
}
var i=s.seg[selc];
if (!i) return false; // no segments!
if (!i) {
showToast('No Segments!', true);
updateUI();
return;
}
selColors = i.col;
var cd = gId('csl').children;
for (let e = cd.length-1; e >= 0; e--)
@ -1104,21 +1128,37 @@ function handleJson(s)
gId('sliderSpeed').value = i.sx;
gId('sliderIntensity').value = i.ix;
if (s.error && s.error != 0) {
var errstr = "";
switch (s.error) {
case 10:
errstr = "Could not mount filesystem!";
break;
case 11:
errstr = "Not enough space to save preset!";
break;
case 12:
errstr = "Preset not found.";
break;
case 19:
errstr = "A filesystem error has occured.";
break;
}
showToast('Error ' + s.error + ": " + errstr, true);
}
selectedPal = i.pal;
selectedFx = i.fx;
//if (!gId('fxlist').querySelector(`input[name="fx"][value="${i.fx}"]`)) location.reload(); //effect list is gone (e.g. if restoring tab). Reload.
displayRover(lastinfo, s);
clearErrorToast();
return true;
updateUI(true);
}
var jsonTimeout;
var reqsLegal = false;
function requestJson(command, rinfo = true, verbose = true, callback = null)
{
gId('connind').style.backgroundColor = "#a90";
if (command && !reqsLegal) return; //stop post requests from chrome onchange event on page restore
lastUpdate = new Date();
if (!jsonTimeout) jsonTimeout = setTimeout(showErrorToast, 3000);
var req = null;
@ -1131,6 +1171,12 @@ function requestJson(command, rinfo = true, verbose = true, callback = null)
command.time = Math.floor(Date.now() / 1000);
req = JSON.stringify(command);
}
if ((command || rinfo) && ws && ws.readyState === WebSocket.OPEN) {
ws.send(req?req:'{"v":true}');
return;
}
fetch(url, {
method: type,
headers: {
@ -1153,24 +1199,8 @@ function requestJson(command, rinfo = true, verbose = true, callback = null)
return;
}
var s = json.state ? json.state : json;
if (!handleJson(s)) {
showToast('No Segments!', true);
updateUI(false);
if (callback) callback();
return;
}
if (s.error && s.error != 0) {
var errstr = "";
switch (s.error) {
case 10: errstr = "Could not mount filesystem!"; break;
case 11: errstr = "Not enough space to save preset!"; break;
case 12: errstr = "The requested preset does not exist."; break;
case 19: errstr = "A filesystem error has occured."; break;
}
showToast('Error ' + s.error + ": " + errstr, true);
}
updateUI(true);
readState(s);
reqsLegal = true;
if (callback) callback();
})
.catch(function (error) {
@ -1191,8 +1221,12 @@ function togglePower()
function toggleNl()
{
nlA = !nlA;
if (nlA) showToast(`Timer active. Your light will turn ${nlTar > 0 ? "on":"off"} ${nlFade ? "over":"after"} ${nlDur} minutes.`);
else showToast('Timer deactivated.');
if (nlA)
{
showToast(`Timer active. Your light will turn ${nlTar > 0 ? "on":"off"} ${nlMode ? "over":"after"} ${nlDur} minutes.`);
} else {
showToast('Timer deactivated.');
}
var obj = {"nl": {"on": nlA}};
requestJson(obj, false);
}
@ -1291,7 +1325,7 @@ function makePlSel(arr) {
function refreshPlE(p) {
var plEDiv = gId(`ple${p}`);
if (!plEDiv) return;
var content = "";
var content = "<div class=\"c\">Playlist entries</div>";
for (var i = 0; i < plJson[p].ps.length; i++) {
content += makePlEntry(p,i);
}
@ -1408,7 +1442,7 @@ ${plSelContent}
<div class="c">Save to ID <input class="noslide" id="p${i}id" type="number" oninput="checkUsed(${i})" max=250 min=1 value=${(i>0)?i:getLowestUnusedP()}></div>
<div class="c">
<button class="btn btn-i btn-p" onclick="saveP(${i},${pl})"><i class="icons btn-icon">&#xe390;</i>Save</button>
${(i>0)?'<button class="btn btn-i btn-pl-del" id="p'+i+'del" onclick="delP('+i+')"><i class="icons btn-icon">&#xe037;</i>':'<button class="btn btn-p" onclick="resetPUtil()">Cancel'}</button>
${(i>0)?'<button class="btn btn-i btn-pl-del" id="p'+i+'del" onclick="delP('+i+')"><i class="icons btn-icon">&#xe037;</i>Delete':'<button class="btn btn-p" onclick="resetPUtil()">Cancel'}</button>
</div>
<div class="pwarn ${(i>0)?"bp":""} c" id="p${i}warn"></div>
${(i>0)? ('<div class="h">ID ' +i+ '</div>'):""}`;
@ -1432,6 +1466,11 @@ function makePlEntry(p,i) {
</td>
<td><button class="btn btn-i btn-pl-add" onclick="addPl(${p},${i})"><i class="icons btn-icon">&#xe18a;</i></button></td>
</tr>
<tr>
<td class="h">Duration</td>
<td class="h">Transition</td>
<td class="h">#${i+1}</td>
</tr>
<tr>
<td width="40%"><input class="noslide segn" type="number" placeholder="Duration" max=6553.0 min=0.2 step=0.1 oninput="pleDur(${p},${i},this)" value="${plJson[p].dur[i]/10.0}">s</td>
<td width="40%"><input class="noslide segn" type="number" placeholder="Transition" max=65.0 min=0.0 step=0.1 oninput="pleTr(${p},${i},this)" value="${plJson[p].transition[i]/10.0}">s</td>
@ -1555,14 +1594,15 @@ function setX(ind = null)
function setPalette(paletteId = null)
{
if (paletteId === null) {
paletteId = parseInt(d.querySelector('#selectPalette input[name="palette"]:checked').value);
paletteId = parseInt(d.querySelector('#pallist input[name="palette"]:checked').value);
} else {
d.querySelector(`#selectPalette input[name="palette"][value="${paletteId}`).checked = true;
d.querySelector(`#pallist input[name="palette"][value="${paletteId}`).checked = true;
}
var selElement = d.querySelector('#selectPalette .selected');
if (selElement) selElement.classList.remove('selected');
d.querySelector(`#selectPalette .lstI[data-id="${paletteId}"]`).classList.add('selected');
var selElement = d.querySelector('#pallist .selected');
if (selElement) {
selElement.classList.remove('selected')
}
d.querySelector(`#pallist .lstI[data-id="${paletteId}"]`).classList.add('selected');
var obj = {"seg": {"pal": paletteId}};
requestJson(obj, false, noWS);
}
@ -2026,13 +2066,13 @@ function move(e)
}
function showNodes() {
return (lastinfo.ndc > 0 && (w > 797 || (w > 539 && w < 720)));
gId('buttonNodes').style.display = (lastinfo.ndc > 0 && (w > 797 || (w > 539 && w < 720))) ? "block":"none";
}
function size()
{
w = window.innerWidth;
gId('buttonNodes').style.display = showNodes() ? "block":"none";
showNodes();
var h = gId('top').clientHeight;
sCol('--th', h + "px");
sCol('--bh', gId('bot').clientHeight + "px");

View File

@ -92,7 +92,6 @@
}
//returns mem usage
function getMem(type, len, p0) {
//len = parseInt(len);
if (type < 32) {
if (maxM < 10000 && p0==3) { //8266 DMA uses 5x the mem
if (type > 29) return len*20; //RGBW
@ -154,7 +153,7 @@
}
// gId("ew"+n).onclick = (type > 31 && type < 48) ? (function(){return false}) : (function(){}); // prevent change for analog
// isRGBW |= gId("ew"+n).checked;
isRGBW |= (type == 30 || type == 31 || type == 44 || type == 45); // RGBW checkbox, TYPE_xxxx values from const.h
isRGBW |= (type == 30 || type == 31 || (type > 40 && type < 46 && type != 43)); // RGBW checkbox, TYPE_xxxx values from const.h
gId("dig"+n).style.display = (type > 31 && type < 48) ? "none":"inline"; // hide reverse, skip 1st & count for analog
gId("psd"+n).innerHTML = (type > 31 && type < 48) ? "Index:":"Start:"; // change analog start description
}
@ -240,6 +239,8 @@
function lastEnd(i) {
if (i<1) return 0;
v = parseInt(d.getElementsByName("LS"+(i-1))[0].value) + parseInt(d.getElementsByName("LC"+(i-1))[0].value);
var type = parseInt(d.getElementsByName("LT"+(i-1))[0].value);
if (type > 31 && type < 48) v = 1; //PWM busses
if (isNaN(v)) return 0;
return v;
}
@ -288,9 +289,9 @@ Color Order:
<span id="p3d${i}"></span><input type="number" name="L3${i}" min="0" max="33" class="s" onchange="UI()"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" min="0" max="33" class="s" onchange="UI()"/>
<br>
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" min="0" max="8191" value="${lastEnd(i)}" readonly required />&nbsp;
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" class="l" min="0" max="8191" value="${lastEnd(i)}" readonly required />&nbsp;
<div id="dig${i}" style="display:inline">
Count: <input type="number" name="LC${i}" min="0" max="${maxPB}" value="1" required oninput="UI()" /><br>
Count: <input type="number" name="LC${i}" class="l" min="0" max="${maxPB}" value="1" required oninput="UI()" /><br>
Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
&nbsp;Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"><br>
</div>
@ -358,7 +359,7 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
<br>
Enable automatic brightness limiter: <input type="checkbox" name="ABen" onchange="enABL()" id="able"><br>
<div id="abl">
Maximum Current: <input name="MA" type="number" min="250" max="65000" oninput="UI()" required> mA<br>
Maximum Current: <input name="MA" type="number" class="l" min="250" max="65000" oninput="UI()" required> mA<br>
<div id="ampwarning" style="color: orange; display: none;">
&#9888; Your power supply provides high current.<br>
To improve the safety of your setup,<br>
@ -393,37 +394,36 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
<div id="btns"></div>
Touch threshold: <input type="number" min="0" max="100" name="TT" required><br>
IR GPIO: <input type="number" min="-1" max="40" name="IR" onchange="UI()" class="s"><select name="IT" onchange="UI()">
<option value="0">Remote disabled</option>
<option value="1">24-key RGB</option>
<option value="2">24-key with CT</option>
<option value="3">40-key blue</option>
<option value="4">44-key RGB</option>
<option value="5">21-key RGB</option>
<option value="6">6-key black</option>
<option value="7">9-key red</option>
<option value="8">JSON remote</option>
<option value=0>Remote disabled</option>
<option value=1>24-key RGB</option>
<option value=2>24-key with CT</option>
<option value=3>40-key blue</option>
<option value=4>44-key RGB</option>
<option value=5>21-key RGB</option>
<option value=6>6-key black</option>
<option value=7>9-key red</option>
<option value=8>JSON remote</option>
</select><span style="cursor: pointer;" onclick="off('IR')">&nbsp;&#215;</span><br>
<div id="json" style="display:none;">JSON file: <input type="file" name="data" accept=".json"> <input type="button" value="Upload" onclick="uploadFile('/ir.json');"><br></div>
<div id="toast"></div>
<a href="https://github.com/Aircoookie/WLED/wiki/Infrared-Control" target="_blank">IR info</a><br>
Relay pin: <input type="number" min="-1" max="40" name="RL" onchange="UI()" class="s"> invert <input type="checkbox" name="RM"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#215;</span><br>
Relay pin: <input type="number" min="-1" max="33" name="RL" onchange="UI()" class="s"> invert <input type="checkbox" name="RM"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#215;</span><br>
<hr style="width:260px">
<h3>Defaults</h3>
Turn LEDs on after power up/reset: <input type="checkbox" name="BO"><br>
Default brightness: <input name="CA" type="number" min="0" max="255" required> (0-255)<br><br>
Apply preset <input name="BP" type="number" min="0" max="250" required> at boot (0 uses defaults)
<br>- <i>or</i> -<br>
Set current preset cycle setting as boot default: <input type="checkbox" name="PC"><br><br>
Default brightness: <input name="CA" type="number" class="m" min="0" max="255" required> (0-255)<br><br>
Apply preset <input name="BP" type="number" class="m" min="0" max="250" required> at boot (0 uses defaults)
<br><br>
Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br>
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br>
Brightness factor: <input name="BF" type="number" min="1" max="255" required> %
Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %
<h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br>
Transition Time: <input name="TD" maxlength="5" size="2"> ms<br>
Transition Time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
Enable Palette transitions: <input type="checkbox" name="PF">
<h3>Timed light</h3>
Default Duration: <input name="TL" type="number" min="1" max="255" required> min<br>
Default Target brightness: <input name="TB" type="number" min="0" max="255" required><br>
Default Duration: <input name="TL" type="number" class="m" min="1" max="255" required> min<br>
Default Target brightness: <input name="TB" type="number" class="m" min="0" max="255" required><br>
Mode:
<select name="TW">
<option value="0">Wait and set</option>

View File

@ -87,6 +87,7 @@ Password: <input type="password" name="MQPASS" maxlength="64"><br>
Client ID: <input name="MQCID" maxlength="40"><br>
Device Topic: <input name="MD" maxlength="32"><br>
Group Topic: <input name="MG" maxlength="32"><br>
Publish on button press: <input type="checkbox" name="BM"><br>
<i>Reboot required to apply changes. </i><a href="https://github.com/Aircoookie/WLED/wiki/MQTT" target="_blank">MQTT info</a>
<h3>Philips Hue</h3>
<i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br>

View File

@ -54,16 +54,16 @@ input[type="number"].xl {
width: 85px;
}
input[type="number"].l {
width: 60px;
width: 63px;
}
input[type="number"].m {
width: 55px;
width: 56px;
}
input[type="number"].s {
width: 42px;
width: 49px;
}
input[type="number"].xs {
width: 35px;
width: 42px;
}
select {
margin: 2px;

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -161,7 +161,7 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
hueColormode = 1;
} else //hs mode
{
hueHue = root[F("hue")];
hueHue = root["hue"];
hueSat = root[F("sat")];
hueColormode = 2;
}

View File

@ -9,11 +9,14 @@
void deserializeSegment(JsonObject elem, byte it, byte presetId)
{
byte id = elem["id"] | it;
if (id < strip.getMaxSegments())
{
WS2812FX::Segment& seg = strip.getSegment(id);
uint16_t start = elem[F("start")] | seg.start;
int stop = elem["stop"] | -1;
if (id >= strip.getMaxSegments()) return;
WS2812FX::Segment& seg = strip.getSegment(id);
//WS2812FX::Segment prev;
//prev = seg; //make a backup so we can tell if something changed
uint16_t start = elem[F("start")] | seg.start;
int stop = elem["stop"] | -1;
if (elem["n"]) {
// name field exists
@ -38,64 +41,64 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
}
}
if (stop < 0) {
uint16_t len = elem[F("len")];
stop = (len > 0) ? start + len : seg.stop;
}
uint16_t grp = elem[F("grp")] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing;
strip.setSegment(id, start, stop, grp, spc);
seg.offset = elem[F("of")] | seg.offset;
if (stop > start && seg.offset > stop - start -1) seg.offset = stop - start -1;
if (stop < 0) {
uint16_t len = elem[F("len")];
stop = (len > 0) ? start + len : seg.stop;
}
uint16_t grp = elem[F("grp")] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing;
strip.setSegment(id, start, stop, grp, spc);
seg.offset = elem[F("of")] | seg.offset;
if (stop > start && seg.offset > stop - start -1) seg.offset = stop - start -1;
int segbri = elem["bri"] | -1;
if (segbri == 0) {
seg.setOption(SEG_OPTION_ON, 0, id);
} else if (segbri > 0) {
seg.setOpacity(segbri, id);
seg.setOption(SEG_OPTION_ON, 1, id);
}
int segbri = elem["bri"] | -1;
if (segbri == 0) {
seg.setOption(SEG_OPTION_ON, 0, id);
} else if (segbri > 0) {
seg.setOpacity(segbri, id);
seg.setOption(SEG_OPTION_ON, 1, id);
}
seg.setOption(SEG_OPTION_ON, elem["on"] | seg.getOption(SEG_OPTION_ON), id);
seg.setOption(SEG_OPTION_ON, elem["on"] | seg.getOption(SEG_OPTION_ON), id);
JsonArray colarr = elem["col"];
if (!colarr.isNull())
JsonArray colarr = elem["col"];
if (!colarr.isNull())
{
for (uint8_t i = 0; i < 3; i++)
{
for (uint8_t i = 0; i < 3; i++)
{
int rgbw[] = {0,0,0,0};
bool colValid = false;
if (colarr[i].is<unsigned long>()) {
// unsigned long RGBW (@blazoncek v2 experimental API implementation)
uint32_t colX = colarr[i];
rgbw[0] = (colX >> 16) & 0xFF;
rgbw[1] = (colX >> 8) & 0xFF;
rgbw[2] = (colX ) & 0xFF;
rgbw[3] = (colX >> 24) & 0xFF;
colValid = true;
} else {
JsonArray colX = colarr[i];
if (colX.isNull()) {
byte brgbw[] = {0,0,0,0};
const char* hexCol = colarr[i];
if (hexCol == nullptr) { //Kelvin color temperature (or invalid), e.g 2400
int kelvin = colarr[i] | -1;
if (kelvin < 0) continue;
if (kelvin == 0) seg.setColor(i, 0, id);
if (kelvin > 0) colorKtoRGB(kelvin, brgbw);
colValid = true;
} else { //HEX string, e.g. "FFAA00"
colValid = colorFromHexString(brgbw, hexCol);
}
for (uint8_t c = 0; c < 4; c++) rgbw[c] = brgbw[c];
} else { //Array of ints (RGB or RGBW color), e.g. [255,160,0]
byte sz = colX.size();
if (sz == 0) continue; //do nothing on empty array
byte cp = copyArray(colX, rgbw, 4);
if (cp == 1 && rgbw[0] == 0)
seg.setColor(i, 0, id);
int rgbw[] = {0,0,0,0};
bool colValid = false;
if (colarr[i].is<unsigned long>()) {
// unsigned long RGBW (@blazoncek v2 experimental API implementation)
uint32_t colX = colarr[i];
rgbw[0] = (colX >> 16) & 0xFF;
rgbw[1] = (colX >> 8) & 0xFF;
rgbw[2] = (colX ) & 0xFF;
rgbw[3] = (colX >> 24) & 0xFF;
colValid = true;
} else {
JsonArray colX = colarr[i];
if (colX.isNull()) {
byte brgbw[] = {0,0,0,0};
const char* hexCol = colarr[i];
if (hexCol == nullptr) { //Kelvin color temperature (or invalid), e.g 2400
int kelvin = colarr[i] | -1;
if (kelvin < 0) continue;
if (kelvin == 0) seg.setColor(i, 0, id);
if (kelvin > 0) colorKtoRGB(kelvin, brgbw);
colValid = true;
} else { //HEX string, e.g. "FFAA00"
colValid = colorFromHexString(brgbw, hexCol);
}
for (uint8_t c = 0; c < 4; c++) rgbw[c] = brgbw[c];
} else { //Array of ints (RGB or RGBW color), e.g. [255,160,0]
byte sz = colX.size();
if (sz == 0) continue; //do nothing on empty array
byte cp = copyArray(colX, rgbw, 4);
if (cp == 1 && rgbw[0] == 0)
seg.setColor(i, 0, id);
colValid = true;
}
if (!colValid) continue;
@ -109,90 +112,90 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
}
}
}
// lx parser
#ifdef WLED_ENABLE_LOXONE
int lx = elem[F("lx")] | -1;
if (lx > 0) {
parseLxJson(lx, id, false);
}
int ly = elem[F("ly")] | -1;
if (ly > 0) {
parseLxJson(ly, id, true);
}
#endif
//if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal);
seg.setOption(SEG_OPTION_SELECTED, elem[F("sel")] | seg.getOption(SEG_OPTION_SELECTED));
seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED));
seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR ));
//temporary, strip object gets updated via colorUpdated()
if (id == strip.getMainSegmentId()) {
byte effectPrev = effectCurrent;
effectCurrent = elem[F("fx")] | effectCurrent;
if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
effectSpeed = elem[F("sx")] | effectSpeed;
effectIntensity = elem[F("ix")] | effectIntensity;
effectPalette = elem["pal"] | effectPalette;
} else { //permanent
byte fx = elem[F("fx")] | seg.mode;
if (fx != seg.mode && fx < strip.getModeCount()) {
strip.setMode(id, fx);
if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually
}
seg.speed = elem[F("sx")] | seg.speed;
seg.intensity = elem[F("ix")] | seg.intensity;
seg.palette = elem["pal"] | seg.palette;
}
JsonArray iarr = elem[F("i")]; //set individual LEDs
if (!iarr.isNull()) {
strip.setPixelSegment(id);
//freeze and init to black
if (!seg.getOption(SEG_OPTION_FREEZE)) {
seg.setOption(SEG_OPTION_FREEZE, true);
strip.fill(0);
}
uint16_t start = 0, stop = 0;
byte set = 0; //0 nothing set, 1 start set, 2 range set
for (uint16_t i = 0; i < iarr.size(); i++) {
if(iarr[i].is<JsonInteger>()) {
if (!set) {
start = iarr[i];
set = 1;
} else {
stop = iarr[i];
set = 2;
}
} else {
JsonArray icol = iarr[i];
if (icol.isNull()) break;
byte sz = icol.size();
if (sz == 0 || sz > 4) break;
int rgbw[] = {0,0,0,0};
copyArray(icol, rgbw);
if (set < 2) stop = start + 1;
for (uint16_t i = start; i < stop; i++) {
strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
}
if (!set) start++;
set = 0;
}
}
strip.setPixelSegment(255);
strip.trigger();
} else { //return to regular effect
seg.setOption(SEG_OPTION_FREEZE, false);
}
}
// lx parser
#ifdef WLED_ENABLE_LOXONE
int lx = elem[F("lx")] | -1;
if (lx > 0) {
parseLxJson(lx, id, false);
}
int ly = elem[F("ly")] | -1;
if (ly > 0) {
parseLxJson(ly, id, true);
}
#endif
//if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal);
seg.setOption(SEG_OPTION_SELECTED, elem[F("sel")] | seg.getOption(SEG_OPTION_SELECTED));
seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED));
seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR ));
//temporary, strip object gets updated via colorUpdated()
if (id == strip.getMainSegmentId()) {
byte effectPrev = effectCurrent;
effectCurrent = elem[F("fx")] | effectCurrent;
if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
effectSpeed = elem[F("sx")] | effectSpeed;
effectIntensity = elem[F("ix")] | effectIntensity;
effectPalette = elem["pal"] | effectPalette;
} else { //permanent
byte fx = elem[F("fx")] | seg.mode;
if (fx != seg.mode && fx < strip.getModeCount()) {
strip.setMode(id, fx);
if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually
}
seg.speed = elem[F("sx")] | seg.speed;
seg.intensity = elem[F("ix")] | seg.intensity;
seg.palette = elem["pal"] | seg.palette;
}
JsonArray iarr = elem[F("i")]; //set individual LEDs
if (!iarr.isNull()) {
strip.setPixelSegment(id);
//freeze and init to black
if (!seg.getOption(SEG_OPTION_FREEZE)) {
seg.setOption(SEG_OPTION_FREEZE, true);
strip.fill(0);
}
uint16_t start = 0, stop = 0;
byte set = 0; //0 nothing set, 1 start set, 2 range set
for (uint16_t i = 0; i < iarr.size(); i++) {
if(iarr[i].is<JsonInteger>()) {
if (!set) {
start = iarr[i];
set = 1;
} else {
stop = iarr[i];
set = 2;
}
} else {
JsonArray icol = iarr[i];
if (icol.isNull()) break;
byte sz = icol.size();
if (sz == 0 || sz > 4) break;
int rgbw[] = {0,0,0,0};
copyArray(icol, rgbw);
if (set < 2) stop = start + 1;
for (uint16_t i = start; i < stop; i++) {
strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
}
if (!set) start++;
set = 0;
}
}
strip.setPixelSegment(255);
strip.trigger();
} else { //return to regular effect
seg.setOption(SEG_OPTION_FREEZE, false);
}
return; // seg.hasChanged(prev);
}
bool deserializeState(JsonObject root, byte presetId)
@ -337,6 +340,8 @@ bool deserializeState(JsonObject root, byte presetId)
if (!playlist.isNull()) {
loadPlaylist(playlist, presetId);
noNotification = true; //do not notify both for this request and the first playlist entry
} else {
interfaceUpdateCallMode = NOTIFIER_CALL_MODE_WS_SEND;
}
colorUpdated(noNotification ? NOTIFIER_CALL_MODE_NO_NOTIFY : NOTIFIER_CALL_MODE_DIRECT_CHANGE);

View File

@ -115,7 +115,7 @@ void colorUpdated(int callMode)
notify(callMode);
//set flag to update blynk and mqtt
//set flag to update blynk, ws and mqtt
interfaceUpdateCallMode = callMode;
} else {
if (nightlightActive && !nightlightActiveOld &&
@ -180,6 +180,11 @@ void colorUpdated(int callMode)
void updateInterfaces(uint8_t callMode)
{
sendDataWs();
if (callMode == NOTIFIER_CALL_MODE_WS_SEND) {
lastInterfaceUpdate = millis();
return;
}
#ifndef WLED_DISABLE_ALEXA
if (espalexaDevice != nullptr && callMode != NOTIFIER_CALL_MODE_ALEXA) {
espalexaDevice->setValue(bri);

View File

@ -258,6 +258,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strlcpy(mqttClientID, request->arg(F("MQCID")).c_str(), 41);
strlcpy(mqttDeviceTopic, request->arg(F("MD")).c_str(), 33);
strlcpy(mqttGroupTopic, request->arg(F("MG")).c_str(), 33);
buttonPublishMqtt = request->hasArg(F("BM"));
#endif
#ifndef WLED_DISABLE_HUESYNC
@ -550,13 +551,6 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
DEBUG_PRINTLN(req);
strip.applyToAllSelected = false;
//snapshot to check if request changed values later, temporary.
byte prevCol[4] = {col[0], col[1], col[2], col[3]};
byte prevColSec[4] = {colSec[0], colSec[1], colSec[2], colSec[3]};
byte prevEffect = effectCurrent;
byte prevSpeed = effectSpeed;
byte prevIntensity = effectIntensity;
byte prevPalette = effectPalette;
//segment select (sets main segment)
byte prevMain = strip.getMainSegmentId();
@ -616,6 +610,14 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("PL="));
if (pos > 0) applyPreset(getNumVal(&req, pos));
//snapshot to check if request changed values later, temporary.
byte prevCol[4] = {col[0], col[1], col[2], col[3]};
byte prevColSec[4] = {colSec[0], colSec[1], colSec[2], colSec[3]};
byte prevEffect = effectCurrent;
byte prevSpeed = effectSpeed;
byte prevIntensity = effectIntensity;
byte prevPalette = effectPalette;
//set brightness
updateVal(&req, "&A=", &bri);

View File

@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2107021
#define VERSION 2107031
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@ -438,6 +438,7 @@ WLED_GLOBAL byte briLast _INIT(128); // brightness before turned off. U
WLED_GLOBAL byte whiteLast _INIT(128); // white channel before turned off. Used for toggle function
// button
WLED_GLOBAL bool buttonPublishMqtt _INIT(false);
WLED_GLOBAL bool buttonPressedBefore[WLED_MAX_BUTTONS] _INIT({false});
WLED_GLOBAL bool buttonLongPressed[WLED_MAX_BUTTONS] _INIT({false});
WLED_GLOBAL unsigned long buttonPressedTime[WLED_MAX_BUTTONS] _INIT({0});

View File

@ -40,7 +40,8 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
}
verboseResponse = deserializeState(root);
}
if (verboseResponse || millis() - lastInterfaceUpdate < 1900) sendDataWs(client); //update if it takes longer than 100ms until next "broadcast"
//update if it takes longer than 300ms until next "broadcast"
if (verboseResponse && (millis() - lastInterfaceUpdate < 1700 || !interfaceUpdateCallMode)) sendDataWs(client);
}
} else {
//message is comprised of multiple frames or the frame is split into multiple packets

View File

@ -474,6 +474,7 @@ void getSettingsJS(byte subPage, char* dest)
sappends('s',SET_F("MQCID"),mqttClientID);
sappends('s',SET_F("MD"),mqttDeviceTopic);
sappends('s',SET_F("MG"),mqttGroupTopic);
sappend('c',SET_F("BM"),buttonPublishMqtt);
#endif
#ifndef WLED_DISABLE_HUESYNC