Merge pull request #3238 from Aircoookie/beta-3

Beta 3
This commit is contained in:
Blaž Kristan 2023-06-13 21:38:10 +02:00 committed by GitHub
commit 670461c66f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 8693 additions and 7811 deletions

View File

@ -1,7 +1,15 @@
## WLED changelog ## WLED changelog
#### Build 2306020 #### Build 2306130
- Bumped version to 0.14-b3 (beta 3)
- added pin dropdowns in LED preferences (not for LED pins) and usermods
- introduced (unused ATM) NeoGammaWLEDMethod class
- Reverse proxy support
- PCF8754 support for Rotary encoder (requires wiring INT pin to ESP GPIO)
- Rely on global I2C pins for usermods (breaking change)
- various fixes and enhancements
#### Build 2306020
- Support for segment sets (PR #3171) - Support for segment sets (PR #3171)
- Reduce sound simulation modes to 2 to facilitiate segment sets - Reduce sound simulation modes to 2 to facilitiate segment sets
- Trigger button immediately on press if all configured presets are the same (PR #3226) - Trigger button immediately on press if all configured presets are the same (PR #3226)

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "wled", "name": "wled",
"version": "0.14.0-b2", "version": "0.14.0-b3",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "wled", "name": "wled",
"version": "0.14.0-b2", "version": "0.14.0-b3",
"description": "Tools for WLED project", "description": "Tools for WLED project",
"main": "tools/cdata.js", "main": "tools/cdata.js",
"directories": { "directories": {

View File

@ -8,7 +8,6 @@
#pragma once #pragma once
#include "wled.h" #include "wled.h"
#include <Wire.h>
#include <BH1750.h> #include <BH1750.h>
// the max frequency to check photoresistor, 10 seconds // the max frequency to check photoresistor, 10 seconds
@ -56,15 +55,6 @@ private:
static const char _offset[]; static const char _offset[];
static const char _HomeAssistantDiscovery[]; static const char _HomeAssistantDiscovery[];
// set the default pins based on the architecture, these get overridden by Usermod menu settings
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
#define HW_PIN_SCL 22
#define HW_PIN_SDA 21
#else // ESP8266 boards
#define HW_PIN_SCL 5
#define HW_PIN_SDA 4
#endif
int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
bool initDone = false; bool initDone = false;
bool sensorFound = false; bool sensorFound = false;
@ -123,14 +113,8 @@ private:
public: public:
void setup() void setup()
{ {
bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } }; // allocate pins
PinOwner po = PinOwner::UM_BH1750; // defaults to being pinowner for SCL/SDA pins if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) return;
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
if (!pinManager.allocateMultiplePins(pins, 2, po)) return;
Wire.begin(ioPin[1], ioPin[0]);
sensorFound = lightMeter.begin(); sensorFound = lightMeter.begin();
initDone = true; initDone = true;
} }
@ -216,9 +200,6 @@ public:
top[FPSTR(_minReadInterval)] = minReadingInterval; top[FPSTR(_minReadInterval)] = minReadingInterval;
top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery;
top[FPSTR(_offset)] = offset; top[FPSTR(_offset)] = offset;
JsonArray io_pin = top.createNestedArray(F("pin"));
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
DEBUG_PRINTLN(F("BH1750 config saved.")); DEBUG_PRINTLN(F("BH1750 config saved."));
} }
@ -226,8 +207,6 @@ public:
// called before setup() to populate properties from values stored in cfg.json // called before setup() to populate properties from values stored in cfg.json
bool readFromConfig(JsonObject &root) bool readFromConfig(JsonObject &root)
{ {
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
// we look for JSON object. // we look for JSON object.
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) if (top.isNull())
@ -244,27 +223,12 @@ public:
configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms
configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false);
configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1); configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1);
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
DEBUG_PRINTLN(F(" config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
bool pinsChanged = false;
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
PinOwner po = PinOwner::UM_BH1750;
if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
setup();
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[F("pin")].isNull();
} }
return configComplete; return configComplete;

View File

@ -9,7 +9,6 @@
#include "wled.h" #include "wled.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h>
#include <BME280I2C.h> // BME280 sensor #include <BME280I2C.h> // BME280 sensor
#include <EnvironmentCalculations.h> // BME280 extended measurements #include <EnvironmentCalculations.h> // BME280 extended measurements
@ -34,7 +33,6 @@ private:
#ifdef ESP8266 #ifdef ESP8266
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
#endif #endif
int8_t ioPin[2] = {i2c_scl, i2c_sda}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
bool initDone = false; bool initDone = false;
// BME280 sensor settings // BME280 sensor settings
@ -186,13 +184,8 @@ private:
public: public:
void setup() void setup()
{ {
bool HW_Pins_Used = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda); // note whether architecture-based hardware SCL/SDA pins used PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } }; // allocate pins
PinOwner po = PinOwner::UM_BME280; // defaults to being pinowner for SCL/SDA pins if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { sensorType=0; return; }
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
if (!pinManager.allocateMultiplePins(pins, 2, po)) { sensorType=0; return; }
Wire.begin(ioPin[1], ioPin[0]);
if (!bme.begin()) if (!bme.begin())
{ {
@ -415,9 +408,6 @@ public:
top[F("PublishAlways")] = PublishAlways; top[F("PublishAlways")] = PublishAlways;
top[F("UseCelsius")] = UseCelsius; top[F("UseCelsius")] = UseCelsius;
top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery; top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery;
JsonArray io_pin = top.createNestedArray(F("pin"));
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
DEBUG_PRINTLN(F("BME280 config saved.")); DEBUG_PRINTLN(F("BME280 config saved."));
} }
@ -427,8 +417,6 @@ public:
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) { if (top.isNull()) {
DEBUG_PRINT(F(_name)); DEBUG_PRINT(F(_name));
@ -447,27 +435,14 @@ public:
configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false); configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false);
configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true); configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true);
configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false); configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false);
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
DEBUG_PRINTLN(F(" config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page // changing parameters from settings page
bool pinsChanged = false;
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
PinOwner po = PinOwner::UM_BME280;
if (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
setup();
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[F("pin")].isNull();
} }
return configComplete; return configComplete;

View File

@ -20,7 +20,7 @@
* This usermod handles PIR sensor states. * This usermod handles PIR sensor states.
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH. * The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off. * When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
* * Maintained by: @blazoncek
* *
* Usermods allow you to add own functionality to WLED more easily * Usermods allow you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
@ -38,21 +38,21 @@ public:
~PIRsensorSwitch() {} ~PIRsensorSwitch() {}
//Enable/Disable the PIR sensor //Enable/Disable the PIR sensor
void EnablePIRsensor(bool en) { enabled = en; } inline void EnablePIRsensor(bool en) { enabled = en; }
// Get PIR sensor enabled/disabled state // Get PIR sensor enabled/disabled state
bool PIRsensorEnabled() { return enabled; } inline bool PIRsensorEnabled() { return enabled; }
private: private:
byte prevPreset = 0; byte prevPreset = 0;
byte prevPlaylist = 0; byte prevPlaylist = 0;
uint32_t offTimerStart = 0; // off timer start time volatile unsigned long offTimerStart = 0; // off timer start time
volatile bool PIRtriggered = false; // did PIR trigger?
byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE
byte sensorPinState = LOW; // current PIR sensor pin state byte sensorPinState = LOW; // current PIR sensor pin state
bool initDone = false; // status of initialization bool initDone = false; // status of initialization
bool PIRtriggered = false;
unsigned long lastLoop = 0; unsigned long lastLoop = 0;
// configurable parameters // configurable parameters
@ -66,6 +66,7 @@ private:
// flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR) // flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
bool m_offOnly = false; bool m_offOnly = false;
bool m_offMode = offMode; bool m_offMode = offMode;
bool m_override = false;
// Home Assistant // Home Assistant
bool HomeAssistantDiscovery = false; // is HA discovery turned on bool HomeAssistantDiscovery = false; // is HA discovery turned on
@ -81,12 +82,122 @@ private:
static const char _offOnly[]; static const char _offOnly[];
static const char _haDiscovery[]; static const char _haDiscovery[];
static const char _notify[]; static const char _notify[];
static const char _override[];
/** /**
* check if it is daytime * check if it is daytime
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime * if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
*/ */
bool isDayTime() { static bool isDayTime();
/**
* switch strip on/off
*/
void switchStrip(bool switchOn);
void publishMqtt(const char* state);
// Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
void publishHomeAssistantAutodiscovery();
/**
* Read and update PIR sensor state.
* Initilize/reset switch off timer
*/
bool updatePIRsensorState();
/**
* switch off the strip if the delay has elapsed
*/
bool handleOffTimer();
public:
//Functions called by WLED
/**
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup();
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
//void connected();
/**
* onMqttConnect() is called when MQTT connection is established
*/
void onMqttConnect(bool sessionPresent);
/**
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop();
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*
* Add PIR sensor state and switch off timer duration to jsoninfo
*/
void addToJsonInfo(JsonObject &root);
/**
* onStateChanged() is used to detect WLED state change
*/
void onStateChange(uint8_t mode);
/**
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void addToJsonState(JsonObject &root);
/**
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject &root);
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root);
/**
* provide UI information and allow extending UI options
*/
void appendConfigData();
/**
* restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject &root);
/**
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() { return USERMOD_ID_PIRSWITCH; }
};
// strings to reduce flash memory usage (used more than twice)
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";
const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery";
const char PIRsensorSwitch::_notify[] PROGMEM = "notifications";
const char PIRsensorSwitch::_override[] PROGMEM = "override";
bool PIRsensorSwitch::isDayTime() {
updateLocalTime(); updateLocalTime();
uint8_t hr = hour(localTime); uint8_t hr = hour(localTime);
uint8_t mi = minute(localTime); uint8_t mi = minute(localTime);
@ -106,10 +217,7 @@ private:
return false; return false;
} }
/** void PIRsensorSwitch::switchStrip(bool switchOn)
* switch strip on/off
*/
void switchStrip(bool switchOn)
{ {
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing
if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing
@ -158,7 +266,7 @@ private:
} }
} }
void publishMqtt(const char* state) void PIRsensorSwitch::publishMqtt(const char* state)
{ {
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
//Check if MQTT Connected, otherwise it will crash the 8266 //Check if MQTT Connected, otherwise it will crash the 8266
@ -171,8 +279,7 @@ private:
#endif #endif
} }
// Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. void PIRsensorSwitch::publishHomeAssistantAutodiscovery()
void publishHomeAssistantAutodiscovery()
{ {
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
if (WLED_MQTT_CONNECTED) { if (WLED_MQTT_CONNECTED) {
@ -207,11 +314,7 @@ private:
#endif #endif
} }
/** bool PIRsensorSwitch::updatePIRsensorState()
* Read and update PIR sensor state.
* Initilize/reset switch off timer
*/
bool updatePIRsensorState()
{ {
bool pinState = digitalRead(PIRsensorPin); bool pinState = digitalRead(PIRsensorPin);
if (pinState != sensorPinState) { if (pinState != sensorPinState) {
@ -232,10 +335,7 @@ private:
return false; return false;
} }
/** bool PIRsensorSwitch::handleOffTimer()
* switch off the strip if the delay has elapsed
*/
bool handleOffTimer()
{ {
if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) { if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) {
offTimerStart = 0; offTimerStart = 0;
@ -249,14 +349,9 @@ private:
return false; return false;
} }
public:
//Functions called by WLED //Functions called by WLED
/** void PIRsensorSwitch::setup()
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup()
{ {
if (enabled) { if (enabled) {
// pin retrieved from cfg.json (readFromConfig()) prior to running setup() // pin retrieved from cfg.json (readFromConfig()) prior to running setup()
@ -275,27 +370,14 @@ public:
initDone = true; initDone = true;
} }
/** void PIRsensorSwitch::onMqttConnect(bool sessionPresent)
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{ {
}
/**
* onMqttConnect() is called when MQTT connection is established
*/
void onMqttConnect(bool sessionPresent) {
if (HomeAssistantDiscovery) { if (HomeAssistantDiscovery) {
publishHomeAssistantAutodiscovery(); publishHomeAssistantAutodiscovery();
} }
} }
/** void PIRsensorSwitch::loop()
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop()
{ {
// only check sensors 4x/s // only check sensors 4x/s
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
@ -306,12 +388,7 @@ public:
} }
} }
/** void PIRsensorSwitch::addToJsonInfo(JsonObject &root)
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*
* Add PIR sensor state and switch off timer duration to jsoninfo
*/
void addToJsonInfo(JsonObject &root)
{ {
JsonObject user = root["u"]; JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u"); if (user.isNull()) user = root.createNestedObject("u");
@ -371,13 +448,10 @@ public:
sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false;
} }
/** void PIRsensorSwitch::onStateChange(uint8_t mode) {
* onStateChanged() is used to detect WLED state change
*/
void onStateChange(uint8_t mode) {
if (!initDone) return; if (!initDone) return;
DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart); DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart);
if (PIRtriggered && offTimerStart) { if (m_override && PIRtriggered && offTimerStart) { // debounce
// checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger // checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger
DEBUG_PRINTLN(F("PIR: Canceled.")); DEBUG_PRINTLN(F("PIR: Canceled."));
offTimerStart = 0; offTimerStart = 0;
@ -385,22 +459,7 @@ public:
} }
} }
/** void PIRsensorSwitch::readFromJsonState(JsonObject &root)
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
/*
void addToJsonState(JsonObject &root)
{
}
*/
/**
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject &root)
{ {
if (!initDone) return; // prevent crash on boot applyPreset() if (!initDone) return; // prevent crash on boot applyPreset()
JsonObject usermod = root[FPSTR(_name)]; JsonObject usermod = root[FPSTR(_name)];
@ -411,11 +470,7 @@ public:
} }
} }
void PIRsensorSwitch::addToConfig(JsonObject &root)
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root)
{ {
JsonObject top = root.createNestedObject(FPSTR(_name)); JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled; top[FPSTR(_enabled)] = enabled;
@ -426,24 +481,20 @@ public:
top[FPSTR(_nightTime)] = m_nightTimeOnly; top[FPSTR(_nightTime)] = m_nightTimeOnly;
top[FPSTR(_mqttOnly)] = m_mqttOnly; top[FPSTR(_mqttOnly)] = m_mqttOnly;
top[FPSTR(_offOnly)] = m_offOnly; top[FPSTR(_offOnly)] = m_offOnly;
top[FPSTR(_override)] = m_override;
top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery; top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery;
top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY); top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY);
DEBUG_PRINTLN(F("PIR config saved.")); DEBUG_PRINTLN(F("PIR config saved."));
} }
void appendConfigData() void PIRsensorSwitch::appendConfigData()
{ {
oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field
oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field
oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field
} }
/** bool PIRsensorSwitch::readFromConfig(JsonObject &root)
* restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject &root)
{ {
bool oldEnabled = enabled; bool oldEnabled = enabled;
int8_t oldPin = PIRsensorPin; int8_t oldPin = PIRsensorPin;
@ -469,6 +520,7 @@ public:
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly; m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly; m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly; m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
m_override = top[FPSTR(_override)] | m_override;
HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery; HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery;
NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY; NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY;
@ -498,27 +550,5 @@ public:
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_haDiscovery)].isNull(); return !top[FPSTR(_override)].isNull();
} }
/**
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_ID_PIRSWITCH;
}
};
// strings to reduce flash memory usage (used more than twice)
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";
const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery";
const char PIRsensorSwitch::_notify[] PROGMEM = "notifications";

View File

@ -17,12 +17,6 @@
#ifndef TFT_HEIGHT #ifndef TFT_HEIGHT
#error Please define TFT_HEIGHT #error Please define TFT_HEIGHT
#endif #endif
#ifndef TFT_MOSI
#error Please define TFT_MOSI
#endif
#ifndef TFT_SCLK
#error Please define TFT_SCLK
#endif
#ifndef TFT_DC #ifndef TFT_DC
#error Please define TFT_DC #error Please define TFT_DC
#endif #endif
@ -140,8 +134,14 @@ class St7789DisplayUsermod : public Usermod {
*/ */
void setup() void setup()
{ {
PinManagerPinType pins[] = { { TFT_MOSI, true }, { TFT_MISO, false}, { TFT_SCLK, true }, { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; PinManagerPinType spiPins[] = { { spi_mosi, true }, { spi_miso, false}, { spi_sclk, true } };
if (!pinManager.allocateMultiplePins(pins, 7, PinOwner::UM_FourLineDisplay)) { enabled = false; return; } if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; }
PinManagerPinType displayPins[] = { { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } };
if (!pinManager.allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) {
pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI);
enabled = false;
return;
}
tft.init(); tft.init();
tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip. tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip.
@ -365,9 +365,6 @@ class St7789DisplayUsermod : public Usermod {
{ {
JsonObject top = root.createNestedObject("ST7789"); JsonObject top = root.createNestedObject("ST7789");
JsonArray pins = top.createNestedArray("pin"); JsonArray pins = top.createNestedArray("pin");
pins.add(TFT_MOSI);
pins.add(TFT_MISO);
pins.add(TFT_SCLK);
pins.add(TFT_CS); pins.add(TFT_CS);
pins.add(TFT_DC); pins.add(TFT_DC);
pins.add(TFT_RST); pins.add(TFT_RST);
@ -376,6 +373,13 @@ class St7789DisplayUsermod : public Usermod {
} }
void appendConfigData() {
oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');"));
oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');"));
oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');"));
oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI BL');"));
}
/* /*
* readFromConfig() can be used to read back the custom settings you added with addToConfig(). * readFromConfig() can be used to read back the custom settings you added with addToConfig().
* This is called by WLED when settings are loaded (currently this only happens once immediately after boot) * This is called by WLED when settings are loaded (currently this only happens once immediately after boot)

View File

@ -13,14 +13,6 @@
Adafruit_Si7021 si7021; Adafruit_Si7021 si7021;
#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards
uint8_t SCL_PIN = 22;
uint8_t SDA_PIN = 21;
#else //ESP8266 boards
uint8_t SCL_PIN = 5;
uint8_t SDA_PIN = 4;
#endif
class Si7021_MQTT_HA : public Usermod class Si7021_MQTT_HA : public Usermod
{ {
private: private:
@ -184,7 +176,6 @@ class Si7021_MQTT_HA : public Usermod
{ {
if (enabled) { if (enabled) {
Serial.println("Si7021_MQTT_HA: Starting!"); Serial.println("Si7021_MQTT_HA: Starting!");
Wire.begin(SDA_PIN, SCL_PIN);
Serial.println("Si7021_MQTT_HA: Initializing sensors.. "); Serial.println("Si7021_MQTT_HA: Initializing sensors.. ");
_initializeSensor(); _initializeSensor();
} }

View File

@ -52,7 +52,6 @@ class UsermodVL53L0XGestures : public Usermod {
void setup() { void setup() {
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
Wire.begin();
sensor.setTimeout(150); sensor.setTimeout(150);
if (!sensor.init()) if (!sensor.init())

View File

@ -569,16 +569,6 @@ class AudioReactive : public Usermod {
#else #else
int8_t i2sckPin = I2S_CKPIN; int8_t i2sckPin = I2S_CKPIN;
#endif #endif
#ifndef ES7243_SDAPIN
int8_t sdaPin = -1;
#else
int8_t sdaPin = ES7243_SDAPIN;
#endif
#ifndef ES7243_SCLPIN
int8_t sclPin = -1;
#else
int8_t sclPin = ES7243_SCLPIN;
#endif
#ifndef MCLK_PIN #ifndef MCLK_PIN
int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/ int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/
#else #else
@ -1136,7 +1126,7 @@ class AudioReactive : public Usermod {
DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only)."));
audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE);
delay(100); delay(100);
if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
break; break;
case 3: case 3:
DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
@ -1657,8 +1647,6 @@ class AudioReactive : public Usermod {
pinArray.add(i2swsPin); pinArray.add(i2swsPin);
pinArray.add(i2sckPin); pinArray.add(i2sckPin);
pinArray.add(mclkPin); pinArray.add(mclkPin);
pinArray.add(sdaPin);
pinArray.add(sclPin);
JsonObject cfg = top.createNestedObject("config"); JsonObject cfg = top.createNestedObject("config");
cfg[F("squelch")] = soundSquelch; cfg[F("squelch")] = soundSquelch;
@ -1719,8 +1707,6 @@ class AudioReactive : public Usermod {
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin);
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin);
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin);
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][4], sdaPin);
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][5], sclPin);
configComplete &= getJsonValue(top["config"][F("squelch")], soundSquelch); configComplete &= getJsonValue(top["config"][F("squelch")], soundSquelch);
configComplete &= getJsonValue(top["config"][F("gain")], sampleGain); configComplete &= getJsonValue(top["config"][F("gain")], sampleGain);
@ -1784,8 +1770,6 @@ class AudioReactive : public Usermod {
#else #else
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'<i>master clock</i>','I2S MCLK');")); oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'<i>master clock</i>','I2S MCLK');"));
#endif #endif
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',4,'','I2C SDA');"));
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',5,'','I2C SCL');"));
} }

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <Wire.h>
#include "wled.h" #include "wled.h"
#include <driver/i2s.h> #include <driver/i2s.h>
#include <driver/adc.h> #include <driver/adc.h>
@ -383,21 +382,12 @@ class I2SSource : public AudioSource {
*/ */
class ES7243 : public I2SSource { class ES7243 : public I2SSource {
private: private:
// I2C initialization functions for ES7243
void _es7243I2cBegin() {
bool i2c_initialized = Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U);
if (i2c_initialized == false) {
DEBUGSR_PRINTLN(F("AR: ES7243 failed to initialize I2C bus driver."));
}
}
void _es7243I2cWrite(uint8_t reg, uint8_t val) { void _es7243I2cWrite(uint8_t reg, uint8_t val) {
#ifndef ES7243_ADDR #ifndef ES7243_ADDR
Wire.beginTransmission(0x13);
#define ES7243_ADDR 0x13 // default address #define ES7243_ADDR 0x13 // default address
#else
Wire.beginTransmission(ES7243_ADDR);
#endif #endif
Wire.beginTransmission(ES7243_ADDR);
Wire.write((uint8_t)reg); Wire.write((uint8_t)reg);
Wire.write((uint8_t)val); Wire.write((uint8_t)val);
uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK
@ -407,7 +397,6 @@ class ES7243 : public I2SSource {
} }
void _es7243InitAdc() { void _es7243InitAdc() {
_es7243I2cBegin();
_es7243I2cWrite(0x00, 0x01); _es7243I2cWrite(0x00, 0x01);
_es7243I2cWrite(0x06, 0x00); _es7243I2cWrite(0x06, 0x00);
_es7243I2cWrite(0x05, 0x1B); _es7243I2cWrite(0x05, 0x1B);
@ -422,44 +411,20 @@ public:
_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
}; };
void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) {
// check that pins are valid
if ((sdaPin < 0) || (sclPin < 0)) {
DEBUGSR_PRINTF("\nAR: invalid ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin);
return;
}
if ((i2sckPin < 0) || (mclkPin < 0)) { if ((i2sckPin < 0) || (mclkPin < 0)) {
DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin);
return; return;
} }
// Reserve SDA and SCL pins of the I2C interface
PinManagerPinType es7243Pins[2] = { { sdaPin, true }, { sclPin, true } };
if (!pinManager.allocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C)) {
pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C);
DEBUGSR_PRINTF("\nAR: Failed to allocate ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin);
return;
}
pin_ES7243_SDA = sdaPin;
pin_ES7243_SCL = sclPin;
// First route mclk, then configure ADC over I2C, then configure I2S // First route mclk, then configure ADC over I2C, then configure I2S
_es7243InitAdc(); _es7243InitAdc();
I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
} }
void deinitialize() { void deinitialize() {
// Release SDA and SCL pins of the I2C interface
PinManagerPinType es7243Pins[2] = { { pin_ES7243_SDA, true }, { pin_ES7243_SCL, true } };
pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C);
I2SSource::deinitialize(); I2SSource::deinitialize();
} }
private:
int8_t pin_ES7243_SDA;
int8_t pin_ES7243_SCL;
}; };

View File

