Multi button support.

This commit is contained in:
Blaz Kristan 2021-05-20 19:54:07 +02:00
parent 1617658bfe
commit 69099fcdd7
13 changed files with 1677 additions and 1376 deletions

View File

@ -6,105 +6,124 @@
#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
void shortPressAction()
void shortPressAction(uint8_t b)
{
if (!macroButton)
if (!macroButton[b])
{
toggleOnOff();
colorUpdated(NOTIFIER_CALL_MODE_BUTTON);
} else {
applyPreset(macroButton);
applyPreset(macroButton[b]);
}
}
bool isButtonPressed()
bool isButtonPressed(uint8_t i)
{
if (btnPin>=0 && digitalRead(btnPin) == LOW) return true;
#ifdef TOUCHPIN
if (touchRead(TOUCHPIN) <= TOUCH_THRESHOLD) return true;
#endif
if (btnPin[i]<0) return false;
switch (buttonType[i]) {
case BTN_TYPE_NONE:
case BTN_TYPE_RESERVED:
break;
case BTN_TYPE_PUSH:
case BTN_TYPE_SWITCH:
if (digitalRead(btnPin[i]) == LOW) return true;
break;
case BTN_TYPE_PUSH_ACT_HIGH:
case BTN_TYPE_SWITCH_ACT_HIGH:
if (digitalRead(btnPin[i]) == HIGH) return true;
break;
case BTN_TYPE_TOUCH:
#ifdef ARDUINO_ARCH_ESP32
if (touchRead(btnPin[i]) <= touchThreshold) return true;
DEBUG_PRINT(F("Touch value: "));
DEBUG_PRINTLN(touchRead(btnPin[i]));
#endif
break;
}
return false;
}
void handleSwitch()
void handleSwitch(uint8_t b)
{
if (buttonPressedBefore != isButtonPressed()) {
buttonPressedTime = millis();
buttonPressedBefore = !buttonPressedBefore;
if (buttonPressedBefore[b] != isButtonPressed(b)) {
buttonPressedTime[b] = millis();
buttonPressedBefore[b] = !buttonPressedBefore[b];
}
if (buttonLongPressed == buttonPressedBefore) return;
if (buttonLongPressed[b] == buttonPressedBefore[b]) return;
if (millis() - buttonPressedTime > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
if (buttonPressedBefore) { //LOW, falling edge, switch closed
if (macroButton) applyPreset(macroButton);
if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
if (buttonPressedBefore[b]) { //LOW, falling edge, switch closed
if (macroButton[b]) applyPreset(macroButton[b]);
else { //turn on
if (!bri) {toggleOnOff(); colorUpdated(NOTIFIER_CALL_MODE_BUTTON);}
}
} else { //HIGH, rising edge, switch opened
if (macroLongPress) applyPreset(macroLongPress);
if (macroLongPress[b]) applyPreset(macroLongPress[b]);
else { //turn off
if (bri) {toggleOnOff(); colorUpdated(NOTIFIER_CALL_MODE_BUTTON);}
}
}
buttonLongPressed = buttonPressedBefore; //save the last "long term" switch state
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
}
}
void handleButton()
{
if (btnPin<0 || buttonType < BTN_TYPE_PUSH) return;
for (uint8_t b=0; b<WLED_MAX_BUTTONS; b++) {
if (btnPin[b]<0 || !(buttonType[b] > BTN_TYPE_NONE)) continue;
if (buttonType == BTN_TYPE_SWITCH) { //button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NO gpio0)
handleSwitch(); return;
}
if (buttonType[b] == BTN_TYPE_SWITCH) { //button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
handleSwitch(b); continue;
}
//momentary button logic
if (isButtonPressed()) //pressed
{
if (!buttonPressedBefore) buttonPressedTime = millis();
buttonPressedBefore = true;
if (millis() - buttonPressedTime > 600) //long press
//momentary button logic
if (isButtonPressed(b)) //pressed
{
if (!buttonLongPressed)
{
if (macroLongPress) {applyPreset(macroLongPress);}
else _setRandomColor(false,true);
if (!buttonPressedBefore[b]) buttonPressedTime[b] = millis();
buttonPressedBefore[b] = true;
buttonLongPressed = true;
if (millis() - buttonPressedTime[b] > 600) //long press
{
if (!buttonLongPressed[b])
{
if (macroLongPress[b]) {applyPreset(macroLongPress[b]);}
else _setRandomColor(false,true);
buttonLongPressed[b] = true;
}
}
}
}
else if (!isButtonPressed() && buttonPressedBefore) //released
{
long dur = millis() - buttonPressedTime;
if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore = false; return;} //too short "press", debounce
bool doublePress = buttonWaitTime;
buttonWaitTime = 0;
if (dur > 6000) //long press
else if (!isButtonPressed(b) && buttonPressedBefore[b]) //released
{
WLED::instance().initAP(true);
}
else if (!buttonLongPressed) { //short press
if (macroDoublePress)
{
if (doublePress) applyPreset(macroDoublePress);
else buttonWaitTime = millis();
} else shortPressAction();
}
buttonPressedBefore = false;
buttonLongPressed = false;
}
long dur = millis() - buttonPressedTime[b];
if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce
bool doublePress = buttonWaitTime[b];
buttonWaitTime[b] = 0;
if (buttonWaitTime && millis() - buttonWaitTime > 450 && !buttonPressedBefore)
{
buttonWaitTime = 0;
shortPressAction();
if (dur > 6000 && b==0) //long press on button 0
{
WLED::instance().initAP(true);
}
else if (!buttonLongPressed[b]) { //short press
if (macroDoublePress[b])
{
if (doublePress) applyPreset(macroDoublePress[b]);
else buttonWaitTime[b] = millis();
} else shortPressAction(b);
}
buttonPressedBefore[b] = false;
buttonLongPressed[b] = false;
}
if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > 450 && !buttonPressedBefore[b])
{
buttonWaitTime[b] = 0;
shortPressAction(b);
}
}
}
@ -128,7 +147,8 @@ void handleIO()
{
if (!offMode) {
#ifdef ESP8266
//turn off built-in LED if strip is turned off
// turn off built-in LED if strip is turned off
// this will break digital bus so will need to be reinitialised on On
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
#endif

View File

@ -62,7 +62,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonArray ap_ip = ap["ip"];
for (byte i = 0; i < 4; i++) {
apIP[i] = ap_ip;
}*/
}
*/
noWifiSleep = doc[F("wifi")][F("sleep")] | !noWifiSleep; // inverted
noWifiSleep = !noWifiSleep;
@ -89,13 +90,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES) break;
uint8_t pins[5] = {255, 255, 255, 255, 255};
JsonArray pinArr = elm[F("pin")];
JsonArray pinArr = elm["pin"];
if (pinArr.size() == 0) continue;
pins[0] = pinArr[0];
uint8_t i = 0;
for (int p : pinArr) {
pins[i] = p;
i++;
pins[i++] = p;
if (i>4) break;
}
@ -123,22 +123,47 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus
JsonObject hw_btn_ins_0 = hw[F("btn")][F("ins")][0];
CJSON(buttonType, hw_btn_ins_0["type"]);
int hw_btn_pin = hw_btn_ins_0[F("pin")][0] | -2; //-2 = not present in doc, keep current. -1 = disable
if (hw_btn_pin > -2) {
if (pinManager.allocatePin(hw_btn_pin,false)) {
btnPin = hw_btn_pin;
pinMode(btnPin, INPUT_PULLUP);
} else {
btnPin = -1;
// read multiple button configuration
JsonArray hw_btn_ins = hw[F("btn")][F("ins")];
if (!hw_btn_ins.isNull()) {
uint8_t s = 0;
for (JsonObject btn : hw_btn_ins) {
CJSON(buttonType[s], btn["type"]);
int8_t pin = btn[F("pin")][0] | -1;
if (pin > -1) {
if (pinManager.allocatePin(pin,false)) {
btnPin[s] = pin;
pinMode(btnPin[s], INPUT_PULLUP);
} else {
btnPin[s] = -1;
}
}
JsonArray hw_btn_ins_0_macros = btn[F("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]);
if (++s >= WLED_MAX_BUTTONS) break; // max buttons reached
}
// clear remaining buttons
for (; s<WLED_MAX_BUTTONS; s++) {
btnPin[s] = -1;
buttonType[s] = BTN_TYPE_NONE;
macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
}
} else {
// new install/missing configuration (button 0 has defaults)
if (fromFS)
for (uint8_t s=1; s<WLED_MAX_BUTTONS; s++) {
btnPin[s] = -1;
buttonType[s] = BTN_TYPE_NONE;
macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
}
}
JsonArray hw_btn_ins_0_macros = hw_btn_ins_0[F("macros")];
CJSON(macroButton, hw_btn_ins_0_macros[0]);
CJSON(macroLongPress,hw_btn_ins_0_macros[1]);
CJSON(macroDoublePress, hw_btn_ins_0_macros[2]);
CJSON(touchThreshold,hw[F("btn")][F("tt")]);
#ifndef WLED_DISABLE_INFRARED
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
@ -166,7 +191,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
rlyMde = !relay["rev"];
}
//int hw_status_pin = hw[F("status")][F("pin")]; // -1
//int hw_status_pin = hw[F("status")]["pin"]; // -1
JsonObject light = doc[F("light")];
CJSON(briMultiplier, light[F("scale-bri")]);
@ -181,7 +206,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject light_tr = light[F("tr")];
CJSON(fadeTransition, light_tr[F("mode")]);
int tdd = light_tr[F("dur")] | -1;
int tdd = light_tr["dur"] | -1;
if (tdd >= 0) transitionDelayDefault = tdd * 100;
CJSON(strip.paletteFade, light_tr["pal"]);
@ -205,7 +230,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(presetCycleMin, def_cy[F("range")][0]);
CJSON(presetCycleMax, def_cy[F("range")][1]);
tdd = def_cy[F("dur")] | -1;
tdd = def_cy["dur"] | -1;
if (tdd > 0) presetCycleTime = tdd;
JsonObject interfaces = doc["if"];
@ -257,6 +282,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(macroAlexaOn, interfaces[F("va")][F("macros")][0]);
CJSON(macroAlexaOff, interfaces[F("va")][F("macros")][1]);
#ifndef WLED_DISABLE_BLYNK
const char* apikey = interfaces["blynk"][F("token")] | "Hidden";
tdd = strnlen(apikey, 36);
if (tdd > 20 || tdd == 0)
@ -265,7 +291,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject if_blynk = interfaces["blynk"];
getStringFromJson(blynkHost, if_blynk[F("host")], 33);
CJSON(blynkPort, if_blynk["port"]);
#endif
#ifdef WLED_ENABLE_MQTT
JsonObject if_mqtt = interfaces["mqtt"];
CJSON(mqttEnabled, if_mqtt["en"]);
getStringFromJson(mqttServer, if_mqtt[F("broker")], 33);
@ -276,7 +304,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
getStringFromJson(mqttDeviceTopic, if_mqtt[F("topics")][F("device")], 33); // "wled/test"
getStringFromJson(mqttGroupTopic, if_mqtt[F("topics")][F("group")], 33); // ""
#endif
#ifndef WLED_DISABLE_HUESYNC
JsonObject if_hue = interfaces[F("hue")];
CJSON(huePollingEnabled, if_hue["en"]);
CJSON(huePollLightId, if_hue["id"]);
@ -292,6 +322,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
for (byte i = 0; i < 4; i++)
CJSON(hueIP[i], if_hue_ip[i]);
#endif
JsonObject if_ntp = interfaces[F("ntp")];
CJSON(ntpEnabled, if_ntp["en"]);
@ -308,7 +339,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(countdownMode, ol[F("cntdwn")]);
if (prev != overlayDefault) overlayCurrent = overlayDefault;
CJSON(overlayMin, ol[F("min")]);
CJSON(overlayMin, ol["min"]);
CJSON(overlayMax, ol[F("max")]);
CJSON(analogClock12pixel, ol[F("o12pix")]);
CJSON(analogClock5MinuteMarks, ol[F("o5m")]);
@ -333,7 +364,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (it > 9) break;
if (it<8 && timer[F("hour")]==255) it=8; // hour==255 -> sunrise/sunset
CJSON(timerHours[it], timer[F("hour")]);
CJSON(timerMinutes[it], timer[F("min")]);
CJSON(timerMinutes[it], timer["min"]);
CJSON(timerMacro[it], timer[F("macro")]);
byte dowPrev = timerWeekday[it];
@ -379,6 +410,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
#endif
DEBUG_PRINTLN(F("Starting usermod config."));
JsonObject usermods_settings = doc["um"];
if (!usermods_settings.isNull()) usermods.readFromConfig(usermods_settings);
@ -492,27 +524,38 @@ void serializeConfig() {
ins["type"] = bus->getType();
}
// button(s)
JsonObject hw_btn = hw.createNestedObject("btn");
hw_btn["max"] = WLED_MAX_BUTTONS; // just information about max number of buttons (not actually used)
JsonArray hw_btn_ins = hw_btn.createNestedArray("ins");
// button BTNPIN
// there is always at least one button
JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject();
hw_btn_ins_0["type"] = buttonType;
hw_btn_ins_0["type"] = buttonType[0];
JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin");
hw_btn_ins_0_pin.add(btnPin);
hw_btn_ins_0_pin.add(btnPin[0]);
JsonArray hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros");
hw_btn_ins_0_macros.add(macroButton);
hw_btn_ins_0_macros.add(macroLongPress);
hw_btn_ins_0_macros.add(macroDoublePress);
hw_btn_ins_0_macros.add(macroButton[0]);
hw_btn_ins_0_macros.add(macroLongPress[0]);
hw_btn_ins_0_macros.add(macroDoublePress[0]);
// additional buttons
for (uint8_t i=1; i<WLED_MAX_BUTTONS; i++) {
//if (btnPin[i]<0) continue;
hw_btn_ins_0 = hw_btn_ins.createNestedObject();
hw_btn_ins_0["type"] = buttonType[i];
hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin");
hw_btn_ins_0_pin.add(btnPin[i]);
hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros");
hw_btn_ins_0_macros.add(macroButton[i]);
hw_btn_ins_0_macros.add(macroLongPress[i]);
hw_btn_ins_0_macros.add(macroDoublePress[i]);
}
hw_btn[F("tt")] = touchThreshold;
#ifndef WLED_DISABLE_INFRARED
JsonObject hw_ir = hw.createNestedObject("ir");
hw_ir["pin"] = irPin;
hw_ir[F("type")] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
#endif
hw_ir["type"] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
JsonObject hw_relay = hw.createNestedObject(F("relay"));
hw_relay["pin"] = rlyPin;
@ -531,12 +574,12 @@ void serializeConfig() {
JsonObject light_tr = light.createNestedObject("tr");
light_tr[F("mode")] = fadeTransition;
light_tr[F("dur")] = transitionDelayDefault / 100;
light_tr["dur"] = transitionDelayDefault / 100;
light_tr["pal"] = strip.paletteFade;
JsonObject light_nl = light.createNestedObject("nl");
light_nl[F("mode")] = nightlightMode;
light_nl[F("dur")] = nightlightDelayMinsDefault;
light_nl["dur"] = nightlightDelayMinsDefault;
light_nl[F("tbri")] = nightlightTargetBri;
light_nl[F("macro")] = macroNl;
@ -553,7 +596,7 @@ void serializeConfig() {
JsonArray def_cy_range = def_cy.createNestedArray(F("range"));
def_cy_range.add(presetCycleMin);
def_cy_range.add(presetCycleMax);
def_cy[F("dur")] = presetCycleTime;
def_cy["dur"] = presetCycleTime;
}
JsonObject interfaces = doc.createNestedObject("if");
@ -565,7 +608,7 @@ void serializeConfig() {
JsonObject if_sync_recv = if_sync.createNestedObject("recv");
if_sync_recv["bri"] = receiveNotificationBrightness;
if_sync_recv["col"] = receiveNotificationColor;
if_sync_recv[F("fx")] = receiveNotificationEffects;
if_sync_recv["fx"] = receiveNotificationEffects;
JsonObject if_sync_send = if_sync.createNestedObject("send");
if_sync_send[F("dir")] = notifyDirect;
@ -600,11 +643,15 @@ void serializeConfig() {
JsonArray if_va_macros = if_va.createNestedArray("macros");
if_va_macros.add(macroAlexaOn);
if_va_macros.add(macroAlexaOff);
#ifndef WLED_DISABLE_BLYNK
JsonObject if_blynk = interfaces.createNestedObject("blynk");
if_blynk[F("token")] = strlen(blynkApiKey) ? "Hidden":"";
if_blynk[F("host")] = blynkHost;
if_blynk["port"] = blynkPort;
#endif
#ifdef WLED_ENABLE_MQTT
JsonObject if_mqtt = interfaces.createNestedObject("mqtt");
if_mqtt["en"] = mqttEnabled;
if_mqtt[F("broker")] = mqttServer;
@ -616,7 +663,9 @@ void serializeConfig() {
JsonObject if_mqtt_topics = if_mqtt.createNestedObject(F("topics"));
if_mqtt_topics[F("device")] = mqttDeviceTopic;
if_mqtt_topics[F("group")] = mqttGroupTopic;
#endif
#ifndef WLED_DISABLE_HUESYNC
JsonObject if_hue = interfaces.createNestedObject("hue");
if_hue["en"] = huePollingEnabled;
if_hue["id"] = huePollLightId;
@ -631,6 +680,7 @@ void serializeConfig() {
for (byte i = 0; i < 4; i++) {
if_hue_ip.add(hueIP[i]);
}
#endif
JsonObject if_ntp = interfaces.createNestedObject("ntp");
if_ntp["en"] = ntpEnabled;
@ -645,7 +695,7 @@ void serializeConfig() {
ol[F("clock")] = overlayDefault;
ol[F("cntdwn")] = countdownMode;
ol[F("min")] = overlayMin;
ol["min"] = overlayMin;
ol[F("max")] = overlayMax;
ol[F("o12pix")] = analogClock12pixel;
ol[F("o5m")] = analogClock5MinuteMarks;
@ -666,7 +716,7 @@ void serializeConfig() {
JsonObject timers_ins0 = timers_ins.createNestedObject();
timers_ins0["en"] = (timerWeekday[i] & 0x01);
timers_ins0[F("hour")] = timerHours[i];
timers_ins0[F("min")] = timerMinutes[i];
timers_ins0["min"] = timerMinutes[i];
timers_ins0[F("macro")] = timerMacro[i];
timers_ins0[F("dow")] = timerWeekday[i] >> 1;
}
@ -688,7 +738,6 @@ void serializeConfig() {
for (byte i = 0; i < 15; i++)
dmx_fixmap.add(DMXFixtureMap[i]);
#endif
//}
JsonObject usermods_settings = doc.createNestedObject("um");
usermods.addToConfig(usermods_settings);
@ -715,15 +764,21 @@ bool deserializeConfigSec() {
JsonObject interfaces = doc["if"];
#ifndef WLED_DISABLE_BLYNK
const char* apikey = interfaces["blynk"][F("token")] | "Hidden";
int tdd = strnlen(apikey, 36);
if (tdd > 20 || tdd == 0)
getStringFromJson(blynkApiKey, apikey, 36);
#endif
#ifdef WLED_ENABLE_MQTT
JsonObject if_mqtt = interfaces["mqtt"];
getStringFromJson(mqttPass, if_mqtt["psk"], 41);
#endif
#ifndef WLED_DISABLE_HUESYNC
getStringFromJson(hueApiKey, interfaces[F("hue")][F("key")], 47);
#endif
JsonObject ota = doc["ota"];
getStringFromJson(otaPass, ota[F("pwd")], 33);
@ -750,12 +805,18 @@ void serializeConfigSec() {
ap["psk"] = apPass;
JsonObject interfaces = doc.createNestedObject("if");
#ifndef WLED_DISABLE_BLYNK
JsonObject if_blynk = interfaces.createNestedObject("blynk");
if_blynk[F("token")] = blynkApiKey;
#endif
#ifdef WLED_ENABLE_MQTT
JsonObject if_mqtt = interfaces.createNestedObject("mqtt");
if_mqtt["psk"] = mqttPass;
#endif
#ifndef WLED_DISABLE_HUESYNC
JsonObject if_hue = interfaces.createNestedObject("hue");
if_hue[F("key")] = hueApiKey;
#endif
JsonObject ota = doc.createNestedObject("ota");
ota[F("pwd")] = otaPass;

View File

@ -31,37 +31,45 @@
#endif
#endif
#ifndef WLED_MAX_BUTTONS
#ifdef ESP8266
#define WLED_MAX_BUTTONS 2
#else
#define WLED_MAX_BUTTONS 4
#endif
#endif
//Usermod IDs
#define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present
#define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID
#define USERMOD_ID_EXAMPLE 2 //Usermod "usermod_v2_example.h"
#define USERMOD_ID_TEMPERATURE 3 //Usermod "usermod_temperature.h"
#define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h"
#define USERMOD_ID_PIRSWITCH 5 //Usermod "usermod_PIR_sensor_switch.h"
#define USERMOD_ID_IMU 6 //Usermod "usermod_mpu6050_imu.h"
#define USERMOD_ID_FOUR_LINE_DISP 7 //Usermod "usermod_v2_four_line_display.h
#define USERMOD_ID_ROTARY_ENC_UI 8 //Usermod "usermod_v2_rotary_encoder_ui.h"
#define USERMOD_ID_AUTO_SAVE 9 //Usermod "usermod_v2_auto_save.h"
#define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h"
#define USERMOD_ID_MODE_SORT 11 //Usermod "usermod_v2_mode_sort.h"
#define USERMOD_ID_VL53L0X 12 //Usermod "usermod_vl53l0x_gestures.h"
#define USERMOD_ID_MULTI_RELAY 101 //Usermod "usermod_multi_relay.h"
#define USERMOD_ID_ANIMATED_STAIRCASE 102 //Usermod "Animated_Staircase.h"
#define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present
#define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID
#define USERMOD_ID_EXAMPLE 2 //Usermod "usermod_v2_example.h"
#define USERMOD_ID_TEMPERATURE 3 //Usermod "usermod_temperature.h"
#define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h"
#define USERMOD_ID_PIRSWITCH 5 //Usermod "usermod_PIR_sensor_switch.h"
#define USERMOD_ID_IMU 6 //Usermod "usermod_mpu6050_imu.h"
#define USERMOD_ID_FOUR_LINE_DISP 7 //Usermod "usermod_v2_four_line_display.h
#define USERMOD_ID_ROTARY_ENC_UI 8 //Usermod "usermod_v2_rotary_encoder_ui.h"
#define USERMOD_ID_AUTO_SAVE 9 //Usermod "usermod_v2_auto_save.h"
#define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h"
#define USERMOD_ID_MODE_SORT 11 //Usermod "usermod_v2_mode_sort.h"
#define USERMOD_ID_VL53L0X 12 //Usermod "usermod_vl53l0x_gestures.h"
#define USERMOD_ID_MULTI_RELAY 13 //Usermod "usermod_multi_relay.h"
#define USERMOD_ID_ANIMATED_STAIRCASE 14 //Usermod "Animated_Staircase.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
#define AP_BEHAVIOR_NO_CONN 1 //Open when no connection (either after boot or if connection is lost)
#define AP_BEHAVIOR_ALWAYS 2 //Always open
#define AP_BEHAVIOR_BUTTON_ONLY 3 //Only when button pressed for 6 sec
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
#define AP_BEHAVIOR_NO_CONN 1 //Open when no connection (either after boot or if connection is lost)
#define AP_BEHAVIOR_ALWAYS 2 //Always open
#define AP_BEHAVIOR_BUTTON_ONLY 3 //Only when button pressed for 6 sec
//Notifier callMode
#define NOTIFIER_CALL_MODE_INIT 0 //no updates on init, can be used to disable updates
#define NOTIFIER_CALL_MODE_INIT 0 //no updates on init, can be used to disable updates
#define NOTIFIER_CALL_MODE_DIRECT_CHANGE 1
#define NOTIFIER_CALL_MODE_BUTTON 2
#define NOTIFIER_CALL_MODE_NOTIFICATION 3
#define NOTIFIER_CALL_MODE_NIGHTLIGHT 4
#define NOTIFIER_CALL_MODE_NO_NOTIFY 5
#define NOTIFIER_CALL_MODE_FX_CHANGED 6 //no longer used
#define NOTIFIER_CALL_MODE_FX_CHANGED 6 //no longer used
#define NOTIFIER_CALL_MODE_HUE 7
#define NOTIFIER_CALL_MODE_PRESET_CYCLE 8
#define NOTIFIER_CALL_MODE_BLYNK 9
@ -148,9 +156,10 @@
#define BTN_TYPE_NONE 0
#define BTN_TYPE_RESERVED 1
#define BTN_TYPE_PUSH 2
#define BTN_TYPE_PUSH_ACT_HIGH 3 //not implemented
#define BTN_TYPE_PUSH_ACT_HIGH 3
#define BTN_TYPE_SWITCH 4
#define BTN_TYPE_SWITCH_ACT_HIGH 5 //not implemented
#define BTN_TYPE_SWITCH_ACT_HIGH 5
#define BTN_TYPE_TOUCH 6
//Ethernet board types
#define WLED_NUM_ETH_TYPES 5
@ -203,7 +212,7 @@
// maximum number of LEDs - more than 1500 LEDs (or 500 DMA "LEDPIN 3" driven ones) will cause a low memory condition on ESP8266
#ifndef MAX_LEDS
#ifdef ESP8266
#define MAX_LEDS 8192 //rely on memory limit to limit this to 1600 LEDs
#define MAX_LEDS 1664 // can't rely on memory limit to limit this to 1600 LEDs
#else
#define MAX_LEDS 8192
#endif
@ -243,7 +252,7 @@
#ifdef ESP8266
#define JSON_BUFFER_SIZE 9216
#else
#define JSON_BUFFER_SIZE 16384
#define JSON_BUFFER_SIZE 20480
#endif
// Maximum size of node map (list of other WLED instances)

View File

@ -1,356 +1,409 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=500">
<title>LED Settings</title>
<script>
var d=document,laprev=55,maxB=1,maxM=5000,maxPB=4096,bquot=0; //maximum bytes for LED allocation: 5kB for 8266, 32kB for 32
function H()
{
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#led-settings");
}
function B()
{
window.open("/settings","_self");
}
function off(n){
d.getElementsByName(n)[0].value = -1;
}
function bLimits(b,p,m) {
maxB = b; maxM = m; maxPB = p;
}
function trySubmit(event) {
event.preventDefault();
var LCs = d.getElementsByTagName("input");
for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2);
//check for pin conflicts
if (nm=="L0" || nm=="L1" || nm=="RL" || nm=="BT" || nm=="IR" || nm=="AX")
if (LCs[i].value!="" && LCs[i].value!="-1") {
if (LCs[i].value > 5 && LCs[i].value < 12) {alert("Sorry, pins 6-11 can not be used.");LCs[i].focus();return;}
if (d.um_p && d.um_p.some((e)=>e==parseInt(LCs[i].value,10))) {alert("Usermod pin conflict!");LCs[i].focus();return;}
for (j=i+1; j<LCs.length; j++)
{
var n2 = LCs[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="RL" || n2=="BT" || n2=="IR" || n2=="AX")
if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {alert("Pin conflict!");LCs[i].focus();return;}
}
}
}
if (bquot > 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000) msg += " Consider using an ESP32."; alert(msg); return;}
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
if (d.Sf.reportValidity()) d.Sf.submit();
}
function S(){GetV();setABL();}
function enABL()
{
var en = d.getElementById('able').checked;
d.Sf.LA.value = (en) ? laprev:0;
d.getElementById('abl').style.display = (en) ? 'inline':'none';
d.getElementById('psu2').style.display = (en) ? 'inline':'none';
if (d.Sf.LA.value > 0) setABL();
}
function enLA()
{
var val = d.Sf.LAsel.value;
d.Sf.LA.value = val;
d.getElementById('LAdis').style.display = (val == 50) ? 'inline':'none';
UI();
}
function setABL()
{
d.getElementById('able').checked = true;
d.Sf.LAsel.value = 50;
switch (parseInt(d.Sf.LA.value)) {
case 0: d.getElementById('able').checked = false; enABL(); break;
case 30: d.Sf.LAsel.value = 30; break;
case 35: d.Sf.LAsel.value = 35; break;
case 55: d.Sf.LAsel.value = 55; break;
case 255: d.Sf.LAsel.value = 255; break;
default: d.getElementById('LAdis').style.display = 'inline';
}
d.getElementById('m1').innerHTML = maxM;
UI();
}
//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
return len*15;
} else if (maxM >= 10000) //ESP32 RMT uses double buffer?
{
if (type > 29) return len*8; //RGBW
return len*6;
}
if (type > 29) return len*4; //RGBW
return len*3;
}
if (type > 31 && type < 48) return 5;
if (type == 44 || type == 45) return len*4; //RGBW
return len*3;
}
function UI()
{
var isRGBW = false, memu = 0;
d.getElementById('ampwarning').style.display = (d.Sf.MA.value > 7200) ? 'inline':'none';
if (d.Sf.LA.value == 255) laprev = 12;
else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value;
var s = d.getElementsByTagName("select");
for (i=0; i<s.length; i++) {
if (s[i].name.substring(0,2)=="LT") {
n=s[i].name.substring(2);
var type = s[i].value;
d.getElementById("p0d"+n).innerHTML = (type > 49) ? "Data pin:" : (type >41) ? "Pins:" : "Pin:";
d.getElementById("p1d"+n).innerHTML = (type > 49) ? "Clk:" : "";
var LK = d.getElementsByName("L1"+n)[0];
memu += getMem(type, d.getElementsByName("LC"+n)[0].value, d.getElementsByName("L0"+n)[0].value);
for (p=1; p<5; p++) {
var LK = d.getElementsByName("L"+p+n)[0];
if (!LK) continue;
if ((type>49 && p==1) || (type>41 && type < 50 && (p+40 < type))) // TYPE_xxxx values from const.h
{
LK.style.display = "inline";
LK.required = true;
} else {
LK.style.display = "none";
LK.required = false;
LK.value="";
}
}
if (type == 30 || type == 31 || (type > 40 && type < 46 && type != 43)) isRGBW = true;
d.getElementById("dig"+n).style.display = (type > 31 && type < 48) ? "none":"inline";
d.getElementById("psd"+n).innerHTML = (type > 31 && type < 48) ? "Index:":"Start:";
}
}
var myC = d.querySelectorAll('.wc'),
l = myC.length;
for (i = 0; i < l; i++) {
myC[i].style.display = (isRGBW) ? 'inline':'none';
}
if (d.activeElement == d.getElementsByName("LC")[0]) {
var o = d.getElementsByClassName("iST");
var i = o.length;
if (i == 1) d.getElementsByName("LC0")[0].value = d.getElementsByName("LC")[0].value;
}
var LCs = d.getElementsByTagName("input");
var sLC = 0, maxLC = 0;
for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2);
if (nm=="LC" && LCs[i].name != "LC") {var c = parseInt(LCs[i].value,10); if (c) {sLC+=c; if (c>maxLC) maxLC = c;} continue;}
}
d.getElementById('m0').innerHTML = memu;
bquot = memu / maxM * 100;
d.getElementById('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? bquot > 90 ? "red":"orange":"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`;
d.getElementById('ledwarning').style.display = (maxLC > 800 || bquot > 80) ? 'inline':'none';
//TODO add warning "Recommended pins on ESP8266 are 1 and 2 (3 only with low LED count)"
//TODO add overmemory warning
//TODO block disallowed pins 6-11
d.getElementById('wreason').innerHTML = (bquot > 80) ? "than 60%% of max. LED memory" : "800 LEDs per pin";
//var val = Math.ceil((100 + d.Sf.LC.value * laprev)/500)/2;
var val = Math.ceil((100 + sLC * laprev)/500)/2;
val = (val > 5) ? Math.ceil(val) : val;
var s = "";
var is12V = (d.Sf.LAsel.value == 30);
var isWS2815 = (d.Sf.LAsel.value == 255);
if (val < 1.02 && !is12V && !isWS2815)
{
s = "ESP 5V pin with 1A USB supply";
} else
{
s += is12V ? "12V ": isWS2815 ? "WS2815 12V " : "5V ";
s += val;
s += "A supply connected to LEDs";
}
var val2 = Math.ceil((100 + sLC * laprev)/1500)/2;
val2 = (val2 > 5) ? Math.ceil(val2) : val2;
var s2 = "(for most effects, ~";
s2 += val2;
s2 += "A is enough)<br>";
d.getElementById('psu').innerHTML = s;
d.getElementById('psu2').innerHTML = isWS2815 ? "" : s2;
}
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);
if (isNaN(v)) return 0;
return v;
}
function addLEDs(n)
{
if (n>1) {maxB=n; d.getElementById("+").style.display="inline"; return;}
var o = d.getElementsByClassName("iST");
var i = o.length;
if ((n==1 && i>=maxB) || (n==-1 && i==0)) return;
var f = d.getElementById("mLC");
if (n==1) {
var cn = `<div class="iST">
${i>0?'<hr style="width:260px">':''}
${i+1}:
<select name="LT${i}" onchange="UI()">
<option value="22">WS281x</option>
<option value="30">SK6812 RGBW</option>
<option value="31">TM1814</option>
<option value="24">400kHz</option>
<option value="50">WS2801</option>
<option value="51">APA102</option>
<option value="52">LPD8806</option>
<option value="53">P9813</option>
<option value="41">PWM White</option>
<option value="42">PWM WWCW</option>
<option value="43">PWM RGB</option>
<option value="44">PWM RGBW</option>
<option value="45">PWM RGBWC</option>
</select>&nbsp;
Color Order:
<select name="CO${i}">
<option value="0">GRB</option>
<option value="1">RGB</option>
<option value="2">BRG</option>
<option value="3">RBG</option>
<option value="4">BGR</option>
<option value="5">GBR</option>
</select><br>
<span id="p0d${i}">Pin:</span> <input type="number" name="L0${i}" min="0" max="40" required style="width:35px" oninput="UI()"/>
<span id="p1d${i}">Clock:</span> <input type="number" name="L1${i}" min="0" max="40" style="width:35px"/>
<span id="p2d${i}"></span><input type="number" name="L2${i}" min="0" max="40" style="width:35px"/>
<span id="p3d${i}"></span><input type="number" name="L3${i}" min="0" max="40" style="width:35px"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" min="0" max="40" style="width:35px"/>
<br>
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" min="0" max="8191" value="${lastEnd(i)}" 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>
Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}">&nbsp;
</div>
Reverse: <input type="checkbox" name="CV${i}"><br>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
}
if (n==-1) {
o[--i].remove();--i;
}
d.getElementById("+").style.display = (i<maxB-1) ? "inline":"none";
d.getElementById("-").style.display = (i>0) ? "inline":"none";
UI();
}
function GetV()
{
//values injected by server while sending HTML
//d.um_p=[];addLEDs(3);d.Sf.LC.value=250;addLEDs(1);d.Sf.L00.value=2;d.Sf.L10.value=0;d.Sf.LC0.value=250;d.Sf.LT0.value=22;d.Sf.CO0.value=0;d.Sf.LS0.value=0;d.Sf.LS0.checked=0;d.Sf.MA.value=5400;d.Sf.LA.value=55;d.getElementsByClassName("pow")[0].innerHTML="350mA";d.Sf.CA.value=40;d.Sf.AW.value=3;d.Sf.BO.checked=0;d.Sf.BP.value=3;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=0;d.Sf.BF.value=64;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=1;d.Sf.PB.selectedIndex=0;d.Sf.RV.checked=0;d.Sf.SL.checked=0;d.Sf.RL.value=12;d.Sf.RM.checked=0;d.Sf.BT.value=-1;d.Sf.IR.value=-1;d.Sf.AX.value=-1;
}
</script>
<style>
@import url("style.css");
</style>
</head>
<body onload="S()">
<form id="form_s" name="Sf" method="post" onsubmit="trySubmit(event)">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
<h2>LED &amp; Hardware setup</h2>
Total LED count: <input name="LC" type="number" min="1" max="8192" oninput="UI()" required><br>
<i>Recommended power supply for brightest white:</i><br>
<b><span id="psu">?</span></b><br>
<span id="psu2"><br></span>
<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>
<div id="ampwarning" style="color: orange; display: none;">
&#9888; Your power supply provides high current.<br>
To improve the safety of your setup,<br>
please use thick cables,<br>
multiple power injection points and a fuse!<br>
</div>
<i>Automatically limits brightness to stay close to the limit.<br>
Keep at &lt;1A if powering LEDs directly from the ESP 5V pin!<br>
If you are using an external power supply, enter its rating.<br>
(Current estimated usage: <span class="pow">unknown</span>)</i><br><br>
LED voltage (Max. current for a single LED):<br>
<select name="LAsel" onchange="enLA()">
<option value="55" selected>5V default (55mA)</option>
<option value="35">5V efficient (35mA)</option>
<option value="30">12V (30mA)</option>
<option value="255">WS2815 (12mA)</option>
<option value="50">Custom</option>
</select><br>
<span id="LAdis" style="display: none;">Custom max. current per LED: <input name="LA" type="number" min="0" max="255" id="la" oninput="UI()" required> mA<br></span>
<i>Keep at default if you are unsure about your type of LEDs.</i><br>
</div>
<h3>Hardware setup</h3>
<div id="mLC">LED outputs:</div>
<button type="button" id="+" onclick="addLEDs(1)" style="display:none;border-radius:20px;height:36px;">+</button>
<button type="button" id="-" onclick="addLEDs(-1)" style="display:none;border-radius:20px;width:36px;height:36px;">-</button><br>
LED Memory Usage: <span id="m0">0</span> / <span id="m1">?</span> B<br>
<div id="dbar" style="display:inline-block; width: 100px; height: 10px; border-radius: 20px;"></div><br>
<div id="ledwarning" style="color: orange; display: none;">
&#9888; You might run into stability or lag issues.<br>
Use less than <span id="wreason">800 LEDs per pin</span> for the best experience!<br>
</div>
Button pin: <input type="number" min="-1" max="40" name="BT" onchange="UI()"><span style="cursor: pointer;" onclick="off('BT')">&nbsp;&#215;</span><br>
IR pin: <input type="number" min="-1" max="40" name="IR" onchange="UI()"><span style="cursor: pointer;" onclick="off('IR')">&nbsp;&#215;</span><br>
Relay pin: <input type="number" min="-1" max="40" name="RL" onchange="UI()"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#215;</span><br>
Active high <input type="checkbox" name="RM">
<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>
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> %
<h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br>
Transition Time: <input name="TD" maxlength="5" size="2"> 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>
Mode:
<select name="TW">
<option value="0">Wait and set</option>
<option value="1">Fade</option>
<option value="2">Fade Color</option>
<option value="3">Sunrise</option>
</select>
<h3>Advanced</h3>
Palette blending:
<select name="PB">
<option value="0">Linear (wrap if moving)</option>
<option value="1">Linear (always wrap)</option>
<option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option>
</select><br>
<span class="wc">
Auto-calculate white channel from RGB:<br>
<select name="AW">
<option value=0>None</option>
<option value=1>Brighter</option>
<option value=2>Accurate</option>
<option value=3>Dual</option>
<option value=4>Legacy</option>
</select>
<br></span><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=500">
<title>LED Settings</title>
<script>
var d=document,laprev=55,maxB=1,maxM=5000,maxPB=4096,bquot=0; //maximum bytes for LED allocation: 5kB for 8266, 32kB for 32
function H()
{
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#led-settings");
}
function B()
{
window.open("/settings","_self");
}
function gId(n){return d.getElementById(n);}
function off(n){
d.getElementsByName(n)[0].value = -1;
}
function bLimits(b,p,m) {
maxB = b; maxM = m; maxPB = p;
}
function pinsOK() {
var LCs = d.getElementsByTagName("input");
for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2);
//check for pin conflicts
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR")
if (LCs[i].value!="" && LCs[i].value!="-1") {
if (d.um_p && d.um_p.some((e)=>e==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(d.um_p)} can't be used.`);LCs[i].value="";LCs[i].focus();return false;}
else if (LCs[i].value > 5 && LCs[i].value < 12) {alert("Sorry, pins 6-11 can not be used.");LCs[i].value="";LCs[i].focus();return false;}
for (j=i+1; j<LCs.length; j++)
{
var n2 = LCs[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4" || n2=="RL" || n2=="BT" || n2=="IR")
if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {alert(`Pin conflict between ${nm}/${n2}!`);LCs[j].value="";LCs[j].focus();return false;}
}
}
}
return true;
}
function trySubmit(e) {
e.preventDefault();
if (!pinsOK()) {e.stopPropagation();return false;} // Prevent form submission and contact with server
if (bquot > 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);}
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
}
function S(){GetV();setABL();}
function enABL()
{
var en = gId('able').checked;
d.Sf.LA.value = (en) ? laprev:0;
gId('abl').style.display = (en) ? 'inline':'none';
gId('psu2').style.display = (en) ? 'inline':'none';
if (d.Sf.LA.value > 0) setABL();
}
function enLA()
{
var val = d.Sf.LAsel.value;
d.Sf.LA.value = val;
gId('LAdis').style.display = (val == 50) ? 'inline':'none';
UI();
}
function setABL()
{
gId('able').checked = true;
d.Sf.LAsel.value = 50;
switch (parseInt(d.Sf.LA.value)) {
case 0: gId('able').checked = false; enABL(); break;
case 30: d.Sf.LAsel.value = 30; break;
case 35: d.Sf.LAsel.value = 35; break;
case 55: d.Sf.LAsel.value = 55; break;
case 255: d.Sf.LAsel.value = 255; break;
default: gId('LAdis').style.display = 'inline';
}
gId('m1').innerHTML = maxM;
d.getElementsByName("Sf")[0].addEventListener("submit", trySubmit);
UI();
}
//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
return len*15;
} else if (maxM >= 10000) //ESP32 RMT uses double buffer?
{
if (type > 29) return len*8; //RGBW
return len*6;
}
if (type > 29) return len*4; //RGBW
return len*3;
}
if (type > 31 && type < 48) return 5;
if (type == 44 || type == 45) return len*4; //RGBW
return len*3;
}
function UI(change=false)
{
var isRGBW = false, memu = 0;
gId('ampwarning').style.display = (d.Sf.MA.value > 7200) ? 'inline':'none';
if (d.Sf.LA.value == 255) laprev = 12;
else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value;
var s = d.getElementsByTagName("select");
for (i=0; i<s.length; i++) {
if (s[i].name.substring(0,2)=="LT") {
n=s[i].name.substring(2);
var type = parseInt(s[i].value,10);
gId("p0d"+n).innerHTML = (type > 49) ? "Data:" : (type >41) ? "Pins:" : "Pin:";
gId("p1d"+n).innerHTML = (type > 49) ? "Clk:" : "";
var LK = d.getElementsByName("L1"+n)[0];
memu += getMem(type, d.getElementsByName("LC"+n)[0].value, d.getElementsByName("L0"+n)[0].value);
for (p=1; p<5; p++) {
var LK = d.getElementsByName("L"+p+n)[0];
if (!LK) continue;
if ((type>49 && p==1) || (type>41 && type < 50 && (p+40 < type))) // TYPE_xxxx values from const.h
{
LK.style.display = "inline";
LK.required = true;
} else {
LK.style.display = "none";
LK.required = false;
LK.value="";
}
}
if (type == 30 || type == 31 || (type > 40 && type < 46 && type != 43)) isRGBW = true;
gId("dig"+n).style.display = (type > 31 && type < 48) ? "none":"inline";
gId("psd"+n).innerHTML = (type > 31 && type < 48) ? "Index:":"Start:";
}
}
var myC = d.querySelectorAll('.wc'),
l = myC.length;
for (i = 0; i < l; i++) {
myC[i].style.display = (isRGBW) ? 'inline':'none';
}
if (d.activeElement == d.getElementsByName("LC")[0]) {
var o = d.getElementsByClassName("iST");
var i = o.length;
if (i == 1) d.getElementsByName("LC0")[0].value = d.getElementsByName("LC")[0].value;
}
var LCs = d.getElementsByTagName("input");
var sLC = 0, maxLC = 0;
for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2);
if (nm=="LC" && LCs[i].name !== "LC") {
var n=LCs[i].name.substring(2);
var c=parseInt(LCs[i].value,10);
if(gId("ls"+n).readOnly) gId("ls"+n).value=sLC;
if(c){sLC+=c;if(c>maxLC)maxLC=c;}
continue;
}
if (nm=="L0" || nm=="L1") {
var lc=d.getElementsByName("LC"+LCs[i].name.substring(2))[0];
lc.max=maxPB;
}
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR")
if (LCs[i].value!="" && LCs[i].value!="-1") {
var p = [];
if (d.um_p && Array.isArray(d.um_p)) for (k=0;k<d.um_p.length;k++) p.push(d.um_p[k]);
for (j=0; j<LCs.length; j++) {
if (i==j) continue;
var n2 = LCs[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4" || n2=="RL" || n2=="BT" || n2=="IR")
if (LCs[j].value!="" && LCs[j].value!="-1") p.push(parseInt(LCs[j].value,10));
}
if (p.some((e)=>e==parseInt(LCs[i].value,10))) LCs[i].style.color="red"; else LCs[i].style.color="#fff";
}
}
gId('m0').innerHTML = memu;
bquot = memu / maxM * 100;
gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`;
gId('ledwarning').style.display = (sLC > maxPB || maxLC > 800 || bquot > 80) ? 'inline':'none';
gId('ledwarning').style.color = (sLC > maxPB || maxLC > maxPB || bquot > 100) ? 'red':'orange';
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>WARNING: Using over ${maxM}B!</b>)` : "") : "800 LEDs per pin";
//var val = Math.ceil((100 + d.Sf.LC.value * laprev)/500)/2;
var val = Math.ceil((100 + sLC * laprev)/500)/2;
val = (val > 5) ? Math.ceil(val) : val;
var s = "";
var is12V = (d.Sf.LAsel.value == 30);
var isWS2815 = (d.Sf.LAsel.value == 255);
if (val < 1.02 && !is12V && !isWS2815)
{
s = "ESP 5V pin with 1A USB supply";
} else
{
s += is12V ? "12V ": isWS2815 ? "WS2815 12V " : "5V ";
s += val;
s += "A supply connected to LEDs";
}
var val2 = Math.ceil((100 + sLC * laprev)/1500)/2;
val2 = (val2 > 5) ? Math.ceil(val2) : val2;
var s2 = "(for most effects, ~";
s2 += val2;
s2 += "A is enough)<br>";
gId('psu').innerHTML = s;
gId('psu2').innerHTML = isWS2815 ? "" : s2;
}
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);
if (isNaN(v)) return 0;
return v;
}
function addLEDs(n)
{
if (n>1) {maxB=n; gId("+").style.display="inline"; return;}
var o = d.getElementsByClassName("iST");
var i = o.length;
if ((n==1 && i>=maxB) || (n==-1 && i==0)) return;
var f = gId("mLC");
if (n==1) {
// npm run build has trouble minimizing spaces inside string
var cn = `<div class="iST">
${i>0?'<hr style="width:260px">':''}
${i+1}:
<select name="LT${i}" onchange="UI()">
<option value="22">WS281x</option>
<option value="30">SK6812 RGBW</option>
<option value="31">TM1814</option>
<option value="24">400kHz</option>
<option value="50">WS2801</option>
<option value="51">APA102</option>
<option value="52">LPD8806</option>
<option value="53">P9813</option>
<option value="41">PWM White</option>
<option value="42">PWM WWCW</option>
<option value="43">PWM RGB</option>
<option value="44">PWM RGBW</option>
<option value="45">PWM RGBWC</option>
</select>&nbsp;
Color Order:
<select name="CO${i}">
<option value="0">GRB</option>
<option value="1">RGB</option>
<option value="2">BRG</option>
<option value="3">RBG</option>
<option value="4">BGR</option>
<option value="5">GBR</option>
</select><br>
<span id="p0d${i}">Pin:</span> <input type="number" name="L0${i}" min="0" max="40" required style="width:35px" onchange="UI()"/>
<span id="p1d${i}">Clock:</span> <input type="number" name="L1${i}" min="0" max="40" style="width:35px" onchange="UI()"/>
<span id="p2d${i}"></span><input type="number" name="L2${i}" min="0" max="40" style="width:35px" onchange="UI()"/>
<span id="p3d${i}"></span><input type="number" name="L3${i}" min="0" max="40" style="width:35px" onchange="UI()"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" min="0" max="40" style="width:35px" 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)}" 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>
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>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
}
if (n==-1) {
o[--i].remove();--i;
}
gId("+").style.display = (i<maxB-1) ? "inline":"none";
gId("-").style.display = (i>0) ? "inline":"none";
UI();
}
function addBtn(i,p,t) {
var c = gId("btns").innerHTML;
var bt = "BT" + i;
var be = "BE" + i;
c += `Button ${i} pin: <input type="number" min="-1" max="40" name="${bt}" onchange="UI()" value="${p}">`;
c += `<select name="${be}">`
c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`;
c += `<option value="2" ${t==2?"selected":""}>Pushbutton</option>`;
c += `<option value="2" ${t==3?"selected":""}>Push inverted</option>`;
c += `<option value="4" ${t==4?"selected":""}>Switch</option>`;
c += `<option value="5" ${t==4?"selected":""}>Switch inverted</option>`;
c += `<option value="6" ${t==6?"selected":""}>Touch</option>`;
c += `</select>`;
c += `<span style="cursor: pointer;" onclick="off('${bt}')">&nbsp;&#215;</span><br>`;
gId("btns").innerHTML = c;
}
function GetV()
{
//values injected by server while sending HTML
//maxM=5000;maxPB=1536;d.um_p=[1,6,7,8,9,10,11];addLEDs(3);d.Sf.LC.value=250;addLEDs(1);d.Sf.L00.value=2;d.Sf.L10.value=0;d.Sf.LC0.value=250;d.Sf.LT0.value=22;d.Sf.CO0.value=0;d.Sf.LS0.value=0;d.Sf.LS0.checked=0;d.Sf.MA.value=5400;d.Sf.LA.value=55;d.getElementsByClassName("pow")[0].innerHTML="350mA";d.Sf.CA.value=40;d.Sf.AW.value=3;d.Sf.BO.checked=0;d.Sf.BP.value=3;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=0;d.Sf.BF.value=64;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=1;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=12;d.Sf.RM.checked=0;addBtn(0,0,2);addBtn(1,3,4);addBtn(2,-1,0);d.Sf.IR.value=-1;
}
</script>
<style>
@import url("style.css");
</style>
</head>
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
<h2>LED &amp; Hardware setup</h2>
Total LED count: <input name="LC" id="LC" type="number" min="1" max="8192" oninput="UI()" required><br>
<i>Recommended power supply for brightest white:</i><br>
<b><span id="psu">?</span></b><br>
<span id="psu2"><br></span>
<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>
<div id="ampwarning" style="color: orange; display: none;">
&#9888; Your power supply provides high current.<br>
To improve the safety of your setup,<br>
please use thick cables,<br>
multiple power injection points and a fuse!<br>
</div>
<i>Automatically limits brightness to stay close to the limit.<br>
Keep at &lt;1A if powering LEDs directly from the ESP 5V pin!<br>
If you are using an external power supply, enter its rating.<br>
(Current estimated usage: <span class="pow">unknown</span>)</i><br><br>
LED voltage (Max. current for a single LED):<br>
<select name="LAsel" onchange="enLA()">
<option value="55" selected>5V default (55mA)</option>
<option value="35">5V efficient (35mA)</option>
<option value="30">12V (30mA)</option>
<option value="255">WS2815 (12mA)</option>
<option value="50">Custom</option>
</select><br>
<span id="LAdis" style="display: none;">Custom max. current per LED: <input name="LA" type="number" min="0" max="255" id="la" oninput="UI()" required> mA<br></span>
<i>Keep at default if you are unsure about your type of LEDs.</i><br>
</div>
<h3>Hardware setup</h3>
<div id="mLC">LED outputs:</div>
<button type="button" id="+" onclick="addLEDs(1)" style="display:none;border-radius:20px;height:36px;">+</button>
<button type="button" id="-" onclick="addLEDs(-1)" style="display:none;border-radius:20px;width:36px;height:36px;">-</button><br>
LED Memory Usage: <span id="m0">0</span> / <span id="m1">?</span> B<br>
<div id="dbar" style="display:inline-block; width: 100px; height: 10px; border-radius: 20px;"></div><br>
<div id="ledwarning" style="color: orange; display: none;">
&#9888; You might run into stability or lag issues.<br>
Use less than <span id="wreason">800 LEDs per pin</span> for the best experience!<br>
</div><hr style="width:260px">
<div id="btns"></div>
Touch threshold: <input type="number" min="0" max="100" name="TT" required><br>
IR pin: <input type="number" min="-1" max="40" name="IR" onchange="UI()"><select name="IT">
<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>
<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()"> 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>
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> %
<h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br>
Transition Time: <input name="TD" maxlength="5" size="2"> 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>
Mode:
<select name="TW">
<option value="0">Wait and set</option>
<option value="1">Fade</option>
<option value="2">Fade Color</option>
<option value="3">Sunrise</option>
</select>
<h3>Advanced</h3>
Palette blending:
<select name="PB">
<option value="0">Linear (wrap if moving)</option>
<option value="1">Linear (always wrap)</option>
<option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option>
</select><br>
<span class="wc">
Auto-calculate white channel from RGB:<br>
<select name="AW">
<option value=0>None</option>
<option value=1>Brighter</option>
<option value=2>Accurate</option>
<option value=3>Dual</option>
<option value=4>Legacy</option>
</select>
<br></span><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>

View File

@ -15,29 +15,10 @@ function GetV(){var d=document;}
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
<h2>Sync setup</h2>
<h3>Button setup</h3>
Button type:
<select name=BT>
<option value=0>Disabled</option>
<option value=2>Pushbutton</option>
<option value=4>Switch</option>
</select><br>
Infrared remote:
<select name=IR>
<option value=0>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>
</select><br>
<a href="https://github.com/Aircoookie/WLED/wiki/Infrared-Control" target="_blank">IR info</a>
<h3>WLED Broadcast</h3>
UDP Port: <input name="UP" type="number" min="1" max="65535" class="d5" required><br>
2nd Port: <input name="U2" type="number" min="1" max="65535" class="d5" required><br>
Receive <input type="checkbox" name="RB">Brightness, <input type="checkbox" name="RC">Color, and <input type="checkbox" name="RX">Effects<br>
Receive: <input type="checkbox" name="RB">Brightness, <input type="checkbox" name="RC">Color, and <input type="checkbox" name="RX">Effects<br>
Send notifications on direct change: <input type="checkbox" name="SD"><br>
Send notifications on button press or IR: <input type="checkbox" name="SB"><br>
Send Alexa notifications: <input type="checkbox" name="SA"><br>

View File

@ -6,41 +6,41 @@
<title>Time Settings</title>
<script>
var d=document;
function H()
function H()
{
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#time-settings");
}
function B()
{
window.open("/settings","_self");
}
function S()
{
BTa();GetV();Cs();FC();
}
function gId(s)
{
return d.getElementById(s);
}
function Cs()
{
gId("cac").style.display="none";
gId("coc").style.display="block";
gId("ccc").style.display="none";
if (gId("ca").selected)
{
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#time-settings");
gId("cac").style.display="block";
}
function B()
if (gId("cc").selected)
{
window.open("/settings","_self");
gId("coc").style.display="none";
gId("ccc").style.display="block";
}
function S()
if (gId("cn").selected)
{
BTa();GetV();Cs();FC();
}
function gId(s)
{
return d.getElementById(s);
}
function Cs()
{
gId("cac").style.display="none";
gId("coc").style.display="block";
gId("ccc").style.display="none";
if (gId("ca").selected)
{
gId("cac").style.display="block";
}
if (gId("cc").selected)
{
gId("coc").style.display="none";
gId("ccc").style.display="block";
}
if (gId("cn").selected)
{
gId("coc").style.display="none";
}
gId("coc").style.display="none";
}
}
function BTa()
{
var ih="<tr><th>Active</th><th>Hour</th><th>Minute</th><th>Preset</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr>";
@ -75,10 +75,25 @@
gId("W"+i).value=a[i];
}
}
function GetV()
{
//values injected by server while sending HTML
}
function addRow(i,p,l,d) {
var t = gId("macros"); // table
var rCnt = t.rows.length; // get the number of rows.
var tr = t.insertRow(rCnt); // table row.
var td = document.createElement('td'); // TABLE DEFINITION.
td = tr.insertCell(0);
td.innerHTML = `Button ${i}:`;
td = tr.insertCell(1);
td.innerHTML = `<input name="MP${i}" type="number" min="0" max="250" value="${p}" required>`;
td = tr.insertCell(2);
td.innerHTML = `<input name="ML${i}" type="number" min="0" max="250" value="${l}" required>`;
td = tr.insertCell(3);
td.innerHTML = `<input name="MD${i}" type="number" min="0" max="250" value="${d}" required>`;
}
function GetV()
{
//values injected by server while sending HTML
}
</script>
<style>
@import url("style.css");
@ -107,12 +122,12 @@
<option value="10">JST(KST)</option>
<option value="11">AEST/AEDT</option>
<option value="12">NZST/NZDT</option>
<option value="13">North Korea</option>
<option value="14">IST (India)</option>
<option value="15">CA-Saskatchewan</option>
<option value="16">ACST</option>
<option value="17">ACST/ACDT</option>
<option value="18">HST (Hawaii)</option>
<option value="13">North Korea</option>
<option value="14">IST (India)</option>
<option value="15">CA-Saskatchewan</option>
<option value="16">ACST</option>
<option value="17">ACST/ACDT</option>
<option value="18">HST (Hawaii)</option>
</select><br>
UTC offset: <input name="UO" type="number" min="-65500" max="65500" required> seconds (max. 18 hours)<br>
Current local time is <span class="times">unknown</span>.<br>
@ -143,15 +158,26 @@
Year: 20 <input name="CY" type="number" min="0" max="99" required> Month: <input name="CI" type="number" min="1" max="12" required> Day: <input name="CD" type="number" min="1" max="31" required><br>
Hour: <input name="CH" type="number" min="0" max="23" required> Minute: <input name="CM" type="number" min="0" max="59" required> Second: <input name="CS" type="number" min="0" max="59" required><br>
<h3>Macro presets</h3>
<b>Macros have moved!</b><br>
<i>Presets now also can be used as macros to save both JSON and HTTP API commands.<br>
Just enter the preset id below!</i>
<b>Macros have moved!</b><br>
<i>Presets now also can be used as macros to save both JSON and HTTP API commands.<br>
Just enter the preset id below!</i>
<i>Use 0 for the default action instead of a preset</i><br>
Alexa On/Off Preset: <input name="A0" type="number" min="0" max="250" required> <input name="A1" type="number" min="0" max="250" required><br>
Button short press Preset: <input name="MP" type="number" min="0" max="250" required><br>
Long Press: <input name="ML" type="number" min="0" max="250" required> Double press: <input name="MD" type="number" min="0" max="250" required><br>
Countdown-Over Preset: <input name="MC" type="number" min="0" max="250" required><br>
Timed-Light-Over Presets: <input name="MN" type="number" min="0" max="250" required><br>
<h3>Button actions</h3>
<table style="margin: 0 auto;" id="macros">
<thead>
<tr>
<td>push<br>switch</td>
<td>short<br>on-&gt;off</td>
<td>long<br>off-&gt;on</td>
<td>double<br>N/A</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<h3>Time-controlled presets</h3>
<div style="display: inline-block">
<table id="TMT">

View File

@ -20,8 +20,8 @@ void handleBlynk();
void updateBlynk();
//button.cpp
void shortPressAction();
bool isButtonPressed();
void shortPressAction(uint8_t b=0);
bool isButtonPressed(uint8_t b=0);
void handleButton();
void handleIO();

File diff suppressed because one or more lines are too long

View File

@ -81,9 +81,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
#ifndef WLED_DISABLE_INFRARED
if (irPin>=0 && pinManager.isPinAllocated(irPin)) pinManager.deallocatePin(irPin);
#endif
if (btnPin>=0 && pinManager.isPinAllocated(btnPin)) pinManager.deallocatePin(btnPin);
//TODO remove all busses, but not in this system call
//busses->removeAll();
for (uint8_t s=0; s<WLED_MAX_BUTTONS; s++)
if (btnPin[s]>=0 && pinManager.isPinAllocated(btnPin[s]))
pinManager.deallocatePin(btnPin[s]);
strip.isRgbw = false;
uint8_t colorOrder, type, skip;
@ -99,7 +99,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip 1st LED
if (!request->hasArg(lp)) {
DEBUG_PRINTLN("No data."); break;
DEBUG_PRINTLN(F("No data.")); break;
}
for (uint8_t i = 0; i < 5; i++) {
lp[1] = 48+i;
@ -109,7 +109,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
type = request->arg(lt).toInt();
strip.isRgbw = strip.isRgbw || BusManager::isRgbw(type);
skip = request->hasArg(sl) ? LED_SKIP_AMOUNT : 0;
if (request->hasArg(lc) && request->arg(lc).toInt() > 0) {
length = request->arg(lc).toInt();
} else {
@ -128,14 +128,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (t > 0 && t <= MAX_LEDS) ledCount = t;
// upate other pins
#ifndef WLED_DISABLE_INFRARED
int hw_ir_pin = request->arg(F("IR")).toInt();
if (pinManager.isPinOk(hw_ir_pin) && pinManager.allocatePin(hw_ir_pin,false)) {
if (pinManager.allocatePin(hw_ir_pin,false)) {
irPin = hw_ir_pin;
} else {
irPin = -1;
}
#endif
irEnabled = request->arg(F("IT")).toInt();
int hw_rly_pin = request->arg(F("RL")).toInt();
if (pinManager.allocatePin(hw_rly_pin,true)) {
@ -145,13 +144,20 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
rlyMde = (bool)request->hasArg(F("RM"));
int hw_btn_pin = request->arg(F("BT")).toInt();
if (pinManager.allocatePin(hw_btn_pin,false)) {
btnPin = hw_btn_pin;
pinMode(btnPin, INPUT_PULLUP);
} else {
btnPin = -1;
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
char bt[4] = "BT"; bt[2] = 48+i; bt[3] = 0; // button pin
char be[4] = "BE"; be[2] = 48+i; be[3] = 0; // button type
int hw_btn_pin = request->arg(bt).toInt();
if (pinManager.allocatePin(hw_btn_pin,false)) {
btnPin[i] = hw_btn_pin;
pinMode(btnPin[i], INPUT_PULLUP);
buttonType[i] = request->arg(be).toInt();
} else {
btnPin[i] = -1;
buttonType[i] = BTN_TYPE_NONE;
}
}
touchThreshold = request->arg(F("TT")).toInt();
strip.ablMilliampsMax = request->arg(F("MA")).toInt();
strip.milliampsPerLed = request->arg(F("LA")).toInt();
@ -195,8 +201,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
//SYNC
if (subPage == 4)
{
buttonType = request->arg(F("BT")).toInt();
irEnabled = request->arg(F("IR")).toInt();
int t = request->arg(F("UP")).toInt();
if (t > 0) udpPort = t;
t = request->arg(F("U2")).toInt();
@ -238,6 +242,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
alexaEnabled = request->hasArg(F("AL"));
strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33);
#ifndef WLED_DISABLE_BLYNK
strlcpy(blynkHost, request->arg("BH").c_str(), 33);
t = request->arg(F("BP")).toInt();
if (t > 0) blynkPort = t;
@ -245,6 +250,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (request->hasArg("BK") && !request->arg("BK").equals(F("Hidden"))) {
strlcpy(blynkApiKey, request->arg("BK").c_str(), 36); initBlynk(blynkApiKey, blynkHost, blynkPort);
}
#endif
#ifdef WLED_ENABLE_MQTT
mqttEnabled = request->hasArg(F("MQ"));
@ -307,8 +313,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
analogClock5MinuteMarks = request->hasArg(F("O5"));
analogClockSecondsTrail = request->hasArg(F("OS"));
#ifndef WLED_DISABLE_CRONIXIE
strcpy(cronixieDisplay,request->arg(F("CX")).c_str());
cronixieBacklight = request->hasArg(F("CB"));
#endif
countdownMode = request->hasArg(F("CE"));
countdownYear = request->arg(F("CY")).toInt();
countdownMonth = request->arg(F("CI")).toInt();
@ -320,11 +328,17 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
macroAlexaOn = request->arg(F("A0")).toInt();
macroAlexaOff = request->arg(F("A1")).toInt();
macroButton = request->arg(F("MP")).toInt();
macroLongPress = request->arg(F("ML")).toInt();
macroCountdown = request->arg(F("MC")).toInt();
macroNl = request->arg(F("MN")).toInt();
macroDoublePress = request->arg(F("MD")).toInt();
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
char mp[4] = "MP"; mp[2] = 48+i; mp[3] = 0; // short
char ml[4] = "ML"; ml[2] = 48+i; ml[3] = 0; // long
char md[4] = "MD"; md[2] = 48+i; md[3] = 0; // double
//if (!request->hasArg(mp)) break;
macroButton[i] = request->arg(mp).toInt(); // these will default to 0 if not present
macroLongPress[i] = request->arg(ml).toInt();
macroDoublePress[i] = request->arg(md).toInt();
}
char k[3]; k[2] = 0;
for (int i = 0; i<10; i++)

File diff suppressed because it is too large Load Diff

View File

@ -113,6 +113,26 @@
#include "src/dependencies/json/AsyncJson-v6.h"
#include "src/dependencies/json/ArduinoJson-v6.h"
// ESP32-WROVER features SPI RAM (aka PSRAM) which can be allocated using ps_malloc()
// we can create custom PSRAMDynamicJsonDocument to use such feature (replacing DynamicJsonDocument)
// The following is a construct to enable code to compile without it.
// There is a code thet will still not use PSRAM though:
// AsyncJsonResponse is a derived class that implements DynamicJsonDocument (AsyncJson-v6.h)
#ifdef ARDUINO_ARCH_ESP32
struct PSRAM_Allocator {
void* allocate(size_t size) {
if (psramFound()) return ps_malloc(size); // use PSRAM if it exists
else return malloc(size); // fallback
}
void deallocate(void* pointer) {
free(pointer);
}
};
using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#else
#define PSRAMDynamicJsonDocument DynamicJsonDocument
#endif
#include "fcn_declare.h"
#include "html_ui.h"
#include "html_settings.h"
@ -188,9 +208,9 @@ WLED_GLOBAL char otaPass[33] _INIT(DEFAULT_OTA_PASS);
// Hardware CONFIG (only changeble HERE, not at runtime)
// LED strip pin, button pin and IR pin changeable in NpbWrapper.h!
#ifndef BTNPIN
WLED_GLOBAL int8_t btnPin _INIT(0);
WLED_GLOBAL int8_t btnPin[WLED_MAX_BUTTONS] _INIT({0});
#else
WLED_GLOBAL int8_t btnPin _INIT(BTNPIN);
WLED_GLOBAL int8_t btnPin[WLED_MAX_BUTTONS] _INIT({BTNPIN});
#endif
#ifndef RLYPIN
WLED_GLOBAL int8_t rlyPin _INIT(12);
@ -204,7 +224,7 @@ WLED_GLOBAL bool rlyMde _INIT(true);
WLED_GLOBAL bool rlyMde _INIT(RLYMDE);
#endif
#ifndef IRPIN
WLED_GLOBAL int8_t irPin _INIT(4);
WLED_GLOBAL int8_t irPin _INIT(-1);
#else
WLED_GLOBAL int8_t irPin _INIT(IRPIN);
#endif
@ -259,7 +279,7 @@ WLED_GLOBAL NodesMap Nodes;
WLED_GLOBAL bool nodeListEnabled _INIT(true);
WLED_GLOBAL bool nodeBroadcastEnabled _INIT(true);
WLED_GLOBAL byte buttonType _INIT(BTN_TYPE_PUSH);
WLED_GLOBAL byte buttonType[WLED_MAX_BUTTONS] _INIT({BTN_TYPE_PUSH});
WLED_GLOBAL byte irEnabled _INIT(0); // Infrared receiver
WLED_GLOBAL uint16_t udpPort _INIT(21324); // WLED notifier default port
@ -279,9 +299,11 @@ WLED_GLOBAL bool notifyTwice _INIT(false); // notificatio
WLED_GLOBAL bool alexaEnabled _INIT(false); // enable device discovery by Amazon Echo
WLED_GLOBAL char alexaInvocationName[33] _INIT("Light"); // speech control name of device. Choose something voice-to-text can understand
#ifndef WLED_DISABLE_BLYNK
WLED_GLOBAL char blynkApiKey[36] _INIT(""); // Auth token for Blynk server. If empty, no connection will be made
WLED_GLOBAL char blynkHost[33] _INIT("blynk-cloud.com"); // Default Blynk host
WLED_GLOBAL uint16_t blynkPort _INIT(80); // Default Blynk port
#endif
WLED_GLOBAL uint16_t realtimeTimeoutMs _INIT(2500); // ms timeout of realtime mode before returning to normal mode
WLED_GLOBAL int arlsOffset _INIT(0); // realtime LED offset
@ -311,6 +333,7 @@ WLED_GLOBAL char mqttPass[41] _INIT(""); // optional: password
WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID
WLED_GLOBAL uint16_t mqttPort _INIT(1883);
#ifndef WLED_DISABLE_HUESYNC
WLED_GLOBAL bool huePollingEnabled _INIT(false); // poll hue bridge for light state
WLED_GLOBAL uint16_t huePollIntervalMs _INIT(2500); // low values (< 1sec) may cause lag but offer quicker response
WLED_GLOBAL char hueApiKey[47] _INIT("api"); // key token will be obtained from bridge
@ -319,6 +342,7 @@ WLED_GLOBAL IPAddress hueIP _INIT_N(((0, 0, 0, 0))); // IP address of the bridge
WLED_GLOBAL bool hueApplyOnOff _INIT(true);
WLED_GLOBAL bool hueApplyBri _INIT(true);
WLED_GLOBAL bool hueApplyColor _INIT(true);
#endif
// Time CONFIG
WLED_GLOBAL bool ntpEnabled _INIT(false); // get internet time. Only required if you use clock overlays or time-activated macros
@ -333,8 +357,10 @@ WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your
WLED_GLOBAL bool analogClockSecondsTrail _INIT(false); // Display seconds as trail of LEDs instead of a single pixel
WLED_GLOBAL bool analogClock5MinuteMarks _INIT(false); // Light pixels at every 5-minute position
#ifndef WLED_DISABLE_CRONIXIE
WLED_GLOBAL char cronixieDisplay[7] _INIT("HHMMSS"); // Cronixie Display mask. See wled13_cronixie.ino
WLED_GLOBAL bool cronixieBacklight _INIT(true); // Allow digits to be back-illuminated
#endif
WLED_GLOBAL bool countdownMode _INIT(false); // Clock will count down towards date
WLED_GLOBAL byte countdownYear _INIT(20), countdownMonth _INIT(1); // Countdown target date, year is last two digits
@ -344,7 +370,9 @@ WLED_GLOBAL byte countdownMin _INIT(0) , countdownSec _INIT(0);
WLED_GLOBAL byte macroNl _INIT(0); // after nightlight delay over
WLED_GLOBAL byte macroCountdown _INIT(0);
WLED_GLOBAL byte macroAlexaOn _INIT(0), macroAlexaOff _INIT(0);
WLED_GLOBAL byte macroButton _INIT(0), macroLongPress _INIT(0), macroDoublePress _INIT(0);
WLED_GLOBAL byte macroButton[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL byte macroLongPress[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL byte macroDoublePress[WLED_MAX_BUTTONS] _INIT({0});
// Security CONFIG
WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks
@ -405,10 +433,11 @@ 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 buttonPressedBefore _INIT(false);
WLED_GLOBAL bool buttonLongPressed _INIT(false);
WLED_GLOBAL unsigned long buttonPressedTime _INIT(0);
WLED_GLOBAL unsigned long buttonWaitTime _INIT(0);
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});
WLED_GLOBAL unsigned long buttonWaitTime[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL byte touchThreshold _INIT(TOUCH_THRESHOLD);
// notifications
WLED_GLOBAL bool notifyDirectDefault _INIT(notifyDirect);

View File

@ -93,7 +93,7 @@ void loadSettingsFromEEPROM()
notifyButton = EEPROM.read(230);
notifyTwice = EEPROM.read(231);
buttonType = EEPROM.read(232) ? BTN_TYPE_PUSH : BTN_TYPE_NONE;
buttonType[0] = EEPROM.read(232) ? BTN_TYPE_PUSH : BTN_TYPE_NONE;
staticIP[0] = EEPROM.read(234);
staticIP[1] = EEPROM.read(235);
@ -157,6 +157,7 @@ void loadSettingsFromEEPROM()
receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects);
if (lastEEPROMversion > 4) {
#ifndef WLED_DISABLE_HUESYNC
huePollingEnabled = EEPROM.read(2048);
//hueUpdatingEnabled = EEPROM.read(2049);
for (int i = 2050; i < 2054; ++i)
@ -172,6 +173,7 @@ void loadSettingsFromEEPROM()
hueApplyBri = EEPROM.read(2104);
hueApplyColor = EEPROM.read(2105);
huePollLightId = EEPROM.read(2106);
#endif
}
if (lastEEPROMversion > 5) {
overlayMin = EEPROM.read(2150);
@ -188,18 +190,20 @@ void loadSettingsFromEEPROM()
countdownSec = EEPROM.read(2161);
setCountdown();
#ifndef WLED_DISABLE_CRONIXIE
readStringFromEEPROM(2165, cronixieDisplay, 6);
cronixieBacklight = EEPROM.read(2171);
#endif
//macroBoot = EEPROM.read(2175);
macroAlexaOn = EEPROM.read(2176);
macroAlexaOff = EEPROM.read(2177);
macroButton = EEPROM.read(2178);
macroLongPress = EEPROM.read(2179);
macroButton[0] = EEPROM.read(2178);
macroLongPress[0] = EEPROM.read(2179);
macroCountdown = EEPROM.read(2180);
macroNl = EEPROM.read(2181);
macroDoublePress = EEPROM.read(2182);
if (macroDoublePress > 16) macroDoublePress = 0;
macroDoublePress[0] = EEPROM.read(2182);
if (macroDoublePress[0] > 16) macroDoublePress[0] = 0;
}
if (lastEEPROMversion > 6)
@ -237,7 +241,7 @@ void loadSettingsFromEEPROM()
if (lastEEPROMversion > 9)
{
strip.setColorOrder(EEPROM.read(383));
//strip.setColorOrder(EEPROM.read(383));
irEnabled = EEPROM.read(385);
strip.ablMilliampsMax = EEPROM.read(387) + ((EEPROM.read(388) << 8) & 0xFF00);
} else if (lastEEPROMversion > 1) //ABL is off by default when updating from version older than 0.8.2
@ -343,8 +347,10 @@ void loadSettingsFromEEPROM()
//custom macro memory (16 slots/ each 64byte)
//1024-2047 reserved
#ifndef WLED_DISABLE_BLYNK
readStringFromEEPROM(2220, blynkApiKey, 35);
if (strlen(blynkApiKey) < 25) blynkApiKey[0] = 0;
#endif
#ifdef WLED_ENABLE_DMX
// DMX (2530 - 2549)2535
@ -417,10 +423,10 @@ void deEEP() {
for (byte j = 0; j < numChannels; j++) colX.add(EEPROM.read(memloc + j));
}
segObj[F("fx")] = EEPROM.read(i+10);
segObj["fx"] = EEPROM.read(i+10);
segObj[F("sx")] = EEPROM.read(i+11);
segObj[F("ix")] = EEPROM.read(i+16);
segObj[F("pal")] = EEPROM.read(i+17);
segObj["pal"] = EEPROM.read(i+17);
} else {
WS2812FX::Segment* seg = strip.getSegments();
memcpy(seg, EEPROM.getDataPtr() +i+2, 240);

View File

@ -256,27 +256,72 @@ void getSettingsJS(byte subPage, char* dest)
if (subPage == 2) {
char nS[8];
// add usermod pins as d.um_p array (TODO: usermod config shouldn't use state. instead we should load "um" object from cfg.json)
/*DynamicJsonDocument doc(JSON_BUFFER_SIZE);
JsonObject mods = doc.createNestedObject(F("mods"));
usermods.addToJsonState(mods);
// add reserved and usermod pins as d.um_p array
DynamicJsonDocument doc(JSON_BUFFER_SIZE/2);
JsonObject mods = doc.createNestedObject(F("um"));
usermods.addToConfig(mods);
oappend(SET_F("d.um_p=["));
if (!mods.isNull()) {
uint8_t i=0;
oappend(SET_F("d.um_p=["));
for (JsonPair kv : mods) {
if (strncmp_P(kv.key().c_str(),PSTR("pin_"),4) == 0) {
if (i++) oappend(SET_F(","));
oappend(itoa((int)kv.value(),nS,10));
if (!kv.value().isNull()) {
// element is an JsonObject
JsonObject obj = kv.value();
if (obj["pin"] != nullptr) {
if (obj["pin"].is<JsonArray>()) {
JsonArray pins = obj["pin"].as<JsonArray>();
for (JsonVariant pv : pins) {
if (i++) oappend(SET_F(","));
oappendi(pv.as<int>());
}
} else {
if (i++) oappend(SET_F(","));
oappendi(obj["pin"].as<int>());
}
}
}
}
oappend(SET_F("];"));
}*/
if (i) oappend(SET_F(","));
oappend(SET_F("6,7,8,9,10,11")); // flash memory pins
#ifdef WLED_ENABLE_DMX
oappend(SET_F(",2")); // DMX hardcoded pin
#endif
#ifdef WLED_ENABLE_ADALIGHT
// inform settings page that pin 3 is used by ADALights if not aleready used by strip (previous setup)
// NOTE: this will prohibit pin 3 use on new installs
{
bool pin3used = false;
for (uint8_t s=0; s < busses.getNumBusses(); s++) {
Bus* bus = busses.getBus(s);
uint8_t pins[5];
uint8_t nPins = bus->getPins(pins);
for (uint8_t i = 0; i < nPins; i++) {
if (pins[i] == 3) {
pin3used = true;
break;
}
}
if (pin3used) break;
}
if (!pin3used && pinManager.isPinAllocated(3)) oappend(SET_F(",3")); // ADALight (RX) pin
}
#endif
#ifdef WLED_DEBUG
oappend(SET_F(",1")); // debug output (TX) pin
#endif
#ifdef ARDUINO_ARCH_ESP32
if (psramFound()) oappend(SET_F(",16,17")); // GPIO16 & GPIO17 reserved for SPI RAM
#endif
//TODO: add reservations for Ethernet shield pins
#ifdef WLED_USE_ETHERNET
#endif
}
oappend(SET_F("];"));
// set limits
oappend(SET_F("bLimits("));
oappend(itoa(WLED_MAX_BUSSES,nS,10));
oappend(",");
oappend(itoa(MAX_LEDS_PER_BUS,nS,10));
oappend(",");
oappend(itoa(WLED_MAX_BUSSES,nS,10)); oappend(",");
oappend(itoa(MAX_LEDS_PER_BUS,nS,10)); oappend(",");
oappend(itoa(MAX_LED_MEMORY,nS,10));
oappend(SET_F(");"));
@ -286,7 +331,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',SET_F("LC"),ledCount);
for (uint8_t s=0; s < busses.getNumBusses(); s++){
for (uint8_t s=0; s < busses.getNumBusses(); s++) {
Bus* bus = busses.getBus(s);
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
@ -320,7 +365,6 @@ void getSettingsJS(byte subPage, char* dest)
}
sappend('v',SET_F("CA"),briS);
sappend('v',SET_F("AW"),strip.rgbwMode);
sappend('c',SET_F("BO"),turnOnAtBoot);
@ -338,8 +382,16 @@ void getSettingsJS(byte subPage, char* dest)
sappend('i',SET_F("PB"),strip.paletteBlend);
sappend('v',SET_F("RL"),rlyPin);
sappend('c',SET_F("RM"),rlyMde);
sappend('v',SET_F("BT"),btnPin);
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
oappend(SET_F("addBtn("));
oappend(itoa(i,nS,10)); oappend(",");
oappend(itoa(btnPin[i],nS,10)); oappend(",");
oappend(itoa(buttonType[i],nS,10));
oappend(SET_F(");"));
}
sappend('v',SET_F("TT"),touchThreshold);
sappend('v',SET_F("IR"),irPin);
sappend('v',SET_F("IT"),irEnabled);
}
if (subPage == 3)
@ -350,8 +402,6 @@ void getSettingsJS(byte subPage, char* dest)
if (subPage == 4)
{
sappend('v',SET_F("BT"),buttonType);
sappend('v',SET_F("IR"),irEnabled);
sappend('v',SET_F("UP"),udpPort);
sappend('v',SET_F("U2"),udpPort2);
sappend('c',SET_F("RB"),receiveNotificationBrightness);
@ -381,8 +431,10 @@ void getSettingsJS(byte subPage, char* dest)
sappends('s',SET_F("AI"),alexaInvocationName);
sappend('c',SET_F("SA"),notifyAlexa);
sappends('s',SET_F("BK"),(char*)((blynkEnabled)?SET_F("Hidden"):""));
#ifndef WLED_DISABLE_BLYNK
sappends('s',SET_F("BH"),blynkHost);
sappend('v',SET_F("BP"),blynkPort);
#endif
#ifdef WLED_ENABLE_MQTT
sappend('c',SET_F("MQ"),mqttEnabled);
@ -451,8 +503,10 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',SET_F("OM"),analogClock12pixel);
sappend('c',SET_F("OS"),analogClockSecondsTrail);
sappend('c',SET_F("O5"),analogClock5MinuteMarks);
#ifndef WLED_DISABLE_CRONIXIE
sappends('s',SET_F("CX"),cronixieDisplay);
sappend('c',SET_F("CB"),cronixieBacklight);
#endif
sappend('c',SET_F("CE"),countdownMode);
sappend('v',SET_F("CY"),countdownYear);
sappend('v',SET_F("CI"),countdownMonth);
@ -463,11 +517,16 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',SET_F("A0"),macroAlexaOn);
sappend('v',SET_F("A1"),macroAlexaOff);
sappend('v',SET_F("MP"),macroButton);
sappend('v',SET_F("ML"),macroLongPress);
sappend('v',SET_F("MC"),macroCountdown);
sappend('v',SET_F("MN"),macroNl);
sappend('v',SET_F("MD"),macroDoublePress);
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
oappend(SET_F("addRow("));
oappend(itoa(i,tm,10)); oappend(",");
oappend(itoa(macroButton[i],tm,10)); oappend(",");
oappend(itoa(macroLongPress[i],tm,10)); oappend(",");
oappend(itoa(macroDoublePress[i],tm,10));
oappend(SET_F(");"));
}
char k[4];
k[2] = 0; //Time macros