@ -87,10 +87,8 @@ class MPU6050Driver : public Usermod {
void setup() { void setup() {
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
// join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin(); Wire.setClock(400000U); // 400kHz I2C clock. Comment this line if having compilation difficulties
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
Fastwire::setup(400, true); Fastwire::setup(400, true);
#endif #endif

View File

@ -20,6 +20,14 @@
#define ON true #define ON true
#define OFF false #define OFF false
#ifndef USERMOD_USE_PCF8574
#undef USE_PCF8574
#define USE_PCF8574 false
#else
#undef USE_PCF8574
#define USE_PCF8574 true
#endif
#ifndef PCF8574_ADDRESS #ifndef PCF8574_ADDRESS
#define PCF8574_ADDRESS 0x20 // some may start at 0x38 #define PCF8574_ADDRESS 0x20 // some may start at 0x38
#endif #endif
@ -52,21 +60,15 @@ class MultiRelay : public Usermod {
// array of relays // array of relays
Relay _relay[MULTI_RELAY_MAX_RELAYS]; Relay _relay[MULTI_RELAY_MAX_RELAYS];
// switch timer start time uint32_t _switchTimerStart; // switch timer start time
uint32_t _switchTimerStart = 0; bool _oldMode; // old brightness
// old brightness bool enabled; // usermod enabled
bool _oldMode; bool initDone; // status of initialisation
bool usePcf8574;
// usermod enabled uint8_t addrPcf8574;
bool enabled = false; // needs to be configured (no default config) bool HAautodiscovery;
// status of initialisation uint16_t periodicBroadcastSec;
bool initDone = false; unsigned long lastBroadcast;
bool usePcf8574 = false;
uint8_t addrPcf8574 = PCF8574_ADDRESS;
bool HAautodiscovery = false;
uint16_t periodicBroadcastSec = 60;
unsigned long lastBroadcast = 0;
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
static const char _name[]; static const char _name[];
@ -103,7 +105,7 @@ class MultiRelay : public Usermod {
/** /**
* desctructor * desctructor
*/ */
~MultiRelay() {} //~MultiRelay() {}
/** /**
* Enable/Disable the usermod * Enable/Disable the usermod
@ -331,7 +333,16 @@ byte MultiRelay::IOexpanderRead(int address) {
// public methods // public methods
MultiRelay::MultiRelay() { MultiRelay::MultiRelay()
: _switchTimerStart(0)
, enabled(false)
, initDone(false)
, usePcf8574(USE_PCF8574)
, addrPcf8574(PCF8574_ADDRESS)
, HAautodiscovery(false)
, periodicBroadcastSec(60)
, lastBroadcast(0)
{
const int8_t defPins[] = {MULTI_RELAY_PINS}; const int8_t defPins[] = {MULTI_RELAY_PINS};
for (size_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (size_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
_relay[i].pin = i<sizeof(defPins) ? defPins[i] : -1; _relay[i].pin = i<sizeof(defPins) ? defPins[i] : -1;

View File

@ -6,7 +6,6 @@
#include "wled.h" #include "wled.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h>
#include <Adafruit_Sensor.h> #include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h> #include <Adafruit_BMP280.h>
#include <Adafruit_CCS811.h> #include <Adafruit_CCS811.h>
@ -16,14 +15,6 @@ Adafruit_BMP280 bmp;
Adafruit_Si7021 si7021; Adafruit_Si7021 si7021;
Adafruit_CCS811 ccs811; Adafruit_CCS811 ccs811;
#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards
uint8_t SCL_PIN = 22;
uint8_t SDA_PIN = 21;
#else //ESP8266 boards
uint8_t SCL_PIN = 5;
uint8_t SDA_PIN = 4;
#endif
class UserMod_SensorsToMQTT : public Usermod class UserMod_SensorsToMQTT : public Usermod
{ {
private: private:
@ -231,7 +222,6 @@ public:
void setup() void setup()
{ {
Serial.println("Starting!"); Serial.println("Starting!");
Wire.begin(SDA_PIN, SCL_PIN);
Serial.println("Initializing sensors.. "); Serial.println("Initializing sensors.. ");
_initialize(); _initialize();
} }

View File

@ -1,20 +1,24 @@
#pragma once #pragma once
#include "wled.h" #include "wled.h"
#undef U8X8_NO_HW_I2C // borrowed from WLEDMM: we do want I2C hardware drivers - if possible
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/ #include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
#include "4LD_wled_fonts.c" #include "4LD_wled_fonts.c"
#ifndef FLD_ESP32_NO_THREADS
#define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!!
#endif
// //
// Insired by the usermod_v2_four_line_display // Inspired by the usermod_v2_four_line_display
// //
// v2 usermod for using 128x32 or 128x64 i2c // v2 usermod for using 128x32 or 128x64 i2c
// OLED displays to provide a four line display // OLED displays to provide a four line display
// for WLED. // for WLED.
// //
// Dependencies // Dependencies
// * This usermod REQURES the ModeSortUsermod
// * This Usermod works best, by far, when coupled // * This Usermod works best, by far, when coupled
// with RotaryEncoderUIUsermod. // with RotaryEncoderUI ALT Usermod.
// //
// Make sure to enable NTP and set your time zone in WLED Config | Time. // Make sure to enable NTP and set your time zone in WLED Config | Time.
// //
@ -23,22 +27,14 @@
// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine) // REQUIREMENT: * U8g2 (the version already in platformio.ini is fine)
// REQUIREMENT: * Wire // REQUIREMENT: * Wire
// //
// If display does not work or looks corrupted check the
// constructor reference:
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
// or check the gallery:
// https://github.com/olikraus/u8g2/wiki/gallery
//The SCL and SDA pins are defined here.
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL i2c_scl
#endif
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA i2c_sda
#endif
#ifndef FLD_PIN_CLOCKSPI
#define FLD_PIN_CLOCKSPI spi_sclk
#endif
#ifndef FLD_PIN_DATASPI
#define FLD_PIN_DATASPI spi_mosi
#endif
#ifndef FLD_PIN_CS #ifndef FLD_PIN_CS
#define FLD_PIN_CS spi_cs #define FLD_PIN_CS 15
#endif #endif
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
@ -97,9 +93,11 @@ typedef enum {
class FourLineDisplayUsermod : public Usermod { class FourLineDisplayUsermod : public Usermod {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
public: public:
FourLineDisplayUsermod() { if (!instance) instance = this; } FourLineDisplayUsermod() { if (!instance) instance = this; }
static FourLineDisplayUsermod* getInstance(void) { return instance; } static FourLineDisplayUsermod* getInstance(void) { return instance; }
#endif
private: private:
@ -112,10 +110,10 @@ class FourLineDisplayUsermod : public Usermod {
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
#ifndef FLD_SPI_DEFAULT #ifndef FLD_SPI_DEFAULT
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA int8_t ioPin[3] = {-1, -1, -1}; // I2C pins: SCL, SDA
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000) uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
#else #else
int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST int8_t ioPin[3] = {FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // custom SPI pins: CS, DC, RST
uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz) uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz)
#endif #endif
@ -178,7 +176,194 @@ class FourLineDisplayUsermod : public Usermod {
// https://github.com/olikraus/u8g2/wiki/gallery // https://github.com/olikraus/u8g2/wiki/gallery
// some displays need this to properly apply contrast // some displays need this to properly apply contrast
void setVcomh(bool highContrast) { void setVcomh(bool highContrast);
void startDisplay();
/**
* Wrappers for screen drawing
*/
void setFlipMode(uint8_t mode);
void setContrast(uint8_t contrast);
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false);
void draw2x2String(uint8_t col, uint8_t row, const char *string);
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false);
void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font);
void draw2x2GlyphIcons();
uint8_t getCols();
void clear();
void setPowerSave(uint8_t save);
void center(String &line, uint8_t width);
/**
* Display the current date and time in large characters
* on the middle rows. Based 24 or 12 hour depending on
* the useAMPM configuration.
*/
void showTime();
/**
* Enable sleep (turn the display off) or clock mode.
*/
void sleepOrClock(bool enabled);
public:
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void setup();
// gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here
void connected();
/**
* Da loop.
*/
void loop();
//function to update lastredraw
inline void updateRedrawTime() { lastRedraw = millis(); }
/**
* Redraw the screen (but only if things have changed
* or if forceRedraw).
*/
void redraw(bool forceRedraw);
void updateBrightness();
void updateSpeed();
void updateIntensity();
void drawStatusIcons();
/**
* marks the position of the arrow showing
* the current setting being changed
* pass line and colum info
*/
void setMarkLine(byte newMarkLineNum, byte newMarkColNum);
//Draw the arrow for the current setting beiong changed
void drawArrow();
//Display the current effect or palette (desiredEntry)
// on the appropriate line (row).
void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row);
/**
* If there screen is off or in clock is displayed,
* this will return true. This allows us to throw away
* the first input from the rotary encoder but
* to wake up the screen.
*/
bool wakeDisplay();
/**
* Allows you to show one line and a glyph as overlay for a period of time.
* Clears the screen and prints.
* Used in Rotary Encoder usermod.
*/
void overlay(const char* line1, long showHowLong, byte glyphType);
/**
* Allows you to show Akemi WLED logo overlay for a period of time.
* Clears the screen and prints.
*/
void overlayLogo(long showHowLong);
/**
* Allows you to show two lines as overlay for a period of time.
* Clears the screen and prints.
* Used in Auto Save usermod
*/
void overlay(const char* line1, const char* line2, long showHowLong);
void networkOverlay(const char* line1, long showHowLong);
/**
* handleButton() can be used to override default button behaviour. Returning true
* will prevent button working in a default way.
* Replicating button.cpp
*/
bool handleButton(uint8_t b);
void onUpdateBegin(bool init);
/*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor
*/
//void addToJsonInfo(JsonObject& root);
/*
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void addToJsonState(JsonObject& root);
/*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void readFromJsonState(JsonObject& root);
void appendConfigData();
/*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
* If you want to force saving the current state, use serializeConfig() in your loop().
*
* CAUTION: serializeConfig() will initiate a filesystem write operation.
* It might cause the LEDs to stutter and will cause flash wear if called too often.
* Use it sparingly and always in the loop, never in network callbacks!
*
* addToConfig() will also not yet add your setting to one of the settings pages automatically.
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
*
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/
void addToConfig(JsonObject& root);
/*
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
* This is called by WLED when settings are loaded (currently this only happens once immediately after boot)
*
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*/
bool readFromConfig(JsonObject& root);
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() {
return USERMOD_ID_FOUR_LINE_DISP;
}
};
// strings to reduce flash memory usage (used more than twice)
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
const char FourLineDisplayUsermod::_enabled[] PROGMEM = "enabled";
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate-ms";
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
const char FourLineDisplayUsermod::_showSeconds[] PROGMEM = "showSeconds";
const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz";
const char FourLineDisplayUsermod::_contrastFix[] PROGMEM = "contrastFix";
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
FourLineDisplayUsermod *FourLineDisplayUsermod::instance = nullptr;
#endif
// some displays need this to properly apply contrast
void FourLineDisplayUsermod::setVcomh(bool highContrast) {
if (type == NONE || !enabled) return;
u8x8_t *u8x8_struct = u8x8->getU8x8(); u8x8_t *u8x8_struct = u8x8->getU8x8();
u8x8_cad_StartTransfer(u8x8_struct); u8x8_cad_StartTransfer(u8x8_struct);
u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value
@ -186,18 +371,32 @@ class FourLineDisplayUsermod : public Usermod {
u8x8_cad_EndTransfer(u8x8_struct); u8x8_cad_EndTransfer(u8x8_struct);
} }
void FourLineDisplayUsermod::startDisplay() {
if (type == NONE || !enabled) return;
lineHeight = u8x8->getRows() > 4 ? 2 : 1;
DEBUG_PRINTLN(F("Starting display."));
u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin();
setFlipMode(flip);
setVcomh(contrastFix);
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...");
overlayLogo(3500);
}
/** /**
* Wrappers for screen drawing * Wrappers for screen drawing
*/ */
void setFlipMode(uint8_t mode) { void FourLineDisplayUsermod::setFlipMode(uint8_t mode) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
u8x8->setFlipMode(mode); u8x8->setFlipMode(mode);
} }
void setContrast(uint8_t contrast) { void FourLineDisplayUsermod::setContrast(uint8_t contrast) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
u8x8->setContrast(contrast); u8x8->setContrast(contrast);
} }
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { void FourLineDisplayUsermod::drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->setFont(u8x8_font_chroma48medium8_r); u8x8->setFont(u8x8_font_chroma48medium8_r);
@ -205,14 +404,14 @@ class FourLineDisplayUsermod : public Usermod {
else u8x8->drawString(col, row, string); else u8x8->drawString(col, row, string);
drawing = false; drawing = false;
} }
void draw2x2String(uint8_t col, uint8_t row, const char *string) { void FourLineDisplayUsermod::draw2x2String(uint8_t col, uint8_t row, const char *string) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->setFont(u8x8_font_chroma48medium8_r); u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->draw2x2String(col, row, string); u8x8->draw2x2String(col, row, string);
drawing = false; drawing = false;
} }
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) { void FourLineDisplayUsermod::drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->setFont(font); u8x8->setFont(font);
@ -220,35 +419,35 @@ class FourLineDisplayUsermod : public Usermod {
else u8x8->drawGlyph(col, row, glyph); else u8x8->drawGlyph(col, row, glyph);
drawing = false; drawing = false;
} }
void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) { void FourLineDisplayUsermod::draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->setFont(font); u8x8->setFont(font);
u8x8->draw2x2Glyph(col, row, glyph); u8x8->draw2x2Glyph(col, row, glyph);
drawing = false; drawing = false;
} }
uint8_t getCols() { uint8_t FourLineDisplayUsermod::getCols() {
if (type==NONE || !enabled) return 0; if (type==NONE || !enabled) return 0;
return u8x8->getCols(); return u8x8->getCols();
} }
void clear() { void FourLineDisplayUsermod::clear() {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->clear(); u8x8->clear();
drawing = false; drawing = false;
} }
void setPowerSave(uint8_t save) { void FourLineDisplayUsermod::setPowerSave(uint8_t save) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
u8x8->setPowerSave(save); u8x8->setPowerSave(save);
} }
void center(String &line, uint8_t width) { void FourLineDisplayUsermod::center(String &line, uint8_t width) {
int len = line.length(); int len = line.length();
if (len<width) for (byte i=(width-len)/2; i>0; i--) line = ' ' + line; if (len<width) for (byte i=(width-len)/2; i>0; i--) line = ' ' + line;
for (byte i=line.length(); i<width; i++) line += ' '; for (byte i=line.length(); i<width; i++) line += ' ';
} }
void draw2x2GlyphIcons() { void FourLineDisplayUsermod::draw2x2GlyphIcons() {
drawing = true; drawing = true;
if (lineHeight == 2) { if (lineHeight == 2) {
drawGlyph( 1, 0, 1, u8x8_4LineDisplay_WLED_icons_2x2, true); //brightness icon drawGlyph( 1, 0, 1, u8x8_4LineDisplay_WLED_icons_2x2, true); //brightness icon
@ -271,7 +470,7 @@ class FourLineDisplayUsermod : public Usermod {
* on the middle rows. Based 24 or 12 hour depending on * on the middle rows. Based 24 or 12 hour depending on
* the useAMPM configuration. * the useAMPM configuration.
*/ */
void showTime() { void FourLineDisplayUsermod::showTime() {
if (type == NONE || !enabled || !displayTurnedOff) return; if (type == NONE || !enabled || !displayTurnedOff) return;
unsigned long now = millis(); unsigned long now = millis();
@ -317,7 +516,7 @@ class FourLineDisplayUsermod : public Usermod {
/** /**
* Enable sleep (turn the display off) or clock mode. * Enable sleep (turn the display off) or clock mode.
*/ */
void sleepOrClock(bool enabled) { void FourLineDisplayUsermod::sleepOrClock(bool enabled) {
if (enabled) { if (enabled) {
displayTurnedOff = true; displayTurnedOff = true;
if (clockMode && ntpEnabled) { if (clockMode && ntpEnabled) {
@ -331,143 +530,66 @@ class FourLineDisplayUsermod : public Usermod {
} }
} }
public:
// gets called once at boot. Do all initialization that doesn't depend on // gets called once at boot. Do all initialization that doesn't depend on
// network here // network here
void setup() { void FourLineDisplayUsermod::setup() {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
PinOwner po = PinOwner::UM_FourLineDisplay;
// check if pins are -1 and disable usermod as PinManager::allocateMultiplePins() will accept -1 as a valid pin
if (isSPI) { if (isSPI) {
uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } };
uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; if (ioPin[0]==-1 || ioPin[1]==-1 || ioPin[1]==-1) { type=NONE; return; }
if (ioPin[0] < 0 || ioPin[1] < 0) {
ioPin[0] = hw_sclk;
ioPin[1] = hw_mosi;
}
isHW = (ioPin[0]==hw_sclk && ioPin[1]==hw_mosi);
PinManagerPinType cspins[3] = { { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true } };
if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
if (isHW) po = PinOwner::HW_SPI; // allow multiple allocations of HW I2C bus pins PinManagerPinType pins[2] = { { spi_sclk, true }, { spi_mosi, true } };
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; if (spi_sclk==-1 || spi_mosi==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_SPI)) {
if (!pinManager.allocateMultiplePins(pins, 2, po)) {
pinManager.deallocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay); pinManager.deallocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay);
type = NONE; type = NONE;
return; return;
} }
} else { } else {
uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; PinManagerPinType pins[2] = { {i2c_scl, true }, { i2c_sda, true } };
uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; if (i2c_scl==-1 || i2c_sda==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { type=NONE; return; }
if (ioPin[0] < 0 || ioPin[1] < 0) {
ioPin[0] = hw_scl;
ioPin[1] = hw_sda;
}
isHW = (ioPin[0]==hw_scl && ioPin[1]==hw_sda);
if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
PinManagerPinType pins[2] = { {ioPin[0], true }, { ioPin[1], true } };
if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; }
} }
DEBUG_PRINTLN(F("Allocating display.")); DEBUG_PRINTLN(F("Allocating display."));
/*
// At some point it may be good to not new/delete U8X8 object but use this instead
// (does not currently work)
//-------------------------------------------------------------------------------
switch (type) { switch (type) {
case SSD1306: // U8X8 uses Wire (or Wire1 with 2ND constructor) and will use existing Wire properties (calls Wire.begin() though)
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); case SSD1306: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(); break;
break; case SH1106: u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(); break;
case SH1106: case SSD1306_64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(); break;
u8x8_Setup(u8x8.getU8x8(), u8x8_d_sh1106_128x64_winstar, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); case SSD1305: u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(); break;
break; case SSD1305_64: u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(); break;
case SSD1306_64: // U8X8 uses global SPI variable that is attached to VSPI bus on ESP32
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); case SSD1306_SPI: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset
break; case SSD1306_SPI64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset
case SSD1305: // catchall
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1305_128x32_adafruit, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); default: u8x8 = (U8X8 *) new U8X8_NULL(); break;
break;
case SSD1305_64:
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino);
break;
case SSD1306_SPI:
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_4wire_sw_spi, u8x8_gpio_and_delay_arduino);
break;
case SSD1306_SPI64:
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, u8x8_byte_arduino_4wire_sw_spi, u8x8_gpio_and_delay_arduino);
break;
default:
type = NONE;
return;
}
if (isSPI) {
if (!isHW) u8x8_SetPin_4Wire_SW_SPI(u8x8.getU8x8(), ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else u8x8_SetPin_4Wire_HW_SPI(u8x8.getU8x8(), ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
} else {
if (!isHW) u8x8_SetPin_SW_I2C(u8x8.getU8x8(), ioPin[0], ioPin[1], U8X8_PIN_NONE); // SCL, SDA, reset
else u8x8_SetPin_HW_I2C(u8x8.getU8x8(), U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
}
*/
switch (type) {
case SSD1306:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SH1106:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SSD1306_64:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SSD1305:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SSD1305_64:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SSD1306_SPI:
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
break;
case SSD1306_SPI64:
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
break;
default:
u8x8 = nullptr;
} }
if (nullptr == u8x8) { if (nullptr == u8x8) {
DEBUG_PRINTLN(F("Display init failed.")); DEBUG_PRINTLN(F("Display init failed."));
pinManager.deallocateMultiplePins((const uint8_t*)ioPin, isSPI ? 5 : 2, po); if (isSPI) {
int8_t pins[] = {spi_sclk, spi_mosi};
pinManager.deallocateMultiplePins((const uint8_t*)pins, 2, PinOwner::HW_SPI);
pinManager.deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay);
} else {
int8_t pins[] = {i2c_scl, i2c_sda};
pinManager.deallocateMultiplePins((const uint8_t*)pins, 2, PinOwner::HW_I2C);
}
type = NONE; type = NONE;
return; return;
} }
lineHeight = u8x8->getRows() > 4 ? 2 : 1; startDisplay();
DEBUG_PRINTLN(F("Starting display."));
u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin();
setFlipMode(flip);
setVcomh(contrastFix);
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...");
overlayLogo(3500);
onUpdateBegin(false); // create Display task onUpdateBegin(false); // create Display task
initDone = true; initDone = true;
} }
// gets called every time WiFi is (re-)connected. Initialize own network // gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here // interfaces here
void connected() { void FourLineDisplayUsermod::connected() {
knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() :
knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
networkOverlay(PSTR("NETWORK INFO"),7000); networkOverlay(PSTR("NETWORK INFO"),7000);
@ -476,8 +598,8 @@ class FourLineDisplayUsermod : public Usermod {
/** /**
* Da loop. * Da loop.
*/ */
void loop() { void FourLineDisplayUsermod::loop() {
#ifndef ARDUINO_ARCH_ESP32 #if !(defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS))
if (!enabled || strip.isUpdating()) return; if (!enabled || strip.isUpdating()) return;
unsigned long now = millis(); unsigned long now = millis();
if (now < nextUpdate) return; if (now < nextUpdate) return;
@ -486,16 +608,11 @@ class FourLineDisplayUsermod : public Usermod {
#endif #endif
} }
//function to update lastredraw
void updateRedrawTime() {
lastRedraw = millis();
}
/** /**
* Redraw the screen (but only if things have changed * Redraw the screen (but only if things have changed
* or if forceRedraw). * or if forceRedraw).
*/ */
void redraw(bool forceRedraw) { void FourLineDisplayUsermod::redraw(bool forceRedraw) {
bool needRedraw = false; bool needRedraw = false;
unsigned long now = millis(); unsigned long now = millis();
@ -607,7 +724,12 @@ class FourLineDisplayUsermod : public Usermod {
showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info
} }
void updateBrightness() { void FourLineDisplayUsermod::updateBrightness() {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing || lockRedraw) return;
#endif
knownBrightness = bri; knownBrightness = bri;
if (overlayUntil == 0) { if (overlayUntil == 0) {
lockRedraw = true; lockRedraw = true;
@ -619,7 +741,12 @@ class FourLineDisplayUsermod : public Usermod {
} }
} }
void updateSpeed() { void FourLineDisplayUsermod::updateSpeed() {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing || lockRedraw) return;
#endif
knownEffectSpeed = effectSpeed; knownEffectSpeed = effectSpeed;
if (overlayUntil == 0) { if (overlayUntil == 0) {
lockRedraw = true; lockRedraw = true;
@ -631,7 +758,12 @@ class FourLineDisplayUsermod : public Usermod {
} }
} }
void updateIntensity() { void FourLineDisplayUsermod::updateIntensity() {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing || lockRedraw) return;
#endif
knownEffectIntensity = effectIntensity; knownEffectIntensity = effectIntensity;
if (overlayUntil == 0) { if (overlayUntil == 0) {
lockRedraw = true; lockRedraw = true;
@ -643,7 +775,12 @@ class FourLineDisplayUsermod : public Usermod {
} }
} }
void drawStatusIcons() { void FourLineDisplayUsermod::drawStatusIcons() {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing || lockRedraw) return;
#endif
uint8_t col = 15; uint8_t col = 15;
uint8_t row = 0; uint8_t row = 0;
lockRedraw = true; lockRedraw = true;
@ -660,13 +797,18 @@ class FourLineDisplayUsermod : public Usermod {
* the current setting being changed * the current setting being changed
* pass line and colum info * pass line and colum info
*/ */
void setMarkLine(byte newMarkLineNum, byte newMarkColNum) { void FourLineDisplayUsermod::setMarkLine(byte newMarkLineNum, byte newMarkColNum) {
markLineNum = newMarkLineNum; markLineNum = newMarkLineNum;
markColNum = newMarkColNum; markColNum = newMarkColNum;
} }
//Draw the arrow for the current setting beiong changed //Draw the arrow for the current setting beiong changed
void drawArrow() { void FourLineDisplayUsermod::drawArrow() {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing || lockRedraw) return;
#endif
lockRedraw = true; lockRedraw = true;
if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1); if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1);
lockRedraw = false; lockRedraw = false;
@ -674,7 +816,12 @@ class FourLineDisplayUsermod : public Usermod {
//Display the current effect or palette (desiredEntry) //Display the current effect or palette (desiredEntry)
// on the appropriate line (row). // on the appropriate line (row).
void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing || lockRedraw) return;
#endif
char lineBuffer[MAX_JSON_CHARS]; char lineBuffer[MAX_JSON_CHARS];
if (overlayUntil == 0) { if (overlayUntil == 0) {
lockRedraw = true; lockRedraw = true;
@ -740,12 +887,14 @@ class FourLineDisplayUsermod : public Usermod {
* the first input from the rotary encoder but * the first input from the rotary encoder but
* to wake up the screen. * to wake up the screen.
*/ */
bool wakeDisplay() { bool FourLineDisplayUsermod::wakeDisplay() {
if (type == NONE || !enabled) return false; if (type == NONE || !enabled) return false;
if (displayTurnedOff) { if (displayTurnedOff) {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis(); unsigned long now = millis();
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing) return false; if (drawing || lockRedraw) return false;
#endif
lockRedraw = true; lockRedraw = true;
clear(); clear();
// Turn the display back on // Turn the display back on
@ -761,10 +910,12 @@ class FourLineDisplayUsermod : public Usermod {
* Clears the screen and prints. * Clears the screen and prints.
* Used in Rotary Encoder usermod. * Used in Rotary Encoder usermod.
*/ */
void overlay(const char* line1, long showHowLong, byte glyphType) { void FourLineDisplayUsermod::overlay(const char* line1, long showHowLong, byte glyphType) {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis(); unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing) return; if (drawing || lockRedraw) return;
#endif
lockRedraw = true; lockRedraw = true;
// Turn the display back on // Turn the display back on
if (!wakeDisplay()) clear(); if (!wakeDisplay()) clear();
@ -786,10 +937,12 @@ class FourLineDisplayUsermod : public Usermod {
* Allows you to show Akemi WLED logo overlay for a period of time. * Allows you to show Akemi WLED logo overlay for a period of time.
* Clears the screen and prints. * Clears the screen and prints.
*/ */
void overlayLogo(long showHowLong) { void FourLineDisplayUsermod::overlayLogo(long showHowLong) {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis(); unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing) return; if (drawing || lockRedraw) return;
#endif
lockRedraw = true; lockRedraw = true;
// Turn the display back on // Turn the display back on
if (!wakeDisplay()) clear(); if (!wakeDisplay()) clear();
@ -848,10 +1001,12 @@ class FourLineDisplayUsermod : public Usermod {
* Clears the screen and prints. * Clears the screen and prints.
* Used in Auto Save usermod * Used in Auto Save usermod
*/ */
void overlay(const char* line1, const char* line2, long showHowLong) { void FourLineDisplayUsermod::overlay(const char* line1, const char* line2, long showHowLong) {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis(); unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing) return; if (drawing || lockRedraw) return;
#endif
lockRedraw = true; lockRedraw = true;
// Turn the display back on // Turn the display back on
if (!wakeDisplay()) clear(); if (!wakeDisplay()) clear();
@ -870,10 +1025,12 @@ class FourLineDisplayUsermod : public Usermod {
lockRedraw = false; lockRedraw = false;
} }
void networkOverlay(const char* line1, long showHowLong) { void FourLineDisplayUsermod::networkOverlay(const char* line1, long showHowLong) {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis(); unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing) return; if (drawing || lockRedraw) return;
#endif
lockRedraw = true; lockRedraw = true;
String line; String line;
@ -915,7 +1072,7 @@ class FourLineDisplayUsermod : public Usermod {
* will prevent button working in a default way. * will prevent button working in a default way.
* Replicating button.cpp * Replicating button.cpp
*/ */
bool handleButton(uint8_t b) { bool FourLineDisplayUsermod::handleButton(uint8_t b) {
yield(); yield();
if (!enabled if (!enabled
|| b // butto 0 only || b // butto 0 only
@ -989,8 +1146,8 @@ class FourLineDisplayUsermod : public Usermod {
#else #else
#define ARDUINO_RUNNING_CORE 1 #define ARDUINO_RUNNING_CORE 1
#endif #endif
void onUpdateBegin(bool init) { void FourLineDisplayUsermod::onUpdateBegin(bool init) {
#ifdef ARDUINO_ARCH_ESP32 #if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
if (init && Display_Task) { if (init && Display_Task) {
vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash
} else { } else {
@ -1026,7 +1183,7 @@ class FourLineDisplayUsermod : public Usermod {
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor * Below it is shown how this could be used for e.g. a light sensor
*/ */
//void addToJsonInfo(JsonObject& root) { //void FourLineDisplayUsermod::addToJsonInfo(JsonObject& root) {
//JsonObject user = root["u"]; //JsonObject user = root["u"];
//if (user.isNull()) user = root.createNestedObject("u"); //if (user.isNull()) user = root.createNestedObject("u");
//JsonArray data = user.createNestedArray(F("4LineDisplay")); //JsonArray data = user.createNestedArray(F("4LineDisplay"));
@ -1037,18 +1194,18 @@ class FourLineDisplayUsermod : public Usermod {
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
//void addToJsonState(JsonObject& root) { //void FourLineDisplayUsermod::addToJsonState(JsonObject& root) {
//} //}
/* /*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
//void readFromJsonState(JsonObject& root) { //void FourLineDisplayUsermod::readFromJsonState(JsonObject& root) {
// if (!initDone) return; // prevent crash on boot applyPreset() // if (!initDone) return; // prevent crash on boot applyPreset()
//} //}
void appendConfigData() { void FourLineDisplayUsermod::appendConfigData() {
oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); oappend(SET_F("dd=addDropdown('4LineDisplay','type');"));
oappend(SET_F("addOption(dd,'None',0);")); oappend(SET_F("addOption(dd,'None',0);"));
oappend(SET_F("addOption(dd,'SSD1306',1);")); oappend(SET_F("addOption(dd,'SSD1306',1);"));
@ -1058,11 +1215,10 @@ class FourLineDisplayUsermod : public Usermod {
oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); oappend(SET_F("addOption(dd,'SSD1305 128x64',5);"));
oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); oappend(SET_F("addOption(dd,'SSD1306 SPI',6);"));
oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'<i>-1 use global</i>','I2C/SPI CLK');")); oappend(SET_F("addInfo('4LineDisplay:type',1,'<br><i class=\"warn\">Change may require reboot</i>','');"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'<i>-1 use global</i>','I2C/SPI DTA');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI CS');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',3,'','SPI DC');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',4,'','SPI RST');"));
} }
/* /*
@ -1079,27 +1235,13 @@ class FourLineDisplayUsermod : public Usermod {
* *
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/ */
void addToConfig(JsonObject& root) { void FourLineDisplayUsermod::addToConfig(JsonObject& root) {
// determine if we are using global HW pins (data & clock)
int8_t hw_dta, hw_clk;
if ((type == SSD1306_SPI || type == SSD1306_SPI64)) {
hw_clk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk;
hw_dta = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi;
} else {
hw_clk = i2c_scl<0 ? HW_PIN_SCL : i2c_scl;
hw_dta = i2c_sda<0 ? HW_PIN_SDA : i2c_sda;
}
JsonObject top = root.createNestedObject(FPSTR(_name)); JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled; top[FPSTR(_enabled)] = enabled;
JsonArray io_pin = top.createNestedArray("pin");
for (int i=0; i<5; i++) {
if (i==0 && ioPin[i]==hw_clk) io_pin.add(-1); // do not store global HW pin
else if (i==1 && ioPin[i]==hw_dta) io_pin.add(-1); // do not store global HW pin
else io_pin.add(ioPin[i]);
}
top["type"] = type; top["type"] = type;
JsonArray io_pin = top.createNestedArray("pin");
for (int i=0; i<3; i++) io_pin.add(ioPin[i]);
top[FPSTR(_flip)] = (bool) flip; top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_contrast)] = contrast; top[FPSTR(_contrast)] = contrast;
top[FPSTR(_contrastFix)] = (bool) contrastFix; top[FPSTR(_contrastFix)] = (bool) contrastFix;
@ -1122,10 +1264,10 @@ class FourLineDisplayUsermod : public Usermod {
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :) * If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*/ */
bool readFromConfig(JsonObject& root) { bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) {
bool needsRedraw = false; bool needsRedraw = false;
DisplayType newType = type; DisplayType newType = type;
int8_t oldPin[5]; for (byte i=0; i<5; i++) oldPin[i] = ioPin[i]; int8_t oldPin[3]; for (byte i=0; i<3; i++) oldPin[i] = ioPin[i];
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) { if (top.isNull()) {
@ -1136,7 +1278,7 @@ class FourLineDisplayUsermod : public Usermod {
enabled = top[FPSTR(_enabled)] | enabled; enabled = top[FPSTR(_enabled)] | enabled;
newType = top["type"] | newType; newType = top["type"] | newType;
for (byte i=0; i<5; i++) ioPin[i] = top["pin"][i] | ioPin[i]; for (byte i=0; i<3; i++) ioPin[i] = top["pin"][i] | ioPin[i];
flip = top[FPSTR(_flip)] | flip; flip = top[FPSTR(_flip)] | flip;
contrast = top[FPSTR(_contrast)] | contrast; contrast = top[FPSTR(_contrast)] | contrast;
#ifndef ARDUINO_ARCH_ESP32 #ifndef ARDUINO_ARCH_ESP32
@ -1162,26 +1304,76 @@ class FourLineDisplayUsermod : public Usermod {
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page // changing parameters from settings page
bool pinsChanged = false; bool pinsChanged = false;
for (byte i=0; i<5; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; } for (byte i=0; i<3; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; }
if (pinsChanged || type!=newType) { if (pinsChanged || type!=newType) {
if (type != NONE) delete u8x8;
PinOwner po = PinOwner::UM_FourLineDisplay;
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
bool newSPI = (newType == SSD1306_SPI || newType == SSD1306_SPI64);
if (isSPI) { if (isSPI) {
pinManager.deallocateMultiplePins((const uint8_t *)(&oldPin[2]), 3, po); if (pinsChanged || !newSPI) pinManager.deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay);
uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; if (!newSPI) {
uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; // was SPI but is no longer SPI
bool isHW = (oldPin[0]==hw_sclk && oldPin[1]==hw_mosi); int8_t oldPins[] = {spi_sclk, spi_mosi};
if (isHW) po = PinOwner::HW_SPI; pinManager.deallocateMultiplePins((const uint8_t*)oldPins, 2, PinOwner::HW_SPI);
PinManagerPinType pins[2] = { {i2c_scl, true }, { i2c_sda, true } };
if (i2c_scl==-1 || i2c_sda==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { newType=NONE; }
} else { } else {
uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; // still SPI but pins changed
uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } };
bool isHW = (oldPin[0]==hw_scl && oldPin[1]==hw_sda); if (ioPin[0]==-1 || ioPin[1]==-1 || ioPin[1]==-1) { newType=NONE; }
if (isHW) po = PinOwner::HW_I2C; else if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; }
}
} else if (newSPI) {
// was I2C but is now SPI
int8_t oldPins[] = {i2c_scl, i2c_sda};
pinManager.deallocateMultiplePins((const uint8_t*)oldPins, 2, PinOwner::HW_I2C);
PinManagerPinType pins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } };
if (ioPin[0]==-1 || ioPin[1]==-1 || ioPin[1]==-1) { newType=NONE; }
else if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; }
else {
PinManagerPinType pins[2] = { { spi_sclk, true }, { spi_mosi, true } };
if (spi_sclk==-1 || spi_mosi==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_SPI)) {
pinManager.deallocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay);
newType = NONE;
}
}
} else {
// just I2C tye changed
} }
pinManager.deallocateMultiplePins((const uint8_t *)oldPin, 2, po);
type = newType; type = newType;
setup(); switch (type) {
case SSD1306:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SH1106:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_sh1106_128x64_winstar, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SSD1306_64:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SSD1305:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x32_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SSD1305_64:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SSD1306_SPI:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset
break;
case SSD1306_SPI64:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset
break;
default:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_null_cb, u8x8_cad_empty, u8x8_byte_empty, u8x8_dummy_cb);
break;
}
startDisplay();
needsRedraw |= true; needsRedraw |= true;
} else { } else {
u8x8->setBusClock(ioFrequency); // can be used for SPI too u8x8->setBusClock(ioFrequency); // can be used for SPI too
@ -1196,27 +1388,3 @@ class FourLineDisplayUsermod : public Usermod {
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_contrastFix)].isNull(); return !top[FPSTR(_contrastFix)].isNull();
} }
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() {
return USERMOD_ID_FOUR_LINE_DISP;
}
};
// strings to reduce flash memory usage (used more than twice)
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
const char FourLineDisplayUsermod::_enabled[] PROGMEM = "enabled";
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate-ms";
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
const char FourLineDisplayUsermod::_showSeconds[] PROGMEM = "showSeconds";
const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz";
const char FourLineDisplayUsermod::_contrastFix[] PROGMEM = "contrastFix";
FourLineDisplayUsermod *FourLineDisplayUsermod::instance = nullptr;

View File

@ -45,7 +45,23 @@
#define ENCODER_SW_PIN 19 #define ENCODER_SW_PIN 19
#endif #endif
// The last UI state, remove color and saturation option if diplay not active(too many options) #ifndef USERMOD_USE_PCF8574
#undef USE_PCF8574
#define USE_PCF8574 false
#else
#undef USE_PCF8574
#define USE_PCF8574 true
#endif
#ifndef PCF8574_ADDRESS
#define PCF8574_ADDRESS 0x20 // some may start at 0x38
#endif
#ifndef PCF8574_INT_PIN
#define PCF8574_INT_PIN -1 // GPIO connected to INT pin on PCF8574
#endif
// The last UI state, remove color and saturation option if display not active (too many options)
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
#define LAST_UI_STATE 11 #define LAST_UI_STATE 11
#else #else
@ -113,62 +129,83 @@ static int re_qstringCmp(const void *ap, const void *bp) {
} }
static volatile uint8_t pcfPortData = 0; // port expander port state
static volatile uint8_t addrPcf8574 = PCF8574_ADDRESS; // has to be accessible in ISR
// Interrupt routine to read I2C rotary state
// if we are to use PCF8574 port expander we will need to rely on interrupts as polling I2C every 2ms
// is a waste of resources and causes 4LD to fail.
// in such case rely on ISR to read pin values and store them into static variable
static void IRAM_ATTR i2cReadingISR() {
Wire.requestFrom(addrPcf8574, 1U);
if (Wire.available()) {
pcfPortData = Wire.read();
}
}
class RotaryEncoderUIUsermod : public Usermod { class RotaryEncoderUIUsermod : public Usermod {
private: private:
int8_t fadeAmount = 5; // Amount to change every step (brightness)
const int8_t fadeAmount; // Amount to change every step (brightness)
unsigned long loopTime; unsigned long loopTime;
unsigned long buttonPressedTime = 0; unsigned long buttonPressedTime;
unsigned long buttonWaitTime = 0; unsigned long buttonWaitTime;
bool buttonPressedBefore = false; bool buttonPressedBefore;
bool buttonLongPressed = false; bool buttonLongPressed;
int8_t pinA = ENCODER_DT_PIN; // DT from encoder int8_t pinA; // DT from encoder
int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder int8_t pinB; // CLK from encoder
int8_t pinC = ENCODER_SW_PIN; // SW from encoder int8_t pinC; // SW from encoder
unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed, ... unsigned char select_state; // 0: brightness, 1: effect, 2: effect speed, ...
uint16_t currentHue1 = 16; // default boot color uint16_t currentHue1; // default boot color
byte currentSat1 = 255; byte currentSat1;
uint8_t currentCCT;
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
FourLineDisplayUsermod *display; FourLineDisplayUsermod *display;
#else #else
void* display = nullptr; void* display;
#endif #endif
// Pointers the start of the mode names within JSON_mode_names // Pointers the start of the mode names within JSON_mode_names
const char **modes_qstrings = nullptr; const char **modes_qstrings;
// Array of mode indexes in alphabetical order. // Array of mode indexes in alphabetical order.
byte *modes_alpha_indexes = nullptr; byte *modes_alpha_indexes;
// Pointers the start of the palette names within JSON_palette_names // Pointers the start of the palette names within JSON_palette_names
const char **palettes_qstrings = nullptr; const char **palettes_qstrings;
// Array of palette indexes in alphabetical order. // Array of palette indexes in alphabetical order.
byte *palettes_alpha_indexes = nullptr; byte *palettes_alpha_indexes;
unsigned char Enc_A; struct { // reduce memory footprint
unsigned char Enc_B; bool Enc_A : 1;
unsigned char Enc_A_prev = 0; bool Enc_B : 1;
bool Enc_A_prev : 1;
};
bool currentEffectAndPaletteInitialized = false; bool currentEffectAndPaletteInitialized;
uint8_t effectCurrentIndex = 0; uint8_t effectCurrentIndex;
uint8_t effectPaletteIndex = 0; uint8_t effectPaletteIndex;
uint8_t knownMode = 0; uint8_t knownMode;
uint8_t knownPalette = 0; uint8_t knownPalette;
uint8_t currentCCT = 128; byte presetHigh;
byte presetLow;
byte presetHigh = 0; bool applyToAll;
byte presetLow = 0;
bool applyToAll = true; bool initDone;
bool enabled;
bool initDone = false; bool usePcf8574;
bool enabled = true; int8_t pinIRQ;
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
static const char _name[]; static const char _name[];
@ -179,12 +216,178 @@ private:
static const char _presetHigh[]; static const char _presetHigh[];
static const char _presetLow[]; static const char _presetLow[];
static const char _applyToAll[]; static const char _applyToAll[];
static const char _pcf8574[];
static const char _pcfAddress[];
static const char _pcfINTpin[];
/**
* readPin() - read rotary encoder pin value
*/
byte readPin(uint8_t pin);
/** /**
* Sort the modes and palettes to the index arrays * Sort the modes and palettes to the index arrays
* modes_alpha_indexes and palettes_alpha_indexes. * modes_alpha_indexes and palettes_alpha_indexes.
*/ */
void sortModesAndPalettes() { void sortModesAndPalettes();
byte *re_initIndexArray(int numModes);
/**
* Return an array of mode or palette names from the JSON string.
* They don't end in '\0', they end in '"'.
*/
const char **re_findModeStrings(const char json[], int numModes);
/**
* Sort either the modes or the palettes using quicksort.
*/
void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip);
public:
RotaryEncoderUIUsermod()
: fadeAmount(5)
, buttonPressedTime(0)
, buttonWaitTime(0)
, buttonPressedBefore(false)
, buttonLongPressed(false)
, pinA(ENCODER_DT_PIN)
, pinB(ENCODER_CLK_PIN)
, pinC(ENCODER_SW_PIN)
, select_state(0)
, currentHue1(16)
, currentSat1(255)
, currentCCT(128)
, display(nullptr)
, modes_qstrings(nullptr)
, modes_alpha_indexes(nullptr)
, palettes_qstrings(nullptr)
, palettes_alpha_indexes(nullptr)
, currentEffectAndPaletteInitialized(false)
, effectCurrentIndex(0)
, effectPaletteIndex(0)
, knownMode(0)
, knownPalette(0)
, presetHigh(0)
, presetLow(0)
, applyToAll(true)
, initDone(false)
, enabled(true)
, usePcf8574(USE_PCF8574)
, pinIRQ(PCF8574_INT_PIN)
{}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() { return USERMOD_ID_ROTARY_ENC_UI; }
/**
* Enable/Disable the usermod
*/
inline void enable(bool enable) { if (!(pinA<0 || pinB<0 || pinC<0)) enabled = enable; }
/**
* Get usermod enabled/disabled state
*/
inline bool isEnabled() { return enabled; }
/**
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup();
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
//void connected();
/**
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop();
#ifndef WLED_DISABLE_MQTT
//bool onMqttMessage(char* topic, char* payload);
//void onMqttConnect(bool sessionPresent);
#endif
/**
* handleButton() can be used to override default button behaviour. Returning true
* will prevent button working in a default way.
* Replicating button.cpp
*/
//bool handleButton(uint8_t b);
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*/
//void addToJsonInfo(JsonObject &root);
/**
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void addToJsonState(JsonObject &root);
/**
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void readFromJsonState(JsonObject &root);
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root);
//void appendConfigData();
/**
* restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject &root);
// custom methods
void displayNetworkInfo();
void findCurrentEffectAndPalette();
bool changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph);
void lampUdated();
void changeBrightness(bool increase);
void changeEffect(bool increase);
void changeEffectSpeed(bool increase);
void changeEffectIntensity(bool increase);
void changeCustom(uint8_t par, bool increase);
void changePalette(bool increase);
void changeHue(bool increase);
void changeSat(bool increase);
void changePreset(bool increase);
void changeCCT(bool increase);
};
/**
* readPin() - read rotary encoder pin value
*/
byte RotaryEncoderUIUsermod::readPin(uint8_t pin) {
if (usePcf8574) {
return (pcfPortData>>pin) & 1;
} else {
return digitalRead(pin);
}
}
/**
* Sort the modes and palettes to the index arrays
* modes_alpha_indexes and palettes_alpha_indexes.
*/
void RotaryEncoderUIUsermod::sortModesAndPalettes() {
DEBUG_PRINTLN(F("Sorting modes and palettes."));
//modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount());
modes_qstrings = strip.getModeDataSrc(); modes_qstrings = strip.getModeDataSrc();
modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); modes_alpha_indexes = re_initIndexArray(strip.getModeCount());
@ -200,7 +403,7 @@ private:
re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount);
} }
byte *re_initIndexArray(int numModes) { byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
byte *indexes = (byte *)malloc(sizeof(byte) * numModes); byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
for (byte i = 0; i < numModes; i++) { for (byte i = 0; i < numModes; i++) {
indexes[i] = i; indexes[i] = i;
@ -212,7 +415,7 @@ private:
* Return an array of mode or palette names from the JSON string. * Return an array of mode or palette names from the JSON string.
* They don't end in '\0', they end in '"'. * They don't end in '\0', they end in '"'.
*/ */
const char **re_findModeStrings(const char json[], int numModes) { const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int numModes) {
const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes); const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes);
uint8_t modeIndex = 0; uint8_t modeIndex = 0;
bool insideQuotes = false; bool insideQuotes = false;
@ -250,7 +453,7 @@ private:
/** /**
* Sort either the modes or the palettes using quicksort. * Sort either the modes or the palettes using quicksort.
*/ */
void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) { void RotaryEncoderUIUsermod::re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) {
if (!modeNames) return; if (!modeNames) return;
listBeingSorted = modeNames; listBeingSorted = modeNames;
qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp); qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp);
@ -258,21 +461,37 @@ private:
} }
public: // public methods
/* /*
* setup() is called once at boot. WiFi is not yet connected at this point. * setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar. * You can use it to initialize variables, sensors or similar.
*/ */
void setup() void RotaryEncoderUIUsermod::setup()
{ {
DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); DEBUG_PRINTLN(F("Usermod Rotary Encoder init."));
if (usePcf8574) {
if ((i2c_sda == i2c_scl && i2c_sda == -1) || pinA<0 || pinB<0 || pinC<0) {
DEBUG_PRINTLN(F("I2C and/or PCF8574 pins unused, disabling."));
enabled = false;
return;
} else {
if (pinIRQ >= 0 && pinManager.allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) {
pinMode(pinIRQ, INPUT_PULLUP);
attachInterrupt(pinIRQ, i2cReadingISR, FALLING); // RISING, FALLING, CHANGE, ONLOW, ONHIGH
DEBUG_PRINTLN(F("Interrupt attached."));
} else {
DEBUG_PRINTLN(F("Unable to allocate interrupt pin, disabling."));
pinIRQ = -1;
enabled = false;
return;
}
}
} else {
PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } };
if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) {
// BUG: configuring this usermod with conflicting pins
// will cause it to de-allocate pins it does not own
// (at second config)
// This is the exact type of bug solved by pinManager
// tracking the owner tags....
pinA = pinB = pinC = -1; pinA = pinB = pinC = -1;
enabled = false; enabled = false;
return; return;
@ -284,6 +503,7 @@ public:
pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO);
pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO);
pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO);
}
loopTime = millis(); loopTime = millis();
@ -301,20 +521,11 @@ public:
#endif #endif
initDone = true; initDone = true;
Enc_A = digitalRead(pinA); // Read encoder pins Enc_A = readPin(pinA); // Read encoder pins
Enc_B = digitalRead(pinB); Enc_B = readPin(pinB);
Enc_A_prev = Enc_A; Enc_A_prev = Enc_A;
} }
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
//Serial.println("Connected to WiFi!");
}
/* /*
* loop() is called continuously. Here you can check for events, read sensors, etc. * loop() is called continuously. Here you can check for events, read sensors, etc.
* *
@ -325,7 +536,7 @@ public:
* 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
* Instead, use a timer check as shown here. * Instead, use a timer check as shown here.
*/ */
void loop() void RotaryEncoderUIUsermod::loop()
{ {
if (!enabled || strip.isUpdating()) return; if (!enabled || strip.isUpdating()) return;
unsigned long currentTime = millis(); // get the current elapsed time unsigned long currentTime = millis(); // get the current elapsed time
@ -339,14 +550,13 @@ public:
} }
if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) { if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) {
DEBUG_PRINTLN(F("Current mode or palette changed."));
currentEffectAndPaletteInitialized = false; currentEffectAndPaletteInitialized = false;
} }
if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
{ {
loopTime = currentTime; // Updates loopTime bool buttonPressed = !readPin(pinC); //0=pressed, 1=released
bool buttonPressed = !digitalRead(pinC); //0=pressed, 1=released
if (buttonPressed) { if (buttonPressed) {
if (!buttonPressedBefore) buttonPressedTime = currentTime; if (!buttonPressedBefore) buttonPressedTime = currentTime;
buttonPressedBefore = true; buttonPressedBefore = true;
@ -413,8 +623,8 @@ public:
if (changedState) select_state = newState; if (changedState) select_state = newState;
} }
Enc_A = digitalRead(pinA); // Read encoder pins Enc_A = readPin(pinA); // Read encoder pins
Enc_B = digitalRead(pinB); Enc_B = readPin(pinB);
if ((Enc_A) && (!Enc_A_prev)) if ((Enc_A) && (!Enc_A_prev))
{ // A has gone from high to low { // A has gone from high to low
if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse
@ -453,16 +663,18 @@ public:
} }
} }
Enc_A_prev = Enc_A; // Store value of A for next time Enc_A_prev = Enc_A; // Store value of A for next time
loopTime = currentTime; // Updates loopTime
} }
} }
void displayNetworkInfo() { void RotaryEncoderUIUsermod::displayNetworkInfo() {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
display->networkOverlay(PSTR("NETWORK INFO"), 10000); display->networkOverlay(PSTR("NETWORK INFO"), 10000);
#endif #endif
} }
void findCurrentEffectAndPalette() { void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() {
DEBUG_PRINTLN(F("Finding current mode and palette."));
currentEffectAndPaletteInitialized = true; currentEffectAndPaletteInitialized = true;
for (uint8_t i = 0; i < strip.getModeCount(); i++) { for (uint8_t i = 0; i < strip.getModeCount(); i++) {
if (modes_alpha_indexes[i] == effectCurrent) { if (modes_alpha_indexes[i] == effectCurrent) {
@ -470,6 +682,7 @@ public:
break; break;
} }
} }
DEBUG_PRINTLN(F("Found current mode."));
for (uint8_t i = 0; i < strip.getPaletteCount(); i++) { for (uint8_t i = 0; i < strip.getPaletteCount(); i++) {
if (palettes_alpha_indexes[i] == effectPalette) { if (palettes_alpha_indexes[i] == effectPalette) {
@ -477,9 +690,10 @@ public:
break; break;
} }
} }
DEBUG_PRINTLN(F("Found palette."));
} }
boolean changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) { bool RotaryEncoderUIUsermod::changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display != nullptr) { if (display != nullptr) {
if (display->wakeDisplay()) { if (display->wakeDisplay()) {
@ -494,7 +708,7 @@ public:
return true; return true;
} }
void lampUdated() { void RotaryEncoderUIUsermod::lampUdated() {
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
//setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required) //setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required)
@ -502,7 +716,7 @@ public:
updateInterfaces(CALL_MODE_BUTTON); updateInterfaces(CALL_MODE_BUTTON);
} }
void changeBrightness(bool increase) { void RotaryEncoderUIUsermod::changeBrightness(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -519,7 +733,7 @@ public:
} }
void changeEffect(bool increase) { void RotaryEncoderUIUsermod::changeEffect(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -548,7 +762,7 @@ public:
} }
void changeEffectSpeed(bool increase) { void RotaryEncoderUIUsermod::changeEffectSpeed(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -576,7 +790,7 @@ public:
} }
void changeEffectIntensity(bool increase) { void RotaryEncoderUIUsermod::changeEffectIntensity(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -604,7 +818,7 @@ public:
} }
void changeCustom(uint8_t par, bool increase) { void RotaryEncoderUIUsermod::changeCustom(uint8_t par, bool increase) {
uint8_t val = 0; uint8_t val = 0;
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
@ -649,7 +863,7 @@ public:
} }
void changePalette(bool increase) { void RotaryEncoderUIUsermod::changePalette(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -678,7 +892,7 @@ public:
} }
void changeHue(bool increase){ void RotaryEncoderUIUsermod::changeHue(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -708,7 +922,7 @@ public:
#endif #endif
} }
void changeSat(bool increase){ void RotaryEncoderUIUsermod::changeSat(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -737,7 +951,7 @@ public:
#endif #endif
} }
void changePreset(bool increase) { void RotaryEncoderUIUsermod::changePreset(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -769,7 +983,7 @@ public:
} }
} }
void changeCCT(bool increase){ void RotaryEncoderUIUsermod::changeCCT(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -803,7 +1017,7 @@ public:
* Below it is shown how this could be used for e.g. a light sensor * Below it is shown how this could be used for e.g. a light sensor
*/ */
/* /*
void addToJsonInfo(JsonObject& root) void RotaryEncoderUIUsermod::addToJsonInfo(JsonObject& root)
{ {
int reading = 20; int reading = 20;
//this code adds "u":{"Light":[20," lux"]} to the info object //this code adds "u":{"Light":[20," lux"]} to the info object
@ -820,7 +1034,7 @@ public:
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
/* /*
void addToJsonState(JsonObject &root) void RotaryEncoderUIUsermod::addToJsonState(JsonObject &root)
{ {
//root["user0"] = userVar0; //root["user0"] = userVar0;
} }
@ -831,7 +1045,7 @@ public:
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
/* /*
void readFromJsonState(JsonObject &root) void RotaryEncoderUIUsermod::readFromJsonState(JsonObject &root)
{ {
//userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
@ -841,7 +1055,7 @@ public:
/** /**
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json * addToConfig() (called from set.cpp) stores persistent properties to cfg.json
*/ */
void addToConfig(JsonObject &root) { void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) {
// we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} // we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = enabled; top[FPSTR(_enabled)] = enabled;
@ -851,15 +1065,22 @@ public:
top[FPSTR(_presetLow)] = presetLow; top[FPSTR(_presetLow)] = presetLow;
top[FPSTR(_presetHigh)] = presetHigh; top[FPSTR(_presetHigh)] = presetHigh;
top[FPSTR(_applyToAll)] = applyToAll; top[FPSTR(_applyToAll)] = applyToAll;
top[FPSTR(_pcf8574)] = usePcf8574;
top[FPSTR(_pcfAddress)] = addrPcf8574;
top[FPSTR(_pcfINTpin)] = pinIRQ;
DEBUG_PRINTLN(F("Rotary Encoder config saved.")); DEBUG_PRINTLN(F("Rotary Encoder config saved."));
} }
//void RotaryEncoderUIUsermod::appendConfigData() {
// oappend(SET_F("addInfo('RotaryEncoderUIUsermod:PCF8574-address',1,'<i>(not hex!)</i>');"));
//}
/** /**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
* *
* The function should return true if configuration was successfully loaded or false if there was no configuration. * The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject &root) { bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) {
// we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} // we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) { if (top.isNull()) {
@ -870,6 +1091,8 @@ public:
int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA;
int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB;
int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC;
int8_t newIRQpin = top[FPSTR(_pcfINTpin)] | pinIRQ;
bool oldPcf8574 = usePcf8574;
presetHigh = top[FPSTR(_presetHigh)] | presetHigh; presetHigh = top[FPSTR(_presetHigh)] | presetHigh;
presetLow = top[FPSTR(_presetLow)] | presetLow; presetLow = top[FPSTR(_presetLow)] | presetLow;
@ -879,6 +1102,9 @@ public:
enabled = top[FPSTR(_enabled)] | enabled; enabled = top[FPSTR(_enabled)] | enabled;
applyToAll = top[FPSTR(_applyToAll)] | applyToAll; applyToAll = top[FPSTR(_applyToAll)] | applyToAll;
usePcf8574 = top[FPSTR(_pcf8574)] | usePcf8574;
addrPcf8574 = top[FPSTR(_pcfAddress)] | addrPcf8574;
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
@ -889,10 +1115,20 @@ public:
} else { } else {
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page // changing parameters from settings page
if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) { if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin || pinIRQ!=newIRQpin) {
if (oldPcf8574) {
if (pinIRQ >= 0) {
detachInterrupt(pinIRQ);
pinManager.deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI);
DEBUG_PRINTLN(F("Deallocated old IRQ pin."));
}
pinIRQ = newIRQpin;
} else {
pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI);
DEBUG_PRINTLN(F("Deallocated old pins."));
}
pinA = newDTpin; pinA = newDTpin;
pinB = newCLKpin; pinB = newCLKpin;
pinC = newSWpin; pinC = newSWpin;
@ -904,18 +1140,9 @@ public:
} }
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_applyToAll)].isNull(); return !top[FPSTR(_pcfINTpin)].isNull();
} }
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_ID_ROTARY_ENC_UI;
}
};
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder"; const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder";
@ -926,3 +1153,6 @@ const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin";
const char RotaryEncoderUIUsermod::_presetHigh[] PROGMEM = "preset-high"; const char RotaryEncoderUIUsermod::_presetHigh[] PROGMEM = "preset-high";
const char RotaryEncoderUIUsermod::_presetLow[] PROGMEM = "preset-low"; const char RotaryEncoderUIUsermod::_presetLow[] PROGMEM = "preset-low";
const char RotaryEncoderUIUsermod::_applyToAll[] PROGMEM = "apply-2-all-seg"; const char RotaryEncoderUIUsermod::_applyToAll[] PROGMEM = "apply-2-all-seg";
const char RotaryEncoderUIUsermod::_pcf8574[] PROGMEM = "use-PCF8574";
const char RotaryEncoderUIUsermod::_pcfAddress[] PROGMEM = "PCF8574-address";
const char RotaryEncoderUIUsermod::_pcfINTpin[] PROGMEM = "PCF8574-INT-pin";

View File

@ -184,7 +184,7 @@ void Segment::deallocateData() {
void Segment::resetIfRequired() { void Segment::resetIfRequired() {
if (reset) { if (reset) {
if (leds && !Segment::_globalLeds) { free(leds); leds = nullptr; } if (leds && !Segment::_globalLeds) { free(leds); leds = nullptr; }
if (transitional && _t) { transitional = false; delete _t; _t = nullptr; } //if (transitional && _t) { transitional = false; delete _t; _t = nullptr; }
deallocateData(); deallocateData();
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
reset = false; // setOption(SEG_OPTION_RESET, false); reset = false; // setOption(SEG_OPTION_RESET, false);
@ -367,16 +367,15 @@ CRGBPalette16 &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal
void Segment::handleTransition() { void Segment::handleTransition() {
if (!transitional) return; if (!transitional) return;
unsigned long maxWait = millis() + 20; uint16_t _progress = progress();
if (mode == FX_MODE_STATIC && next_time > maxWait) next_time = maxWait; if (_t) { // thanks to @nXm AKA https://github.com/NMeirer
if (progress() == 0xFFFFU) { if (_progress >= 32767U && _t->_modeP != mode) markForReset();
if (_t) { if (_progress == 0xFFFFU) {
if (_t->_modeP != mode) markForReset();
delete _t; delete _t;
_t = nullptr; _t = nullptr;
} }
transitional = false; // finish transitioning segment
} }
if (_progress == 0xFFFFU) transitional = false; // finish transitioning segment
} }
void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) {
@ -1095,6 +1094,8 @@ void WS2812FX::service() {
_isServicing = true; _isServicing = true;
_segment_index = 0; _segment_index = 0;
for (segment &seg : _segments) { for (segment &seg : _segments) {
// process transition (mode changes in the middle of transition)
seg.handleTransition();
// reset the segment runtime data if needed // reset the segment runtime data if needed
seg.resetIfRequired(); seg.resetIfRequired();
@ -1123,8 +1124,6 @@ void WS2812FX::service() {
delay = (*_mode[seg.currentMode(seg.mode)])(); delay = (*_mode[seg.currentMode(seg.mode)])();
if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++;
if (seg.transitional && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition if (seg.transitional && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition
seg.handleTransition();
} }
seg.next_time = nowUp + delay; seg.next_time = nowUp + delay;

View File

@ -122,6 +122,9 @@
#define I_SS_LPO_3 48 #define I_SS_LPO_3 48
// In the following NeoGammaNullMethod can be replaced with NeoGammaWLEDMethod to perform Gamma correction implicitly
// unfortunately that may apply Gamma correction to pre-calculated palettes which is undesired
/*** ESP8266 Neopixel methods ***/ /*** ESP8266 Neopixel methods ***/
#ifdef ESP8266 #ifdef ESP8266
//RGB //RGB

View File

@ -297,9 +297,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } }; PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } };
if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) {
#ifdef ESP32 #ifdef ESP32
Wire.setPins(i2c_sda, i2c_scl); // this will fail if Wire is initilised (Wire.begin() called prior) if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initilised (Wire.begin() called prior)
else Wire.begin();
#else
Wire.begin(i2c_sda, i2c_scl);
#endif #endif
Wire.begin();
} else { } else {
i2c_sda = -1; i2c_sda = -1;
i2c_scl = -1; i2c_scl = -1;
@ -336,7 +338,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (light_gc_col > 1.0f) gammaCorrectCol = true; if (light_gc_col > 1.0f) gammaCorrectCol = true;
else gammaCorrectCol = false; else gammaCorrectCol = false;
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) { if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) {
if (gammaCorrectVal != 2.8f) calcGammaTable(gammaCorrectVal); if (gammaCorrectVal != 2.8f) NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
} else { } else {
gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false; gammaCorrectBri = false;

View File

@ -302,7 +302,7 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) {
} }
//gamma 2.8 lookup table used for color correction //gamma 2.8 lookup table used for color correction
static byte gammaT[] = { uint8_t NeoGammaWLEDMethod::gammaT[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
@ -320,27 +320,22 @@ static byte gammaT[] = {
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
uint8_t gamma8_cal(uint8_t b, float gamma)
{
return (int)(powf((float)b / 255.0f, gamma) * 255.0f + 0.5f);
}
// re-calculates & fills gamma table // re-calculates & fills gamma table
void calcGammaTable(float gamma) void NeoGammaWLEDMethod::calcGammaTable(float gamma)
{ {
for (uint16_t i = 0; i < 256; i++) { for (size_t i = 0; i < 256; i++) {
gammaT[i] = gamma8_cal(i, gamma); gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f);
} }
} }
// used for individual channel or brightness gamma correction uint8_t NeoGammaWLEDMethod::Correct(uint8_t value)
uint8_t gamma8(uint8_t b)
{ {
return gammaT[b]; if (!gammaCorrectCol) return value;
return gammaT[value];
} }
// used for color gamma correction // used for color gamma correction
uint32_t gamma32(uint32_t color) uint32_t NeoGammaWLEDMethod::Correct32(uint32_t color)
{ {
if (!gammaCorrectCol) return color; if (!gammaCorrectCol) return color;
uint8_t w = W(color); uint8_t w = W(color);

View File

@ -320,7 +320,8 @@
// WLED Error modes // WLED Error modes
#define ERR_NONE 0 // All good :) #define ERR_NONE 0 // All good :)
#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?) #define ERR_DENIED 1 // Permission denied
#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?) OBSOLETE
#define ERR_NOBUF 3 // JSON buffer was not released in time, request cannot be handled at this time #define ERR_NOBUF 3 // JSON buffer was not released in time, request cannot be handled at this time
#define ERR_JSON 9 // JSON parsing failed (input too large?) #define ERR_JSON 9 // JSON parsing failed (input too large?)
#define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?) #define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?)

View File

@ -42,6 +42,6 @@
<img alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAbUExURQAAAAB81gCU/zKq/////9bW1oCAgGhoaAAAAGPLX8AAAAAJdFJOU///////////AFNPeBIAAAAJcEhZcwAADsAAAA7AAWrWiQkAAACdSURBVDhPxc9bDoUgEANQebP/FUuHMjBGY/B+3EYR7RH0qC/ZBc6HwCljgHO+xZIVSI2sYgHaG7EBWh8jWoxTrCBFdDJ+BD4lbIHxAcz8APAVLTsrZE4eQD5qzt3cAFTYokC4YCN9Gybgu4yAQtBFLQXHuHABA7JMeOEC/E0W5uy9gv4vo5QHK2i7yq2C8UABM4HmL+CSTXCTF1DrCX6+Gp9zB5dsAAAAAElFTkSuQmCC"> <img alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAbUExURQAAAAB81gCU/zKq/////9bW1oCAgGhoaAAAAGPLX8AAAAAJdFJOU///////////AFNPeBIAAAAJcEhZcwAADsAAAA7AAWrWiQkAAACdSURBVDhPxc9bDoUgEANQebP/FUuHMjBGY/B+3EYR7RH0qC/ZBc6HwCljgHO+xZIVSI2sYgHaG7EBWh8jWoxTrCBFdDJ+BD4lbIHxAcz8APAVLTsrZE4eQD5qzt3cAFTYokC4YCN9Gybgu4yAQtBFLQXHuHABA7JMeOEC/E0W5uy9gv4vo5QHK2i7yq2C8UABM4HmL+CSTXCTF1DrCX6+Gp9zB5dsAAAAAElFTkSuQmCC">
<h1>404 Not Found</h1> <h1>404 Not Found</h1>
<b>Akemi does not know where you are headed...</b><br><br> <b>Akemi does not know where you are headed...</b><br><br>
<button onclick="window.location.href='/sliders'">Back to controls</button> <button onclick="window.location.href='../sliders'">Back to controls</button>
</body> </body>
</html> </html>

View File

@ -410,6 +410,7 @@ button {
position: -webkit-sticky; position: -webkit-sticky;
position: sticky; position: sticky;
bottom: 0; bottom: 0;
max-width: 300px;
} }
#sliders .labels { #sliders .labels {
@ -418,7 +419,7 @@ button {
} }
.slider { .slider {
max-width: 300px; /*max-width: 300px;*/
/* margin: 5px auto; add 5px; if you want some vertical space but looks ugly */ /* margin: 5px auto; add 5px; if you want some vertical space but looks ugly */
border-radius: 24px; border-radius: 24px;
position: relative; position: relative;
@ -434,12 +435,16 @@ button {
z-index: 0; z-index: 0;
} }
#sliders .slider {
padding-right: 64px; /* offset for bubble */
}
#sliders .slider, #info .slider { #sliders .slider, #info .slider {
background-color: var(--c-2); background-color: var(--c-2);
} }
#sliders .sliderwrap, .sbs .sliderwrap { #sliders .sliderwrap, .sbs .sliderwrap {
left: 16px; /* offset for icon */ left: 32px; /* offset for icon */
} }
.filter, .option { .filter, .option {
@ -685,18 +690,19 @@ img {
.sliderbubble { .sliderbubble {
width: 24px; width: 24px;
position: relative; position: absolute;
display: inline-block; display: inline-block;
border-radius: 10px; border-radius: 16px;
background: var(--c-3); background: var(--c-3);
color: var(--c-f); color: var(--c-f);
padding: 2px 4px; padding: 4px;
font-size: 14px; font-size: 14px;
right: 3px; right: 6px;
transition: visibility 0.25s ease, opacity 0.25s ease; transition: visibility .25s ease,opacity .25s ease;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
left: 8px; /* left: 8px; */
top: 4px;
} }
output.sliderbubbleshow { output.sliderbubbleshow {
@ -1022,8 +1028,9 @@ textarea {
/* segment power wrapper */ /* segment power wrapper */
.sbs { .sbs {
padding: 1px 0 1px 20px; /*padding: 1px 0 1px 20px;*/
display: var(--sgp); display: var(--sgp);
width: 100%;
} }
.pname { .pname {
@ -1538,8 +1545,11 @@ TD .checkmark, TD .radiomark {
#sliders .sliderbubble { #sliders .sliderbubble {
display: none; display: none;
} }
.sliderwrap { #sliders .sliderwrap, .sbs .sliderwrap {
width: calc(100% - 28px); width: calc(100% - 42px);
}
#sliders .slider {
padding-right: 0;
} }
#sliders .sliderwrap { #sliders .sliderwrap {
left: 12px; left: 12px;

View File

@ -67,7 +67,7 @@
<button id="buttonSr" onclick="toggleLiveview()"><i class="icons">&#xe410;</i><p class="tab-label">Peek</p></button> <button id="buttonSr" onclick="toggleLiveview()"><i class="icons">&#xe410;</i><p class="tab-label">Peek</p></button>
<button id="buttonI" onclick="toggleInfo()"><i class="icons">&#xe066;</i><p class="tab-label">Info</p></button> <button id="buttonI" onclick="toggleInfo()"><i class="icons">&#xe066;</i><p class="tab-label">Info</p></button>
<button id="buttonNodes" onclick="toggleNodes()"><i class="icons">&#xe22d;</i><p class="tab-label">Nodes</p></button> <button id="buttonNodes" onclick="toggleNodes()"><i class="icons">&#xe22d;</i><p class="tab-label">Nodes</p></button>
<button onclick="window.location.href='/settings';"><i class="icons">&#xe0a2;</i><p class="tab-label">Config</p></button> <button onclick="window.location.href=getURL('/settings');"><i class="icons">&#xe0a2;</i><p class="tab-label">Config</p></button>
<button id="buttonPcm" onclick="togglePcMode(true)"><i class="icons">&#xe23d;</i><p class="tab-label">PC Mode</p></button> <button id="buttonPcm" onclick="togglePcMode(true)"><i class="icons">&#xe23d;</i><p class="tab-label">PC Mode</p></button>
</div> </div>
<div id="briwrap"> <div id="briwrap">
@ -88,7 +88,7 @@
<div class ="container"> <div class ="container">
<div id="Colors" class="tabcontent"> <div id="Colors" class="tabcontent">
<div id="picker" class="noslide"></div> <div id="picker" class="noslide"></div>
<div id="hwrap" class="slider"> <div id="hwrap" class="slider" style="margin-top: 20px;">
<div class="sliderwrap il"> <div class="sliderwrap il">
<input id="sliderH" class="noslide" oninput="fromH()" onchange="setColor(0)" max="359" min="0" type="range" value="0" step="any"> <input id="sliderH" class="noslide" oninput="fromH()" onchange="setColor(0)" max="359" min="0" type="range" value="0" step="any">
<div class="sliderdisplay" style="background: linear-gradient(90deg, #f00 2%, #ff0 19%, #0f0 35%, #0ff 52%, #00f 68%, #f0f 85%, #f00)"></div> <div class="sliderdisplay" style="background: linear-gradient(90deg, #f00 2%, #ff0 19%, #0f0 35%, #0ff 52%, #00f 68%, #f0f 85%, #f00)"></div>
@ -199,7 +199,7 @@
</div> </div>
</div> </div>
<div style="padding-bottom: 10px;"> <div style="padding-bottom: 10px;">
<button class="btn btn-xs" type="button" onclick="window.location.href=(loc?'http://'+locip:'')+'/cpal.htm'"><i class="icons btn-icon">&#xe18a;</i></button> <button class="btn btn-xs" type="button" onclick="window.location.href=getURL('/cpal.htm')"><i class="icons btn-icon">&#xe18a;</i></button>
<button class="btn btn-xs" type="button" onclick="palettesData=null;localStorage.removeItem('wledPalx');requestJson({rmcpal:true});setTimeout(loadPalettes,250,loadPalettesData);"><i class="icons btn-icon">&#xe037;</i></button> <button class="btn btn-xs" type="button" onclick="palettesData=null;localStorage.removeItem('wledPalx');requestJson({rmcpal:true});setTimeout(loadPalettes,250,loadPalettesData);"><i class="icons btn-icon">&#xe037;</i></button>
</div> </div>
</div> </div>
@ -363,7 +363,7 @@
<div> <div>
<button class="btn infobtn" onclick="requestJson()">Refresh</button> <button class="btn infobtn" onclick="requestJson()">Refresh</button>
<button class="btn infobtn" onclick="toggleNodes()">Instance List</button> <button class="btn infobtn" onclick="toggleNodes()">Instance List</button>
<button class="btn infobtn" onclick="window.open('/update','_self');">Update WLED</button> <button class="btn infobtn" onclick="window.open(getURL('/update'),'_self');">Update WLED</button>
<button class="btn infobtn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button> <button class="btn infobtn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button>
</div> </div>
<br> <br>

View File

@ -1,5 +1,5 @@
//page js //page js
var loc = false, locip; var loc = false, locip, locproto = "http:";
var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true; var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true;
var hasWhite = false, hasRGB = false, hasCCT = false; var hasWhite = false, hasRGB = false, hasCCT = false;
var nlDur = 60, nlTar = 0; var nlDur = 60, nlTar = 0;
@ -193,21 +193,38 @@ function loadSkinCSS(cId)
l.id = cId; l.id = cId;
l.rel = 'stylesheet'; l.rel = 'stylesheet';
l.type = 'text/css'; l.type = 'text/css';
l.href = (loc?`http://${locip}`:'.') + '/skin.css'; l.href = getURL('/skin.css');
l.media = 'all'; l.media = 'all';
h.appendChild(l); h.appendChild(l);
} }
} }
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
}
function onLoad() function onLoad()
{ {
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy and/or HTTPS
let pathn = l.pathname;
let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/");
//while (paths[0]==="") paths.shift();
locproto = l.protocol;
locip = l.hostname + (l.port ? ":" + l.port : "");
if (paths.length > 0 && paths[0]!=="") {
loc = true;
locip += "/" + paths[0];
} else if (locproto==="https:") {
loc = true;
}
} }
var sett = localStorage.getItem('wledUiCfg'); var sett = localStorage.getItem('wledUiCfg');
if (sett) cfg = mergeDeep(cfg, JSON.parse(sett)); if (sett) cfg = mergeDeep(cfg, JSON.parse(sett));
@ -217,7 +234,7 @@ function onLoad()
if (localStorage.getItem('pcm') == "true" || (!/Mobi/.test(navigator.userAgent) && localStorage.getItem('pcm') == null)) togglePcMode(true); if (localStorage.getItem('pcm') == "true" || (!/Mobi/.test(navigator.userAgent) && localStorage.getItem('pcm') == null)) togglePcMode(true);
applyCfg(); applyCfg();
if (cfg.comp.hdays) { //load custom holiday list if (cfg.comp.hdays) { //load custom holiday list
fetch((loc?`http://${locip}`:'.') + "/holidays.json", { // may be loaded from external source fetch(getURL("/holidays.json"), { // may be loaded from external source
method: 'get' method: 'get'
}) })
.then((res)=>{ .then((res)=>{
@ -433,9 +450,7 @@ function loadPresets(callback = null)
// afterwards // afterwards
if (!callback && pmt == pmtLast) return; if (!callback && pmt == pmtLast) return;
var url = (loc?`http://${locip}`:'') + '/presets.json'; fetch(getURL('/presets.json'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then(res => { .then(res => {
@ -459,9 +474,7 @@ function loadPresets(callback = null)
function loadPalettes(callback = null) function loadPalettes(callback = null)
{ {
var url = (loc?`http://${locip}`:'') + '/json/palettes'; fetch(getURL('/json/palettes'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then((res)=>{ .then((res)=>{
@ -483,9 +496,7 @@ function loadPalettes(callback = null)
function loadFX(callback = null) function loadFX(callback = null)
{ {
var url = (loc?`http://${locip}`:'') + '/json/effects'; fetch(getURL('/json/effects'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then((res)=>{ .then((res)=>{
@ -497,6 +508,7 @@ function loadFX(callback = null)
populateEffects(); populateEffects();
}) })
.catch((e)=>{ .catch((e)=>{
//setTimeout(loadFX, 250); // retry
showToast(e, true); showToast(e, true);
}) })
.finally(()=>{ .finally(()=>{
@ -507,9 +519,7 @@ function loadFX(callback = null)
function loadFXData(callback = null) function loadFXData(callback = null)
{ {
var url = (loc?`http://${locip}`:'') + '/json/fxdata'; fetch(getURL('/json/fxdata'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then((res)=>{ .then((res)=>{
@ -524,6 +534,7 @@ function loadFXData(callback = null)
}) })
.catch((e)=>{ .catch((e)=>{
fxdata = []; fxdata = [];
//setTimeout(loadFXData, 250); // retry
showToast(e, true); showToast(e, true);
}) })
.finally(()=>{ .finally(()=>{
@ -1032,8 +1043,7 @@ function populateNodes(i,n)
function loadNodes() function loadNodes()
{ {
var url = (loc?`http://${locip}`:'') + '/json/nodes'; fetch(getURL('/json/nodes'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then((res)=>{ .then((res)=>{
@ -1253,6 +1263,7 @@ function updateSelectedFx()
// hide non-0D effects if segment only has 1 pixel (0D) // hide non-0D effects if segment only has 1 pixel (0D)
var fxs = parent.querySelectorAll('.lstI'); var fxs = parent.querySelectorAll('.lstI');
for (const fx of fxs) { for (const fx of fxs) {
if (!fx.dataset.opt) continue;
let opts = fx.dataset.opt.split(";"); let opts = fx.dataset.opt.split(";");
if (fx.dataset.id>0) { if (fx.dataset.id>0) {
if (segLmax==0) fx.classList.add('hide'); // none of the segments selected (hide all effects) if (segLmax==0) fx.classList.add('hide'); // none of the segments selected (hide all effects)
@ -1293,7 +1304,8 @@ function cmpP(a, b)
function makeWS() { function makeWS() {
if (ws || lastinfo.ws < 0) return; if (ws || lastinfo.ws < 0) return;
ws = new WebSocket((window.location.protocol == "https:"?"wss":"ws")+'://'+(loc?locip:window.location.hostname)+'/ws'); let url = loc ? getURL('/ws').replace("http","ws") : "ws://"+window.location.hostname+"/ws";
ws = new WebSocket(url);
ws.binaryType = "arraybuffer"; ws.binaryType = "arraybuffer";
ws.onmessage = (e)=>{ ws.onmessage = (e)=>{
if (e.data instanceof ArrayBuffer) return; // liveview packet if (e.data instanceof ArrayBuffer) return; // liveview packet
@ -1572,7 +1584,6 @@ function requestJson(command=null)
if (command && !reqsLegal) return; // stop post requests from chrome onchange event on page restore if (command && !reqsLegal) return; // stop post requests from chrome onchange event on page restore
if (!jsonTimeout) jsonTimeout = setTimeout(()=>{if (ws) ws.close(); ws=null; showErrorToast()}, 3000); if (!jsonTimeout) jsonTimeout = setTimeout(()=>{if (ws) ws.close(); ws=null; showErrorToast()}, 3000);
var req = null; var req = null;
var url = (loc?`http://${locip}`:'') + '/json/si';
var useWs = (ws && ws.readyState === WebSocket.OPEN); var useWs = (ws && ws.readyState === WebSocket.OPEN);
var type = command ? 'post':'get'; var type = command ? 'post':'get';
if (command) { if (command) {
@ -1593,7 +1604,7 @@ function requestJson(command=null)
return; return;
} }
fetch(url, { fetch(getURL('/json/si'), {
method: type, method: type,
headers: { headers: {
"Content-type": "application/json; charset=UTF-8" "Content-type": "application/json; charset=UTF-8"
@ -1688,7 +1699,7 @@ function toggleLiveview()
} }
gId(lvID).style.display = (isLv) ? "block":"none"; gId(lvID).style.display = (isLv) ? "block":"none";
var url = (loc?`http://${locip}`:'') + "/" + lvID; var url = getURL("/" + lvID);
gId(lvID).src = (isLv) ? url:"about:blank"; gId(lvID).src = (isLv) ? url:"about:blank";
gId('buttonSr').className = (isLv) ? "active":""; gId('buttonSr').className = (isLv) ? "active":"";
if (!isLv && ws && ws.readyState === WebSocket.OPEN) ws.send('{"lv":false}'); if (!isLv && ws && ws.readyState === WebSocket.OPEN) ws.send('{"lv":false}');
@ -2584,7 +2595,7 @@ function cnfReset()
bt.innerHTML = "Confirm Reboot"; bt.innerHTML = "Confirm Reboot";
cnfr = true; return; cnfr = true; return;
} }
window.location.href = "/reset"; window.location.href = getURL("/reset");
} }
var cnfrS = false; var cnfrS = false;
@ -2638,9 +2649,7 @@ function loadPalettesData(callback = null)
function getPalettesData(page, callback) function getPalettesData(page, callback)
{ {
var url = (loc?`http://${locip}`:'') + `/json/palx?page=${page}`; fetch(getURL(`/json/palx?page=${page}`), {
fetch(url, {
method: 'get', method: 'get',
headers: { headers: {
"Content-type": "application/json; charset=UTF-8" "Content-type": "application/json; charset=UTF-8"

View File

@ -31,7 +31,7 @@
tmout = setTimeout(update, 250); tmout = setTimeout(update, 250);
return; return;
} }
fetch('/json/live') fetch('./json/live')
.then(res => { .then(res => {
if (!res.ok) { if (!res.ok) {
clearTimeout(tmout); clearTimeout(tmout);

View File

@ -29,8 +29,15 @@
//console.info("Peek uses top WS"); //console.info("Peek uses top WS");
ws.send("{'lv':true}"); ws.send("{'lv':true}");
} else { } else {
console.info("Peek WS opening"); //console.info("Peek WS opening");
ws = new WebSocket((window.location.protocol == "https:"?"wss":"ws")+"://"+document.location.host+"/ws"); let l = window.location;
let pathn = l.pathname;
let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/");
let url = l.origin.replace("http","ws");
if (paths.length > 1) {
url += "/" + paths[0];
}
ws = new WebSocket(url+"/ws");
ws.onopen = function () { ws.onopen = function () {
//console.info("Peek WS open"); //console.info("Peek WS open");
ws.send("{'lv':true}"); ws.send("{'lv':true}");

View File

@ -33,7 +33,14 @@
if (ws && ws.readyState === WebSocket.OPEN) { if (ws && ws.readyState === WebSocket.OPEN) {
ws.send("{'lv':true}"); ws.send("{'lv':true}");
} else { } else {
ws = new WebSocket((window.location.protocol == "https:"?"wss":"ws")+"://"+document.location.host+"/ws"); let l = window.location;
let pathn = l.pathname;
let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/");
let url = l.origin.replace("http","ws");
if (paths.length > 1) {
url += "/" + paths[0];
}
ws = new WebSocket(url+"/ws");
ws.onopen = ()=>{ ws.onopen = ()=>{
ws.send("{'lv':true}"); ws.send("{'lv':true}");
} }

View File

@ -6,8 +6,8 @@
<title>WLED Message</title> <title>WLED Message</title>
<script> <script>
function B() { window.history.back() }; function B() { window.history.back() };
function RS() { window.location = "/settings"; } function RS() { window.location = "../settings"; }
function RP() { top.location.href = "/"; } function RP() { top.location.href = "../"; }
</script> </script>
<style> <style>
@import url("style.css"); @import url("style.css");

View File

@ -6,7 +6,7 @@
<title>WLED Settings</title> <title>WLED Settings</title>
<script> <script>
var d=document; var d=document;
var loc = false, locip; var loc = false, locip, locproto = "http:";
function gId(n){return d.getElementById(n);} function gId(n){return d.getElementById(n);}
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) { function loadJS(FILE_URL, async = true) {
@ -27,16 +27,28 @@
}); });
} }
function S() { function S() {
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy
let path = l.pathname;
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 1) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
} }
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=0'; }
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed loadJS(getURL('/settings/s.js?p=0'), false); // If we set async false, file is loaded and executed, then next statement is processed
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
</script> </script>
<style> <style>
@ -65,15 +77,15 @@
</style> </style>
</head> </head>
<body onload="S()"> <body onload="S()">
<button type=submit id="b" onclick="window.location='/'">Back</button> <button type=submit id="b" onclick="window.location=getURL('/')">Back</button>
<button type="submit" onclick="window.location='./settings/wifi'">WiFi Setup</button> <button type="submit" onclick="window.location=getURL('/settings/wifi')">WiFi Setup</button>
<button type="submit" onclick="window.location='./settings/leds'">LED Preferences</button> <button type="submit" onclick="window.location=getURL('/settings/leds')">LED Preferences</button>
<button id="2dbtn" style="display:none;" type="submit" onclick="window.location='./settings/2D'">2D Configuration</button> <button id="2dbtn" style="display:none;" type="submit" onclick="window.location=getURL('/settings/2D')">2D Configuration</button>
<button type="submit" onclick="window.location='./settings/ui'">User Interface</button> <button type="submit" onclick="window.location=getURL('/settings/ui')">User Interface</button>
<button id="dmxbtn" style="display:none;" type="submit" onclick="window.location='./settings/dmx'">DMX Output</button> <button id="dmxbtn" style="display:none;" type="submit" onclick="window.location=getURL('/settings/dmx')">DMX Output</button>
<button type="submit" onclick="window.location='./settings/sync'">Sync Interfaces</button> <button type="submit" onclick="window.location=getURL('/settings/sync')">Sync Interfaces</button>
<button type="submit" onclick="window.location='./settings/time'">Time & Macros</button> <button type="submit" onclick="window.location=getURL('/settings/time')">Time & Macros</button>
<button type="submit" onclick="window.location='./settings/um'">Usermods</button> <button type="submit" onclick="window.location=getURL('/settings/um')">Usermods</button>
<button type="submit" onclick="window.location='./settings/sec'">Security & Updates</button> <button type="submit" onclick="window.location=getURL('/settings/sec')">Security & Updates</button>
</body> </body>
</html> </html>

View File

@ -7,11 +7,11 @@
<title>2D Set-up</title> <title>2D Set-up</title>
<script> <script>
var d=document; var d=document;
var loc = false, locip; var loc = false, locip, locproto = "http:";
var maxPanels=64; var maxPanels=64;
var ctx = null; // WLEDMM var ctx = null; // WLEDMM
function H(){window.open("https://kno.wled.ge/features/2D");} function H(){window.open("https://kno.wled.ge/features/2D");}
function B(){window.open("/settings","_self");} function B(){window.open(getURL("/settings"),"_self");}
function gId(n){return d.getElementById(n);} function gId(n){return d.getElementById(n);}
function fS(){d.Sf.submit();} // <button type=submit> sometimes didn't work function fS(){d.Sf.submit();} // <button type=submit> sometimes didn't work
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
@ -35,16 +35,29 @@
}); });
} }
function S() { function S() {
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy
let path = l.pathname;
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
} }
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=10'; }
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed loadJS(getURL('/settings/s.js?p=10'), false); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/2D');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
function UI() { function UI() {
@ -322,7 +335,7 @@ Y:<input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()">
<option value="1">Vertical</option> <option value="1">Vertical</option>
</select><br> </select><br>
Serpentine: <input type="checkbox" name="PS"><br> Serpentine: <input type="checkbox" name="PS"><br>
<i style="color:#fa0;">Pressing Populate will create LED panel layout with pre-arranged matrix.<br>Values above <i>will not</i> affect final layout.<br> <i class="warn">Pressing Populate will create LED panel layout with pre-arranged matrix.<br>Values above <i>will not</i> affect final layout.<br>
WARNING: You may need to update each panel parameters after they are generated.</i><br> WARNING: You may need to update each panel parameters after they are generated.</i><br>
<button type="button" onclick="gen();expand(gId('expGen'),gId('mxGen'));">Populate</button> <button type="button" onclick="gen();expand(gId('expGen'),gId('mxGen'));">Populate</button>
</div> </div>

View File

@ -7,9 +7,9 @@
<title>DMX Settings</title> <title>DMX Settings</title>
<script> <script>
var d=document; var d=document;
var loc = false, locip; var loc = false, locip, locproto = "http:";
function H(){window.open("https://github.com/Aircoookie/WLED/wiki/DMX");} function H(){window.open("https://github.com/Aircoookie/WLED/wiki/DMX");}
function B(){window.history.back();} function B(){window.open(getURL("/settings"),"_self");}
function GCH(num) { function GCH(num) {
d.getElementById('dmxchannels').innerHTML += ""; d.getElementById('dmxchannels').innerHTML += "";
for (i=0;i<num;i++) { for (i=0;i<num;i++) {
@ -54,16 +54,29 @@
}); });
} }
function S(){ function S(){
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy
let path = l.pathname;
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
} }
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=7'; }
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed loadJS(getURL('/settings/s.js?p=7'), false); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/dmx');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
</script> </script>
<style>@import url("style.css");</style> <style>@import url("style.css");</style>

View File

@ -10,11 +10,11 @@
d.um_p = []; d.um_p = [];
d.rsvd = []; d.rsvd = [];
d.ro_gpio = []; d.ro_gpio = [];
d.max_gpio = 39; d.max_gpio = 50;
var customStarts=false,startsDirty=[],maxCOOverrides=5; var customStarts=false,startsDirty=[],maxCOOverrides=5;
var loc = false, locip; var loc = false, locip, locproto = "http:";
function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");} function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");}
function B(){window.open("/settings","_self");} function B(){window.open(getURL("/settings"),"_self");}
function gId(n){return d.getElementById(n);} function gId(n){return d.getElementById(n);}
function off(n){d.getElementsByName(n)[0].value = -1;} function off(n){d.getElementsByName(n)[0].value = -1;}
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
@ -26,8 +26,12 @@
d.body.appendChild(scE); d.body.appendChild(scE);
// success event // success event
scE.addEventListener("load", () => { scE.addEventListener("load", () => {
GetV();checkSi();setABL(); GetV();
checkSi();
setABL();
d.Sf.addEventListener("submit", trySubmit);
if (d.um_p[0]==-1) d.um_p.shift(); if (d.um_p[0]==-1) d.um_p.shift();
pinDropdowns();
}); });
// error event // error event
scE.addEventListener("error", (ev) => { scE.addEventListener("error", (ev) => {
@ -49,7 +53,7 @@
maxB = b; maxV = v; maxM = m; maxPB = p; maxL = l; maxB = b; maxV = v; maxM = m; maxPB = p; maxL = l;
} }
function pinsOK() { function pinsOK() {
var LCs = d.getElementsByTagName("input"); var LCs = d.Sf.querySelectorAll("#mLC input[name^=L]"); // input fields
for (i=0; i<LCs.length; i++) { for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2); var nm = LCs[i].name.substring(0,2);
// ignore IP address // ignore IP address
@ -59,23 +63,36 @@
if (t>=80) continue; if (t>=80) continue;
} }
//check for pin conflicts //check for pin conflicts
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR") 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 (LCs[i].value!="" && LCs[i].value!="-1") {
var p = []; // used pin array var p = d.rsvd.concat(d.um_p); // used pin array
for (k=0;k<d.rsvd.length;k++) p.push(d.rsvd[k]); // fill with reservations d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
for (k=0;k<d.um_p.length;k++) p.push(d.um_p[k]); // fill with usermod pins if (p.some((e)=>e==parseInt(LCs[i].value))) {
if (p.some((e)=>e==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);LCs[i].value="";LCs[i].focus();return false;} alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);
else if (!(nm == "IR" || nm=="BT") && d.ro_gpio.some((e)=>e==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);LCs[i].value="";LCs[i].focus();return false;} LCs[i].value="";
for (j=i+1; j<LCs.length; j++) LCs[i].focus();
{ return false;
}
else if (/*!(nm == "IR" || nm=="BT") &&*/ d.ro_gpio.some((e)=>e==parseInt(LCs[i].value))) {
alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);
LCs[i].value="";
LCs[i].focus();
return false;
}
for (j=i+1; j<LCs.length; j++) {
var n2 = LCs[j].name.substring(0,2); 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 (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4"/* || n2=="RL" || n2=="BT" || n2=="IR"*/) {
if (n2.substring(0,1)==="L") { if (n2.substring(0,1)==="L") {
var m = LCs[j].name.substring(2); var m = LCs[j].name.substring(2);
var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10); var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10);
if (t2>=80) continue; if (t2>=80) continue;
} }
if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {alert(`Pin conflict between ${LCs[i].name}/${LCs[j].name}!`);LCs[j].value="";LCs[j].focus();return false;} if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {
alert(`Pin conflict between ${LCs[i].name}/${LCs[j].name}!`);
LCs[j].value="";
LCs[j].focus();
return false;
}
} }
} }
} }
@ -96,6 +113,7 @@
gId('abl').style.display = (en) ? 'inline':'none'; gId('abl').style.display = (en) ? 'inline':'none';
gId('psu2').style.display = (en) ? 'inline':'none'; gId('psu2').style.display = (en) ? 'inline':'none';
if (d.Sf.LA.value > 0) setABL(); if (d.Sf.LA.value > 0) setABL();
UI();
} }
function enLA() function enLA()
{ {
@ -117,28 +135,28 @@
default: gId('LAdis').style.display = 'inline'; default: gId('LAdis').style.display = 'inline';
} }
gId('m1').innerHTML = maxM; gId('m1').innerHTML = maxM;
d.getElementsByName("Sf")[0].addEventListener("submit", trySubmit);
UI();
} }
//returns mem usage //returns mem usage
function getMem(t, n) { function getMem(t, n) {
let len = parseInt(d.getElementsByName("LC"+n)[0].value); let len = parseInt(d.getElementsByName("LC"+n)[0].value);
len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too
let dbl = 0;
if (d.Sf.LD.checked) dbl = len * 3; // double buffering
if (t < 32) { if (t < 32) {
if (t==26 || t==29) len *= 2; // 16 bit LEDs if (t==26 || t==29) len *= 2; // 16 bit LEDs
if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem
if (t > 28) return len*20; //RGBW if (t > 28) return len*20 + dbl; //RGBW
return len*15; return len*15 + dbl;
} else if (maxM >= 10000) //ESP32 RMT uses double buffer? } else if (maxM >= 10000) //ESP32 RMT uses double buffer?
{ {
if (t > 28) return len*8; //RGBW if (t > 28) return len*8 + dbl; //RGBW
return len*6; return len*6 + dbl;
} }
if (t > 28) return len*4; //RGBW if (t > 28) return len*4 + dbl; //RGBW
return len*3; return len*3 + dbl;
} }
if (t > 31 && t < 48) return 5; if (t > 31 && t < 48) return 5; // analog
return len*3; return len*3 + dbl;
} }
function UI(change=false) function UI(change=false)
@ -151,15 +169,13 @@
else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value; else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value;
// enable/disable LED fields // enable/disable LED fields
var s = d.getElementsByTagName("select"); d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{
for (i=0; i<s.length; i++) {
// is the field a LED type? // is the field a LED type?
if (s[i].name.substring(0,2)=="LT") { var n = s.name.substring(2);
var n = s[i].name.substring(2); var t = parseInt(s.value);
var t = parseInt(s[i].value,10);
gId("p0d"+n).innerHTML = (t>=80 && t<96) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:"; gId("p0d"+n).innerHTML = (t>=80 && t<96) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:";
gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : ""; gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : "";
var LK = d.getElementsByName("L1"+n)[0]; // clock pin //var LK = d.getElementsByName("L1"+n)[0]; // clock pin
memu += getMem(t, n); // calc memory memu += getMem(t, n); // calc memory
@ -196,8 +212,7 @@
gId("dig"+n+"l").style.display = (t > 48 && t < 64) ? "inline":"none"; // bus clock speed gId("dig"+n+"l").style.display = (t > 48 && t < 64) ? "inline":"none"; // bus clock speed
gId("rev"+n).innerHTML = (t >= 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog gId("rev"+n).innerHTML = (t >= 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog
gId("psd"+n).innerHTML = (t >= 40 && t < 48) ? "Index:":"Start:"; // change analog start description gId("psd"+n).innerHTML = (t >= 40 && t < 48) ? "Index:":"Start:"; // change analog start description
} });
}
// display global white channel overrides // display global white channel overrides
gId("wc").style.display = (gRGBW) ? 'inline':'none'; gId("wc").style.display = (gRGBW) ? 'inline':'none';
if (!gRGBW) { if (!gRGBW) {
@ -205,7 +220,7 @@
d.Sf.CR.checked = false; d.Sf.CR.checked = false;
} }
// check for pin conflicts // check for pin conflicts
var LCs = d.getElementsByTagName("input"); var LCs = d.Sf.querySelectorAll("#mLC input[name^=L]"); // input fields
var sLC = 0, sPC = 0, maxLC = 0; var sLC = 0, sPC = 0, maxLC = 0;
for (i=0; i<LCs.length; i++) { for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2); // field name var nm = LCs[i].name.substring(0,2); // field name
@ -243,15 +258,14 @@
} }
} }
// check for pin conflicts // check for pin conflicts
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR") 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 (LCs[i].value!="" && LCs[i].value!="-1") {
var p = []; // used pin array var p = d.rsvd.concat(d.um_p); // used pin array
for (k=0;k<d.rsvd.length;k++) p.push(d.rsvd[k]); // fill with reservations d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
for (k=0;k<d.um_p.length;k++) p.push(d.um_p[k]); // fill with usermod pins
for (j=0; j<LCs.length; j++) { for (j=0; j<LCs.length; j++) {
if (i==j) continue; if (i==j) continue;
var n2 = LCs[j].name.substring(0,2); 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 (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4"/* || n2=="RL" || n2=="BT" || n2=="IR"*/) {
if (n2.substring(0,1)==="L") { if (n2.substring(0,1)==="L") {
var m = LCs[j].name.substring(2); var m = LCs[j].name.substring(2);
var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10); var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10);
@ -261,13 +275,13 @@
} }
} }
// now check for conflicts // now check for conflicts
if (p.some((e)=>e==parseInt(LCs[i].value,10))) LCs[i].style.color="red"; else LCs[i].style.color=d.ro_gpio.some((e)=>e==parseInt(LCs[i].value,10))?"orange":"#fff"; if (p.some((e)=>e==parseInt(LCs[i].value))) LCs[i].style.color="red"; else LCs[i].style.color=d.ro_gpio.some((e)=>e==parseInt(LCs[i].value))?"orange":"#fff";
} }
// check buttons, IR & relay // check buttons, IR & relay
if (nm=="IR" || nm=="BT" || nm=="RL") { //if (nm=="IR" || nm=="BT" || nm=="RL") {
LCs[i].max = d.max_gpio; // LCs[i].max = d.max_gpio;
LCs[i].min = -1; // LCs[i].min = -1;
} //}
} }
// update total led count // update total led count
gId("lc").textContent = sLC; gId("lc").textContent = sLC;
@ -366,11 +380,11 @@ ${i+1}:
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp; <span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp;
<div id="dig${i}c" style="display:inline">Length: <input type="number" name="LC${i}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div><br> <div id="dig${i}c" style="display:inline">Length: <input type="number" name="LC${i}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div><br>
</div> </div>
<span id="p0d${i}">GPIO:</span> <input type="number" name="L0${i}" required class="s" onchange="UI()"/> <span id="p0d${i}">GPIO:</span><input type="number" name="L0${i}" required class="s" onchange="UI();pinUpd(this);"/>
<span id="p1d${i}"></span><input type="number" name="L1${i}" class="s" onchange="UI()"/> <span id="p1d${i}"></span><input type="number" name="L1${i}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p2d${i}"></span><input type="number" name="L2${i}" class="s" onchange="UI()"/> <span id="p2d${i}"></span><input type="number" name="L2${i}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p3d${i}"></span><input type="number" name="L3${i}" class="s" onchange="UI()"/> <span id="p3d${i}"></span><input type="number" name="L3${i}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" class="s" onchange="UI()"/> <span id="p4d${i}"></span><input type="number" name="L4${i}" class="s" onchange="UI();pinUpd(this);"/>
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div> <div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></div> <div id="dig${i}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div> <div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
@ -546,17 +560,126 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
} }
} }
} }
function pinDropdowns() {
let fields = ["IR","RL"]; // IR & relay
gId("btns").querySelectorAll('input[type="number"]').forEach((e)=>{fields.push(e.name);}) // buttons
for (let i of d.Sf.elements) {
if (i.type === "number" && fields.includes(i.name)) { //select all pin select elements
let v = parseInt(i.value);
let sel = addDropdown(i.name,0);
for (var j = -1; j <= d.max_gpio; j++) {
if (d.rsvd.includes(j)) continue;
let foundPin = d.um_p.indexOf(j);
let txt = (j === -1) ? "unused" : `${j}`;
if (foundPin >= 0 && j !== v) txt += ` used`; // already reserved pin
if (d.ro_gpio.includes(j)) txt += " (R/O)";
let opt = addOption(sel, txt, j);
if (j === v) opt.selected = true; // this is "our" pin
else if (d.um_p.includes(j)) opt.disabled = true; // someone else's pin
}
}
}
// update select options
d.Sf.querySelectorAll("select.pin").forEach((e)=>{pinUpd(e);});
// add dataset values for LED GPIO pins
d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i)=>{
if (i.value!=="" && i.value>=0)
i.dataset.val = i.value;
});
}
function pinUpd(e) {
// update changed select options across all usermods
let oldV = parseInt(e.dataset.val);
e.dataset.val = e.value;
let txt = e.name;
let pins = [];
d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i)=>{
if (i.value!=="" && i.value>=0 && i.max<255)
pins.push(i.value);
});
let selects = d.Sf.querySelectorAll("select.pin");
for (let sel of selects) {
if (sel == e) continue
Array.from(sel.options).forEach((i)=>{
let led = pins.includes(i.value);
if (!(i.value==oldV || i.value==e.value || led)) return;
if (i.value == -1) {
i.text = "unused";
return
}
i.text = i.value;
if (i.value==oldV) {
i.disabled = false;
}
if (i.value==e.value || led) {
i.disabled = true;
i.text += ` ${led?'LED':txt}`;
}
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
});
}
}
// https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option
function addDropdown(field) {
let sel = d.createElement('select');
sel.classList.add("pin");
let inp = d.getElementsByName(field)[0];
if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName
let v = inp.value;
let n = inp.name;
// copy the existing input element's attributes to the new select element
for (var i = 0; i < inp.attributes.length; ++ i) {
var att = inp.attributes[i];
// type and value don't apply, so skip them
// ** you might also want to skip style, or others -- modify as needed **
if (att.name != 'type' && att.name != 'value' && att.name != 'class' && att.name != 'style') {
sel.setAttribute(att.name, att.value);
}
}
sel.setAttribute("data-val", v);
sel.setAttribute("onchange", "pinUpd(this)");
// finally, replace the old input element with the new select element
inp.parentElement.replaceChild(sel, inp);
return sel;
}
return null;
}
function addOption(sel,txt,val) {
if (sel===null) return; // select object missing
let opt = d.createElement("option");
opt.value = val;
opt.text = txt;
sel.appendChild(opt);
for (let i=0; i<sel.childNodes.length; i++) {
let c = sel.childNodes[i];
if (c.value == sel.dataset.val) sel.selectedIndex = i;
}
return opt;
}
function S() { function S() {
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy
let path = l.pathname;
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
} }
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=2'; }
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed loadJS(getURL('/settings/s.js?p=2'), false); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/leds');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
</script> </script>
<style>@import url("style.css");</style> <style>@import url("style.css");</style>
@ -576,7 +699,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
Enable automatic brightness limiter: <input type="checkbox" name="ABen" onchange="enABL()" id="able"><br> Enable automatic brightness limiter: <input type="checkbox" name="ABen" onchange="enABL()" id="able"><br>
<div id="abl"> <div id="abl">
Maximum Current: <input name="MA" type="number" class="l" 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;"> <div id="ampwarning" class="warn" style="display: none;">
&#9888; Your power supply provides high current.<br> &#9888; Your power supply provides high current.<br>
To improve the safety of your setup,<br> To improve the safety of your setup,<br>
please use thick cables,<br> please use thick cables,<br>
@ -604,14 +727,14 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
<button type="button" id="-" onclick="addLEDs(-1,false)">-</button><br> <button type="button" id="-" onclick="addLEDs(-1,false)">-</button><br>
LED Memory Usage: <span id="m0">0</span> / <span id="m1">?</span> B<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="dbar" style="display:inline-block; width: 100px; height: 10px; border-radius: 20px;"></div><br>
<div id="ledwarning" style="color: orange; display: none;"> <div id="ledwarning" class="warn" style="display: none;">
&#9888; You might run into stability or lag issues.<br> &#9888; You might run into stability or lag issues.<br>
Use less than <span id="wreason">800 LEDs per output</span> for the best experience!<br> Use less than <span id="wreason">800 LEDs per output</span> for the best experience!<br>
</div> </div>
<hr class="sml"> <hr class="sml">
Make a segment for each output: <input type="checkbox" name="MS"><br> Make a segment for each output: <input type="checkbox" name="MS"><br>
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"><br> Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"><br>
Use global LED buffer: <input type="checkbox" name="LD"><br> Use global LED buffer: <input type="checkbox" name="LD" onchange="UI()"><br>
<hr class="sml"> <hr class="sml">
<div id="color_order_mapping"> <div id="color_order_mapping">
Color Order Override: Color Order Override:

View File

@ -7,7 +7,7 @@
<title>PIN required</title> <title>PIN required</title>
<script> <script>
var d = document; var d = document;
function B() { window.open("/settings","_self"); } function B() { window.open("../settings","_self"); }
</script> </script>
<style> <style>
@import url("style.css"); @import url("style.css");

View File

@ -7,10 +7,10 @@
<title>Misc Settings</title> <title>Misc Settings</title>
<script> <script>
var d = document; var d = document;
var loc = false, locip; var loc = false, locip, locproto = "http:";
function H() { window.open("https://kno.wled.ge/features/settings/#security-settings"); } function H() { window.open("https://kno.wled.ge/features/settings/#security-settings"); }
function B() { window.open("/settings","_self"); } function B() { window.open(getURL("/settings"),"_self"); }
function U() { window.open("/update","_self"); } function U() { window.open(getURL("/update"),"_self"); }
function gId(s) { return d.getElementById(s); } function gId(s) { return d.getElementById(s); }
function isObj(o) { return (o && typeof o === 'object' && !Array.isArray(o)); } function isObj(o) { return (o && typeof o === 'object' && !Array.isArray(o)); }
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
@ -47,7 +47,7 @@
var req = new XMLHttpRequest(); var req = new XMLHttpRequest();
req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)}); req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
req.addEventListener('error', function(e){showToast(e.stack,true);}); req.addEventListener('error', function(e){showToast(e.stack,true);});
req.open("POST", "/upload"); req.open("POST", getURL("/upload"));
var formData = new FormData(); var formData = new FormData();
formData.append("data", fO.files[0], name); formData.append("data", fO.files[0], name);
req.send(formData); req.send(formData);
@ -65,16 +65,33 @@
x.setAttribute("download","wled_" + x.getAttribute("download") + (sd=="WLED"?"":("_" +sd))); x.setAttribute("download","wled_" + x.getAttribute("download") + (sd=="WLED"?"":("_" +sd)));
} }
function S() { function S() {
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy
let path = l.pathname;
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
} }
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=6'; }
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed if (loc) {
gId("bckcfg").setAttribute('href',getURL(gId("bckcfg").pathname));
gId("bckpresets").setAttribute('href',getURL(gId("bckpresets").pathname));
}
loadJS(getURL('/settings/s.js?p=6'), false); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/sec');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
</script> </script>
<style> <style>
@ -89,7 +106,7 @@
</div> </div>
<h2>Security & Update setup</h2> <h2>Security & Update setup</h2>
Settings PIN: <input type="password" id="PIN" name="PIN" size="4" maxlength="4" minlength="4" onkeydown="checkNum(this)" pattern="[0-9]*" inputmode="numeric" title="Please enter a 4 digit number"><br> Settings PIN: <input type="password" id="PIN" name="PIN" size="4" maxlength="4" minlength="4" onkeydown="checkNum(this)" pattern="[0-9]*" inputmode="numeric" title="Please enter a 4 digit number"><br>
<div style="color: #fa0;">&#9888; Unencrypted transmission. Be prudent when selecting PIN, do NOT use your banking, door, SIM, etc. pin!</div><br> <div class="warn">&#9888; Unencrypted transmission. Be prudent when selecting PIN, do NOT use your banking, door, SIM, etc. pin!</div><br>
Lock wireless (OTA) software update: <input type="checkbox" name="NO"><br> Lock wireless (OTA) software update: <input type="checkbox" name="NO"><br>
Passphrase: <input type="password" name="OP" maxlength="32"><br> Passphrase: <input type="password" name="OP" maxlength="32"><br>
To enable OTA, for security reasons you need to also enter the correct password!<br> To enable OTA, for security reasons you need to also enter the correct password!<br>
@ -99,7 +116,7 @@
Deny access to WiFi settings if locked: <input type="checkbox" name="OW"><br><br> Deny access to WiFi settings if locked: <input type="checkbox" name="OW"><br><br>
Factory reset: <input type="checkbox" name="RS"><br> Factory reset: <input type="checkbox" name="RS"><br>
All settings and presets will be erased.<br><br> All settings and presets will be erased.<br><br>
<div style="color: #fa0;">&#9888; Unencrypted transmission. An attacker on the same network can intercept form data!</div> <div class="warn">&#9888; Unencrypted transmission. An attacker on the same network can intercept form data!</div>
<hr> <hr>
<h3>Software Update</h3> <h3>Software Update</h3>
<button type="button" onclick="U()">Manual OTA Update</button><br> <button type="button" onclick="U()">Manual OTA Update</button><br>
@ -110,7 +127,7 @@
<div>Restore presets<br><input type="file" name="data" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data,'/presets.json');">Upload</button><br></div><br> <div>Restore presets<br><input type="file" name="data" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data,'/presets.json');">Upload</button><br></div><br>
<a class="btn lnk" id="bckpresets" href="/cfg.json" download="cfg">Backup configuration</a><br> <a class="btn lnk" id="bckpresets" href="/cfg.json" download="cfg">Backup configuration</a><br>
<div>Restore configuration<br><input type="file" name="data2" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data2,'/cfg.json');">Upload</button><br></div> <div>Restore configuration<br><input type="file" name="data2" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data2,'/cfg.json');">Upload</button><br></div>
<div style="color: #fa0;">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br> <div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
Incorrect configuration may require a factory reset or re-flashing of your ESP.</div> Incorrect configuration may require a factory reset or re-flashing of your ESP.</div>
For security reasons, passwords are not backed up. For security reasons, passwords are not backed up.
<hr> <hr>

View File

@ -6,11 +6,11 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Sync Settings</title> <title>Sync Settings</title>
<script>var d=document; <script>var d=document;
var loc = false, locip; var loc = false, locip, locproto = "http:";
function gId(s){return d.getElementById(s);} function gId(s){return d.getElementById(s);}
function toggle(el){gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide");} function toggle(el){gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide");}
function H(){window.open("https://kno.wled.ge/interfaces/udp-notifier/");} function H(){window.open("https://kno.wled.ge/interfaces/udp-notifier/");}
function B(){window.open("/settings","_self");} function B(){window.open(getURL("/settings"),"_self");}
function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;} function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;}
else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} } else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} }
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
@ -56,16 +56,28 @@
function SP(){var p = d.Sf.DI.value; gId("xp").style.display = (p > 0)?"none":"block"; if (p > 0) d.Sf.EP.value = p;} function SP(){var p = d.Sf.DI.value; gId("xp").style.display = (p > 0)?"none":"block"; if (p > 0) d.Sf.EP.value = p;}
function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();FC();} function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();FC();}
function S(){ function S(){
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy
let paths = l.pathname.slice(1,l.pathname.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
} }
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=4'; }
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed loadJS(getURL('/settings/s.js?p=4'), false); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/sync');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
</script> </script>
<style>@import url("style.css");</style> <style>@import url("style.css");</style>
@ -172,7 +184,7 @@ Realtime LED offset: <input name="WO" type="number" min="-255" max="255" require
<hr class="sml"> <hr class="sml">
<h3>Alexa Voice Assistant</h3> <h3>Alexa Voice Assistant</h3>
<div id="NoAlexa" class="hide"> <div id="NoAlexa" class="hide">
<em style="color:#fa0;">This firmware build does not include Alexa support.<br></em><br> <i class="warn">This firmware build does not include Alexa support.<br></i><br>
</div> </div>
<div id="Alexa"> <div id="Alexa">
Emulate Alexa device: <input type="checkbox" name="AL"><br> Emulate Alexa device: <input type="checkbox" name="AL"><br>
@ -180,7 +192,7 @@ Alexa invocation name: <input type="text" name="AI" maxlength="32"><br>
Also emulate devices to call the first <input name="AP" type="number" class="s" min="0" max="9" required> presets<br><br> Also emulate devices to call the first <input name="AP" type="number" class="s" min="0" max="9" required> presets<br><br>
</div> </div>
<hr class="sml"> <hr class="sml">
<div style="color: #fa0;">&#9888; <b>MQTT and Hue sync all connect to external hosts!<br> <div class="warn">&#9888; <b>MQTT and Hue sync all connect to external hosts!<br>
This may impact the responsiveness of WLED.</b><br> This may impact the responsiveness of WLED.</b><br>
</div> </div>
For best results, only use one of these services at a time.<br> For best results, only use one of these services at a time.<br>
@ -188,7 +200,7 @@ For best results, only use one of these services at a time.<br>
<hr class="sml"> <hr class="sml">
<h3>MQTT</h3> <h3>MQTT</h3>
<div id="NoMQTT" class="hide"> <div id="NoMQTT" class="hide">
<em style="color:#fa0;">This firmware build does not include MQTT support.<br></em> <i class="warn">This firmware build does not include MQTT support.<br></i>
</div> </div>
<div id="MQTT"> <div id="MQTT">
Enable MQTT: <input type="checkbox" name="MQ"><br> Enable MQTT: <input type="checkbox" name="MQ"><br>
@ -207,7 +219,7 @@ Retain brightness & color messages: <input type="checkbox" name="RT"><br>
</div> </div>
<h3>Philips Hue</h3> <h3>Philips Hue</h3>
<div id="NoHue" class="hide"> <div id="NoHue" class="hide">
<em style="color:#fa0;">This firmware build does not include Philips Hue support.<br></em> <em class="warn">This firmware build does not include Philips Hue support.<br></em>
</div> </div>
<div id="Hue"> <div id="Hue">
<i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br> <i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br>

View File

@ -7,11 +7,11 @@
<title>Time Settings</title> <title>Time Settings</title>
<script> <script>
var d=document; var d=document;
var loc = false, locip; var loc = false, locip, locproto = "http:";
var el=false; var el=false;
var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
function H() { window.open("https://kno.wled.ge/features/settings/#time-settings"); } function H() { window.open("https://kno.wled.ge/features/settings/#time-settings"); }
function B() { window.open("/settings","_self"); } function B() { window.open(getURL("/settings"),"_self"); }
function gId(s) { return d.getElementById(s); } function gId(s) { return d.getElementById(s); }
function gN(s) { return d.getElementsByName(s)[0]; } function gN(s) { return d.getElementsByName(s)[0]; }
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
@ -33,16 +33,29 @@
}); });
} }
function S() { function S() {
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy
let path = l.pathname;
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
} }
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=5'; }
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed loadJS(getURL('/settings/s.js?p=5'), false); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/time');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
function expand(o,i) function expand(o,i)
{ {

View File

@ -7,7 +7,7 @@
<title>UI Settings</title> <title>UI Settings</title>
<script> <script>
var d = document; var d = document;
var loc = false, locip; var loc = false, locip, locproto = "http:";
var initial_ds, initial_st, initial_su; var initial_ds, initial_st, initial_su;
var sett = null; var sett = null;
var l = { var l = {
@ -185,21 +185,33 @@
alert("Loading of configuration script failed.\nIncomplete page data!"); alert("Loading of configuration script failed.\nIncomplete page data!");
}); });
} }
function S() function S() {
{ let l = window.location;
if (window.location.protocol == "file:") { if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy
let path = l.pathname;
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
} }
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=3'; }
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed loadJS(getURL('/settings/s.js?p=3'), false); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/ui');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
function H() { window.open("https://kno.wled.ge/features/settings/#user-interface-settings"); } function H() { window.open("https://kno.wled.ge/features/settings/#user-interface-settings"); }
function B() { window.open("/settings","_self"); } function B() { window.open(getURL("/settings"),"_self"); }
function UI() function UI()
{ {
gId('idonthateyou').style.display = (gId('dm').checked) ? 'inline':'none'; gId('idonthateyou').style.display = (gId('dm').checked) ? 'inline':'none';
@ -249,7 +261,7 @@
Server description: <input type="text" name="DS" maxlength="32"><br> Server description: <input type="text" name="DS" maxlength="32"><br>
Sync button toggles both send and receive: <input type="checkbox" name="ST"><br> Sync button toggles both send and receive: <input type="checkbox" name="ST"><br>
<div id="NoSimple" class="hide"> <div id="NoSimple" class="hide">
<em style="color:#fa0;">This firmware build does not include simplified UI support.<br></em> <i class="warn">This firmware build does not include simplified UI support.<br></i>
</div> </div>
<div id="Simple">Enable simplified UI: <input type="checkbox" name="SU"><br></div> <div id="Simple">Enable simplified UI: <input type="checkbox" name="SU"><br></div>
<i>The following UI customization settings are unique both to the WLED device and this browser.<br> <i>The following UI customization settings are unique both to the WLED device and this browser.<br>

View File

@ -7,19 +7,19 @@
<title>Usermod Settings</title> <title>Usermod Settings</title>
<script> <script>
var d = document; var d = document;
d.max_gpio = 39; d.max_gpio = 50;
d.um_p = []; d.um_p = [];
d.rsvd = []; d.rsvd = [];
d.ro_gpio = []; d.ro_gpio = [];
var umCfg = {}; var umCfg = {};
var pins = [], pinO = [], owner; var pins = [], pinO = [], owner;
var loc = false, locip; var loc = false, locip, locproto = "http:";
var urows; var urows;
var numM = 0; var numM = 0;
function gId(s) { return d.getElementById(s); } function gId(s) { return d.getElementById(s); }
function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); } function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); }
function H() { window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings"); } function H() { window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings"); }
function B() { window.open("/settings","_self"); } function B() { window.open(getURL("/settings"),"_self"); }
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) { function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script"); let scE = d.createElement("script");
@ -39,6 +39,7 @@
d.Sf.MISO.max = d.max_gpio; d.Sf.MISO.max = d.max_gpio;
let inp = d.getElementsByTagName("input"); let inp = d.getElementsByTagName("input");
for (let i of inp) if (i.type === "number" && i.name.replace("[]","").substr(-3) === "pin") i.max = d.max_gpio; for (let i of inp) if (i.type === "number" && i.name.replace("[]","").substr(-3) === "pin") i.max = d.max_gpio;
pinDropdowns();
}); });
// error event // error event
scE.addEventListener("error", (ev) => { scE.addEventListener("error", (ev) => {
@ -47,16 +48,30 @@
}); });
} }
function S() { function S() {
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy
let path = l.pathname;
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
}
} }
ldS(); ldS();
if (!numM) gId("um").innerHTML = "No Usermods installed."; if (!numM) gId("um").innerHTML = "No Usermods installed.";
if (loc) d.Sf.action = getURL('/settings/um');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer // https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer
function isF(n) { return n === +n && n !== (n|0); } function isF(n) { return n === +n && n !== (n|0); }
@ -89,8 +104,10 @@
if (isO(o)) { if (isO(o)) {
for (const [k,v] of Object.entries(o)) { for (const [k,v] of Object.entries(o)) {
if (isO(v)) { if (isO(v)) {
let oldO = owner; // keep parent name
owner = k; owner = k;
getPins(v); getPins(v);
owner = oldO;
continue; continue;
} }
if (k.replace("[]","").substr(-3)=="pin") { if (k.replace("[]","").substr(-3)=="pin") {
@ -149,11 +166,67 @@
urows += `<input type="${t==="int"?"number":t}" name="${k}:${f}${a?"[]":""}" ${c} oninput="check(this,'${k.substr(k.indexOf(":")+1)}')"><br>`; urows += `<input type="${t==="int"?"number":t}" name="${k}:${f}${a?"[]":""}" ${c} oninput="check(this,'${k.substr(k.indexOf(":")+1)}')"><br>`;
} }
} }
function pinDropdowns() {
for (let i of d.Sf.elements) {
if (i.type === "number" && (i.name.includes("pin") || ["SDA","SCL","MOSI","MISO","SCLK"].includes(i.name))) { //select all pin select elements
let v = parseInt(i.value);
let sel = addDropdown(i.name,0);
for (var j = -1; j <= d.max_gpio; j++) {
if (d.rsvd.includes(j)) continue;
let foundPin = pins.indexOf(j);
let txt = (j === -1) ? "unused" : `${j}`;
if (foundPin >= 0 && j !== v) txt += ` ${pinO[foundPin]=="if"?"global":pinO[foundPin]}`; // already reserved pin
if (d.ro_gpio.includes(j)) txt += " (R/O)";
let opt = addOption(sel, txt, j);
if (j === v) opt.selected = true; // this is "our" pin
else if (pins.includes(j)) opt.disabled = true; // someone else's pin
}
}
}
}
function UI(e) {
// update changed select options across all usermods
let oldV = parseInt(e.dataset.val);
e.dataset.val = e.value;
let txt = e.name.split(":")[e.name.split(":").length-2];
let selects = d.Sf.querySelectorAll("select[class='pin']");
for (let sel of selects) {
if (sel == e) continue
Array.from(sel.options).forEach((i)=>{
if (!(i.value==oldV || i.value==e.value)) return;
if (i.value == -1) {
i.text = "unused";
return
}
i.text = i.value;
if (i.value==oldV) {
i.disabled = false;
}
if (i.value==e.value) {
i.disabled = true;
i.text += ` ${txt}`;
}
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
});
}
}
// https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option // https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option
function addDropdown(um,fld) { function addDropdown(um,fld) {
let sel = d.createElement('select'); let sel = d.createElement('select');
let arr = d.getElementsByName(um+":"+fld); if (typeof(fld) === "string") { // parameter from usermod (field name)
let inp = arr[1]; // assume 1st field to be hidden (type) if (fld.includes("pin")) sel.classList.add("pin");
um += ":"+fld;
} else if (typeof(fld) === "number") sel.classList.add("pin"); // a hack to add a class
let arr = d.getElementsByName(um);
let idx = arr[0].type==="hidden"?1:0; // ignore hidden field
if (arr.length > 2) {
// we have array of values (usually pins)
for (let i of arr) {
if (i.type === "number") break;
idx++;
}
}
let inp = arr[idx];
if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName
let v = inp.value; let v = inp.value;
let n = inp.name; let n = inp.name;
@ -167,8 +240,10 @@
} }
} }
sel.setAttribute("data-val", v); sel.setAttribute("data-val", v);
sel.setAttribute("onchange", "UI(this)");
// finally, replace the old input element with the new select element // finally, replace the old input element with the new select element
inp.parentElement.replaceChild(sel, inp); inp.parentElement.replaceChild(sel, inp);
if (arr[0].type==="hidden") arr[0].parentElement.removeChild(arr[0]); // remove hidden element from DOM
return sel; return sel;
} }
return null; return null;
@ -183,6 +258,7 @@
let c = sel.childNodes[i]; let c = sel.childNodes[i];
if (c.value == sel.dataset.val) sel.selectedIndex = i; if (c.value == sel.dataset.val) sel.selectedIndex = i;
} }
return opt;
} }
// https://stackoverflow.com/questions/26440494/insert-text-after-this-input-element-with-javascript // https://stackoverflow.com/questions/26440494/insert-text-after-this-input-element-with-javascript
function addInfo(name,el,txt, txt2="") { function addInfo(name,el,txt, txt2="") {
@ -194,10 +270,13 @@
if (txt2!="") obj[el].insertAdjacentHTML('beforebegin', txt2 + '&nbsp;'); //add pre texts if (txt2!="") obj[el].insertAdjacentHTML('beforebegin', txt2 + '&nbsp;'); //add pre texts
} }
} }
// add Help Button
function addHB(um) {
addInfo(um + ':help',0,`<button onclick="location.href='https://kno.wled.ge/usermods/${um}'" type="button">?</button>`);
}
// load settings and insert values into DOM // load settings and insert values into DOM
function ldS() { function ldS() {
var url = (loc?`http://${locip}`:'') + '/cfg.json'; fetch(getURL('/cfg.json'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then(res => { .then(res => {
@ -216,8 +295,7 @@
} }
if (urows==="") urows = "Usermods configuration not found.<br>Press <i>Save</i> to initialize defaults."; if (urows==="") urows = "Usermods configuration not found.<br>Press <i>Save</i> to initialize defaults.";
gId("um").innerHTML = urows; gId("um").innerHTML = urows;
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=8'; loadJS(getURL('/settings/s.js?p=8'), false); // If we set async false, file is loaded and executed, then next statement is processed
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
}) })
.catch((error)=>{ .catch((error)=>{
gId('lserr').style.display = "inline"; gId('lserr').style.display = "inline";
@ -242,15 +320,17 @@
</div> </div>
<h2>Usermod Setup</h2> <h2>Usermod Setup</h2>
Global I<sup>2</sup>C GPIOs (HW)<br> Global I<sup>2</sup>C GPIOs (HW)<br>
<i style="color: orange;">(only changable on ESP32, change requires reboot!)</i><br> <i class="warn">(change requires reboot!)</i><br>
SDA:<input type="number" min="-1" max="48" name="SDA" onchange="check(this,'if')" class="s" placeholder="SDA"> SDA:<input type="number" min="-1" max="48" name="SDA" onchange="check(this,'if')" class="s" placeholder="SDA">
SCL:<input type="number" min="-1" max="48" name="SCL" onchange="check(this,'if')" class="s" placeholder="SCL"> SCL:<input type="number" min="-1" max="48" name="SCL" onchange="check(this,'if')" class="s" placeholder="SCL">
<hr class="sml"> <hr class="sml">
Global SPI GPIOs (HW)<br> Global SPI GPIOs (HW)<br>
<i style="color: orange;">(only changable on ESP32, change requires reboot!)</i><br> <i class="warn">(only changable on ESP32, change requires reboot!)</i><br>
MOSI:<input type="number" min="-1" max="48" name="MOSI" onchange="check(this,'if')" class="s" placeholder="MOSI"> MOSI:<input type="number" min="-1" max="48" name="MOSI" onchange="check(this,'if')" class="s" placeholder="MOSI">
MISO:<input type="number" min="-1" max="48" name="MISO" onchange="check(this,'if')" class="s" placeholder="MISO"> MISO:<input type="number" min="-1" max="48" name="MISO" onchange="check(this,'if')" class="s" placeholder="MISO">
SCLK:<input type="number" min="-1" max="48" name="SCLK" onchange="check(this,'if')" class="s" placeholder="SCLK"> SCLK:<input type="number" min="-1" max="48" name="SCLK" onchange="check(this,'if')" class="s" placeholder="SCLK">
<hr class="sml">
Reboot after save? <input type="checkbox" name="RBT"><br>
<div id="um">Loading settings...</div> <div id="um">Loading settings...</div>
<hr><button type="button" onclick="B()">Back</button><button type="submit">Save</button> <hr><button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form> </form>

View File

@ -7,21 +7,19 @@
<title>WiFi Settings</title> <title>WiFi Settings</title>
<script> <script>
var d = document; var d = document;
var loc = false, locip; var loc = false, locip, locproto = "http:";
var scanLoops = 0, preScanSSID = ""; var scanLoops = 0, preScanSSID = "";
function gId(e) { return d.getElementById(e); } function gId(e) { return d.getElementById(e); }
function cE(e) { return d.createElement(e); } function cE(e) { return d.createElement(e); }
function H(){window.open("https://kno.wled.ge/features/settings/#wifi-settings");} function H(){window.open("https://kno.wled.ge/features/settings/#wifi-settings");}
function B(){window.open("/settings","_self");} function B(){window.open(getURL("/settings"),"_self");}
function N() { function N() {
const url = (loc?`http://${locip}`:"") + "/json/net";
const button = gId("scan"); const button = gId("scan");
button.disabled = true; button.disabled = true;
button.textContent = "Scanning..."; button.textContent = "Scanning...";
fetch(url).then((response) => { fetch(getURL("/json/net")).then((response) => {
return response.json(); return response.json();
}).then((json) => { }).then((json) => {
// Get the list of networks only, defaulting to an empty array. // Get the list of networks only, defaulting to an empty array.
@ -122,16 +120,29 @@
}); });
} }
function S() { function S() {
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem("locIp"); locip = localStorage.getItem('locIp');
if (!locip) { if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem("locIp", locip); localStorage.setItem('locIp', locip);
}
} else {
// detect reverse proxy
let path = l.pathname;
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
locproto = l.protocol;
loc = true;
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
} }
} }
let url = (loc?`http://${locip}`:'') + '/settings/s.js?p=1'; loadJS(getURL('/settings/s.js?p=1'), false); // If we set async false, file is loaded and executed, then next statement is processed
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed if (loc) d.Sf.action = getURL('/settings/wifi');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
} }
</script> </script>
<style>@import url("style.css");</style> <style>@import url("style.css");</style>

View File

@ -1,5 +1,5 @@
//page js //page js
var loc = false, locip; var loc = false, locip, locproto = "http:";
var noNewSegs = false; var noNewSegs = false;
var isOn = false, isInfo = false, isNodes = false, isRgbw = false, cct = false; var isOn = false, isInfo = false, isNodes = false, isRgbw = false, cct = false;
var whites = [0,0,0]; var whites = [0,0,0];
@ -148,22 +148,38 @@ function loadSkinCSS(cId)
l.id = cId; l.id = cId;
l.rel = 'stylesheet'; l.rel = 'stylesheet';
l.type = 'text/css'; l.type = 'text/css';
l.href = (loc?`http://${locip}`:'.') + '/skin.css'; l.href = getURL('/skin.css');
l.media = 'all'; l.media = 'all';
h.appendChild(l); h.appendChild(l);
} }
} }
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
}
async function onLoad() async function onLoad()
{ {
if (window.location.protocol == "file:") { let l = window.location;
if (l.protocol == "file:") {
loc = true; loc = true;
locip = localStorage.getItem('locIp'); locip = localStorage.getItem('locIp');
if (!locip) if (!locip) {
{
locip = prompt("File Mode. Please enter WLED IP!"); locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip); localStorage.setItem('locIp', locip);
} }
} else {
// detect reverse proxy and/or HTTPS
let pathn = l.pathname;
let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/");
//while (paths[0]==="") paths.shift();
locproto = l.protocol;
locip = l.hostname + (l.port ? ":" + l.port : "");
if (paths.length > 0 && paths[0]!=="") {
loc = true;
locip += "/" + paths[0];
} else if (locproto==="https:") {
loc = true;
}
} }
var sett = localStorage.getItem('wledUiCfg'); var sett = localStorage.getItem('wledUiCfg');
if (sett) cfg = mergeDeep(cfg, JSON.parse(sett)); if (sett) cfg = mergeDeep(cfg, JSON.parse(sett));
@ -173,7 +189,7 @@ async function onLoad()
applyCfg(); applyCfg();
if (cfg.theme.bg.url=="" || cfg.theme.bg.url === "https://picsum.photos/1920/1080") { if (cfg.theme.bg.url=="" || cfg.theme.bg.url === "https://picsum.photos/1920/1080") {
var iUrl = cfg.theme.bg.url; var iUrl = cfg.theme.bg.url;
fetch((loc?`http://${locip}`:'.') + "/holidays.json", { fetch(getURL("/holidays.json"), {
method: 'get' method: 'get'
}) })
.then((res)=>{ .then((res)=>{
@ -330,9 +346,7 @@ function loadPresets(callback = null)
pmtLast = pmt; pmtLast = pmt;
var url = (loc?`http://${locip}`:'') + '/presets.json'; fetch(getURL('/presets.json'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then(res => { .then(res => {
@ -355,9 +369,7 @@ function loadPresets(callback = null)
function loadPalettes(callback = null) function loadPalettes(callback = null)
{ {
var url = (loc?`http://${locip}`:'') + '/json/palettes'; fetch(getURL('/json/palettes'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then(res => { .then(res => {
@ -379,9 +391,7 @@ function loadPalettes(callback = null)
function loadFX(callback = null) function loadFX(callback = null)
{ {
var url = (loc?`http://${locip}`:'') + '/json/effects'; fetch(getURL('/json/effects'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then(res => { .then(res => {
@ -403,9 +413,7 @@ function loadFX(callback = null)
function loadFXData(callback = null) function loadFXData(callback = null)
{ {
var url = (loc?`http://${locip}`:'') + '/json/fxdata'; fetch(getURL('/json/fxdata'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then(res => { .then(res => {
@ -611,8 +619,7 @@ function populateNodes(i,n)
function loadNodes() function loadNodes()
{ {
var url = (loc?`http://${locip}`:'') + '/json/nodes'; fetch(getURL('/json/nodes'), {
fetch(url, {
method: 'get' method: 'get'
}) })
.then(res => { .then(res => {
@ -855,7 +862,8 @@ function cmpP(a, b)
function makeWS() { function makeWS() {
if (ws) return; if (ws) return;
ws = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws'); let url = loc ? getURL('/ws').replace("http","ws") : "ws://"+window.location.hostname+"/ws";
ws = new WebSocket(url);
ws.onmessage = (e)=>{ ws.onmessage = (e)=>{
var json = JSON.parse(e.data); var json = JSON.parse(e.data);
if (json.leds) return; //liveview packet if (json.leds) return; //liveview packet
@ -974,7 +982,6 @@ function requestJson(command=null)
if (command && !reqsLegal) return; //stop post requests from chrome onchange event on page restore if (command && !reqsLegal) return; //stop post requests from chrome onchange event on page restore
if (!jsonTimeout) jsonTimeout = setTimeout(showErrorToast, 3000); if (!jsonTimeout) jsonTimeout = setTimeout(showErrorToast, 3000);
var req = null; var req = null;
var url = (loc?`http://${locip}`:'') + '/json/si';
var useWs = (ws && ws.readyState === WebSocket.OPEN); var useWs = (ws && ws.readyState === WebSocket.OPEN);
var type = command ? 'post':'get'; var type = command ? 'post':'get';
if (command) { if (command) {
@ -991,7 +998,7 @@ function requestJson(command=null)
setTimeout(requestJson,200); setTimeout(requestJson,200);
} }
fetch(url, { fetch(getURL('/json/si'), {
method: type, method: type,
headers: { headers: {
"Content-type": "application/json; charset=UTF-8" "Content-type": "application/json; charset=UTF-8"
@ -1314,9 +1321,7 @@ function loadPalettesData(callback = null)
function getPalettesData(page, callback) function getPalettesData(page, callback)
{ {
var url = (loc?`http://${locip}`:'') + `/json/palx?page=${page}`; fetch(getURL(`/json/palx?page=${page}`), {
fetch(url, {
method: 'get', method: 'get',
headers: { headers: {
"Content-type": "application/json; charset=UTF-8" "Content-type": "application/json; charset=UTF-8"

View File

@ -61,6 +61,12 @@ button.sml {
.hide { .hide {
display: none; display: none;
} }
.err {
color: #f00;
}
.warn {
color: #fa0;
}
input { input {
background: #333; background: #333;
color: #fff; color: #fff;
@ -114,6 +120,10 @@ select {
font-family: Verdana, sans-serif; font-family: Verdana, sans-serif;
border: 0.5ch solid #333; border: 0.5ch solid #333;
} }
select.pin {
max-width: 120px;
text-overflow: ellipsis;
}
tr { tr {
line-height: 100%; line-height: 100%;
} }

View File

@ -15,7 +15,7 @@
<body onload="GetV()"> <body onload="GetV()">
<h2>WLED Software Update</h2> <h2>WLED Software Update</h2>
<form method='POST' action='/update' id='uf' enctype='multipart/form-data' onsubmit="U()"> <form method='POST' action='./update' id='uf' enctype='multipart/form-data' onsubmit="U()">
Installed version: <span class="sip">##VERSION##</span><br> Installed version: <span class="sip">##VERSION##</span><br>
Download the latest binary: <a href="https://github.com/Aircoookie/WLED/releases" target="_blank"> Download the latest binary: <a href="https://github.com/Aircoookie/WLED/releases" target="_blank">
<img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br> <img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br>

View File

@ -55,9 +55,9 @@
<h3>Thank you for installing my application!</h3> <h3>Thank you for installing my application!</h3>
<b>Next steps:</b><br><br> <b>Next steps:</b><br><br>
Connect the module to your local WiFi here!<br> Connect the module to your local WiFi here!<br>
<button onclick="window.location.href='/settings/wifi'">WiFi settings</button><br> <button onclick="window.location.href='./settings/wifi'">WiFi settings</button><br>
<i>Just trying this out in AP mode?</i><br> <i>Just trying this out in AP mode?</i><br>
<button onclick="window.location.href='/sliders'">To the controls!</button><br> <button onclick="window.location.href='./sliders'">To the controls!</button><br>
</div> </div>
</body> </body>
</html> </html>

View File

@ -25,7 +25,7 @@ void handleDDPPacket(e131_packet_t* p) {
} }
} }
uint8_t ddpChannelsPerLed = (p->dataType & 0b00111000 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel) uint8_t ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel)
uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed; uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed;
start += DMXAddress / ddpChannelsPerLed; start += DMXAddress / ddpChannelsPerLed;

View File

@ -50,6 +50,18 @@ bool getJsonValue(const JsonVariant& element, DestType& destination, const Defau
//colors.cpp //colors.cpp
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
class NeoGammaWLEDMethod {
public:
static uint8_t Correct(uint8_t value); // apply Gamma to single channel
static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB)
static void calcGammaTable(float gamma); // re-calculates & fills gamma table
static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB)
private:
static uint8_t gammaT[];
};
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false);
uint32_t color_add(uint32_t,uint32_t); uint32_t color_add(uint32_t,uint32_t);
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
@ -63,10 +75,6 @@ bool colorFromHexString(byte* rgb, const char* in);
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb); uint16_t approximateKelvinFromRGB(uint32_t rgb);
void setRandomColor(byte* rgb); void setRandomColor(byte* rgb);
uint8_t gamma8_cal(uint8_t b, float gamma);
void calcGammaTable(float gamma);
uint8_t gamma8(uint8_t b);
uint32_t gamma32(uint32_t);
//dmx.cpp //dmx.cpp
void initDMX(); void initDMX();
@ -99,7 +107,7 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len);
void handleImprovPacket(); void handleImprovPacket();
void sendImprovStateResponse(uint8_t state, bool error = false); void sendImprovStateResponse(uint8_t state, bool error = false);
void sendImprovInfoResponse(); void sendImprovInfoResponse();
void sendImprovRPCResponse(uint8_t commandId); void sendImprovRPCResponse(byte commandId);
//ir.cpp //ir.cpp
void applyRepeatActions(); void applyRepeatActions();
@ -129,8 +137,8 @@ bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE,
void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false); void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
void serializeInfo(JsonObject root); void serializeInfo(JsonObject root);
void serializeModeNames(JsonArray arr, const char *qstring); void serializeModeNames(JsonArray root);
void serializeModeData(JsonObject root); void serializeModeData(JsonArray root);
void serveJson(AsyncWebServerRequest* request); void serveJson(AsyncWebServerRequest* request);
#ifdef WLED_ENABLE_JSONLIVE #ifdef WLED_ENABLE_JSONLIVE
bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0);
@ -152,9 +160,11 @@ void handleTransitions();
void handleNightlight(); void handleNightlight();
byte scaledBri(byte in); byte scaledBri(byte in);
#ifdef WLED_ENABLE_LOXONE
//lx_parser.cpp //lx_parser.cpp
bool parseLx(int lxValue, byte* rgbw); bool parseLx(int lxValue, byte* rgbw);
void parseLxJson(int lxValue, byte segId, bool secondary); void parseLxJson(int lxValue, byte segId, bool secondary);
#endif
//mqtt.cpp //mqtt.cpp
bool initMqtt(); bool initMqtt();
@ -203,7 +213,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=tru
//udp.cpp //udp.cpp
void notify(byte callMode, bool followUp=false); void notify(byte callMode, bool followUp=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false); uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri=255, bool isRGBW=false);
void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC);
void exitRealtime(); void exitRealtime();
void handleNotifications(); void handleNotifications();
@ -372,7 +382,6 @@ void serveIndexOrWelcome(AsyncWebServerRequest *request);
void serveIndex(AsyncWebServerRequest* request); void serveIndex(AsyncWebServerRequest* request);
String msgProcessor(const String& var); String msgProcessor(const String& var);
void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255); void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255);
String settingsProcessor(const String& var);
String dmxProcessor(const String& var); String dmxProcessor(const String& var);
void serveSettings(AsyncWebServerRequest* request, bool post = false); void serveSettings(AsyncWebServerRequest* request, bool post = false);
void serveSettingsJS(AsyncWebServerRequest* request); void serveSettingsJS(AsyncWebServerRequest* request);

View File

@ -20,7 +20,7 @@ const uint8_t PAGE_usermod[] PROGMEM = {
// Autogenerated from wled00/data/msg.htm, do not edit!! // Autogenerated from wled00/data/msg.htm, do not edit!!
const char PAGE_msg[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta content="width=device-width" name="viewport"> const char PAGE_msg[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta content="width=device-width" name="viewport">
<title>WLED Message</title><script> <title>WLED Message</title><script>
function B(){window.history.back()}function RS(){window.location="/settings"}function RP(){top.location.href="/"} function B(){window.history.back()}function RS(){window.location="../settings"}function RP(){top.location.href="../"}
</script><style>@import url("style.css");</style></head><body><h2>%MSG%</body></html>)====="; </script><style>@import url("style.css");</style></head><body><h2>%MSG%</body></html>)=====";
@ -41,52 +41,52 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
#endif #endif
// Autogenerated from wled00/data/update.htm, do not edit!! // Autogenerated from wled00/data/update.htm, do not edit!!
const uint16_t PAGE_update_length = 615; const uint16_t PAGE_update_length = 616;
const uint8_t PAGE_update[] PROGMEM = { const uint8_t PAGE_update[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x75, 0x53, 0x5d, 0x6f, 0xd4, 0x30, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x75, 0x53, 0x4d, 0x6f, 0xd4, 0x30,
0x10, 0x7c, 0xcf, 0xaf, 0x70, 0xfd, 0x74, 0x27, 0x71, 0x4e, 0xa9, 0x78, 0xa1, 0x24, 0x29, 0x1c, 0x10, 0xbd, 0xe7, 0x57, 0x18, 0x9f, 0x76, 0x25, 0xd6, 0x2e, 0x1f, 0x17, 0x4a, 0x92, 0x42, 0x69,
0xad, 0x50, 0x25, 0xa4, 0x56, 0x6a, 0x0b, 0xe2, 0x09, 0x39, 0xf6, 0xe6, 0x62, 0xce, 0xb1, 0x53, 0x85, 0x2a, 0x21, 0xb5, 0x52, 0x5b, 0x10, 0x27, 0xe4, 0xd8, 0x93, 0x8d, 0x59, 0xc7, 0x4e, 0xed,
0x7b, 0x73, 0xa7, 0xa8, 0xea, 0x7f, 0x67, 0xe3, 0xdc, 0x15, 0xc4, 0xc7, 0x4b, 0x14, 0x67, 0x67, 0xc9, 0xae, 0x56, 0xa8, 0xff, 0x9d, 0x89, 0xb3, 0x5b, 0x10, 0x1f, 0x97, 0x28, 0xce, 0xbc, 0x79,
0xc7, 0xbb, 0x33, 0x93, 0xe2, 0xe4, 0xf2, 0xe6, 0xe3, 0xfd, 0xb7, 0xdb, 0x2b, 0xd6, 0x62, 0x67, 0x9e, 0x79, 0xef, 0xa5, 0x7c, 0x76, 0x71, 0xfd, 0xe1, 0xee, 0xeb, 0xcd, 0x25, 0xeb, 0xb0, 0x77,
0xab, 0xe2, 0xf0, 0x04, 0xa9, 0xab, 0xa2, 0x03, 0x94, 0x4c, 0x79, 0x87, 0xe0, 0xb0, 0xe4, 0x7b, 0x75, 0x79, 0x78, 0x82, 0x32, 0x75, 0xd9, 0x03, 0x2a, 0xa6, 0x83, 0x47, 0xf0, 0x58, 0xf1, 0x9d,
0xa3, 0xb1, 0x2d, 0x35, 0xec, 0x8c, 0x82, 0x55, 0x3a, 0x70, 0xe6, 0x64, 0x07, 0x25, 0xdf, 0x19, 0x35, 0xd8, 0x55, 0x06, 0xb6, 0x56, 0xc3, 0x2a, 0x1f, 0x38, 0xf3, 0xaa, 0x87, 0x8a, 0x6f, 0x2d,
0xd8, 0xf7, 0x3e, 0x20, 0xaf, 0xb2, 0x02, 0x0d, 0x5a, 0xa8, 0xbe, 0x7e, 0xbe, 0xba, 0x64, 0x0f, 0xec, 0x86, 0x10, 0x91, 0xd7, 0x45, 0x89, 0x16, 0x1d, 0xd4, 0x5f, 0x3e, 0x5d, 0x5e, 0xb0, 0xfb,
0xbd, 0x96, 0x08, 0x45, 0x3e, 0x7f, 0x2a, 0xa2, 0x0a, 0xa6, 0xc7, 0x2a, 0x6b, 0x06, 0xa7, 0xd0, 0xc1, 0x28, 0x84, 0x52, 0xce, 0x9f, 0xca, 0xa4, 0xa3, 0x1d, 0xb0, 0x2e, 0xda, 0xd1, 0x6b, 0xb4,
0x78, 0xc7, 0xd6, 0x8b, 0xe5, 0xd3, 0xde, 0x38, 0xed, 0xf7, 0xa2, 0x35, 0x11, 0x7d, 0x18, 0x45, 0xc1, 0xb3, 0xf3, 0xc5, 0xf2, 0xc7, 0xce, 0x7a, 0x13, 0x76, 0xa2, 0xb3, 0x09, 0x43, 0xdc, 0x8b,
0x2d, 0xd5, 0x76, 0xb1, 0x7c, 0x7e, 0x81, 0x3c, 0x10, 0x44, 0x7b, 0x35, 0x74, 0x34, 0x81, 0xd8, 0x46, 0xe9, 0xcd, 0x62, 0xf9, 0xf8, 0x04, 0xb9, 0x27, 0x88, 0x09, 0x7a, 0xec, 0x69, 0x02, 0xb1,
0x00, 0x5e, 0x59, 0x98, 0x5e, 0xd7, 0xe3, 0xb5, 0x5e, 0xf0, 0xa1, 0xe1, 0x4b, 0x11, 0x71, 0xb4, 0x06, 0xbc, 0x74, 0x30, 0xbd, 0x9e, 0xef, 0xaf, 0xcc, 0x82, 0x8f, 0x2d, 0x5f, 0x8a, 0x84, 0x7b,
0x20, 0xb4, 0x89, 0xbd, 0x95, 0x63, 0xc9, 0x9d, 0x77, 0xc0, 0x5f, 0xfd, 0xb7, 0xa5, 0x8b, 0x9b, 0x07, 0xc2, 0xd8, 0x34, 0x38, 0xb5, 0xaf, 0xb8, 0x0f, 0x1e, 0xf8, 0xf3, 0xff, 0xb6, 0xf4, 0x69,
0xbf, 0x7b, 0x6a, 0xeb, 0xd5, 0x96, 0x3f, 0x67, 0x45, 0x7e, 0x18, 0xf1, 0x30, 0x2a, 0x8b, 0x41, 0xfd, 0x77, 0x4f, 0xe3, 0x82, 0xde, 0xf0, 0xc7, 0xa2, 0x94, 0x87, 0x11, 0x0f, 0xa3, 0xb2, 0x14,
0x95, 0x3c, 0x8f, 0x80, 0x68, 0xdc, 0x26, 0xe6, 0x51, 0xfc, 0x88, 0x17, 0x7d, 0xf9, 0x96, 0x57, 0x75, 0xc5, 0x65, 0x02, 0x44, 0xeb, 0xd7, 0x49, 0x26, 0xf1, 0x3d, 0x9d, 0x0d, 0xd5, 0x1b, 0x5e,
0xbf, 0x21, 0x27, 0xaa, 0x2a, 0x7b, 0x6f, 0xba, 0x49, 0x00, 0x36, 0x04, 0xbb, 0xe0, 0x33, 0xbd, 0xff, 0x86, 0x9c, 0xa8, 0xea, 0xe2, 0x9d, 0xed, 0x27, 0x01, 0xd8, 0x18, 0xdd, 0x82, 0xcf, 0xf4,
0x8a, 0x91, 0x2f, 0xdf, 0x11, 0x32, 0x21, 0x8a, 0x7c, 0x96, 0xb4, 0xf6, 0x7a, 0x64, 0xde, 0x59, 0x3a, 0x25, 0xbe, 0x7c, 0x4b, 0xc8, 0x8c, 0x28, 0xe5, 0x2c, 0x69, 0x13, 0xcc, 0x9e, 0x05, 0xef,
0x2f, 0x75, 0xc9, 0x3f, 0x01, 0x7e, 0x59, 0x2c, 0x89, 0xae, 0x3d, 0xab, 0xb2, 0x24, 0xd9, 0x9d, 0x82, 0x32, 0x15, 0xff, 0x08, 0xf8, 0x79, 0xb1, 0x24, 0xba, 0xee, 0x65, 0x5d, 0x64, 0xc9, 0x6e,
0x6f, 0x70, 0x2f, 0x03, 0xbc, 0x68, 0x47, 0x95, 0xa2, 0xf1, 0xa1, 0x63, 0xe4, 0x45, 0xeb, 0xa9, 0x43, 0x8b, 0x3b, 0x15, 0xe1, 0x49, 0x3b, 0xaa, 0x94, 0x6d, 0x88, 0x3d, 0x23, 0x2f, 0xba, 0x40,
0xe7, 0xf6, 0xe6, 0xee, 0x9e, 0x33, 0x99, 0xe4, 0xa1, 0xe1, 0x86, 0x84, 0xe3, 0xcc, 0x50, 0x89, 0x3d, 0x37, 0xd7, 0xb7, 0x77, 0x9c, 0xa9, 0x2c, 0x4f, 0xc5, 0x85, 0x1c, 0x33, 0x90, 0x33, 0x4b,
0xf4, 0x60, 0x19, 0x90, 0x72, 0x63, 0x4f, 0xa6, 0x74, 0x83, 0x45, 0xd3, 0xcb, 0x80, 0xf9, 0xd4, 0x35, 0x12, 0x84, 0x15, 0x40, 0xd2, 0xed, 0x07, 0x72, 0xa5, 0x1f, 0x1d, 0xda, 0x41, 0x45, 0x94,
0xbf, 0x22, 0x98, 0xe4, 0x74, 0x73, 0x1c, 0xea, 0xce, 0x90, 0x9b, 0x0f, 0xd3, 0xc5, 0xd7, 0x2e, 0x13, 0xc1, 0x8a, 0x60, 0x8a, 0xd3, 0xd5, 0x69, 0x6c, 0x7a, 0x4b, 0x76, 0xde, 0x4f, 0x37, 0x5f,
0xa2, 0xb4, 0x16, 0x34, 0xdb, 0x41, 0x88, 0xc4, 0x78, 0xce, 0x8a, 0xd8, 0x4b, 0xc7, 0x32, 0x65, 0xf9, 0x84, 0xca, 0x39, 0x30, 0x6c, 0x0b, 0x31, 0x11, 0xe5, 0x29, 0x2b, 0xd3, 0xa0, 0x3c, 0x2b,
0x65, 0x8c, 0x25, 0x8f, 0xa6, 0xe7, 0xd5, 0xa9, 0x78, 0xfd, 0x46, 0x9c, 0xae, 0xea, 0x33, 0x5a, 0xb4, 0x53, 0x29, 0x55, 0x3c, 0xd9, 0x81, 0xd7, 0x27, 0xe2, 0xc5, 0x6b, 0x71, 0xb2, 0x6a, 0x5e,
0x86, 0x8a, 0xb4, 0x44, 0xa8, 0x2e, 0xfd, 0x3e, 0x2d, 0xc1, 0xb0, 0x05, 0x66, 0x69, 0x84, 0x88, 0xd1, 0x36, 0x54, 0xa4, 0x2d, 0x62, 0x7d, 0x11, 0x76, 0x79, 0x0b, 0x86, 0x1d, 0x30, 0x47, 0x23,
0xac, 0x36, 0x4e, 0x86, 0x91, 0x28, 0x24, 0xcb, 0xda, 0x00, 0x4d, 0xc9, 0x5b, 0xc4, 0x3e, 0x9e, 0x24, 0x64, 0x8d, 0xf5, 0x2a, 0xee, 0x89, 0x42, 0xb1, 0xa2, 0x8b, 0xd0, 0x56, 0xbc, 0x43, 0x1c,
0xe7, 0xf9, 0xc6, 0x60, 0x3b, 0xd4, 0x42, 0xf9, 0x2e, 0xff, 0x60, 0x82, 0xf2, 0xde, 0x6f, 0x0d, 0xd2, 0xa9, 0x94, 0x6b, 0x8b, 0xdd, 0xd8, 0x08, 0x1d, 0x7a, 0xf9, 0xde, 0x46, 0x1d, 0x42, 0xd8,
0xe4, 0xd3, 0xc6, 0x79, 0x00, 0x0b, 0x32, 0x42, 0xe4, 0x0c, 0x65, 0x20, 0xbb, 0x4a, 0xfe, 0xbd, 0x58, 0x90, 0xd3, 0xca, 0x32, 0x82, 0x03, 0x95, 0x20, 0x71, 0x86, 0x2a, 0x92, 0x5f, 0x15, 0xff,
0xb6, 0xd2, 0x6d, 0x49, 0x15, 0xd3, 0x6d, 0x58, 0x96, 0x3c, 0x38, 0xf2, 0xd0, 0x17, 0x11, 0x5b, 0xd6, 0x38, 0xe5, 0x37, 0x24, 0x8b, 0xed, 0xd7, 0xac, 0xc8, 0x26, 0x1c, 0x79, 0xe8, 0x8b, 0x48,
0x03, 0x56, 0x47, 0x61, 0xfc, 0x81, 0xf6, 0x48, 0xf1, 0x27, 0xb5, 0x88, 0xbb, 0xcd, 0x45, 0x52, 0x9d, 0x05, 0x67, 0x92, 0xb0, 0xe1, 0x40, 0x7b, 0xa4, 0xf8, 0x93, 0x5a, 0xa4, 0xed, 0xfa, 0x2c,
0xbf, 0x6c, 0x68, 0xc2, 0x55, 0x7c, 0x1c, 0x48, 0xd9, 0x29, 0xa3, 0xb9, 0x4c, 0x3b, 0x14, 0xc6, 0xcb, 0x5f, 0xb5, 0x34, 0xe1, 0x2a, 0x3d, 0x8c, 0x24, 0xed, 0x14, 0x52, 0xa9, 0xf2, 0x0e, 0xa5,
0xf5, 0x03, 0xb2, 0x59, 0xae, 0xc6, 0x58, 0x38, 0xe6, 0xf9, 0x28, 0x6a, 0x80, 0xc7, 0xc1, 0x04, 0xf5, 0xc3, 0x88, 0x6c, 0x96, 0xab, 0xb5, 0x0e, 0x8e, 0x81, 0x3e, 0x8a, 0x1a, 0xe1, 0x61, 0xb4,
0xd0, 0x33, 0xba, 0x1e, 0x10, 0x29, 0x92, 0x33, 0x7c, 0x96, 0x91, 0xc8, 0x66, 0xa3, 0x4e, 0x8a, 0x11, 0xcc, 0x8c, 0x6e, 0x46, 0x44, 0xca, 0xe4, 0x0c, 0x9f, 0x65, 0x24, 0xb2, 0xd9, 0xa9, 0x67,
0x7c, 0x2e, 0xff, 0x03, 0x3a, 0x1f, 0x26, 0xed, 0x95, 0x35, 0x6a, 0x5b, 0xf2, 0xf5, 0x24, 0xfd, 0xa5, 0x9c, 0xcb, 0xff, 0x80, 0xce, 0x87, 0x49, 0x7b, 0xed, 0xac, 0xde, 0x54, 0xfc, 0x7c, 0x92,
0x9a, 0x92, 0xfe, 0xab, 0x29, 0x79, 0x54, 0x15, 0xda, 0xec, 0xb2, 0x64, 0xe5, 0x94, 0x53, 0xa2, 0xfe, 0x9c, 0xa2, 0xfe, 0xab, 0x29, 0x7b, 0x54, 0x97, 0xc6, 0x6e, 0x8b, 0x6c, 0xe5, 0x14, 0x54,
0xa9, 0x12, 0x3b, 0x85, 0x4f, 0x08, 0x41, 0xe0, 0x44, 0x7e, 0x9b, 0x96, 0x65, 0xda, 0x33, 0xe7, 0xa2, 0xa9, 0x33, 0x3b, 0xa5, 0x4f, 0x08, 0x41, 0xe0, 0x4c, 0x7e, 0x93, 0x97, 0x65, 0x26, 0x30,
0x91, 0x29, 0xeb, 0xe9, 0xe0, 0x03, 0xcd, 0xda, 0x04, 0x88, 0x6d, 0xf2, 0xa3, 0x97, 0x1b, 0x60, 0x1f, 0x90, 0x69, 0x17, 0xe8, 0x10, 0x22, 0xcd, 0xda, 0x46, 0x48, 0x5d, 0xf6, 0x63, 0x50, 0x6b,
0xe7, 0xcb, 0x22, 0x27, 0xbe, 0x69, 0xdd, 0x29, 0x74, 0x53, 0x02, 0xa7, 0x5f, 0xfb, 0x27, 0x3b, 0x60, 0xa7, 0xcb, 0x52, 0x12, 0xdf, 0xb4, 0xee, 0x94, 0xba, 0x29, 0x82, 0xd3, 0xbf, 0xfd, 0x13,
0x01, 0xc5, 0x54, 0xf0, 0x03, 0x00, 0x00 0x46, 0x22, 0xf9, 0xe1, 0xf1, 0x03, 0x00, 0x00
}; };
// Autogenerated from wled00/data/welcome.htm, do not edit!! // Autogenerated from wled00/data/welcome.htm, do not edit!!
const uint16_t PAGE_welcome_length = 1528; const uint16_t PAGE_welcome_length = 1529;
const uint8_t PAGE_welcome[] PROGMEM = { const uint8_t PAGE_welcome[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x56, 0x5b, 0x93, 0xaa, 0x3a, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x56, 0x5b, 0x93, 0xaa, 0x3a,
0x16, 0x7e, 0xef, 0x5f, 0xc1, 0x76, 0xea, 0xd4, 0x79, 0x70, 0x77, 0x73, 0x13, 0x51, 0xdb, 0xee, 0x16, 0x7e, 0xef, 0x5f, 0xc1, 0x76, 0xea, 0xd4, 0x79, 0x70, 0x77, 0x73, 0x13, 0x51, 0xdb, 0xee,
@ -177,18 +177,18 @@ const uint8_t PAGE_welcome[] PROGMEM = {
0x72, 0xff, 0x3e, 0x19, 0x38, 0x4d, 0xa1, 0xcb, 0x04, 0x2e, 0x8e, 0x42, 0x82, 0xbd, 0x3c, 0xbe, 0x72, 0xff, 0x3e, 0x19, 0x38, 0x4d, 0xa1, 0xcb, 0x04, 0x2e, 0x8e, 0x42, 0x82, 0xbd, 0x3c, 0xbe,
0x07, 0xe3, 0x7e, 0x89, 0x10, 0x63, 0x17, 0xc4, 0xc2, 0x16, 0x0d, 0x90, 0x10, 0x42, 0xc2, 0x05, 0x07, 0xe3, 0x7e, 0x89, 0x10, 0x63, 0x17, 0xc4, 0xc2, 0x16, 0x0d, 0x90, 0x10, 0x42, 0xc2, 0x05,
0xf1, 0x6e, 0x73, 0xd7, 0x26, 0xe1, 0x09, 0xa7, 0x2e, 0x8f, 0x71, 0x7c, 0xfb, 0xf3, 0x8c, 0x52, 0xf1, 0x6e, 0x73, 0xd7, 0x26, 0xe1, 0x09, 0xa7, 0x2e, 0x8f, 0x71, 0x7c, 0xfb, 0xf3, 0x8c, 0x52,
0x0f, 0x9f, 0x5f, 0xca, 0xc5, 0x65, 0xc0, 0x97, 0x90, 0x40, 0xff, 0xad, 0x22, 0x72, 0xe1, 0x66, 0x0f, 0x9f, 0x5f, 0xca, 0xc5, 0x65, 0xc0, 0x97, 0x90, 0x40, 0xff, 0xad, 0xf2, 0x22, 0x72, 0xe5,
0x9c, 0x0e, 0x15, 0xcf, 0xc8, 0x47, 0x95, 0x3f, 0xdf, 0xef, 0x6e, 0xbe, 0x41, 0xce, 0xe0, 0xee, 0x66, 0x9c, 0x0f, 0x15, 0xcf, 0xc8, 0x47, 0x95, 0x3f, 0xdf, 0xef, 0x7e, 0xbe, 0x41, 0x4e, 0xe1,
0xe6, 0x41, 0x03, 0xbd, 0x3f, 0x8d, 0x73, 0xca, 0x29, 0x90, 0x6b, 0x99, 0x00, 0x0b, 0x11, 0x15, 0xee, 0xe7, 0xc1, 0x03, 0xbd, 0x3f, 0x8d, 0x73, 0xca, 0x39, 0x90, 0x6b, 0x99, 0x01, 0x0b, 0x11,
0xb8, 0xf6, 0xf1, 0xa4, 0x84, 0xce, 0xbc, 0x24, 0x05, 0xff, 0xdd, 0x16, 0xd1, 0xfb, 0x3f, 0x0b, 0x15, 0xb8, 0xf8, 0xf1, 0xac, 0x84, 0xce, 0xbc, 0x64, 0x05, 0xff, 0xdd, 0x16, 0xd1, 0xfb, 0x3f,
0xcf, 0x55, 0x09, 0x12, 0xca, 0x03, 0xaf, 0xf1, 0x3d, 0xb7, 0x52, 0xee, 0x09, 0x8e, 0xe9, 0x8f, 0x8c, 0xcf, 0x75, 0x09, 0x12, 0xca, 0x23, 0xaf, 0xf1, 0x3d, 0xbb, 0x52, 0xf0, 0x09, 0x8e, 0xe9,
0xbf, 0xc6, 0x16, 0xf9, 0xb6, 0xf3, 0xcd, 0x17, 0x1f, 0x65, 0x5d, 0xbc, 0xff, 0x07, 0xf9, 0x2f, 0x8f, 0xbf, 0x06, 0x17, 0xf9, 0xc6, 0xf3, 0xed, 0x17, 0x1f, 0x85, 0x5d, 0xbc, 0xff, 0x0b, 0xf9,
0xec, 0xfa, 0x82, 0xd2, 0x99, 0x08, 0x00, 0x00 0x2f, 0x52, 0xe7, 0x8f, 0x07, 0x9b, 0x08, 0x00, 0x00
}; };
// Autogenerated from wled00/data/liveview.htm, do not edit!! // Autogenerated from wled00/data/liveview.htm, do not edit!!
const uint16_t PAGE_liveview_length = 547; const uint16_t PAGE_liveview_length = 548;
const uint8_t PAGE_liveview[] PROGMEM = { const uint8_t PAGE_liveview[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x53, 0x4d, 0x6f, 0xdb, 0x30, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x53, 0x4d, 0x6f, 0xdb, 0x30,
0x0c, 0xbd, 0xe7, 0x57, 0x78, 0x2a, 0x5a, 0x48, 0x88, 0x63, 0x3b, 0xc5, 0xba, 0x8f, 0xf8, 0xe3, 0x0c, 0xbd, 0xe7, 0x57, 0x78, 0x2a, 0x5a, 0x48, 0x88, 0x63, 0x3b, 0xc5, 0xba, 0x8f, 0xf8, 0xe3,
@ -212,128 +212,133 @@ const uint8_t PAGE_liveview[] PROGMEM = {
0xbc, 0x11, 0x03, 0x6f, 0x74, 0x02, 0xec, 0xd5, 0x86, 0x4a, 0x2b, 0x7c, 0x1b, 0x6a, 0x4f, 0x1a, 0xbc, 0x11, 0x03, 0x6f, 0x74, 0x02, 0xec, 0xd5, 0x86, 0x4a, 0x2b, 0x7c, 0x1b, 0x6a, 0x4f, 0x1a,
0x25, 0x25, 0x18, 0xe6, 0x00, 0xbd, 0x33, 0x91, 0xd0, 0xc0, 0xdd, 0x83, 0x6a, 0x21, 0xdc, 0xa6, 0x25, 0x25, 0x18, 0xe6, 0x00, 0xbd, 0x33, 0x91, 0xd0, 0xc0, 0xdd, 0x83, 0x6a, 0x21, 0xdc, 0xa6,
0x23, 0x07, 0x8b, 0x7b, 0xab, 0xe4, 0x64, 0x97, 0x41, 0xd5, 0x93, 0x73, 0x22, 0x8b, 0x2f, 0xaf, 0x23, 0x07, 0x8b, 0x7b, 0xab, 0xe4, 0x64, 0x97, 0x41, 0xd5, 0x93, 0x73, 0x22, 0x8b, 0x2f, 0xaf,
0x32, 0xc6, 0xf2, 0x0d, 0xa0, 0x68, 0x28, 0x49, 0x7f, 0x77, 0xd6, 0xa4, 0x3a, 0xc8, 0x46, 0x58, 0x32, 0xc6, 0xf2, 0x0d, 0xa0, 0x68, 0x28, 0x49, 0xd2, 0xdf, 0x9d, 0x35, 0xa9, 0x0e, 0xba, 0x11,
0x12, 0x94, 0x36, 0x14, 0xcb, 0x8a, 0x62, 0x62, 0x1f, 0x9f, 0x9f, 0xe9, 0xbf, 0xa8, 0xff, 0xc3, 0x96, 0x04, 0xa9, 0x0d, 0xc5, 0xb2, 0xa2, 0x98, 0xd8, 0xc7, 0xe7, 0x67, 0xfa, 0x2f, 0xee, 0xff,
0x1a, 0x68, 0x63, 0x4c, 0x06, 0x42, 0xca, 0xd8, 0x2b, 0xdb, 0x7e, 0x28, 0x2e, 0xf4, 0x51, 0x2b, 0xd0, 0x06, 0xde, 0x18, 0x93, 0x81, 0x90, 0x32, 0xf6, 0xca, 0xb6, 0x1f, 0xaa, 0x0b, 0x8d, 0xd4,
0x13, 0xe8, 0x16, 0xb5, 0xe3, 0x52, 0x85, 0x32, 0xe8, 0xe7, 0x4c, 0x42, 0x1d, 0x93, 0xd8, 0x97, 0xca, 0x04, 0xba, 0x45, 0xed, 0xb8, 0x54, 0xa1, 0x0e, 0xfa, 0x39, 0x93, 0x50, 0xc7, 0x24, 0xf6,
0x98, 0x68, 0x90, 0x5d, 0x58, 0x4c, 0x8d, 0x4d, 0xbe, 0xb1, 0x8e, 0xaa, 0x32, 0xcb, 0x55, 0xe1, 0x25, 0x26, 0x1a, 0x64, 0x17, 0x16, 0x53, 0x63, 0x93, 0x6f, 0xac, 0xa3, 0xaa, 0xcc, 0x72, 0x55,
0x73, 0x35, 0x9f, 0xb3, 0xf1, 0xbe, 0x3d, 0x82, 0x7e, 0xaa, 0x5f, 0xb9, 0x3d, 0x22, 0xab, 0x0f, 0xf8, 0x5c, 0xcd, 0xe7, 0x6c, 0xbc, 0x6f, 0x8f, 0xa0, 0x9f, 0xea, 0x57, 0x6e, 0x8f, 0xc8, 0xea,
0x17, 0x17, 0xd4, 0x96, 0x36, 0xe9, 0xfc, 0xba, 0x43, 0xa7, 0x4c, 0x4d, 0x2f, 0x43, 0x0a, 0x30, 0xc3, 0xc5, 0x05, 0xb5, 0xa5, 0x4d, 0x3a, 0xbf, 0xee, 0xd0, 0x29, 0x53, 0xd3, 0xcb, 0x90, 0x02,
0x0f, 0xd3, 0x41, 0xe6, 0x36, 0x0e, 0x04, 0x8b, 0x65, 0x80, 0x0c, 0x07, 0x31, 0x61, 0x87, 0xe1, 0xcc, 0xc3, 0x78, 0x90, 0xb9, 0x8d, 0x03, 0xc1, 0x62, 0x19, 0x20, 0xc3, 0x41, 0x4c, 0xd8, 0x61,
0xcf, 0x48, 0xfc, 0xa2, 0x63, 0x0d, 0x78, 0xa3, 0x61, 0x30, 0xbf, 0xec, 0x6e, 0x25, 0x9d, 0x9a, 0xf8, 0x33, 0x12, 0xbf, 0x08, 0x59, 0x03, 0xde, 0x68, 0x18, 0xcc, 0x2f, 0xbb, 0x5b, 0x49, 0xa7,
0xc3, 0x92, 0xb1, 0x7f, 0xc9, 0xeb, 0x64, 0x94, 0x10, 0xbf, 0x45, 0x88, 0xf7, 0x19, 0x3b, 0xb0, 0xee, 0xb0, 0x64, 0x6c, 0x60, 0xf2, 0x3a, 0x1a, 0x25, 0xc4, 0x6f, 0x11, 0xe2, 0x7d, 0xc6, 0x0e,
0x44, 0xf0, 0x41, 0x5e, 0x7a, 0x6a, 0x24, 0x45, 0xb6, 0x7f, 0xb3, 0x9a, 0x07, 0xc6, 0xc6, 0x79, 0x2c, 0x11, 0x7c, 0xd0, 0x97, 0x9e, 0x3a, 0x49, 0x91, 0xed, 0xdf, 0xac, 0xe6, 0x81, 0xb1, 0x71,
0x9a, 0x86, 0xa5, 0x48, 0xa7, 0x51, 0x4a, 0xc7, 0x17, 0xf9, 0x07, 0xe3, 0xed, 0x60, 0x85, 0xa7, 0xa0, 0xa6, 0x69, 0x29, 0xd2, 0x69, 0x96, 0xd2, 0xf1, 0x49, 0xfe, 0x01, 0x0f, 0xfb, 0xef, 0x75,
0x03, 0x00, 0x00 0xa8, 0x03, 0x00, 0x00
}; };
// Autogenerated from wled00/data/liveviewws.htm, do not edit!! // Autogenerated from wled00/data/liveviewws.htm, do not edit!!
const uint16_t PAGE_liveviewws_length = 711; const uint16_t PAGE_liveviewws_length = 747;
const uint8_t PAGE_liveviewws[] PROGMEM = { const uint8_t PAGE_liveviewws[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x6d, 0x54, 0x5d, 0x8f, 0x9b, 0x3a, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x6d, 0x54, 0x6d, 0x8f, 0x9b, 0x38,
0x10, 0x7d, 0xcf, 0xaf, 0xa0, 0xde, 0xdb, 0x2d, 0x56, 0x08, 0x24, 0xdb, 0xdb, 0x2f, 0xc0, 0x59, 0x10, 0xfe, 0x9e, 0x5f, 0x41, 0xbd, 0xd7, 0x2d, 0x28, 0x84, 0x90, 0x6d, 0xaf, 0x2f, 0x01, 0xa7,
0xb5, 0xb7, 0x79, 0xa8, 0xb4, 0x6a, 0x57, 0xda, 0x5e, 0xad, 0xaa, 0x55, 0xa4, 0x1a, 0x98, 0x80, 0xba, 0x5e, 0xf3, 0xe1, 0xa4, 0x55, 0xbb, 0xd2, 0xb6, 0x5a, 0x9d, 0x56, 0x91, 0x6a, 0x60, 0x02,
0xef, 0x82, 0x1d, 0xd9, 0x43, 0x50, 0x84, 0xf8, 0xef, 0x77, 0x20, 0xdb, 0xac, 0x2a, 0x95, 0x07, 0xbe, 0x05, 0x1b, 0xd9, 0x03, 0x28, 0x42, 0xfc, 0xf7, 0x1b, 0x93, 0x74, 0xab, 0x93, 0x8e, 0x0f,
0xdb, 0xc3, 0x9c, 0x99, 0x33, 0x33, 0x3e, 0x90, 0xbe, 0xf8, 0xfc, 0xed, 0x9f, 0xef, 0x3f, 0x6e, 0x60, 0x7b, 0xe6, 0x99, 0x97, 0x67, 0x1e, 0x93, 0xbe, 0xf8, 0xfc, 0xf5, 0xcf, 0x6f, 0x7f, 0xdf,
0x37, 0x5e, 0x85, 0x4d, 0xbd, 0x4e, 0x9f, 0x56, 0x90, 0xc5, 0x3a, 0x6d, 0x00, 0xa5, 0xa7, 0x65, 0xed, 0xbd, 0x0a, 0x9b, 0x7a, 0x97, 0x5e, 0xde, 0x20, 0x8a, 0x5d, 0xda, 0x00, 0x0a, 0x4f, 0x89,
0x03, 0x82, 0x1d, 0x14, 0x74, 0x7b, 0x63, 0x91, 0x79, 0xb3, 0xdc, 0x68, 0x04, 0x8d, 0x82, 0x75, 0x06, 0x38, 0xeb, 0x25, 0x0c, 0xad, 0x36, 0xc8, 0xbc, 0x45, 0xae, 0x15, 0x82, 0x42, 0xce, 0x06,
0xaa, 0xc0, 0x4a, 0x14, 0x70, 0x50, 0x39, 0x2c, 0x26, 0x23, 0x50, 0x5a, 0xa1, 0x92, 0xf5, 0xc2, 0x59, 0x60, 0xc5, 0x0b, 0xe8, 0x65, 0x0e, 0xab, 0x79, 0x13, 0x4a, 0x25, 0x51, 0x8a, 0x7a, 0x65,
0xe5, 0xb2, 0x06, 0xb1, 0x0a, 0x1a, 0x7a, 0xd1, 0xb4, 0xcd, 0x2f, 0x9b, 0x3d, 0xe5, 0x9c, 0xe5, 0x73, 0x51, 0x03, 0xdf, 0x84, 0x0d, 0x1d, 0x34, 0x5d, 0xf3, 0x73, 0xcf, 0x2e, 0x31, 0x17, 0x79,
0x95, 0xb4, 0x0e, 0x28, 0x47, 0x8b, 0xbb, 0xc5, 0x7b, 0xf6, 0x1b, 0x15, 0x56, 0xd0, 0xc0, 0x22, 0x25, 0x8c, 0x05, 0x8a, 0xd1, 0xe1, 0x71, 0xf5, 0x9e, 0xfd, 0x27, 0x15, 0x56, 0xd0, 0xc0, 0x2a,
0x37, 0xb5, 0xb1, 0xcc, 0x3b, 0x93, 0x5d, 0x5c, 0x4d, 0x0f, 0x41, 0x51, 0x61, 0x0d, 0xeb, 0xd9, 0xd7, 0xb5, 0x36, 0xcc, 0x7b, 0x4e, 0x76, 0x75, 0x33, 0x3f, 0xe4, 0x8a, 0x12, 0x6b, 0xd8, 0x2d,
0xfd, 0xcd, 0xe6, 0xb3, 0x77, 0xa3, 0x0e, 0xe0, 0xdd, 0x5a, 0x18, 0xcb, 0x4b, 0xa3, 0x93, 0x27, 0x1e, 0x6e, 0xf7, 0x9f, 0xbd, 0x5b, 0xd9, 0x83, 0x77, 0x67, 0xc0, 0x95, 0x97, 0xae, 0xcf, 0x96,
0x75, 0x78, 0x1c, 0x01, 0x99, 0x29, 0x8e, 0x7d, 0x23, 0x6d, 0xa9, 0x74, 0xbc, 0x1c, 0x2e, 0x72, 0xd4, 0xe2, 0xc9, 0x39, 0x64, 0xba, 0x38, 0x8d, 0x8d, 0x30, 0xa5, 0x54, 0xdb, 0x78, 0xba, 0xca,
0xa9, 0x0f, 0x7d, 0x26, 0xf3, 0xc7, 0xd2, 0x9a, 0x56, 0x17, 0xf1, 0xc5, 0x72, 0xb9, 0x4c, 0x76, 0x85, 0xea, 0xc7, 0x4c, 0xe4, 0x4f, 0xa5, 0xd1, 0x9d, 0x2a, 0xb6, 0x57, 0x71, 0x1c, 0x27, 0x47,
0xaa, 0x46, 0xb0, 0x71, 0x66, 0x55, 0x59, 0xa1, 0x06, 0xe7, 0xfc, 0xd5, 0xbb, 0x37, 0x2f, 0x79, 0x59, 0x23, 0x98, 0x6d, 0x66, 0x64, 0x59, 0xa1, 0x02, 0x6b, 0xfd, 0xcd, 0xbb, 0xdf, 0x5f, 0x06,
0x32, 0x75, 0x13, 0xaf, 0x96, 0xcb, 0x97, 0x49, 0x05, 0xa3, 0xef, 0x74, 0xde, 0x1b, 0x47, 0xfd, 0xc9, 0xdc, 0xcd, 0x76, 0x13, 0xc7, 0x2f, 0x93, 0x0a, 0x9c, 0xed, 0xbc, 0x6e, 0xb5, 0xa5, 0xfe,
0x19, 0x1d, 0xcb, 0xcc, 0x99, 0xba, 0x45, 0x18, 0x66, 0x69, 0x74, 0xa2, 0x4b, 0xa3, 0xd3, 0xcc, 0xb4, 0xda, 0x8a, 0xcc, 0xea, 0xba, 0x43, 0x98, 0x16, 0xe9, 0xfa, 0x9c, 0x2e, 0x5d, 0x9f, 0x39,
0x46, 0xd6, 0x75, 0x5a, 0xa8, 0x83, 0xa7, 0x0a, 0xc1, 0x46, 0x52, 0x2a, 0x39, 0x22, 0x9b, 0xea, 0x73, 0x59, 0x77, 0x69, 0x21, 0x7b, 0x4f, 0x16, 0x9c, 0xb9, 0xa4, 0x54, 0xf2, 0x9a, 0xf6, 0x54,
0xca, 0xad, 0xda, 0xe3, 0x7a, 0x76, 0x90, 0xd6, 0xeb, 0x5c, 0x82, 0xf6, 0xd8, 0x77, 0x4e, 0xa0, 0x57, 0x6e, 0x64, 0x8b, 0xbb, 0x45, 0x2f, 0x8c, 0x37, 0xd8, 0x04, 0xcd, 0x69, 0x1c, 0x2c, 0x47,
0xd9, 0x87, 0x9d, 0xd2, 0x85, 0xe9, 0xc2, 0xce, 0x0d, 0xb9, 0xc4, 0xbc, 0xf2, 0x81, 0xf7, 0x43, 0xdd, 0x46, 0x83, 0x54, 0x85, 0x1e, 0xa2, 0xc1, 0x4e, 0xb9, 0xc0, 0xbc, 0xf2, 0x21, 0x18, 0x27,
0xe7, 0x2e, 0x2f, 0x3b, 0x17, 0x5a, 0xca, 0x7a, 0xbc, 0x43, 0x89, 0x20, 0x84, 0xb8, 0x87, 0xec, 0x79, 0xf4, 0x07, 0x7b, 0x7d, 0x3d, 0xd8, 0xc8, 0x50, 0xe0, 0xd3, 0x3d, 0x0a, 0x04, 0xce, 0xf9,
0xce, 0xe4, 0x8f, 0x80, 0xe1, 0xb7, 0xdb, 0xcd, 0xd7, 0x6b, 0x72, 0x3b, 0xd0, 0x85, 0xcf, 0xfa, 0x03, 0x64, 0xf7, 0x3a, 0x7f, 0x02, 0x8c, 0xbe, 0xde, 0xed, 0xbf, 0x04, 0x64, 0xb6, 0xa0, 0x0a,
0x57, 0xf5, 0xe1, 0x55, 0x8c, 0xb6, 0x85, 0x81, 0xf1, 0xd8, 0xa7, 0xa1, 0x51, 0x79, 0x10, 0x2a, 0x9f, 0x8d, 0xaf, 0xea, 0xfe, 0xd5, 0x16, 0x4d, 0x07, 0x13, 0x0b, 0x12, 0xa8, 0x2d, 0x8c, 0x35,
0xbd, 0x33, 0x3e, 0xbb, 0x05, 0x78, 0xf4, 0xee, 0xef, 0x3c, 0xb3, 0x07, 0xad, 0x74, 0xc9, 0x78, 0xa0, 0x07, 0xfc, 0x12, 0xbb, 0xd6, 0x14, 0x9a, 0x4a, 0x0f, 0x91, 0x43, 0xd4, 0x0a, 0xac, 0x1c,
0xe0, 0x13, 0xad, 0x86, 0xce, 0x3b, 0xa7, 0xf3, 0x7d, 0x56, 0x21, 0xee, 0x5d, 0xcc, 0x84, 0x78, 0xd9, 0xa1, 0xe1, 0x18, 0xd9, 0x9a, 0xe6, 0xe7, 0x6f, 0x42, 0x8c, 0x28, 0x90, 0x7d, 0x90, 0x58,
0xaa, 0xa5, 0x36, 0x54, 0x0a, 0xb5, 0x1a, 0xee, 0xad, 0x41, 0x43, 0xb7, 0x71, 0xcd, 0x3a, 0xe7, 0xf9, 0x6c, 0xcd, 0x82, 0x8f, 0xab, 0xcd, 0xb6, 0xd7, 0xb2, 0xf0, 0xe2, 0x20, 0xb2, 0x6d, 0x2d,
0x58, 0x4c, 0x2b, 0xe3, 0x73, 0x16, 0x47, 0x11, 0x9b, 0x17, 0x26, 0x6f, 0x1b, 0xba, 0x9d, 0x67, 0x71, 0x3e, 0x0d, 0x15, 0xc1, 0x35, 0xd1, 0x25, 0x15, 0xd5, 0xd5, 0xd6, 0x82, 0xa0, 0xac, 0x42,
0x70, 0x65, 0x1c, 0xce, 0x59, 0x34, 0x62, 0x78, 0x68, 0xf4, 0x48, 0x29, 0x76, 0xad, 0xce, 0x47, 0x6c, 0x59, 0xc8, 0x06, 0x4b, 0x99, 0x4d, 0x54, 0x83, 0x2a, 0xb1, 0xda, 0x6d, 0xae, 0xaf, 0x7d,
0xa7, 0xcf, 0xfb, 0x3f, 0x17, 0x3c, 0xf0, 0x80, 0x1c, 0x99, 0xd2, 0xd2, 0x1e, 0xbf, 0x1f, 0xf7, 0xb5, 0xe4, 0x04, 0x5a, 0x9a, 0xc7, 0xf8, 0x10, 0x84, 0xd4, 0x0e, 0x57, 0x30, 0x78, 0xcf, 0x1d,
0x24, 0x01, 0x69, 0xad, 0x3c, 0x66, 0xed, 0x6e, 0x07, 0x96, 0x8d, 0x3e, 0x59, 0x14, 0x9b, 0x03, 0x90, 0x99, 0xad, 0x1d, 0x28, 0x88, 0xb4, 0xd2, 0x2d, 0x28, 0x7e, 0xec, 0x54, 0xee, 0x8a, 0xf4,
0x11, 0xdd, 0x28, 0x47, 0x6a, 0x00, 0xeb, 0xb3, 0x86, 0xee, 0x4a, 0x96, 0xc0, 0x02, 0x10, 0xeb, 0x83, 0xf1, 0xff, 0x1b, 0x9b, 0x26, 0x3a, 0xcf, 0xa4, 0x12, 0xe6, 0xf4, 0xed, 0xd4, 0x92, 0x58,
0x7e, 0x9c, 0xa4, 0xda, 0xf9, 0xec, 0xc1, 0x64, 0xff, 0x41, 0x8e, 0xde, 0xc7, 0x31, 0xfc, 0xd3, 0x84, 0x31, 0xe2, 0x94, 0x75, 0xc7, 0x23, 0x18, 0x16, 0x92, 0x4d, 0x14, 0xc5, 0xbe, 0x27, 0xc1,
0x14, 0xbe, 0xa5, 0x8e, 0x68, 0xc4, 0x77, 0x68, 0xa9, 0xf5, 0x90, 0x54, 0x58, 0xfb, 0x10, 0x16, 0xdc, 0x4a, 0x4b, 0xba, 0x01, 0xe3, 0xb3, 0x86, 0xa6, 0x2a, 0x4a, 0x60, 0x21, 0xf0, 0xdd, 0xe8,
0x12, 0x25, 0xe7, 0x7d, 0x0d, 0xe8, 0xc1, 0x34, 0x88, 0x7f, 0x95, 0xc6, 0xf7, 0x53, 0x94, 0x0f, 0x38, 0x27, 0x62, 0xd9, 0xa3, 0xce, 0xfe, 0x81, 0x1c, 0xbd, 0x3f, 0x1c, 0xfc, 0xd3, 0x0c, 0x3f,
0x23, 0xcf, 0x09, 0x91, 0x50, 0xce, 0x77, 0x6f, 0x5f, 0x08, 0x78, 0x58, 0x6e, 0xb9, 0x05, 0x6c, 0x30, 0x62, 0x17, 0xf5, 0x3d, 0x1a, 0xa9, 0xca, 0x88, 0xf4, 0x5a, 0xfb, 0x10, 0x15, 0x02, 0x45,
0xad, 0x4e, 0xc6, 0x18, 0x52, 0x63, 0xad, 0x34, 0x48, 0xbb, 0x28, 0xad, 0x2c, 0x14, 0xe1, 0xfd, 0x10, 0x5c, 0x98, 0x74, 0xc5, 0x7f, 0x97, 0x0a, 0xdf, 0xcf, 0x28, 0x1f, 0x5c, 0x9e, 0xb3, 0x47,
0x0f, 0xcb, 0x02, 0xca, 0x80, 0x05, 0x5a, 0x40, 0x58, 0x83, 0x2e, 0xe9, 0x53, 0xb0, 0xe2, 0x4a, 0x42, 0x31, 0xdf, 0xbd, 0x7d, 0xc1, 0xc1, 0x35, 0x6a, 0x00, 0x3b, 0xa3, 0x12, 0x87, 0x21, 0xdd,
0x50, 0xe8, 0x6a, 0x7b, 0xfd, 0x77, 0x7c, 0x95, 0xec, 0x8c, 0xf5, 0x95, 0xb0, 0x89, 0x4a, 0x75, 0xd6, 0x52, 0x81, 0x30, 0xab, 0xd2, 0x88, 0x42, 0x92, 0xbf, 0xff, 0x21, 0x2e, 0xa0, 0x0c, 0x19,
0xa2, 0xe6, 0xe2, 0x35, 0xc7, 0xb9, 0xf8, 0x69, 0xcb, 0xcc, 0xff, 0xab, 0x87, 0x07, 0xb5, 0x1d, 0x31, 0x0f, 0x17, 0xa6, 0x88, 0xd2, 0x1b, 0x4e, 0xd0, 0xcd, 0xe1, 0xe3, 0x9b, 0xed, 0x4d, 0x72,
0x82, 0x69, 0x9f, 0xaf, 0xce, 0xa7, 0xab, 0xed, 0xc0, 0x7f, 0x06, 0x04, 0x5f, 0xbc, 0xbe, 0xbc, 0xd4, 0xc6, 0x97, 0x5c, 0x25, 0x32, 0x35, 0x89, 0x5c, 0xf2, 0xd7, 0x01, 0x2e, 0xf9, 0x0f, 0x53,
0xf4, 0x09, 0xcd, 0x02, 0xc6, 0x93, 0x71, 0xe7, 0x2c, 0x38, 0x0f, 0xbe, 0x04, 0xdc, 0xd4, 0x30, 0x66, 0xfe, 0x6f, 0x23, 0x3c, 0xca, 0xc3, 0x14, 0xce, 0xdf, 0xe5, 0xe6, 0x79, 0x75, 0x73, 0x98,
0x1e, 0x3f, 0x1d, 0xbf, 0xd0, 0x5c, 0x27, 0xbd, 0xf1, 0x70, 0x92, 0x64, 0xf8, 0x2c, 0x76, 0x81, 0x82, 0x1f, 0x21, 0xb9, 0xaf, 0x5e, 0x13, 0xe5, 0xe4, 0x4d, 0xa3, 0x08, 0x12, 0xf7, 0x0d, 0x58,
0xc3, 0xb3, 0xb2, 0x7e, 0xa9, 0x03, 0xac, 0xa5, 0xa2, 0xce, 0xf2, 0x98, 0xcc, 0x98, 0x06, 0xca, 0x58, 0xe8, 0xbc, 0x6b, 0x5c, 0x9d, 0x25, 0xe0, 0xbe, 0x06, 0xb7, 0xfc, 0x74, 0xfa, 0x8b, 0x68,
0x87, 0x81, 0x8f, 0xb2, 0x3e, 0xa9, 0x35, 0x8d, 0x4e, 0x8a, 0x8e, 0xa6, 0x1f, 0xc3, 0xff, 0x16, 0x9d, 0x95, 0x49, 0x93, 0x75, 0xe2, 0x8d, 0x7e, 0x5d, 0x0b, 0x8e, 0xd3, 0x2f, 0x0d, 0xd2, 0xe5,
0x7e, 0x9e, 0x8e, 0x2e, 0x04, 0x00, 0x00 0x23, 0x99, 0x43, 0x04, 0xc6, 0x50, 0x51, 0xec, 0x0e, 0xe0, 0xc9, 0x7b, 0xb8, 0xf7, 0xe6, 0xed,
0x96, 0x08, 0xa5, 0x71, 0x04, 0xee, 0x02, 0x9c, 0x75, 0x9d, 0xae, 0xcf, 0xda, 0x5f, 0xcf, 0xbf,
0x90, 0x7f, 0x01, 0x26, 0x24, 0x5a, 0x77, 0x58, 0x04, 0x00, 0x00
}; };
// Autogenerated from wled00/data/liveviewws2D.htm, do not edit!! // Autogenerated from wled00/data/liveviewws2D.htm, do not edit!!
const uint16_t PAGE_liveviewws2D_length = 818; const uint16_t PAGE_liveviewws2D_length = 870;
const uint8_t PAGE_liveviewws2D[] PROGMEM = { const uint8_t PAGE_liveviewws2D[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x6d, 0x54, 0x6d, 0x6f, 0xdb, 0x36, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x6d, 0x54, 0x6d, 0x6f, 0xdb, 0x36,
0x10, 0xfe, 0xee, 0x5f, 0xa1, 0x70, 0x43, 0x2b, 0xda, 0xb2, 0xe4, 0xb8, 0xed, 0x96, 0xd9, 0xa2, 0x10, 0xfe, 0xee, 0x5f, 0xa1, 0x70, 0x43, 0x2a, 0xc6, 0xb2, 0x64, 0xbb, 0xed, 0x96, 0xc5, 0xa2,
0x8b, 0x36, 0x35, 0xb0, 0x02, 0xd9, 0x6a, 0xc0, 0x19, 0x82, 0x21, 0x30, 0x50, 0x5a, 0x3a, 0x5b, 0x87, 0x35, 0x0d, 0xb0, 0x02, 0xd9, 0x6a, 0x20, 0x19, 0x82, 0x21, 0x30, 0x50, 0x5a, 0x3a, 0x5b,
0x5c, 0x25, 0xd2, 0xa0, 0xce, 0x96, 0x35, 0x47, 0xff, 0x7d, 0x47, 0xc9, 0xf1, 0x32, 0x74, 0xfa, 0x5c, 0x25, 0xd2, 0x20, 0xcf, 0x96, 0x35, 0x47, 0xff, 0x7d, 0x47, 0xc9, 0xc9, 0x32, 0x74, 0xfe,
0x40, 0x91, 0xf7, 0xf2, 0xdc, 0xf1, 0xb9, 0x3b, 0xc6, 0x57, 0x9f, 0xbe, 0xdc, 0xde, 0xff, 0xb9, 0x20, 0x93, 0xf7, 0xf2, 0xdc, 0xdb, 0x73, 0x4c, 0xcf, 0x3e, 0x7e, 0xbe, 0xbe, 0xff, 0x73, 0x71,
0x98, 0x7b, 0x19, 0x16, 0xf9, 0x2c, 0x3e, 0xaf, 0x20, 0xd3, 0x59, 0x5c, 0x00, 0x4a, 0x4f, 0xcb, 0x13, 0x14, 0x58, 0x95, 0xf3, 0xf4, 0xf4, 0x05, 0x99, 0xcf, 0xd3, 0x0a, 0x50, 0x06, 0x5a, 0x56,
0x02, 0x04, 0x3b, 0x28, 0xa8, 0x76, 0xc6, 0x22, 0xf3, 0x7a, 0x89, 0xd1, 0x08, 0x1a, 0x05, 0xab, 0x20, 0xd8, 0x5e, 0x41, 0xbd, 0x35, 0x16, 0x59, 0x30, 0xc8, 0x8c, 0x46, 0xd0, 0x28, 0x58, 0xad,
0x54, 0x8a, 0x99, 0x48, 0xe1, 0xa0, 0x12, 0x18, 0xb6, 0x87, 0x40, 0x69, 0x85, 0x4a, 0xe6, 0xc3, 0x72, 0x2c, 0x44, 0x0e, 0x7b, 0x95, 0xc1, 0xa8, 0xbb, 0x44, 0x4a, 0x2b, 0x54, 0xb2, 0x1c, 0xb9,
0x32, 0x91, 0x39, 0x88, 0xeb, 0xa0, 0x20, 0x41, 0xb1, 0x2f, 0x9e, 0xcf, 0xec, 0x8c, 0xd9, 0x4b, 0x4c, 0x96, 0x20, 0x26, 0x51, 0x45, 0x82, 0x6a, 0x57, 0x3d, 0xdf, 0xd9, 0x09, 0x73, 0x90, 0x15,
0x32, 0x69, 0x4b, 0x20, 0x8c, 0x3d, 0x6e, 0x86, 0x37, 0xec, 0x3f, 0xa1, 0x30, 0x83, 0x02, 0x86, 0xd2, 0x3a, 0x20, 0x8c, 0x1d, 0xae, 0x47, 0x97, 0xec, 0x3f, 0xa1, 0xb0, 0x80, 0x0a, 0x46, 0x99,
0x89, 0xc9, 0x8d, 0x65, 0xde, 0x25, 0xd8, 0x0f, 0xe3, 0xf6, 0x23, 0x53, 0x54, 0x98, 0xc3, 0xac, 0x29, 0x8d, 0x65, 0xc1, 0x4b, 0xb0, 0xef, 0xa6, 0xdd, 0x8f, 0x4c, 0x51, 0x61, 0x09, 0xf3, 0xc1,
0xf7, 0x70, 0x37, 0xff, 0xe4, 0xdd, 0xa9, 0x03, 0x78, 0x0b, 0x0b, 0x2e, 0xbd, 0x38, 0xea, 0x34, 0xc3, 0xed, 0xcd, 0xc7, 0xe0, 0x56, 0xed, 0x21, 0x58, 0x58, 0xf0, 0xe9, 0xa5, 0x49, 0xaf, 0x49,
0x71, 0x89, 0x35, 0xfd, 0xd6, 0x26, 0xad, 0x4f, 0x85, 0xb4, 0x5b, 0xa5, 0x27, 0xa3, 0x26, 0x8e, 0x1d, 0x36, 0xf4, 0xb7, 0x32, 0x79, 0x73, 0xac, 0xa4, 0xdd, 0x28, 0x7d, 0x35, 0x6e, 0xd3, 0xa4,
0x3a, 0x69, 0x1c, 0x75, 0x57, 0x73, 0xda, 0x59, 0x9c, 0x48, 0x7d, 0x90, 0xa5, 0xd7, 0x53, 0xa9, 0x97, 0xa6, 0x49, 0x5f, 0x9a, 0xd7, 0xce, 0xd3, 0x4c, 0xea, 0xbd, 0x74, 0xc1, 0x40, 0xe5, 0x82,
0x60, 0x6e, 0x4f, 0xe8, 0x51, 0x27, 0x23, 0x94, 0xc4, 0xaa, 0x1d, 0xce, 0x7a, 0x07, 0x69, 0xbd, 0xf9, 0x33, 0xa1, 0x27, 0xbd, 0x8c, 0x50, 0x32, 0xab, 0xb6, 0x38, 0x1f, 0xec, 0xa5, 0x0d, 0x32,
0x44, 0xa4, 0x26, 0xd9, 0x17, 0x94, 0x48, 0xb8, 0x05, 0x9c, 0xe7, 0xe0, 0xb6, 0x1f, 0xeb, 0xcf, 0x91, 0x9b, 0x6c, 0x57, 0x51, 0x22, 0xf1, 0x06, 0xf0, 0xa6, 0x04, 0x7f, 0xfc, 0xd0, 0x7c, 0xca,
0xa9, 0xdf, 0xb9, 0xf1, 0x20, 0x87, 0xb4, 0x14, 0x8c, 0x05, 0x98, 0x59, 0x83, 0x94, 0x45, 0x2a, 0xc3, 0xde, 0x8d, 0x47, 0x25, 0xe4, 0x4e, 0x30, 0x16, 0x61, 0x61, 0x0d, 0x52, 0x16, 0xb9, 0x38,
0xae, 0xae, 0xa7, 0x9b, 0xbd, 0x4e, 0x50, 0x19, 0xed, 0xd1, 0x55, 0x6f, 0x5b, 0x58, 0x9f, 0x9f, 0x9b, 0xcc, 0xd6, 0x3b, 0x9d, 0xa1, 0x32, 0x3a, 0xa0, 0x52, 0xaf, 0x3b, 0xd8, 0x90, 0x1f, 0xb3,
0x92, 0xb0, 0xe3, 0x2d, 0xfc, 0xe5, 0xa6, 0x5f, 0x29, 0x9d, 0x9a, 0x2a, 0x54, 0x5a, 0x83, 0x7d, 0xb8, 0xef, 0x5b, 0xfc, 0xd3, 0xe5, 0x45, 0xad, 0x74, 0x6e, 0xea, 0x58, 0x69, 0x0d, 0xf6, 0xa1,
0x68, 0x09, 0x4c, 0xc2, 0x0c, 0xd4, 0x36, 0xc3, 0xef, 0xd4, 0xbf, 0xb6, 0xe2, 0xe6, 0x05, 0xd2, 0x6b, 0x60, 0x16, 0x17, 0xa0, 0x36, 0x05, 0x7e, 0xa3, 0xfe, 0xb5, 0x13, 0xb7, 0xaf, 0x90, 0x66,
0xb4, 0xcd, 0x0c, 0x8f, 0x22, 0x71, 0x49, 0xdd, 0x3a, 0xa2, 0x8e, 0xe8, 0xb3, 0x71, 0xca, 0xf8, 0x5d, 0x66, 0x78, 0x10, 0x99, 0x4f, 0xea, 0xda, 0x37, 0xea, 0x80, 0x21, 0x9b, 0xe6, 0x8c, 0xcf,
0x54, 0x6d, 0x7c, 0xd2, 0xf0, 0x93, 0x33, 0xa9, 0xca, 0x29, 0xda, 0xfa, 0x54, 0x95, 0x02, 0xcd, 0xd4, 0x3a, 0x24, 0x0d, 0x3f, 0x7a, 0x93, 0xda, 0xcd, 0xd0, 0x36, 0xc7, 0xda, 0x09, 0x34, 0xdb,
0x2e, 0x3c, 0x63, 0x56, 0x65, 0x93, 0x48, 0x4c, 0x32, 0x1f, 0xf9, 0xa9, 0xa9, 0xca, 0x57, 0xaf, 0xf8, 0x84, 0x59, 0xbb, 0x36, 0x93, 0x98, 0x15, 0x21, 0xf2, 0x63, 0x4b, 0xd6, 0xb5, 0x3b, 0x3f,
0xaa, 0x32, 0xb4, 0xc4, 0x4c, 0xbd, 0x44, 0x89, 0x20, 0x84, 0x78, 0x80, 0xf5, 0xd2, 0x24, 0xdf, 0xaf, 0x5d, 0x6c, 0xa9, 0x39, 0xcd, 0x1d, 0x4a, 0x04, 0x21, 0xc4, 0x03, 0xac, 0xee, 0x4c, 0xf6,
0x00, 0xc3, 0x2f, 0x8b, 0xf9, 0xef, 0xef, 0x49, 0x5d, 0x82, 0xa6, 0xfb, 0x9e, 0x5e, 0xe7, 0x87, 0x15, 0x30, 0xfe, 0xbc, 0xb8, 0xf9, 0x9d, 0x93, 0xda, 0x81, 0xa6, 0x92, 0x8f, 0x6f, 0xca, 0xfd,
0xd7, 0x13, 0xb4, 0x7b, 0x68, 0x18, 0x9f, 0xf8, 0x84, 0xaa, 0xa1, 0xf2, 0x2e, 0xd6, 0xbe, 0xcf, 0x9b, 0x2b, 0xb4, 0x3b, 0x68, 0x29, 0x14, 0x94, 0x0e, 0x8e, 0x25, 0x60, 0x80, 0xe2, 0x84, 0x5d,
0x32, 0xc4, 0x5d, 0x39, 0x61, 0x42, 0x9c, 0x43, 0xe5, 0x86, 0x22, 0x11, 0x0b, 0xe1, 0x8e, 0xa8, 0x1a, 0x82, 0xa6, 0xb2, 0x23, 0x10, 0x18, 0x6f, 0x25, 0x16, 0x7e, 0xae, 0x91, 0x15, 0x10, 0xbb,
0x31, 0x54, 0xd7, 0xf7, 0xac, 0x2a, 0x4b, 0x36, 0xa1, 0x95, 0xf1, 0x01, 0x9b, 0x44, 0x11, 0x1b, 0x92, 0xa8, 0x12, 0x4e, 0x22, 0x88, 0x09, 0xc8, 0x3d, 0x28, 0x2c, 0x42, 0x96, 0x30, 0xfe, 0xf3,
0x5c, 0xe8, 0xbd, 0x18, 0x67, 0xa6, 0xc4, 0x01, 0x8b, 0x9c, 0x0d, 0x0f, 0x8d, 0x36, 0x3b, 0xd0, 0x68, 0x72, 0xb5, 0x37, 0x2a, 0x0f, 0xc6, 0x3c, 0x76, 0xdb, 0x52, 0x61, 0x27, 0x8d, 0x34, 0xb9,
0xc2, 0xe7, 0x62, 0x76, 0xfa, 0xff, 0x4c, 0x9a, 0x80, 0xe4, 0x6b, 0xa5, 0xa5, 0xad, 0xef, 0xeb, 0x1b, 0xab, 0x68, 0x80, 0x94, 0xd7, 0xb6, 0x94, 0xe4, 0xca, 0x0a, 0xc4, 0x2d, 0x8b, 0x58, 0xed,
0x1d, 0x75, 0x91, 0xb4, 0x56, 0xd6, 0xeb, 0xfd, 0x66, 0x03, 0x96, 0x39, 0x9d, 0x4c, 0xd3, 0xf9, 0x28, 0xb2, 0x8d, 0x4b, 0xd0, 0x1b, 0x2c, 0xe6, 0x93, 0xf3, 0xf3, 0x50, 0x0f, 0x05, 0x39, 0x0d,
0x81, 0x22, 0xdc, 0xa9, 0x92, 0x1a, 0x0a, 0xac, 0xcf, 0x0a, 0x28, 0x4b, 0xb9, 0x05, 0xaa, 0x19, 0xed, 0xe3, 0x78, 0xc9, 0x23, 0x2a, 0x47, 0x68, 0xa8, 0x83, 0x97, 0x0a, 0x48, 0xcd, 0x12, 0xef,
0xa1, 0x3a, 0x86, 0x88, 0x34, 0xf6, 0x68, 0xd6, 0x7f, 0x41, 0x82, 0xde, 0x07, 0xe7, 0xfe, 0xb1, 0xc4, 0x63, 0xa3, 0xcd, 0x16, 0xb4, 0x08, 0xb9, 0x98, 0x1f, 0xff, 0xbf, 0xa4, 0xb6, 0x25, 0xf9,
0x75, 0x5f, 0xd1, 0x55, 0x88, 0xba, 0x25, 0x5a, 0xa5, 0xb7, 0x21, 0x35, 0x72, 0xee, 0x63, 0x98, 0x4a, 0x69, 0x69, 0x9b, 0xfb, 0x66, 0x4b, 0x8c, 0x94, 0xd6, 0xca, 0x66, 0xb5, 0x5b, 0xaf, 0xc1,
0x4a, 0x94, 0x9c, 0x9f, 0x72, 0x40, 0x0f, 0x5b, 0x06, 0xfe, 0x50, 0x1a, 0x6f, 0x5a, 0x2f, 0x1f, 0xb2, 0x88, 0x74, 0x32, 0xcf, 0x6f, 0xf6, 0xc4, 0x80, 0x5b, 0xe5, 0x88, 0x9c, 0x60, 0x43, 0x56,
0x5c, 0x9c, 0xce, 0xc2, 0x15, 0xe2, 0xe7, 0x9f, 0xae, 0x04, 0x3e, 0x8e, 0x56, 0x4f, 0x4f, 0x63, 0x81, 0x73, 0x72, 0x03, 0x34, 0x7f, 0x42, 0xf5, 0xdd, 0xa6, 0x96, 0xb2, 0x47, 0xb3, 0xfa, 0x0b,
0xb7, 0xb9, 0xa6, 0xcd, 0x95, 0x2b, 0x8e, 0x05, 0xdc, 0x5b, 0x3d, 0x75, 0x10, 0x96, 0xe4, 0xe3, 0x32, 0x0c, 0x7e, 0xf1, 0xee, 0x1f, 0x3a, 0xf7, 0x25, 0xa3, 0xbe, 0xa2, 0xb9, 0x43, 0xab, 0xf4,
0x55, 0xa0, 0xe9, 0xf7, 0x66, 0x15, 0x18, 0xf1, 0x9b, 0xc4, 0x2c, 0xa4, 0xd9, 0xf1, 0xcf, 0xcd, 0x26, 0xa6, 0xa5, 0x28, 0x43, 0x8c, 0x73, 0x89, 0x92, 0xf3, 0x53, 0x0f, 0x7d, 0xda, 0x7f, 0x28,
0x12, 0xd9, 0x4b, 0x63, 0x44, 0x9a, 0x07, 0xb2, 0x33, 0xd8, 0xe4, 0xc6, 0x58, 0xff, 0xd9, 0x66, 0x8d, 0x97, 0x9d, 0x57, 0x08, 0x3e, 0x4e, 0x6f, 0xe1, 0x87, 0xfa, 0xe3, 0x0f, 0x67, 0x02, 0xa9,
0x68, 0xfa, 0x96, 0x47, 0xe3, 0xae, 0x2d, 0x40, 0xbc, 0x9d, 0x6e, 0x48, 0x59, 0x8b, 0xf0, 0xdd, 0xc4, 0xa7, 0xa7, 0xa9, 0x3f, 0x4c, 0xe8, 0x70, 0xe6, 0x07, 0x6d, 0x01, 0x77, 0x56, 0xcf, 0x3c,
0xb4, 0x8e, 0xf5, 0xb4, 0x1e, 0x0c, 0xb8, 0x13, 0x1c, 0x9d, 0xe0, 0x18, 0xdb, 0xe9, 0x91, 0x04, 0x84, 0x25, 0xf9, 0x74, 0xe9, 0x9b, 0xf7, 0xf8, 0x76, 0x19, 0x49, 0xf1, 0x1b, 0x0d, 0x20, 0xa6,
0x94, 0x45, 0xb8, 0x51, 0x79, 0xbe, 0x74, 0xa3, 0x21, 0xbe, 0xda, 0xed, 0xda, 0xff, 0xf1, 0x84, 0x3d, 0x0c, 0x4f, 0xc4, 0x4b, 0xec, 0x0b, 0xc9, 0x12, 0xcd, 0x23, 0xd5, 0x1b, 0xac, 0x4b, 0x63,
0x8f, 0xb0, 0x6a, 0x82, 0xf6, 0x3f, 0xb8, 0xbe, 0xec, 0xc6, 0xab, 0x86, 0x7f, 0x0d, 0x9c, 0xc3, 0x6c, 0xf8, 0x6c, 0x33, 0x92, 0x17, 0x96, 0x27, 0xd3, 0x9e, 0x62, 0x20, 0xde, 0xcd, 0xd6, 0xa4,
0x1a, 0x68, 0xa2, 0x16, 0x14, 0xdf, 0xe7, 0xed, 0x59, 0xda, 0xc4, 0x3f, 0xf6, 0xcd, 0x40, 0x06, 0x6c, 0x44, 0xfc, 0x7e, 0xd6, 0xa4, 0x7a, 0xd6, 0x0c, 0x87, 0xdc, 0x0b, 0x0e, 0x5e, 0x70, 0x48,
0x75, 0xdf, 0x04, 0xe1, 0x5b, 0x5a, 0x46, 0xc1, 0xb8, 0xdf, 0x66, 0xb8, 0xf8, 0xdc, 0xd9, 0xb8, 0xed, 0xec, 0x40, 0x02, 0xca, 0x22, 0x5e, 0xab, 0xb2, 0xbc, 0xf3, 0x6b, 0x26, 0xbe, 0xd8, 0xcd,
0x20, 0x64, 0x0e, 0x03, 0xf1, 0xa6, 0xf9, 0xb7, 0xef, 0x68, 0xb6, 0x4b, 0x93, 0x43, 0x08, 0xd6, 0x2a, 0xfc, 0xfe, 0x88, 0x8f, 0xb0, 0x6c, 0xa3, 0xee, 0x7f, 0x38, 0x79, 0x39, 0x4d, 0x97, 0x2d,
0x52, 0x66, 0x6c, 0x01, 0xf0, 0xcd, 0x7b, 0x58, 0x7a, 0xed, 0x71, 0x42, 0x65, 0xe1, 0x4d, 0xc3, 0xff, 0x12, 0x79, 0x87, 0x15, 0xd0, 0x70, 0x17, 0x14, 0x3f, 0xe4, 0xdd, 0x5d, 0xda, 0x2c, 0x3c,
0x9b, 0x73, 0x13, 0x7d, 0x5f, 0x3e, 0x0b, 0xa5, 0xfa, 0xfb, 0xb9, 0x7a, 0xcf, 0x53, 0xf7, 0xf4, 0x5c, 0xc8, 0xa1, 0x8a, 0x9a, 0x0b, 0x19, 0xc5, 0xef, 0xe8, 0x33, 0x8e, 0xa6, 0x17, 0x5d, 0x86,
0xe4, 0xbf, 0x98, 0x90, 0x97, 0xd3, 0x38, 0x0a, 0x48, 0x71, 0xaf, 0x0a, 0x30, 0x7b, 0x6a, 0x52, 0x8b, 0x4f, 0xbd, 0x8d, 0x0f, 0x42, 0xe6, 0x30, 0x14, 0x6f, 0xdb, 0x7f, 0x39, 0x4c, 0xef, 0x84,
0xfe, 0xd2, 0x89, 0x46, 0xb5, 0x09, 0xc6, 0xef, 0x46, 0x9c, 0x37, 0xbc, 0x47, 0x0f, 0x45, 0x37, 0x33, 0x25, 0xf1, 0xca, 0x5a, 0xca, 0x8c, 0x2d, 0x00, 0xbe, 0x06, 0x0f, 0x77, 0x41, 0x77, 0xbd,
0xf8, 0x71, 0xd4, 0xbd, 0x11, 0x51, 0xfb, 0x22, 0xfe, 0x03, 0x69, 0xb3, 0xce, 0x95, 0x27, 0x05, 0xa2, 0xb1, 0xd0, 0x50, 0x79, 0x7b, 0xe2, 0xe7, 0xb7, 0xe3, 0xb3, 0xe0, 0xd4, 0xdf, 0xcf, 0xd3,
0x00, 0x00 0x7b, 0xde, 0xe0, 0xa7, 0xa7, 0xf0, 0xd5, 0xb6, 0xbd, 0xde, 0xec, 0x71, 0x44, 0x8a, 0x7b, 0x55,
0x81, 0xd9, 0x61, 0xd8, 0x11, 0xe9, 0xf5, 0xda, 0xb7, 0xd1, 0xf4, 0xfd, 0x98, 0xf3, 0x96, 0x0f,
0xe8, 0xd1, 0xe9, 0x1f, 0x91, 0x34, 0xe9, 0xdf, 0x9b, 0xa4, 0x7b, 0x5d, 0xff, 0x01, 0x47, 0xe8,
0xd7, 0x03, 0x73, 0x05, 0x00, 0x00
}; };
@ -341,60 +346,60 @@ const uint8_t PAGE_liveviewws2D[] PROGMEM = {
const uint16_t PAGE_404_length = 868; const uint16_t PAGE_404_length = 868;
const uint8_t PAGE_404[] PROGMEM = { const uint8_t PAGE_404[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x65, 0x54, 0x5b, 0x73, 0xaa, 0x3a, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x65, 0x54, 0x5b, 0x73, 0xaa, 0x3a,
0x14, 0x7e, 0xef, 0xaf, 0xe0, 0x78, 0xe6, 0xcc, 0x7e, 0x68, 0x2d, 0xa8, 0xd8, 0x2a, 0xa2, 0x33, 0x14, 0x7e, 0xef, 0xaf, 0x60, 0x7b, 0x66, 0xcf, 0x7e, 0x68, 0x15, 0x54, 0xac, 0x8a, 0xe8, 0x4c,
0x01, 0x51, 0xec, 0xc5, 0x7a, 0xa3, 0xd6, 0xbe, 0x05, 0x12, 0x21, 0x15, 0x08, 0x4d, 0x82, 0x62, 0x40, 0x14, 0x7b, 0xf1, 0x4e, 0xad, 0x7d, 0x0b, 0x10, 0x21, 0x15, 0x08, 0x4d, 0x82, 0x62, 0x3b,
0x3b, 0xfd, 0xef, 0x3b, 0x40, 0xf7, 0x9c, 0xce, 0xec, 0x35, 0x03, 0x2b, 0xf9, 0x56, 0xd6, 0x7d, 0xfd, 0xef, 0x3b, 0x40, 0x3b, 0xa7, 0x33, 0x7b, 0xcd, 0xc0, 0x4a, 0xbe, 0x95, 0x75, 0x5f, 0x89,
0x25, 0xe6, 0x3f, 0xe3, 0x27, 0x7b, 0xb3, 0x5b, 0x38, 0x4a, 0x24, 0x92, 0x78, 0x64, 0x7e, 0xff, 0xfe, 0x6b, 0xbc, 0x30, 0xb7, 0xfb, 0xa5, 0x25, 0x85, 0x3c, 0x8e, 0x46, 0xfa, 0xd7, 0x1f, 0x41,
0x31, 0x44, 0x23, 0x33, 0xc1, 0x02, 0x2a, 0x41, 0x04, 0x19, 0xc7, 0x62, 0xd8, 0xc8, 0xc5, 0xbe, 0x7f, 0xa4, 0xc7, 0x88, 0x43, 0xc9, 0x0b, 0x21, 0x65, 0x88, 0x0f, 0x6b, 0x19, 0x3f, 0xd4, 0x7b,
0xd9, 0x6b, 0x7c, 0xa3, 0x17, 0x01, 0x4d, 0x05, 0x4e, 0x25, 0x7c, 0x22, 0x48, 0x44, 0x43, 0x84, 0xb5, 0x2f, 0xf4, 0xca, 0x23, 0x09, 0x47, 0x89, 0x80, 0xcf, 0xd8, 0xe7, 0xe1, 0xd0, 0x47, 0x27,
0x8f, 0x24, 0xc0, 0xcd, 0x6a, 0xd3, 0x50, 0x52, 0x98, 0xe0, 0x61, 0xe3, 0x48, 0xf0, 0x29, 0xa3, 0xec, 0xa1, 0x7a, 0xb9, 0xa9, 0x49, 0x09, 0x8c, 0xd1, 0xb0, 0x76, 0xc2, 0xe8, 0x9c, 0x12, 0xca,
0x4c, 0xfc, 0xd1, 0xa9, 0x51, 0x11, 0xe1, 0x04, 0x37, 0x03, 0x1a, 0x53, 0xd6, 0xf8, 0x61, 0xe6, 0xbf, 0x75, 0x2a, 0x94, 0x87, 0x28, 0x46, 0x75, 0x8f, 0x44, 0x84, 0xd6, 0x7e, 0x98, 0xf9, 0xaf,
0xdf, 0x76, 0x45, 0xf2, 0xac, 0x20, 0x22, 0xc6, 0xa3, 0x39, 0x15, 0xca, 0x9e, 0xe6, 0x29, 0x32, 0x55, 0x92, 0x38, 0xcb, 0x31, 0x8f, 0xd0, 0x68, 0x4e, 0xb8, 0x74, 0x20, 0x59, 0xe2, 0xeb, 0x72,
0xd5, 0x1a, 0x30, 0xb9, 0x38, 0x4b, 0x76, 0xe1, 0x53, 0x74, 0xfe, 0xdc, 0x4b, 0xb5, 0xe6, 0x1e, 0x05, 0xe8, 0x8c, 0x5f, 0x04, 0xbb, 0x72, 0x89, 0x7f, 0xf9, 0x38, 0x08, 0xb5, 0xfa, 0x01, 0xc6,
0x26, 0x24, 0x3e, 0x1b, 0xcf, 0x98, 0x21, 0x98, 0xc2, 0x2b, 0x17, 0xc7, 0x47, 0x2c, 0x48, 0x00, 0x38, 0xba, 0x68, 0x4f, 0x88, 0xfa, 0x30, 0x81, 0x37, 0x36, 0x8a, 0x4e, 0x88, 0x63, 0x0f, 0xde,
0xaf, 0x38, 0x4c, 0x79, 0x93, 0x63, 0x46, 0xf6, 0x03, 0x81, 0x0b, 0xd1, 0x84, 0x31, 0x09, 0x53, 0x30, 0x98, 0xb0, 0x3a, 0x43, 0x14, 0x1f, 0x06, 0x1c, 0xe5, 0xbc, 0x0e, 0x23, 0x1c, 0x24, 0x9a,
0x23, 0x90, 0x7e, 0x30, 0x1b, 0xf8, 0x30, 0x38, 0x84, 0xac, 0xb4, 0x5c, 0x07, 0x61, 0x94, 0x9e, 0x27, 0xfc, 0x20, 0x3a, 0x70, 0xa1, 0x77, 0x0c, 0x68, 0x61, 0xb9, 0x0a, 0x42, 0x2b, 0x3c, 0x0f,
0x07, 0x09, 0x64, 0x21, 0x49, 0x0d, 0x6d, 0xf0, 0x8d, 0xed, 0xf7, 0xfb, 0x2f, 0x92, 0x84, 0x9f, 0x62, 0x48, 0x03, 0x9c, 0x68, 0xca, 0xe0, 0x0b, 0x3b, 0x1c, 0x0e, 0x9f, 0x38, 0x0e, 0x3e, 0xca,
0x55, 0x42, 0x86, 0xae, 0x69, 0x59, 0x21, 0xcf, 0x14, 0x75, 0x82, 0x46, 0x57, 0xfb, 0x6f, 0x40, 0x84, 0x34, 0x55, 0x51, 0xd2, 0x5c, 0x9c, 0xc9, 0xab, 0x04, 0xb5, 0x8e, 0xf2, 0x7b, 0x80, 0x63,
0x12, 0x18, 0xe2, 0x26, 0xc3, 0x29, 0x92, 0x8e, 0xd2, 0xd0, 0xc8, 0x48, 0x81, 0x63, 0x28, 0x30, 0x18, 0xa0, 0x3a, 0x45, 0x89, 0x2f, 0x1c, 0x25, 0x81, 0x96, 0xe2, 0x1c, 0x45, 0x90, 0x23, 0xff,
0xfa, 0x4b, 0x12, 0x30, 0xc2, 0xb3, 0x26, 0x46, 0x21, 0xe6, 0x7f, 0xfc, 0xb4, 0xbb, 0x59, 0xa1, 0x1f, 0x89, 0x47, 0x31, 0x4b, 0xeb, 0xc8, 0x0f, 0x10, 0xfb, 0xf6, 0xd3, 0xea, 0xa4, 0xb9, 0xa4,
0x68, 0x4a, 0xb3, 0xa5, 0x95, 0xfc, 0xcb, 0xcf, 0x85, 0xa0, 0xe9, 0x27, 0xcd, 0x45, 0x4c, 0x52, 0x48, 0xf5, 0xa6, 0x52, 0xf0, 0x4f, 0x37, 0xe3, 0x9c, 0x24, 0x1f, 0x24, 0xe3, 0x11, 0x4e, 0x50,
0x5c, 0x46, 0x91, 0x33, 0x2e, 0xc3, 0xc8, 0x28, 0xa9, 0x62, 0xce, 0x20, 0x42, 0xa5, 0xa5, 0x5e, 0x11, 0x45, 0x46, 0x99, 0x08, 0x23, 0x25, 0xb8, 0x8c, 0x39, 0x85, 0xbe, 0x5f, 0x58, 0xea, 0x95,
0x15, 0x45, 0x65, 0xa1, 0xd4, 0x1c, 0xd4, 0xd1, 0xb4, 0x3b, 0xe5, 0xba, 0xca, 0x54, 0x30, 0x99, 0x51, 0x94, 0x16, 0x0a, 0xcd, 0x41, 0x15, 0x4d, 0xab, 0x5d, 0xac, 0xcb, 0x4c, 0x39, 0x15, 0xa9,
0xfa, 0x9e, 0xb2, 0xc4, 0xc8, 0xb3, 0x0c, 0xb3, 0x00, 0x72, 0x3c, 0xf8, 0x59, 0xab, 0xe8, 0x4f, 0x1f, 0x08, 0x8d, 0xb5, 0x2c, 0x4d, 0x11, 0xf5, 0x20, 0x43, 0x83, 0x9f, 0xb5, 0x0a, 0xbf, 0x6b,
0x8d, 0x6a, 0x94, 0x93, 0x0f, 0x6c, 0xb4, 0xfa, 0x52, 0xfb, 0xef, 0xaa, 0x74, 0x3a, 0x9d, 0x1f, 0x54, 0xa1, 0x0c, 0xbf, 0x23, 0xad, 0xd9, 0x17, 0xda, 0xff, 0x56, 0xa5, 0xdd, 0x6e, 0xff, 0x28,
0xc5, 0x18, 0xf8, 0x94, 0xc9, 0x74, 0x0c, 0x4d, 0xe1, 0x34, 0x26, 0x48, 0xf9, 0x81, 0x35, 0x19, 0xc6, 0xc0, 0x25, 0x54, 0xa4, 0xa3, 0x29, 0x12, 0x23, 0x11, 0xf6, 0xa5, 0x1f, 0x58, 0x9d, 0x42,
0x44, 0x24, 0xe7, 0x55, 0x4e, 0x5f, 0x17, 0xa6, 0x5a, 0xf7, 0xc9, 0x54, 0xeb, 0x19, 0x2a, 0xdb, 0x1f, 0x67, 0xac, 0xcc, 0xe9, 0xf3, 0x4a, 0x97, 0xab, 0x3e, 0xe9, 0x72, 0x35, 0x43, 0x45, 0xbb,
0x35, 0x32, 0x65, 0x29, 0x15, 0x18, 0xcb, 0x36, 0xcb, 0x96, 0x73, 0x16, 0x0c, 0x1b, 0x08, 0x0a, 0x46, 0xba, 0x28, 0xa5, 0x04, 0x23, 0xd1, 0x66, 0xd1, 0x72, 0x46, 0xbd, 0x61, 0xcd, 0x87, 0x1c,
0x68, 0x54, 0x85, 0x52, 0xb3, 0x34, 0x94, 0xee, 0x39, 0xbe, 0xd1, 0xaf, 0xc8, 0xb3, 0xf5, 0xb4, 0x6a, 0x65, 0xa1, 0xe4, 0x34, 0x09, 0x84, 0x7b, 0x86, 0x6e, 0xd5, 0x1b, 0xfc, 0x64, 0x2c, 0xd6,
0x3a, 0x69, 0xf7, 0xd3, 0x90, 0x02, 0x49, 0xf3, 0xb5, 0x17, 0x39, 0x5e, 0x28, 0x57, 0x76, 0xb9, 0x67, 0xe5, 0x7e, 0x1a, 0x10, 0x20, 0x68, 0xbe, 0x71, 0x42, 0xcb, 0x09, 0xc4, 0xca, 0x2c, 0xb6,
0x05, 0xa1, 0x0d, 0x1e, 0x25, 0xb3, 0x9c, 0x6c, 0xc6, 0xa6, 0x15, 0xf2, 0x32, 0x5f, 0xaf, 0xb4, 0x20, 0x30, 0xc1, 0xa3, 0x60, 0x86, 0x95, 0xce, 0xe8, 0xb4, 0x44, 0x9e, 0xe7, 0x9b, 0xb5, 0x32,
0x19, 0x60, 0x5c, 0x0f, 0x6e, 0x96, 0x25, 0xb0, 0x4a, 0x97, 0x5e, 0xcb, 0x92, 0x0a, 0xc5, 0xdb, 0x03, 0x94, 0xa9, 0xde, 0xed, 0xaa, 0x00, 0xd6, 0xc9, 0xca, 0x69, 0x1a, 0x42, 0x21, 0x7f, 0x3d,
0xe9, 0xd8, 0xdb, 0x2d, 0xbd, 0x12, 0xf4, 0x3d, 0xa7, 0xf0, 0x56, 0x95, 0xdc, 0xea, 0xb5, 0x42, 0x9f, 0x7a, 0xfb, 0x95, 0x53, 0x80, 0xae, 0x63, 0xe5, 0xce, 0xba, 0x94, 0x1b, 0xbd, 0x66, 0x60,
0xdb, 0x53, 0x3f, 0xee, 0xdf, 0xd5, 0x92, 0xfa, 0xfe, 0xb6, 0x45, 0x6d, 0x10, 0x4e, 0x23, 0x0a, 0x3a, 0xf2, 0xfb, 0xfd, 0x9b, 0x5c, 0x50, 0xdf, 0xdd, 0x35, 0x89, 0x09, 0x82, 0x69, 0x48, 0x60,
0x4b, 0xf1, 0x74, 0xf1, 0xf0, 0xd2, 0xab, 0x2c, 0xdf, 0xa1, 0xc9, 0xdd, 0x93, 0xa7, 0xfe, 0x4f, 0x21, 0x9e, 0x2e, 0x1f, 0x9e, 0x7b, 0xa5, 0xe5, 0x3b, 0x7f, 0x72, 0xb7, 0x70, 0xe4, 0xff, 0x09,
0x60, 0x32, 0x5f, 0x60, 0x6b, 0x56, 0xc9, 0x02, 0x27, 0x7a, 0x0d, 0x4e, 0x00, 0x8c, 0x79, 0xb9, 0x4c, 0xe6, 0x4b, 0x64, 0xcc, 0x4a, 0x99, 0x67, 0x85, 0x2f, 0xde, 0x19, 0x80, 0x31, 0x2b, 0xb6,
0xbd, 0x05, 0x60, 0xcb, 0xb6, 0x64, 0x79, 0x28, 0x03, 0x45, 0x6b, 0x6f, 0x65, 0x3d, 0x8f, 0xa3, 0x5d, 0x00, 0x76, 0x74, 0x87, 0x57, 0xc7, 0x22, 0x50, 0x7f, 0xe3, 0xac, 0x8d, 0xa7, 0x71, 0xb8,
0x45, 0x11, 0xf4, 0xfd, 0x31, 0xf5, 0x42, 0x07, 0xcc, 0x97, 0xd8, 0x5f, 0xa8, 0x13, 0x2f, 0x77, 0xcc, 0xbd, 0xbe, 0x3b, 0x26, 0x4e, 0x60, 0x81, 0xf9, 0x0a, 0xb9, 0x4b, 0x79, 0xe2, 0x64, 0xf6,
0x1f, 0xdf, 0xac, 0xe9, 0x4e, 0xb5, 0x2e, 0x3b, 0xce, 0x6e, 0x75, 0xbb, 0x72, 0xb5, 0x77, 0x5b, 0xe3, 0xab, 0x31, 0xdd, 0xcb, 0xc6, 0x75, 0xdb, 0xda, 0xaf, 0xbb, 0x6b, 0x5b, 0x79, 0x33, 0xe5,
0x7d, 0xb5, 0x82, 0x1b, 0xf7, 0x64, 0xc7, 0x6f, 0xa1, 0xfb, 0x74, 0x59, 0xbc, 0xce, 0x9e, 0xd7, 0x17, 0xc3, 0xbb, 0xb5, 0xcf, 0x66, 0xf4, 0x1a, 0xd8, 0x8b, 0xeb, 0xfc, 0x65, 0xf6, 0xb4, 0x99,
0xb3, 0x36, 0xdf, 0x85, 0x2e, 0x9c, 0xde, 0x3a, 0xd6, 0x36, 0xea, 0xbd, 0x6d, 0x69, 0xb1, 0x61, 0xb5, 0xd8, 0x3e, 0xb0, 0xe1, 0xb4, 0x6b, 0x19, 0xbb, 0xb0, 0xf7, 0xba, 0x23, 0xf9, 0x96, 0x9a,
0xb6, 0x35, 0x41, 0xe3, 0xbb, 0x4b, 0x6b, 0xac, 0xc7, 0xfe, 0xcc, 0x2d, 0x40, 0xf0, 0xd1, 0x03, 0xc6, 0xc4, 0x1f, 0xdf, 0x5d, 0x1b, 0x63, 0x35, 0x72, 0x67, 0x76, 0x0e, 0xbc, 0xf7, 0x1e, 0x58,
0x0b, 0xf0, 0xfc, 0xb0, 0xe1, 0xec, 0xd5, 0xd1, 0xf1, 0x72, 0xdc, 0x7d, 0xff, 0x10, 0x9d, 0x00, 0x82, 0xa7, 0x87, 0x2d, 0xa3, 0x2f, 0x96, 0x8a, 0x56, 0xe3, 0xce, 0xdb, 0x3b, 0x6f, 0x7b, 0x60,
0x4c, 0x36, 0x3b, 0x7a, 0xb0, 0xf5, 0x9d, 0x3d, 0xef, 0x4f, 0xcf, 0x7e, 0x98, 0xeb, 0x67, 0xb0, 0xb2, 0xdd, 0x93, 0xa3, 0xa9, 0xee, 0xcd, 0x79, 0x7f, 0x7a, 0x71, 0x83, 0x4c, 0xbd, 0x80, 0x15,
0x14, 0xd6, 0xe4, 0x61, 0xf9, 0xe2, 0xe6, 0x2e, 0xb0, 0xc0, 0xed, 0xdd, 0x23, 0x7e, 0x72, 0x6c, 0x37, 0x26, 0x0f, 0xab, 0x67, 0x3b, 0xb3, 0x81, 0x01, 0xba, 0x77, 0x8f, 0x68, 0x61, 0x99, 0xb2,
0xd5, 0xd1, 0xb6, 0xdd, 0xfc, 0xdc, 0x0f, 0x8f, 0xfa, 0x91, 0x76, 0x97, 0xee, 0x7d, 0x9b, 0xdc, 0xa5, 0xec, 0x3a, 0xd9, 0xa5, 0x1f, 0x9c, 0xd4, 0x13, 0xe9, 0xac, 0xec, 0xfb, 0x16, 0xee, 0x5e,
0x9e, 0xdf, 0xdb, 0x76, 0xcf, 0x03, 0xd6, 0xa3, 0xee, 0x26, 0x0f, 0x97, 0xf6, 0x7a, 0xf3, 0x62, 0xde, 0x5a, 0x66, 0xcf, 0x01, 0xc6, 0xa3, 0x6a, 0xc7, 0x0f, 0xd7, 0xe6, 0x66, 0xfb, 0x6c, 0x6e,
0x6f, 0x26, 0xad, 0x31, 0xb3, 0x5f, 0x6e, 0x2e, 0xa7, 0x59, 0xff, 0xc3, 0xea, 0xa2, 0x2a, 0x5b, 0x27, 0xcd, 0x31, 0x35, 0x9f, 0x6f, 0xaf, 0xa7, 0x69, 0xff, 0xdd, 0xe8, 0xf8, 0x65, 0xb6, 0xc0,
0xe0, 0xc4, 0x93, 0xcd, 0x61, 0x9d, 0x2f, 0x13, 0xdb, 0x6e, 0x8c, 0x2e, 0xcc, 0xa8, 0x35, 0xd2, 0x8a, 0x26, 0xdb, 0xe3, 0x26, 0x5b, 0xc5, 0xa6, 0x59, 0x1b, 0x5d, 0xe9, 0x61, 0x73, 0xa4, 0x2a,
0x35, 0x5d, 0x29, 0xef, 0xeb, 0xa4, 0xbe, 0xaf, 0x12, 0x31, 0xfd, 0x11, 0x38, 0xe0, 0x84, 0x28, 0xaa, 0x54, 0xdc, 0xd7, 0x49, 0x75, 0x5f, 0x05, 0xa2, 0xbb, 0x23, 0x70, 0x44, 0x31, 0x96, 0x7c,
0x88, 0x62, 0xae, 0xa4, 0x52, 0x76, 0x48, 0xe9, 0x49, 0x39, 0x45, 0x98, 0x61, 0xe5, 0x4c, 0x73, 0x82, 0x98, 0x94, 0x08, 0xd9, 0x31, 0x21, 0x67, 0xe9, 0x1c, 0x22, 0x8a, 0xa4, 0x0b, 0xc9, 0x24,
0x05, 0x4a, 0x5e, 0x0e, 0x08, 0x46, 0xd7, 0xd7, 0xd7, 0xa6, 0xea, 0x4b, 0x0d, 0x56, 0x7d, 0x17, 0x28, 0x78, 0x31, 0x20, 0xc8, 0x6f, 0x34, 0x1a, 0xba, 0xec, 0x0a, 0x0d, 0x5a, 0x7e, 0x57, 0x7a,
0x66, 0x7d, 0x19, 0x14, 0x9a, 0x06, 0x31, 0x09, 0x0e, 0xc3, 0x5f, 0x27, 0x92, 0x22, 0x7a, 0xba, 0x75, 0x19, 0x24, 0x92, 0x78, 0x11, 0xf6, 0x8e, 0xc3, 0x3f, 0x67, 0x9c, 0xf8, 0xe4, 0xdc, 0x88,
0x8e, 0x69, 0x00, 0x05, 0xa1, 0xe9, 0x75, 0xc4, 0xf0, 0x7e, 0xd8, 0x50, 0xb9, 0x1c, 0x43, 0xcc, 0x88, 0x07, 0x39, 0x26, 0x49, 0x23, 0xa4, 0xe8, 0x30, 0xac, 0x35, 0x1a, 0x32, 0x13, 0x83, 0x88,
0x78, 0xe3, 0xd7, 0xc8, 0x92, 0xb3, 0xab, 0x08, 0xaa, 0x94, 0x6f, 0x09, 0xa3, 0x31, 0x97, 0xe6, 0x28, 0xab, 0xfd, 0x19, 0x19, 0x62, 0x7a, 0x25, 0x4e, 0xa4, 0xe2, 0x35, 0xa1, 0x24, 0x62, 0xc2,
0x2a, 0x13, 0xd2, 0x96, 0x5a, 0x8f, 0x9f, 0x5a, 0xbd, 0x6a, 0xbf, 0x01, 0x22, 0xc8, 0xb7, 0x64, 0x60, 0x69, 0x44, 0x58, 0x93, 0xab, 0x01, 0x94, 0xcb, 0x77, 0xed, 0x2f, 0xa3, 0xab, 0x2b, 0xfe,
0xeb, 0x04, 0x00, 0x00 0xed, 0x04, 0x00, 0x00
}; };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -189,7 +189,7 @@ void sendImprovInfoResponse() {
out[11] = 4; //Firmware len ("WLED") out[11] = 4; //Firmware len ("WLED")
out[12] = 'W'; out[13] = 'L'; out[14] = 'E'; out[15] = 'D'; out[12] = 'W'; out[13] = 'L'; out[14] = 'E'; out[15] = 'D';
uint8_t lengthSum = 17; uint8_t lengthSum = 17;
uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.14.0-b2/%i"),VERSION); uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.14.0-b3/%i"),VERSION);
out[16] = vlen; lengthSum += vlen; out[16] = vlen; lengthSum += vlen;
uint8_t hlen = 7; uint8_t hlen = 7;
#ifdef ESP8266 #ifdef ESP8266

View File

@ -9,6 +9,7 @@
#define JSON_PATH_PALETTES 5 #define JSON_PATH_PALETTES 5
#define JSON_PATH_FXDATA 6 #define JSON_PATH_FXDATA 6
#define JSON_PATH_NETWORKS 7 #define JSON_PATH_NETWORKS 7
#define JSON_PATH_EFFECTS 8
/* /*
* JSON API (De)serialization * JSON API (De)serialization
@ -812,7 +813,7 @@ void setPaletteColors(JsonArray json, byte* tcp)
} }
} }
void serializePalettes(JsonObject root, AsyncWebServerRequest* request) void serializePalettes(JsonObject root, int page)
{ {
byte tcp[72]; byte tcp[72];
#ifdef ESP8266 #ifdef ESP8266
@ -821,11 +822,6 @@ void serializePalettes(JsonObject root, AsyncWebServerRequest* request)
int itemPerPage = 8; int itemPerPage = 8;
#endif #endif
int page = 0;
if (request->hasParam("page")) {
page = request->getParam("page")->value().toInt();
}
int palettesCount = strip.getPaletteCount(); int palettesCount = strip.getPaletteCount();
int customPalettes = strip.customPalettes.size(); int customPalettes = strip.customPalettes.size();
@ -1001,6 +997,7 @@ void serveJson(AsyncWebServerRequest* request)
else if (url.indexOf("info") > 0) subJson = JSON_PATH_INFO; else if (url.indexOf("info") > 0) subJson = JSON_PATH_INFO;
else if (url.indexOf("si") > 0) subJson = JSON_PATH_STATE_INFO; else if (url.indexOf("si") > 0) subJson = JSON_PATH_STATE_INFO;
else if (url.indexOf("nodes") > 0) subJson = JSON_PATH_NODES; else if (url.indexOf("nodes") > 0) subJson = JSON_PATH_NODES;
else if (url.indexOf("eff") > 0) subJson = JSON_PATH_EFFECTS;
else if (url.indexOf("palx") > 0) subJson = JSON_PATH_PALETTES; else if (url.indexOf("palx") > 0) subJson = JSON_PATH_PALETTES;
else if (url.indexOf("fxda") > 0) subJson = JSON_PATH_FXDATA; else if (url.indexOf("fxda") > 0) subJson = JSON_PATH_FXDATA;
else if (url.indexOf("net") > 0) subJson = JSON_PATH_NETWORKS; else if (url.indexOf("net") > 0) subJson = JSON_PATH_NETWORKS;
@ -1010,20 +1007,6 @@ void serveJson(AsyncWebServerRequest* request)
return; return;
} }
#endif #endif
else if (url.indexOf(F("eff")) > 0) {
// this serves just effect names without FX data extensions in names
if (requestJSONBufferLock(19)) {
AsyncJsonResponse* response = new AsyncJsonResponse(&doc, true); // array document
JsonArray lDoc = response->getRoot();
serializeModeNames(lDoc); // remove WLED-SR extensions from effect names
response->setLength();
request->send(response);
releaseJSONBufferLock();
} else {
request->send(503, "application/json", F("{\"error\":3}"));
}
return;
}
else if (url.indexOf("pal") > 0) { else if (url.indexOf("pal") > 0) {
request->send_P(200, "application/json", JSON_palette_names); request->send_P(200, "application/json", JSON_palette_names);
return; return;
@ -1040,7 +1023,7 @@ void serveJson(AsyncWebServerRequest* request)
request->send(503, "application/json", F("{\"error\":3}")); request->send(503, "application/json", F("{\"error\":3}"));
return; return;
} }
AsyncJsonResponse *response = new AsyncJsonResponse(&doc, subJson==6); AsyncJsonResponse *response = new AsyncJsonResponse(&doc, subJson==JSON_PATH_FXDATA || subJson==JSON_PATH_EFFECTS); // will clear and convert JsonDocument into JsonArray if necessary
JsonVariant lDoc = response->getRoot(); JsonVariant lDoc = response->getRoot();
@ -1053,9 +1036,11 @@ void serveJson(AsyncWebServerRequest* request)
case JSON_PATH_NODES: case JSON_PATH_NODES:
serializeNodes(lDoc); break; serializeNodes(lDoc); break;
case JSON_PATH_PALETTES: case JSON_PATH_PALETTES:
serializePalettes(lDoc, request); break; serializePalettes(lDoc, request->hasParam("page") ? request->getParam("page")->value().toInt() : 0); break;
case JSON_PATH_EFFECTS:
serializeModeNames(lDoc); break;
case JSON_PATH_FXDATA: case JSON_PATH_FXDATA:
serializeModeData(lDoc.as<JsonArray>()); break; serializeModeData(lDoc); break;
case JSON_PATH_NETWORKS: case JSON_PATH_NETWORKS:
serializeNetworks(lDoc); break; serializeNetworks(lDoc); break;
default: //all default: //all
@ -1074,7 +1059,9 @@ void serveJson(AsyncWebServerRequest* request)
DEBUG_PRINTF("JSON buffer size: %u for request: %d\n", lDoc.memoryUsage(), subJson); DEBUG_PRINTF("JSON buffer size: %u for request: %d\n", lDoc.memoryUsage(), subJson);
response->setLength(); size_t len = response->setLength();
DEBUG_PRINT(F("JSON content length: ")); DEBUG_PRINTLN(len);
request->send(response); request->send(response);
releaseJSONBufferLock(); releaseJSONBufferLock();
} }

View File

@ -37,8 +37,8 @@ void applyValuesToSelectedSegs()
if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;} if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;}
if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;} if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;}
if (effectPalette != selsegPrev.palette) {seg.palette = effectPalette; stateChanged = true;} if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette); stateChanged = true;}
if (effectCurrent != selsegPrev.mode) {strip.setMode(i, effectCurrent); stateChanged = true;} if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent); stateChanged = true;}
uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]); uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]);
uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]); uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]);
if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0); stateChanged = true;} if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0); stateChanged = true;}

View File

@ -5,7 +5,7 @@
/* /*
* Parser for Loxone formats * Parser for Loxone formats
*/ */
bool parseLx(int lxValue, byte rgbw[4]) bool parseLx(int lxValue, byte* rgbw)
{ {
DEBUG_PRINT(F("LX: Lox = ")); DEBUG_PRINT(F("LX: Lox = "));
DEBUG_PRINTLN(lxValue); DEBUG_PRINTLN(lxValue);

View File

@ -167,7 +167,8 @@ void handlePresets()
changePreset = true; changePreset = true;
} else { } else {
if (!fdo["seg"].isNull() || !fdo["on"].isNull() || !fdo["bri"].isNull() || !fdo["nl"].isNull() || !fdo["ps"].isNull() || !fdo[F("playlist")].isNull()) changePreset = true; if (!fdo["seg"].isNull() || !fdo["on"].isNull() || !fdo["bri"].isNull() || !fdo["nl"].isNull() || !fdo["ps"].isNull() || !fdo[F("playlist")].isNull()) changePreset = true;
fdo.remove("ps"); //remove load request for presets to prevent recursive crash if (!(tmpMode == CALL_MODE_BUTTON_PRESET && fdo["ps"].is<const char *>() && strchr(fdo["ps"].as<const char *>(),'~') != strrchr(fdo["ps"].as<const char *>(),'~')))
fdo.remove("ps"); // remove load request for presets to prevent recursive crash (if not called by button and contains preset cycling string "1~5~")
deserializeState(fdo, CALL_MODE_NO_NOTIFY, tmpPreset); // may change presetToApply by calling applyPreset() deserializeState(fdo, CALL_MODE_NO_NOTIFY, tmpPreset); // may change presetToApply by calling applyPreset()
} }
if (!errorFlag && tmpPreset < 255 && changePreset) presetCycCurr = currentPreset = tmpPreset; if (!errorFlag && tmpPreset < 255 && changePreset) presetCycCurr = currentPreset = tmpPreset;

View File

@ -233,7 +233,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
gammaCorrectCol = request->hasArg(F("GC")); gammaCorrectCol = request->hasArg(F("GC"));
gammaCorrectVal = request->arg(F("GV")).toFloat(); gammaCorrectVal = request->arg(F("GV")).toFloat();
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3)
calcGammaTable(gammaCorrectVal); NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
else { else {
gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false; gammaCorrectBri = false;
@ -462,7 +462,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
clearEEPROM(); clearEEPROM();
#endif #endif
serveMessage(request, 200, F("All Settings erased."), F("Connect to WLED-AP to setup again"),255); serveMessage(request, 200, F("All Settings erased."), F("Connect to WLED-AP to setup again"),255);
doReboot = true; doReboot = true; // may reboot immediately on dual-core system (race condition) which is desireable in this case
} }
if (request->hasArg(F("PIN"))) { if (request->hasArg(F("PIN"))) {
@ -539,27 +539,24 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// global I2C & SPI pins // global I2C & SPI pins
int8_t hw_sda_pin = !request->arg(F("SDA")).length() ? -1 : (int)request->arg(F("SDA")).toInt(); int8_t hw_sda_pin = !request->arg(F("SDA")).length() ? -1 : (int)request->arg(F("SDA")).toInt();
int8_t hw_scl_pin = !request->arg(F("SCL")).length() ? -1 : (int)request->arg(F("SCL")).toInt(); int8_t hw_scl_pin = !request->arg(F("SCL")).length() ? -1 : (int)request->arg(F("SCL")).toInt();
#ifdef ESP8266 if (i2c_sda != hw_sda_pin || i2c_scl != hw_scl_pin) {
// cannot change pins on ESP8266 // only if pins changed
if (hw_sda_pin >= 0 && hw_sda_pin != HW_PIN_SDA) hw_sda_pin = HW_PIN_SDA; uint8_t old_i2c[2] = { static_cast<uint8_t>(i2c_scl), static_cast<uint8_t>(i2c_sda) };
if (hw_scl_pin >= 0 && hw_scl_pin != HW_PIN_SCL) hw_scl_pin = HW_PIN_SCL; pinManager.deallocateMultiplePins(old_i2c, 2, PinOwner::HW_I2C); // just in case deallocation of old pins
#endif
PinManagerPinType i2c[2] = { { hw_sda_pin, true }, { hw_scl_pin, true } }; PinManagerPinType i2c[2] = { { hw_sda_pin, true }, { hw_scl_pin, true } };
if (hw_sda_pin >= 0 && hw_scl_pin >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { if (hw_sda_pin >= 0 && hw_scl_pin >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) {
i2c_sda = hw_sda_pin; i2c_sda = hw_sda_pin;
i2c_scl = hw_scl_pin; i2c_scl = hw_scl_pin;
#ifdef ESP32 // no bus re-initialisation as usermods do not get any notification
Wire.setPins(i2c_sda, i2c_scl); // this will fail if Wire is initilised (Wire.begin() called) //Wire.begin(i2c_sda, i2c_scl);
#endif
Wire.begin();
} else { } else {
// there is no Wire.end() // there is no Wire.end()
DEBUG_PRINTLN(F("Could not allocate I2C pins.")); DEBUG_PRINTLN(F("Could not allocate I2C pins."));
uint8_t i2c[2] = { static_cast<uint8_t>(i2c_scl), static_cast<uint8_t>(i2c_sda) };
pinManager.deallocateMultiplePins(i2c, 2, PinOwner::HW_I2C); // just in case deallocation of old pins
i2c_sda = -1; i2c_sda = -1;
i2c_scl = -1; i2c_scl = -1;
} }
}
int8_t hw_mosi_pin = !request->arg(F("MOSI")).length() ? -1 : (int)request->arg(F("MOSI")).toInt(); int8_t hw_mosi_pin = !request->arg(F("MOSI")).length() ? -1 : (int)request->arg(F("MOSI")).toInt();
int8_t hw_miso_pin = !request->arg(F("MISO")).length() ? -1 : (int)request->arg(F("MISO")).toInt(); int8_t hw_miso_pin = !request->arg(F("MISO")).length() ? -1 : (int)request->arg(F("MISO")).toInt();
int8_t hw_sclk_pin = !request->arg(F("SCLK")).length() ? -1 : (int)request->arg(F("SCLK")).toInt(); int8_t hw_sclk_pin = !request->arg(F("SCLK")).length() ? -1 : (int)request->arg(F("SCLK")).toInt();
@ -569,6 +566,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (hw_miso_pin >= 0 && hw_miso_pin != HW_PIN_MISOSPI) hw_mosi_pin = HW_PIN_MISOSPI; if (hw_miso_pin >= 0 && hw_miso_pin != HW_PIN_MISOSPI) hw_mosi_pin = HW_PIN_MISOSPI;
if (hw_sclk_pin >= 0 && hw_sclk_pin != HW_PIN_CLOCKSPI) hw_sclk_pin = HW_PIN_CLOCKSPI; if (hw_sclk_pin >= 0 && hw_sclk_pin != HW_PIN_CLOCKSPI) hw_sclk_pin = HW_PIN_CLOCKSPI;
#endif #endif
if (spi_mosi != hw_mosi_pin || spi_miso != hw_miso_pin || spi_sclk != hw_sclk_pin) {
// only if pins changed
uint8_t old_spi[3] = { static_cast<uint8_t>(spi_mosi), static_cast<uint8_t>(spi_miso), static_cast<uint8_t>(spi_sclk) };
pinManager.deallocateMultiplePins(old_spi, 3, PinOwner::HW_SPI); // just in case deallocation of old pins
PinManagerPinType spi[3] = { { hw_mosi_pin, true }, { hw_miso_pin, true }, { hw_sclk_pin, true } }; PinManagerPinType spi[3] = { { hw_mosi_pin, true }, { hw_miso_pin, true }, { hw_sclk_pin, true } };
if (hw_mosi_pin >= 0 && hw_sclk_pin >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { if (hw_mosi_pin >= 0 && hw_sclk_pin >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) {
spi_mosi = hw_mosi_pin; spi_mosi = hw_mosi_pin;
@ -584,12 +585,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
} else { } else {
//SPI.end(); //SPI.end();
DEBUG_PRINTLN(F("Could not allocate SPI pins.")); DEBUG_PRINTLN(F("Could not allocate SPI pins."));
uint8_t spi[3] = { static_cast<uint8_t>(spi_mosi), static_cast<uint8_t>(spi_miso), static_cast<uint8_t>(spi_sclk) };
pinManager.deallocateMultiplePins(spi, 3, PinOwner::HW_SPI); // just in case deallocation of old pins
spi_mosi = -1; spi_mosi = -1;
spi_miso = -1; spi_miso = -1;
spi_sclk = -1; spi_sclk = -1;
} }
}
JsonObject um = doc.createNestedObject("um"); JsonObject um = doc.createNestedObject("um");
@ -708,7 +708,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
#endif #endif
lastEditTime = millis(); lastEditTime = millis();
if (subPage != 2 && !doReboot) doSerializeConfig = true; //serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init) // do not save if factory reset or LED settings (which are saved after LED re-init)
doSerializeConfig = subPage != 2 && !(subPage == 6 && doReboot);
if (subPage == 8) doReboot = request->hasArg(F("RBT")); // prevent race condition on dual core system (set reboot here, after doSerializeConfig has been set)
#ifndef WLED_DISABLE_ALEXA #ifndef WLED_DISABLE_ALEXA
if (subPage == 4) alexaInit(); if (subPage == 4) alexaInit();
#endif #endif

View File

@ -73,13 +73,6 @@ void WLED::loop()
handleAlexa(); handleAlexa();
#endif #endif
yield();
if (doSerializeConfig) serializeConfig();
if (doReboot && !doInitBusses) // if busses have to be inited & saved, wait until next iteration
reset();
if (doCloseFile) { if (doCloseFile) {
closeFile(); closeFile();
yield(); yield();
@ -169,13 +162,14 @@ void WLED::loop()
strip.finalizeInit(); // also loads default ledmap if present strip.finalizeInit(); // also loads default ledmap if present
if (aligned) strip.makeAutoSegments(); if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments(); else strip.fixInvalidSegments();
yield(); doSerializeConfig = true;
serializeConfig();
} }
if (loadLedmap >= 0) { if (loadLedmap >= 0) {
if (!strip.deserializeMap(loadLedmap) && strip.isMatrix && loadLedmap == 0) strip.setUpMatrix(); if (!strip.deserializeMap(loadLedmap) && strip.isMatrix && loadLedmap == 0) strip.setUpMatrix();
loadLedmap = -1; loadLedmap = -1;
} }
yield();
if (doSerializeConfig) serializeConfig();
yield(); yield();
handleWs(); handleWs();
@ -229,6 +223,9 @@ void WLED::loop()
ESP.wdtFeed(); ESP.wdtFeed();
#endif #endif
#endif #endif
if (doReboot && (!doInitBusses || !doSerializeConfig)) // if busses have to be inited & saved, wait until next iteration
reset();
} }
void WLED::enableWatchdog() { void WLED::enableWatchdog() {

View File

@ -3,12 +3,12 @@
/* /*
Main sketch, global variable declarations Main sketch, global variable declarations
@title WLED project sketch @title WLED project sketch
@version 0.14.0-b2 @version 0.14.0-b3
@author Christian Schwinne @author Christian Schwinne
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2306020 #define VERSION 2306130
//uncomment this if you have a "my_config.h" file you'd like to use //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG

View File

@ -195,7 +195,7 @@ void initServer()
JsonObject root = doc.as<JsonObject>(); JsonObject root = doc.as<JsonObject>();
if (error || root.isNull()) { if (error || root.isNull()) {
releaseJSONBufferLock(); releaseJSONBufferLock();
request->send(400, "application/json", F("{\"error\":9}")); request->send(400, "application/json", F("{\"error\":9}")); // ERR_JSON
return; return;
} }
const String& url = request->url(); const String& url = request->url();
@ -210,6 +210,11 @@ void initServer()
*/ */
verboseResponse = deserializeState(root); verboseResponse = deserializeState(root);
} else { } else {
if (!correctPIN && strlen(settingsPIN)>0) {
request->send(403, "application/json", F("{\"error\":1}")); // ERR_DENIED
releaseJSONBufferLock();
return;
}
verboseResponse = deserializeConfig(root); //use verboseResponse to determine whether cfg change should be saved immediately verboseResponse = deserializeConfig(root); //use verboseResponse to determine whether cfg change should be saved immediately
} }
releaseJSONBufferLock(); releaseJSONBufferLock();
@ -611,7 +616,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post)
} else { } else {
if (!s2[0]) strcpy_P(s2, s_redirecting); if (!s2[0]) strcpy_P(s2, s_redirecting);
serveMessage(request, 200, s, s2, (subPage == 1 || (subPage == 6 && doReboot)) ? 129 : (correctPIN ? 1 : 3)); serveMessage(request, 200, s, s2, (subPage == 1 || ((subPage == 6 || subPage == 8) && doReboot)) ? 129 : (correctPIN ? 1 : 3));
return; return;
} }
} }

View File

@ -78,7 +78,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
if((info->index + len) == info->len){ if((info->index + len) == info->len){
if(info->final){ if(info->final){
if(info->message_opcode == WS_TEXT) { if(info->message_opcode == WS_TEXT) {
client->text(F("{\"error\":9}")); //we do not handle split packets right now client->text(F("{\"error\":9}")); // ERR_JSON we do not handle split packets right now
} }
} }
} }

View File

@ -197,7 +197,7 @@ void appendGPIOinfo() {
#elif defined(CONFIG_IDF_TARGET_ESP32C3) #elif defined(CONFIG_IDF_TARGET_ESP32C3)
oappend(SET_F("d.rsvd=[11,12,13,14,15,16,17")); oappend(SET_F("d.rsvd=[11,12,13,14,15,16,17"));
#elif defined(ESP32) #elif defined(ESP32)
oappend(SET_F("d.rsvd=[6,7,8,9,10,11,24,28,29,30,31")); oappend(SET_F("d.rsvd=[6,7,8,9,10,11,24,28,29,30,31,37,38"));
#else #else
oappend(SET_F("d.rsvd=[6,7,8,9,10,11")); oappend(SET_F("d.rsvd=[6,7,8,9,10,11"));
#endif #endif