+
+
\ No newline at end of file
diff --git a/usermods/RelayBlinds/presets.json b/usermods/RelayBlinds/presets.json
new file mode 100644
index 0000000000..95b5871537
--- /dev/null
+++ b/usermods/RelayBlinds/presets.json
@@ -0,0 +1 @@
+{"0":{},"2":{"n":"▲","win":"U0=2"},"1":{"n":"▼","win":"U0=1"}}
\ No newline at end of file
diff --git a/usermods/RelayBlinds/readme.md b/usermods/RelayBlinds/readme.md
new file mode 100644
index 0000000000..0c3d2a0ba5
--- /dev/null
+++ b/usermods/RelayBlinds/readme.md
@@ -0,0 +1,8 @@
+# RelayBlinds usermod
+
+This simple usermod toggles two relay pins momentarily (default for 500ms) when `userVar0` is set.
+This can be used to e.g. "push" the buttons of a window blinds motor controller.
+
+v1 usermod. Please replace usermod.cpp in the `wled00` directory with the one in this file.
+You may upload `index.htm` to `[WLED-IP]/edit` to replace the default lighting UI with a simple Up/Down button one.
+Also, a simple `presets.json` file is available, this makes the relay actions controllable via two presets to facilitate control e.g. via the default UI or Alexa.
\ No newline at end of file
diff --git a/usermods/RelayBlinds/usermod.cpp b/usermods/RelayBlinds/usermod.cpp
new file mode 100644
index 0000000000..ee61b0cce3
--- /dev/null
+++ b/usermods/RelayBlinds/usermod.cpp
@@ -0,0 +1,83 @@
+#include "wled.h"
+
+//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
+
+//gets called once at boot. Do all initialization that doesn't depend on network here
+void userSetup()
+{
+
+}
+
+//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
+void userConnected()
+{
+
+}
+
+/*
+ * Physical IO
+ */
+#define PIN_UP_RELAY 4
+#define PIN_DN_RELAY 5
+#define PIN_ON_TIME 500
+bool upActive = false, upActiveBefore = false, downActive = false, downActiveBefore = false;
+unsigned long upStartTime = 0, downStartTime = 0;
+
+void handleRelay()
+{
+ //up and down relays
+ if (userVar0) {
+ upActive = true;
+ if (userVar0 == 1) {
+ upActive = false;
+ downActive = true;
+ }
+ userVar0 = 0;
+ }
+
+ if (upActive)
+ {
+ if(!upActiveBefore)
+ {
+ pinMode(PIN_UP_RELAY, OUTPUT);
+ digitalWrite(PIN_UP_RELAY, LOW);
+ upActiveBefore = true;
+ upStartTime = millis();
+ DEBUG_PRINTLN("UPA");
+ }
+ if (millis()- upStartTime > PIN_ON_TIME)
+ {
+ upActive = false;
+ DEBUG_PRINTLN("UPN");
+ }
+ } else if (upActiveBefore)
+ {
+ pinMode(PIN_UP_RELAY, INPUT);
+ upActiveBefore = false;
+ }
+
+ if (downActive)
+ {
+ if(!downActiveBefore)
+ {
+ pinMode(PIN_DN_RELAY, OUTPUT);
+ digitalWrite(PIN_DN_RELAY, LOW);
+ downActiveBefore = true;
+ downStartTime = millis();
+ }
+ if (millis()- downStartTime > PIN_ON_TIME)
+ {
+ downActive = false;
+ }
+ } else if (downActiveBefore)
+ {
+ pinMode(PIN_DN_RELAY, INPUT);
+ downActiveBefore = false;
+ }
+}
+
+//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
+void userLoop()
+{
+ handleRelay();
+}
\ No newline at end of file
diff --git a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h
index 3b785fada0..9c3be7cc28 100644
--- a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h
+++ b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h
@@ -22,12 +22,12 @@
// 10 bits
#ifndef USERMOD_SN_PHOTORESISTOR_ADC_PRECISION
-#define USERMOD_SN_PHOTORESISTOR_ADC_PRECISION 1024.0
+#define USERMOD_SN_PHOTORESISTOR_ADC_PRECISION 1024.0f
#endif
// resistor size 10K hms
#ifndef USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE
-#define USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE 10000.0
+#define USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE 10000.0f
#endif
// only report if differance grater than offset value
@@ -123,6 +123,11 @@ class Usermod_SN_Photoresistor : public Usermod
}
}
+ uint16_t getLastLDRValue()
+ {
+ return lastLDRValue;
+ }
+
void addToJsonInfo(JsonObject &root)
{
JsonObject user = root[F("u")];
diff --git a/usermods/ST7789_display/README.md b/usermods/ST7789_display/README.md
index 653fdd752b..b98d5be002 100644
--- a/usermods/ST7789_display/README.md
+++ b/usermods/ST7789_display/README.md
@@ -12,7 +12,7 @@ This usermod allow to use 240x240 display to display following:
## Hardware
***
-![Hardware](images/ST7789_guide.jpg)
+![Hardware](images/ST7789_Guide.jpg)
## Library used
diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h
index bd501d08f0..19ad5790ae 100644
--- a/usermods/ST7789_display/ST7789_display.h
+++ b/usermods/ST7789_display/ST7789_display.h
@@ -118,11 +118,11 @@ class St7789DisplayUsermod : public Usermod {
{
needRedraw = true;
}
- else if (knownMode != strip.getMode())
+ else if (knownMode != strip.getMainSegment().mode)
{
needRedraw = true;
}
- else if (knownPalette != strip.getSegment(0).palette)
+ else if (knownPalette != strip.getMainSegment().palette)
{
needRedraw = true;
}
@@ -148,8 +148,8 @@ class St7789DisplayUsermod : public Usermod {
#endif
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
knownBrightness = bri;
- knownMode = strip.getMode();
- knownPalette = strip.getSegment(0).palette;
+ knownMode = strip.getMainSegment().mode;
+ knownPalette = strip.getMainSegment().palette;
tft.fillScreen(TFT_BLACK);
tft.setTextSize(2);
diff --git a/usermods/TTGO-T-Display/usermod.cpp b/usermods/TTGO-T-Display/usermod.cpp
index 75e90b1ebe..9e08a001a1 100644
--- a/usermods/TTGO-T-Display/usermod.cpp
+++ b/usermods/TTGO-T-Display/usermod.cpp
@@ -110,9 +110,9 @@ void userLoop() {
needRedraw = true;
} else if (knownBrightness != bri) {
needRedraw = true;
- } else if (knownMode != strip.getMode()) {
+ } else if (knownMode != strip.getMainSegment().mode) {
needRedraw = true;
- } else if (knownPalette != strip.getSegment(0).palette) {
+ } else if (knownPalette != strip.getMainSegment().palette) {
needRedraw = true;
}
@@ -136,8 +136,8 @@ void userLoop() {
#endif
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
knownBrightness = bri;
- knownMode = strip.getMode();
- knownPalette = strip.getSegment(0).palette;
+ knownMode = strip.getMainSegment().mode;
+ knownPalette = strip.getMainSegment().palette;
tft.fillScreen(TFT_BLACK);
tft.setTextSize(2);
diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h
index 7c209f4754..40df0e5333 100644
--- a/usermods/Temperature/usermod_temperature.h
+++ b/usermods/Temperature/usermod_temperature.h
@@ -37,12 +37,12 @@ class UsermodTemperature : public Usermod {
// used to determine when we can read the sensors temperature
// we have to wait at least 93.75 ms after requestTemperatures() is called
unsigned long lastTemperaturesRequest;
- float temperature = -100; // default to -100, DS18B20 only goes down to -50C
+ float temperature;
// indicates requestTemperatures has been called but the sensor measurement is not complete
bool waitingForConversion = false;
// flag set at startup if DS18B20 sensor not found, avoids trying to keep getting
// temperature if flashed to a board without a sensor attached
- bool sensorFound = false;
+ byte sensorFound;
bool enabled = true;
@@ -54,27 +54,47 @@ class UsermodTemperature : public Usermod {
//Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013
float readDallas() {
- byte i;
- byte data[2];
+ byte data[9];
int16_t result; // raw data from sensor
- if (!oneWire->reset()) return -127.0f; // send reset command and fail fast
- oneWire->skip(); // skip ROM
- oneWire->write(0xBE); // read (temperature) from EEPROM
- for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature
- for (i=2; i < 8; i++) oneWire->read(); // read unused bytes
- result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning
- if (data[1]&0x80) result |= 0xFF00; // fix negative value
- oneWire->reset();
- oneWire->skip(); // skip ROM
- oneWire->write(0x44,parasite); // request new temperature reading (without parasite power)
- return (float)result + ((data[0]&0x0008) ? 0.5f : 0.0f);
+ float retVal = -127.0f;
+ if (oneWire->reset()) { // if reset() fails there are no OneWire devices
+ oneWire->skip(); // skip ROM
+ oneWire->write(0xBE); // read (temperature) from EEPROM
+ oneWire->read_bytes(data, 9); // first 2 bytes contain temperature
+ #ifdef WLED_DEBUG
+ if (OneWire::crc8(data,8) != data[8]) {
+ DEBUG_PRINTLN(F("CRC error reading temperature."));
+ for (byte i=0; i < 9; i++) DEBUG_PRINTF("0x%02X ", data[i]);
+ DEBUG_PRINT(F(" => "));
+ DEBUG_PRINTF("0x%02X\n", OneWire::crc8(data,8));
+ }
+ #endif
+ switch(sensorFound) {
+ case 0x10: // DS18S20 has 9-bit precision
+ result = (data[1] << 8) | data[0];
+ retVal = float(result) * 0.5f;
+ break;
+ case 0x22: // DS18B20
+ case 0x28: // DS1822
+ case 0x3B: // DS1825
+ case 0x42: // DS28EA00
+ result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning
+ if (data[1] & 0x80) result |= 0xF000; // fix negative value
+ retVal = float(result) + ((data[0] & 0x08) ? 0.5f : 0.0f);
+ break;
+ }
+ }
+ for (byte i=1; i<9; i++) data[0] &= data[i];
+ return data[0]==0xFF ? -127.0f : retVal;
}
void requestTemperatures() {
- readDallas();
+ DEBUG_PRINTLN(F("Requesting temperature."));
+ oneWire->reset();
+ oneWire->skip(); // skip ROM
+ oneWire->write(0x44,parasite); // request new temperature reading (TODO: parasite would need special handling)
lastTemperaturesRequest = millis();
waitingForConversion = true;
- DEBUG_PRINTLN(F("Requested temperature."));
}
void readTemperature() {
@@ -102,10 +122,13 @@ class UsermodTemperature : public Usermod {
case 0x3B: // DS1825
case 0x42: // DS28EA00
DEBUG_PRINTLN(F("Sensor found."));
+ sensorFound = deviceAddress[0];
+ DEBUG_PRINTF("0x%02X\n", sensorFound);
return true;
}
}
}
+ DEBUG_PRINTLN(F("Sensor NOT found."));
return false;
}
@@ -113,16 +136,16 @@ class UsermodTemperature : public Usermod {
void setup() {
int retries = 10;
+ sensorFound = 0;
+ temperature = -127.0f; // default to -127, DS18B20 only goes down to -50C
if (enabled) {
// config says we are enabled
DEBUG_PRINTLN(F("Allocating temperature pin..."));
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) {
oneWire = new OneWire(temperaturePin);
- if (!oneWire->reset()) {
- sensorFound = false; // resetting 1-Wire bus yielded an error
- } else {
- while ((sensorFound=findSensor()) && retries--) {
+ if (oneWire->reset()) {
+ while (!findSensor() && retries--) {
delay(25); // try to find sensor
}
}
@@ -131,7 +154,6 @@ class UsermodTemperature : public Usermod {
DEBUG_PRINTLN(F("Temperature pin allocation failed."));
}
temperaturePin = -1; // allocation failed
- sensorFound = false;
}
}
lastMeasurement = millis() - readingInterval + 10000;
@@ -139,8 +161,9 @@ class UsermodTemperature : public Usermod {
}
void loop() {
- if (!enabled || strip.isUpdating()) return;
+ if (!enabled || !sensorFound || strip.isUpdating()) return;
+ static uint8_t errorCount = 0;
unsigned long now = millis();
// check to see if we are due for taking a measurement
@@ -156,20 +179,26 @@ class UsermodTemperature : public Usermod {
}
// we were waiting for a conversion to complete, have we waited log enough?
- if (now - lastTemperaturesRequest >= 100 /* 93.75ms per the datasheet but can be up to 750ms */) {
+ if (now - lastTemperaturesRequest >= 750 /* 93.75ms per the datasheet but can be up to 750ms */) {
readTemperature();
+ if (getTemperatureC() < -100.0f) {
+ if (++errorCount > 10) sensorFound = 0;
+ lastMeasurement = now - readingInterval + 300; // force new measurement in 300ms
+ return;
+ }
+ errorCount = 0;
if (WLED_MQTT_CONNECTED) {
char subuf[64];
strcpy(subuf, mqttDeviceTopic);
- if (-100 <= temperature) {
+ if (temperature > -100.0f) {
// dont publish super low temperature as the graph will get messed up
// the DallasTemperature library returns -127C or -196.6F when problem
// reading the sensor
strcat_P(subuf, PSTR("/temperature"));
- mqtt->publish(subuf, 0, false, String(temperature).c_str());
+ mqtt->publish(subuf, 0, false, String(getTemperatureC()).c_str());
strcat_P(subuf, PSTR("_f"));
- mqtt->publish(subuf, 0, false, String((float)temperature * 1.8f + 32).c_str());
+ mqtt->publish(subuf, 0, false, String(getTemperatureF()).c_str());
} else {
// publish something else to indicate status?
}
@@ -202,13 +231,13 @@ class UsermodTemperature : public Usermod {
JsonArray temp = user.createNestedArray(FPSTR(_name));
//temp.add(F("Loaded."));
- if (temperature <= -100.0 || (!sensorFound && temperature == -1.0)) {
+ if (temperature <= -100.0f) {
temp.add(0);
temp.add(F(" Sensor Error!"));
return;
}
- temp.add(degC ? temperature : (float)temperature * 1.8f + 32);
+ temp.add(degC ? getTemperatureC() : getTemperatureF());
if (degC) temp.add(F("°C"));
else temp.add(F("°F"));
}
@@ -252,23 +281,21 @@ class UsermodTemperature : public Usermod {
bool readFromConfig(JsonObject &root) {
// we look for JSON object: {"Temperature": {"pin": 0, "degC": true}}
int8_t newTemperaturePin = temperaturePin;
+ DEBUG_PRINT(FPSTR(_name));
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
- DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
enabled = top[FPSTR(_enabled)] | enabled;
newTemperaturePin = top["pin"] | newTemperaturePin;
-// newTemperaturePin = min(33,max(-1,(int)newTemperaturePin)); // bounds check
degC = top["degC"] | degC;
readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000;
readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms
parasite = top[FPSTR(_parasite)] | parasite;
- DEBUG_PRINT(FPSTR(_name));
if (!initDone) {
// first run: reading from cfg.json
temperaturePin = newTemperaturePin;
diff --git a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h
index 83f26e084d..210ec3f585 100644
--- a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h
+++ b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h
@@ -21,6 +21,14 @@
#include
#include
+#ifdef ARDUINO_ARCH_ESP32
+ #define HW_PIN_SCL 22
+ #define HW_PIN_SDA 21
+#else
+ #define HW_PIN_SCL 5
+ #define HW_PIN_SDA 4
+#endif
+
#ifndef VL53L0X_MAX_RANGE_MM
#define VL53L0X_MAX_RANGE_MM 230 // max height in millimiters to react for motions
#endif
@@ -42,6 +50,7 @@ class UsermodVL53L0XGestures : public Usermod {
//Private class members. You can declare variables and functions only accessible to your usermod here
unsigned long lastTime = 0;
VL53L0X sensor;
+ bool enabled = true;
bool wasMotionBefore = false;
bool isLongMotion = false;
@@ -50,6 +59,8 @@ class UsermodVL53L0XGestures : public Usermod {
public:
void setup() {
+ PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } };
+ if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
Wire.begin();
sensor.setTimeout(150);
@@ -63,6 +74,7 @@ class UsermodVL53L0XGestures : public Usermod {
void loop() {
+ if (!enabled || strip.isUpdating()) return;
if (millis() - lastTime > VL53L0X_DELAY_MS)
{
lastTime = millis();
@@ -94,7 +106,7 @@ class UsermodVL53L0XGestures : public Usermod {
// set brightness according to range
bri = (VL53L0X_MAX_RANGE_MM - max(range, VL53L0X_MIN_RANGE_OFFSET)) * 255 / (VL53L0X_MAX_RANGE_MM - VL53L0X_MIN_RANGE_OFFSET);
DEBUG_PRINTF(F("new brightness: %d"), bri);
- colorUpdated(1);
+ stateUpdated(1);
}
} else if (wasMotionBefore) { //released
long dur = millis() - motionStartTime;
@@ -110,6 +122,19 @@ class UsermodVL53L0XGestures : public Usermod {
}
}
+ /*
+ * 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)
+ * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
+ */
+ void addToConfig(JsonObject& root)
+ {
+ JsonObject top = root.createNestedObject("VL53L0x");
+ JsonArray pins = top.createNestedArray("pin");
+ pins.add(HW_PIN_SCL);
+ pins.add(HW_PIN_SDA);
+ }
+
/*
* 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.
diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp
index a93b20c994..79241b5e20 100644
--- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp
+++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp
@@ -137,9 +137,9 @@ void userLoop() {
needRedraw = true;
} else if (knownBrightness != bri) {
needRedraw = true;
- } else if (knownMode != strip.getMode()) {
+ } else if (knownMode != strip.getMainSegment().mode) {
needRedraw = true;
- } else if (knownPalette != strip.getSegment(0).palette) {
+ } else if (knownPalette != strip.getMainSegment().palette) {
needRedraw = true;
}
@@ -163,8 +163,8 @@ void userLoop() {
#endif
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
knownBrightness = bri;
- knownMode = strip.getMode();
- knownPalette = strip.getSegment(0).palette;
+ knownMode = strip.getMainSegment().mode;
+ knownPalette = strip.getMainSegment().palette;
u8x8.clear();
u8x8.setFont(u8x8_font_chroma48medium8_r);
diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp
index 15ec58add1..fe53a46283 100644
--- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp
+++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp
@@ -143,9 +143,9 @@ void userLoop() {
needRedraw = true;
} else if (knownBrightness != bri) {
needRedraw = true;
- } else if (knownMode != strip.getMode()) {
+ } else if (knownMode != strip.getMainSegment().mode) {
needRedraw = true;
- } else if (knownPalette != strip.getSegment(0).palette) {
+ } else if (knownPalette != strip.getMainSegment().palette) {
needRedraw = true;
}
@@ -169,8 +169,8 @@ void userLoop() {
#endif
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
knownBrightness = bri;
- knownMode = strip.getMode();
- knownPalette = strip.getSegment(0).palette;
+ knownMode = strip.getMainSegment().mode;
+ knownPalette = strip.getMainSegment().palette;
u8x8.clear();
u8x8.setFont(u8x8_font_chroma48medium8_r);
diff --git a/usermods/battery_keypad_controller/wled06_usermod.ino b/usermods/battery_keypad_controller/wled06_usermod.ino
index acc1bd8c44..b70682b438 100644
--- a/usermods/battery_keypad_controller/wled06_usermod.ino
+++ b/usermods/battery_keypad_controller/wled06_usermod.ino
@@ -54,35 +54,27 @@ void userLoop()
switch (myKey) {
case '1':
applyPreset(1);
- colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '2':
applyPreset(2);
- colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '3':
applyPreset(3);
- colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '4':
applyPreset(4);
- colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '5':
applyPreset(5);
- colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '6':
applyPreset(6);
- colorUpdated(CALL_MODE_FX_CHANGED);
break;
case 'A':
applyPreset(7);
- colorUpdated(CALL_MODE_FX_CHANGED);
break;
case 'B':
applyPreset(8);
- colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '7':
diff --git a/usermods/battery_status_basic/usermod_v2_battery_status_basic.h b/usermods/battery_status_basic/usermod_v2_battery_status_basic.h
index ab9cba3bc3..cb3c0867ce 100644
--- a/usermods/battery_status_basic/usermod_v2_battery_status_basic.h
+++ b/usermods/battery_status_basic/usermod_v2_battery_status_basic.h
@@ -21,10 +21,10 @@
#ifndef USERMOD_BATTERY_ADC_PRECISION
#ifdef ARDUINO_ARCH_ESP32
// 12 bits
- #define USERMOD_BATTERY_ADC_PRECISION 4095.0
+ #define USERMOD_BATTERY_ADC_PRECISION 4095.0f
#else
// 10 bits
- #define USERMOD_BATTERY_ADC_PRECISION 1024.0
+ #define USERMOD_BATTERY_ADC_PRECISION 1024.0f
#endif
#endif
@@ -39,11 +39,11 @@
// https://batterybro.com/blogs/18650-wholesale-battery-reviews/18852515-when-to-recycle-18650-batteries-and-how-to-start-a-collection-center-in-your-vape-shop
// Discharge voltage: 2.5 volt + .1 for personal safety
#ifndef USERMOD_BATTERY_MIN_VOLTAGE
- #define USERMOD_BATTERY_MIN_VOLTAGE 2.6
+ #define USERMOD_BATTERY_MIN_VOLTAGE 2.6f
#endif
#ifndef USERMOD_BATTERY_MAX_VOLTAGE
- #define USERMOD_BATTERY_MAX_VOLTAGE 4.2
+ #define USERMOD_BATTERY_MAX_VOLTAGE 4.2f
#endif
class UsermodBatteryBasic : public Usermod
diff --git a/usermods/esp32_multistrip/NpbWrapper.h b/usermods/esp32_multistrip/NpbWrapper.h
deleted file mode 100644
index 84cf8ac0d0..0000000000
--- a/usermods/esp32_multistrip/NpbWrapper.h
+++ /dev/null
@@ -1,515 +0,0 @@
-//this code is a modified version of https://github.com/Makuna/NeoPixelBus/issues/103
-#ifndef NpbWrapper_h
-#define NpbWrapper_h
-
-// make sure we're using esp32 platform
-#ifndef ARDUINO_ARCH_ESP32
- #error This version of NbpWrapper.h only works with ESP32 hardware.
-#endif
-
-#ifndef NUM_STRIPS
- #error Need to define number of LED strips using build flag -D NUM_STRIPS=4 for 4 LED strips
-#endif
-
-#ifndef PIXEL_COUNTS
- #error Need to define pixel counts using build flag -D PIXEL_COUNTS="25, 25, 25, 25" for 4 LED strips with 25 LEDs each
-#endif
-
-#ifndef DATA_PINS
- #error Need to define data pins using build flag -D DATA_PINS="1, 2, 3, 4" if LED strips are on data pins 1, 2, 3, and 4
-#endif
-
-// //PIN CONFIGURATION
-#ifndef LEDPIN
- #define LEDPIN 1 // Legacy pin def required by some other portions of code. This pin is not used do drive LEDs.
-#endif
-
-#ifndef IRPIN
- #define IRPIN -1 //infrared pin (-1 to disable) MagicHome: 4, H801 Wifi: 0
-#endif
-
-#ifndef RLYPIN
- #define RLYPIN -1 //pin for relay, will be set HIGH if LEDs are on (-1 to disable). Also usable for standby leds, triggers,...
-#endif
-
-#ifndef AUXPIN
- #define AUXPIN -1 //debug auxiliary output pin (-1 to disable)
-#endif
-
-#ifndef RLYMDE
- #define RLYMDE 1 //mode for relay, 0: LOW if LEDs are on 1: HIGH if LEDs are on
-#endif
-
-#include
-#include "const.h"
-
-const uint8_t numStrips = NUM_STRIPS; // max 8 strips allowed on esp32
-const uint16_t pixelCounts[numStrips] = {PIXEL_COUNTS}; // number of pixels on each strip
-const uint8_t dataPins[numStrips] = {DATA_PINS}; // change these pins based on your board
-
-#define PIXELFEATURE3 NeoGrbFeature
-#define PIXELFEATURE4 NeoGrbwFeature
-
-// ESP32 has 8 RMT interfaces available, each of which can drive a strip of pixels
-// Convenience #defines for creating NeoPixelBrightnessBus on each RMT interface for both GRB and GRBW LED strips
-#define NeoPixelBrightnessBusGrbRmt0 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbRmt1 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbRmt2 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbRmt3 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbRmt4 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbRmt5 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbRmt6 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbRmt7 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbwRmt0 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbwRmt1 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbwRmt2 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbwRmt3 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbwRmt4 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbwRmt5 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbwRmt6 NeoPixelBrightnessBus
-#define NeoPixelBrightnessBusGrbwRmt7 NeoPixelBrightnessBus
-
-enum NeoPixelType
-{
- NeoPixelType_None = 0,
- NeoPixelType_Grb = 1,
- NeoPixelType_Grbw = 2,
- NeoPixelType_End = 3
-};
-
-class NeoPixelWrapper
-{
-public:
- NeoPixelWrapper() :
- _type(NeoPixelType_None)
- {
- // On initialization fill in the pixelStripStartIdx array with the beginning index of each strip
- // relative to th entire array.
- uint16_t totalPixels = 0;
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- pixelStripStartIdx[idx] = totalPixels;
- totalPixels += pixelCounts[idx];
- }
- }
-
- ~NeoPixelWrapper()
- {
- cleanup();
- }
-
- void Begin(NeoPixelType type, uint16_t pixelCount)
- {
-
- cleanup();
-
- _type = type;
-
- switch (_type)
- {
- case NeoPixelType_Grb:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: pGrb0 = new NeoPixelBrightnessBusGrbRmt0(pixelCounts[idx], dataPins[idx]); pGrb0->Begin(); break;
- case 1: pGrb1 = new NeoPixelBrightnessBusGrbRmt1(pixelCounts[idx], dataPins[idx]); pGrb1->Begin(); break;
- case 2: pGrb2 = new NeoPixelBrightnessBusGrbRmt2(pixelCounts[idx], dataPins[idx]); pGrb2->Begin(); break;
- case 3: pGrb3 = new NeoPixelBrightnessBusGrbRmt3(pixelCounts[idx], dataPins[idx]); pGrb3->Begin(); break;
- case 4: pGrb4 = new NeoPixelBrightnessBusGrbRmt4(pixelCounts[idx], dataPins[idx]); pGrb4->Begin(); break;
- case 5: pGrb5 = new NeoPixelBrightnessBusGrbRmt5(pixelCounts[idx], dataPins[idx]); pGrb5->Begin(); break;
- case 6: pGrb6 = new NeoPixelBrightnessBusGrbRmt6(pixelCounts[idx], dataPins[idx]); pGrb6->Begin(); break;
- case 7: pGrb7 = new NeoPixelBrightnessBusGrbRmt7(pixelCounts[idx], dataPins[idx]); pGrb7->Begin(); break;
- }
- }
- break;
- }
-
- case NeoPixelType_Grbw:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: pGrbw0 = new NeoPixelBrightnessBusGrbwRmt0(pixelCounts[idx], dataPins[idx]); pGrbw0->Begin(); break;
- case 1: pGrbw1 = new NeoPixelBrightnessBusGrbwRmt1(pixelCounts[idx], dataPins[idx]); pGrbw1->Begin(); break;
- case 2: pGrbw2 = new NeoPixelBrightnessBusGrbwRmt2(pixelCounts[idx], dataPins[idx]); pGrbw2->Begin(); break;
- case 3: pGrbw3 = new NeoPixelBrightnessBusGrbwRmt3(pixelCounts[idx], dataPins[idx]); pGrbw3->Begin(); break;
- case 4: pGrbw4 = new NeoPixelBrightnessBusGrbwRmt4(pixelCounts[idx], dataPins[idx]); pGrbw4->Begin(); break;
- case 5: pGrbw5 = new NeoPixelBrightnessBusGrbwRmt5(pixelCounts[idx], dataPins[idx]); pGrbw5->Begin(); break;
- case 6: pGrbw6 = new NeoPixelBrightnessBusGrbwRmt6(pixelCounts[idx], dataPins[idx]); pGrbw6->Begin(); break;
- case 7: pGrbw7 = new NeoPixelBrightnessBusGrbwRmt7(pixelCounts[idx], dataPins[idx]); pGrbw7->Begin(); break;
- }
- }
- break;
- }
- }
- }
-
- void Show()
- {
- switch (_type)
- {
- case NeoPixelType_Grb:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: pGrb0->Show(); break;
- case 1: pGrb1->Show(); break;
- case 2: pGrb2->Show(); break;
- case 3: pGrb3->Show(); break;
- case 4: pGrb4->Show(); break;
- case 5: pGrb5->Show(); break;
- case 6: pGrb6->Show(); break;
- case 7: pGrb7->Show(); break;
- }
- }
- break;
- }
- case NeoPixelType_Grbw:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: pGrbw0->Show(); break;
- case 1: pGrbw1->Show(); break;
- case 2: pGrbw2->Show(); break;
- case 3: pGrbw3->Show(); break;
- case 4: pGrbw4->Show(); break;
- case 5: pGrbw5->Show(); break;
- case 6: pGrbw6->Show(); break;
- case 7: pGrbw7->Show(); break;
- }
- }
- break;
- }
- }
- }
-
- bool CanShow()
- {
- bool canShow = true;
- switch (_type)
- {
- case NeoPixelType_Grb:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: canShow &= pGrb0->CanShow(); break;
- case 1: canShow &= pGrb1->CanShow(); break;
- case 2: canShow &= pGrb2->CanShow(); break;
- case 3: canShow &= pGrb3->CanShow(); break;
- case 4: canShow &= pGrb4->CanShow(); break;
- case 5: canShow &= pGrb5->CanShow(); break;
- case 6: canShow &= pGrb6->CanShow(); break;
- case 7: canShow &= pGrb7->CanShow(); break;
- }
- }
- break;
- }
- case NeoPixelType_Grbw:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: canShow &= pGrbw0->CanShow(); break;
- case 1: canShow &= pGrbw1->CanShow(); break;
- case 2: canShow &= pGrbw2->CanShow(); break;
- case 3: canShow &= pGrbw3->CanShow(); break;
- case 4: canShow &= pGrbw4->CanShow(); break;
- case 5: canShow &= pGrbw5->CanShow(); break;
- case 6: canShow &= pGrbw6->CanShow(); break;
- case 7: canShow &= pGrbw7->CanShow(); break;
- }
- }
- break;
- }
- }
- return canShow;
- }
-
- void SetPixelColorRaw(uint16_t indexPixel, RgbwColor c)
- {
- // figure out which strip this pixel index is on
- uint8_t stripIdx = 0;
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- if (indexPixel >= pixelStripStartIdx[idx])
- {
- stripIdx = idx;
- }
- else
- {
- break;
- }
- }
- // subtract strip start index so we're addressing just this strip instead of all pixels on all strips
- indexPixel -= pixelStripStartIdx[stripIdx];
- switch (_type)
- {
- case NeoPixelType_Grb:
- {
- RgbColor rgb = RgbColor(c.R, c.G, c.B);
- switch (stripIdx)
- {
- case 0: pGrb0->SetPixelColor(indexPixel, rgb); break;
- case 1: pGrb1->SetPixelColor(indexPixel, rgb); break;
- case 2: pGrb2->SetPixelColor(indexPixel, rgb); break;
- case 3: pGrb3->SetPixelColor(indexPixel, rgb); break;
- case 4: pGrb4->SetPixelColor(indexPixel, rgb); break;
- case 5: pGrb5->SetPixelColor(indexPixel, rgb); break;
- case 6: pGrb6->SetPixelColor(indexPixel, rgb); break;
- case 7: pGrb7->SetPixelColor(indexPixel, rgb); break;
- }
- break;
- }
- case NeoPixelType_Grbw:
- {
- switch (stripIdx)
- {
- case 0: pGrbw0->SetPixelColor(indexPixel, c); break;
- case 1: pGrbw1->SetPixelColor(indexPixel, c); break;
- case 2: pGrbw2->SetPixelColor(indexPixel, c); break;
- case 3: pGrbw3->SetPixelColor(indexPixel, c); break;
- case 4: pGrbw4->SetPixelColor(indexPixel, c); break;
- case 5: pGrbw5->SetPixelColor(indexPixel, c); break;
- case 6: pGrbw6->SetPixelColor(indexPixel, c); break;
- case 7: pGrbw7->SetPixelColor(indexPixel, c); break;
- }
- break;
- }
- }
- }
-
- void SetPixelColor(uint16_t indexPixel, RgbwColor c)
- {
- /*
- Set pixel color with necessary color order conversion.
- */
-
- RgbwColor col;
-
- uint8_t co = _colorOrder;
- #ifdef COLOR_ORDER_OVERRIDE
- if (indexPixel >= COO_MIN && indexPixel < COO_MAX) co = COO_ORDER;
- #endif
-
- //reorder channels to selected order
- switch (co)
- {
- case 0: col.G = c.G; col.R = c.R; col.B = c.B; break; //0 = GRB, default
- case 1: col.G = c.R; col.R = c.G; col.B = c.B; break; //1 = RGB, common for WS2811
- case 2: col.G = c.B; col.R = c.R; col.B = c.G; break; //2 = BRG
- case 3: col.G = c.R; col.R = c.B; col.B = c.G; break; //3 = RBG
- case 4: col.G = c.B; col.R = c.G; col.B = c.R; break; //4 = BGR
- default: col.G = c.G; col.R = c.B; col.B = c.R; break; //5 = GBR
- }
- col.W = c.W;
-
- SetPixelColorRaw(indexPixel, col);
- }
-
- void SetBrightness(byte b)
- {
- switch (_type)
- {
- case NeoPixelType_Grb:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: pGrb0->SetBrightness(b); break;
- case 1: pGrb1->SetBrightness(b); break;
- case 2: pGrb2->SetBrightness(b); break;
- case 3: pGrb3->SetBrightness(b); break;
- case 4: pGrb4->SetBrightness(b); break;
- case 5: pGrb5->SetBrightness(b); break;
- case 6: pGrb6->SetBrightness(b); break;
- case 7: pGrb7->SetBrightness(b); break;
- }
- }
- break;
- }
- case NeoPixelType_Grbw:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: pGrbw0->SetBrightness(b); break;
- case 1: pGrbw1->SetBrightness(b); break;
- case 2: pGrbw2->SetBrightness(b); break;
- case 3: pGrbw3->SetBrightness(b); break;
- case 4: pGrbw4->SetBrightness(b); break;
- case 5: pGrbw5->SetBrightness(b); break;
- case 6: pGrbw6->SetBrightness(b); break;
- case 7: pGrbw7->SetBrightness(b); break;
- }
- }
- break;
- }
- }
- }
-
- void SetColorOrder(byte colorOrder)
- {
- _colorOrder = colorOrder;
- }
-
- uint8_t GetColorOrder()
- {
- return _colorOrder;
- }
-
- RgbwColor GetPixelColorRaw(uint16_t indexPixel) const
- {
- // figure out which strip this pixel index is on
- uint8_t stripIdx = 0;
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- if (indexPixel >= pixelStripStartIdx[idx])
- {
- stripIdx = idx;
- }
- else
- {
- break;
- }
- }
- // subtract strip start index so we're addressing just this strip instead of all pixels on all strips
- indexPixel -= pixelStripStartIdx[stripIdx];
- switch (_type)
- {
- case NeoPixelType_Grb:
- {
- switch (stripIdx)
- {
- case 0: return pGrb0->GetPixelColor(indexPixel);
- case 1: return pGrb1->GetPixelColor(indexPixel);
- case 2: return pGrb2->GetPixelColor(indexPixel);
- case 3: return pGrb3->GetPixelColor(indexPixel);
- case 4: return pGrb4->GetPixelColor(indexPixel);
- case 5: return pGrb5->GetPixelColor(indexPixel);
- case 6: return pGrb6->GetPixelColor(indexPixel);
- case 7: return pGrb7->GetPixelColor(indexPixel);
- }
- break;
- }
- case NeoPixelType_Grbw:
- switch (stripIdx)
- {
- case 0: return pGrbw0->GetPixelColor(indexPixel);
- case 1: return pGrbw1->GetPixelColor(indexPixel);
- case 2: return pGrbw2->GetPixelColor(indexPixel);
- case 3: return pGrbw3->GetPixelColor(indexPixel);
- case 4: return pGrbw4->GetPixelColor(indexPixel);
- case 5: return pGrbw5->GetPixelColor(indexPixel);
- case 6: return pGrbw6->GetPixelColor(indexPixel);
- case 7: return pGrbw7->GetPixelColor(indexPixel);
- }
- break;
- }
- return 0;
- }
-
- // NOTE: Due to feature differences, some support RGBW but the method name
- // here needs to be unique, thus GetPixeColorRgbw
- uint32_t GetPixelColorRgbw(uint16_t indexPixel) const
- {
- RgbwColor col = GetPixelColorRaw(indexPixel);
- uint8_t co = _colorOrder;
- #ifdef COLOR_ORDER_OVERRIDE
- if (indexPixel >= COO_MIN && indexPixel < COO_MAX) co = COO_ORDER;
- #endif
-
- switch (co)
- {
- // W G R B
- case 0: return ((col.W << 24) | (col.G << 8) | (col.R << 16) | (col.B)); //0 = GRB, default
- case 1: return ((col.W << 24) | (col.R << 8) | (col.G << 16) | (col.B)); //1 = RGB, common for WS2811
- case 2: return ((col.W << 24) | (col.B << 8) | (col.R << 16) | (col.G)); //2 = BRG
- case 3: return ((col.W << 24) | (col.B << 8) | (col.G << 16) | (col.R)); //3 = RBG
- case 4: return ((col.W << 24) | (col.R << 8) | (col.B << 16) | (col.G)); //4 = BGR
- case 5: return ((col.W << 24) | (col.G << 8) | (col.B << 16) | (col.R)); //5 = GBR
- }
-
- return 0;
-
- }
-
-
-private:
- NeoPixelType _type;
- byte _colorOrder = 0;
-
- uint16_t pixelStripStartIdx[numStrips];
-
- // pointers for every possible type for up to 8 strips
- NeoPixelBrightnessBusGrbRmt0 *pGrb0;
- NeoPixelBrightnessBusGrbRmt1 *pGrb1;
- NeoPixelBrightnessBusGrbRmt2 *pGrb2;
- NeoPixelBrightnessBusGrbRmt3 *pGrb3;
- NeoPixelBrightnessBusGrbRmt4 *pGrb4;
- NeoPixelBrightnessBusGrbRmt5 *pGrb5;
- NeoPixelBrightnessBusGrbRmt6 *pGrb6;
- NeoPixelBrightnessBusGrbRmt7 *pGrb7;
- NeoPixelBrightnessBusGrbwRmt0 *pGrbw0;
- NeoPixelBrightnessBusGrbwRmt1 *pGrbw1;
- NeoPixelBrightnessBusGrbwRmt2 *pGrbw2;
- NeoPixelBrightnessBusGrbwRmt3 *pGrbw3;
- NeoPixelBrightnessBusGrbwRmt4 *pGrbw4;
- NeoPixelBrightnessBusGrbwRmt5 *pGrbw5;
- NeoPixelBrightnessBusGrbwRmt6 *pGrbw6;
- NeoPixelBrightnessBusGrbwRmt7 *pGrbw7;
-
- void cleanup()
- {
- switch (_type)
- {
- case NeoPixelType_Grb:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: delete pGrb0; pGrb0 = NULL; break;
- case 1: delete pGrb1; pGrb1 = NULL; break;
- case 2: delete pGrb2; pGrb2 = NULL; break;
- case 3: delete pGrb3; pGrb3 = NULL; break;
- case 4: delete pGrb4; pGrb4 = NULL; break;
- case 5: delete pGrb5; pGrb5 = NULL; break;
- case 6: delete pGrb6; pGrb6 = NULL; break;
- case 7: delete pGrb7; pGrb7 = NULL; break;
- }
- }
- break;
- }
- case NeoPixelType_Grbw:
- {
- for (uint8_t idx = 0; idx < numStrips; idx++)
- {
- switch (idx)
- {
- case 0: delete pGrbw0; pGrbw0 = NULL; break;
- case 1: delete pGrbw1; pGrbw1 = NULL; break;
- case 2: delete pGrbw2; pGrbw2 = NULL; break;
- case 3: delete pGrbw3; pGrbw3 = NULL; break;
- case 4: delete pGrbw4; pGrbw4 = NULL; break;
- case 5: delete pGrbw5; pGrbw5 = NULL; break;
- case 6: delete pGrbw6; pGrbw6 = NULL; break;
- case 7: delete pGrbw7; pGrbw7 = NULL; break;
- }
- }
- }
- }
- }
-};
-#endif
diff --git a/usermods/esp32_multistrip/README.md b/usermods/esp32_multistrip/README.md
deleted file mode 100644
index 87b895284b..0000000000
--- a/usermods/esp32_multistrip/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# esp32_multistrip
-
-This usermod enables up to 8 data pins to be used from an esp32 module to drive separate LED strands. This only works with one-wire LEDs like the WS2812.
-
-The esp32 RMT hardware is used for data output. See here for hardware driver implementation details: https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
-
-Pass the following variables to the compiler as build flags:
-
- - `ESP32_MULTISTRIP`
- - Define this to use usermod NpbWrapper.h instead of default one in WLED.
- - `NUM_STRIPS`
- - Number of strips in use
- - `PIXEL_COUNTS`
- - List of pixel counts in each strip
- - `DATA_PINS`
- - List of data pins each strip is attached to. There may be board-specific restrictions on which pins can be used for RTM.
-
-From the perspective of WLED software, the LEDs are addressed as one long strand. The modified NbpWrapper.h file addresses the appropriate strand from the overall LED index based on the number of LEDs defined in each strand.
-
-See `platformio_override.ini` for example configuration.
-
-Tested on low cost ESP-WROOM-32 dev boards from Amazon, such as those sold by KeeYees.
diff --git a/usermods/esp32_multistrip/platformio_override.ini b/usermods/esp32_multistrip/platformio_override.ini
deleted file mode 100644
index afdef67667..0000000000
--- a/usermods/esp32_multistrip/platformio_override.ini
+++ /dev/null
@@ -1,16 +0,0 @@
-; Example platformio_override.ini that shows how to configure your environment to use the multistrip usermod.
-; Copy this file to the base wled directory that contains platformio.ini.
-; Multistrip requires ESP32 because it has many more pins that can be used as LED outputs.
-; Need to define NUM_STRIPS, PIXEL_COUNTS, and DATA_PINS as shown below.
-
-[platformio]
-default_envs = esp32_multistrip
-
-[env:esp32_multistrip]
-extends=env:esp32dev
-build_flags = ${env:esp32dev.build_flags}
- -D ESP32_MULTISTRIP ; define this variable to use ESP32_MULTISTRIP usermod
- -D NUM_STRIPS=4 ; number of pixel strips in use
- -D PIXEL_COUNTS="50, 50, 50, 50" ; number of pixels in each strip
- -D DATA_PINS="25, 26, 32, 33" ; esp32 pins used for each pixel strip. available pins depends on esp32 module.
-
\ No newline at end of file
diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h
index 965ab41b9e..4aa2a128fb 100644
--- a/usermods/mpu6050_imu/usermod_mpu6050_imu.h
+++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h
@@ -42,6 +42,14 @@
#include "Wire.h"
#endif
+#ifdef ARDUINO_ARCH_ESP32
+ #define HW_PIN_SCL 22
+ #define HW_PIN_SDA 21
+#else
+ #define HW_PIN_SCL 5
+ #define HW_PIN_SDA 4
+#endif
+
// ================================================================
// === INTERRUPT DETECTION ROUTINE ===
// ================================================================
@@ -55,7 +63,8 @@ void IRAM_ATTR dmpDataReady() {
class MPU6050Driver : public Usermod {
private:
MPU6050 mpu;
-
+ bool enabled = true;
+
// MPU control/status vars
bool dmpReady = false; // set true if DMP init was successful
uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU
@@ -84,6 +93,8 @@ class MPU6050Driver : public Usermod {
* setup() is called once at boot. WiFi is not yet connected at this point.
*/
void setup() {
+ PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } };
+ 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
Wire.begin();
@@ -93,16 +104,16 @@ class MPU6050Driver : public Usermod {
#endif
// initialize device
- Serial.println(F("Initializing I2C devices..."));
+ DEBUG_PRINTLN(F("Initializing I2C devices..."));
mpu.initialize();
pinMode(INTERRUPT_PIN, INPUT);
// verify connection
- Serial.println(F("Testing device connections..."));
- Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
+ DEBUG_PRINTLN(F("Testing device connections..."));
+ DEBUG_PRINTLN(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
// load and configure the DMP
- Serial.println(F("Initializing DMP..."));
+ DEBUG_PRINTLN(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize();
// supply your own gyro offsets here, scaled for min sensitivity
@@ -114,16 +125,16 @@ class MPU6050Driver : public Usermod {
// make sure it worked (returns 0 if so)
if (devStatus == 0) {
// turn on the DMP, now that it's ready
- Serial.println(F("Enabling DMP..."));
+ DEBUG_PRINTLN(F("Enabling DMP..."));
mpu.setDMPEnabled(true);
// enable Arduino interrupt detection
- Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
+ DEBUG_PRINTLN(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
mpuIntStatus = mpu.getIntStatus();
// set our DMP Ready flag so the main loop() function knows it's okay to use it
- Serial.println(F("DMP ready! Waiting for first interrupt..."));
+ DEBUG_PRINTLN(F("DMP ready! Waiting for first interrupt..."));
dmpReady = true;
// get expected DMP packet size for later comparison
@@ -133,9 +144,9 @@ class MPU6050Driver : public Usermod {
// 1 = initial memory load failed
// 2 = DMP configuration updates failed
// (if it's going to break, usually the code will be 1)
- Serial.print(F("DMP Initialization failed (code "));
- Serial.print(devStatus);
- Serial.println(F(")"));
+ DEBUG_PRINT(F("DMP Initialization failed (code "));
+ DEBUG_PRINT(devStatus);
+ DEBUG_PRINTLN(F(")"));
}
}
@@ -144,7 +155,7 @@ class MPU6050Driver : public Usermod {
* Use it to initialize network interfaces
*/
void connected() {
- //Serial.println("Connected to WiFi!");
+ //DEBUG_PRINTLN("Connected to WiFi!");
}
@@ -153,7 +164,7 @@ class MPU6050Driver : public Usermod {
*/
void loop() {
// if programming failed, don't try to do anything
- if (!dmpReady) return;
+ if (!enabled || !dmpReady || strip.isUpdating()) return;
// wait for MPU interrupt or extra packet(s) available
if (!mpuInterrupt && fifoCount < packetSize) return;
@@ -169,7 +180,7 @@ class MPU6050Driver : public Usermod {
if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
// reset so we can continue cleanly
mpu.resetFIFO();
- Serial.println(F("FIFO overflow!"));
+ DEBUG_PRINTLN(F("FIFO overflow!"));
// otherwise, check for DMP data ready interrupt (this should happen frequently)
} else if (mpuIntStatus & 0x02) {
@@ -259,10 +270,23 @@ class MPU6050Driver : public Usermod {
*/
void readFromJsonState(JsonObject& root)
{
- //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
+ //if (root["bri"] == 255) DEBUG_PRINTLN(F("Don't burn down your garage!"));
+ }
+
+
+ /*
+ * 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)
+ * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
+ */
+ void addToConfig(JsonObject& root)
+ {
+ JsonObject top = root.createNestedObject("MPU6050_IMU");
+ JsonArray pins = top.createNestedArray("pin");
+ pins.add(HW_PIN_SCL);
+ pins.add(HW_PIN_SDA);
}
-
-
+
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
*/
diff --git a/usermods/mqtt_switch_v2/README.md b/usermods/mqtt_switch_v2/README.md
index dc0e259fb5..148e4a5643 100644
--- a/usermods/mqtt_switch_v2/README.md
+++ b/usermods/mqtt_switch_v2/README.md
@@ -1,3 +1,7 @@
+# DEPRECATION NOTICE
+This usermod is deprecated and no longer maintained. It will be removed in a future WLED release. Please use usermod multi_relay which has more features.
+
+
# MQTT controllable switches
This usermod allows controlling switches (e.g. relays) via MQTT.
diff --git a/usermods/mqtt_switch_v2/usermod_mqtt_switch.h b/usermods/mqtt_switch_v2/usermod_mqtt_switch.h
index 40241206d4..67dfc9cc08 100644
--- a/usermods/mqtt_switch_v2/usermod_mqtt_switch.h
+++ b/usermods/mqtt_switch_v2/usermod_mqtt_switch.h
@@ -1,5 +1,7 @@
#pragma once
+#warning "This usermod is deprecated and no longer maintained. It will be removed in a future WLED release. Please use usermod multi_relay which has more features."
+
#include "wled.h"
#ifndef WLED_ENABLE_MQTT
#error "This user mod requires MQTT to be enabled."
diff --git a/usermods/multi_relay/readme.md b/usermods/multi_relay/readme.md
index a5f47bf5e5..2d933cdabe 100644
--- a/usermods/multi_relay/readme.md
+++ b/usermods/multi_relay/readme.md
@@ -5,32 +5,42 @@ This usermod-v2 modification allows the connection of multiple relays each with
## HTTP API
All responses are returned as JSON.
-Status Request: `http://[device-ip]/relays`
-Switch Command: `http://[device-ip]/relays?switch=1,0,1,1`
+* Status Request: `http://[device-ip]/relays`
+* Switch Command: `http://[device-ip]/relays?switch=1,0,1,1`
+
The number of numbers behind the switch parameter must correspond to the number of relays. The number 1 switches the relay on. The number 0 switches the relay off.
-Toggle Command: `http://[device-ip]/relays?toggle=1,0,1,1`
+* Toggle Command: `http://[device-ip]/relays?toggle=1,0,1,1`
+
The number of numbers behind the parameter switch must correspond to the number of relays. The number 1 causes a toggling of the relay. The number 0 leaves the state of the device.
Examples
-1. 4 relays at all, relay 2 will be toggled: `http://[device-ip]/relays?toggle=0,1,0,0`
-2. 3 relays at all, relay 1&3 will be switched on: `http://[device-ip]/relays?switch=1,0,1`
+1. total of 4 relays, relay 2 will be toggled: `http://[device-ip]/relays?toggle=0,1,0,0`
+2. total of 3 relays, relay 1&3 will be switched on: `http://[device-ip]/relays?switch=1,0,1`
+
+## JSON API
+You can switch relay state using the following JSON object transmitted to: `http://[device-ip]/json`
+
+
+Switch relay 0 on: `{"MultiRelay":{"relay":0,"on":true}}`
+
+Switch relay4 3 & 4 off: `{"MultiRelay":[{"relay":2,"on":false},{"relay":3,"on":false}]}`
## MQTT API
-wled/deviceMAC/relay/0/command on|off|toggle
-wled/deviceMAC/relay/1/command on|off|toggle
+* `wled`/_deviceMAC_/`relay`/`0`/`command` `on`|`off`|`toggle`
+* `wled`/_deviceMAC_/`relay`/`1`/`command` `on`|`off`|`toggle`
When relay is switched it will publish a message:
-wled/deviceMAC/relay/0 on|off
+* `wled`/_deviceMAC_/`relay`/`0` `on`|`off`
## Usermod installation
1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `usermods.add(new MultiRelay());` at the bottom of `usermods_list.cpp`.
or
-2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY`in your platformio.ini
+2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY` in your platformio.ini
You can override the default maximum number (4) of relays by defining MULTI_RELAY_MAX_RELAYS.
@@ -70,10 +80,21 @@ void registerUsermods()
Usermod can be configured in Usermods settings page.
+* `enabled` - enable/disable usermod
+* `pin` - GPIO pin where relay is attached to ESP
+* `delay-s` - delay in seconds after on/off command is received
+* `active-high` - toggle high/low activation of relay (can be used to reverse relay states)
+* `external` - if enabled WLED does not control relay, it can only be triggered by external command (MQTT, HTTP, JSON or button)
+* `button` - button (from LED Settings) that controls this relay
+
If there is no MultiRelay section, just save current configuration and re-open Usermods settings page.
Have fun - @blazoncek
## Change log
2021-04
-* First implementation.
\ No newline at end of file
+* First implementation.
+
+2021-11
+* Added information about dynamic configuration options
+* Added button support.
\ No newline at end of file
diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h
index c3d55d66d6..6143a6b997 100644
--- a/usermods/multi_relay/usermod_multi_relay.h
+++ b/usermods/multi_relay/usermod_multi_relay.h
@@ -6,6 +6,8 @@
#define MULTI_RELAY_MAX_RELAYS 4
#endif
+#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
+
#define ON true
#define OFF false
@@ -23,6 +25,7 @@ typedef struct relay_t {
bool state;
bool external;
uint16_t delay;
+ int8_t button;
} Relay;
@@ -35,13 +38,18 @@ class MultiRelay : public Usermod {
// switch timer start time
uint32_t _switchTimerStart = 0;
// old brightness
- bool _oldBrightness = 0;
+ bool _oldMode;
// usermod enabled
bool enabled = false; // needs to be configured (no default config)
// status of initialisation
bool initDone = false;
+ bool HAautodiscovery = false;
+
+ uint16_t periodicBroadcastSec = 60;
+ unsigned long lastBroadcast = 0;
+
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
@@ -49,14 +57,16 @@ class MultiRelay : public Usermod {
static const char _delay_str[];
static const char _activeHigh[];
static const char _external[];
+ static const char _button[];
+ static const char _broadcast[];
+ static const char _HAautodiscovery[];
-
- void publishMqtt(const char* state, int relay) {
+ void publishMqtt(int relay) {
//Check if MQTT Connected, otherwise it will crash the 8266
if (WLED_MQTT_CONNECTED){
char subuf[64];
sprintf_P(subuf, PSTR("%s/relay/%d"), mqttDeviceTopic, relay);
- mqtt->publish(subuf, 0, false, state);
+ mqtt->publish(subuf, 0, false, _relay[relay].state ? "on" : "off");
}
}
@@ -64,15 +74,19 @@ class MultiRelay : public Usermod {
* switch off the strip if the delay has elapsed
*/
void handleOffTimer() {
+ unsigned long now = millis();
bool activeRelays = false;
for (uint8_t i=0; i 0 && millis() - _switchTimerStart > (_relay[i].delay*1000)) {
+ if (_relay[i].active && _switchTimerStart > 0 && now - _switchTimerStart > (_relay[i].delay*1000)) {
if (!_relay[i].external) toggleRelay(i);
_relay[i].active = false;
+ } else if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) {
+ if (_relay[i].pin>=0) publishMqtt(i);
}
activeRelays = activeRelays || _relay[i].active;
}
if (!activeRelays) _switchTimerStart = 0;
+ if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) lastBroadcast = now;
}
/**
@@ -101,7 +115,7 @@ class MultiRelay : public Usermod {
for (int i=0; ivalue(), ',', i);
if (value==-1) {
- error = F("There must be as much arugments as relays");
+ error = F("There must be as many arguments as relays");
} else {
// Switch
if (_relay[i].external) switchRelay(i, (bool)value);
@@ -114,7 +128,7 @@ class MultiRelay : public Usermod {
for (int i=0;ivalue(), ',', i);
if (value==-1) {
- error = F("There must be as mutch arugments as relays");
+ error = F("There must be as many arguments as relays");
} else {
// Toggle
if (value && _relay[i].external) toggleRelay(i);
@@ -170,6 +184,7 @@ class MultiRelay : public Usermod {
_relay[i].active = false;
_relay[i].state = false;
_relay[i].external = false;
+ _relay[i].button = -1;
}
}
/**
@@ -194,7 +209,7 @@ class MultiRelay : public Usermod {
_relay[relay].state = mode;
pinMode(_relay[relay].pin, OUTPUT);
digitalWrite(_relay[relay].pin, mode ? !_relay[relay].mode : _relay[relay].mode);
- publishMqtt(mode ? "on" : "off", relay);
+ publishMqtt(relay);
}
/**
@@ -247,6 +262,50 @@ class MultiRelay : public Usermod {
strcpy(subuf, mqttDeviceTopic);
strcat_P(subuf, PSTR("/relay/#"));
mqtt->subscribe(subuf, 0);
+ if (HAautodiscovery) publishHomeAssistantAutodiscovery();
+ for (uint8_t i=0; i= 0 && _relay[i].external) {
+ StaticJsonDocument<1024> json;
+ sprintf_P(buf, PSTR("%s Switch %d"), serverDescription, i); //max length: 33 + 8 + 3 = 44
+ json[F("name")] = buf;
+
+ sprintf_P(buf, PSTR("%s/relay/%d"), mqttDeviceTopic, i); //max length: 33 + 7 + 3 = 43
+ json["~"] = buf;
+ strcat_P(buf, PSTR("/command"));
+ mqtt->subscribe(buf, 0);
+
+ json[F("stat_t")] = "~";
+ json[F("cmd_t")] = F("~/command");
+ json[F("pl_off")] = F("off");
+ json[F("pl_on")] = F("on");
+ json[F("uniq_id")] = uid;
+
+ strcpy(buf, mqttDeviceTopic); //max length: 33 + 7 = 40
+ strcat_P(buf, PSTR("/status"));
+ json[F("avty_t")] = buf;
+ json[F("pl_avail")] = F("online");
+ json[F("pl_not_avail")] = F("offline");
+ //TODO: dev
+ payload_size = serializeJson(json, json_str);
+ } else {
+ //Unpublish disabled or internal relays
+ json_str[0] = 0;
+ payload_size = 0;
+ }
+ sprintf_P(buf, PSTR("homeassistant/switch/%s/config"), uid);
+ mqtt->publish(buf, 0, true, json_str, payload_size);
}
}
@@ -261,11 +320,12 @@ class MultiRelay : public Usermod {
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
_relay[i].pin = -1; // allocation failed
} else {
- switchRelay(i, _relay[i].state = (bool)bri);
+ if (!_relay[i].external) _relay[i].state = !offMode;
+ switchRelay(i, _relay[i].state);
_relay[i].active = false;
}
}
- _oldBrightness = (bool)bri;
+ _oldMode = offMode;
initDone = true;
}
@@ -281,24 +341,119 @@ class MultiRelay : public Usermod {
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop() {
+ yield();
if (!enabled || strip.isUpdating()) return;
static unsigned long lastUpdate = 0;
- if (millis() - lastUpdate < 200) return; // update only 5 times/s
+ if (millis() - lastUpdate < 100) return; // update only 10 times/s
lastUpdate = millis();
//set relay when LEDs turn on
- if (_oldBrightness != (bool)bri) {
- _oldBrightness = (bool)bri;
+ if (_oldMode != offMode) {
+ _oldMode = offMode;
_switchTimerStart = millis();
for (uint8_t i=0; i=0) _relay[i].active = true;
+ if (_relay[i].pin>=0 && !_relay[i].external) _relay[i].active = true;
}
}
handleOffTimer();
}
+ /**
+ * 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) {
+ yield();
+ if (!enabled
+ || buttonType[b] == BTN_TYPE_NONE
+ || buttonType[b] == BTN_TYPE_RESERVED
+ || buttonType[b] == BTN_TYPE_PIR_SENSOR
+ || buttonType[b] == BTN_TYPE_ANALOG
+ || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
+ return false;
+ }
+
+ bool handled = false;
+ for (uint8_t i=0; i WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
+ for (uint8_t i=0; i=0 && _relay[i].button == b) {
+ switchRelay(i, buttonPressedBefore[b]);
+ buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
+ }
+ }
+ }
+ return handled;
+ }
+
+ //momentary button logic
+ if (isButtonPressed(b)) { //pressed
+
+ if (!buttonPressedBefore[b]) buttonPressedTime[b] = now;
+ buttonPressedBefore[b] = true;
+
+ if (now - buttonPressedTime[b] > 600) { //long press
+ //longPressAction(b); //not exposed
+ //handled = false; //use if you want to pass to default behaviour
+ buttonLongPressed[b] = true;
+ }
+
+ } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
+
+ long dur = now - buttonPressedTime[b];
+ if (dur < WLED_DEBOUNCE_THRESHOLD) {
+ buttonPressedBefore[b] = false;
+ return handled;
+ } //too short "press", debounce
+ bool doublePress = buttonWaitTime[b]; //did we have short press before?
+ buttonWaitTime[b] = 0;
+
+ if (!buttonLongPressed[b]) { //short press
+ // if this is second release within 350ms it is a double press (buttonWaitTime!=0)
+ if (doublePress) {
+ //doublePressAction(b); //not exposed
+ //handled = false; //use if you want to pass to default behaviour
+ } else {
+ buttonWaitTime[b] = now;
+ }
+ }
+ buttonPressedBefore[b] = false;
+ buttonLongPressed[b] = false;
+ }
+ // if 350ms elapsed since last press/release it is a short press
+ if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) {
+ buttonWaitTime[b] = 0;
+ //shortPressAction(b); //not exposed
+ for (uint8_t i=0; i=0 && _relay[i].button == b) {
+ toggleRelay(i);
+ }
+ }
+ }
+ return handled;
+ }
+
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*/
@@ -310,6 +465,26 @@ class MultiRelay : public Usermod {
JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name
infoArr.add(String(getActiveRelayCount()));
+
+ String uiDomString;
+ for (uint8_t i=0; i");
+ uiDomString += F("Relay ");
+ uiDomString += i;
+ uiDomString += F(" ");
+ JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
+
+ infoArr.add(_relay[i].state ? "on" : "off");
+ }
}
}
@@ -317,15 +492,46 @@ class MultiRelay : public Usermod {
* 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) {
- //}
+ void addToJsonState(JsonObject &root) {
+ if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
+ JsonObject multiRelay = root[FPSTR(_name)];
+ if (multiRelay.isNull()) {
+ multiRelay = root.createNestedObject(FPSTR(_name));
+ }
+ #if MULTI_RELAY_MAX_RELAYS > 1
+ JsonArray rel_arr = multiRelay.createNestedArray(F("relays"));
+ for (uint8_t i=0; i() && usermod[FPSTR(_relay_str)].is() && usermod[FPSTR(_relay_str)].as()>=0) {
+ switchRelay(usermod[FPSTR(_relay_str)].as(), usermod["on"].as());
+ }
+ } else if (root[FPSTR(_name)].is()) {
+ JsonArray relays = root[FPSTR(_name)].as();
+ for (JsonVariant r : relays) {
+ if (r["on"].is() && r[FPSTR(_relay_str)].is() && r[FPSTR(_relay_str)].as()>=0) {
+ switchRelay(r[FPSTR(_relay_str)].as(), r["on"].as());
+ }
+ }
+ }
+ }
/**
* provide the changeable values
@@ -334,6 +540,7 @@ class MultiRelay : public Usermod {
JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
+ top[FPSTR(_broadcast)] = periodicBroadcastSec;
for (uint8_t i=0; i=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
if (!_relay[i].external) {
- switchRelay(i, _relay[i].state = (bool)bri);
+ _relay[i].state = !offMode;
+ switchRelay(i, _relay[i].state);
+ _oldMode = offMode;
}
} else {
_relay[i].pin = -1;
@@ -404,7 +619,7 @@ class MultiRelay : public Usermod {
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
- return !top[F("relay-0")]["pin"].isNull();
+ return !top[FPSTR(_broadcast)].isNull();
}
/**
@@ -424,3 +639,6 @@ const char MultiRelay::_relay_str[] PROGMEM = "relay";
const char MultiRelay::_delay_str[] PROGMEM = "delay-s";
const char MultiRelay::_activeHigh[] PROGMEM = "active-high";
const char MultiRelay::_external[] PROGMEM = "external";
+const char MultiRelay::_button[] PROGMEM = "button";
+const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec";
+const char MultiRelay::_HAautodiscovery[] PROGMEM = "HA-autodiscovery";
diff --git a/usermods/project_cars_shiftlight/wled06_usermod.ino b/usermods/project_cars_shiftlight/wled06_usermod.ino
index 0edad7ddde..9d3f1d4477 100644
--- a/usermods/project_cars_shiftlight/wled06_usermod.ino
+++ b/usermods/project_cars_shiftlight/wled06_usermod.ino
@@ -5,6 +5,8 @@
* I've had good results with settings around 5 (20 fps).
*
*/
+#include "wled.h"
+
const uint8_t PCARS_dimcolor = 20;
WiFiUDP UDP;
const unsigned int PCARS_localUdpPort = 5606; // local port to listen on
@@ -49,11 +51,12 @@ void PCARS_readValues() {
void PCARS_buildcolorbars() {
boolean activated = false;
float ledratio = 0;
+ uint16_t totalLen = strip.getLengthTotal();
- for (uint16_t i = 0; i < ledCount; i++) {
+ for (uint16_t i = 0; i < totalLen; i++) {
if (PCARS_rpmRatio < .95 || (millis() % 100 > 70 )) {
- ledratio = (float)i / (float)ledCount;
+ ledratio = (float)i / (float)totalLen;
if (ledratio < PCARS_rpmRatio) {
activated = true;
} else {
diff --git a/usermods/quinled-an-penta/quinled-an-penta.h b/usermods/quinled-an-penta/quinled-an-penta.h
new file mode 100644
index 0000000000..5153ee58a9
--- /dev/null
+++ b/usermods/quinled-an-penta/quinled-an-penta.h
@@ -0,0 +1,755 @@
+#pragma once
+
+#include "U8g2lib.h"
+#include "SHT85.h"
+#include "Wire.h"
+#include "wled.h"
+
+class QuinLEDAnPentaUsermod : public Usermod
+{
+ private:
+ bool enabled = false;
+ bool firstRunDone = false;
+ bool initDone = false;
+ U8G2 *oledDisplay = nullptr;
+ SHT *sht30TempHumidSensor;
+
+ // Network info vars
+ bool networkHasChanged = false;
+ bool lastKnownNetworkConnected;
+ IPAddress lastKnownIp;
+ bool lastKnownWiFiConnected;
+ String lastKnownSsid;
+ bool lastKnownApActive;
+ char *lastKnownApSsid;
+ char *lastKnownApPass;
+ byte lastKnownApChannel;
+ int lastKnownEthType;
+ bool lastKnownEthLinkUp;
+
+ // Brightness / LEDC vars
+ byte lastKnownBri = 0;
+ int8_t currentBussesNumPins[5] = {0, 0, 0, 0, 0};
+ int8_t currentLedPins[5] = {0, 0, 0, 0, 0};
+ uint8_t currentLedcReads[5] = {0, 0, 0, 0, 0};
+ uint8_t lastKnownLedcReads[5] = {0, 0, 0, 0, 0};
+
+ // OLED vars
+ bool oledEnabled = false;
+ bool oledInitDone = false;
+ bool oledUseProgressBars = false;
+ bool oledFlipScreen = false;
+ bool oledFixBuggedScreen = false;
+ byte oledMaxPage = 3;
+ byte oledCurrentPage = 3; // Start with the network page to help identifying the IP
+ byte oledSecondsPerPage = 10;
+ unsigned long oledLogoDrawn = 0;
+ unsigned long oledLastTimeUpdated = 0;
+ unsigned long oledLastTimePageChange = 0;
+ unsigned long oledLastTimeFixBuggedScreen = 0;
+
+ // SHT30 vars
+ bool shtEnabled = false;
+ bool shtInitDone = false;
+ bool shtReadDataSuccess = false;
+ byte shtI2cAddress = 0x44;
+ unsigned long shtLastTimeUpdated = 0;
+ bool shtDataRequested = false;
+ float shtCurrentTemp = 0;
+ float shtLastKnownTemp = 0;
+ float shtCurrentHumidity = 0;
+ float shtLastKnownHumidity = 0;
+
+ // Pin/IO vars
+ const int8_t anPentaLEDPins[5] = {14, 13, 12, 4, 2};
+ int8_t oledSpiClk = 15;
+ int8_t oledSpiData = 16;
+ int8_t oledSpiCs = 27;
+ int8_t oledSpiDc = 32;
+ int8_t oledSpiRst = 33;
+ int8_t shtSda = 1;
+ int8_t shtScl = 3;
+
+
+ bool isAnPentaLedPin(int8_t pin)
+ {
+ for(int8_t i = 0; i <= 4; i++)
+ {
+ if(anPentaLEDPins[i] == pin)
+ return true;
+ }
+ return false;
+ }
+
+ void getCurrentUsedLedPins()
+ {
+ for (int8_t lp = 0; lp <= 4; lp++) currentLedPins[lp] = 0;
+ byte numBusses = busses.getNumBusses();
+ byte numUsedPins = 0;
+
+ for (int8_t b = 0; b < numBusses; b++) {
+ Bus* curBus = busses.getBus(b);
+ if (curBus != nullptr) {
+ uint8_t pins[5] = {0, 0, 0, 0, 0};
+ currentBussesNumPins[b] = curBus->getPins(pins);
+ for (int8_t p = 0; p < currentBussesNumPins[b]; p++) {
+ if (isAnPentaLedPin(pins[p])) {
+ currentLedPins[numUsedPins] = pins[p];
+ numUsedPins++;
+ }
+ }
+ }
+ }
+ }
+
+ void getCurrentLedcValues()
+ {
+ byte numBusses = busses.getNumBusses();
+ byte numLedc = 0;
+
+ for (int8_t b = 0; b < numBusses; b++) {
+ Bus* curBus = busses.getBus(b);
+ if (curBus != nullptr) {
+ uint32_t curPixColor = curBus->getPixelColor(0);
+ uint8_t _data[5] = {255, 255, 255, 255, 255};
+ _data[3] = curPixColor >> 24;
+ _data[0] = curPixColor >> 16;
+ _data[1] = curPixColor >> 8;
+ _data[2] = curPixColor;
+
+ for (uint8_t i = 0; i < currentBussesNumPins[b]; i++) {
+ currentLedcReads[numLedc] = (_data[i] * bri) / 255;
+ numLedc++;
+ }
+ }
+ }
+ }
+
+
+ void initOledDisplay()
+ {
+ PinManagerPinType pins[5] = { { oledSpiClk, true }, { oledSpiData, true }, { oledSpiCs, true }, { oledSpiDc, true }, { oledSpiRst, true } };
+ if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) {
+ DEBUG_PRINTF("[%s] OLED pin allocation failed!\n", _name);
+ oledEnabled = oledInitDone = false;
+ return;
+ }
+
+ oledDisplay = (U8G2 *) new U8G2_SSD1306_128X64_NONAME_2_4W_SW_SPI(U8G2_R0, oledSpiClk, oledSpiData, oledSpiCs, oledSpiDc, oledSpiRst);
+ if (oledDisplay == nullptr) {
+ DEBUG_PRINTF("[%s] OLED init failed!\n", _name);
+ oledEnabled = oledInitDone = false;
+ return;
+ }
+
+ oledDisplay->begin();
+ oledDisplay->setBusClock(40 * 1000 * 1000);
+ oledDisplay->setContrast(10);
+ oledDisplay->setPowerSave(0);
+ oledDisplay->setFont(u8g2_font_6x10_tf);
+ oledDisplay->setFlipMode(oledFlipScreen);
+
+ oledDisplay->firstPage();
+ do {
+ oledDisplay->drawXBMP(0, 16, 128, 36, quinLedLogo);
+ } while (oledDisplay->nextPage());
+ oledLogoDrawn = millis();
+
+ oledInitDone = true;
+ }
+
+ void cleanupOledDisplay()
+ {
+ if (oledInitDone) {
+ oledDisplay->clear();
+ }
+
+ pinManager.deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta);
+ pinManager.deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta);
+ pinManager.deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta);
+ pinManager.deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta);
+ pinManager.deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta);
+
+ delete oledDisplay;
+
+ oledEnabled = false;
+ oledInitDone = false;
+ }
+
+ bool isOledReady()
+ {
+ return oledEnabled && oledInitDone;
+ }
+
+ void initSht30TempHumiditySensor()
+ {
+ PinManagerPinType pins[2] = { { shtSda, true }, { shtScl, true } };
+ if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) {
+ DEBUG_PRINTF("[%s] SHT30 pin allocation failed!\n", _name);
+ shtEnabled = shtInitDone = false;
+ return;
+ }
+
+ TwoWire *wire = new TwoWire(1);
+ wire->setClock(400000);
+
+ sht30TempHumidSensor = (SHT *) new SHT30();
+ sht30TempHumidSensor->begin(shtI2cAddress, wire);
+ // The SHT lib calls wire.begin() again without the SDA and SCL pins... So call it again here...
+ wire->begin(shtSda, shtScl);
+ if (sht30TempHumidSensor->readStatus() == 0xFFFF) {
+ DEBUG_PRINTF("[%s] SHT30 init failed!\n", _name);
+ shtEnabled = shtInitDone = false;
+ return;
+ }
+
+ shtInitDone = true;
+ }
+
+ void cleanupSht30TempHumiditySensor()
+ {
+ if (shtInitDone) {
+ sht30TempHumidSensor->reset();
+ }
+
+ pinManager.deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta);
+ pinManager.deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta);
+
+ delete sht30TempHumidSensor;
+
+ shtEnabled = false;
+ shtInitDone = false;
+ }
+
+ void cleanup()
+ {
+ if (isOledReady()) {
+ cleanupOledDisplay();
+ }
+
+ if (isShtReady()) {
+ cleanupSht30TempHumiditySensor();
+ }
+
+ enabled = false;
+ }
+
+ bool oledCheckForNetworkChanges()
+ {
+ if (lastKnownNetworkConnected != Network.isConnected() || lastKnownIp != Network.localIP()
+ || lastKnownWiFiConnected != WiFi.isConnected() || lastKnownSsid != WiFi.SSID()
+ || lastKnownApActive != apActive || lastKnownApSsid != apSSID || lastKnownApPass != apPass || lastKnownApChannel != apChannel) {
+ lastKnownNetworkConnected = Network.isConnected();
+ lastKnownIp = Network.localIP();
+ lastKnownWiFiConnected = WiFi.isConnected();
+ lastKnownSsid = WiFi.SSID();
+ lastKnownApActive = apActive;
+ lastKnownApSsid = apSSID;
+ lastKnownApPass = apPass;
+ lastKnownApChannel = apChannel;
+
+ return networkHasChanged = true;
+ }
+ #ifdef WLED_USE_ETHERNET
+ if (lastKnownEthType != ethernetType || lastKnownEthLinkUp != ETH.linkUp()) {
+ lastKnownEthType = ethernetType;
+ lastKnownEthLinkUp = ETH.linkUp();
+
+ return networkHasChanged = true;
+ }
+ #endif
+
+ return networkHasChanged = false;
+ }
+
+ byte oledGetNextPage()
+ {
+ return oledCurrentPage + 1 <= oledMaxPage ? oledCurrentPage + 1 : 1;
+ }
+
+ void oledShowPage(byte page, bool updateLastTimePageChange = false)
+ {
+ oledCurrentPage = page;
+ updateOledDisplay();
+ oledLastTimeUpdated = millis();
+ if (updateLastTimePageChange) oledLastTimePageChange = oledLastTimeUpdated;
+ }
+
+ /*
+ * Page 1: Overall brightness and LED outputs
+ * Page 2: General info like temp, humidity and others
+ * Page 3: Network info
+ */
+ void updateOledDisplay()
+ {
+ if (!isOledReady()) return;
+
+ oledDisplay->firstPage();
+ do {
+ oledDisplay->setFont(u8g2_font_chroma48medium8_8r);
+ oledDisplay->drawStr(0, 8, serverDescription);
+ oledDisplay->drawHLine(0, 13, 127);
+ oledDisplay->setFont(u8g2_font_6x10_tf);
+
+ byte charPerRow = 21;
+ byte oledRow = 23;
+ switch (oledCurrentPage) {
+ // LED Outputs
+ case 1:
+ {
+ char charCurrentBrightness[charPerRow+1] = "Brightness:";
+ if (oledUseProgressBars) {
+ oledDisplay->drawStr(0, oledRow, charCurrentBrightness);
+ // There is no method to draw a filled box with rounded corners. So draw the rounded frame first, then fill that frame accordingly to LED percentage
+ oledDisplay->drawRFrame(68, oledRow - 6, 60, 7, 2);
+ oledDisplay->drawBox(69, oledRow - 5, int(round(58*getPercentageForBrightness(bri)) / 100), 5);
+ }
+ else {
+ sprintf(charCurrentBrightness, "%s %d%%", charCurrentBrightness, getPercentageForBrightness(bri));
+ oledDisplay->drawStr(0, oledRow, charCurrentBrightness);
+ }
+ oledRow += 8;
+
+ byte drawnLines = 0;
+ for (int8_t app = 0; app <= 4; app++) {
+ for (int8_t clp = 0; clp <= 4; clp++) {
+ if (anPentaLEDPins[app] == currentLedPins[clp]) {
+ char charCurrentLedcReads[17];
+ sprintf(charCurrentLedcReads, "LED %d:", app+1);
+ if (oledUseProgressBars) {
+ oledDisplay->drawStr(0, oledRow+(drawnLines*8), charCurrentLedcReads);
+ oledDisplay->drawRFrame(38, oledRow - 6 + (drawnLines * 8), 90, 7, 2);
+ oledDisplay->drawBox(39, oledRow - 5 + (drawnLines * 8), int(round(88*getPercentageForBrightness(currentLedcReads[clp])) / 100), 5);
+ }
+ else {
+ sprintf(charCurrentLedcReads, "%s %d%%", charCurrentLedcReads, getPercentageForBrightness(currentLedcReads[clp]));
+ oledDisplay->drawStr(0, oledRow+(drawnLines*8), charCurrentLedcReads);
+ }
+
+ drawnLines++;
+ }
+ }
+ }
+ break;
+ }
+
+ // Various info
+ case 2:
+ {
+ if (isShtReady() && shtReadDataSuccess) {
+ char charShtCurrentTemp[charPerRow+4]; // Reserve 3 more bytes than usual as we gonna have one UTF8 char which can be up to 4 bytes.
+ sprintf(charShtCurrentTemp, "Temperature: %.02f°C", shtCurrentTemp);
+ char charShtCurrentHumidity[charPerRow+1];
+ sprintf(charShtCurrentHumidity, "Humidity: %.02f RH", shtCurrentHumidity);
+
+ oledDisplay->drawUTF8(0, oledRow, charShtCurrentTemp);
+ oledDisplay->drawStr(0, oledRow + 10, charShtCurrentHumidity);
+ oledRow += 20;
+ }
+
+ if (mqttEnabled && mqttServer[0] != 0) {
+ char charMqttStatus[charPerRow+1];
+ sprintf(charMqttStatus, "MQTT: %s", (WLED_MQTT_CONNECTED ? "Connected" : "Disconnected"));
+ oledDisplay->drawStr(0, oledRow, charMqttStatus);
+ oledRow += 10;
+ }
+
+ // Always draw these two on the bottom
+ char charUptime[charPerRow+1];
+ sprintf(charUptime, "Uptime: %ds", int(millis()/1000 + rolloverMillis*4294967)); // From json.cpp
+ oledDisplay->drawStr(0, 53, charUptime);
+
+ char charWledVersion[charPerRow+1];
+ sprintf(charWledVersion, "WLED v%s", versionString);
+ oledDisplay->drawStr(0, 63, charWledVersion);
+ break;
+ }
+
+ // Network Info
+ case 3:
+ #ifdef WLED_USE_ETHERNET
+ if (lastKnownEthType == WLED_ETH_NONE) {
+ oledDisplay->drawStr(0, oledRow, "Ethernet: No board selected");
+ oledRow += 10;
+ }
+ else if (!lastKnownEthLinkUp) {
+ oledDisplay->drawStr(0, oledRow, "Ethernet: Link Down");
+ oledRow += 10;
+ }
+ #endif
+
+ if (lastKnownNetworkConnected) {
+ #ifdef WLED_USE_ETHERNET
+ if (lastKnownEthLinkUp) {
+ oledDisplay->drawStr(0, oledRow, "Ethernet: Link Up");
+ oledRow += 10;
+ }
+ else
+ #endif
+ // Wi-Fi can be active with ETH being connected, but we don't mind...
+ if (lastKnownWiFiConnected) {
+ #ifdef WLED_USE_ETHERNET
+ if (!lastKnownEthLinkUp) {
+ #endif
+
+ oledDisplay->drawStr(0, oledRow, "Wi-Fi: Connected");
+ char currentSsidChar[lastKnownSsid.length() + 1];
+ lastKnownSsid.toCharArray(currentSsidChar, lastKnownSsid.length() + 1);
+ char charCurrentSsid[50];
+ sprintf(charCurrentSsid, "SSID: %s", currentSsidChar);
+ oledDisplay->drawStr(0, oledRow + 10, charCurrentSsid);
+ oledRow += 20;
+
+ #ifdef WLED_USE_ETHERNET
+ }
+ #endif
+ }
+
+ String currentIpStr = lastKnownIp.toString();
+ char currentIpChar[currentIpStr.length() + 1];
+ currentIpStr.toCharArray(currentIpChar, currentIpStr.length() + 1);
+ char charCurrentIp[30];
+ sprintf(charCurrentIp, "IP: %s", currentIpChar);
+ oledDisplay->drawStr(0, oledRow, charCurrentIp);
+ }
+ // If WLED AP is active. Theoretically, it can even be active with ETH being connected, but we don't mind...
+ else if (lastKnownApActive) {
+ char charCurrentApStatus[charPerRow+1];
+ sprintf(charCurrentApStatus, "WLED AP: %s (Ch: %d)", (lastKnownApActive ? "On" : "Off"), lastKnownApChannel);
+ oledDisplay->drawStr(0, oledRow, charCurrentApStatus);
+
+ char charCurrentApSsid[charPerRow+1];
+ sprintf(charCurrentApSsid, "SSID: %s", lastKnownApSsid);
+ oledDisplay->drawStr(0, oledRow + 10, charCurrentApSsid);
+
+ char charCurrentApPass[charPerRow+1];
+ sprintf(charCurrentApPass, "PW: %s", lastKnownApPass);
+ oledDisplay->drawStr(0, oledRow + 20, charCurrentApPass);
+
+ // IP is hardcoded / no var exists in WLED at the time this mod was coded, so also hardcode it here
+ oledDisplay->drawStr(0, oledRow + 30, "IP: 4.3.2.1");
+ }
+
+ break;
+ }
+ } while (oledDisplay->nextPage());
+ }
+
+ bool isShtReady()
+ {
+ return shtEnabled && shtInitDone;
+ }
+
+
+ public:
+ // strings to reduce flash memory usage (used more than twice)
+ static const char _name[];
+ static const char _enabled[];
+ static const char _oledEnabled[];
+ static const char _oledUseProgressBars[];
+ static const char _oledFlipScreen[];
+ static const char _oledSecondsPerPage[];
+ static const char _oledFixBuggedScreen[];
+ static const char _shtEnabled[];
+ static const unsigned char quinLedLogo[];
+
+
+ static int8_t getPercentageForBrightness(byte brightness)
+ {
+ return int(((float)brightness / (float)255) * 100);
+ }
+
+
+ /*
+ * 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) {
+ lastKnownBri = bri;
+
+ if (oledEnabled) {
+ initOledDisplay();
+ }
+
+ if (shtEnabled) {
+ initSht30TempHumiditySensor();
+ }
+
+ getCurrentUsedLedPins();
+
+ initDone = true;
+ }
+
+ firstRunDone = true;
+ }
+
+ /*
+ * loop() is called continuously. Here you can check for events, read sensors, etc.
+ *
+ * Tips:
+ * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection.
+ * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker.
+ *
+ * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
+ * Instead, use a timer check as shown here.
+ */
+ void loop()
+ {
+ if (!enabled || !initDone || strip.isUpdating()) return;
+
+ if (isShtReady()) {
+ if (millis() - shtLastTimeUpdated > 30000 && !shtDataRequested) {
+ sht30TempHumidSensor->requestData();
+ shtDataRequested = true;
+
+ shtLastTimeUpdated = millis();
+ }
+
+ if (shtDataRequested) {
+ if (sht30TempHumidSensor->dataReady()) {
+ if (sht30TempHumidSensor->readData()) {
+ shtCurrentTemp = sht30TempHumidSensor->getTemperature();
+ shtCurrentHumidity = sht30TempHumidSensor->getHumidity();
+ shtReadDataSuccess = true;
+ }
+ else {
+ shtReadDataSuccess = false;
+ }
+
+ shtDataRequested = false;
+ }
+ }
+ }
+
+ if (isOledReady() && millis() - oledLogoDrawn > 3000) {
+ // Check for changes on the current page and update the OLED if a change is detected
+ if (millis() - oledLastTimeUpdated > 150) {
+ // If there was a network change, force page 3 (network page)
+ if (oledCheckForNetworkChanges()) {
+ oledCurrentPage = 3;
+ }
+ // Only redraw a page if there was a change for that page
+ switch (oledCurrentPage) {
+ case 1:
+ lastKnownBri = bri;
+ // Probably causes lag to always do ledcRead(), so rather re-do the math, 'cause we can't easily get it...
+ getCurrentLedcValues();
+
+ if (bri != lastKnownBri || lastKnownLedcReads[0] != currentLedcReads[0] || lastKnownLedcReads[1] != currentLedcReads[1] || lastKnownLedcReads[2] != currentLedcReads[2]
+ || lastKnownLedcReads[3] != currentLedcReads[3] || lastKnownLedcReads[4] != currentLedcReads[4]) {
+ lastKnownLedcReads[0] = currentLedcReads[0]; lastKnownLedcReads[1] = currentLedcReads[1]; lastKnownLedcReads[2] = currentLedcReads[2]; lastKnownLedcReads[3] = currentLedcReads[3]; lastKnownLedcReads[4] = currentLedcReads[4];
+
+ oledShowPage(1);
+ }
+ break;
+
+ case 2:
+ if (shtLastKnownTemp != shtCurrentTemp || shtLastKnownHumidity != shtCurrentHumidity) {
+ shtLastKnownTemp = shtCurrentTemp;
+ shtLastKnownHumidity = shtCurrentHumidity;
+
+ oledShowPage(2);
+ }
+ break;
+
+ case 3:
+ if (networkHasChanged) {
+ networkHasChanged = false;
+
+ oledShowPage(3, true);
+ }
+ break;
+ }
+ }
+ // Cycle through OLED pages
+ if (millis() - oledLastTimePageChange > oledSecondsPerPage * 1000) {
+ // Periodically fixing a "bugged out" OLED. More details in the ReadMe
+ if (oledFixBuggedScreen && millis() - oledLastTimeFixBuggedScreen > 60000) {
+ oledDisplay->begin();
+ oledLastTimeFixBuggedScreen = millis();
+ }
+ oledShowPage(oledGetNextPage(), true);
+ }
+ }
+ }
+
+ void addToConfig(JsonObject &root)
+ {
+ JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
+
+ top[FPSTR(_enabled)] = enabled;
+ top[FPSTR(_oledEnabled)] = oledEnabled;
+ top[FPSTR(_oledUseProgressBars)] = oledUseProgressBars;
+ top[FPSTR(_oledFlipScreen)] = oledFlipScreen;
+ top[FPSTR(_oledSecondsPerPage)] = oledSecondsPerPage;
+ top[FPSTR(_oledFixBuggedScreen)] = oledFixBuggedScreen;
+ top[FPSTR(_shtEnabled)] = shtEnabled;
+
+ // Update LED pins on config save
+ getCurrentUsedLedPins();
+ }
+
+ /**
+ * 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)
+ {
+ JsonObject top = root[FPSTR(_name)];
+ if (top.isNull()) {
+ DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name);
+ return false;
+ }
+
+ bool oldEnabled = enabled;
+ bool oldOledEnabled = oledEnabled;
+ bool oldOledFlipScreen = oledFlipScreen;
+ bool oldShtEnabled = shtEnabled;
+
+ getJsonValue(top[FPSTR(_enabled)], enabled);
+ getJsonValue(top[FPSTR(_oledEnabled)], oledEnabled);
+ getJsonValue(top[FPSTR(_oledUseProgressBars)], oledUseProgressBars);
+ getJsonValue(top[FPSTR(_oledFlipScreen)], oledFlipScreen);
+ getJsonValue(top[FPSTR(_oledSecondsPerPage)], oledSecondsPerPage);
+ getJsonValue(top[FPSTR(_oledFixBuggedScreen)], oledFixBuggedScreen);
+ getJsonValue(top[FPSTR(_shtEnabled)], shtEnabled);
+
+ // First run: reading from cfg.json, nothing to do here, will be all done in setup()
+ if (!firstRunDone) {
+ DEBUG_PRINTF("[%s] First run, nothing to do\n", _name);
+ }
+ // Check if mod has been en-/disabled
+ else if (enabled != oldEnabled) {
+ enabled ? setup() : cleanup();
+ DEBUG_PRINTF("[%s] Usermod has been en-/disabled\n", _name);
+ }
+ // Config has been changed, so adopt to changes
+ else if (enabled) {
+ if (oldOledEnabled != oledEnabled) {
+ oledEnabled ? initOledDisplay() : cleanupOledDisplay();
+ }
+ else if (oledEnabled && oldOledFlipScreen != oledFlipScreen) {
+ oledDisplay->clear();
+ oledDisplay->setFlipMode(oledFlipScreen);
+ oledShowPage(oledCurrentPage);
+ }
+
+ if (oldShtEnabled != shtEnabled) {
+ shtEnabled ? initSht30TempHumiditySensor() : cleanupSht30TempHumiditySensor();
+ }
+
+ DEBUG_PRINTF("[%s] Config (re)loaded\n", _name);
+ }
+
+ return true;
+ }
+
+ void addToJsonInfo(JsonObject& root)
+ {
+ if (!enabled && !isShtReady()) {
+ return;
+ }
+
+ JsonObject user = root["u"];
+ if (user.isNull()) user = root.createNestedObject("u");
+
+ JsonArray jsonTemp = user.createNestedArray("Temperature");
+ JsonArray jsonHumidity = user.createNestedArray("Humidity");
+
+ if (shtLastTimeUpdated == 0 || !shtReadDataSuccess) {
+ jsonTemp.add(0);
+ jsonHumidity.add(0);
+ if (shtLastTimeUpdated == 0) {
+ jsonTemp.add(" Not read yet");
+ jsonHumidity.add(" Not read yet");
+ }
+ else {
+ jsonTemp.add(" Error");
+ jsonHumidity.add(" Error");
+ }
+
+ return;
+ }
+
+ jsonHumidity.add(shtCurrentHumidity);
+ jsonHumidity.add(" RH");
+
+ jsonTemp.add(shtCurrentTemp);
+ jsonTemp.add(" °C");
+ }
+
+ /*
+ * 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_QUINLED_AN_PENTA;
+ }
+};
+
+// strings to reduce flash memory usage (used more than twice)
+// Config settings
+const char QuinLEDAnPentaUsermod::_name[] PROGMEM = "QuinLED-An-Penta";
+const char QuinLEDAnPentaUsermod::_enabled[] PROGMEM = "Enabled";
+const char QuinLEDAnPentaUsermod::_oledEnabled[] PROGMEM = "Enable-OLED";
+const char QuinLEDAnPentaUsermod::_oledUseProgressBars[] PROGMEM = "OLED-Use-Progress-Bars";
+const char QuinLEDAnPentaUsermod::_oledFlipScreen[] PROGMEM = "OLED-Flip-Screen-180";
+const char QuinLEDAnPentaUsermod::_oledSecondsPerPage[] PROGMEM = "OLED-Seconds-Per-Page";
+const char QuinLEDAnPentaUsermod::_oledFixBuggedScreen[] PROGMEM = "OLED-Fix-Bugged-Screen";
+const char QuinLEDAnPentaUsermod::_shtEnabled[] PROGMEM = "Enable-SHT30-Temp-Humidity-Sensor";
+// Other strings
+
+const unsigned char QuinLEDAnPentaUsermod::quinLedLogo[] PROGMEM = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xFD, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x80, 0xFF,
+ 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x3F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0x07, 0xFE, 0xFF, 0xFF, 0x0F, 0xFC,
+ 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFC, 0x0F, 0xFE,
+ 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0xE3, 0xFF, 0xA5, 0xFF, 0xFF, 0xFF,
+ 0x0F, 0xFC, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xE1, 0xFF,
+ 0x00, 0xF0, 0xE3, 0xFF, 0x0F, 0xFE, 0x1F, 0xFE, 0xFF, 0xFF, 0x3F, 0xFF,
+ 0xFF, 0xFF, 0xE3, 0xFF, 0x00, 0xF0, 0x00, 0xFF, 0x07, 0xFE, 0x1F, 0xFC,
+ 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xFF, 0x00, 0xF0, 0x00, 0xFE,
+ 0x07, 0xFF, 0x1F, 0xFC, 0xF0, 0xC7, 0x3F, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF,
+ 0xF1, 0xFF, 0x00, 0xFC, 0x07, 0xFF, 0x1F, 0xFE, 0xF0, 0xC3, 0x1F, 0xFE,
+ 0x00, 0xFF, 0xE1, 0xFF, 0xF1, 0xFF, 0x30, 0xF8, 0x07, 0xFF, 0x1F, 0xFE,
+ 0xF0, 0xC3, 0x1F, 0xFE, 0x00, 0xFC, 0xC3, 0xFF, 0xE1, 0xFF, 0xF0, 0xF0,
+ 0x03, 0xFF, 0x0F, 0x7E, 0xF0, 0xC3, 0x1F, 0x7E, 0x00, 0xF8, 0xE3, 0xFF,
+ 0xE1, 0xFF, 0xF1, 0xF1, 0x83, 0xFF, 0x0F, 0x7E, 0xF0, 0xC3, 0x1F, 0x7E,
+ 0x00, 0xF0, 0xC3, 0xFF, 0xE1, 0xFF, 0xF1, 0xE1, 0x83, 0xFF, 0x0F, 0xFE,
+ 0xF0, 0xC3, 0x1F, 0xFE, 0xF8, 0xF0, 0xC3, 0xFF, 0xA1, 0xFF, 0xF1, 0xE3,
+ 0x81, 0xFF, 0x0F, 0x7E, 0xF0, 0xC1, 0x1F, 0x7E, 0xF0, 0xF0, 0xC3, 0xFF,
+ 0x01, 0xF8, 0xE1, 0xC3, 0x83, 0xFF, 0x0F, 0x7F, 0xF8, 0xC3, 0x1F, 0x7E,
+ 0xF8, 0xF0, 0xC3, 0xFF, 0x03, 0xF8, 0xE1, 0xC7, 0x81, 0xE4, 0x0F, 0x7F,
+ 0xF0, 0xC3, 0x1F, 0xFE, 0xF8, 0xF0, 0xC3, 0xFF, 0x01, 0xF8, 0xE3, 0xC7,
+ 0x01, 0xC0, 0x07, 0x7F, 0xF8, 0xC1, 0x1F, 0x7E, 0xF0, 0xE1, 0xC3, 0xFF,
+ 0xC3, 0xFD, 0xE1, 0x87, 0x01, 0x00, 0x07, 0x7F, 0xF8, 0xC3, 0x1F, 0x7E,
+ 0xF8, 0xF0, 0xC3, 0xFF, 0xE3, 0xFF, 0xE3, 0x87, 0x01, 0x00, 0x82, 0x3F,
+ 0xF8, 0xE1, 0x1F, 0xFE, 0xF8, 0xE1, 0xC3, 0xFF, 0xC3, 0xFF, 0xC3, 0x87,
+ 0x01, 0x00, 0x80, 0x3F, 0xF8, 0xC1, 0x1F, 0x7E, 0xF0, 0xF1, 0xC3, 0xFF,
+ 0xC3, 0xFF, 0xC3, 0x87, 0x03, 0x0F, 0x80, 0x3F, 0xF8, 0xE1, 0x0F, 0x7E,
+ 0xF8, 0xE1, 0x87, 0xFF, 0xC3, 0xFF, 0xC7, 0x87, 0x03, 0x04, 0xC0, 0x7F,
+ 0xF0, 0xE1, 0x0F, 0xFF, 0xF8, 0xF1, 0x87, 0xFF, 0xC3, 0xFF, 0xC3, 0x87,
+ 0x07, 0x00, 0xE0, 0x7F, 0x00, 0xE0, 0x1F, 0x7E, 0xF0, 0xE0, 0xC3, 0xFF,
+ 0xC7, 0xFF, 0x87, 0x87, 0x0F, 0x00, 0xE0, 0x7F, 0x00, 0xE0, 0x0F, 0x7F,
+ 0xF8, 0xE1, 0x07, 0x80, 0x07, 0xEA, 0x87, 0xC1, 0x0F, 0x00, 0x80, 0xFF,
+ 0x00, 0xE0, 0x1F, 0x7E, 0xF0, 0xE1, 0x07, 0x00, 0x03, 0x80, 0x07, 0xC0,
+ 0x7F, 0x00, 0x00, 0xFF, 0x01, 0xE0, 0x1F, 0xFF, 0xF8, 0xE1, 0x07, 0x00,
+ 0x07, 0x00, 0x07, 0xE0, 0xFF, 0xF7, 0x01, 0xFF, 0x57, 0xF7, 0x9F, 0xFF,
+ 0xFC, 0xF1, 0x0F, 0x00, 0x07, 0x80, 0x0F, 0xE0, 0xFF, 0xFF, 0x03, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xBF, 0xFE,
+ 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+};
\ No newline at end of file
diff --git a/usermods/quinled-an-penta/readme.md b/usermods/quinled-an-penta/readme.md
new file mode 100644
index 0000000000..4a4c0290ab
--- /dev/null
+++ b/usermods/quinled-an-penta/readme.md
@@ -0,0 +1,79 @@
+# QuinLED-An-Penta
+The (un)official usermod to get the best out of the QuinLED-An-Penta (https://quinled.info/quinled-an-penta/), like using the OLED and the SHT30 temperature/humidity sensor.
+
+## Requirements
+* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2
+* "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85
+
+## Usermod installation
+Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one, add the buildflag `-D QUINLED_AN_PENTA` and the below library dependencies.
+
+ESP32 (**without** ethernet):
+```
+[env:custom_esp32dev_usermod_quinled_an_penta]
+extends = env:esp32dev
+build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D QUINLED_AN_PENTA
+lib_deps = ${esp32.lib_deps}
+ olikraus/U8g2@~2.28.8
+ robtillaart/SHT85@~0.2.0
+```
+
+ESP32 (**with** ethernet):
+```
+[env:custom_esp32dev_usermod_quinled_an_penta]
+extends = env:esp32dev
+build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D WLED_USE_ETHERNET -D QUINLED_AN_PENTA
+lib_deps = ${esp32.lib_deps}
+ olikraus/U8g2@~2.28.8
+ robtillaart/SHT85@~0.2.0
+```
+
+## Some words about the (optional) OLED
+This mod has been optimized for an SSD1306 driven 128x64 OLED. Using a smaller OLED or an OLED using a different driver will result in unexpected results.
+I highly recommend using these "two color monochromatic OLEDs", which have the first 16 pixels in a different color than the other 48, e.g. a yellow/blue OLED.
+Also note, you need to have an **SPI** driven OLED, **not i2c**!
+
+### Limitations combined with Ethernet
+The initial development of this mod had been done with a beta version of the QuinLED-An-Penta, which had a different IO layout for the OLED: The CS pin used to be IO_0, but has been changed to IO27 with the first v1 public release. Unfortunately, IO27 is used by the Ethernet boards, so WLED will not let you enable the OLED screen, if you're using it with Ethernet. This unfortunately makes the development I've done to support/show Ethernet information void, as it cannot be used.
+However (and I've not tried this, as I don't own a v1 board): You can try to modify this mod and try to use IO27 for the OLED and share it with the Ethernet board. It is "just" the chip select pin, so there is a chance that both can coexist and use the same IO. You need to skip WLEDs PinManager for the CS pin, so WLED will not block using it. If you don't know how this works: Leave it. If you know what I'm talking about: Try it and please let me know on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
+
+### My OLED flickers after some time, what should I do?
+That's a tricky one: During development I saw that the OLED sometimes starts to "bug out" / flicker and won't work anymore. This seems to be caused by the high PWM interference the board produces. It seems to loose its settings and then doesn't know how to draw anymore. Turns out the only way to fix this is to call the libraries `begin()` method again which will re-initialize the display.
+If you're facing this issue, you can enable a setting I've added which will call the `begin()` roughly every 60 seconds between a page change. This will make the page change take ~500ms, but will fix the display.
+
+
+## Configuration
+Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D QUINLED_AN_PENTA`, you will see the config for it there:
+* Enable-OLED:
+ * What it does: Enables the optional SPI driven OLED that can be mounted to the 7-pin female header. Won't work with Ethernet, read above.
+ * Possible values: Enabled/Disabled
+ * Default: Disabled
+* OLED-Use-Progress-Bars:
+ * What it does: Toggle between showing percentage numbers or a progress-bar-like visualization for overall brightness and each LED channels brightness level
+ * Possible values: Enabled/Disabled
+ * Default: Disabled
+* OLED-Flip-Screen-180:
+ * What it does: Flips the screen 180° / upside-down
+ * Possible values: Enabled/Disabled
+ * Default: Disabled
+* OLED-Seconds-Per-Page:
+ * What it does: Defines how long the OLED should stay on one page in seconds before changing to the next
+ * Possible values: Enabled/Disabled
+ * Default: 10
+* OLED-Fix-Bugged-Screen:
+ * What it does: Enable this if your OLED flickers after some time. For more info read above under ["My OLED flickers after some time, what should I do?"](#My-OLED-flickers-after-some-time-what-should-I-do)
+ * Possible values: Enabled/Disabled
+ * Default: Disabled
+* Enable-SHT30-Temp-Humidity-Sensor:
+ * What it does: Enables the onboard SHT30 temperature and humidity sensor
+ * Possible values: Enabled/Disabled
+ * Default: Disabled
+
+## Change log
+2021-12
+* Adjusted IO layout to match An-Penta v1r1
+2021-10
+* First implementation.
+
+## Credits
+ezcGman | Andy: Find me on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
\ No newline at end of file
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/README.md b/usermods/quinled_digquad_preassembled_unofficial_v0.1/README.md
deleted file mode 100644
index 39ae4edd11..0000000000
--- a/usermods/quinled_digquad_preassembled_unofficial_v0.1/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# QuinLED-Dig-Quad Preassembled Unofficial Build
-
-This usermod targets the [Preassembled QuinLED-Dig-Quad](https://quinled.info/pre-assembled-quinled-dig-quad/). Tested on board revision v1r6b,
-and includes the following features:
-
- * **Multi-channel Support** - enabling use of LED1, LED2, LED3, LED4 pins to work using segments
- * **Temperature Sensor Support** - pulls readings from the built-in temperature sensor and adds the reading to the *Info* page in the UI
-
-## Background
-
-As a starting point, you should check out this awesome video from Quindor: [How to compile WLED yourself](https://quinled.info/2020/12/22/livestream-wled-compile/). The usermod you are reading now just provides some shortcuts for parts of what were covered in that video.
-
-## Build Firmware with Multi-channel and Temp Support
-
-1. Copy the `platformio_override.ini` file to the project's root directory
-1. If using VS Code with the PlatformIO plugin like in the video, you will now see this new project task listed in the PLATFORMIO panel at the bottom as `env:QL-DigQuad-Pre-v0.1` (you probably need to hit the refresh button)
-
-
-
-1. Edit this file from the root directory as needed:
-
-
-
- * `PIXEL_COUNTS` may need to be adjusted for your set-up. E.g. I have lots of LEDs in Channel 1, but that's probably unusual for most
- * `DATA_PINS` may need to be changed to "16,3,1,26" instead of "16,1,3,26" apparently depending on the board revision or some such
-
-1. Build the mod (e.g. click `Build` from the project task circled above) and update your firmware using the `QL-DigQuad-Pre-v0.1` file, e.g. using _Manual OTA_ from the Config menu. Based on the video and my own experience, you might need to build twice 🤷♂️.
-
-## Observing Temperature
-
-Hopefully you can now see the Temperature listed in the Info page. If not, use Chrome Developer Tools to find the current temperature
-
-1. Open the Developer Tools Console
-2. Enter `lastinfo.u.Temperature` to view the Temperature array
-
-
-
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/json-temp.png b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/json-temp.png
deleted file mode 100644
index 66e5011233..0000000000
Binary files a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/json-temp.png and /dev/null differ
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/params.png b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/params.png
deleted file mode 100644
index 64233f86bf..0000000000
Binary files a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/params.png and /dev/null differ
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/pio-screenshot.png b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/pio-screenshot.png
deleted file mode 100644
index e178ed1601..0000000000
Binary files a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/pio-screenshot.png and /dev/null differ
diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/platformio_override.ini b/usermods/quinled_digquad_preassembled_unofficial_v0.1/platformio_override.ini
deleted file mode 100644
index 6f416668fa..0000000000
--- a/usermods/quinled_digquad_preassembled_unofficial_v0.1/platformio_override.ini
+++ /dev/null
@@ -1,16 +0,0 @@
-; QuinLED-Dig-Quad Preassembled Unofficial
-
-[env:QL-DigQuad-Pre-v0.1]
-extends = env:esp32dev
-build_flags = ${common.build_flags_esp32}
- -D ESP32_MULTISTRIP
- -D NUM_STRIPS=4
- -D PIXEL_COUNTS="600, 300, 300, 300"
- -D DATA_PINS="16,1,3,26"
- -D RLYPIN=19
- -D BTNPIN=17
- -D USERMOD_DALLASTEMPERATURE
- -D USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL=10000
-lib_deps = ${env.lib_deps}
- milesburton/DallasTemperature@^3.9.0
- OneWire@~2.3.5
\ No newline at end of file
diff --git a/usermods/seven_segment_display_reloaded/readme.md b/usermods/seven_segment_display_reloaded/readme.md
new file mode 100644
index 0000000000..09479754c1
--- /dev/null
+++ b/usermods/seven_segment_display_reloaded/readme.md
@@ -0,0 +1,129 @@
+# Seven Segment Display Reloaded
+
+Usermod that uses the overlay feature to create a configurable seven segment display.
+Optimized for maximum configurability and use with seven segment clocks by parallyze (https://www.instructables.com/member/parallyze/instructables/)
+Very loosely based on the existing usermod "seven segment display".
+
+
+## Installation
+
+Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SSDR` in `my_config.h`.
+
+For the auto brightness option, the usermod SN_Photoresistor has to be installed as well. See SN_Photoresistor/readme.md for instructions.
+
+## Settings
+All settings can be controlled the usermod setting page.
+Part of the settings can be controlled through MQTT with a raw payload or through a json request to /json/state.
+
+### enabled
+Enables/disables this overlay usermod
+
+### inverted
+Enables the inverted mode in which the background should be enabled and the digits should be black (leds off)
+
+### Colon-blinking
+Enables the blinking colon(s) if they are defined
+
+### enable-auto-brightness
+Enables the auto brightness feature. Can be only used with the usermod SN_Photoresistor installed.
+
+### auto-brightness-min / auto-brightness-max
+The lux value calculated from usermod SN_Photoresistor will be mapped to the values defined here.
+The mapping is 0 - 1000 lux will be mapped to auto-brightness-min - auto-brightness-max
+
+The mA current protection of WLED will override the calculated value if it is too high.
+
+### Display-Mask
+Defines the type of the time/date display.
+For example "H:m" (default)
+- H - 00-23 hours
+- h - 01-12 hours
+- k - 01-24 hours
+- m - 00-59 minutes
+- s - 00-59 seconds
+- d - 01-31 day of month
+- M - 01-12 month
+- y - 21 last two positions of year
+- Y - 2021 year
+- : for a colon
+
+### LED-Numbers
+- LED-Numbers-Hours
+- LED-Numbers-Minutes
+- LED-Numbers-Seconds
+- LED-Numbers-Colons
+- LED-Numbers-Day
+- LED-Numbers-Month
+- LED-Numbers-Year
+
+See following example for usage.
+
+
+## Example
+
+Example for Leds definition
+```
+ < A >
+/\ /\
+F B
+\/ \/
+ < G >
+/\ /\
+E C
+\/ \/
+ < D >
+```
+
+Leds or Range of Leds are seperated by a comma ","
+
+Segments are seperated by a semicolon ";" and are read as A;B;C;D;E;F;G
+
+Digits are seperated by colon ":" -> A;B;C;D;E;F;G:A;B;C;D;E;F;G
+
+Ranges are defined as lower to higher (lower first)
+
+For example, an clock definition for the following clock (https://www.instructables.com/Lazy-7-Quick-Build-Edition/) is
+
+- hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
+
+- minute "37-38;39-40;42-43;44,31;32-33;35-36;34,41:21-22;23-24;26-27;28,15;16-17;19-20;18,25"
+
+or
+
+- hour "6,7;8,9;11,12;13,0;1,2;4,5;3,10:52,53;54,55;57,58;59,46;47,48;50,51;49,56"
+
+- minute "15,28;16,17;19,20;21,22;23,24;26,27;18,25:31,44;32,33;35,36;37,38;39,40;42,43;34,41"
+
+depending on the orientation.
+
+# The example detailed:
+hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
+
+there are two digits seperated by ":"
+
+- 59,46;47-48;50-51;52-53;54-55;57-58;49,56
+- 0,13;1-2;4-5;6-7;8-9;11-12;3,10
+
+In the first digit,
+the **segment A** consists of the leds number **59 and 46**., **segment B** consists of the leds number **47, 48** and so on
+
+The second digit starts again with **segment A** and leds **0 and 13**, **segment B** consists of the leds number **1 and 2** and so on
+
+### first digit of the hour
+- Segment A: 59, 46
+- Segment B: 47, 48
+- Segment C: 50, 51
+- Segment D: 52, 53
+- Segment E: 54, 55
+- Segment F: 57, 58
+- Segment G: 49, 56
+
+### second digit of the hour
+
+- Segment A: 0, 13
+- Segment B: 1, 2
+- Segment C: 4, 5
+- Segment D: 6, 7
+- Segment E: 8, 9
+- Segment F: 11, 12
+- Segment G: 3, 10
\ No newline at end of file
diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h
new file mode 100644
index 0000000000..6274abceec
--- /dev/null
+++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h
@@ -0,0 +1,555 @@
+#pragma once
+
+#include "wled.h"
+
+class UsermodSSDR : public Usermod {
+
+//#define REFRESHTIME 497
+
+private:
+ //Runtime variables.
+ unsigned long umSSDRLastRefresh = 0;
+ unsigned long umSSDRResfreshTime = 3000;
+ bool umSSDRDisplayTime = false;
+ bool umSSDRInverted = false;
+ bool umSSDRColonblink = true;
+ bool umSSDREnableLDR = false;
+ String umSSDRHours = "";
+ String umSSDRMinutes = "";
+ String umSSDRSeconds = "";
+ String umSSDRColons = "";
+ String umSSDRDays = "";
+ String umSSDRMonths = "";
+ String umSSDRYears = "";
+ uint16_t umSSDRLength = 0;
+ uint16_t umSSDRBrightnessMin = 0;
+ uint16_t umSSDRBrightnessMax = 255;
+
+ bool* umSSDRMask = 0;
+
+ /*// H - 00-23 hours
+ // h - 01-12 hours
+ // k - 01-24 hours
+ // m - 00-59 minutes
+ // s - 00-59 seconds
+ // d - 01-31 day of month
+ // M - 01-12 month
+ // y - 21 last two positions of year
+ // Y - 2021 year
+ // : for a colon
+ */
+ String umSSDRDisplayMask = "H:m"; //This should reflect physical equipment.
+
+ /* Segment order, seen from the front:
+
+ < A >
+ /\ /\
+ F B
+ \/ \/
+ < G >
+ /\ /\
+ E C
+ \/ \/
+ < D >
+
+ */
+
+ uint8_t umSSDRNumbers[11][7] = {
+ // A B C D E F G
+ { 1, 1, 1, 1, 1, 1, 0 }, // 0
+ { 0, 1, 1, 0, 0, 0, 0 }, // 1
+ { 1, 1, 0, 1, 1, 0, 1 }, // 2
+ { 1, 1, 1, 1, 0, 0, 1 }, // 3
+ { 0, 1, 1, 0, 0, 1, 1 }, // 4
+ { 1, 0, 1, 1, 0, 1, 1 }, // 5
+ { 1, 0, 1, 1, 1, 1, 1 }, // 6
+ { 1, 1, 1, 0, 0, 0, 0 }, // 7
+ { 1, 1, 1, 1, 1, 1, 1 }, // 8
+ { 1, 1, 1, 1, 0, 1, 1 }, // 9
+ { 0, 0, 0, 0, 0, 0, 0 } // blank
+ };
+
+ //String to reduce flash memory usage
+ static const char _str_name[];
+ static const char _str_ldrEnabled[];
+ static const char _str_timeEnabled[];
+ static const char _str_inverted[];
+ static const char _str_colonblink[];
+ static const char _str_displayMask[];
+ static const char _str_hours[];
+ static const char _str_minutes[];
+ static const char _str_seconds[];
+ static const char _str_colons[];
+ static const char _str_days[];
+ static const char _str_months[];
+ static const char _str_years[];
+ static const char _str_minBrightness[];
+ static const char _str_maxBrightness[];
+
+#ifdef USERMOD_SN_PHOTORESISTOR
+ Usermod_SN_Photoresistor *ptr;
+#else
+ void* ptr = nullptr;
+#endif
+
+ void _overlaySevenSegmentDraw() {
+ int displayMaskLen = static_cast(umSSDRDisplayMask.length());
+ bool colonsDone = false;
+ _setAllFalse();
+ for (int index = 0; index < displayMaskLen; index++) {
+ int timeVar = 0;
+ switch (umSSDRDisplayMask[index]) {
+ case 'h':
+ timeVar = hourFormat12(localTime);
+ _showElements(&umSSDRHours, timeVar, 0, 1);
+ break;
+ case 'H':
+ timeVar = hour(localTime);
+ _showElements(&umSSDRHours, timeVar, 0, 1);
+ break;
+ case 'k':
+ timeVar = hour(localTime) + 1;
+ _showElements(&umSSDRHours, timeVar, 0, 0);
+ break;
+ case 'm':
+ timeVar = minute(localTime);
+ _showElements(&umSSDRMinutes, timeVar, 0, 0);
+ break;
+ case 's':
+ timeVar = second(localTime);
+ _showElements(&umSSDRSeconds, timeVar, 0, 0);
+ break;
+ case 'd':
+ timeVar = day(localTime);
+ _showElements(&umSSDRDays, timeVar, 0, 0);
+ break;
+ case 'M':
+ timeVar = month(localTime);
+ _showElements(&umSSDRMonths, timeVar, 0, 0);
+ break;
+ case 'y':
+ timeVar = second(localTime);
+ _showElements(&umSSDRYears, timeVar, 0, 0);
+ break;
+ case 'Y':
+ timeVar = year(localTime);
+ _showElements(&umSSDRYears, timeVar, 0, 0);
+ break;
+ case ':':
+ if (!colonsDone) { // only call _setColons once as all colons are printed when the first colon is found
+ _setColons();
+ colonsDone = true;
+ }
+ break;
+ }
+ }
+ _setMaskToLeds();
+ }
+
+ void _setColons() {
+ if ( umSSDRColonblink ) {
+ if ( second(localTime) % 2 == 0 ) {
+ _showElements(&umSSDRColons, 0, 1, 0);
+ }
+ } else {
+ _showElements(&umSSDRColons, 0, 1, 0);
+ }
+ }
+
+ void _showElements(String *map, int timevar, bool isColon, bool removeZero
+
+) {
+ if (!(*map).equals("") && !(*map) == NULL) {
+ int length = String(timevar).length();
+ bool addZero = false;
+ if (length == 1) {
+ length = 2;
+ addZero = true;
+ }
+ int timeArr[length];
+ if(addZero) {
+ if(removeZero)
+ {
+ timeArr[1] = 10;
+ timeArr[0] = timevar;
+ }
+ else
+ {
+ timeArr[1] = 0;
+ timeArr[0] = timevar;
+ }
+ } else {
+ int count = 0;
+ while (timevar) {
+ timeArr[count] = timevar%10;
+ timevar /= 10;
+ count++;
+ };
+ }
+
+
+ int colonsLen = static_cast((*map).length());
+ int count = 0;
+ int countSegments = 0;
+ int countDigit = 0;
+ bool range = false;
+ int lastSeenLedNr = 0;
+
+ for (int index = 0; index < colonsLen; index++) {
+ switch ((*map)[index]) {
+ case '-':
+ lastSeenLedNr = _checkForNumber(count, index, map);
+ count = 0;
+ range = true;
+ break;
+ case ':':
+ _setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
+ count = 0;
+ range = false;
+ countDigit++;
+ countSegments = 0;
+ break;
+ case ';':
+ _setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
+ count = 0;
+ range = false;
+ countSegments++;
+ break;
+ case ',':
+ _setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
+ count = 0;
+ range = false;
+ break;
+ default:
+ count++;
+ break;
+ }
+ }
+ _setLeds(_checkForNumber(count, colonsLen, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
+ }
+ }
+
+ void _setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon) {
+
+ if ((colon && umSSDRColonblink) || umSSDRNumbers[number][countSegments]) {
+
+ if (range) {
+ for(int i = lastSeenLedNr; i <= lednr; i++) {
+ umSSDRMask[i] = true;
+ }
+ } else {
+ umSSDRMask[lednr] = true;
+ }
+ }
+ }
+
+ void _setMaskToLeds() {
+ for(int i = 0; i <= umSSDRLength; i++) {
+ if ((!umSSDRInverted && !umSSDRMask[i]) || (umSSDRInverted && umSSDRMask[i])) {
+ strip.setPixelColor(i, 0x000000);
+ }
+ }
+ }
+
+ void _setAllFalse() {
+ for(int i = 0; i <= umSSDRLength; i++) {
+ umSSDRMask[i] = false;
+ }
+ }
+
+ int _checkForNumber(int count, int index, String *map) {
+ String number = (*map).substring(index - count, index);
+ return number.toInt();
+ }
+
+ void _publishMQTTint_P(const char *subTopic, int value)
+ {
+ if(mqtt == NULL) return;
+
+ char buffer[64];
+ char valBuffer[12];
+ sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic);
+ sprintf_P(valBuffer, PSTR("%d"), value);
+ mqtt->publish(buffer, 2, true, valBuffer);
+ }
+
+ void _publishMQTTstr_P(const char *subTopic, String Value)
+ {
+ if(mqtt == NULL) return;
+ char buffer[64];
+ sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic);
+ mqtt->publish(buffer, 2, true, Value.c_str(), Value.length());
+ }
+
+ bool _cmpIntSetting_P(char *topic, char *payload, const char *setting, void *value)
+ {
+ if (strcmp_P(topic, setting) == 0)
+ {
+ *((int *)value) = strtol(payload, NULL, 10);
+ _publishMQTTint_P(setting, *((int *)value));
+ return true;
+ }
+ return false;
+ }
+
+ bool _handleSetting(char *topic, char *payload) {
+ if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &umSSDRDisplayTime)) {
+ return true;
+ }
+ if (_cmpIntSetting_P(topic, payload, _str_ldrEnabled, &umSSDREnableLDR)) {
+ return true;
+ }
+ if (_cmpIntSetting_P(topic, payload, _str_inverted, &umSSDRInverted)) {
+ return true;
+ }
+ if (_cmpIntSetting_P(topic, payload, _str_colonblink, &umSSDRColonblink)) {
+ return true;
+ }
+ if (strcmp_P(topic, _str_displayMask) == 0) {
+ umSSDRDisplayMask = String(payload);
+ _publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask);
+ return true;
+ }
+ return false;
+ }
+
+ void _updateMQTT()
+ {
+ _publishMQTTint_P(_str_timeEnabled, umSSDRDisplayTime);
+ _publishMQTTint_P(_str_ldrEnabled, umSSDREnableLDR);
+ _publishMQTTint_P(_str_inverted, umSSDRInverted);
+ _publishMQTTint_P(_str_colonblink, umSSDRColonblink);
+
+ _publishMQTTstr_P(_str_hours, umSSDRHours);
+ _publishMQTTstr_P(_str_minutes, umSSDRMinutes);
+ _publishMQTTstr_P(_str_seconds, umSSDRSeconds);
+ _publishMQTTstr_P(_str_colons, umSSDRColons);
+ _publishMQTTstr_P(_str_days, umSSDRDays);
+ _publishMQTTstr_P(_str_months, umSSDRMonths);
+ _publishMQTTstr_P(_str_years, umSSDRYears);
+ _publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask);
+
+ _publishMQTTint_P(_str_minBrightness, umSSDRBrightnessMin);
+ _publishMQTTint_P(_str_maxBrightness, umSSDRBrightnessMax);
+ }
+
+ void _addJSONObject(JsonObject& root) {
+ JsonObject ssdrObj = root[FPSTR(_str_name)];
+ if (ssdrObj.isNull()) {
+ ssdrObj = root.createNestedObject(FPSTR(_str_name));
+ }
+
+ ssdrObj[FPSTR(_str_timeEnabled)] = umSSDRDisplayTime;
+ ssdrObj[FPSTR(_str_ldrEnabled)] = umSSDREnableLDR;
+ ssdrObj[FPSTR(_str_inverted)] = umSSDRInverted;
+ ssdrObj[FPSTR(_str_colonblink)] = umSSDRColonblink;
+ ssdrObj[FPSTR(_str_displayMask)] = umSSDRDisplayMask;
+ ssdrObj[FPSTR(_str_hours)] = umSSDRHours;
+ ssdrObj[FPSTR(_str_minutes)] = umSSDRMinutes;
+ ssdrObj[FPSTR(_str_seconds)] = umSSDRSeconds;
+ ssdrObj[FPSTR(_str_colons)] = umSSDRColons;
+ ssdrObj[FPSTR(_str_days)] = umSSDRDays;
+ ssdrObj[FPSTR(_str_months)] = umSSDRMonths;
+ ssdrObj[FPSTR(_str_years)] = umSSDRYears;
+ ssdrObj[FPSTR(_str_minBrightness)] = umSSDRBrightnessMin;
+ ssdrObj[FPSTR(_str_maxBrightness)] = umSSDRBrightnessMax;
+ }
+
+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() {
+ umSSDRLength = strip.getLengthTotal();
+ if (umSSDRMask != 0) {
+ umSSDRMask = (bool*) realloc(umSSDRMask, umSSDRLength * sizeof(bool));
+ } else {
+ umSSDRMask = (bool*) malloc(umSSDRLength * sizeof(bool));
+ }
+ _setAllFalse();
+
+ #ifdef USERMOD_SN_PHOTORESISTOR
+ ptr = (Usermod_SN_Photoresistor*) usermods.lookup(USERMOD_ID_SN_PHOTORESISTOR);
+ #endif
+ DEBUG_PRINTLN(F("Setup done"));
+ }
+
+ /*
+ * loop() is called continuously. Here you can check for events, read sensors, etc.
+ */
+ void loop() {
+ if (!umSSDRDisplayTime || strip.isUpdating()) {
+ return;
+ }
+ #ifdef USERMOD_ID_SN_PHOTORESISTOR
+ if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) {
+ if (ptr != nullptr) {
+ uint16_t lux = ptr->getLastLDRValue();
+ uint16_t brightness = map(lux, 0, 1000, umSSDRBrightnessMin, umSSDRBrightnessMax);
+ if (bri != brightness) {
+ bri = brightness;
+ stateUpdated(1);
+ }
+ }
+ umSSDRLastRefresh = millis();
+ }
+ #endif
+ }
+
+ void handleOverlayDraw() {
+ if (umSSDRDisplayTime) {
+ _overlaySevenSegmentDraw();
+ }
+ }
+
+/*
+ * 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) {
+ JsonObject user = root[F("u")];
+ if (user.isNull()) {
+ user = root.createNestedObject(F("u"));
+ }
+ JsonArray enabled = user.createNestedArray("Time enabled");
+ enabled.add(umSSDRDisplayTime);
+ JsonArray invert = user.createNestedArray("Time inverted");
+ invert.add(umSSDRInverted);
+ JsonArray blink = user.createNestedArray("Blinking colon");
+ blink.add(umSSDRColonblink);
+ JsonArray ldrEnable = user.createNestedArray("Auto Brightness enabled");
+ ldrEnable.add(umSSDREnableLDR);
+
+ }
+
+ /*
+ * 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) {
+ JsonObject user = root[F("u")];
+ if (user.isNull()) {
+ user = root.createNestedObject(F("u"));
+ }
+ _addJSONObject(user);
+ }
+
+ /*
+ * 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) {
+ JsonObject user = root[F("u")];
+ if (!user.isNull()) {
+ JsonObject ssdrObj = user[FPSTR(_str_name)];
+ umSSDRDisplayTime = ssdrObj[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime;
+ umSSDREnableLDR = ssdrObj[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR;
+ umSSDRInverted = ssdrObj[FPSTR(_str_inverted)] | umSSDRInverted;
+ umSSDRColonblink = ssdrObj[FPSTR(_str_colonblink)] | umSSDRColonblink;
+ umSSDRDisplayMask = ssdrObj[FPSTR(_str_displayMask)] | umSSDRDisplayMask;
+ }
+ }
+
+ void onMqttConnect(bool sessionPresent) {
+ char subBuffer[48];
+ if (mqttDeviceTopic[0] != 0)
+ {
+ _updateMQTT();
+ //subscribe for sevenseg messages on the device topic
+ sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttDeviceTopic, _str_name);
+ mqtt->subscribe(subBuffer, 2);
+ }
+
+ if (mqttGroupTopic[0] != 0)
+ {
+ //subcribe for sevenseg messages on the group topic
+ sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_name);
+ mqtt->subscribe(subBuffer, 2);
+ }
+ }
+
+ bool onMqttMessage(char *topic, char *payload) {
+ //If topic beings iwth sevenSeg cut it off, otherwise not our message.
+ size_t topicPrefixLen = strlen_P(PSTR("/wledSS/"));
+ if (strncmp_P(topic, PSTR("/wledSS/"), topicPrefixLen) == 0) {
+ topic += topicPrefixLen;
+ } else {
+ return false;
+ }
+ //We only care if the topic ends with /set
+ size_t topicLen = strlen(topic);
+ if (topicLen > 4 &&
+ topic[topicLen - 4] == '/' &&
+ topic[topicLen - 3] == 's' &&
+ topic[topicLen - 2] == 'e' &&
+ topic[topicLen - 1] == 't')
+ {
+ //Trim /set and handle it
+ topic[topicLen - 4] = '\0';
+ _handleSetting(topic, payload);
+ }
+ return true;
+ }
+
+ void addToConfig(JsonObject &root) {
+ _addJSONObject(root);
+ }
+
+ bool readFromConfig(JsonObject &root) {
+ JsonObject top = root[FPSTR(_str_name)];
+
+ if (top.isNull()) {
+ DEBUG_PRINT(FPSTR(_str_name));
+ DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
+ return false;
+ }
+
+ umSSDRDisplayTime = (top[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime);
+ umSSDREnableLDR = (top[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR);
+ umSSDRInverted = (top[FPSTR(_str_inverted)] | umSSDRInverted);
+ umSSDRColonblink = (top[FPSTR(_str_colonblink)] | umSSDRColonblink);
+
+ umSSDRDisplayMask = top[FPSTR(_str_displayMask)] | umSSDRDisplayMask;
+ umSSDRHours = top[FPSTR(_str_hours)] | umSSDRHours;
+ umSSDRMinutes = top[FPSTR(_str_minutes)] | umSSDRMinutes;
+ umSSDRSeconds = top[FPSTR(_str_seconds)] | umSSDRSeconds;
+ umSSDRColons = top[FPSTR(_str_colons)] | umSSDRColons;
+ umSSDRDays = top[FPSTR(_str_days)] | umSSDRDays;
+ umSSDRMonths = top[FPSTR(_str_months)] | umSSDRMonths;
+ umSSDRYears = top[FPSTR(_str_years)] | umSSDRYears;
+ umSSDRBrightnessMin = top[FPSTR(_str_minBrightness)] | umSSDRBrightnessMin;
+ umSSDRBrightnessMax = top[FPSTR(_str_maxBrightness)] | umSSDRBrightnessMax;
+
+ DEBUG_PRINT(FPSTR(_str_name));
+ DEBUG_PRINTLN(F(" config (re)loaded."));
+
+ return true;
+ }
+ /*
+ * 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_SSDR;
+ }
+};
+
+const char UsermodSSDR::_str_name[] PROGMEM = "UsermodSSDR";
+const char UsermodSSDR::_str_timeEnabled[] PROGMEM = "enabled";
+const char UsermodSSDR::_str_inverted[] PROGMEM = "inverted";
+const char UsermodSSDR::_str_colonblink[] PROGMEM = "Colon-blinking";
+const char UsermodSSDR::_str_displayMask[] PROGMEM = "Display-Mask";
+const char UsermodSSDR::_str_hours[] PROGMEM = "LED-Numbers-Hours";
+const char UsermodSSDR::_str_minutes[] PROGMEM = "LED-Numbers-Minutes";
+const char UsermodSSDR::_str_seconds[] PROGMEM = "LED-Numbers-Seconds";
+const char UsermodSSDR::_str_colons[] PROGMEM = "LED-Numbers-Colons";
+const char UsermodSSDR::_str_days[] PROGMEM = "LED-Numbers-Day";
+const char UsermodSSDR::_str_months[] PROGMEM = "LED-Numbers-Month";
+const char UsermodSSDR::_str_years[] PROGMEM = "LED-Numbers-Year";
+const char UsermodSSDR::_str_ldrEnabled[] PROGMEM = "enable-auto-brightness";
+const char UsermodSSDR::_str_minBrightness[] PROGMEM = "auto-brightness-min";
+const char UsermodSSDR::_str_maxBrightness[] PROGMEM = "auto-brightness-max";
diff --git a/usermods/ssd1306_i2c_oled_u8g2/README.md b/usermods/ssd1306_i2c_oled_u8g2/README.md
deleted file mode 100644
index 70919cc54e..0000000000
--- a/usermods/ssd1306_i2c_oled_u8g2/README.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# SSD1306 128x32 OLED via I2C with u8g2
-This usermod allows to connect 128x32 Oled display to WLED controlled and show
-the next information:
-- Current SSID
-- IP address if obtained
- * in AP mode and turned off lightning AP password is shown
-- Current effect
-- Current palette
-- On/Off icon (sun/moon)
-
-## Hardware
-![Hardware connection](assets/hw_connection.png)
-
-## Requirements
-Functionality checked with:
-- commit 095429a7df4f9e2b34dd464f7bbfd068df6558eb
-- Wemos d1 mini
-- PlatformIO
-- Generic SSD1306 128x32 I2C OLED display from aliexpress
-
-### Platformio
-Add `U8g2@~2.27.2` dependency to `lib_deps_external` under `[common]` section in `platformio.ini`:
-```ini
-# platformio.ini
-...
-[common]
-...
-lib_deps_external =
- ...
- U8g2@~2.27.2
-...
-```
-
-### Arduino IDE
-Install library `U8g2 by oliver` in `Tools | Include Library | Manage libraries` menu.
\ No newline at end of file
diff --git a/usermods/ssd1306_i2c_oled_u8g2/assets/hw_connection.png b/usermods/ssd1306_i2c_oled_u8g2/assets/hw_connection.png
deleted file mode 100644
index a0e51b4de4..0000000000
Binary files a/usermods/ssd1306_i2c_oled_u8g2/assets/hw_connection.png and /dev/null differ
diff --git a/usermods/ssd1306_i2c_oled_u8g2/wled06_usermod.ino b/usermods/ssd1306_i2c_oled_u8g2/wled06_usermod.ino
deleted file mode 100644
index 8c749603f8..0000000000
--- a/usermods/ssd1306_i2c_oled_u8g2/wled06_usermod.ino
+++ /dev/null
@@ -1,175 +0,0 @@
-#include // from https://github.com/olikraus/u8g2/
-
-//The SCL and SDA pins are defined here.
-//Lolin32 boards use SCL=5 SDA=4
-#define U8X8_PIN_SCL 5
-#define U8X8_PIN_SDA 4
-
-
-// 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
-U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL,
- U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
-
-// gets called once at boot. Do all initialization that doesn't depend on
-// network here
-void userSetup() {
- u8x8.begin();
- u8x8.setPowerSave(0);
- u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
- u8x8.setFont(u8x8_font_chroma48medium8_r);
- u8x8.drawString(0, 0, "Loading...");
-}
-
-// gets called every time WiFi is (re-)connected. Initialize own network
-// interfaces here
-void userConnected() {}
-
-// needRedraw marks if redraw is required to prevent often redrawing.
-bool needRedraw = true;
-
-// Next variables hold the previous known values to determine if redraw is
-// required.
-String knownSsid = "";
-IPAddress knownIp;
-uint8_t knownBrightness = 0;
-uint8_t knownMode = 0;
-uint8_t knownPalette = 0;
-
-long lastUpdate = 0;
-long lastRedraw = 0;
-bool displayTurnedOff = false;
-// How often we are redrawing screen
-#define USER_LOOP_REFRESH_RATE_MS 5000
-
-void userLoop() {
-
- // Check if we time interval for redrawing passes.
- if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) {
- return;
- }
- lastUpdate = millis();
-
- // Turn off display after 3 minutes with no change.
- if(!displayTurnedOff && millis() - lastRedraw > 3*60*1000) {
- u8x8.setPowerSave(1);
- displayTurnedOff = true;
- }
-
- // Check if values which are shown on display changed from the last time.
- if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) {
- needRedraw = true;
- } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) {
- needRedraw = true;
- } else if (knownBrightness != bri) {
- needRedraw = true;
- } else if (knownMode != strip.getMode()) {
- needRedraw = true;
- } else if (knownPalette != strip.getSegment(0).palette) {
- needRedraw = true;
- }
-
- if (!needRedraw) {
- return;
- }
- needRedraw = false;
-
- if (displayTurnedOff)
- {
- u8x8.setPowerSave(0);
- displayTurnedOff = false;
- }
- lastRedraw = millis();
-
- // Update last known values.
- #if defined(ESP8266)
- knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
- #else
- knownSsid = WiFi.SSID();
- #endif
- knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
- knownBrightness = bri;
- knownMode = strip.getMode();
- knownPalette = strip.getSegment(0).palette;
-
- u8x8.clear();
- u8x8.setFont(u8x8_font_chroma48medium8_r);
-
- // First row with Wifi name
- u8x8.setCursor(1, 0);
- u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
- // Print `~` char to indicate that SSID is longer, than owr dicplay
- if (knownSsid.length() > u8x8.getCols())
- u8x8.print("~");
-
- // Second row with IP or Psssword
- u8x8.setCursor(1, 1);
- // Print password in AP mode and if led is OFF.
- if (apActive && bri == 0)
- u8x8.print(apPass);
- else
- u8x8.print(knownIp);
-
- // Third row with mode name
- u8x8.setCursor(2, 2);
- uint8_t qComma = 0;
- bool insideQuotes = false;
- uint8_t printedChars = 0;
- char singleJsonSymbol;
- // Find the mode name in JSON
- for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) {
- singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
- switch (singleJsonSymbol) {
- case '"':
- insideQuotes = !insideQuotes;
- break;
- case '[':
- case ']':
- break;
- case ',':
- qComma++;
- default:
- if (!insideQuotes || (qComma != knownMode))
- break;
- u8x8.print(singleJsonSymbol);
- printedChars++;
- }
- if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
- break;
- }
- // Fourth row with palette name
- u8x8.setCursor(2, 3);
- qComma = 0;
- insideQuotes = false;
- printedChars = 0;
- // Looking for palette name in JSON.
- for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) {
- singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
- switch (singleJsonSymbol) {
- case '"':
- insideQuotes = !insideQuotes;
- break;
- case '[':
- case ']':
- break;
- case ',':
- qComma++;
- default:
- if (!insideQuotes || (qComma != knownPalette))
- break;
- u8x8.print(singleJsonSymbol);
- printedChars++;
- }
- if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
- break;
- }
-
- u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
- u8x8.drawGlyph(0, 0, 80); // wifi icon
- u8x8.drawGlyph(0, 1, 68); // home icon
- u8x8.setFont(u8x8_font_open_iconic_weather_2x2);
- u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon
-}
diff --git a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h
index 67c78feed1..08d551be02 100644
--- a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h
+++ b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h
@@ -111,7 +111,7 @@ class StairwayWipeUsermod : public Usermod {
transitionDelayTemp = 4000; //fade out slowly
#endif
bri = 0;
- colorUpdated(CALL_MODE_NOTIFICATION);
+ stateUpdated(CALL_MODE_NOTIFICATION);
wipeState = 0;
userVar0 = 0;
previousUserVar0 = 0;
diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino
index a0dcc3bb21..eeece4438a 100644
--- a/usermods/stairway_wipe_basic/wled06_usermod.ino
+++ b/usermods/stairway_wipe_basic/wled06_usermod.ino
@@ -104,7 +104,7 @@ void turnOff()
transitionDelayTemp = 4000; //fade out slowly
#endif
bri = 0;
- colorUpdated(CALL_MODE_NOTIFICATION);
+ stateUpdated(CALL_MODE_NOTIFICATION);
wipeState = 0;
userVar0 = 0;
previousUserVar0 = 0;
diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h
index f2864362c9..c289cc32ad 100644
--- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h
+++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h
@@ -64,7 +64,7 @@ class AutoSaveUsermod : public Usermod {
PSTR("~ %02d-%02d %02d:%02d:%02d ~"),
month(localTime), day(localTime),
hour(localTime), minute(localTime), second(localTime));
- savePreset(autoSavePreset, true, presetNameBuffer);
+ savePreset(autoSavePreset, presetNameBuffer);
}
void inline displayOverlay() {
@@ -91,8 +91,8 @@ class AutoSaveUsermod : public Usermod {
knownBrightness = bri;
knownEffectSpeed = effectSpeed;
knownEffectIntensity = effectIntensity;
- knownMode = strip.getMode();
- knownPalette = strip.getSegment(0).palette;
+ knownMode = strip.getMainSegment().mode;
+ knownPalette = strip.getMainSegment().palette;
}
// gets called every time WiFi is (re-)connected. Initialize own network
@@ -106,8 +106,8 @@ class AutoSaveUsermod : public Usermod {
if (!autoSaveAfterSec || !enabled || strip.isUpdating() || currentPreset>0) return; // setting 0 as autosave seconds disables autosave
unsigned long now = millis();
- uint8_t currentMode = strip.getMode();
- uint8_t currentPalette = strip.getSegment(0).palette;
+ uint8_t currentMode = strip.getMainSegment().mode;
+ uint8_t currentPalette = strip.getMainSegment().palette;
unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000;
if (knownBrightness != bri) {
diff --git a/usermods/usermod_v2_four_line_display/readme.md b/usermods/usermod_v2_four_line_display/readme.md
index 367f3d7ac5..47518be905 100644
--- a/usermods/usermod_v2_four_line_display/readme.md
+++ b/usermods/usermod_v2_four_line_display/readme.md
@@ -31,9 +31,33 @@ This usermod requires the `U8g2` and `Wire` libraries. See the
`platformio_override.ini.sample` found in the Rotary Encoder
UI usermod folder for how to include these using `platformio_override.ini`.
+## Configuration
+
+* `enabled` - enable/disable usermod
+* `pin` - GPIO pins used for display; I2C displays use Clk & Data; SPI displays can use SCK, MOSI, CS, DC & RST
+* `type` - display type in numeric format
+ * 1 = I2C SSD1306 128x32
+ * 2 = I2C SH1106 128x32
+ * 3 = I2C SSD1306 128x64 (4 double-height lines)
+ * 4 = I2C SSD1305 128x32
+ * 5 = I2C SSD1305 128x64 (4 double-height lines)
+ * 6 = SPI SSD1306 128x32
+ * 7 = SPI SSD1306 128x64 (4 double-height lines)
+* `contrast` - set display contrast (higher contrast may reduce display lifetime)
+* `refreshRateSec` - time in seconds for display refresh
+* `screenTimeOutSec` - screen saver time-out in seconds
+* `flip` - flip/rotate display 180°
+* `sleepMode` - enable/disable screen saver
+* `clockMode` - enable/disable clock display in screen saver mode
+* `i2c-freq-kHz` - I2C clock frequency in kHz (may help reduce dropped frames, range: 400-3400)
+
## Change Log
2021-02
* First public release
+
2021-04
-* Adaptation for runtime configuration.
\ No newline at end of file
+* Adaptation for runtime configuration.
+
+2021-11
+* Added configuration option description.
\ No newline at end of file
diff --git a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h
index 4bd2f59544..88b18b35ed 100644
--- a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h
+++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h
@@ -26,6 +26,10 @@
//The SCL and SDA pins are defined here.
#ifdef ARDUINO_ARCH_ESP32
+ #define HW_PIN_SCL 22
+ #define HW_PIN_SDA 21
+ #define HW_PIN_CLOCKSPI 18
+ #define HW_PIN_DATASPI 23
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 22
#endif
@@ -48,6 +52,10 @@
#define FLD_PIN_RESET 26
#endif
#else
+ #define HW_PIN_SCL 5
+ #define HW_PIN_SDA 4
+ #define HW_PIN_CLOCKSPI 14
+ #define HW_PIN_DATASPI 13
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 5
#endif
@@ -71,6 +79,14 @@
#endif
#endif
+#ifndef FLD_TYPE
+ #ifndef FLD_SPI_DEFAULT
+ #define FLD_TYPE SSD1306
+ #else
+ #define FLD_TYPE SSD1306_SPI
+ #endif
+#endif
+
// When to time out to the clock or blank the screen
// if SLEEP_MODE_ENABLED.
#define SCREEN_TIMEOUT_MS 60*1000 // 1 min
@@ -88,9 +104,9 @@ typedef enum {
FLD_LINE_BRIGHTNESS = 0,
FLD_LINE_EFFECT_SPEED,
FLD_LINE_EFFECT_INTENSITY,
- FLD_LINE_EFFECT_FFT1, //WLEDSR
- FLD_LINE_EFFECT_FFT2, //WLEDSR
- FLD_LINE_EFFECT_FFT3, //WLEDSR
+ FLD_LINE_EFFECT_CUSTOM1, //WLEDSR
+ FLD_LINE_EFFECT_CUSTOM2, //WLEDSR
+ FLD_LINE_EFFECT_CUSTOM3, //WLEDSR
FLD_LINE_MODE,
FLD_LINE_PALETTE,
FLD_LINE_PRESET, //WLEDSR
@@ -129,11 +145,11 @@ class FourLineDisplayUsermod : public Usermod {
#ifndef FLD_SPI_DEFAULT
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
- DisplayType type = SSD1306; // display type
#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
- DisplayType type = SSD1306_SPI; // display type
+ uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz)
#endif
+ DisplayType type = FLD_TYPE; // display type
bool flip = false; // flip display 180°
uint8_t contrast = 10; // screen contrast
uint8_t lineHeight = 1; // 1 row or 2 rows
@@ -151,9 +167,9 @@ class FourLineDisplayUsermod : public Usermod {
uint8_t knownBrightness = 0;
uint8_t knownEffectSpeed = 0;
uint8_t knownEffectIntensity = 0;
- uint8_t knownEffectFFT1 = 0; //WLEDSR
- uint8_t knownEffectFFT2 = 0; //WLEDSR
- uint8_t knownEffectFFT3 = 0; //WLEDSR
+ uint8_t knownEffectCustom1 = 0; //WLEDSR
+ uint8_t knownEffectCustom2 = 0; //WLEDSR
+ uint8_t knownEffectCustom3 = 0; //WLEDSR
uint8_t knownMode = 0;
uint8_t knownPalette = 0;
uint8_t knownMinute = 99;
@@ -198,73 +214,56 @@ class FourLineDisplayUsermod : public Usermod {
Wire.begin(FLD_PIN_SDA, FLD_PIN_SCL); //increase speed to run display
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
- if (type == NONE) {enabled = false; return;}
- if (type == SSD1306_SPI || type == SSD1306_SPI64 || type == SH1106_SPI) {
- PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true}, { ioPin[2], true }, { ioPin[3], true}, { ioPin[4], true }};
- if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
+ if (type == NONE || !enabled) return;
+
+ bool isHW;
+ PinOwner po = PinOwner::UM_FourLineDisplay;
+ if (type == SSD1306_SPI || type == SSD1306_SPI64) {
+ isHW = (ioPin[0]==HW_PIN_CLOCKSPI && ioPin[1]==HW_PIN_DATASPI);
+ PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true }};
+ if (!pinManager.allocateMultiplePins(pins, 5, po)) { type=NONE; return; }
} else {
- PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true} };
- if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
+ isHW = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA);
+ PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } };
+ if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
+ if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; }
}
+
DEBUG_PRINTLN(F("Allocating display."));
switch (type) {
case SSD1306:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
+ 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
lineHeight = 1;
break;
case SH1106:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
+ 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
lineHeight = 2;
break;
case SSD1306_64:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
+ 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
lineHeight = 2;
break;
case SSD1305:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
+ 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
lineHeight = 1;
break;
case SSD1305_64:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
+ 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
lineHeight = 2;
break;
case SSD1306_SPI:
- if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
- 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
+ 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
lineHeight = 1;
break;
case SSD1306_SPI64:
- if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
- 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
+ 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
lineHeight = 2;
break;
case SH1106_SPI:
@@ -277,9 +276,10 @@ class FourLineDisplayUsermod : public Usermod {
default:
u8x8 = nullptr;
}
+
if (nullptr == u8x8) {
DEBUG_PRINTLN(F("Display init failed."));
- for (byte i=0; i<5 && ioPin[i]>=0; i++) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
+ pinManager.deallocateMultiplePins((const uint8_t*)ioPin, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po);
type = NONE;
enabled = false;
return;
@@ -287,7 +287,7 @@ class FourLineDisplayUsermod : public Usermod {
initDone = true;
DEBUG_PRINTLN(F("Starting display."));
- if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too
+ /*if (!(type == SSD1306_SPI || type == SSD1306_SPI64))*/ u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin();
setFlipMode(flip);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
@@ -303,7 +303,8 @@ class FourLineDisplayUsermod : public Usermod {
* Da loop.
*/
void loop() {
- if (!enabled) return;
+ if (!enabled || millis() - lastUpdate < (clockMode?1000:refreshRate) || strip.isUpdating()) return;
+ lastUpdate = millis();
// WWLEDSR: redraw if
// -- timer and (forcedAutoUpdate or strip idle) and autoRedraw
@@ -312,51 +313,48 @@ class FourLineDisplayUsermod : public Usermod {
// OR
// -- sleepmode and screentimeout
// Note wakeDispay (used by rotaty) triggers its own redraw
- if (millis() - lastUpdate >= (clockMode?1000:refreshRate)) {
- lastUpdate = millis();
- if ( ((forceAutoRedraw || !strip.isUpdating()) && !noAutoRedraw) || checkChangedType() != FLD_LINE_NULL || (sleepMode && (millis() - lastRedraw > screenTimeout)))
- redraw(false);
- }
+ if ( ((forceAutoRedraw || !strip.isUpdating()) && !noAutoRedraw) || checkChangedType() != FLD_LINE_NULL || (sleepMode && (millis() - lastRedraw > screenTimeout)))
+ redraw(false);
}
/**
* Wrappers for screen drawing
*/
void setFlipMode(uint8_t mode) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setFlipMode(mode);
}
void setContrast(uint8_t contrast) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setContrast(contrast);
}
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setFont(u8x8_font_chroma48medium8_r);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string);
else u8x8->drawString(col, row, string);
}
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->draw2x2String(col, row, string);
}
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setFont(font);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph);
else u8x8->drawGlyph(col, row, glyph);
}
uint8_t getCols() {
- if (type==NONE) return 0;
+ if (type==NONE || !enabled) return 0;
return u8x8->getCols();
}
void clear() {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->clear();
}
void setPowerSave(uint8_t save) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setPowerSave(save);
}
@@ -372,15 +370,15 @@ class FourLineDisplayUsermod : public Usermod {
return FLD_LINE_EFFECT_SPEED;
else if (knownEffectIntensity != effectIntensity)
return FLD_LINE_EFFECT_INTENSITY;
- else if (knownEffectFFT1 != effectFFT1)
- return FLD_LINE_EFFECT_FFT1;
- else if (knownEffectFFT2 != effectFFT2)
- return FLD_LINE_EFFECT_FFT2;
- else if (knownEffectFFT3 != effectFFT3)
- return FLD_LINE_EFFECT_FFT3;
- else if (knownMode != strip.getMode())
+ else if (knownEffectCustom1 != effectCustom1)
+ return FLD_LINE_EFFECT_CUSTOM1;
+ else if (knownEffectCustom2 != effectCustom2)
+ return FLD_LINE_EFFECT_CUSTOM2;
+ else if (knownEffectCustom3 != effectCustom3)
+ return FLD_LINE_EFFECT_CUSTOM3;
+ else if (knownMode != strip.getMainSegment().mode)
return FLD_LINE_MODE;
- else if (knownPalette != strip.getSegment(0).palette)
+ else if (knownPalette != strip.getMainSegment().palette)
return FLD_LINE_PALETTE;
else if (knownSquelch != soundSquelch)
return FLD_LINE_SQUELCH;
@@ -411,8 +409,7 @@ class FourLineDisplayUsermod : public Usermod {
static bool showName = false;
unsigned long now = millis();
- if (type==NONE) return;
-
+ if (type == NONE || !enabled) return;
if (overlayUntil > 0) {
if (now >= overlayUntil) {
// Time to display the overlay has elapsed.
@@ -426,14 +423,17 @@ class FourLineDisplayUsermod : public Usermod {
}
// Check if values which are shown on display changed from the last time.
- Line4Type changed = checkChangedType();
- if (forceRedraw || changed != FLD_LINE_NULL) {
- knownHour = 99; // force time update
- if (changed != FLD_LINE_OTHER) //not ip or ssid
- lineType = changed; //WLEDSR: Always show last changed value
- clear();
- }
- else if (!displayTurnedOff && ((now - lastRedraw)/1000)%5 == 0) { //WLEDSR: remove if sleepMode, as rotating should take place independent of sleepmode
+ if (forceRedraw ||
+ (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) ||
+ (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : Network.localIP())) ||
+ (knownBrightness != bri) ||
+ (knownEffectSpeed != effectSpeed) ||
+ (knownEffectIntensity != effectIntensity) ||
+ (knownMode != strip.getMainSegment().mode) ||
+ (knownPalette != strip.getMainSegment().palette)) {
+ knownHour = 99; // force time update
+ lastRedraw = now; // update lastRedraw marker
+ } else if (sleepMode && !displayTurnedOff && ((now - lastRedraw)/1000)%5 == 0) {
// change line every 5s
showName = !showName;
switch (lineType) {
@@ -450,15 +450,15 @@ class FourLineDisplayUsermod : public Usermod {
lineType = FLD_LINE_EFFECT_INTENSITY;
break;
case FLD_LINE_EFFECT_INTENSITY:
- lineType = FLD_LINE_EFFECT_FFT1; //WLEDSR
+ lineType = FLD_LINE_EFFECT_CUSTOM1; //WLEDSR
break;
- case FLD_LINE_EFFECT_FFT1:
- lineType = FLD_LINE_EFFECT_FFT2; //WLEDSR
+ case FLD_LINE_EFFECT_CUSTOM1:
+ lineType = FLD_LINE_EFFECT_CUSTOM2; //WLEDSR
break;
- case FLD_LINE_EFFECT_FFT2:
- lineType = FLD_LINE_EFFECT_FFT3; //WLEDSR
+ case FLD_LINE_EFFECT_CUSTOM2:
+ lineType = FLD_LINE_EFFECT_CUSTOM3; //WLEDSR
break;
- case FLD_LINE_EFFECT_FFT3:
+ case FLD_LINE_EFFECT_CUSTOM3:
lineType = FLD_LINE_PRESET;
break;
case FLD_LINE_PRESET:
@@ -479,12 +479,12 @@ class FourLineDisplayUsermod : public Usermod {
if (lineType == FLD_LINE_EFFECT_SPEED && strlen_P(sliderNames[0]) == 0) //slidername empty
lineType = FLD_LINE_EFFECT_INTENSITY;
if (lineType == FLD_LINE_EFFECT_INTENSITY && strlen_P(sliderNames[1]) == 0)
- lineType = FLD_LINE_EFFECT_FFT1;
- if (lineType == FLD_LINE_EFFECT_FFT1 && strlen_P(sliderNames[2]) == 0)
- lineType = FLD_LINE_EFFECT_FFT2;
- if (lineType == FLD_LINE_EFFECT_FFT2 && strlen_P(sliderNames[3]) == 0)
- lineType = FLD_LINE_EFFECT_FFT3;
- if (lineType == FLD_LINE_EFFECT_FFT3 && strlen_P(sliderNames[4]) == 0)
+ lineType = FLD_LINE_EFFECT_CUSTOM1;
+ if (lineType == FLD_LINE_EFFECT_CUSTOM1 && strlen_P(sliderNames[2]) == 0)
+ lineType = FLD_LINE_EFFECT_CUSTOM2;
+ if (lineType == FLD_LINE_EFFECT_CUSTOM2 && strlen_P(sliderNames[3]) == 0)
+ lineType = FLD_LINE_EFFECT_CUSTOM3;
+ if (lineType == FLD_LINE_EFFECT_CUSTOM3 && strlen_P(sliderNames[4]) == 0)
lineType = FLD_LINE_PRESET;
if (lineType == FLD_LINE_PRESET && currentPreset == -1)
lineType = FLD_LINE_SQUELCH;
@@ -512,13 +512,13 @@ class FourLineDisplayUsermod : public Usermod {
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
knownIp = apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
knownBrightness = bri;
- knownMode = strip.getMode();
- knownPalette = strip.getSegment(0).palette;
+ knownMode = strip.getMainSegment().mode;
+ knownPalette = strip.getMainSegment().palette;
knownEffectSpeed = effectSpeed;
knownEffectIntensity = effectIntensity;
- knownEffectFFT1 = effectFFT1; //WLEDSR
- knownEffectFFT2 = effectFFT2; //WLEDSR
- knownEffectFFT3 = effectFFT3; //WLEDSR
+ knownEffectCustom1 = effectCustom1; //WLEDSR
+ knownEffectCustom2 = effectCustom2; //WLEDSR
+ knownEffectCustom3 = effectCustom3; //WLEDSR
knownSquelch = soundSquelch;
knownGain = sampleGain;
@@ -574,13 +574,13 @@ class FourLineDisplayUsermod : public Usermod {
case FLD_LINE_EFFECT_INTENSITY:
drawGlyph(0, (clockMode?2:3)*lineHeight, 78, u8x8_font_open_iconic_thing_1x1); // kind of fire icon
break;
- case FLD_LINE_EFFECT_FFT1:
+ case FLD_LINE_EFFECT_CUSTOM1:
drawGlyph(0, (clockMode?2:3)*lineHeight, 68, u8x8_font_open_iconic_weather_1x1); // star icon
break;
- case FLD_LINE_EFFECT_FFT2:
+ case FLD_LINE_EFFECT_CUSTOM2:
drawGlyph(0, (clockMode?2:3)*lineHeight, 68, u8x8_font_open_iconic_weather_1x1); // star icon
break;
- case FLD_LINE_EFFECT_FFT3:
+ case FLD_LINE_EFFECT_CUSTOM3:
drawGlyph(0, (clockMode?2:3)*lineHeight, 68, u8x8_font_open_iconic_weather_1x1); // star icon
break;
case FLD_LINE_PRESET:
@@ -605,6 +605,7 @@ class FourLineDisplayUsermod : public Usermod {
//WLEDSR: Use custom slidernames
void drawLine(uint8_t line, Line4Type lineType) {
char lineBuffer[LINE_BUFFER_SIZE];
+ uint8_t printedChars;
switch(lineType) {
case FLD_LINE_BRIGHTNESS:
sprintf_P(lineBuffer, PSTR("Brightness %3d"), bri);
@@ -618,16 +619,16 @@ class FourLineDisplayUsermod : public Usermod {
sprintf_P(lineBuffer, PSTR("%.11s %3d"), sliderNames[1], effectIntensity);
drawString(1, line*lineHeight, lineBuffer);
break;
- case FLD_LINE_EFFECT_FFT1: //WLEDSR
- sprintf_P(lineBuffer, PSTR("%.11s %3d"), sliderNames[2], effectFFT1);
+ case FLD_LINE_EFFECT_CUSTOM1: //WLEDSR
+ sprintf_P(lineBuffer, PSTR("%.11s %3d"), sliderNames[2], effectCustom1);
drawString(1, line*lineHeight, lineBuffer);
break;
- case FLD_LINE_EFFECT_FFT2: //WLEDSR
- sprintf_P(lineBuffer, PSTR("%.11s %3d"), sliderNames[3], effectFFT2);
+ case FLD_LINE_EFFECT_CUSTOM2: //WLEDSR
+ sprintf_P(lineBuffer, PSTR("%.11s %3d"), sliderNames[3], effectCustom2);
drawString(1, line*lineHeight, lineBuffer);
break;
- case FLD_LINE_EFFECT_FFT3: //WLEDSR
- sprintf_P(lineBuffer, PSTR("%.11s %3d"), sliderNames[4], effectFFT3);
+ case FLD_LINE_EFFECT_CUSTOM3: //WLEDSR
+ sprintf_P(lineBuffer, PSTR("%.11s %3d"), sliderNames[4], effectCustom3);
drawString(1, line*lineHeight, lineBuffer);
break;
case FLD_LINE_PRESET:
@@ -635,10 +636,16 @@ class FourLineDisplayUsermod : public Usermod {
drawString(1, line*lineHeight, lineBuffer);
break;
case FLD_LINE_MODE:
- showCurrentEffectOrPalette(knownMode, JSON_mode_names, line);
+ printedChars = extractModeName(knownMode, JSON_mode_names, lineBuffer, LINE_BUFFER_SIZE-1);
+ for (;printedChars < getCols()-2 && printedChars < LINE_BUFFER_SIZE-3; printedChars++) lineBuffer[printedChars]=' ';
+ lineBuffer[printedChars] = 0;
+ drawString(2, line*lineHeight, lineBuffer);
break;
case FLD_LINE_PALETTE:
- showCurrentEffectOrPalette(knownPalette, JSON_palette_names, line);
+ printedChars = extractModeName(knownPalette, JSON_palette_names, lineBuffer, LINE_BUFFER_SIZE-1);
+ for (;printedChars < getCols()-2 && printedChars < LINE_BUFFER_SIZE-3; printedChars++) lineBuffer[printedChars]=' ';
+ lineBuffer[printedChars] = 0;
+ drawString(2, line*lineHeight, lineBuffer);
break;
case FLD_LINE_SQUELCH:
sprintf_P(lineBuffer, PSTR("Squelch %3d"), soundSquelch);
@@ -779,6 +786,7 @@ class FourLineDisplayUsermod : public Usermod {
* to wake up the screen.
*/
bool wakeDisplay() {
+ if (type == NONE || !enabled) return false;
knownHour = 99;
if (displayTurnedOff) {
// Turn the display back on
@@ -798,6 +806,8 @@ class FourLineDisplayUsermod : public Usermod {
* Clears the screen and prints on the middle two lines.
*/
void overlay(const char* line1, const char *line2, long showHowLong) {
+ if (type == NONE || !enabled) return;
+
if (displayTurnedOff) {
// Turn the display back on (includes clear())
sleepOrClock(false);
@@ -859,6 +869,7 @@ class FourLineDisplayUsermod : public Usermod {
* the useAMPM configuration.
*/
void showTime(bool fullScreen = true) {
+ if (type == NONE || !enabled) return;
char lineBuffer[LINE_BUFFER_SIZE];
updateLocalTime();
@@ -952,11 +963,12 @@ class FourLineDisplayUsermod : public Usermod {
*/
void addToConfig(JsonObject& root) {
JsonObject top = root.createNestedObject(FPSTR(_name));
- top[FPSTR(_enabled)] = enabled;
+ top[FPSTR(_enabled)] = enabled;
JsonArray io_pin = top.createNestedArray("pin");
for (byte i=0; i<5; i++) io_pin.add(ioPin[i]);
- top["help4PinTypes"] = F("Clk,Data,CS,DC,RST"); // help for Settings page
+ top["help4Pins"] = F("Clk,Data,CS,DC,RST"); // help for Settings page
top["type"] = type;
+ top["help4Type"] = F("1=SSD1306,2=SH1106,3=SSD1306_128x64,4=SSD1305,5=SSD1305_128x64,6=SSD1306_SPI,7=SSD1306_SPI_128x64"); // help for Settings page
top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_contrast)] = contrast;
top[FPSTR(_refreshRate)] = refreshRate/1000;
@@ -1001,6 +1013,10 @@ class FourLineDisplayUsermod : public Usermod {
forceAutoRedraw = top[FPSTR(_forceAutoRedraw)] | forceAutoRedraw;
noAutoRedraw = top[FPSTR(_noAutoRedraw)] | noAutoRedraw;
ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
+ if (newType == SSD1306_SPI || newType == SSD1306_SPI64)
+ ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
+ else
+ ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
DEBUG_PRINT(FPSTR(_name));
if (!initDone) {
@@ -1015,10 +1031,10 @@ class FourLineDisplayUsermod : public Usermod {
for (byte i=0; i<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; }
if (pinsChanged || type!=newType) {
if (type != NONE) delete u8x8;
- for (byte i=0; i<5; i++) {
- if (ioPin[i]>=0) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
- ioPin[i] = newPin[i];
- }
+ PinOwner po = PinOwner::UM_FourLineDisplay;
+ 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, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po);
+ for (byte i=0; i<5; i++) ioPin[i] = newPin[i];
if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1
type = NONE;
enabled = false;
diff --git a/usermods/usermod_v2_four_line_display_ALT/4LD_wled_fonts.c b/usermods/usermod_v2_four_line_display_ALT/4LD_wled_fonts.c
new file mode 100644
index 0000000000..5495f91947
--- /dev/null
+++ b/usermods/usermod_v2_four_line_display_ALT/4LD_wled_fonts.c
@@ -0,0 +1,477 @@
+#pragma once
+
+//WLED custom fonts, curtesy of @Benji (https://github.com/Proto-molecule)
+
+
+/*
+ Fontname: wled_logo_akemi_4x4
+ Copyright: Benji (https://github.com/proto-molecule)
+ Glyphs: 3/3
+ BBX Build Mode: 3
+ * this logo ...WLED/images/wled_logo_akemi.png
+ * encode map = 1, 2, 3
+*/
+const uint8_t u8x8_wled_logo_akemi_4x4[388] U8X8_FONT_SECTION("u8x8_wled_logo_akemi_4x4") =
+ "\1\3\4\4\0\0\0\0\0\0\0\0\0\340\360\10\350\10\350\210\270\210\350\210\270\350\10\360\340\0\0\0"
+ "\0\0\200\200\0\0@\340\300\340@\0\0\377\377\377\377\377\377\37\37\207\207\371\371\371\377\377\377\0\0\374"
+ "\374\7\7\371\0\0\6\4\15\34x\340\200\177\177\377\351yy\376\356\357\217\177\177\177o\377\377\0\70\77"
+ "\277\376~\71\0\0\0\0\0\0\0\1\3\3\3\1\0\0\37\77\353\365\77\37\0\0\0\0\5\7\2\3"
+ "\7\4\0\0\300\300\300\300\200\200\200\0\0\0\0\0\0\0\200\200\300\300\300\300\200\200\0\0\0\0\0\0"
+ "\0\200\200\300\371\37\37\371\371\7\7\377\374\0\0\0\374\377\377\37\37\341\341\377\377\377\377\374\0\0\0\374"
+ "\377\7\7\231\371\376>\371\371>~\377\277\70\0\270\377\177\77\376\376\71\371\371\71\177\377\277\70\0\70\377"
+ "\177>\376\371\377\377\0\77\77\0\0\4\7\2\7\5\0\0\0\377\377\0\77\77\0\0\0\5\7\2\7\5"
+ "\0\0\377\377\300\300\300\200\200\0\0\0\0\0\0\0\200\200\300\300\300\300\300\200\200\0\0\0\0\0\0\0"
+ "\0\0\0\0\231\231\231\371\377\377\374\0\0\0\374\377\347\347\371\1\1\371\371\7\7\377\374\0\0\0@\340"
+ "\300\340@\0\71\371\371\71\177\377\277\70\0\70\277\377\177\71\371\370\70\371\371~\376\377\77\70\200\340x\34"
+ "\15\4\6\0\0\77\77\0\0\0\5\7\2\7\5\0\0\0\377\377\0\77\77\0\0\1\3\3\1\1\0\0"
+ "\0\0\0";
+
+
+/*
+ Fontname: wled_logo_akemi_5x5
+ Copyright: Benji (https://github.com/proto-molecule)
+ Glyphs: 3/3
+ BBX Build Mode: 3
+ * this logo ...WLED/images/wled_logo_akemi.png
+ * encoded = 1, 2, 3
+*/
+/*
+const uint8_t u8x8_wled_logo_akemi_5x5[604] U8X8_FONT_SECTION("u8x8_wled_logo_akemi_5x5") =
+ "\1\3\5\5\0\0\0\0\0\0\0\0\0\0\0\0\340\340\374\14\354\14\354\14|\14\354\14||\14\354"
+ "\14\374\340\340\0\0\0\0\0\0\0\200\0\0\0\200\200\0\200\200\0\0\0\0\377\377\377\376\377\376\377\377"
+ "\377\377\77\77\307\307\307\307\306\377\377\377\0\0\0\360\374>\77\307\0\0\61cg\357\347\303\301\200\0\0"
+ "\377\377\377\317\317\317\317\360\360\360\374\374\377\377\377\377\377\377\377\377\0\0\200\377\377\340\340\37\0\0\0\0"
+ "\0\0\1\3\17\77\374\360\357\357\177\36\14\17\357\377\376\376>\376\360\357\17\17\14>\177o\340\300\343c"
+ "{\77\17\3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\37\37\362\375\37\37\17\0\0"
+ "\0\0\1\1\1\0\1\1\1\0\0\0\200\300\300\300\300\200\200\0\0\0\0\0\0\0\0\0\0\0\200\200"
+ "\300\300\300\300\200\200\0\0\0\0\0\0\0\0\0\0\0\0\200\200\307\307\377\377\307\307\307\77>\374\360\0"
+ "\0\0\360\374\376\377\377\377\7\7\7\377\377\377\377\376\374\360\0\0\0\0\360\374\36\37\37\343\37\37\340\340"
+ "\37\37\37\340\340\377\377\200\0\200\377\377\377\340\340\340\37\37\37\37\37\37\37\377\377\377\200\0\0\200\377\377"
+ "\340\340\340\34\377\377\3\3\377\377\3\17\77{\343\303\300\303\343s\77\37\3\377\377\3\3\377\377\3\17\77"
+ "{\343\303\300\300\343{\37\17\3\377\377\377\377\0\0\37\37\0\0\1\1\1\1\0\1\1\1\1\0\0\377"
+ "\377\0\0\37\37\0\0\1\1\1\1\0\0\1\1\1\0\0\377\377\300\300\300\200\200\0\0\0\0\0\0\0"
+ "\0\0\0\0\200\200\300\300\300\300\200\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\343\343\343\343"
+ "\343\377\376\374\360\0\0\0\360\374\376\77\77\307\307\7\7\307\307\307\77>\374\360\0\0\0\0\0\200\200\0"
+ "\200\200\0\0\34\34\34\37\37\377\377\377\377\200\0\200\377\377\377\377\37\37\37\0\0\37\37\37\340\340\377\377"
+ "\200\0\0\0\1\303\347\357gc\61\0\3\3\377\377\3\7\37\177s\343\300\303s{\37\17\7\3\377\377"
+ "\3\3\377\377\3\37\77scp<\36\17\3\1\0\0\0\0\0\0\0\37\37\0\0\0\1\1\1\0\1"
+ "\1\1\0\0\0\0\377\377\0\0\37\37\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+*/
+
+/*
+ Fontname: wled_logo_2x2
+ Copyright: Benji (https://github.com/proto-molecule)
+ Glyphs: 4/4
+ BBX Build Mode: 3
+ * this logo https://cdn.discordapp.com/attachments/706623245935444088/927361780613799956/wled_scaled.png
+ * encode map = 1, 2, 3, 4
+*/
+const uint8_t u8x8_wled_logo_2x2[133] U8X8_FONT_SECTION("u8x8_wled_logo_2x2") =
+ "\1\4\2\2\0\0\0\0\0\200\200\360\360\16\16\16\16\0\0\0\340\340\340\340\340\37\37\1\1\0\0\0"
+ "\0\0\0\0\360\360\16\16\16\200\200\16\16\16\360\360\0\0\0\200\37\37\340\340\340\37\37\340\340\340\37\37"
+ "\0\0\0\37\200~~\0\0\0\0\0\0\0\360\360\216\216\216\216\37\340\340\340\340\340\340\340\0\0\37\37"
+ "\343\343\343\343\16\16\0\0ppp\16\16\376\376\16\16\16\360\360\340\340\0\0\0\0\0\340\340\377\377\340"
+ "\340\340\37\37";
+
+
+/*
+ Fontname: wled_logo_4x4
+ Copyright: Created with Fony 1.4.7
+ Glyphs: 4/4
+ BBX Build Mode: 3
+ * this logo https://cdn.discordapp.com/attachments/706623245935444088/927361780613799956/wled_scaled.png
+ * encode map = 1, 2, 3, 4
+*/
+/*
+const uint8_t u8x8_wled_logo_4x4[517] U8X8_FONT_SECTION("u8x8_wled_logo_4x4") =
+ "\1\4\4\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\374\374\374\374\374\374\374\374\374"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\300\300\300\300\300\377\377\377\377\377\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\17\17\17\17\17\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\370\370\370\370\370\370\370\370\370\7\7\7\7\7\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\374\374\374\374\374\0\0\0\0\0\374\374\374\374\374\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\377\377\377\377\377\0\0\0\0\0\300\300\300\300\300\0\0\0\0\0\377\377\377\377\377\0\0"
+ "\0\0\300\300\0\377\377\377\377\377\0\0\0\0\0\377\377\377\377\377\0\0\0\0\0\377\377\377\377\377\0\0"
+ "\0\0\377\377\0\7\7\7\7\7\370\370\370\370\370\7\7\7\7\7\370\370\370\370\370\7\7\7\7\7\0\0"
+ "\0\0\7\7\0\0\0\374\374\374\374\374\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\374\374\374"
+ "\374\374\374\374\300\300\300\77\77\77\77\77\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\300\300\300"
+ "\300\300\300\300\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\37\37\37"
+ "\37\37\37\37\7\7\7\370\370\370\370\370\370\370\370\370\370\370\370\370\0\0\0\0\7\7\7\7\7\370\370\370"
+ "\370\370\370\370\374\374\374\374\374\374\0\0\0\0\0\0\0\0\374\374\374\374\374\374\374\374\374\374\374\374\374\374"
+ "\0\0\0\0\300\300\0\0\0\0\0\0\0\77\77\77\77\77\0\0\0\0\377\377\377\377\377\0\0\0\0\377"
+ "\377\377\377\377\37\37\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\0\0\0\0\377"
+ "\377\377\377\377\370\370\370\370\370\370\0\0\0\0\0\0\0\0\370\370\370\370\377\377\377\377\377\370\370\370\370\377"
+ "\7\7\7\7";
+*/
+
+
+/*
+ Fontname: 4LineDisplay_WLED_icons_1x
+ Copyright: Benji (https://github.com/proto-molecule)
+ Glyphs: 13/13
+ BBX Build Mode: 3
+ * 1 = sun
+ * 2 = skip forward
+ * 3 = fire
+ * 4 = custom palette
+ * 5 = puzzle piece
+ * 6 = moon
+ * 7 = brush
+ * 8 = contrast
+ * 9 = power-standby
+ * 10 = star
+ * 11 = heart
+ * 12 = Akemi
+ *-----------
+ * 20 = wifi
+ * 21 = media-play
+*/
+const uint8_t u8x8_4LineDisplay_WLED_icons_1x1[172] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_1x1") =
+ "\1\25\1\1\0B\30<<\30B\0~<\30\0~<\30\0p\374\77\216\340\370\360\0||>\36\14\64 \336\67"
+ ";\336 \64\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\0\0\0\0\0\0\0\0\0\0\0\0\2\1\11\311"
+ "\311\1\2\0\0~<<\30\30\0";
+
+
+/*
+ Fontname: 4LineDisplay_WLED_icons_2x1
+ Copyright: Benji (https://github.com/proto-molecule)
+ Glyphs: 11/11
+ BBX Build Mode: 3
+ * 1 = sun
+ * 2 = skip forward
+ * 3 = fire
+ * 4 = custom palette
+ * 5 = puzzle piece
+ * 6 = moon
+ * 7 = brush
+ * 8 = contrast
+ * 9 = power-standby
+ * 10 = star
+ * 11 = heart
+ * 12 = Akemi
+*/
+const uint8_t u8x8_4LineDisplay_WLED_icons_2x1[196] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_2x1") =
+ "\1\14\2\1\20\20BB\30\30<\275\275<\30\30BB\20\20\377~<<\70\30\20\0\377~<<"
+ "\70\30\20\0\60p\370\374\77>\236\214\300\340\370\360\360\340\0\0\34"
+ "\66\66<\34\374\374\374\374~\77\77~\374\374\374\374 pp \30<~~\377\370\360\360\340\340\340\340"
+ "@@ \0\200\300\340\360\360p`\10\34\34\16\6\6\3\0\0\70|~\376\376\377\377\377\201\201\203\202"
+ "\302Fl\70\70xL\204\200\200\217\217\200\200\204Lx\70\0\0\10\10\30\330x|\77\77|x\330\30"
+ "\10\10\0\0\14\36\37\77\77\177~\374\374~\177\77\77\37\36\14\24\64 \60>\26\367\33\375\36>\60"
+ " \64\24";
+
+
+/*
+ Fontname: 4LineDisplay_WLED_icons_2x
+ Copyright:
+ Glyphs: 11/11
+ BBX Build Mode: 3
+ * 1 = sun
+ * 2 = skip forward
+ * 3 = fire
+ * 4 = custom palette
+ * 5 = puzzle piece
+ * 6 = moon
+ * 7 = brush
+ * 8 = contrast
+ * 9 = power-standby
+ * 10 = star
+ * 11 = heart
+ * 12 = Akemi
+*/
+const uint8_t u8x8_4LineDisplay_WLED_icons_2x2[389] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_2x2") =
+ "\1\14\2\2\200\200\14\14\300\340\360\363\363\360\340\300\14\14\200\200\1\1\60\60\3\7\17\317\317\17\7\3"
+ "\60\60\1\1\374\370\360\340\340\300\200\0\374\370\360\340\340\300\200\0\77\37\17\7\7\3\1\0\77\37\17\7"
+ "\7\3\1\0\0\200\340\360\377\376\374\360\0\0\300\200\0\0\0\0\17\77\177\377\17\7\301\340\370\374\377\377"
+ "\377|\0\0\360\370\234\236\376\363\363\377\377\363\363\376><\370\360\3\17\77yy\377\377\377\377\317\17\17"
+ "\17\17\7\3\360\360\360\360\366\377\377\366\360\360\360\360\0\0\0\0\377\377\377\377\237\17\17\237\377\377\377\377"
+ "\6\17\17\6\340\370\374\376\377\340\200\0\0\0\0\0\0\0\0\0\3\17\37\77\177\177\177\377\376|||"
+ "\70\30\14\0\0\0\0\0\0\0\0``\360\370|<\36\7\2\0\300\360\376\377\177\77\36\0\1\1\0"
+ "\0\0\0\0\340\370\374\376\376\377\377\377\3\3\7\6\16<\370\340\7\37\77\177\177\377\377\377\300\300\340`"
+ "p<\37\7\300\340p\30\0\0\377\377\0\0\30p\340\300\0\0\17\37\70`\340\300\300\300\300\340`\70"
+ "\37\17\0\0\0@\300\300\300\300\340\374\374\340\300\300\300\300@\0\0\0\0\1s\77\37\17\17\37\77s"
+ "\1\0\0\0\360\370\374\374\374\374\370\360\360\370\374\374\374\374\370\360\0\1\3\7\17\37\77\177\177\77\37\17"
+ "\7\3\1\0\200\200\0\0\0\360\370\374<\334\330\360\0\0\200\200\2\2\14\30\24\37\6~\7\177\7\37"
+ "\24\30\16\2";
+
+/*
+ Fontname: 4LineDisplay_WLED_icons_3x
+ Copyright: Benji (https://github.com/proto-molecule)
+ Glyphs: 11/11
+ BBX Build Mode: 3
+ * 1 = sun
+ * 2 = skip forward
+ * 3 = fire
+ * 4 = custom palette
+ * 5 = puzzle piece
+ * 6 = moon
+ * 7 = brush
+ * 8 = contrast
+ * 9 = power-standby
+ * 10 = star
+ * 11 = heart
+ * 12 = Akemi
+*/
+const uint8_t u8x8_4LineDisplay_WLED_icons_3x3[868] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_3x3") =
+ "\1\14\3\3\0\0\34\34\34\0\200\300\300\340\347\347\347\340\300\300\200\0\34\34\34\0\0\0\34\34\34\0"
+ "\0>\377\377\377\377\377\377\377\377\377\377\377>\0\0\34\34\34\0\0\0\16\16\16\0\0\1\1\3ss"
+ "s\3\1\1\0\0\34\34\34\0\0\0\370\360\340\300\300\200\0\0\0\0\0\0\370\360\340\300\300\200\0\0"
+ "\0\0\0\0\377\377\377\377\377\377\377\376~<\70\20\377\377\377\377\377\377\377\376~<\70\20\37\17\17\7"
+ "\3\1\1\0\0\0\0\0\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\300\361\376\374\370\360\300"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\300\370\374\376\377\377\377\377\377\177\77\17\6\0\200\342\374\370\360\340"
+ "\200\0\0\0\1\17\37\77\177\377\7\3\0\200\360\370\374\376\377\377\377\377\377\377\77\0\0\0\0\200\340\360"
+ "\370\370\374\316\206\206\317\377\377\377\317\206\206\316\374\374\370\360\340\200<\377\377\371\360py\377\377\377\377\377"
+ "\377\377\377\377\377\377\363\341\341\363\377\177\0\1\7\17\34\70x|\377\377\377\377\367\363c\3\3\3\3\1"
+ "\1\1\0\0\300\300\300\300\300\300\300\316\377\377\377\316\300\300\300\300\300\300\0\0\0\0\0\0\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\300\300\340\340\340\300\377\377\377\377\377\377\377\307\3\3\3\307"
+ "\377\377\377\377\377\377\1\1\3\3\3\1\0\300\340\370\374\374\376\377\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0>\377\377\377\377\377\377\377\377\374\360\340\300\300\200\200\0\0\0\0\0\0\200\200\0\1\7\17"
+ "\37\37\77\177\177\177\177\377\377\377\177\177\177\77\77\37\17\7\3\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\200\200\300\340\340\360\370\374|>\17\6\0\0\0\0\0\340\340\360\360\360\342\303\7\17\37\77\37\7\3\1"
+ "\0\0\0\0\0\200\340\360\377\377\377\377\177\77\37\17\0\0\0\0\0\0\0\0\0\0\0\0\0\200\340\360"
+ "\370\374\374\376\376\376\377\377\7\7\7\6\16\16\34\70\360\340\300\0|\377\377\377\377\377\377\377\377\377\377\377"
+ "\0\0\0\0\0\0\0\0\0\377\377\377\0\3\7\17\37\77\177\177\377\377\377\377\340\340\340\340pp\70<"
+ "\37\17\3\0\0\0\200\300\340\340\300\0\0\377\377\377\0\0\300\340\340\300\200\0\0\0\0\0\370\376\377\17"
+ "\3\0\0\0\0\17\17\17\0\0\0\0\0\3\17\377\376\370\0\0\0\7\17\37~\376\376\377\377\377\377\377\376\376~>\36\16\6\6\2\0\0\0\0"
+ "\0\300x<\37\17\17\7\3\7\17\17\37>\177\177\377\377\377\377\377\377\371p\60\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0<\376\377\377\377\377\376<\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0"
+ "\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377~~\377\377"
+ "\377\377~<\377\377\377\377\377\377\377\377\303\1\0\0\0\0\1\303\377\377\377\377\377\377\377\377\0\0\0\0"
+ "\0\0\0\0\0\0\200\340\360\370\374\374\376\376\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\370\377\377\377\377\377\377\377\377\377\376\360\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\7\77\377\377\377\377\377\377\377\377\377\377\377\377\377\376\374\370\370\360\360\360\340\340\340\340\340\340"
+ "\340\340\60\0\0\0\0\1\3\7\17\37\37\77\77\77\177\177\177\177\177\177\177\177\77\77\77\37\37\17\7\3"
+ "\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\200\300\340\340\360\370\374\374"
+ "~\77\16\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\30\34>~\377\377\377\377\177\77\37\7\3\0"
+ "\0\0\0\0\0\0\0\0\0\360\374\376\377\377\377\377\377\376\374\370\0\0\0\3\3\1\0\0\0\0\0\0"
+ "\0\0\0\0@@\340\370\374\377\377\377\177\177\177\77\37\17\7\1\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\200\300\340\360\370\374\374\376\376\376\377\377\377\377\17\17\17\37\36\36>|\374\370\360\340"
+ "\300\200\0\0\360\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\1\3\37"
+ "\377\377\376\360\17\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\200\300\370"
+ "\377\377\177\17\0\0\1\3\7\17\37\77\77\177\177\177\377\377\377\377\360\360\360\370xx|>\77\37\17\7"
+ "\3\1\0\0\0\0\0\0\0\200\300\200\0\0\0\0\377\377\377\377\0\0\0\0\200\300\200\0\0\0\0\0"
+ "\0\0\0\0\300\360\374\376\177\37\7\3\3\0\0\0\377\377\377\377\0\0\0\3\3\7\37\177\376\374\360\300"
+ "\0\0\0\0\77\377\377\377\340\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\340\377\377\377\77"
+ "\0\0\0\0\0\0\3\7\17\37><|x\370\360\360\360\360\360\360\370x|<>\37\17\7\3\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\340\374\374\340\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\20\60p\360\360\360\360\360\360\360\360\370\377\377\377\377\377\377\370\360\360\360\360\360\360\360\360"
+ "p\60\20\0\0\0\0\0\0\0\1\3\7\317\377\377\377\377\377\377\377\377\377\377\377\377\317\7\3\1\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0p>\37\17\17\7\3\1\0\0\1\3\7\17\17\37>p\0\0\0"
+ "\0\0\0\0\0\200\300\340\340\360\360\360\360\360\360\340\340\300\200\0\0\200\300\340\340\360\360\360\360\360\360\340"
+ "\340\300\200\0~\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\376\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377~\0\1\3\7\17\37\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\17"
+ "\7\3\1\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37\77\177\177\77\37\17\7\3\1\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\300\340\340\360\360\360\360\340\340\300\200\0\0\0\0\0\0"
+ "\0\0\0\0\0@\340\300\340@\0\0\0\376\377\377\177\177\177\237\207\347\371\371\371\377\376\0\0\0\0@"
+ "\340\300\340@\2\4\4\35x\340\200\0\30\237\377\177\36\376\376\37\37\377\377\37\177\377\237\30\0\200\340x"
+ "\34\5\4\2\0\0\0\0\0\1\3\3\3\1\0\0\0\17\17\0\0\17\17\0\0\0\1\3\3\3\1\0"
+ "\0\0\0";
+*/
+
+/*
+ Fontname: 4LineDisplay_WLED_icons_6x
+ Copyright: Benji (https://github.com/proto-molecule)
+ Glyphs: 11/11
+ BBX Build Mode: 3
+ * 1 = sun
+ * 2 = skip forward
+ * 3 = fire
+ * 4 = custom palette
+ * 5 = puzzle piece
+ * 6 = moon
+ * 7 = brush
+ * 8 = contrast
+ * 9 = power-standby
+ * 10 = star
+ * 11 = heart
+ * 12 = Akemi
+*/
+// you can replace this (wasteful) font by using 3x3 variant with draw2x2Glyph()
+const uint8_t u8x8_4LineDisplay_WLED_icons_6x6[3460] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_6x6") =
+ "\1\14\6\6\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\36\77\77\77\77\36\0"
+ "\0\0\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\0\0\0\7\17\17\17\17\7"
+ "\0\0\0\0\200\300\340\340\340\360\360\360\360\360\360\340\340\340\300\200\0\0\0\0\7\17\17\17\17\7\0\0"
+ "\0\0\0\0\300\340\340\340\340\300\0\0\0\0\0\0\340\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\376\374\340\0\0\0\0\0\0\300\340\340\340\340\300\3\7\7\7\7\3\0\0\0\0\0\0"
+ "\7\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\7\0\0\0\0\0\0\3\7"
+ "\7\7\7\3\0\0\0\0\0\0\340\360\360\360\360\340\0\0\0\0\1\3\7\7\7\17\17\17\17\17\17\7"
+ "\7\7\3\1\0\0\0\0\340\360\360\360\360\340\0\0\0\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1"
+ "\0\0\0\0\0\0\0\0\0x\374\374\374\374x\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1\0\0"
+ "\0\0\0\0\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\200\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200"
+ "\200\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200\200\0\0\0\0\0\0\0"
+ "\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\177\77\77\37\17\7\7\3\1\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\77\37\17\7"
+ "\7\3\1\0\377\377\377\177\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\377\377\377\177"
+ "\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\3\1\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\3\1\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\376\374\374\370\360\340\300\200\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\200\340\360\374"
+ "\377\377\377\377\377\377\377\377\377\376\370\300\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\300\340\360\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\37\0\0\0\0"
+ "\0\0\4\370\360\360\340\300\200\0\0\0\0\0\0\0\0\0\0\0\370\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\177\77\37\7\3\0\0\0\0\0\200\300\360\374\377\377\377\377\377\377\377\376\370\340\0\0\0"
+ "\0\0\0\0\3\37\177\377\377\377\377\377\377\377\377\377\77\17\7\1\0\0\0\0\0\200\300\360\370\374\376\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\1\3\7\17\37\77\77\177\200"
+ "\0\0\0\0\0\0\340\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\17\1\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\200\300\340\340\360\360\370|<>>>~\377\377\377\377\377\377\377\177"
+ "\77\36\36\36\36<|\370\370\360\360\340\340\200\0\0\0\0\0\0\0\0\300\360\374\376\377\377\377\377\377\377"
+ "\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\370\360\340\340\340\340\360\370\377\377\377\377\377\377\377\377\377"
+ "\374\360\340\200\360\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\17\377\377\377\377\377\377\377\376~>>"
+ "\77\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\376\376\376\377\377\377"
+ "\177\77\37\7\0\0\3\17\77\177\377\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\377\377\377\377\77\17"
+ "\17\7\7\7\7\7\7\7\7\7\3\3\3\3\1\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37"
+ "\37\77\77\177\177\177\377\377\377\377\377\377\377\377\377~\30\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\370\374\376\377\377\377\377\377\377\376\374\360\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\360\360\360\360\360\360\360\360\360\360\360\360"
+ "\360\363\377\377\377\377\377\377\377\377\363\360\360\360\360\360\360\360\360\360\360\360\360\360\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\374\376\376\377\377\377\377"
+ "\377\376\374\360\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\17\17\17\17\17\17\37\77\177\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\3\3\7\7\17\17\17\17\7\7\3\0\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\360\300\0\0\0\0\0\0\0\0\300\360\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\200\300\340\360\360\370\374\374\376\376\7\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\300\360\374\376\377\377\377\377\377\377\377"
+ "\377\377\377\340\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\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\360\300\200\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\17\177\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\376\374\370\360\360\340\340\300\300\300\200\200\200\200\0\0\0\0\0\0\200\200"
+ "\200\200\0\0\0\0\1\7\37\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\7\1\0\0\0\0\0\0\0\0\0\0\1\3\3\7"
+ "\17\17\37\37\37\77\77\77\77\177\177\177\177\177\177\77\77\77\77\37\37\37\17\17\7\3\3\1\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\200\200\300\340\360\360\370\374\374\376\377~\34\10\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\200\300\300\340\360\360\370\374\376\376\377\377\377\377\377\377\177\77\17\7\3"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\4\6\17\17\37\77\177\377"
+ "\377\377\377\377\377\377\77\37\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\370\374\376"
+ "\376\377\377\377\377\377\377\376\376\374\370\340\0\0\0\0\3\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\200\360\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\17\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`px\374\376\377\377\377\377\377\377"
+ "\177\177\177\77\77\37\17\7\3\1\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\200\300\340\360\360\370\374\374\374\376\376\376\377\377\377\377\377\77\77\77\77"
+ "\177~~\376\374\374\374\370\360\360\340\300\200\0\0\0\0\0\0\0\0\0\340\360\374\376\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\1\1\3\7\17\37\177\377\377\376\374"
+ "\360\340\0\0\370\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\1\17\377\377\377\377\377\370\37\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\360\377\377"
+ "\377\377\377\37\0\0\7\17\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0"
+ "\0\0\0\0\0\200\200\300\340\360\370\376\377\377\177\77\17\7\0\0\0\0\0\0\0\0\0\1\3\7\17\17"
+ "\37\77\77\77\177\177\177\377\377\377\377\377\374\374\374\374\376~~\177\77\77\77\37\17\17\7\3\1\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\377\377\377\377\377\377\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\200\300\340\360\370\374\376\376|"
+ "x \0\0\0\0\377\377\377\377\377\377\0\0\0\0 x|\376\376\374\370\360\340\300\200\0\0\0\0\0"
+ "\0\0\0\0\300\370\376\377\377\377\177\17\7\1\0\0\0\0\0\0\0\0\377\377\377\377\377\377\0\0\0\0"
+ "\0\0\0\0\1\7\37\177\377\377\377\376\370\200\0\0\0\0\0\0\177\377\377\377\377\377\200\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\200\377\377\377\377\377\177\0\0"
+ "\0\0\0\0\0\7\37\177\377\377\377\374\370\340\300\200\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\200\200\300\340\370\374\377\377\377\177\37\7\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37\37\77"
+ "\77\177~~~\374\374\374\374\374\374\374\374~~~\177\77\77\37\37\17\7\3\1\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\340\374\374\340\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\300\370\377\377\377\377\377\377\370\300\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\4\14\34<<|\374\374\374\374\374\374\374\374\374\374\374\376\377\377\377\377\377\377\377\377\377"
+ "\377\376\374\374\374\374\374\374\374\374\374\374\374|<<\34\14\4\0\0\0\0\0\0\0\0\0\1\3\3\7"
+ "\17\37\77\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\77\37\17\7\3\3\1\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\370\377\377\377\377\377\377\177\77\37\17\17\37\77\177"
+ "\377\377\377\377\377\377\370\300\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0p>"
+ "\37\17\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\17\37>p\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\200\200\200\300\300\300\300\300\300\200\200\200\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\200\200\200\300\300\300\300\300\300\200\200\200\0\0\0\0\0\0\200\360\370\374\376\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\376\374\370\360\200\200\360\370\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376"
+ "\374\370\360\200\37\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\37\0\0\1\3\7\17\37\77\177\377\377\377"
+ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\17\7"
+ "\3\1\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37\77\177\377\377\377\377\377\377\377\377\377\377\377"
+ "\377\377\377\177\77\37\17\7\3\1\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\3\7\17\37\77\77\37\17\7\3\1\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\300\300\300\300\300\300\300"
+ "\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\340\370\370\376\376\377\377\377\377\377\377\377\377\77\77\77>\376\370\370\340\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0 p\360\340\360p \0\0\0\0\0\0\377\377\377\377\177\177\177\177\177\207\207\340\340\377"
+ "\377\377\377\377\377\377\377\0\0\0\0\0 p\360\340\360p \0\6\4\14\14\15|x\360\200\200\0\0"
+ "pp\177\177\377\377\374|\374\374\374\177\177\177\377\377\377\177\377\377\377\377\177pp\0\0\200\200\360x}"
+ "\14\14\4\6\0\0\0\0\0\0\0\3\37\37|ppp\34\34\37\3\3\0\377\377\377\0\0\0\377\377"
+ "\377\0\3\3\37\37\34ppp~\37\37\3\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\7\7\7\0\0\0\7\7\7\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0";
+
+
+
+/*
+ Fontname: akemi_8x8
+ Copyright: Benji (https://github.com/proto-molecule)
+ Glyphs: 1/1
+ BBX Build Mode: 3
+ * 12 = Akemi
+*/
+/*
+const uint8_t u8x8_akemi_8x8[516] U8X8_FONT_SECTION("u8x8_akemi_8x8") =
+ "\14\14\10\10\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\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\0\0\0\0"
+ "\200\200\200\200\200\200\200\200\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\0\0\0\0\340\340\370\370\376\376\376\376"
+ "\377\377\377\377\377\377\377\377\376\376\376\376\370\370\340\340\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\376\376\377\377\377\377\377\377\377\377"
+ "\377\377\377\377\37\37\37\343\343\343\343\343\343\377\377\377\376\376\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\30\30~~\370\370~~\30\30\0\0\0\0\0\0\0\377\377\377\377\377\77\77\77\77\77"
+ "\77\300\300\300\370\370\370\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\30\0f\0\200\0\0"
+ "\0\0\0\0\6\6\30\30\30\31\371\370\370\340\340\0\0\0\0\0\340\340\377\377\377\377\377\376\376\376\376\376"
+ "\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\371\346\346\6\6\6\6\6\0\340\340\340\341\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\1\1\37\37\377\376\376\340\340\200\201\201\341\341\177\177\37\37\1\1\377\377"
+ "\377\377\1\1\1\1\377\377\377\377\1\1\37\37\177\177\341\341\201\201\200\200\370\370\376\376\37\37\1\1\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\7\7\7\7\7\7\1\1\0\0\0\0\0\0\377\377"
+ "\377\377\0\0\0\0\377\377\377\377\0\0\0\0\0\0\1\1\7\7\7\7\7\7\1\1\0\0\0\0\0\0"
+ "\0\0\0";
+*/
\ No newline at end of file
diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h
index 3dcb5af6aa..383accc52c 100644
--- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h
+++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h
@@ -2,6 +2,7 @@
#include "wled.h"
#include // from https://github.com/olikraus/u8g2/
+#include "4LD_wled_fonts.c"
//
// Insired by the usermod_v2_four_line_display
@@ -25,6 +26,10 @@
//The SCL and SDA pins are defined here.
#ifdef ARDUINO_ARCH_ESP32
+ #define HW_PIN_SCL 22
+ #define HW_PIN_SDA 21
+ #define HW_PIN_CLOCKSPI 18
+ #define HW_PIN_DATASPI 23
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 22
#endif
@@ -47,6 +52,10 @@
#define FLD_PIN_RESET 26
#endif
#else
+ #define HW_PIN_SCL 5
+ #define HW_PIN_SDA 4
+ #define HW_PIN_CLOCKSPI 14
+ #define HW_PIN_DATASPI 13
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 5
#endif
@@ -70,15 +79,20 @@
#endif
#endif
+#ifndef FLD_TYPE
+ #ifndef FLD_SPI_DEFAULT
+ #define FLD_TYPE SSD1306
+ #else
+ #define FLD_TYPE SSD1306_SPI
+ #endif
+#endif
+
// When to time out to the clock or blank the screen
// if SLEEP_MODE_ENABLED.
#define SCREEN_TIMEOUT_MS 60*1000 // 1 min
-#define TIME_INDENT 0
-#define DATE_INDENT 2
-
// Minimum time between redrawing screen in ms
-#define USER_LOOP_REFRESH_RATE_MS 100
+#define USER_LOOP_REFRESH_RATE_MS 1000
// Extra char (+1) for null
#define LINE_BUFFER_SIZE 16+1
@@ -96,172 +110,47 @@ typedef enum {
SSD1306_SPI64 // U8X8_SSD1306_128X64_NONAME_HW_SPI
} DisplayType;
-/*
- Fontname: benji_custom_icons_1x
- Copyright:
- Glyphs: 1/1
- BBX Build Mode: 3
- * 4 = custom palette
-*/
-const uint8_t u8x8_font_benji_custom_icons_1x1[13] U8X8_FONT_SECTION("u8x8_font_benji_custom_icons_1x1") =
- "\4\4\1\1<\370\360\3\17\77yy\377\377\377\377\317\17\17"
- "\17\17\7\3\360\360\360\360\366\377\377\366\360\360\360\360\0\0\0\0\377\377\377\377\237\17\17\237\377\377\377\377"
- "\6\17\17\6\340\370\374\376\377\340\200\0\0\0\0\0\0\0\0\0\3\17\37\77\177\177\177\377\376|||"
- "\70\30\14\0\0\0\0\0\0\0\0``\360\370|<\36\7\2\0\300\360\376\377\177\77\36\0\1\1\0"
- "\0\0\0\0\200\200\14\14\300\340\360\363\363\360\340\300\14\14\200\200\1\1\60\60\3\4\10\310\310\10\4\3"
- "\60\60\1\1";
-
-/*
- Fontname: benji_custom_icons_6x
- Copyright:
- Glyphs: 8/8
- BBX Build Mode: 3
- // 6x6 icons libraries take up a lot of memory thus all the icons uses are consolidated into a single library
- // these are just the required icons stripped from the U8x8 libraries in addition to a few new custom icons
- * 1 = sun
- * 2 = skip forward
- * 3 = fire
- * 4 = custom palette
- * 5 = puzzle piece
- * 6 = moon
- * 7 = brush
- * 8 = custom saturation
-*/
-const uint8_t u8x8_font_benji_custom_icons_6x6[2308] U8X8_FONT_SECTION("u8x8_font_benji_custom_icons_6x6") =
- "\1\10\6\6\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\36\77\77\77\77\36\0"
- "\0\0\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\0\0\0\7\17\17\17\17\7"
- "\0\0\0\0\200\300\340\340\340\360\360\360\360\360\360\340\340\340\300\200\0\0\0\0\7\17\17\17\17\7\0\0"
- "\0\0\0\0\300\340\340\340\340\300\0\0\0\0\0\0\340\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377"
- "\377\377\377\377\377\376\374\340\0\0\0\0\0\0\300\340\340\340\340\300\3\7\7\7\7\3\0\0\0\0\0\0"
- "\7\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\7\0\0\0\0\0\0\3\7"
- "\7\7\7\3\0\0\0\0\0\0\340\360\360\360\360\340\0\0\0\0\1\3\7\7\7\17\17\17\17\17\17\7"
- "\7\7\3\1\0\0\0\0\340\360\360\360\360\340\0\0\0\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1"
- "\0\0\0\0\0\0\0\0\0x\374\374\374\374x\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1\0\0"
- "\0\0\0\0\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\200\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200"
- "\200\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200\200\0\0\0\0\0\0\0"
- "\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377"
- "\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377\377\377\377\377\377\377\377\377"
- "\377\377\177\77\77\37\17\7\7\3\1\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\77\37\17\7"
- "\7\3\1\0\377\377\377\177\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\377\377\377\177"
- "\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\3\1\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\3\1\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\376\374\374\370\360\340\300\200\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\200\340\360\374"
- "\377\377\377\377\377\377\377\377\377\376\370\300\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\300\340\360\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\37\0\0\0\0"
- "\0\0\4\370\360\360\340\300\200\0\0\0\0\0\0\0\0\0\0\0\370\377\377\377\377\377\377\377\377\377\377\377"
- "\377\377\377\377\377\177\77\37\7\3\0\0\0\0\0\200\300\360\374\377\377\377\377\377\377\377\376\370\340\0\0\0"
- "\0\0\0\0\3\37\177\377\377\377\377\377\377\377\377\377\77\17\7\1\0\0\0\0\0\200\300\360\370\374\376\377"
- "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\1\3\7\17\37\77\77\177\200"
- "\0\0\0\0\0\0\340\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\17\1\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\200\300\340\340\360\360\370|<>>>~\377\377\377\377\377\377\377\177"
- "\77\36\36\36\36<|\370\370\360\360\340\340\200\0\0\0\0\0\0\0\0\300\360\374\376\377\377\377\377\377\377"
- "\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\370\360\340\340\340\340\360\370\377\377\377\377\377\377\377\377\377"
- "\374\360\340\200\360\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
- "\377\377\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\17\377\377\377\377\377\377\377\376~>>"
- "\77\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\376\376\376\377\377\377"
- "\177\77\37\7\0\0\3\17\77\177\377\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\377\377\377\377\77\17"
- "\17\7\7\7\7\7\7\7\7\7\3\3\3\3\1\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37"
- "\37\77\77\177\177\177\377\377\377\377\377\377\377\377\377~\30\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\370\374\376\377\377\377\377\377\377\376\374\360\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\360\360\360\360\360\360\360\360\360\360\360\360"
- "\360\363\377\377\377\377\377\377\377\377\363\360\360\360\360\360\360\360\360\360\360\360\360\360\0\0\0\0\0\0\0\0"
- "\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
- "\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377"
- "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\374\376\376\377\377\377\377"
- "\377\376\374\360\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\17\17\17\17\17\17\37\77\177\377\377\377\377"
- "\377\377\377\377\377\377\377\377\3\3\7\7\17\17\17\17\7\7\3\0\377\377\377\377\377\377\377\377\377\377\377\377"
- "\360\300\0\0\0\0\0\0\0\0\300\360\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\200\300\340\360\360\370\374\374\376\376\7\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\300\360\374\376\377\377\377\377\377\377\377"
- "\377\377\377\340\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\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\360\300\200\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\17\177\377\377\377\377\377\377\377\377\377\377"
- "\377\377\377\377\377\377\377\377\377\377\376\374\370\360\360\340\340\300\300\300\200\200\200\200\0\0\0\0\0\0\200\200"
- "\200\200\0\0\0\0\1\7\37\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
- "\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\7\1\0\0\0\0\0\0\0\0\0\0\1\3\3\7"
- "\17\17\37\37\37\77\77\77\77\177\177\177\177\177\177\77\77\77\77\37\37\37\17\17\7\3\3\1\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\200\200\300\340\360\360\370\374\374\376\377~\34\10\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\0\200\300\300\340\360\360\370\374\376\376\377\377\377\377\377\377\177\77\17\7\3"
- "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\4\6\17\17\37\77\177\377"
- "\377\377\377\377\377\377\77\37\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\370\374\376"
- "\376\377\377\377\377\377\377\376\376\374\370\340\0\0\0\0\3\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0\0\0\0\0\0\0\0\200\360\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\17\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`px\374\376\377\377\377\377\377\377"
- "\177\177\177\77\77\37\17\7\3\1\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\200\300\300\200\0\0\0\0\0\0\0\0\0\14\36\77\77\36\14\0\0"
- "\0\0\0\0\0\0\0\200\300\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\7\17\17\7\3"
- "\0\200\300\340\360\360\370\370\370\374\374\374\374\370\370\370\360\360\340\300\200\0\3\7\17\17\7\3\0\0\0\0"
- "\0\0\0\0\300\340\360\360\340\300\0\0\0\0\340\374\377\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177"
- "\177\177\177\177\177\377\374\340\0\0\0\0\300\340\360\360\340\300\0\0\0\1\3\3\1\0\0\0\0\0\1\17"
- "\77\177\370\340\300\200\200\0\0\0\0\0\0\0\0\200\200\300\340\370\177\77\17\1\0\0\0\0\0\1\3\3"
- "\1\0\0\0\0\0\0\0\0\0\60x\374\374x\60\0\0\0\1\3\3\7\7\7\16\16\16\16\7\7\7"
- "\3\3\1\0\0\0\60x\374\374x\60\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\14\36\77\77\36\14\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- "\0\0\0";
class FourLineDisplayUsermod : public Usermod {
private:
bool initDone = false;
- unsigned long lastTime = 0;
// HW interface & configuration
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
+
#ifndef FLD_SPI_DEFAULT
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
- DisplayType type = SSD1306_64; // display type
#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
- DisplayType type = SSD1306_SPI; // display type
+ uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz)
#endif
+
+ DisplayType type = FLD_TYPE; // display type
bool flip = false; // flip display 180°
uint8_t contrast = 10; // screen contrast
uint8_t lineHeight = 1; // 1 row or 2 rows
- uint32_t refreshRate = USER_LOOP_REFRESH_RATE_MS; // in ms
+ uint16_t refreshRate = USER_LOOP_REFRESH_RATE_MS; // in ms
uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms
bool sleepMode = true; // allow screen sleep?
bool clockMode = false; // display clock
-
- // needRedraw marks if redraw is required to prevent often redrawing.
- bool needRedraw = true;
+ bool showSeconds = true; // display clock with seconds
+ bool enabled = true;
+ bool contrastFix = false;
// Next variables hold the previous known values to determine if redraw is
// required.
- String knownSsid = "";
- IPAddress knownIp;
+ String knownSsid = apSSID;
+ IPAddress knownIp = IPAddress(4, 3, 2, 1);
uint8_t knownBrightness = 0;
uint8_t knownEffectSpeed = 0;
uint8_t knownEffectIntensity = 0;
uint8_t knownMode = 0;
uint8_t knownPalette = 0;
uint8_t knownMinute = 99;
+ uint8_t knownHour = 99;
byte brightness100;
byte fxspeed100;
byte fxintensity100;
@@ -270,22 +159,26 @@ class FourLineDisplayUsermod : public Usermod {
bool powerON = true;
bool displayTurnedOff = false;
- unsigned long lastUpdate = 0;
+ unsigned long nextUpdate = 0;
unsigned long lastRedraw = 0;
unsigned long overlayUntil = 0;
+
// Set to 2 or 3 to mark lines 2 or 3. Other values ignored.
- byte markLineNum = 0;
- byte markColNum = 0;
+ byte markLineNum = 255;
+ byte markColNum = 255;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
+ static const char _enabled[];
static const char _contrast[];
static const char _refreshRate[];
static const char _screenTimeOut[];
static const char _flip[];
static const char _sleepMode[];
static const char _clockMode[];
+ static const char _showSeconds[];
static const char _busClkFrequency[];
+ static const char _contrastFix[];
// If display does not work or looks corrupted check the
// constructor reference:
@@ -293,154 +186,189 @@ class FourLineDisplayUsermod : public Usermod {
// or check the gallery:
// https://github.com/olikraus/u8g2/wiki/gallery
+ // some displays need this to properly apply contrast
+ void setVcomh(bool highContrast) {
+ u8x8_t *u8x8_struct = u8x8->getU8x8();
+ u8x8_cad_StartTransfer(u8x8_struct);
+ u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value
+ u8x8_cad_SendArg(u8x8_struct, highContrast ? 0x000 : 0x040); //value 0 for fix, reboot resets default back to 64
+ u8x8_cad_EndTransfer(u8x8_struct);
+ }
+
public:
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void setup() {
- if (type == NONE) return;
- if (type == SSD1306_SPI || type == SSD1306_SPI64) {
- PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true}, { ioPin[2], true }, { ioPin[3], true}, { ioPin[4], true }};
- if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
+ if (type == NONE || !enabled) return;
+
+ bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
+ PinOwner po = PinOwner::UM_FourLineDisplay;
+ if (isSPI) {
+ isHW = (ioPin[0]==HW_PIN_CLOCKSPI && ioPin[1]==HW_PIN_DATASPI);
+ PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true }};
+ if (!pinManager.allocateMultiplePins(pins, 5, po)) { type=NONE; return; }
} else {
- PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true} };
- if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
+ isHW = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA);
+ PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } };
+ if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
+ if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; }
}
+
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) {
case SSD1306:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
- lineHeight = 1;
+ u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino);
break;
case SH1106:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
- lineHeight = 2;
+ u8x8_Setup(u8x8.getU8x8(), u8x8_d_sh1106_128x64_winstar, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino);
break;
case SSD1306_64:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
- lineHeight = 2;
+ u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino);
break;
case SSD1305:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
- lineHeight = 1;
+ u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1305_128x32_adafruit, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
break;
case SSD1305_64:
- #ifdef ESP8266
- if (!(ioPin[0]==5 && ioPin[1]==4))
- u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
- else
- #endif
- u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
- lineHeight = 2;
+ 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:
- if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
- 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
- lineHeight = 1;
+ 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:
- if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
- 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
- lineHeight = 2;
+ 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:
+ 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:
+ 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) {
DEBUG_PRINTLN(F("Display init failed."));
- for (byte i=0; i<5 && ioPin[i]>=0; i++) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
+ pinManager.deallocateMultiplePins((const uint8_t*)ioPin, isSPI ? 5 : 2, po);
type = NONE;
return;
}
- initDone = true;
+ lineHeight = u8x8->getRows() > 4 ? 2 : 1;
DEBUG_PRINTLN(F("Starting display."));
- if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too
+ 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...");
+ //drawString(0, 0, "Loading...");
+ overlayLogo(3500);
+ initDone = true;
}
// gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here
- void connected() {}
+ void connected() {
+ knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() :
+ knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
+ networkOverlay(PSTR("NETWORK INFO"),7000);
+ }
/**
* Da loop.
*/
void loop() {
- if (displayTurnedOff && millis() - lastUpdate < 1000) {
- return;
- }else if (millis() - lastUpdate < refreshRate){
- return;}
+ if (!enabled || strip.isUpdating()) return;
+ unsigned long now = millis();
+ if (now < nextUpdate) return;
+ nextUpdate = now + ((displayTurnedOff && clockMode && showSeconds) ? 1000 : refreshRate);
redraw(false);
- lastUpdate = millis();
}
/**
* Wrappers for screen drawing
*/
void setFlipMode(uint8_t mode) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setFlipMode(mode);
}
void setContrast(uint8_t contrast) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setContrast(contrast);
}
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setFont(u8x8_font_chroma48medium8_r);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string);
else u8x8->drawString(col, row, string);
}
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->draw2x2String(col, row, string);
}
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setFont(font);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph);
else u8x8->drawGlyph(col, row, glyph);
}
+ void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) {
+ if (type == NONE || !enabled) return;
+ u8x8->setFont(font);
+ u8x8->draw2x2Glyph(col, row, glyph);
+ }
uint8_t getCols() {
- if (type==NONE) return 0;
+ if (type==NONE || !enabled) return 0;
return u8x8->getCols();
}
void clear() {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->clear();
}
void setPowerSave(uint8_t save) {
- if (type==NONE) return;
+ if (type == NONE || !enabled) return;
u8x8->setPowerSave(save);
}
@@ -451,99 +379,104 @@ class FourLineDisplayUsermod : public Usermod {
}
//function to update lastredraw
- void updateRedrawTime(){
- lastRedraw = millis();
- }
+ void updateRedrawTime() {
+ lastRedraw = millis();
+ }
/**
* Redraw the screen (but only if things have changed
* or if forceRedraw).
*/
void redraw(bool forceRedraw) {
- if (type==NONE) return;
- if (overlayUntil > 0) {
- if (millis() >= overlayUntil) {
- // Time to display the overlay has elapsed.
- overlayUntil = 0;
- forceRedraw = true;
- } else {
- // We are still displaying the overlay
- // Don't redraw.
- return;
- }
+ bool needRedraw = false;
+ unsigned long now = millis();
+
+ if (type == NONE || !enabled) return;
+ if (overlayUntil > 0) {
+ if (now >= overlayUntil) {
+ // Time to display the overlay has elapsed.
+ overlayUntil = 0;
+ forceRedraw = true;
+ } else {
+ // We are still displaying the overlay
+ // Don't redraw.
+ return;
}
+ }
+
+ if (apActive && WLED_WIFI_CONFIGURED && now<15000) {
+ knownSsid = apSSID;
+ networkOverlay(PSTR("NETWORK INFO"),30000);
+ return;
+ }
-
// Check if values which are shown on display changed from the last time.
if (forceRedraw) {
- needRedraw = true;
+ needRedraw = true;
+ clear();
} else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon
- powerON = !powerON;
- drawStatusIcons();
- lastRedraw = millis();
+ powerON = !powerON;
+ drawStatusIcons();
+ return;
} else if (knownnightlight != nightlightActive) { //trigger moon icon
- knownnightlight = nightlightActive;
- drawStatusIcons();
- if (knownnightlight) overlay(" Timer On", 1000, 6);
- lastRedraw = millis();
- }else if (wificonnected != interfacesInited){ //trigger wifi icon
- wificonnected = interfacesInited;
- drawStatusIcons();
- lastRedraw = millis();
- } else if (knownMode != effectCurrent) {
- knownMode = effectCurrent;
- if(displayTurnedOff)needRedraw = true;
- else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3);
- } else if (knownPalette != effectPalette) {
- knownPalette = effectPalette;
- if(displayTurnedOff)needRedraw = true;
- else showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 2);
+ knownnightlight = nightlightActive;
+ drawStatusIcons();
+ if (knownnightlight) {
+ String timer = PSTR("Timer On");
+ center(timer,LINE_BUFFER_SIZE-1);
+ overlay(timer.c_str(), 2500, 6);
+ }
+ return;
+ } else if (wificonnected != interfacesInited) { //trigger wifi icon
+ wificonnected = interfacesInited;
+ drawStatusIcons();
+ return;
+ } else if (knownMode != effectCurrent || knownPalette != effectPalette) {
+ if (displayTurnedOff) needRedraw = true;
+ else {
+ if (knownPalette != effectPalette) { showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); knownPalette = effectPalette; }
+ if (knownMode != effectCurrent) { showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); knownMode = effectCurrent; }
+ lastRedraw = now;
+ return;
+ }
} else if (knownBrightness != bri) {
- if(displayTurnedOff && nightlightActive){needRedraw = false; knownBrightness = bri;}
- else if(displayTurnedOff)needRedraw = true;
- else updateBrightness();
+ if (displayTurnedOff && nightlightActive) { knownBrightness = bri; }
+ else if (!displayTurnedOff) { updateBrightness(); lastRedraw = now; return; }
} else if (knownEffectSpeed != effectSpeed) {
- if(displayTurnedOff)needRedraw = true;
- else updateSpeed();
+ if (displayTurnedOff) needRedraw = true;
+ else { updateSpeed(); lastRedraw = now; return; }
} else if (knownEffectIntensity != effectIntensity) {
- if(displayTurnedOff)needRedraw = true;
- else updateIntensity();
+ if (displayTurnedOff) needRedraw = true;
+ else { updateIntensity(); lastRedraw = now; return; }
}
-
if (!needRedraw) {
// Nothing to change.
// Turn off display after 1 minutes with no change.
- if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) {
+ if (sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) {
// We will still check if there is a change in redraw()
// and turn it back on if it changed.
+ clear();
sleepOrClock(true);
- } else if (displayTurnedOff && clockMode) {
+ } else if (displayTurnedOff && ntpEnabled) {
showTime();
}
return;
- } else {
- clear();
}
- needRedraw = false;
- lastRedraw = millis();
+ lastRedraw = now;
- if (displayTurnedOff) {
- // Turn the display back on
- sleepOrClock(false);
- }
+ // Turn the display back on
+ wakeDisplay();
// Update last known values.
- knownSsid = apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() :
- knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
- knownBrightness = bri;
- knownMode = effectCurrent;
- knownPalette = effectPalette;
- knownEffectSpeed = effectSpeed;
+ knownBrightness = bri;
+ knownMode = effectCurrent;
+ knownPalette = effectPalette;
+ knownEffectSpeed = effectSpeed;
knownEffectIntensity = effectIntensity;
- knownnightlight = nightlightActive;
- wificonnected = interfacesInited;
+ knownnightlight = nightlightActive;
+ wificonnected = interfacesInited;
// Do the actual drawing
// First row: Icons
@@ -563,57 +496,63 @@ class FourLineDisplayUsermod : public Usermod {
showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info
}
- void updateBrightness(){
+ void updateBrightness() {
knownBrightness = bri;
- if(overlayUntil == 0){
- brightness100 = (((float)(bri)/255)*100);
- char lineBuffer[4];
- sprintf_P(lineBuffer, PSTR("%-3d"), brightness100);
- drawString(1, lineHeight, lineBuffer);
- lastRedraw = millis();}
+ if (overlayUntil == 0) {
+ brightness100 = ((uint16_t)bri*100)/255;
+ char lineBuffer[4];
+ sprintf_P(lineBuffer, PSTR("%-3d"), brightness100);
+ drawString(1, lineHeight, lineBuffer);
+ //lastRedraw = millis();
+ }
}
- void updateSpeed(){
+ void updateSpeed() {
knownEffectSpeed = effectSpeed;
- if(overlayUntil == 0){
- fxspeed100 = (((float)(effectSpeed)/255)*100);
- char lineBuffer[4];
- sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100);
- drawString(5, lineHeight, lineBuffer);
- lastRedraw = millis();}
+ if (overlayUntil == 0) {
+ fxspeed100 = ((uint16_t)effectSpeed*100)/255;
+ char lineBuffer[4];
+ sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100);
+ drawString(5, lineHeight, lineBuffer);
+ //lastRedraw = millis();
+ }
}
- void updateIntensity(){
+ void updateIntensity() {
knownEffectIntensity = effectIntensity;
- if(overlayUntil == 0){
- fxintensity100 = (((float)(effectIntensity)/255)*100);
- char lineBuffer[4];
- sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100);
- drawString(9, lineHeight, lineBuffer);
- lastRedraw = millis();}
- }
-
- void draw2x2GlyphIcons(){
- if(lineHeight == 2){
- drawGlyph(1, 0, 1, u8x8_font_benji_custom_icons_2x2, true);//brightness icon
- drawGlyph(5, 0, 2, u8x8_font_benji_custom_icons_2x2, true);//speed icon
- drawGlyph(9, 0, 3, u8x8_font_benji_custom_icons_2x2, true);//intensity icon
- drawGlyph(14, 2*lineHeight, 4, u8x8_font_benji_custom_icons_2x2, true);//palette icon
- drawGlyph(14, 3*lineHeight, 5, u8x8_font_benji_custom_icons_2x2, true);//effect icon
+ if (overlayUntil == 0) {
+ fxintensity100 = ((uint16_t)effectIntensity*100)/255;
+ char lineBuffer[4];
+ sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100);
+ drawString(9, lineHeight, lineBuffer);
+ //lastRedraw = millis();
}
- else{
- drawGlyph(2, 0, 69, u8x8_font_open_iconic_weather_1x1);//brightness icon
- drawGlyph(6, 0, 72, u8x8_font_open_iconic_play_1x1);//speed icon
- drawGlyph(10, 0, 78, u8x8_font_open_iconic_thing_1x1);//intensity icon
- drawGlyph(15, 2*lineHeight, 4, u8x8_font_benji_custom_icons_1x1);//palette icon
- drawGlyph(15, 3*lineHeight, 70, u8x8_font_open_iconic_thing_1x1);//effect icon
+ }
+
+ void draw2x2GlyphIcons() {
+ if (lineHeight == 2) {
+ drawGlyph( 1, 0, 1, u8x8_4LineDisplay_WLED_icons_2x2, true); //brightness icon
+ drawGlyph( 5, 0, 2, u8x8_4LineDisplay_WLED_icons_2x2, true); //speed icon
+ drawGlyph( 9, 0, 3, u8x8_4LineDisplay_WLED_icons_2x2, true); //intensity icon
+ drawGlyph(14, 2*lineHeight, 4, u8x8_4LineDisplay_WLED_icons_2x2, true); //palette icon
+ drawGlyph(14, 3*lineHeight, 5, u8x8_4LineDisplay_WLED_icons_2x2, true); //effect icon
+ } else {
+ drawGlyph( 1, 0, 1, u8x8_4LineDisplay_WLED_icons_2x1); //brightness icon
+ drawGlyph( 5, 0, 2, u8x8_4LineDisplay_WLED_icons_2x1); //speed icon
+ drawGlyph( 9, 0, 3, u8x8_4LineDisplay_WLED_icons_2x1); //intensity icon
+ drawGlyph(15, 2, 4, u8x8_4LineDisplay_WLED_icons_1x1); //palette icon
+ drawGlyph(15, 3, 5, u8x8_4LineDisplay_WLED_icons_1x1); //effect icon
}
}
- void drawStatusIcons(){
- drawGlyph(14, 0, 80 + (wificonnected?0:1), u8x8_font_open_iconic_embedded_1x1, true); // wifi icon
- drawGlyph(15, 0, 78 + (bri > 0 ? 0 : 3), u8x8_font_open_iconic_embedded_1x1, true); // power icon
- drawGlyph(13, 0, 66 + (nightlightActive?0:4), u8x8_font_open_iconic_weather_1x1, true); // moon icon for nighlight mode
+ void drawStatusIcons() {
+ uint8_t col = 15;
+ uint8_t row = 0;
+ drawGlyph(col, row, (wificonnected ? 20 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // wifi icon
+ if (lineHeight==2) { col--; } else { row++; }
+ drawGlyph(col, row, (bri > 0 ? 9 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // power icon
+ if (lineHeight==2) { col--; } else { col = row = 0; }
+ drawGlyph(col, row, (nightlightActive ? 6 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // moon icon for nighlight mode
}
/**
@@ -622,99 +561,69 @@ class FourLineDisplayUsermod : public Usermod {
* pass line and colum info
*/
void setMarkLine(byte newMarkLineNum, byte newMarkColNum) {
- markLineNum = newMarkLineNum;
- markColNum = newMarkColNum;
+ markLineNum = newMarkLineNum;
+ markColNum = newMarkColNum;
}
//Draw the arrow for the current setting beiong changed
- void drawArrow(){
- if(markColNum != 255 && markLineNum !=255)drawGlyph(markColNum, markLineNum*lineHeight, 69, u8x8_font_open_iconic_play_1x1);
+ void drawArrow() {
+ if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1);
}
//Display the current effect or palette (desiredEntry)
// on the appropriate line (row).
void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) {
- knownMode = effectCurrent;
- knownPalette = effectPalette;
- if(overlayUntil == 0){
- char lineBuffer[MAX_JSON_CHARS];
+ char lineBuffer[MAX_JSON_CHARS];
+ if (overlayUntil == 0) {
+ // Find the mode name in JSON
+ uint8_t printedChars = extractModeName(inputEffPal, qstring, lineBuffer, MAX_JSON_CHARS-1);
+ if (lineBuffer[0]=='*' && lineBuffer[1]==' ') {
+ // remove "* " from dynamic palettes
+ for (byte i=2; i<=printedChars; i++) lineBuffer[i-2] = lineBuffer[i]; //include '\0'
+ printedChars -= 2;
+ }
+ if (lineHeight == 2) { // use this code for 8 line display
char smallBuffer1[MAX_MODE_LINE_SPACE];
char smallBuffer2[MAX_MODE_LINE_SPACE];
- char smallBuffer3[MAX_MODE_LINE_SPACE+1];
- uint8_t qComma = 0;
- bool insideQuotes = false;
- bool spaceHit = false;
- uint8_t printedChars = 0;
uint8_t smallChars1 = 0;
uint8_t smallChars2 = 0;
- uint8_t smallChars3 = 0;
- uint8_t totalCount = 0;
- char singleJsonSymbol;
-
- // Find the mode name in JSON
- for (size_t i = 0; i < strlen_P(qstring); i++) { //find and get the full text for printing
- singleJsonSymbol = pgm_read_byte_near(qstring + i);
- if (singleJsonSymbol == '\0') break;
- switch (singleJsonSymbol) {
- case '"':
- insideQuotes = !insideQuotes;
- break;
- case '[':
- case ']':
- break;
- case ',':
- qComma++;
- default:
- if (!insideQuotes || (qComma != inputEffPal)) break;
- lineBuffer[printedChars++] = singleJsonSymbol;
- totalCount++;
- }
- if ((qComma > inputEffPal)) break;
- }
-
- if(lineHeight ==2){ // use this code for 8 line display
- if(printedChars < (MAX_MODE_LINE_SPACE)){ // use big font if the text fits
- for (;printedChars < (MAX_MODE_LINE_SPACE-1); printedChars++) {lineBuffer[printedChars]=' '; }
- lineBuffer[printedChars] = 0;
- drawString(1, row*lineHeight, lineBuffer);
- lastRedraw = millis();
- }else{ // for long names divide the text into 2 lines and print them small
- for (uint8_t i = 0; i < printedChars; i++){
- switch (lineBuffer[i]){
- case ' ':
- if(i > 4 && !spaceHit) {
- spaceHit = true;
- break;}
- if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i];
- if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i];
- break;
- default:
- if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i];
- if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i];
+ if (printedChars < MAX_MODE_LINE_SPACE) { // use big font if the text fits
+ while (printedChars < (MAX_MODE_LINE_SPACE-1)) lineBuffer[printedChars++]=' ';
+ lineBuffer[printedChars] = 0;
+ drawString(1, row*lineHeight, lineBuffer);
+ } else { // for long names divide the text into 2 lines and print them small
+ bool spaceHit = false;
+ for (uint8_t i = 0; i < printedChars; i++) {
+ switch (lineBuffer[i]) {
+ case ' ':
+ if (i > 4 && !spaceHit) {
+ spaceHit = true;
break;
}
- }
- for (; smallChars1 < (MAX_MODE_LINE_SPACE-1); smallChars1++) smallBuffer1[smallChars1]=' ';
- smallBuffer1[smallChars1] = 0;
- drawString(1, row*lineHeight, smallBuffer1, true);
- for (; smallChars2 < (MAX_MODE_LINE_SPACE-1); smallChars2++) smallBuffer2[smallChars2]=' ';
- smallBuffer2[smallChars2] = 0;
- drawString(1, row*lineHeight+1, smallBuffer2, true);
- lastRedraw = millis();
+ if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i];
+ else smallBuffer1[smallChars1++] = lineBuffer[i];
+ break;
+ default:
+ if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i];
+ else smallBuffer1[smallChars1++] = lineBuffer[i];
+ break;
+ }
}
+ while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' ';
+ smallBuffer1[smallChars1] = 0;
+ drawString(1, row*lineHeight, smallBuffer1, true);
+ while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' ';
+ smallBuffer2[smallChars2] = 0;
+ drawString(1, row*lineHeight+1, smallBuffer2, true);
}
- else{ // use this code for 4 ling displays
- if (printedChars > MAX_MODE_LINE_SPACE) printedChars = MAX_MODE_LINE_SPACE;
- for (uint8_t i = 0; i < printedChars; i++){
- smallBuffer3[smallChars3++] = lineBuffer[i];
- }
-
- for (; smallChars3 < (MAX_MODE_LINE_SPACE); smallChars3++) smallBuffer3[smallChars3]=' ';
- smallBuffer3[smallChars3] = 0;
- drawString(1, row*lineHeight, smallBuffer3, true);
- lastRedraw = millis();
- }
- }
+ } else { // use this code for 4 ling displays
+ char smallBuffer3[MAX_MODE_LINE_SPACE+1]; // uses 1x1 icon for mode/palette
+ uint8_t smallChars3 = 0;
+ for (uint8_t i = 0; i < MAX_MODE_LINE_SPACE; i++) smallBuffer3[smallChars3++] = (i >= printedChars) ? ' ' : lineBuffer[i];
+ smallBuffer3[smallChars3] = 0;
+ drawString(1, row*lineHeight, smallBuffer3, true);
+ }
+ }
}
/**
@@ -724,60 +633,147 @@ class FourLineDisplayUsermod : public Usermod {
* to wake up the screen.
*/
bool wakeDisplay() {
- //knownHour = 99;
+ if (type == NONE || !enabled) return false;
if (displayTurnedOff) {
+ clear();
// Turn the display back on
sleepOrClock(false);
- redraw(true);
+ //lastRedraw = millis();
return true;
}
return false;
}
/**
- * Allows you to show one line and a glyph as overlay for a
- * period of time.
+ * 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) {
- if (displayTurnedOff) {
- // Turn the display back on
- sleepOrClock(false);
- }
+ // Turn the display back on
+ if (!wakeDisplay()) clear();
+ // Print the overlay
+ if (glyphType>0 && glyphType<255) {
+ if (lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_4LineDisplay_WLED_icons_6x6, true); // use 3x3 font with draw2x2Glyph() if flash runs short and comment out 6x6 font
+ else drawGlyph(6, 0, glyphType, u8x8_4LineDisplay_WLED_icons_3x3, true);
+ }
+ if (line1) {
+ String buf = line1;
+ center(buf, getCols());
+ drawString(0, (glyphType<255?3:0)*lineHeight, buf.c_str());
+ }
+ overlayUntil = millis() + showHowLong;
+ }
- // Print the overlay
- clear();
- if (glyphType > 0){
- if ( lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_font_benji_custom_icons_6x6, true);
- else drawGlyph(7, lineHeight, glyphType, u8x8_font_benji_custom_icons_2x2, true);
- }
- if (line1) drawString(0, 3*lineHeight, line1);
- overlayUntil = millis() + showHowLong;
+ /**
+ * Allows you to show Akemi WLED logo overlay for a period of time.
+ * Clears the screen and prints.
+ */
+ void overlayLogo(long showHowLong) {
+ // Turn the display back on
+ if (!wakeDisplay()) clear();
+ // Print the overlay
+ if (lineHeight == 2) {
+ //add a bit of randomness
+ switch (millis()%3) {
+ case 0:
+ //WLED
+ draw2x2Glyph( 0, 2, 1, u8x8_wled_logo_2x2);
+ draw2x2Glyph( 4, 2, 2, u8x8_wled_logo_2x2);
+ draw2x2Glyph( 8, 2, 3, u8x8_wled_logo_2x2);
+ draw2x2Glyph(12, 2, 4, u8x8_wled_logo_2x2);
+ break;
+ case 1:
+ //WLED Akemi
+ drawGlyph( 2, 2, 1, u8x8_wled_logo_akemi_4x4, true);
+ drawGlyph( 6, 2, 2, u8x8_wled_logo_akemi_4x4, true);
+ drawGlyph(10, 2, 3, u8x8_wled_logo_akemi_4x4, true);
+ break;
+ case 2:
+ //Akemi
+ //draw2x2Glyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_3x3); // use this if flash runs short and comment out 6x6 font
+ drawGlyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_6x6, true);
+ drawString(6, 6, "WLED");
+ break;
+ }
+ } else {
+ switch (millis()%3) {
+ case 0:
+ //WLED
+ draw2x2Glyph( 0, 0, 1, u8x8_wled_logo_2x2);
+ draw2x2Glyph( 4, 0, 2, u8x8_wled_logo_2x2);
+ draw2x2Glyph( 8, 0, 3, u8x8_wled_logo_2x2);
+ draw2x2Glyph(12, 0, 4, u8x8_wled_logo_2x2);
+ break;
+ case 1:
+ //WLED Akemi
+ drawGlyph( 2, 0, 1, u8x8_wled_logo_akemi_4x4);
+ drawGlyph( 6, 0, 2, u8x8_wled_logo_akemi_4x4);
+ drawGlyph(10, 0, 3, u8x8_wled_logo_akemi_4x4);
+ break;
+ case 2:
+ //Akemi
+ //drawGlyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_4x4); // a bit nicer, but uses extra 1.5k flash
+ draw2x2Glyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_2x2);
+ break;
+ }
+ }
+ overlayUntil = millis() + 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) {
+ // Turn the display back on
+ if (!wakeDisplay()) clear();
+ // Print the overlay
+ if (line1) {
+ String buf = line1;
+ center(buf, getCols());
+ drawString(0, 1*lineHeight, buf.c_str());
+ }
+ if (line2) {
+ String buf = line2;
+ center(buf, getCols());
+ drawString(0, 2*lineHeight, buf.c_str());
+ }
+ overlayUntil = millis() + showHowLong;
}
void networkOverlay(const char* line1, long showHowLong) {
- if (displayTurnedOff) {
- // Turn the display back on
- sleepOrClock(false);
- }
+ String line;
+ // Turn the display back on
+ if (!wakeDisplay()) clear();
// Print the overlay
- clear();
- // First row string
- if (line1) drawString(0, 0, line1);
+ if (line1) {
+ line = line1;
+ center(line, getCols());
+ drawString(0, 0, line.c_str());
+ }
// Second row with Wifi name
- String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); //
- drawString(0, lineHeight, ssidString.c_str());
+ line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0);
+ if (line.length() < getCols()) center(line, getCols());
+ drawString(0, lineHeight, line.c_str());
// Print `~` char to indicate that SSID is longer, than our display
- if (knownSsid.length() > getCols()) {
- drawString(getCols() - 1, 0, "~");
- }
- // Third row with IP and Psssword in AP Mode
- drawString(0, lineHeight*2, (knownIp.toString()).c_str());
- if (apActive) {
- String appassword = apPass;
- drawString(0, lineHeight*3, appassword.c_str());
- }
- overlayUntil = millis() + showHowLong;
+ if (knownSsid.length() > getCols()) {
+ drawString(getCols() - 1, 0, "~");
+ }
+ // Third row with IP and Password in AP Mode
+ line = knownIp.toString();
+ center(line, getCols());
+ drawString(0, lineHeight*2, line.c_str());
+ line = "";
+ if (apActive) {
+ line = apPass;
+ } else if (strcmp(serverDescription, "WLED") != 0) {
+ line = serverDescription;
+ }
+ center(line, getCols());
+ drawString(0, lineHeight*3, line.c_str());
+ overlayUntil = millis() + showHowLong;
}
@@ -786,16 +782,15 @@ class FourLineDisplayUsermod : public Usermod {
*/
void sleepOrClock(bool enabled) {
if (enabled) {
- if (clockMode) {
- clear();
- knownMinute = 99;
- showTime();
- }else setPowerSave(1);
displayTurnedOff = true;
- }
- else {
- setPowerSave(0);
+ if (clockMode && ntpEnabled) {
+ knownMinute = knownHour = 99;
+ showTime();
+ } else
+ setPowerSave(1);
+ } else {
displayTurnedOff = false;
+ setPowerSave(0);
}
}
@@ -805,31 +800,120 @@ class FourLineDisplayUsermod : public Usermod {
* the useAMPM configuration.
*/
void showTime() {
- if(knownMinute != minute(localTime)){ //only redraw clock if it has changed
+ if (type == NONE || !enabled || !displayTurnedOff) return;
+
char lineBuffer[LINE_BUFFER_SIZE];
+ static byte lastSecond;
+ byte secondCurrent = second(localTime);
+ byte minuteCurrent = minute(localTime);
+ byte hourCurrent = hour(localTime);
+
+ if (knownMinute != minuteCurrent) { //only redraw clock if it has changed
+ //updateLocalTime();
+ byte AmPmHour = hourCurrent;
+ boolean isitAM = true;
+ if (useAMPM) {
+ if (AmPmHour > 11) { AmPmHour -= 12; isitAM = false; }
+ if (AmPmHour == 0) { AmPmHour = 12; }
+ }
+ if (knownHour != hourCurrent) {
+ // only update date when hour changes
+ sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime));
+ draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day
+ }
+ sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent);
+ draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds
+ if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time
+
+ drawStatusIcons(); //icons power, wifi, timer, etc
- //updateLocalTime();
- byte AmPmHour = hour(localTime);
- boolean isitAM = true;
- if (useAMPM) {
- if (AmPmHour > 11) AmPmHour -= 12;
- if (AmPmHour == 0) AmPmHour = 12;
- if (hour(localTime) > 11) isitAM = false;
+ knownMinute = minuteCurrent;
+ knownHour = hourCurrent;
+ } else {
+ if (secondCurrent == lastSecond) return;
+ }
+ if (showSeconds) {
+ lastSecond = secondCurrent;
+ draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":");
+ sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent);
+ drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line
}
- clear();
- drawStatusIcons(); //icons power, wifi, timer, etc
+ }
- sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime));
- draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day
+ /**
+ * 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) {
+ yield();
+ if (!enabled
+ || b // butto 0 only
+ || buttonType[b] == BTN_TYPE_SWITCH
+ || buttonType[b] == BTN_TYPE_NONE
+ || buttonType[b] == BTN_TYPE_RESERVED
+ || buttonType[b] == BTN_TYPE_PIR_SENSOR
+ || buttonType[b] == BTN_TYPE_ANALOG
+ || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
+ return false;
+ }
- sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hour(localTime)), minute(localTime));
- draw2x2String(TIME_INDENT+2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds
+ unsigned long now = millis();
+ static bool buttonPressedBefore = false;
+ static bool buttonLongPressed = false;
+ static unsigned long buttonPressedTime = 0;
+ static unsigned long buttonWaitTime = 0;
+ bool handled = true;
+
+ //momentary button logic
+ if (isButtonPressed(b)) { //pressed
+
+ if (!buttonPressedBefore) buttonPressedTime = now;
+ buttonPressedBefore = true;
+
+ if (now - buttonPressedTime > 600) { //long press
+ buttonLongPressed = true;
+ //TODO: handleButton() handles button 0 without preset in a different way for double click
+ //so we need to override with same behaviour
+ longPressAction(0);
+ //handled = false;
+ }
- if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time
- knownMinute = minute(localTime);
+ } else if (!isButtonPressed(b) && buttonPressedBefore) { //released
+
+ long dur = now - buttonPressedTime;
+ if (dur < 50) {
+ buttonPressedBefore = false;
+ return true;
+ } //too short "press", debounce
+
+ bool doublePress = buttonWaitTime; //did we have short press before?
+ buttonWaitTime = 0;
+
+ if (!buttonLongPressed) { //short press
+ // if this is second release within 350ms it is a double press (buttonWaitTime!=0)
+ //TODO: handleButton() handles button 0 without preset in a different way for double click
+ if (doublePress) {
+ networkOverlay(PSTR("NETWORK INFO"),7000);
+ handled = true;
+ } else {
+ buttonWaitTime = now;
+ }
+ }
+ buttonPressedBefore = false;
+ buttonLongPressed = false;
}
+ // if 350ms elapsed since last press/release it is a short press
+ if (buttonWaitTime && now - buttonWaitTime > 350 && !buttonPressedBefore) {
+ buttonWaitTime = 0;
+ //TODO: handleButton() handles button 0 without preset in a different way for double click
+ //so we need to override with same behaviour
+ shortPressAction(0);
+ //handled = false;
+ }
+ return handled;
}
-
+
/*
* 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.
@@ -873,16 +957,20 @@ class FourLineDisplayUsermod : public Usermod {
*/
void addToConfig(JsonObject& root) {
JsonObject top = root.createNestedObject(FPSTR(_name));
+ top[FPSTR(_enabled)] = enabled;
JsonArray io_pin = top.createNestedArray("pin");
for (byte i=0; i<5; i++) io_pin.add(ioPin[i]);
- top["help4PinTypes"] = F("Clk,Data,CS,DC,RST"); // help for Settings page
+ top["help4Pins"] = F("Clk,Data,CS,DC,RST"); // help for Settings page
top["type"] = type;
+ top["help4Type"] = F("1=SSD1306,2=SH1106,3=SSD1306_128x64,4=SSD1305,5=SSD1305_128x64,6=SSD1306_SPI,7=SSD1306_SPI_128x64"); // help for Settings page
top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_contrast)] = contrast;
- top[FPSTR(_refreshRate)] = refreshRate/10;
+ top[FPSTR(_contrastFix)] = (bool) contrastFix;
+ top[FPSTR(_refreshRate)] = refreshRate;
top[FPSTR(_screenTimeOut)] = screenTimeout/1000;
top[FPSTR(_sleepMode)] = (bool) sleepMode;
top[FPSTR(_clockMode)] = (bool) clockMode;
+ top[FPSTR(_showSeconds)] = (bool) showSeconds;
top[FPSTR(_busClkFrequency)] = ioFrequency/1000;
DEBUG_PRINTLN(F("4 Line Display config saved."));
}
@@ -907,15 +995,22 @@ class FourLineDisplayUsermod : public Usermod {
return false;
}
+ enabled = top[FPSTR(_enabled)] | enabled;
newType = top["type"] | newType;
for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i];
flip = top[FPSTR(_flip)] | flip;
contrast = top[FPSTR(_contrast)] | contrast;
- refreshRate = (top[FPSTR(_refreshRate)] | refreshRate/10) * 10;
+ refreshRate = top[FPSTR(_refreshRate)] | refreshRate;
+ refreshRate = min(5000, max(250, (int)refreshRate));
screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000;
sleepMode = top[FPSTR(_sleepMode)] | sleepMode;
clockMode = top[FPSTR(_clockMode)] | clockMode;
- ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
+ showSeconds = top[FPSTR(_showSeconds)] | showSeconds;
+ contrastFix = top[FPSTR(_contrastFix)] | contrastFix;
+ if (newType == SSD1306_SPI || newType == SSD1306_SPI64)
+ ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
+ else
+ ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
DEBUG_PRINT(FPSTR(_name));
if (!initDone) {
@@ -930,24 +1025,28 @@ class FourLineDisplayUsermod : public Usermod {
for (byte i=0; i<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; }
if (pinsChanged || type!=newType) {
if (type != NONE) delete u8x8;
- for (byte i=0; i<5; i++) {
- if (ioPin[i]>=0) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
- ioPin[i] = newPin[i];
- }
+ PinOwner po = PinOwner::UM_FourLineDisplay;
+ 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, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po);
+ for (byte i=0; i<5; i++) ioPin[i] = newPin[i];
if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1
type = NONE;
return true;
} else type = newType;
setup();
needsRedraw |= true;
+ } else {
+ u8x8->setBusClock(ioFrequency); // can be used for SPI too
+ setVcomh(contrastFix);
+ setContrast(contrast);
+ setFlipMode(flip);
}
- if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too
- setContrast(contrast);
- setFlipMode(flip);
+ knownHour = 99;
if (needsRedraw && !wakeDisplay()) redraw(true);
+ else overlayLogo(3500);
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
- return !(top[_busClkFrequency]).isNull();
+ return !top[FPSTR(_contrastFix)].isNull();
}
/*
@@ -960,11 +1059,14 @@ class FourLineDisplayUsermod : public Usermod {
};
// strings to reduce flash memory usage (used more than twice)
-const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
-const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
-const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate0.01Sec";
-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::_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";
diff --git a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h
index 4994e41200..0953be6a20 100644
--- a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h
+++ b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h
@@ -43,9 +43,9 @@
#define FLD_LINE_MODE 0
#define FLD_LINE_EFFECT_SPEED 0
#define FLD_LINE_EFFECT_INTENSITY 0
-#define FLD_LINE_EFFECT_FFT1 0 //WLEDSR
-#define FLD_LINE_EFFECT_FFT2 0 //WLEDSR
-#define FLD_LINE_EFFECT_FFT3 0 //WLEDSR
+#define FLD_LINE_EFFECT_CUSTOM1 0 //WLEDSR
+#define FLD_LINE_EFFECT_CUSTOM2 0 //WLEDSR
+#define FLD_LINE_EFFECT_CUSTOM3 0 //WLEDSR
#define FLD_LINE_PALETTE 0
#define FLD_LINE_SQUELCH 0 //WLEDSR
#define FLD_LINE_GAIN 0 //WLEDSR
@@ -216,13 +216,13 @@ class RotaryEncoderUIUsermod : public Usermod {
changedState = changeState(sliderNames[1], FLD_LINE_EFFECT_INTENSITY, 3);
break;
case 4:
- changedState = changeState(sliderNames[2], FLD_LINE_EFFECT_FFT1, 3); //WLEDSR
+ changedState = changeState(sliderNames[2], FLD_LINE_EFFECT_CUSTOM1, 3); //WLEDSR
break;
case 5:
- changedState = changeState(sliderNames[3], FLD_LINE_EFFECT_FFT2, 3); //WLEDSR
+ changedState = changeState(sliderNames[3], FLD_LINE_EFFECT_CUSTOM2, 3); //WLEDSR
break;
case 6:
- changedState = changeState(sliderNames[4], FLD_LINE_EFFECT_FFT3, 3); //WLEDSR
+ changedState = changeState(sliderNames[4], FLD_LINE_EFFECT_CUSTOM3, 3); //WLEDSR
break;
case 7:
changedState = changeState("Palette", FLD_LINE_PALETTE, 3);
@@ -264,13 +264,13 @@ class RotaryEncoderUIUsermod : public Usermod {
changeEffectIntensity(true);
break;
case 4:
- changeEffectFFT1(true); //WLEDSR
+ changeEffectCustom1(true); //WLEDSR
break;
case 5:
- changeEffectFFT2(true); //WLEDSR
+ changeEffectCustom2(true); //WLEDSR
break;
case 6:
- changeEffectFFT3(true); //WLEDSR
+ changeEffectCustom3(true); //WLEDSR
break;
case 7:
changePalette(true);
@@ -299,13 +299,13 @@ class RotaryEncoderUIUsermod : public Usermod {
changeEffectIntensity(false);
break;
case 4:
- changeEffectFFT1(false); //WLEDSR
+ changeEffectCustom1(false); //WLEDSR
break;
case 5:
- changeEffectFFT2(false); //WLEDSR
+ changeEffectCustom2(false); //WLEDSR
break;
case 6:
- changeEffectFFT3(false); //WLEDSR
+ changeEffectCustom3(false); //WLEDSR
break;
case 7:
changePalette(false);
@@ -359,11 +359,8 @@ class RotaryEncoderUIUsermod : public Usermod {
}
void lampUdated() {
- strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectFFT1, effectFFT2, effectFFT3, effectPalette);
- //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
- colorUpdated(CALL_MODE_DIRECT_CHANGE);
- updateInterfaces(CALL_MODE_DIRECT_CHANGE);
+ colorUpdated(CALL_MODE_BUTTON);
+ updateInterfaces(CALL_MODE_BUTTON);
}
void changeBrightness(bool increase) {
@@ -438,7 +435,7 @@ class RotaryEncoderUIUsermod : public Usermod {
lampUdated();
}
- void changeEffectFFT1(bool increase) { //WLEDSR
+ void changeEffectCustom1(bool increase) { //WLEDSR
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
@@ -446,15 +443,15 @@ class RotaryEncoderUIUsermod : public Usermod {
}
#endif
if (increase) {
- effectFFT1 = (effectFFT1 + fadeAmount <= 255) ? (effectFFT1 + fadeAmount) : 255;
+ effectCustom1 = (effectCustom1 + fadeAmount <= 255) ? (effectCustom1 + fadeAmount) : 255;
}
else {
- effectFFT1 = (effectFFT1 - fadeAmount >= 0) ? (effectFFT1 - fadeAmount) : 0;
+ effectCustom1 = (effectCustom1 - fadeAmount >= 0) ? (effectCustom1 - fadeAmount) : 0;
}
lampUdated();
}
- void changeEffectFFT2(bool increase) { //WLEDSR
+ void changeEffectCustom2(bool increase) { //WLEDSR
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
@@ -462,15 +459,15 @@ class RotaryEncoderUIUsermod : public Usermod {
}
#endif
if (increase) {
- effectFFT2 = (effectFFT2 + fadeAmount <= 255) ? (effectFFT2 + fadeAmount) : 255;
+ effectCustom2 = (effectCustom2 + fadeAmount <= 255) ? (effectCustom2 + fadeAmount) : 255;
}
else {
- effectFFT2 = (effectFFT2 - fadeAmount >= 0) ? (effectFFT2 - fadeAmount) : 0;
+ effectCustom2 = (effectCustom2 - fadeAmount >= 0) ? (effectCustom2 - fadeAmount) : 0;
}
lampUdated();
}
- void changeEffectFFT3(bool increase) { //WLEDSR
+ void changeEffectCustom3(bool increase) { //WLEDSR
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
@@ -478,10 +475,10 @@ class RotaryEncoderUIUsermod : public Usermod {
}
#endif
if (increase) {
- effectFFT3 = (effectFFT3 + fadeAmount <= 255) ? (effectFFT3 + fadeAmount) : 255;
+ effectCustom3 = (effectCustom3 + fadeAmount <= 255) ? (effectCustom3 + fadeAmount) : 255;
}
else {
- effectFFT3 = (effectFFT3 - fadeAmount >= 0) ? (effectFFT3 - fadeAmount) : 0;
+ effectCustom3 = (effectCustom3 - fadeAmount >= 0) ? (effectCustom3 - fadeAmount) : 0;
}
lampUdated();
}
diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h
index 625af0af35..4629f547a7 100644
--- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h
+++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h
@@ -19,17 +19,20 @@
// Change between modes by pressing a button.
//
// Dependencies
-// * This usermod REQURES the ModeSortUsermod
// * This Usermod works best coupled with
// FourLineDisplayUsermod.
//
-// If FourLineDisplayUsermod is used the folowing options are also inabled
+// If FourLineDisplayUsermod is used the folowing options are also enabled
//
// * main color
// * saturation of main color
// * display network (long press buttion)
//
+#ifdef USERMOD_MODE_SORT
+ #error "Usermod Mode Sort is no longer required. Remove -D USERMOD_MODE_SORT from platformio.ini"
+#endif
+
#ifndef ENCODER_DT_PIN
#define ENCODER_DT_PIN 18
#endif
@@ -44,27 +47,90 @@
// The last UI state, remove color and saturation option if diplay not active(too many options)
#ifdef USERMOD_FOUR_LINE_DISPLAY
- #define LAST_UI_STATE 6
+ #define LAST_UI_STATE 8
#else
#define LAST_UI_STATE 4
#endif
+// Number of modes at the start of the list to not sort
+#define MODE_SORT_SKIP_COUNT 1
+
+// Which list is being sorted
+static char **listBeingSorted;
+
+/**
+ * Modes and palettes are stored as strings that
+ * end in a quote character. Compare two of them.
+ * We are comparing directly within either
+ * JSON_mode_names or JSON_palette_names.
+ */
+static int re_qstringCmp(const void *ap, const void *bp) {
+ char *a = listBeingSorted[*((byte *)ap)];
+ char *b = listBeingSorted[*((byte *)bp)];
+ int i = 0;
+ do {
+ char aVal = pgm_read_byte_near(a + i);
+ if (aVal >= 97 && aVal <= 122) {
+ // Lowercase
+ aVal -= 32;
+ }
+ char bVal = pgm_read_byte_near(b + i);
+ if (bVal >= 97 && bVal <= 122) {
+ // Lowercase
+ bVal -= 32;
+ }
+ // Relly we shouldn't ever get to '\0'
+ if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') {
+ // We're done. one is a substring of the other
+ // or something happenend and the quote didn't stop us.
+ if (aVal == bVal) {
+ // Same value, probably shouldn't happen
+ // with this dataset
+ return 0;
+ }
+ else if (aVal == '"' || aVal == '\0') {
+ return -1;
+ }
+ else {
+ return 1;
+ }
+ }
+ if (aVal == bVal) {
+ // Same characters. Move to the next.
+ i++;
+ continue;
+ }
+ // We're done
+ if (aVal < bVal) {
+ return -1;
+ }
+ else {
+ return 1;
+ }
+ } while (true);
+ // We shouldn't get here.
+ return 0;
+}
+
class RotaryEncoderUIUsermod : public Usermod {
private:
- int fadeAmount = 5; // Amount to change every step (brightness)
- unsigned long currentTime;
+ int8_t fadeAmount = 5; // Amount to change every step (brightness)
unsigned long loopTime;
- unsigned long buttonHoldTIme;
+
+ unsigned long buttonPressedTime = 0;
+ unsigned long buttonWaitTime = 0;
+ bool buttonPressedBefore = false;
+ bool buttonLongPressed = false;
+
int8_t pinA = ENCODER_DT_PIN; // DT from encoder
int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder
int8_t pinC = ENCODER_SW_PIN; // SW from encoder
- unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed
- unsigned char button_state = HIGH;
- unsigned char prev_button_state = HIGH;
- bool networkShown = false;
- uint16_t currentHue1 = 6425; // default reboot color
- byte currentSat1 = 255;
+
+ unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed, ...
+
+ uint16_t currentHue1 = 16; // default boot color
+ byte currentSat1 = 255;
#ifdef USERMOD_FOUR_LINE_DISPLAY
FourLineDisplayUsermod *display;
@@ -72,7 +138,16 @@ class RotaryEncoderUIUsermod : public Usermod {
void* display = nullptr;
#endif
+ // Pointers the start of the mode names within JSON_mode_names
+ char **modes_qstrings = nullptr;
+
+ // Array of mode indexes in alphabetical order.
byte *modes_alpha_indexes = nullptr;
+
+ // Pointers the start of the palette names within JSON_palette_names
+ char **palettes_qstrings = nullptr;
+
+ // Array of palette indexes in alphabetical order.
byte *palettes_alpha_indexes = nullptr;
unsigned char Enc_A;
@@ -85,6 +160,14 @@ class RotaryEncoderUIUsermod : public Usermod {
uint8_t knownMode = 0;
uint8_t knownPalette = 0;
+ uint8_t currentCCT = 128;
+ bool isRgbw = false;
+
+ byte presetHigh = 0;
+ byte presetLow = 0;
+
+ bool applyToAll = true;
+
bool initDone = false;
bool enabled = true;
@@ -94,14 +177,94 @@ class RotaryEncoderUIUsermod : public Usermod {
static const char _DT_pin[];
static const char _CLK_pin[];
static const char _SW_pin[];
+ static const char _presetHigh[];
+ static const char _presetLow[];
+ static const char _applyToAll[];
+
+ /**
+ * Sort the modes and palettes to the index arrays
+ * modes_alpha_indexes and palettes_alpha_indexes.
+ */
+ void sortModesAndPalettes() {
+ modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount());
+ modes_alpha_indexes = re_initIndexArray(strip.getModeCount());
+ re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT);
+
+ palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount());
+ palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount());
+
+ // How many palette names start with '*' and should not be sorted?
+ // (Also skipping the first one, 'Default').
+ int skipPaletteCount = 1;
+ while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount++]) == '*') ;
+ re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount);
+ }
+
+ byte *re_initIndexArray(int numModes) {
+ byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
+ for (byte i = 0; i < numModes; i++) {
+ indexes[i] = i;
+ }
+ return indexes;
+ }
+
+ /**
+ * Return an array of mode or palette names from the JSON string.
+ * They don't end in '\0', they end in '"'.
+ */
+ char **re_findModeStrings(const char json[], int numModes) {
+ char **modeStrings = (char **)malloc(sizeof(char *) * numModes);
+ uint8_t modeIndex = 0;
+ bool insideQuotes = false;
+ // advance past the mark for markLineNum that may exist.
+ char singleJsonSymbol;
+
+ // Find the mode name in JSON
+ bool complete = false;
+ for (size_t i = 0; i < strlen_P(json); i++) {
+ singleJsonSymbol = pgm_read_byte_near(json + i);
+ if (singleJsonSymbol == '\0') break;
+ switch (singleJsonSymbol) {
+ case '"':
+ insideQuotes = !insideQuotes;
+ if (insideQuotes) {
+ // We have a new mode or palette
+ modeStrings[modeIndex] = (char *)(json + i + 1);
+ }
+ break;
+ case '[':
+ break;
+ case ']':
+ if (!insideQuotes) complete = true;
+ break;
+ case ',':
+ if (!insideQuotes) modeIndex++;
+ default:
+ if (!insideQuotes) break;
+ }
+ if (complete) break;
+ }
+ return modeStrings;
+ }
+
+ /**
+ * Sort either the modes or the palettes using quicksort.
+ */
+ void re_sortModes(char **modeNames, byte *indexes, int count, int numSkip) {
+ listBeingSorted = modeNames;
+ qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp);
+ listBeingSorted = nullptr;
+ }
+
public:
/*
- * setup() is called once at boot. WiFi is not yet connected at this point.
- * You can use it to initialize variables, sensors or similar.
- */
+ * 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()
{
+ DEBUG_PRINTLN(F("Usermod Rotary Encoder init."));
PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } };
if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) {
// BUG: configuring this usermod with conflicting pins
@@ -117,12 +280,17 @@ class RotaryEncoderUIUsermod : public Usermod {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
pinMode(pinC, INPUT_PULLUP);
- currentTime = millis();
- loopTime = currentTime;
+ loopTime = millis();
- ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT);
- modes_alpha_indexes = modeSortUsermod->getModesAlphaIndexes();
- palettes_alpha_indexes = modeSortUsermod->getPalettesAlphaIndexes();
+ for (uint8_t s = 0; s < busses.getNumBusses(); s++) {
+ Bus *bus = busses.getBus(s);
+ if (!bus || bus->getLength()==0) break;
+ isRgbw |= bus->isRgbw();
+ }
+
+ currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5;
+
+ if (!initDone) sortModesAndPalettes();
#ifdef USERMOD_FOUR_LINE_DISPLAY
// This Usermod uses FourLineDisplayUsermod for the best experience.
@@ -140,91 +308,87 @@ class RotaryEncoderUIUsermod : public Usermod {
}
/*
- * connected() is called every time the WiFi is (re)connected
- * Use it to initialize network interfaces
- */
+ * 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.
- *
- * Tips:
- * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection.
- * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker.
- *
- * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
- * Instead, use a timer check as shown here.
- */
+ * loop() is called continuously. Here you can check for events, read sensors, etc.
+ *
+ * Tips:
+ * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection.
+ * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker.
+ *
+ * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
+ * Instead, use a timer check as shown here.
+ */
void loop()
{
- currentTime = millis(); // get the current elapsed time
+ if (!enabled || strip.isUpdating()) return;
+ unsigned long currentTime = millis(); // get the current elapsed time
// Initialize effectCurrentIndex and effectPaletteIndex to
// current state. We do it here as (at least) effectCurrent
// is not yet initialized when setup is called.
if (!currentEffectAndPaletteInitialized) {
- findCurrentEffectAndPalette();}
+ findCurrentEffectAndPalette();
+ }
- if(modes_alpha_indexes[effectCurrentIndex] != effectCurrent
- || palettes_alpha_indexes[effectPaletteIndex] != effectPalette){
+ if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) {
currentEffectAndPaletteInitialized = false;
- }
+ }
if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
{
- button_state = digitalRead(pinC);
- if (prev_button_state != button_state)
- {
- if (button_state == HIGH && (millis()-buttonHoldTIme < 3000))
- {
- prev_button_state = button_state;
-
- char newState = select_state + 1;
- if (newState > LAST_UI_STATE) newState = 0;
-
- bool changedState = true;
- if (display != nullptr) {
- switch(newState) {
- case 0:
- changedState = changeState(" Brightness", 1, 0, 1);
- break;
- case 1:
- changedState = changeState(" Speed", 1, 4, 2);
- break;
- case 2:
- changedState = changeState(" Intensity", 1 ,8, 3);
- break;
- case 3:
- changedState = changeState(" Color Palette", 2, 0, 4);
- break;
- case 4:
- changedState = changeState(" Effect", 3, 0, 5);
- break;
- case 5:
- changedState = changeState(" Main Color", 255, 255, 7);
- break;
- case 6:
- changedState = changeState(" Saturation", 255, 255, 8);
- break;
- }
- }
- if (changedState) {
- select_state = newState;
+ loopTime = currentTime; // Updates loopTime
+
+ bool buttonPressed = !digitalRead(pinC); //0=pressed, 1=released
+ if (buttonPressed) {
+ if (!buttonPressedBefore) buttonPressedTime = currentTime;
+ buttonPressedBefore = true;
+ if (currentTime-buttonPressedTime > 3000) {
+ if (!buttonLongPressed) displayNetworkInfo(); //long press for network info
+ buttonLongPressed = true;
+ }
+ } else if (!buttonPressed && buttonPressedBefore) {
+ bool doublePress = buttonWaitTime;
+ buttonWaitTime = 0;
+ if (!buttonLongPressed) {
+ if (doublePress) {
+ toggleOnOff();
+ lampUdated();
+ } else {
+ buttonWaitTime = currentTime;
}
}
- else
- {
- prev_button_state = button_state;
- networkShown = false;
- if(!prev_button_state)buttonHoldTIme = millis();
+ buttonLongPressed = false;
+ buttonPressedBefore = false;
+ }
+ if (buttonWaitTime && currentTime-buttonWaitTime>350 && !buttonPressedBefore) { //same speed as in button.cpp
+ buttonWaitTime = 0;
+ char newState = select_state + 1;
+ bool changedState = true;
+ if (newState > LAST_UI_STATE || (newState == 8 && presetHigh==0 && presetLow == 0)) newState = 0;
+ if (display != nullptr) {
+ switch (newState) {
+ case 0: changedState = changeState(PSTR("Brightness"), 1, 0, 1); break; //1 = sun
+ case 1: changedState = changeState(PSTR("Speed"), 1, 4, 2); break; //2 = skip forward
+ case 2: changedState = changeState(PSTR("Intensity"), 1, 8, 3); break; //3 = fire
+ case 3: changedState = changeState(PSTR("Color Palette"), 2, 0, 4); break; //4 = custom palette
+ case 4: changedState = changeState(PSTR("Effect"), 3, 0, 5); break; //5 = puzzle piece
+ case 5: changedState = changeState(PSTR("Main Color"), 255, 255, 7); break; //7 = brush
+ case 6: changedState = changeState(PSTR("Saturation"), 255, 255, 8); break; //8 = contrast
+ case 7: changedState = changeState(PSTR("CCT"), 255, 255, 10); break; //10 = star
+ case 8: changedState = changeState(PSTR("Preset"), 255, 255, 11); break; //11 = heart
+ }
}
+ if (changedState) select_state = newState;
}
-
- if (!prev_button_state && (millis()-buttonHoldTIme > 3000) && !networkShown) displayNetworkInfo(); //long press for network info
Enc_A = digitalRead(pinA); // Read encoder pins
Enc_B = digitalRead(pinB);
@@ -233,65 +397,39 @@ class RotaryEncoderUIUsermod : public Usermod {
if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse
{ // B is high so clockwise
switch(select_state) {
- case 0:
- changeBrightness(true);
- break;
- case 1:
- changeEffectSpeed(true);
- break;
- case 2:
- changeEffectIntensity(true);
- break;
- case 3:
- changePalette(true);
- break;
- case 4:
- changeEffect(true);
- break;
- case 5:
- changeHue(true);
- break;
- case 6:
- changeSat(true);
- break;
+ case 0: changeBrightness(true); break;
+ case 1: changeEffectSpeed(true); break;
+ case 2: changeEffectIntensity(true); break;
+ case 3: changePalette(true); break;
+ case 4: changeEffect(true); break;
+ case 5: changeHue(true); break;
+ case 6: changeSat(true); break;
+ case 7: changeCCT(true); break;
+ case 8: changePreset(true); break;
}
}
else if (Enc_B == HIGH)
{ // B is low so counter-clockwise
switch(select_state) {
- case 0:
- changeBrightness(false);
- break;
- case 1:
- changeEffectSpeed(false);
- break;
- case 2:
- changeEffectIntensity(false);
- break;
- case 3:
- changePalette(false);
- break;
- case 4:
- changeEffect(false);
- break;
- case 5:
- changeHue(false);
- break;
- case 6:
- changeSat(false);
- break;
+ case 0: changeBrightness(false); break;
+ case 1: changeEffectSpeed(false); break;
+ case 2: changeEffectIntensity(false); break;
+ case 3: changePalette(false); break;
+ case 4: changeEffect(false); break;
+ case 5: changeHue(false); break;
+ case 6: changeSat(false); break;
+ case 7: changeCCT(false); break;
+ case 8: changePreset(false); break;
}
}
}
Enc_A_prev = Enc_A; // Store value of A for next time
- loopTime = currentTime; // Updates loopTime
}
}
- void displayNetworkInfo(){
+ void displayNetworkInfo() {
#ifdef USERMOD_FOUR_LINE_DISPLAY
- display->networkOverlay(" NETWORK INFO", 15000);
- networkShown = true;
+ display->networkOverlay(PSTR("NETWORK INFO"), 10000);
#endif
}
@@ -313,180 +451,292 @@ class RotaryEncoderUIUsermod : public Usermod {
}
boolean changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) {
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- if (display != nullptr) {
- if (display->wakeDisplay()) {
- // Throw away wake up input
- return false;
- }
- display->overlay(stateName, 750, glyph);
- display->setMarkLine(markedLine, markedCol);
- }
- #endif
- return true;
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ if (display != nullptr) {
+ if (display->wakeDisplay()) {
+ // Throw away wake up input
+ display->redraw(true);
+ return false;
+ }
+ display->overlay(stateName, 750, glyph);
+ display->setMarkLine(markedLine, markedCol);
+ }
+ #endif
+ return true;
}
void lampUdated() {
- //bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette);
//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
- colorUpdated(CALL_MODE_DIRECT_CHANGE);
- updateInterfaces(CALL_MODE_DIRECT_CHANGE);
+ //setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required)
+ stateUpdated(CALL_MODE_BUTTON);
+ updateInterfaces(CALL_MODE_BUTTON);
}
void changeBrightness(bool increase) {
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- if (display && display->wakeDisplay()) {
- // Throw away wake up input
- return;
- }
- #endif
- if (increase) bri = (bri + fadeAmount <= 255) ? (bri + fadeAmount) : 255;
- else bri = (bri - fadeAmount >= 0) ? (bri - fadeAmount) : 0;
- lampUdated();
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- display->updateBrightness();
- #endif
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ if (display && display->wakeDisplay()) {
+ display->redraw(true);
+ // Throw away wake up input
+ return;
+ }
+ display->updateRedrawTime();
+ #endif
+ bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0);
+ lampUdated();
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ display->updateBrightness();
+ #endif
}
void changeEffect(bool increase) {
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- if (display && display->wakeDisplay()) {
- // Throw away wake up input
- return;
- }
- #endif
- if (increase) effectCurrentIndex = (effectCurrentIndex + 1 >= strip.getModeCount()) ? 0 : (effectCurrentIndex + 1);
- else effectCurrentIndex = (effectCurrentIndex - 1 < 0) ? (strip.getModeCount() - 1) : (effectCurrentIndex - 1);
- effectCurrent = modes_alpha_indexes[effectCurrentIndex];
- lampUdated();
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- display->showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3);
- #endif
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ if (display && display->wakeDisplay()) {
+ display->redraw(true);
+ // Throw away wake up input
+ return;
+ }
+ display->updateRedrawTime();
+ #endif
+ effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0);
+ effectCurrent = modes_alpha_indexes[effectCurrentIndex];
+ stateChanged = true;
+ if (applyToAll) {
+ for (byte i=0; ishowCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3);
+ #endif
}
void changeEffectSpeed(bool increase) {
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- if (display && display->wakeDisplay()) {
- // Throw away wake up input
- return;
- }
- #endif
- if (increase) effectSpeed = (effectSpeed + fadeAmount <= 255) ? (effectSpeed + fadeAmount) : 255;
- else effectSpeed = (effectSpeed - fadeAmount >= 0) ? (effectSpeed - fadeAmount) : 0;
- lampUdated();
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- display->updateSpeed();
- #endif
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ if (display && display->wakeDisplay()) {
+ display->redraw(true);
+ // Throw away wake up input
+ return;
+ }
+ display->updateRedrawTime();
+ #endif
+ effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0);
+ stateChanged = true;
+ if (applyToAll) {
+ for (byte i=0; iupdateSpeed();
+ #endif
}
void changeEffectIntensity(bool increase) {
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- if (display && display->wakeDisplay()) {
- // Throw away wake up input
- return;
- }
- #endif
- if (increase) effectIntensity = (effectIntensity + fadeAmount <= 255) ? (effectIntensity + fadeAmount) : 255;
- else effectIntensity = (effectIntensity - fadeAmount >= 0) ? (effectIntensity - fadeAmount) : 0;
- lampUdated();
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- display->updateIntensity();
- #endif
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ if (display && display->wakeDisplay()) {
+ display->redraw(true);
+ // Throw away wake up input
+ return;
+ }
+ display->updateRedrawTime();
+ #endif
+ effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0);
+ stateChanged = true;
+ if (applyToAll) {
+ for (byte i=0; iupdateIntensity();
+ #endif
}
void changePalette(bool increase) {
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- if (display && display->wakeDisplay()) {
- // Throw away wake up input
- return;
- }
- #endif
- if (increase) effectPaletteIndex = (effectPaletteIndex + 1 >= strip.getPaletteCount()) ? 0 : (effectPaletteIndex + 1);
- else effectPaletteIndex = (effectPaletteIndex - 1 < 0) ? (strip.getPaletteCount() - 1) : (effectPaletteIndex - 1);
- effectPalette = palettes_alpha_indexes[effectPaletteIndex];
- lampUdated();
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- display->showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2);
- #endif
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ if (display && display->wakeDisplay()) {
+ display->redraw(true);
+ // Throw away wake up input
+ return;
+ }
+ display->updateRedrawTime();
+ #endif
+ effectPaletteIndex = max(min((increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()-1), 0);
+ effectPalette = palettes_alpha_indexes[effectPaletteIndex];
+ stateChanged = true;
+ if (applyToAll) {
+ for (byte i=0; ishowCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2);
+ #endif
}
void changeHue(bool increase){
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- if (display && display->wakeDisplay()) {
- // Throw away wake up input
- return;
- }
- #endif
-
- if(increase) currentHue1 += 321;
- else currentHue1 -= 321;
- colorHStoRGB(currentHue1, currentSat1, col);
- lampUdated();
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- display->updateRedrawTime();
- #endif
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ if (display && display->wakeDisplay()) {
+ display->redraw(true);
+ // Throw away wake up input
+ return;
+ }
+ display->updateRedrawTime();
+ #endif
+ currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0);
+ colorHStoRGB(currentHue1*256, currentSat1, col);
+ stateChanged = true;
+ if (applyToAll) {
+ for (byte i=0; iwakeDisplay()) {
- // Throw away wake up input
- return;
- }
- #endif
-
- if(increase) currentSat1 = (currentSat1 + 5 <= 255 ? (currentSat1 + 5) : 255);
- else currentSat1 = (currentSat1 - 5 >= 0 ? (currentSat1 - 5) : 0);
- colorHStoRGB(currentHue1, currentSat1, col);
- lampUdated();
- #ifdef USERMOD_FOUR_LINE_DISPLAY
- display->updateRedrawTime();
- #endif
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ if (display && display->wakeDisplay()) {
+ display->redraw(true);
+ // Throw away wake up input
+ return;
+ }
+ display->updateRedrawTime();
+ #endif
+ currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0);
+ colorHStoRGB(currentHue1*256, currentSat1, col);
+ if (applyToAll) {
+ for (byte i=0; iwakeDisplay()) {
+ display->redraw(true);
+ // Throw away wake up input
+ return;
+ }
+ display->updateRedrawTime();
+ #endif
+ if (presetHigh && presetLow && presetHigh > presetLow) {
+ String apireq = F("win&PL=~");
+ if (!increase) apireq += '-';
+ apireq += F("&P1=");
+ apireq += presetLow;
+ apireq += F("&P2=");
+ apireq += presetHigh;
+ handleSet(nullptr, apireq, false);
+ lampUdated();
+ }
+ }
+
+ void changeCCT(bool increase){
+ #ifdef USERMOD_FOUR_LINE_DISPLAY
+ if (display && display->wakeDisplay()) {
+ display->redraw(true);
+ // Throw away wake up input
+ return;
+ }
+ display->updateRedrawTime();
+ #endif
+ currentCCT = max(min((increase ? currentCCT+fadeAmount : currentCCT-fadeAmount), 255), 0);
+// if (applyToAll) {
+ for (byte i=0; i 0; i--) {
- setPixelColor( i, getPixelColor( i - 1));
- }
-
- if(SEGENV.step == 0) {
- SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0);
- setPixelColor(0, color_wheel(SEGENV.aux0));
- }
-
- SEGENV.step++;
- if (SEGENV.step > (uint8_t)((255-SEGMENT.intensity) >> 4))
- {
- SEGENV.step = 0;
+ if (SEGENV.call == 0) SEGENV.aux0 = random16(); // random seed for PRNG on start
+
+ uint8_t zoneSize = ((255-SEGMENT.intensity) >> 4) +1;
+ uint16_t PRNG16 = SEGENV.aux0;
+
+ uint8_t z = it % zoneSize;
+ bool nzone = (!z && it != SEGENV.aux1);
+ for (uint16_t i=SEGLEN-1; i > 0; i--) {
+ if (nzone || z >= zoneSize) {
+ uint8_t lastrand = PRNG16 >> 8;
+ int16_t diff = 0;
+ while (abs(diff) < 42) { // make sure the difference between adjacent colors is big enough
+ PRNG16 = (uint16_t)(PRNG16 * 2053) + 13849; // next zone, next 'random' number
+ diff = (PRNG16 >> 8) - lastrand;
+ }
+ if (nzone) {
+ SEGENV.aux0 = PRNG16; // save next starting seed
+ nzone = false;
+ }
+ z = 0;
+ }
+ setPixelColor(i, color_wheel(PRNG16 >> 8));
+ z++;
}
SEGENV.aux1 = it;
@@ -1151,10 +1162,10 @@ uint16_t WS2812FX::mode_fire_flicker(void) {
uint32_t it = now / cycleTime;
if (SEGENV.step == it) return FRAMETIME;
- byte w = (SEGCOLOR(0) >> 24) & 0xFF;
- byte r = (SEGCOLOR(0) >> 16) & 0xFF;
- byte g = (SEGCOLOR(0) >> 8) & 0xFF;
- byte b = (SEGCOLOR(0) & 0xFF);
+ byte w = (SEGCOLOR(0) >> 24);
+ byte r = (SEGCOLOR(0) >> 16);
+ byte g = (SEGCOLOR(0) >> 8);
+ byte b = (SEGCOLOR(0) );
byte lum = (SEGMENT.palette == 0) ? MAX(w, MAX(r, MAX(g, b))) : 255;
lum /= (((256-SEGMENT.intensity)/16)+1);
for(uint16_t i = 0; i < SEGLEN; i++) {
@@ -1217,12 +1228,13 @@ uint16_t WS2812FX::mode_loading(void) {
//American Police Light with all LEDs Red and Blue
-uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, uint16_t width)
+uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2)
{
uint16_t delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster
uint32_t it = now / map(SEGMENT.speed, 0, 255, delay<<4, delay);
uint16_t offset = it % SEGLEN;
+ uint16_t width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip
if (!width) width = 1;
for (uint16_t i = 0; i < width; i++) {
uint16_t indexR = (offset + i) % SEGLEN;
@@ -1234,26 +1246,11 @@ uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, uint16_t width)
}
-//American Police Light with all LEDs Red and Blue
-uint16_t WS2812FX::mode_police_all()
-{
- return police_base(RED, BLUE, (SEGLEN>>1));
-}
-
-
//Police Lights Red and Blue
uint16_t WS2812FX::mode_police()
{
fill(SEGCOLOR(1));
- return police_base(RED, BLUE, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip
-}
-
-
-//Police All with custom colors
-uint16_t WS2812FX::mode_two_areas()
-{
- fill(SEGCOLOR(2));
- return police_base(SEGCOLOR(0), SEGCOLOR(1), ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip
+ return police_base(RED, BLUE);
}
@@ -1263,7 +1260,142 @@ uint16_t WS2812FX::mode_two_dots()
fill(SEGCOLOR(2));
uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1);
- return police_base(SEGCOLOR(0), color2, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip
+ return police_base(SEGCOLOR(0), color2);
+}
+
+
+/*
+ * Fairy, inspired by https://www.youtube.com/watch?v=zeOw5MZWq24
+ */
+//4 bytes
+typedef struct Flasher {
+ uint16_t stateStart;
+ uint8_t stateDur;
+ bool stateOn;
+} flasher;
+
+#define FLASHERS_PER_ZONE 6
+#define MAX_SHIMMER 92
+
+uint16_t WS2812FX::mode_fairy() {
+ //set every pixel to a 'random' color from palette (using seed so it doesn't change between frames)
+ uint16_t PRNG16 = 5100 + _segment_index;
+ for (uint16_t i = 0; i < SEGLEN; i++) {
+ PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
+ setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0));
+ }
+
+ //amount of flasher pixels depending on intensity (0: none, 255: every LED)
+ if (SEGMENT.intensity == 0) return FRAMETIME;
+ uint8_t flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10
+ uint16_t numFlashers = (SEGLEN / flasherDistance) +1;
+
+ uint16_t dataSize = sizeof(flasher) * numFlashers;
+ if (!SEGENV.allocateData(dataSize)) return FRAMETIME; //allocation failed
+ Flasher* flashers = reinterpret_cast(SEGENV.data);
+ uint16_t now16 = now & 0xFFFF;
+
+ //Up to 11 flashers in one brightness zone, afterwards a new zone for every 6 flashers
+ uint16_t zones = numFlashers/FLASHERS_PER_ZONE;
+ if (!zones) zones = 1;
+ uint8_t flashersInZone = numFlashers/zones;
+ uint8_t flasherBri[FLASHERS_PER_ZONE*2 -1];
+
+ for (uint16_t z = 0; z < zones; z++) {
+ uint16_t flasherBriSum = 0;
+ uint16_t firstFlasher = z*flashersInZone;
+ if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1));
+
+ for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) {
+ uint16_t stateTime = now16 - flashers[f].stateStart;
+ //random on/off time reached, switch state
+ if (stateTime > flashers[f].stateDur * 10) {
+ flashers[f].stateOn = !flashers[f].stateOn;
+ if (flashers[f].stateOn) {
+ flashers[f].stateDur = 12 + random8(12 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms
+ } else {
+ flashers[f].stateDur = 20 + random8(6 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms
+ }
+ //flashers[f].stateDur = 51 + random8(2 + ((255 - SEGMENT.speed) >> 1));
+ flashers[f].stateStart = now16;
+ if (stateTime < 255) {
+ flashers[f].stateStart -= 255 -stateTime; //start early to get correct bri
+ flashers[f].stateDur += 26 - stateTime/10;
+ stateTime = 255 - stateTime;
+ } else {
+ stateTime = 0;
+ }
+ }
+ if (stateTime > 255) stateTime = 255; //for flasher brightness calculation, fades in first 255 ms of state
+ //flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? 255-gamma8((510 - stateTime) >> 1) : gamma8((510 - stateTime) >> 1);
+ flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? stateTime : 255 - (stateTime >> 0);
+ flasherBriSum += flasherBri[f - firstFlasher];
+ }
+ //dim factor, to create "shimmer" as other pixels get less voltage if a lot of flashers are on
+ uint8_t avgFlasherBri = flasherBriSum / flashersInZone;
+ uint8_t globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers
+
+ for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) {
+ uint8_t bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255;
+ PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
+ uint16_t flasherPos = f*flasherDistance;
+ setPixelColor(flasherPos, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), bri));
+ for (uint16_t i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) {
+ PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
+ setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0, globalPeakBri));
+ }
+ }
+ }
+ return FRAMETIME;
+}
+
+
+/*
+ * Fairytwinkle. Like Colortwinkle, but starting from all lit and not relying on getPixelColor
+ * Warning: Uses 4 bytes of segment data per pixel
+ */
+uint16_t WS2812FX::mode_fairytwinkle() {
+ uint16_t dataSize = sizeof(flasher) * SEGLEN;
+ if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
+ Flasher* flashers = reinterpret_cast(SEGENV.data);
+ uint16_t now16 = now & 0xFFFF;
+ uint16_t PRNG16 = 5100 + _segment_index;
+
+ uint16_t riseFallTime = 400 + (255-SEGMENT.speed)*3;
+ uint16_t maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1);
+
+ for (uint16_t f = 0; f < SEGLEN; f++) {
+ uint16_t stateTime = now16 - flashers[f].stateStart;
+ //random on/off time reached, switch state
+ if (stateTime > flashers[f].stateDur * 100) {
+ flashers[f].stateOn = !flashers[f].stateOn;
+ bool init = !flashers[f].stateDur;
+ if (flashers[f].stateOn) {
+ flashers[f].stateDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +1;
+ } else {
+ flashers[f].stateDur = riseFallTime/100 + random8(3 + ((255 - SEGMENT.speed) >> 6)) +1;
+ }
+ flashers[f].stateStart = now16;
+ stateTime = 0;
+ if (init) {
+ flashers[f].stateStart -= riseFallTime; //start lit
+ flashers[f].stateDur = riseFallTime/100 + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +5; //fire up a little quicker
+ stateTime = riseFallTime;
+ }
+ }
+ if (flashers[f].stateOn && flashers[f].stateDur > maxDur) flashers[f].stateDur = maxDur; //react more quickly on intensity change
+ if (stateTime > riseFallTime) stateTime = riseFallTime; //for flasher brightness calculation, fades in first 255 ms of state
+ uint8_t fadeprog = 255 - ((stateTime * 255) / riseFallTime);
+ uint8_t flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog);
+ uint16_t lastR = PRNG16;
+ uint16_t diff = 0;
+ while (diff < 0x4000) { //make sure colors of two adjacent LEDs differ enough
+ PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
+ diff = (PRNG16 > lastR) ? PRNG16 - lastR : lastR - PRNG16;
+ }
+ setPixelColor(f, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), flasherBri));
+ }
+ return FRAMETIME;
}
@@ -1469,26 +1601,36 @@ uint16_t WS2812FX::mode_dual_larson_scanner(void){
/*
- * Running random pixels
+ * Running random pixels ("Stream 2")
* Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/RandomChase.h
*/
uint16_t WS2812FX::mode_random_chase(void)
{
+ if (SEGENV.call == 0) {
+ SEGENV.step = RGBW32(random8(), random8(), random8(), 0);
+ SEGENV.aux0 = random16();
+ }
+ uint16_t prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function
uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed));
uint32_t it = now / cycleTime;
- if (SEGENV.step == it) return FRAMETIME;
+ uint32_t color = SEGENV.step;
+ random16_set_seed(SEGENV.aux0);
for(uint16_t i = SEGLEN -1; i > 0; i--) {
- setPixelColor(i, getPixelColor(i-1));
+ uint8_t r = random8(6) != 0 ? (color >> 16 & 0xFF) : random8();
+ uint8_t g = random8(6) != 0 ? (color >> 8 & 0xFF) : random8();
+ uint8_t b = random8(6) != 0 ? (color & 0xFF) : random8();
+ color = RGBW32(r, g, b, 0);
+ setPixelColor(i, r, g, b);
+ if (i == SEGLEN -1 && SEGENV.aux1 != (it & 0xFFFF)) { //new first color in next frame
+ SEGENV.step = color;
+ SEGENV.aux0 = random16_get_seed();
+ }
}
- uint32_t color = getPixelColor(0);
- if (SEGLEN > 1) color = getPixelColor( 1);
- uint8_t r = random8(6) != 0 ? (color >> 16 & 0xFF) : random8();
- uint8_t g = random8(6) != 0 ? (color >> 8 & 0xFF) : random8();
- uint8_t b = random8(6) != 0 ? (color & 0xFF) : random8();
- setPixelColor(0, r, g, b);
- SEGENV.step = it;
+ SEGENV.aux1 = it & 0xFFFF;
+
+ random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG
return FRAMETIME;
}
@@ -1875,7 +2017,7 @@ uint16_t WS2812FX::mode_noise16_2()
// fastled_col = ColorFromPalette(currentPalette, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED.
// setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue);
- setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(index, false, PALETTE_SOLID_WRAP, 0), noise)); // This supports RGBW.
+ setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(index, false, PALETTE_SOLID_WRAP, 0), noise)); // This supports RGBW.
}
return FRAMETIME;
@@ -1903,7 +2045,7 @@ uint16_t WS2812FX::mode_noise16_3()
// fastled_col = ColorFromPalette(currentPalette, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED.
// setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue);
- setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(index, false, PALETTE_SOLID_WRAP, 0), noise)); // This supports RGBW.
+ setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(index, false, PALETTE_SOLID_WRAP, 0), noise)); // This supports RGBW.
}
return FRAMETIME;
@@ -1974,7 +2116,7 @@ uint16_t WS2812FX::mode_colortwinkle()
}
}
}
- return FRAMETIME;
+ return FRAMETIME_FIXED;
}
@@ -1992,7 +2134,7 @@ uint16_t WS2812FX::mode_lake() {
uint8_t lum = (index > wave3) ? index - wave3 : 0;
// fastled_col = ColorFromPalette(currentPalette, map(index,0,255,0,240), lum, LINEARBLEND);
// setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue);
- setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(map(index,0,255,0,240), false, PALETTE_SOLID_WRAP, 0), lum)); // This supports RGBW.
+ setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(map(index,0,255,0,240), false, PALETTE_SOLID_WRAP, 0), lum)); // This supports RGBW.
}
return FRAMETIME;
}
@@ -2122,7 +2264,7 @@ typedef struct Ripple {
#endif
uint16_t WS2812FX::ripple_base(bool rainbow)
{
- uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 18 segment ESP8266
+ uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266
uint16_t dataSize = sizeof(ripple) * maxRipples;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -2212,7 +2354,7 @@ uint16_t WS2812FX::mode_ripple_rainbow(void) {
// incandescent bulbs change color as they get dim down.
#define COOL_LIKE_INCANDESCENT 1
-CRGB WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
+CRGB IRAM_ATTR WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
{
// Overall twinkle speed (changed)
uint16_t ticks = ms / SEGENV.aux0;
@@ -2650,16 +2792,9 @@ uint16_t WS2812FX::mode_popcorn(void) {
if (numPopcorn == 0) numPopcorn = 1;
for(uint8_t i = 0; i < numPopcorn; i++) {
- bool isActive = popcorn[i].pos >= 0.0f;
-
- if (isActive) { // if kernel is active, update its position
+ if (popcorn[i].pos >= 0.0f) { // if kernel is active, update its position
popcorn[i].pos += popcorn[i].vel;
popcorn[i].vel += gravity;
- uint32_t col = color_wheel(popcorn[i].colIndex);
- if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex);
-
- uint16_t ledIndex = popcorn[i].pos;
- if (ledIndex < SEGLEN) setPixelColor(ledIndex, col);
} else { // if kernel is inactive, randomly pop it
if (random8() < 2) { // POP!!!
popcorn[i].pos = 0.01f;
@@ -2678,6 +2813,13 @@ uint16_t WS2812FX::mode_popcorn(void) {
}
}
}
+ if (popcorn[i].pos >= 0.0f) { // draw now active popcorn (either active before or just popped)
+ uint32_t col = color_wheel(popcorn[i].colIndex);
+ if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex);
+
+ uint16_t ledIndex = popcorn[i].pos;
+ if (ledIndex < SEGLEN) setPixelColor(ledIndex, col);
+ }
}
return FRAMETIME;
@@ -2760,7 +2902,7 @@ uint16_t WS2812FX::candle(bool multi)
}
}
- return FRAMETIME;
+ return FRAMETIME_FIXED;
}
uint16_t WS2812FX::mode_candle()
@@ -2905,7 +3047,6 @@ uint16_t WS2812FX::mode_starburst(void) {
return FRAMETIME;
}
#undef STARBURST_MAX_FRAG
-#undef STARBURST_MAX_STARS
/*
* Exploding fireworks effect
@@ -3179,7 +3320,7 @@ uint16_t WS2812FX::mode_plasma(void) {
uint8_t thisBright = qsub8(colorIndex, beatsin8(7,0, (128 - (SEGMENT.intensity>>1))));
// CRGB color = ColorFromPalette(currentPalette, colorIndex, thisBright, LINEARBLEND);
// setPixelColor(i, color.red, color.green, color.blue);
- setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0), thisBright)); // This supports RGBW.
+ setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0), thisBright)); // This supports RGBW.
}
return FRAMETIME;
@@ -3452,7 +3593,7 @@ uint16_t WS2812FX::phased_base(uint8_t moder) { // We're making
//float* phasePtr = reinterpret_cast(SEGENV.step); // Phase change value gets calculated.
static float phase = 0;//phasePtr[0];
uint8_t cutOff = (255-SEGMENT.intensity); // You can change the number of pixels. AKA INTENSITY (was 192).
- uint8_t modVal = 5;//SEGMENT.fft1/8+1; // You can change the modulus. AKA FFT1 (was 5).
+ uint8_t modVal = 5;//SEGMENT.custom1/8+1; // You can change the modulus. AKA Custom1 (was 5).
uint8_t index = now/64; // Set color rotation speed
phase += SEGMENT.speed/32.0; // You can change the speed of the wave. AKA SPEED (was .4)
@@ -3491,7 +3632,7 @@ uint16_t WS2812FX::mode_twinkleup(void) { // A very short twinkl
random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through.
for (int i = 0; iSpeed, SEGMENT.intensity -> Frequency (SEGMENT.fft1 -> Color change, SEGMENT.fft2 -> PWM cutoff)
+// SEGMENT.speed ->Speed, SEGMENT.intensity -> Frequency (SEGMENT.custom1 -> Color change, SEGMENT.custom2 -> PWM cutoff)
//
uint16_t WS2812FX::mode_sinewave(void) { // Adjustable sinewave. By Andrew Tuline
//#define qsuba(x, b) ((x>b)?x-b:0) // Analog Unsigned subtraction macro. if result <0, then => 0
- uint16_t colorIndex = now /32;//(256 - SEGMENT.fft1); // Amount of colour change.
+ uint16_t colorIndex = now /32;//(256 - SEGMENT.custom1); // Amount of colour change.
SEGENV.step += SEGMENT.speed/16; // Speed of animation.
- uint16_t freq = SEGMENT.intensity/4;//SEGMENT.fft2/8; // Frequency of the signal.
+ uint16_t freq = SEGMENT.intensity/4;//SEGMENT.custom2/8; // Frequency of the signal.
for (int i=0; i UINT8_MAX ? UINT8_MAX : SEGLEN;
+ uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast(SEGENV.data);
uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128);
- uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8;
+ uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8;
- for (int i = 0; i < SEGLEN; i++) {
+ for (int i = 0; i < pixelLen; i++) {
pixels[i] = color_blend(pixels[i], color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_SOLID_WRAP, 255), blendSpeed);
- setPixelColor(i, pixels[i]);
shift += 3;
}
+ uint16_t offset = 0;
+ for (int i = 0; i < SEGLEN; i++) {
+ setPixelColor(i, pixels[offset++]);
+ if (offset > pixelLen) offset = 0;
+ }
+
return FRAMETIME;
}
@@ -4051,7 +4198,7 @@ uint16_t WS2812FX::mode_aurora(void) {
SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT);
SEGENV.aux0 = SEGMENT.intensity;
- if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 18 segment ESP8266
+ if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 16 segment ESP8266
return mode_static(); //allocation failed
}
@@ -4116,10 +4263,6 @@ uint16_t WS2812FX::mode_aurora(void) {
//
-// FastLED array, so we can refer to leds[i] instead of getPixel() and setPixel()
-CRGB leds[MAX_LEDS+1]; // See const.h for a value of 1500. The plus 1 is just in case we go over with XY().
-// uint32_t dataStore[4096]; // We are declaring a storage area or 64 x 64 (4096) words.
-
// Sound reactive external variables.
extern int sample;
@@ -4170,7 +4313,7 @@ void WS2812FX::setPixels(CRGB* leds) { // ewowi20210703: use realPixelIndex (rot
uint16_t WS2812FX::mode_perlinmove(void) {
- fade_out(255-SEGMENT.fft1);
+ fade_out(255-SEGMENT.custom1);
for (int i=0; iycen = 0.;
julias->xymag = 1.0;
- SEGMENT.fft1 = 128; // Make sure the location widgets are centered to start. Too bad
- SEGMENT.fft2 = 128; // it doesn't show up on the UI.
- SEGMENT.fft3 = 128;
+ SEGMENT.custom1 = 128; // Make sure the location widgets are centered to start. Too bad
+ SEGMENT.custom2 = 128; // it doesn't show up on the UI.
+ SEGMENT.custom3 = 128;
SEGMENT.intensity = 24;
}
- julias->xcen = julias->xcen + (float)(SEGMENT.fft1 - 128)/100000.;
- julias->ycen = julias->ycen + (float)(SEGMENT.fft2 - 128)/100000.;
- julias->xymag = julias->xymag + (float)(SEGMENT.fft3-128)/100000.;
+ julias->xcen = julias->xcen + (float)(SEGMENT.custom1 - 128)/100000.;
+ julias->ycen = julias->ycen + (float)(SEGMENT.custom2 - 128)/100000.;
+ julias->xymag = julias->xymag + (float)(SEGMENT.custom3-128)/100000.;
if (julias->xymag < 0.01) julias->xymag = 0.01;
if (julias->xymag > 1.0) julias->xymag = 1.0;
@@ -4941,30 +5084,45 @@ uint16_t WS2812FX::mode_2DLissajous(void) { // By: Andrew Tuline
// 2D Matrix //
///////////////////////
-uint16_t WS2812FX::mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline.
+uint16_t WS2812FX::mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi.
if (SEGENV.call == 0) fill_solid(leds, 0);
- if (millis() - SEGENV.step >= ((256-SEGMENT.speed) >>2)) {
+ int fade = map(SEGMENT.custom1, 0, 255, 50, 250); // equals trail size
+ int speed = (256-SEGMENT.speed) >> map(MIN(SEGMENT.height, 150), 0, 150, 0, 3); // slower speeds for small displays
+
+ CRGB spawnColor;
+ CRGB trailColor;
+
+ if (SEGMENT.custom2 > 128) {
+ spawnColor = SEGCOLOR(0);
+ trailColor = SEGCOLOR(1);
+ }
+ else {
+ spawnColor = CRGB(175,255,175);
+ trailColor = CRGB(27,130,39);
+ }
+
+ if (millis() - SEGENV.step >= speed) {
SEGENV.step = millis();
-// if (SEGMENT.fft3 < 128) { // check for orientation, slider in first quarter, default orientation
+ // if (SEGMENT.custom3 < 128) { // check for orientation, slider in first quarter, default orientation
for (int16_t row=SEGMENT.height-1; row>=0; row--) {
for (int16_t col=0; col 15 as 16 is out of bounds
leds[realPixelIndex(mid)].fadeToBlackBy(map(fftResult[1*4], 0, 255, 255, 10)); // TODO - Update
//move to the left
@@ -5962,7 +6120,7 @@ uint16_t WS2812FX::mode_freqmatrix(void) { // Freqmatrix. By Andr
if(SEGENV.aux0 != secondHand) {
SEGENV.aux0 = secondHand;
- double sensitivity = mapf(SEGMENT.fft3, 1, 255, 1, 10);
+ double sensitivity = mapf(SEGMENT.custom3, 1, 255, 1, 10);
int pixVal = sampleAgc * SEGMENT.intensity / 256 * sensitivity;
if (pixVal > 255) pixVal = 255;
@@ -5979,8 +6137,8 @@ uint16_t WS2812FX::mode_freqmatrix(void) { // Freqmatrix. By Andr
if (FFT_MajorPeak < 80) {
color = CRGB::Black;
} else {
- int upperLimit = 20 * SEGMENT.fft2;
- int lowerLimit = 2 * SEGMENT.fft1;
+ int upperLimit = 20 * SEGMENT.custom2;
+ int lowerLimit = 2 * SEGMENT.custom1;
int i = lowerLimit!=upperLimit?map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255):FFT_MajorPeak;
uint16_t b = 255 * intensity;
if (b > 255) b=255;
@@ -6038,9 +6196,9 @@ uint16_t WS2812FX::mode_freqpixels(void) { // Freqpixel. By Andre
// Assign a color to the central (starting pixels) based on the predominant frequencies and the volume. The color is being determined by mapping the MajorPeak from the FFT
// and then mapping this to the HSV color circle. Currently we are sampling at 10240 Hz, so the highest frequency we can look at is 5120Hz.
//
-// SEGMENT.fft1: the lower cut off point for the FFT. (many, most time the lowest values have very little information since they are FFT conversion artifacts. Suggested value is close to but above 0
-// SEGMENT.fft2: The high cut off point. This depends on your sound profile. Most music looks good when this slider is between 50% and 100%.
-// SEGMENT.fft3: "preamp" for the audio signal for audio10.
+// SEGMENT.custom1: the lower cut off point for the FFT. (many, most time the lowest values have very little information since they are FFT conversion artifacts. Suggested value is close to but above 0
+// SEGMENT.custom2: The high cut off point. This depends on your sound profile. Most music looks good when this slider is between 50% and 100%.
+// SEGMENT.custom3: "preamp" for the audio signal for audio10.
//
// I suggest that for this effect you turn the brightness to 95%-100% but again it depends on your soundprofile you find yourself in.
// Instead of using colorpalettes, This effect works on the HSV color circle with red being the lowest frequency
@@ -6061,10 +6219,10 @@ uint16_t WS2812FX::mode_freqwave(void) { // Freqwave. By Andrea
if(SEGENV.aux0 != secondHand) {
SEGENV.aux0 = secondHand;
- //uint8_t fade = SEGMENT.fft3;
+ //uint8_t fade = SEGMENT.custom3;
//uint8_t fadeval;
- double sensitivity = mapf(SEGMENT.fft3, 1, 255, 1, 10);
+ double sensitivity = mapf(SEGMENT.custom3, 1, 255, 1, 10);
int pixVal = sampleAvg * SEGMENT.intensity / 256 * sensitivity;
if (pixVal > 255) pixVal = 255;
@@ -6081,8 +6239,8 @@ uint16_t WS2812FX::mode_freqwave(void) { // Freqwave. By Andrea
if (FFT_MajorPeak < 80) {
color = CRGB::Black;
} else {
- int upperLimit = 20 * SEGMENT.fft2;
- int lowerLimit = 2 * SEGMENT.fft1;
+ int upperLimit = 20 * SEGMENT.custom2;
+ int lowerLimit = 2 * SEGMENT.custom1;
int i = lowerLimit!=upperLimit?map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255):FFT_MajorPeak;
uint16_t b = 255 * intensity;
if (b > 255) b=255;
@@ -6217,8 +6375,8 @@ uint16_t WS2812FX::mode_waterfall(void) { // Waterfall. By: An
if (SEGENV.call == 0) fill_solid(leds, 0);
- binNum = SEGMENT.fft2; // Select a bin.
- maxVol = SEGMENT.fft3/2; // Our volume comparator.
+ binNum = SEGMENT.custom2; // Select a bin.
+ maxVol = SEGMENT.custom3/2; // Our volume comparator.
uint8_t secondHand = micros() / (256-SEGMENT.speed)/500 + 1 % 16;
@@ -6248,17 +6406,21 @@ uint16_t WS2812FX::mode_waterfall(void) { // Waterfall. By: An
// ** 2D GEQ //
/////////////////////////
-uint16_t WS2812FX::GEQ_base(bool centered) { // By Will Tatam. Refactor by Ewoud Wijma.
+uint16_t WS2812FX::GEQ_base(int NUMB_BANDS, bool centered_horizontal, bool centered_vertical, bool color_vertical) { // By Will Tatam. Refactor by Ewoud Wijma.
+
+ // centered_vertical = true;
+ // color_vertical = true;
fadeToBlackBy(leds, SEGMENT.speed);
- int NUMB_BANDS = map(SEGMENT.fft1, 0, 255, 1, 16);
- int barWidth = (SEGMENT.width / NUMB_BANDS);
+ int barWidth = SEGMENT.width / NUMB_BANDS;
+ if (centered_vertical)
+ barWidth /= 2;
int bandInc = 1;
if(barWidth == 0) {
// Matrix narrower than fft bands
barWidth = 1;
- bandInc = (NUMB_BANDS / SEGMENT.width);
+ bandInc = NUMB_BANDS / SEGMENT.width;
}
bool rippleTime;
if (millis() - SEGENV.step >= 255 - SEGMENT.intensity)
@@ -6274,9 +6436,10 @@ uint16_t WS2812FX::GEQ_base(bool centered) { // By Will Tata
int b = 0;
for (int band = 0; band < NUMB_BANDS; band += bandInc) {
int barHeight = map(fftResult[band], 0, 255, 0, SEGMENT.height);
- if ((barHeight % 2 == 1) && centered) barHeight++; //get an even barHeight if centered
- int yStartBar = centered?(SEGMENT.height - barHeight) / 2:0; //lift up the bar if centered
- int yStartPeak = centered?(SEGMENT.height - previousBarHeight[band]) / 2:0; //lift up the peaks if centered
+ if ((barHeight % 2 == 1) && centered_horizontal) barHeight++; //get an even barHeight if centered_horizontal
+ int yStartBar = centered_horizontal?(SEGMENT.height - barHeight) / 2:0; //lift up the bar if centered_horizontal
+ int yStartPeak = centered_horizontal?(SEGMENT.height - previousBarHeight[band]) / 2:0; //lift up the peaks if centered_horizontal
+
for (int w = 0; w < barWidth; w++) {
int x = (barWidth * b) + w;
for (int y=0; y=yStartBar && y= yStartBar && y < yStartBar + barHeight) {
+ if (color_vertical)
+ color = color_from_palette(map(y, 0, SEGMENT.height - 1, 0, 255), false, PALETTE_SOLID_WRAP, 0);
+ else
+ color = color_from_palette(band * 35, false, PALETTE_SOLID_WRAP, 0);
+ }
- //low and high peak (must exist && on peak position && only below if centered effect)
- if ((previousBarHeight[band] > 0) && (SEGMENT.intensity < 255) && (y==yStartPeak || y==yStartPeak + previousBarHeight[band]-1) && (centered || y!=yStartPeak))
+ //low and high peak (must exist && on peak position && only below if centered_horizontal effect)
+ if ((previousBarHeight[band] > 0) && (SEGMENT.intensity < 255) && (y==yStartPeak || y==yStartPeak + previousBarHeight[band]-1) && (centered_horizontal || y!=yStartPeak))
color = SEGCOLOR(2)==CRGB::Black?color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0):SEGCOLOR(2); //low peak
- leds[XY(x, SEGMENT.height - 1 - y)] = color;
+ if (centered_vertical) {
+ leds[XY(SEGMENT.width / 2 - 1 + x, SEGMENT.height - 1 - y)] = color;
+ leds[XY(SEGMENT.width / 2 - 1 - x, SEGMENT.height - 1 - y)] = color;
+ }
+ else {
+ leds[XY(x, SEGMENT.height - 1 - y)] = color;
+ }
}
} //barWidth
b++;
- if (rippleTime) previousBarHeight[band]-=centered?2:1; //delay/ripple effect
+ if (rippleTime) previousBarHeight[band]-=centered_horizontal?2:1; //delay/ripple effect
if (barHeight > previousBarHeight[band]) previousBarHeight[band] = barHeight; //drive the peak up
}
@@ -6305,7 +6478,7 @@ uint16_t WS2812FX::GEQ_base(bool centered) { // By Will Tata
} //GEQ_base
uint16_t WS2812FX::mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma.
- return GEQ_base(false);
+ return GEQ_base(map(SEGMENT.custom1, 0, 255, 1, 16), false, false, false);
} // mode_2DGEQ()
@@ -6314,7 +6487,7 @@ uint16_t WS2812FX::mode_2DGEQ(void) { // By Will Tatam. Code
/////////////////////////
uint16_t WS2812FX::mode_2DCenterBars(void) { // Written by Scott Marley Adapted by Spiro-C..
- return GEQ_base(true);
+ return GEQ_base(16, SEGMENT.custom1 > 128, SEGMENT.custom2 > 128, SEGMENT.custom3 > 128);
} // mode_2DCenterBars()
@@ -6324,7 +6497,7 @@ uint16_t WS2812FX::mode_2DCenterBars(void) { // Written by Scott Ma
uint16_t WS2812FX::mode_2DFunkyPlank(void) { // Written by ??? Adapted by Will Tatam.
- int NUMB_BANDS = map(SEGMENT.fft1, 0, 255, 1, 16);
+ int NUMB_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16);
int barWidth = (SEGMENT.width / NUMB_BANDS);
int bandInc = 1;
if(barWidth == 0) {
@@ -6454,5 +6627,4 @@ uint16_t WS2812FX::mode_2DAkemi(void) {
setPixels(leds);
return FRAMETIME;
-} // mode_2DAkemi
-
+} // mode_2DAkemi
\ No newline at end of file
diff --git a/wled00/FX.h b/wled00/FX.h
index b5633853fb..60e2c0bf26 100644
--- a/wled00/FX.h
+++ b/wled00/FX.h
@@ -48,7 +48,8 @@
/* Not used in all effects yet */
#define WLED_FPS 42
-#define FRAMETIME (1000/WLED_FPS)
+#define FRAMETIME_FIXED (1000/WLED_FPS)
+#define FRAMETIME _frametime
/* each segment uses 52 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
@@ -71,7 +72,8 @@
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)
#define LED_SKIP_AMOUNT 1
-#define MIN_SHOW_DELAY 15
+// NEED WORKAROUND TO ACCESS PRIVATE CLASS VARIABLE '_frametime'
+#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
#define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT _segments[_segment_index]
@@ -80,7 +82,6 @@
#define SEGLEN _virtualSegmentLength
#define SEGACT SEGMENT.stop
#define SPEED_FORMULA_L 5U + (50U*(255U - SEGMENT.speed))/SEGLEN
-#define RESET_RUNTIME memset(_segment_runtimes, 0, sizeof(_segment_runtimes))
// some common colors
#define RED (uint32_t)0xFF0000
@@ -115,7 +116,7 @@
#define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE )
#define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED )
-#define MODE_COUNT 187// 255 // WLEDSR: no need to change this value when effects are added / deleted. First 128 for AC, second for SR
+#define MODE_COUNT 188// WLEDSR: First 128 for AC (incl reserved), rest for SR
#define FX_MODE_STATIC 0
#define FX_MODE_BLINK 1
@@ -161,16 +162,16 @@
#define FX_MODE_COMET 41
#define FX_MODE_FIREWORKS 42
#define FX_MODE_RAIN 43
-#define FX_MODE_TETRIX 44
+#define FX_MODE_TETRIX 44 //was Merry Christmas prior to 0.12.0 (use "Chase 2" with Red/Green)
#define FX_MODE_FIRE_FLICKER 45
#define FX_MODE_GRADIENT 46
#define FX_MODE_LOADING 47
-#define FX_MODE_POLICE 48
-#define FX_MODE_POLICE_ALL 49
+#define FX_MODE_POLICE 48 // candidate for removal (after below three)
+#define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity)
#define FX_MODE_TWO_DOTS 50
-#define FX_MODE_TWO_AREAS 51
+#define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity)
#define FX_MODE_RUNNING_DUAL 52
-#define FX_MODE_HALLOWEEN 53
+#define FX_MODE_HALLOWEEN 53 // candidate for removal
#define FX_MODE_TRICOLOR_CHASE 54
#define FX_MODE_TRICOLOR_WIPE 55
#define FX_MODE_TRICOLOR_FADE 56
@@ -231,7 +232,7 @@
#define FX_MODE_CHUNCHUN 111
#define FX_MODE_DANCING_SHADOWS 112
#define FX_MODE_WASHING_MACHINE 113
-#define FX_MODE_CANDY_CANE 114
+#define FX_MODE_CANDY_CANE 114 // candidate for removal
#define FX_MODE_BLENDS 115
#define FX_MODE_TV_SIMULATOR 116
#define FX_MODE_DYNAMIC_SMOOTH 117
@@ -240,9 +241,9 @@
// Start of Audio Reactive fork (WLEDSR) //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#define DEFAULT_FFT1 (uint8_t)128
-#define DEFAULT_FFT2 (uint8_t)128
-#define DEFAULT_FFT3 (uint8_t)128
+#define DEFAULT_Custom1 (uint8_t)128
+#define DEFAULT_Custom2 (uint8_t)128
+#define DEFAULT_Custom3 (uint8_t)128
// bits 4-6: WLEDSR: used for rotation and reverse
#define ROTATED2D (uint8_t)0x10 //0x01, 0x02, 0x04, 0x08, 0x0F, 0x10, 0x20, 0x40, 0x80, 0xF0
@@ -312,10 +313,11 @@
#define FX_MODE_WAVESINS 184
#define FX_MODE_ROCKTAVES 185
#define FX_MODE_2DAKEMI 186
+#define FX_MODE_CUSTOMEFFECT 187 //WLEDSR Custom Effects
-
+#define floatNull -32768 //WLEDSR Custom Effects
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// End of Audio Reactive fork (WLEDSR) //
+// End of Audio Reactive fork (WLEDSR) //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class WS2812FX {
@@ -328,22 +330,28 @@ class WS2812FX {
// segment parameters
public:
- typedef struct Segment { // 29 (32 in memory?) bytes
+
+ // FastLED array, so we can refer to leds[i] instead of getPixel() and setPixel()
+ CRGB leds[MAX_LEDS+1]; // See const.h for a value of 1500. The plus 1 is just in case we go over with XY().
+
+ typedef struct Segment { // 31 (32 in memory) bytes
uint16_t start;
uint16_t stop; //segment invalid if stop == 0
- uint16_t offset; // WLEDSR: this is in latest AC
+ uint16_t offset;
uint8_t speed;
uint8_t intensity;
- uint8_t fft1; // WLEDSR
- uint8_t fft2; // WLEDSR
- uint8_t fft3; // WLEDSR
+ uint8_t custom1; // WLEDSR
+ uint8_t custom2; // WLEDSR
+ uint8_t custom3; // WLEDSR
uint8_t palette;
uint8_t mode;
- uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected
+ uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected
uint8_t grouping, spacing;
uint8_t opacity;
uint32_t colors[NUM_COLORS];
- uint16_t width; // WLEDSRewowi20210624: add width/height and startX/Y stopX/Y for 2D segments
+ uint8_t cct; //0==1900K, 255==10091K
+ uint8_t _capabilities;
+ uint16_t width; // WLEDSR ewowi20210624: add width/height and startX/Y stopX/Y for 2D segments
uint16_t height; // WLEDSR
uint16_t startX; // WLEDSR
uint16_t startY; // WLEDSR
@@ -354,19 +362,27 @@ class WS2812FX {
bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed
if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false;
if (c == colors[slot]) return false;
- ColorTransition::startTransition(opacity, colors[slot], instance->_transitionDur, segn, slot);
+ uint8_t b = (slot == 1) ? cct : opacity;
+ ColorTransition::startTransition(b, colors[slot], instance->_transitionDur, segn, slot);
colors[slot] = c; return true;
}
+ void setCCT(uint16_t k, uint8_t segn) {
+ if (segn >= MAX_NUM_SEGMENTS) return;
+ if (k > 255) { //kelvin value, convert to 0-255
+ if (k < 1900) k = 1900;
+ if (k > 10091) k = 10091;
+ k = (k - 1900) >> 5;
+ }
+ if (cct == k) return;
+ ColorTransition::startTransition(cct, colors[1], instance->_transitionDur, segn, 1);
+ cct = k;
+ }
void setOpacity(uint8_t o, uint8_t segn) {
if (segn >= MAX_NUM_SEGMENTS) return;
if (opacity == o) return;
ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
opacity = o;
}
- /*uint8_t actualOpacity() { //respects On/Off state
- if (!getOption(SEG_OPTION_ON)) return 0;
- return opacity;
- }*/
void setOption(uint8_t n, bool val, uint8_t segn = 255)
{
bool prevOn = false;
@@ -416,30 +432,10 @@ class WS2812FX {
vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED
return vLength;
}
- uint8_t differs(Segment& b) {
- uint8_t d = 0;
- if (start != b.start) d |= SEG_DIFFERS_BOUNDS;
- if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
- if (offset != b.offset) d |= SEG_DIFFERS_GSO;
- if (grouping != b.grouping) d |= SEG_DIFFERS_GSO;
- if (spacing != b.spacing) d |= SEG_DIFFERS_GSO;
- if (opacity != b.opacity) d |= SEG_DIFFERS_BRI;
- if (mode != b.mode) d |= SEG_DIFFERS_FX;
- if (speed != b.speed) d |= SEG_DIFFERS_FX;
- if (intensity != b.intensity) d |= SEG_DIFFERS_FX;
- if (fft1 != b.fft1) d |= SEG_DIFFERS_FX;
- if (fft2 != b.fft2) d |= SEG_DIFFERS_FX;
- if (fft3 != b.fft3) d |= SEG_DIFFERS_FX;
- if (palette != b.palette) d |= SEG_DIFFERS_FX;
-
- if ((options & 0b00101111) != (b.options & 0b00101111)) d |= SEG_DIFFERS_OPT;
- for (uint8_t i = 0; i < NUM_COLORS; i++)
- {
- if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL;
- }
- return d;
- }
+ uint8_t differs(Segment& b);
+ inline uint8_t getLightCapabilities() {return _capabilities;}
+ void refreshLightCapabilities();
} segment;
// segment runtime parameters
@@ -493,8 +489,9 @@ class WS2812FX {
* Flags that before the next effect is calculated,
* the internal segment state should be reset.
* Call resetIfRequired before calling the next effect function.
+ * Safe to call from interrupts and network requests.
*/
- inline void reset() { _requiresReset = true; }
+ inline void markForReset() { _requiresReset = true; }
private:
uint16_t _dataLen = 0;
bool _requiresReset = false;
@@ -539,7 +536,7 @@ class WS2812FX {
if (t.segment == s) //this is an active transition on the same segment+color
{
bool wasTurningOff = (oldBri == 0);
- t.briOld = t.currentBri(wasTurningOff);
+ t.briOld = t.currentBri(wasTurningOff, slot);
t.colorOld = t.currentColor(oldCol);
} else {
t.briOld = oldBri;
@@ -571,11 +568,15 @@ class WS2812FX {
uint32_t currentColor(uint32_t colorNew) {
return instance->color_blend(colorOld, colorNew, progress(true), true);
}
- uint8_t currentBri(bool turningOff = false) {
+ uint8_t currentBri(bool turningOff = false, uint8_t slot = 0) {
uint8_t segn = segment & 0x3F;
if (segn >= MAX_NUM_SEGMENTS) return 0;
uint8_t briNew = instance->_segments[segn].opacity;
- if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0;
+ if (slot == 0) {
+ if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0;
+ } else { //transition slot 1 brightness for CCT transition
+ briNew = instance->_segments[segn].cct;
+ }
uint32_t prog = progress() + 1;
return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16;
}
@@ -631,9 +632,9 @@ class WS2812FX {
_mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient;
_mode[FX_MODE_LOADING] = &WS2812FX::mode_loading;
_mode[FX_MODE_POLICE] = &WS2812FX::mode_police;
- _mode[FX_MODE_POLICE_ALL] = &WS2812FX::mode_police_all;
+ _mode[FX_MODE_FAIRY] = &WS2812FX::mode_fairy;
_mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots;
- _mode[FX_MODE_TWO_AREAS] = &WS2812FX::mode_two_areas;
+ _mode[FX_MODE_FAIRYTWINKLE] = &WS2812FX::mode_fairytwinkle;
_mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual;
_mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween;
_mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase;
@@ -784,6 +785,7 @@ class WS2812FX {
_mode[FX_MODE_WAVESINS] = &WS2812FX::mode_wavesins;
_mode[FX_MODE_ROCKTAVES] = &WS2812FX::mode_rocktaves;
_mode[FX_MODE_2DAKEMI] = &WS2812FX::mode_2DAkemi;
+ _mode[FX_MODE_CUSTOMEFFECT] = &WS2812FX::mode_customEffect; //WLEDSR Custom Effects
#ifdef WLEDSR_LARGE
// _mode[FX_MODE_2DPOOLNOISE] = &WS2812FX::mode_2DPoolnoise; //code not in fx.cpp
@@ -817,54 +819,64 @@ class WS2812FX {
setMode(uint8_t segid, uint8_t m),
setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
setColor(uint8_t slot, uint32_t c),
- setBrightness(uint8_t b),
+ setCCT(uint16_t k),
+ setBrightness(uint8_t b, bool direct = false),
setRange(uint16_t i, uint16_t i2, uint32_t col),
setShowCallback(show_callback cb),
setTransition(uint16_t t),
setTransitionMode(bool t),
calcGammaTable(float),
trigger(void),
- setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0),
+ setReset(uint8_t n),
+ setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0, uint16_t offset = UINT16_MAX),
+ setMainSegmentId(uint8_t n),
+ restartRuntime(),
resetSegments(),
- makeAutoSegments(),
+ makeAutoSegments(bool forceReset = false),
fixInvalidSegments(),
setPixelColor(uint16_t n, uint32_t c),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
show(void),
- setPixelSegment(uint8_t n),
+ setTargetFps(uint8_t fps),
deserializeMap(uint8_t n=0);
bool
- isRgbw = false,
- isOffRefreshRequred = false, //periodic refresh is required for the strip to remain off.
gammaCorrectBri = false,
gammaCorrectCol = true,
- applyToAllSelected = true,
- setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t f1, uint8_t f2, uint8_t f3, uint8_t p), //WLEDSR: add f1,2,3
+ // REMOVED IN 7b969bb
+ // applyToAllSelected = true,
+ // setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t c1, uint8_t c2, uint8_t c3, uint8_t p), //WLEDSR: add c1,c2,c3
checkSegmentAlignment(void),
+ hasRGBWBus(void),
+ hasCCTBus(void),
// return true if the strip is being sent pixel updates
isUpdating(void);
uint8_t
- mainSegment = 0,
- rgbwMode = RGBW_MODE_DUAL,
paletteFade = 0,
paletteBlend = 0,
milliampsPerLed = 55,
+ autoWhiteMode = RGBW_MODE_DUAL,
+ cctBlending = 0,
getBrightness(void),
- getMode(void),
- getSpeed(void),
getModeCount(void),
getPaletteCount(void),
getMaxSegments(void),
getActiveSegmentsNum(void),
- //getFirstSelectedSegment(void),
+ getFirstSelectedSegId(void),
getMainSegmentId(void),
+ getLastActiveSegmentId(void),
+ getTargetFps(void),
+ setPixelSegment(uint8_t n),
gamma8(uint8_t),
gamma8_cal(uint8_t, float),
- sin_gap(uint16_t),
get_random_wheel_index(uint8_t);
+ inline uint8_t sin_gap(uint16_t in) {
+ if (in & 0x100) return 0;
+ return sin8(in + 192); // correct phase shift of sine so that it starts and stops at 0
+ }
+
int8_t
tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec);
@@ -874,7 +886,8 @@ class WS2812FX {
triwave16(uint16_t),
getLengthTotal(void),
getLengthPhysical(void),
- getFps();
+ getFps(),
+ getMinShowDelay(); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC
uint32_t
now,
@@ -885,14 +898,12 @@ class WS2812FX {
currentColor(uint32_t colorNew, uint8_t tNr),
gamma32(uint32_t),
getLastShow(void),
- getPixelColor(uint16_t),
- getColor(void);
-
- WS2812FX::Segment&
- getSegment(uint8_t n);
+ getPixelColor(uint16_t);
- WS2812FX::Segment_runtime
- getSegmentRuntime(void);
+ WS2812FX::Segment
+ &getSegment(uint8_t n),
+ &getFirstSelectedSeg(void),
+ &getMainSegment(void);
WS2812FX::Segment*
getSegments(void);
@@ -949,9 +960,9 @@ class WS2812FX {
mode_gradient(void),
mode_loading(void),
mode_police(void),
- mode_police_all(void),
+ mode_fairy(void),
mode_two_dots(void),
- mode_two_areas(void),
+ mode_fairytwinkle(void),
mode_running_dual(void),
mode_bicolor_chase(void),
mode_tricolor_chase(void),
@@ -1118,14 +1129,14 @@ class WS2812FX {
mode_2DWaverly(void),
mode_2DDrift(void),
mode_2DColoredBursts(void),
-
- mode_2DJulia(void);
+ mode_2DJulia(void),
+ mode_customEffect(void); //WLEDSR Custom Effects
// mode_2DPoolnoise(void),
// mode_2DTwister(void);
// mode_2DCAElementary(void);
uint16_t
- GEQ_base(bool); //private???
+ GEQ_base(int, bool, bool, bool); //private???
uint16_t
_lengthRaw; //private? not in AC (anymore)
@@ -1133,6 +1144,11 @@ class WS2812FX {
bool
_skipFirstMode; //private? not in AC (anymore)
+ //WLEDSR Custom Effects
+ float arti_external_function(uint8_t function, float par1 = floatNull, float par2 = floatNull, float par3 = floatNull, float par4 = floatNull, float par5 = floatNull);
+ float arti_get_external_variable(uint8_t variable, float par1 = floatNull, float par2 = floatNull, float par3 = floatNull);
+ void arti_set_external_variable(float value, uint8_t variable, float par1 = floatNull, float par2 = floatNull, float par3 = floatNull);
+
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// End of Audio Reactive fork (WLEDSR) //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1150,9 +1166,13 @@ class WS2812FX {
uint16_t _usedSegmentData = 0;
uint16_t _transitionDur = 750;
+ uint8_t _targetFps = 42;
+ uint16_t _frametime = (1000/42);
uint16_t _cumulativeFps = 2;
bool
+ _isOffRefreshRequired = false, //periodic refresh is required for the strip to remain off.
+ _hasWhiteChannel = false,
_triggered;
mode_ptr _mode[MODE_COUNT]; // SRAM footprint: 4 bytes per element
@@ -1173,7 +1193,7 @@ class WS2812FX {
chase(uint32_t, uint32_t, uint32_t, bool),
gradient_base(bool),
ripple_base(bool),
- police_base(uint32_t, uint32_t, uint16_t),
+ police_base(uint32_t, uint32_t),
running(uint32_t, uint32_t, bool theatre=false),
tricolor_chase(uint32_t, uint32_t),
twinklefox_base(bool),
@@ -1199,12 +1219,16 @@ class WS2812FX {
uint32_t _colors_t[3];
uint8_t _bri_t;
+ bool _no_rgb = false;
+
uint8_t _segment_index = 0;
uint8_t _segment_index_palette_last = 99;
+ uint8_t _mainSegment;
+
segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 27 bytes per element
//WLEDSR: add f1,2,3
- // start, stop, offset, speed, intensity, fft1, fft2, fft3, palette, mode, options, grouping, spacing, opacity (unused), color[]
- {0, 7, 0, DEFAULT_SPEED, DEFAULT_INTENSITY, DEFAULT_FFT1, DEFAULT_FFT2, DEFAULT_FFT3, 0, DEFAULT_MODE, NO_OPTIONS, 1, 0, 255, {DEFAULT_COLOR}}
+ // start, stop, offset, speed, intensity, custom1, custom2, custom3, palette, mode, options, grouping, spacing, opacity (unused), color[], capabilities
+ {0, 7, 0, DEFAULT_SPEED, DEFAULT_INTENSITY, DEFAULT_Custom1, DEFAULT_Custom2, DEFAULT_Custom3, 0, DEFAULT_MODE, NO_OPTIONS, 1, 0, 255, {DEFAULT_COLOR}, 0}
};
segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 28 bytes per element
friend class Segment_runtime;
@@ -1215,6 +1239,10 @@ class WS2812FX {
uint16_t
realPixelIndex(uint16_t i),
transitionProgress(uint8_t tNr);
+
+ public:
+ inline bool hasWhiteChannel(void) {return _hasWhiteChannel;}
+ inline bool isOffRefreshRequired(void) {return _isOffRefreshRequired;}
};
//10 names per line
@@ -1280,7 +1308,7 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Colorful",
"Traffic Light",
"Sweep Random",
-"Running 2",
+"Chase 2",
"Aurora",
"Stream",
"Scanner",
@@ -1292,9 +1320,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Gradient",
"Loading",
"Police",
-"Police All",
+"Fairy",
"Two Dots",
-"Two Areas",
+"Fairytwinkle",
"Running Dual",
"Halloween",
"Tri Chase",
@@ -1396,7 +1424,7 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"2D Squared Swirl@,,,,Blur;,,;!",
"2D Fire2012@Speed;;",
"2D DNA@Scroll speed,Blur;;!",
-"2D Matrix@Falling speed,Spawning rate;;",
+"2D Matrix@Falling speed,Spawning rate,Trail,Custom color ☑;Spawn,Trail;",
"2D Metaballs@;;",
" ♫ Freqmap@Fade rate,Starting color;,!;!",
" ♪ Gravcenter@Rate of fall,Sensitivity;,!;!",
@@ -1404,7 +1432,7 @@ const char JSON_mode_names[] PROGMEM = R"=====([
" ♫ Gravfreq@Rate of fall,Sensivity;,!;!",
" ♫ DJ Light@Speed;;",
" ♫ 2D Funky Plank@Scroll speed,,# of bands;;",
-" ♫ 2D CenterBars@Bar speed,Ripple decay,# of bands;,,Peak Color;!",
+" ♫ 2D CenterBars@Bar speed,Ripple decay,Center ↔ ☑,Center ↕ ☑, Color ↕ ☑;,,Peak Color;!",
"2D Pulser@Speed,Blur;;!",
" ♫ Blurz@Fade rate,Blur amount;,Color mix;!",
"2D Drift@Rotation speed,Blur amount;;!",
@@ -1415,7 +1443,7 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Reserved for PoolNoise",
"Reserved for Twister",
"Reserved for Elementary",
-"2D Game Of Life@!,Palette toggle;!,!;!",
+"2D Game Of Life@!,Palette ☑;!,!;!",
"2D Tartan@X scale,Y scale;;!",
"2D Polar Lights@Speed,X scale,Palette;;",
" ♪ 2D Swirl@!,Sensitivity,Blur;,Bg Swirl;!",
@@ -1429,7 +1457,8 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"2D Black Hole@Outer X frequency,Inner X frequency,Inner Y frequency;;",
"Wavesins@Speed,Brightness variation,Starting Color,Range of Colors,Color variation;;!",
" ♫ Rocktaves@;,!;!",
-" ♫ 2D Akemi@Color speed,Dance toggle;Head palette,Arms & Legs,Eyes & Mouth;Face palette"
+" ♫ 2D Akemi@Color speed,Dance ☑;Head palette,Arms & Legs,Eyes & Mouth;Face palette",
+" ⚙️ Custom Effect@Speed,Intensity,Custom 1, Custom 2, Custom 3;!;!"
])=====";
//WLEDSR: second part (not SR specific, but in latest SR, not in AC (Pallettes added in WLEDSR from Retro Clown->END))
diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp
index 81691d77ea..077a4b1a31 100644
--- a/wled00/FX_fcn.cpp
+++ b/wled00/FX_fcn.cpp
@@ -67,8 +67,13 @@
//do not call this method from system context (network callback)
void WS2812FX::finalizeInit(void)
{
- RESET_RUNTIME;
- isRgbw = isOffRefreshRequred = false;
+ //reset segment runtimes
+ for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) {
+ _segment_runtimes[i].markForReset();
+ _segment_runtimes[i].resetIfRequired();
+ }
+
+ _hasWhiteChannel = _isOffRefreshRequired = false;
//if busses failed to load, add default (fresh install, FS issue, ...)
if (busses.getNumBusses() == 0) {
@@ -87,19 +92,15 @@ void WS2812FX::finalizeInit(void)
}
}
- deserializeMap();
-
- set2DSegment(0); // ewowi20210629: initialize 2D segment variables
-
_length = 0;
for (uint8_t i=0; igetStart() + bus->getLength() > MAX_LEDS) break;
//RGBW mode is enabled if at least one of the strips is RGBW
- isRgbw |= bus->isRgbw();
+ _hasWhiteChannel |= bus->isRgbw();
//refresh is required to remain off if at least one of the strips requires the refresh.
- isOffRefreshRequred |= bus->isOffRefreshRequired();
+ _isOffRefreshRequired |= bus->isOffRefreshRequired();
uint16_t busEnd = bus->getStart() + bus->getLength();
if (busEnd > _length) _length = busEnd;
#ifdef ESP8266
@@ -110,7 +111,9 @@ void WS2812FX::finalizeInit(void)
if (pins[0] == 3) bd->reinit();
#endif
}
- ledCount = _length;
+
+ setStripOrPanelWidthAndHeight();
+ set2DSegment(0); // ewowi20210629: initialize 2D segment variables
//segments are created in makeAutoSegments();
@@ -133,7 +136,8 @@ void WS2812FX::service() {
if (!SEGMENT.isActive()) continue;
- if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary
+ // last condition ensures all solid segments are updated at the same time
+ if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0))
{
if (SEGMENT.grouping == 0) SEGMENT.grouping = 1; //sanity check
doShow = true;
@@ -142,14 +146,19 @@ void WS2812FX::service() {
if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen
_virtualSegmentLength = SEGMENT.virtualLength();
_bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2];
+ uint8_t _cct_t = SEGMENT.cct;
if (!IS_SEGMENT_ON) _bri_t = 0;
for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) {
if ((transitions[t].segment & 0x3F) != i) continue;
uint8_t slot = transitions[t].segment >> 6;
if (slot == 0) _bri_t = transitions[t].currentBri();
+ if (slot == 1) _cct_t = transitions[t].currentBri(false, 1);
_colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]);
}
- for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]);
+ if (!cctFromRgb || correctWB) busses.setSegmentCCT(_cct_t, correctWB);
+ for (uint8_t c = 0; c < NUM_COLORS; c++) {
+ _colors_t[c] = gamma32(_colors_t[c]);
+ }
handle_palette();
//WLEDSR: swap width and height if rotated
if (IS_ROTATED2D && stripOrMatrixPanel == 1) {//matrix
@@ -164,13 +173,21 @@ void WS2812FX::service() {
delay = (this->*_mode[SEGMENT.mode])(); //effect function
else
delay = (this->*_mode[FX_MODE_BLINK])(); //WLEDSR: blink if mode has not been activated
+
+ // if segment is not RGB capable, force None auto white mode
+ // If not RGB capable, also treat palette as if default (0), as palettes set white channel to 0
+ _no_rgb = !(SEGMENT.getLightCapabilities() & 0x01);
+ if (_no_rgb) Bus::setAutoWhiteMode(RGBW_MODE_MANUAL_ONLY);
+ delay = (this->*_mode[SEGMENT.mode])(); //effect function
if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++;
+ Bus::setAutoWhiteMode(strip.autoWhiteMode);
}
SEGENV.next_time = nowUp + delay;
}
}
_virtualSegmentLength = 0;
+ busses.setSegmentCCT(-1);
if(doShow) {
yield();
show();
@@ -178,16 +195,12 @@ void WS2812FX::service() {
_triggered = false;
}
-void WS2812FX::setPixelColor(uint16_t n, uint32_t c) {
- uint8_t w = (c >> 24);
- uint8_t r = (c >> 16);
- uint8_t g = (c >> 8);
- uint8_t b = c ;
- setPixelColor(n, r, g, b, w);
+void IRAM_ATTR WS2812FX::setPixelColor(uint16_t n, uint32_t c) {
+ setPixelColor(n, R(c), G(c), B(c), W(c));
}
// used to map from segment index to logical pixel, taking into account grouping, offsets, reverse and mirroring
-uint16_t WS2812FX::realPixelIndex(uint16_t i) { // ewowi20210703: will not map to physical pixel index but to rotated and mirrored logical pixel index as matrix panels will require mapping.
+uint16_t IRAM_ATTR WS2812FX::realPixelIndex(uint16_t i) { // ewowi20210703: will not map to physical pixel index but to rotated and mirrored logical pixel index as matrix panels will require mapping.
// Mapping is done in logicalToPhysical below. Function will not be renamed to keep it consistent with Aircoookie
int16_t iGroup = i * SEGMENT.groupLength();
@@ -236,36 +249,22 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) { // ewowi20210703: will not map t
return realIndex;
}
-void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
+void IRAM_ATTR WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
{
- // auto calculate white channel value if enabled
- if (isRgbw) {
- if (rgbwMode == RGBW_MODE_AUTO_BRIGHTER || (w == 0 && (rgbwMode == RGBW_MODE_DUAL || rgbwMode == RGBW_MODE_LEGACY)))
- {
- // white value is set to lowest RGB channel
- // thank you to @Def3nder!
- w = r < g ? (r < b ? r : b) : (g < b ? g : b);
- } else if (rgbwMode == RGBW_MODE_AUTO_ACCURATE && w == 0)
- {
- w = r < g ? (r < b ? r : b) : (g < b ? g : b);
- r -= w; g -= w; b -= w;
- }
- }
+ if (SEGLEN) {//from segment
+ uint16_t realIndex = realPixelIndex(i); // ewowi20210624: from segment index to logical index
+ uint16_t len = SEGMENT.length();
- if (SEGLEN) { //from segment
- // color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
+ //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
if (_bri_t < 255) {
r = scale8(r, _bri_t);
g = scale8(g, _bri_t);
b = scale8(b, _bri_t);
w = scale8(w, _bri_t);
}
- uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
+ uint32_t col = RGBW32(r, g, b, w);
/* Set all the pixels in the group */
- uint16_t realIndex = realPixelIndex(i); // ewowi20210624: from segment index to logical index
- uint16_t len = SEGMENT.length();
-
for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j);
if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
@@ -289,8 +288,8 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
}
} else { //live data, etc.
if (i < customMappingSize) i = customMappingTable[i];
- uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
- busses.setPixelColor(logicalToPhysical(i), col); // ewowi20210624: logicalToPhysical: Maps logical led index to physical led index.
+ busses.setPixelColor(i, RGBW32(r, g, b, w));
+ busses.setPixelColor(logicalToPhysical(i), RGBW32(r, g, b, w)); // ewowi20210624: logicalToPhysical: Maps logical led index to physical led index.
}
}
@@ -343,7 +342,7 @@ void WS2812FX::estimateCurrentAndLimitBri() {
uint32_t busPowerSum = 0;
for (uint16_t i = 0; i < len; i++) { //sum up the usage of each LED
uint32_t c = bus->getPixelColor(i);
- byte r = c >> 16, g = c >> 8, b = c, w = c >> 24;
+ byte r = R(c), g = G(c), b = B(c), w = W(c);
if(useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
busPowerSum += (MAX(MAX(r,g),b)) * 3;
@@ -415,6 +414,20 @@ uint16_t WS2812FX::getFps() {
return _cumulativeFps +1;
}
+uint8_t WS2812FX::getTargetFps() {
+ return _targetFps;
+}
+
+void WS2812FX::setTargetFps(uint8_t fps) {
+ if (fps > 0 && fps <= 120) _targetFps = fps;
+ _frametime = 1000 / _targetFps;
+}
+
+// Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC
+uint16_t WS2812FX::getMinShowDelay() {
+ return MIN_SHOW_DELAY;
+}
+
/**
* Forces the next frame to be computed on all active segments.
*/
@@ -429,7 +442,7 @@ void WS2812FX::setMode(uint8_t segid, uint8_t m) {
if (_segments[segid].mode != m)
{
- _segment_runtimes[segid].reset();
+ _segment_runtimes[segid].markForReset();
_segments[segid].mode = m;
}
}
@@ -444,12 +457,12 @@ uint8_t WS2812FX::getPaletteCount()
return 13 + GRADIENT_PALETTE_COUNT;
}
-//TODO effect transitions
+// REMOVED IN 7b969bb
+/* //TODO effect transitions
-
-bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t f1, uint8_t f2, uint8_t f3, uint8_t p) {
+bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t c1, uint8_t c2, uint8_t c3, uint8_t p) {
Segment& seg = _segments[getMainSegmentId()];
- uint8_t modePrev = seg.mode, speedPrev = seg.speed, intensityPrev = seg.intensity, fft1Prev = seg.fft1, fft2Prev = seg.fft2, fft3Prev = seg.fft3, palettePrev = seg.palette;
+ uint8_t modePrev = seg.mode, speedPrev = seg.speed, intensityPrev = seg.intensity, custom1Prev = seg.custom1, custom2Prev = seg.custom2, custom3Prev = seg.custom3, palettePrev = seg.palette;
bool applied = false;
@@ -460,9 +473,9 @@ bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t f1, uin
{
_segments[i].speed = s;
_segments[i].intensity = in;
- _segments[i].fft1 = f1;
- _segments[i].fft2 = f2;
- _segments[i].fft3 = f3;
+ _segments[i].custom1 = c1;
+ _segments[i].custom2 = c2;
+ _segments[i].custom3 = c3;
_segments[i].palette = p;
setMode(i, m);
applied = true;
@@ -473,62 +486,59 @@ bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t f1, uin
if (!applyToAllSelected || !applied) {
seg.speed = s;
seg.intensity = in;
- seg.fft1 = f1;
- seg.fft2 = f2;
- seg.fft3 = f3;
+ seg.custom1 = c1;
+ seg.custom2 = c2;
+ seg.custom3 = c3;
seg.palette = p;
setMode(mainSegment, m);
}
- if (seg.mode != modePrev || seg.speed != speedPrev || seg.intensity != intensityPrev || seg.fft1 != fft1Prev || seg.fft2 != fft2Prev || seg.fft3 != fft3Prev || seg.palette != palettePrev) return true;
+ if (seg.mode != modePrev || seg.speed != speedPrev || seg.intensity != intensityPrev || seg.custom1 != custom1Prev || seg.custom2 != custom2Prev || seg.custom3 != custom3Prev || seg.palette != palettePrev) return true;
return false;
-}
+} */
void WS2812FX::setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
- setColor(slot, ((uint32_t)w << 24) |((uint32_t)r << 16) | ((uint32_t)g << 8) | b);
+ setColor(slot, RGBW32(r, g, b, w));
}
+//applies to all active and selected segments
void WS2812FX::setColor(uint8_t slot, uint32_t c) {
if (slot >= NUM_COLORS) return;
- bool applied = false;
-
- if (applyToAllSelected) {
- for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
- {
- if (_segments[i].isSelected()) {
- _segments[i].setColor(slot, c, i);
- applied = true;
- }
+ for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
+ {
+ if (_segments[i].isActive() && _segments[i].isSelected()) {
+ _segments[i].setColor(slot, c, i);
}
}
+}
- if (!applyToAllSelected || !applied) {
- uint8_t mainseg = getMainSegmentId();
- _segments[mainseg].setColor(slot, c, mainseg);
+void WS2812FX::setCCT(uint16_t k) {
+ for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
+ {
+ if (_segments[i].isActive() && _segments[i].isSelected()) {
+ _segments[i].setCCT(k, i);
+ }
}
}
-void WS2812FX::setBrightness(uint8_t b) {
+void WS2812FX::setBrightness(uint8_t b, bool direct) {
if (gammaCorrectBri) b = gamma8(b);
if (_brightness == b) return;
_brightness = b;
- _segment_index = 0;
if (_brightness == 0) { //unfreeze all segments on power off
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
_segments[i].setOption(SEG_OPTION_FREEZE, false);
}
}
- if (SEGENV.next_time > millis() + 22 && millis() - _lastShow > MIN_SHOW_DELAY) show();//apply brightness change immediately if no refresh soon
-}
-
-uint8_t WS2812FX::getMode(void) {
- return _segments[getMainSegmentId()].mode;
-}
-
-uint8_t WS2812FX::getSpeed(void) {
- return _segments[getMainSegmentId()].speed;
+ if (direct) {
+ // would be dangerous if applied immediately (could exceed ABL), but will not output until the next show()
+ busses.setBrightness(b);
+ } else {
+ unsigned long t = millis();
+ if (_segment_runtimes[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon
+ }
}
uint8_t WS2812FX::getBrightness(void) {
@@ -539,24 +549,38 @@ uint8_t WS2812FX::getMaxSegments(void) {
return MAX_NUM_SEGMENTS;
}
-/*uint8_t WS2812FX::getFirstSelectedSegment(void)
+uint8_t WS2812FX::getFirstSelectedSegId(void)
{
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].isActive() && _segments[i].isSelected()) return i;
}
- for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) //if none selected, get first active
+ // if none selected, use the main segment
+ return getMainSegmentId();
+}
+
+void WS2812FX::setMainSegmentId(uint8_t n) {
+ if (n >= MAX_NUM_SEGMENTS) return;
+ //use supplied n if active, or first active
+ if (_segments[n].isActive()) {
+ _mainSegment = n; return;
+ }
+ for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
- if (_segments[i].isActive()) return i;
+ if (_segments[i].isActive()) {
+ _mainSegment = i; return;
+ }
}
- return 0;
-}*/
+ _mainSegment = 0;
+ return;
+}
uint8_t WS2812FX::getMainSegmentId(void) {
- if (mainSegment >= MAX_NUM_SEGMENTS) return 0;
- if (_segments[mainSegment].isActive()) return mainSegment;
- for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) //get first active
- {
+ return _mainSegment;
+}
+
+uint8_t WS2812FX::getLastActiveSegmentId(void) {
+ for (uint8_t i = MAX_NUM_SEGMENTS -1; i > 0; i--) {
if (_segments[i].isActive()) return i;
}
return 0;
@@ -571,10 +595,6 @@ uint8_t WS2812FX::getActiveSegmentsNum(void) {
return c;
}
-uint32_t WS2812FX::getColor(void) {
- return _segments[getMainSegmentId()].colors[0];
-}
-
uint32_t WS2812FX::getPixelColor(uint16_t i)
{
i = realPixelIndex(i);
@@ -596,8 +616,12 @@ WS2812FX::Segment& WS2812FX::getSegment(uint8_t id) {
return _segments[id];
}
-WS2812FX::Segment_runtime WS2812FX::getSegmentRuntime(void) {
- return SEGENV;
+WS2812FX::Segment& WS2812FX::getFirstSelectedSeg(void) {
+ return _segments[getFirstSelectedSegId()];
+}
+
+WS2812FX::Segment& WS2812FX::getMainSegment(void) {
+ return _segments[getMainSegmentId()];
}
WS2812FX::Segment* WS2812FX::getSegments(void) {
@@ -622,6 +646,84 @@ uint16_t WS2812FX::getLengthPhysical(void) {
return len;
}
+uint8_t WS2812FX::Segment::differs(Segment& b) {
+ uint8_t d = 0;
+ if (start != b.start) d |= SEG_DIFFERS_BOUNDS;
+ if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
+ if (offset != b.offset) d |= SEG_DIFFERS_GSO;
+ if (grouping != b.grouping) d |= SEG_DIFFERS_GSO;
+ if (spacing != b.spacing) d |= SEG_DIFFERS_GSO;
+ if (opacity != b.opacity) d |= SEG_DIFFERS_BRI;
+ if (mode != b.mode) d |= SEG_DIFFERS_FX;
+ if (speed != b.speed) d |= SEG_DIFFERS_FX;
+ if (intensity != b.intensity) d |= SEG_DIFFERS_FX;
+ if (palette != b.palette) d |= SEG_DIFFERS_FX;
+
+ if ((options & 0b00101110) != (b.options & 0b00101110)) d |= SEG_DIFFERS_OPT;
+ if ((options & 0x01) != (b.options & 0x01)) d |= SEG_DIFFERS_SEL;
+
+ for (uint8_t i = 0; i < NUM_COLORS; i++)
+ {
+ if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL;
+ }
+
+ return d;
+}
+
+void WS2812FX::Segment::refreshLightCapabilities() {
+ if (!isActive()) {
+ _capabilities = 0; return;
+ }
+ uint8_t capabilities = 0;
+ uint8_t awm = instance->autoWhiteMode;
+ bool whiteSlider = (awm == RGBW_MODE_DUAL || awm == RGBW_MODE_MANUAL_ONLY);
+ bool segHasValidBus = false;
+
+ for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
+ Bus *bus = busses.getBus(b);
+ if (bus == nullptr || bus->getLength()==0) break;
+ if (bus->getStart() >= stop) continue;
+ if (bus->getStart() + bus->getLength() <= start) continue;
+
+ segHasValidBus = true;
+ uint8_t type = bus->getType();
+ if (type != TYPE_ANALOG_1CH && (cctFromRgb || type != TYPE_ANALOG_2CH))
+ {
+ capabilities |= 0x01; // segment supports RGB (full color)
+ }
+ if (bus->isRgbw() && whiteSlider) capabilities |= 0x02; // segment supports white channel
+ if (!cctFromRgb) {
+ switch (type) {
+ case TYPE_ANALOG_5CH:
+ case TYPE_ANALOG_2CH:
+ capabilities |= 0x04; //segment supports white CCT
+ }
+ }
+ if (correctWB && type != TYPE_ANALOG_1CH) capabilities |= 0x04; //white balance correction (uses CCT slider)
+ }
+ // if seg has any bus, but no bus has RGB, it by definition supports white (at least for now)
+ // In case of no RGB, disregard auto white mode and always show a white slider
+ if (segHasValidBus && !(capabilities & 0x01)) capabilities |= 0x02; // segment supports white channel
+ _capabilities = capabilities;
+}
+
+//used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw.
+//returns if there is an RGBW bus (supports RGB and White, not only white)
+//not influenced by auto-white mode, also true if white slider does not affect output white channel
+bool WS2812FX::hasRGBWBus(void) {
+ for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
+ Bus *bus = busses.getBus(b);
+ if (bus == nullptr || bus->getLength()==0) break;
+ switch (bus->getType()) {
+ case TYPE_SK6812_RGBW:
+ case TYPE_TM1814:
+ case TYPE_ANALOG_4CH:
+ return true;
+ }
+ }
+ return false;
+}
+
// WLEDSR: calculate 2D segment variables using start/stop of segment using the x/y coordinnates of start and stop to determine topleft (startX/Y) and bottomright (stopXY) of the segment
void WS2812FX::set2DSegment(uint8_t n) {
Segment& seg = _segments[n];
@@ -639,7 +741,8 @@ void WS2812FX::set2DSegment(uint8_t n) {
//WLEDSR, for strips we need ledCountx1 dimension
void WS2812FX::setStripOrPanelWidthAndHeight() {
if (stripOrMatrixPanel == 0) { //strip
- matrixWidth = ledCount;
+ // ledCount was removed, replaced by strip.getLengthTotal()
+ matrixWidth = strip.getLengthTotal();
matrixHeight = 1;
//following code commented, in case we need it in the future
@@ -681,12 +784,29 @@ void WS2812FX::setStripOrPanelWidthAndHeight() {
// Serial.printf("setStripOrPanelWidthAndHeight %d %d %d", strip.stripOrMatrixPanel, strip.matrixWidth, strip.matrixHeight);Serial.println();
}
-void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) {
+bool WS2812FX::hasCCTBus(void) {
+ if (cctFromRgb && !correctWB) return false;
+ for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
+ Bus *bus = busses.getBus(b);
+ if (bus == nullptr || bus->getLength()==0) break;
+ switch (bus->getType()) {
+ case TYPE_ANALOG_5CH:
+ case TYPE_ANALOG_2CH:
+ return true;
+ }
+ }
+ return false;
+}
+
+void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset) {
if (n >= MAX_NUM_SEGMENTS) return;
Segment& seg = _segments[n];
- // return if neither bounds nor grouping have changed
- if (seg.start == i1 && seg.stop == i2 && (!grouping || (seg.grouping == grouping && seg.spacing == spacing))) return;
+ //return if neither bounds nor grouping have changed
+ bool boundsUnchanged = (seg.start == i1 && seg.stop == i2);
+ if (boundsUnchanged
+ && (!grouping || (seg.grouping == grouping && seg.spacing == spacing))
+ && (offset == UINT16_MAX || offset == seg.offset)) return;
if (seg.stop) setRange(seg.start, seg.stop -1, 0); // turn old segment range off
if (i2 <= i1) // disable segment
@@ -696,17 +816,8 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
delete[] seg.name;
seg.name = nullptr;
}
- if (n == mainSegment) //if main segment is deleted, set first active as main segment
- {
- for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
- {
- if (_segments[i].isActive()) {
- mainSegment = i;
- return;
- }
- }
- mainSegment = 0; //should not happen (always at least one active segment)
- }
+ // if main segment is deleted, set first active as main segment
+ if (n == _mainSegment) setMainSegmentId(0);
return;
}
if (i1 < _length) seg.start = i1;
@@ -719,12 +830,24 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
seg.grouping = grouping;
seg.spacing = spacing;
}
- _segment_runtimes[n].reset();
+ if (offset < UINT16_MAX) seg.offset = offset;
+ _segment_runtimes[n].markForReset();
+ if (!boundsUnchanged) seg.refreshLightCapabilities();
+}
+
+void WS2812FX::restartRuntime() {
+ for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) {
+ _segment_runtimes[i].markForReset();
+ }
+}
+
+void WS2812FX::setReset(uint8_t n) {
+ _segment_runtimes[n].markForReset();
}
void WS2812FX::resetSegments() {
- for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) if (_segments[i].name) delete _segments[i].name;
- mainSegment = 0;
+ for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) if (_segments[i].name) delete[] _segments[i].name;
+ _mainSegment = 0;
memset(_segments, 0, sizeof(_segments));
//memset(_segment_runtimes, 0, sizeof(_segment_runtimes));
_segment_index = 0;
@@ -738,6 +861,7 @@ void WS2812FX::resetSegments() {
_segments[0].setOption(SEG_OPTION_SELECTED, 1);
_segments[0].setOption(SEG_OPTION_ON, 1);
_segments[0].opacity = 255;
+ _segments[0].cct = 127;
for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++)
{
@@ -745,18 +869,18 @@ void WS2812FX::resetSegments() {
_segments[i].grouping = 1;
_segments[i].setOption(SEG_OPTION_ON, 1);
_segments[i].opacity = 255;
+ _segments[i].cct = 127;
_segments[i].speed = DEFAULT_SPEED;
_segments[i].intensity = DEFAULT_INTENSITY;
- _segment_runtimes[i].reset();
+ _segment_runtimes[i].markForReset();
}
- _segment_runtimes[0].reset();
+ _segment_runtimes[0].markForReset();
}
-void WS2812FX::makeAutoSegments() {
- uint16_t segStarts[MAX_NUM_SEGMENTS] = {0};
- uint16_t segStops [MAX_NUM_SEGMENTS] = {0};
-
+void WS2812FX::makeAutoSegments(bool forceReset) {
if (autoSegments) { //make one segment per bus
+ uint16_t segStarts[MAX_NUM_SEGMENTS] = {0};
+ uint16_t segStops [MAX_NUM_SEGMENTS] = {0};
uint8_t s = 0;
for (uint8_t i = 0; i < busses.getNumBusses(); i++) {
Bus* b = busses.getBus(i);
@@ -779,9 +903,15 @@ void WS2812FX::makeAutoSegments() {
setSegment(i, segStarts[i], segStops[i]);
}
} else {
- //expand the main seg to the entire length, but only if there are no other segments
+ //expand the main seg to the entire length, but only if there are no other segments, or reset is forced
uint8_t mainSeg = getMainSegmentId();
+ if (forceReset) {
+ for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) {
+ setSegment(i, 0, 0);
+ }
+ }
+
if (getActiveSegmentsNum() < 2) {
setSegment(mainSeg, 0, _length);
}
@@ -796,6 +926,8 @@ void WS2812FX::fixInvalidSegments() {
{
if (_segments[i].start >= _length) setSegment(i, 0, 0);
if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length);
+ // this is always called as the last step after finalizeInit(), update covered bus types
+ getSegment(i).refreshLightCapabilities();
}
}
@@ -816,15 +948,16 @@ bool WS2812FX::checkSegmentAlignment() {
}
//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply)
-void WS2812FX::setPixelSegment(uint8_t n)
+//Note: If called in an interrupt (e.g. JSON API), original segment must be restored,
+//otherwise it can lead to a crash on ESP32 because _segment_index is modified while in use by the main thread
+uint8_t WS2812FX::setPixelSegment(uint8_t n)
{
+ uint8_t prevSegId = _segment_index;
if (n < MAX_NUM_SEGMENTS) {
_segment_index = n;
- _virtualSegmentLength = SEGMENT.length();
- } else {
- _segment_index = 0;
- _virtualSegmentLength = 0;
+ _virtualSegmentLength = SEGMENT.virtualLength();
}
+ return prevSegId;
}
void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col)
@@ -850,41 +983,41 @@ void WS2812FX::setTransition(uint16_t t)
void WS2812FX::setTransitionMode(bool t)
{
- unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
+ unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
- _segment_index = i;
- SEGMENT.setOption(SEG_OPTION_TRANSITIONAL, t);
+ _segments[i].setOption(SEG_OPTION_TRANSITIONAL, t);
- if (t && SEGMENT.mode == FX_MODE_STATIC && SEGENV.next_time > waitMax) SEGENV.next_time = waitMax;
+ if (t && _segments[i].mode == FX_MODE_STATIC && _segment_runtimes[i].next_time > waitMax)
+ _segment_runtimes[i].next_time = waitMax;
}
}
/*
* color blend function
*/
-uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) {
+uint32_t IRAM_ATTR WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) {
if(blend == 0) return color1;
uint16_t blendmax = b16 ? 0xFFFF : 0xFF;
if(blend == blendmax) return color2;
uint8_t shift = b16 ? 16 : 8;
- uint32_t w1 = (color1 >> 24) & 0xFF;
- uint32_t r1 = (color1 >> 16) & 0xFF;
- uint32_t g1 = (color1 >> 8) & 0xFF;
- uint32_t b1 = color1 & 0xFF;
+ uint32_t w1 = W(color1);
+ uint32_t r1 = R(color1);
+ uint32_t g1 = G(color1);
+ uint32_t b1 = B(color1);
- uint32_t w2 = (color2 >> 24) & 0xFF;
- uint32_t r2 = (color2 >> 16) & 0xFF;
- uint32_t g2 = (color2 >> 8) & 0xFF;
- uint32_t b2 = color2 & 0xFF;
+ uint32_t w2 = W(color2);
+ uint32_t r2 = R(color2);
+ uint32_t g2 = G(color2);
+ uint32_t b2 = B(color2);
uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift;
uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift;
uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift;
uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift;
- return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3));
+ return RGBW32(r3, g3, b3, w3);
}
/*
@@ -917,10 +1050,10 @@ void WS2812FX::fade2black(uint8_t rate) {
for(uint16_t i = 0; i < SEGLEN; i++) {
color = getPixelColor(i);
- int w1 = (color >> 24) & 0xff;
- int r1 = (color >> 16) & 0xff;
- int g1 = (color >> 8) & 0xff;
- int b1 = color & 0xff;
+ int w1 = W(color);
+ int r1 = R(color);
+ int g1 = G(color);
+ int b1 = B(color);
int w = w1 * mappedRate;
int r = r1 * (mappedRate * 1.05); // acount for the fact that leds stay red on much lower intensities
@@ -939,17 +1072,17 @@ void WS2812FX::fade_out(uint8_t rate) {
float mappedRate = float(rate) +1.1;
uint32_t color = SEGCOLOR(1); // target color
- int w2 = (color >> 24) & 0xff;
- int r2 = (color >> 16) & 0xff;
- int g2 = (color >> 8) & 0xff;
- int b2 = color & 0xff;
+ int w2 = W(color);
+ int r2 = R(color);
+ int g2 = G(color);
+ int b2 = B(color);
for(uint16_t i = 0; i < SEGLEN; i++) {
color = getPixelColor(i);
- int w1 = (color >> 24) & 0xff;
- int r1 = (color >> 16) & 0xff;
- int g1 = (color >> 8) & 0xff;
- int b1 = color & 0xff;
+ int w1 = W(color);
+ int r1 = R(color);
+ int g1 = G(color);
+ int b1 = B(color);
int wdelta = (w2 - w1) / mappedRate;
int rdelta = (r2 - r1) / mappedRate;
@@ -983,9 +1116,9 @@ void WS2812FX::blur(uint8_t blur_amount)
cur += carryover;
if(i > 0) {
uint32_t c = getPixelColor(i-1);
- uint8_t r = (c >> 16 & 0xFF);
- uint8_t g = (c >> 8 & 0xFF);
- uint8_t b = (c & 0xFF);
+ uint8_t r = R(c);
+ uint8_t g = G(c);
+ uint8_t b = B(c);
setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue));
}
setPixelColor(i,cur.red, cur.green, cur.blue);
@@ -993,18 +1126,12 @@ void WS2812FX::blur(uint8_t blur_amount)
}
}
-uint16_t WS2812FX::triwave16(uint16_t in)
+uint16_t IRAM_ATTR WS2812FX::triwave16(uint16_t in)
{
if (in < 0x8000) return in *2;
return 0xFFFF - (in - 0x8000)*2;
}
-uint8_t WS2812FX::sin_gap(uint16_t in) {
- if (in & 0x100) return 0;
- //if (in > 255) return 0;
- return sin8(in + 192); //correct phase shift of sine so that it starts and stops at 0
-}
-
/*
* Generates a tristate square wave w/ attac & decay
* @param x input value 0-255
@@ -1066,18 +1193,18 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) {
}
-uint32_t WS2812FX::crgb_to_col(CRGB fastled)
+uint32_t IRAM_ATTR WS2812FX::crgb_to_col(CRGB fastled)
{
- return (((uint32_t)fastled.red << 16) | ((uint32_t)fastled.green << 8) | fastled.blue);
+ return RGBW32(fastled.red, fastled.green, fastled.blue, 0);
}
-CRGB WS2812FX::col_to_crgb(uint32_t color)
+CRGB IRAM_ATTR WS2812FX::col_to_crgb(uint32_t color)
{
CRGB fastled_col;
- fastled_col.red = (color >> 16 & 0xFF);
- fastled_col.green = (color >> 8 & 0xFF);
- fastled_col.blue = (color & 0xFF);
+ fastled_col.red = R(color);
+ fastled_col.green = G(color);
+ fastled_col.blue = B(color);
return fastled_col;
}
@@ -1195,30 +1322,25 @@ void WS2812FX::handle_palette(void)
* @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling)
* @returns Single color from palette
*/
-uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri)
+uint32_t IRAM_ATTR WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri)
{
- if (SEGMENT.palette == 0 && mcol < 3) {
+ if ((SEGMENT.palette == 0 && mcol < 3) || _no_rgb) {
uint32_t color = SEGCOLOR(mcol);
- if (pbri != 255) {
- CRGB crgb_color = col_to_crgb(color);
- crgb_color.nscale8_video(pbri);
- return crgb_to_col(crgb_color);
- } else {
- return color;
- }
+ if (pbri == 255) return color;
+ return RGBW32(scale8_video(R(color),pbri), scale8_video(G(color),pbri), scale8_video(B(color),pbri), scale8_video(W(color),pbri));
}
uint8_t paletteIndex = i;
if (mapping && SEGLEN > 1) paletteIndex = (i*255)/(SEGLEN -1);
if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGB fastled_col;
- fastled_col = ColorFromPalette( currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND);
+ fastled_col = ColorFromPalette(currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND);
return crgb_to_col(fastled_col);
}
-//load custom mapping table from JSON file
+//load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
void WS2812FX::deserializeMap(uint8_t n) {
char fileName[32];
strcpy_P(fileName, PSTR("/ledmap"));
@@ -1236,11 +1358,19 @@ void WS2812FX::deserializeMap(uint8_t n) {
return;
}
- DynamicJsonDocument doc(JSON_BUFFER_SIZE); // full sized buffer for larger maps
+ #ifdef WLED_USE_DYNAMIC_JSON
+ DynamicJsonDocument doc(JSON_BUFFER_SIZE);
+ #else
+ if (!requestJSONBufferLock(7)) return;
+ #endif
+
DEBUG_PRINT(F("Reading LED map from "));
DEBUG_PRINTLN(fileName);
- if (!readObjectFromFile(fileName, nullptr, &doc)) return; //if file does not exist just exit
+ if (!readObjectFromFile(fileName, nullptr, &doc)) {
+ releaseJSONBufferLock();
+ return; //if file does not exist just exit
+ }
// erase old custom ledmap
if (customMappingTable != nullptr) {
@@ -1257,6 +1387,8 @@ void WS2812FX::deserializeMap(uint8_t n) {
customMappingTable[i] = (uint16_t) map[i];
}
}
+
+ releaseJSONBufferLock();
}
//gamma 2.8 lookup table used for color correction
@@ -1297,15 +1429,20 @@ uint8_t WS2812FX::gamma8(uint8_t b)
uint32_t WS2812FX::gamma32(uint32_t color)
{
if (!gammaCorrectCol) return color;
- uint8_t w = (color >> 24);
- uint8_t r = (color >> 16);
- uint8_t g = (color >> 8);
- uint8_t b = color;
+ uint8_t w = W(color);
+ uint8_t r = R(color);
+ uint8_t g = G(color);
+ uint8_t b = B(color);
w = gammaT[w];
r = gammaT[r];
g = gammaT[g];
b = gammaT[b];
- return ((w << 24) | (r << 16) | (g << 8) | (b));
+ return RGBW32(r, g, b, w);
}
-WS2812FX* WS2812FX::instance = nullptr;
\ No newline at end of file
+WS2812FX* WS2812FX::instance = nullptr;
+
+//Bus static member definition, would belong in bus_manager.cpp
+int16_t Bus::_cct = -1;
+uint8_t Bus::_cctBlend = 0;
+uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL;
\ No newline at end of file
diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp
index 2e5b9b17b0..e41b7e3fb8 100644
--- a/wled00/alexa.cpp
+++ b/wled00/alexa.cpp
@@ -44,7 +44,7 @@ void onAlexaChange(EspalexaDevice* dev)
if (bri == 0)
{
bri = briLast;
- colorUpdated(CALL_MODE_ALEXA);
+ stateUpdated(CALL_MODE_ALEXA);
}
} else {
applyPreset(macroAlexaOn, CALL_MODE_ALEXA);
@@ -58,7 +58,7 @@ void onAlexaChange(EspalexaDevice* dev)
{
briLast = bri;
bri = 0;
- colorUpdated(CALL_MODE_ALEXA);
+ stateUpdated(CALL_MODE_ALEXA);
}
} else {
applyPreset(macroAlexaOff, CALL_MODE_ALEXA);
@@ -67,33 +67,37 @@ void onAlexaChange(EspalexaDevice* dev)
} else if (m == EspalexaDeviceProperty::bri)
{
bri = espalexaDevice->getValue();
- colorUpdated(CALL_MODE_ALEXA);
+ stateUpdated(CALL_MODE_ALEXA);
} else //color
{
if (espalexaDevice->getColorMode() == EspalexaColorMode::ct) //shade of white
{
+ byte rgbw[4];
uint16_t ct = espalexaDevice->getCt();
- if (strip.isRgbw)
- {
+ if (!ct) return;
+ uint16_t k = 1000000 / ct; //mireds to kelvin
+
+ if (strip.hasCCTBus()) {
+ strip.setCCT(k);
+ rgbw[0]= 0; rgbw[1]= 0; rgbw[2]= 0; rgbw[3]= 255;
+ } else if (strip.hasWhiteChannel()) {
switch (ct) { //these values empirically look good on RGBW
- case 199: col[0]=255; col[1]=255; col[2]=255; col[3]=255; break;
- case 234: col[0]=127; col[1]=127; col[2]=127; col[3]=255; break;
- case 284: col[0]= 0; col[1]= 0; col[2]= 0; col[3]=255; break;
- case 350: col[0]=130; col[1]= 90; col[2]= 0; col[3]=255; break;
- case 383: col[0]=255; col[1]=153; col[2]= 0; col[3]=255; break;
+ case 199: rgbw[0]=255; rgbw[1]=255; rgbw[2]=255; rgbw[3]=255; break;
+ case 234: rgbw[0]=127; rgbw[1]=127; rgbw[2]=127; rgbw[3]=255; break;
+ case 284: rgbw[0]= 0; rgbw[1]= 0; rgbw[2]= 0; rgbw[3]=255; break;
+ case 350: rgbw[0]=130; rgbw[1]= 90; rgbw[2]= 0; rgbw[3]=255; break;
+ case 383: rgbw[0]=255; rgbw[1]=153; rgbw[2]= 0; rgbw[3]=255; break;
+ default : colorKtoRGB(k, rgbw);
}
} else {
- colorCTtoRGB(ct, col);
+ colorKtoRGB(k, rgbw);
}
+ strip.setColor(0, rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
} else {
uint32_t color = espalexaDevice->getRGB();
-
- col[0] = ((color >> 16) & 0xFF);
- col[1] = ((color >> 8) & 0xFF);
- col[2] = ( color & 0xFF);
- col[3] = 0;
+ strip.setColor(0, color);
}
- colorUpdated(CALL_MODE_ALEXA);
+ stateUpdated(CALL_MODE_ALEXA);
}
}
diff --git a/wled00/audio_reactive.h b/wled00/audio_reactive.h
index 2b10894dd2..ce64df1530 100644
--- a/wled00/audio_reactive.h
+++ b/wled00/audio_reactive.h
@@ -17,6 +17,9 @@
#include "wled.h"
#include
+#include "audio_source.h"
+
+AudioSource *audioSource;
// ALL AUDIO INPUT PINS DEFINED IN wled.h AND CONFIGURABLE VIA UI
@@ -37,9 +40,10 @@
// #define MIC_SAMPLING_LOG
// #define FFT_SAMPLING_LOG
-const i2s_port_t I2S_PORT = I2S_NUM_0;
-const int BLOCK_SIZE = 64;
+//#define MAJORPEAK_SUPPRESS_NOISE // define to activate a dirty hack that ignores the lowest + hightest FFT bins
+const i2s_port_t I2S_PORT = I2S_NUM_0;
+const int BLOCK_SIZE = 128;
const int SAMPLE_RATE = 10240; // Base sample rate in Hz
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
@@ -146,7 +150,6 @@ void getSample() {
#endif
micLev = ((micLev * 31) + micIn) / 32; // Smooth it out over the last 32 samples for automatic centering
micIn -= micLev; // Let's center it to 0 now
- micIn = abs(micIn); // And get the absolute value of each sample
/*---------DEBUG---------*/
DEBUGSR_PRINT("\t\t"); DEBUGSR_PRINT(micIn);
/*-------END DEBUG-------*/
@@ -155,14 +158,16 @@ void getSample() {
expAdjF = (weighting * micIn + (1.0-weighting) * expAdjF);
expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF;
+ expAdjF = fabs(expAdjF); // Now (!) take the absolute value
tmpSample = (int)expAdjF;
/*---------DEBUG---------*/
DEBUGSR_PRINT("\t\t"); DEBUGSR_PRINT(sample);
/*-------END DEBUG-------*/
+ micIn = abs(micIn); // And get the absolute value of each sample
sampleAdj = tmpSample * sampleGain / 40 + tmpSample / 16; // Adjust the gain.
- sampleAdj = min(sampleAdj, 255);
+ sampleAdj = min(sampleAdj, 255); // Question: why are we limiting the value to 8 bits ???
sample = sampleAdj; // ONLY update sample ONCE!!!!
sampleAvg = ((sampleAvg * 15) + sample) / 16; // Smooth it out over the last 16 samples.
@@ -172,7 +177,10 @@ void getSample() {
DEBUGSR_PRINT("\t\t"); DEBUGSR_PRINT(sampleAvg); DEBUGSR_PRINT("\n\n");
/*-------END DEBUG-------*/
- if (millis() - timeOfPeak > MIN_SHOW_DELAY) { // Auto-reset of samplePeak after a complete frame has passed.
+ // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC
+ uint16_t MinShowDelay = strip.getMinShowDelay();
+
+ if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed.
samplePeak = 0;
udpSamplePeak = 0;
}
@@ -180,7 +188,7 @@ void getSample() {
if (userVar1 == 0) samplePeak = 0;
// Poor man's beat detection by seeing if sample > Average + some value.
// Serial.print(binNum); Serial.print("\t"); Serial.print(fftBin[binNum]); Serial.print("\t"); Serial.print(fftAvg[binNum/16]); Serial.print("\t"); Serial.print(maxVol); Serial.print("\t"); Serial.println(samplePeak);
- if (fftBin[binNum] > ( maxVol) && millis() > (peakTime + 100)) { // This goe through ALL of the 255 bins
+ if ((fftBin[binNum] > maxVol) && (millis() > (peakTime + 100))) { // This goe through ALL of the 255 bins
// if (sample > (sampleAvg + maxVol) && millis() > (peakTime + 200)) {
// Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync.
samplePeak = 1;
@@ -194,11 +202,27 @@ void getSample() {
/*
* A simple averaging multiplier to automatically adjust sound sensitivity.
*/
+/*
+ * A simple, but hairy, averaging multiplier to automatically adjust sound sensitivity.
+ * not sure if not sure "sample" or "sampleAvg" is the correct input signal for AGC
+ */
void agcAvg() {
- multAgc = (sampleAvg < 1) ? targetAgc : targetAgc / sampleAvg; // Make the multiplier so that sampleAvg * multiplier = setpoint
- int tmpAgc = sample * multAgc;
- if (tmpAgc > 255) tmpAgc = 0;
+ float lastMultAgc = multAgc;
+ float tmpAgc;
+ if(fabs(sampleAvg) < 2.0) {
+ tmpAgc = sampleAvg; // signal below squelch -> deliver silence
+ multAgc = multAgc * 0.95; // slightly decrease gain multiplier
+ } else {
+ multAgc = (sampleAvg < 1) ? targetAgc : targetAgc / sampleAvg; // Make the multiplier so that sampleAvg * multiplier = setpoint
+ }
+
+ if (multAgc < 0.5) multAgc = 0.5; // signal higher than 2*setpoint -> don't reduce it further
+ multAgc = (lastMultAgc*127.0 +multAgc) / 128.0; //apply some filtering to gain multiplier -> smoother transitions
+ tmpAgc = (float)sample * multAgc; // apply gain to signal
+ if (tmpAgc <= (soundSquelch*1.2)) tmpAgc = sample; // check against squelch threshold - increased by 20% to avoid artefacts (ripples)
+
+ if (tmpAgc > 255) tmpAgc = 255;
sampleAgc = tmpAgc; // ONLY update sampleAgc ONCE because it's used elsewhere asynchronously!!!!
userVar0 = sampleAvg * 4;
if (userVar0 > 255) userVar0 = 255;
@@ -266,8 +290,9 @@ double fftAdd( int from, int to) {
// FFT main code
void FFTcode( void * parameter) {
DEBUG_PRINT("FFT running on core: "); DEBUG_PRINTLN(xPortGetCoreID());
- //double beatSample = 0; // COMMENTED OUT - UNUSED VARIABLE COMPILER WARNINGS
- //double envelope = 0; // COMMENTED OUT - UNUSED VARIABLE COMPILER WARNINGS
+#ifdef MAJORPEAK_SUPPRESS_NOISE
+ static double xtemp[24] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+#endif
for(;;) {
delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy.
@@ -276,39 +301,32 @@ void FFTcode( void * parameter) {
// Only run the FFT computing code if we're not in Receive mode
if (audioSyncEnabled & (1 << 1))
continue;
+ audioSource->getSamples(vReal, samples);
- microseconds = micros();
- //extern double volume; // COMMENTED OUT - UNUSED VARIABLE COMPILER WARNINGS
-
- for(int i=0; i> 2; // Analog Read
- } else {
- int32_t digitalSample = 0;
- size_t bytes_read = 0;
- esp_err_t result = i2s_read(I2S_PORT, &digitalSample, sizeof(digitalSample), &bytes_read, /*portMAX_DELAY*/ 10);
- //int bytes_read = i2s_pop_sample(I2S_PORT, (char *)&digitalSample, portMAX_DELAY); // no timeout
- if (bytes_read > 0) micData = abs(digitalSample >> 16);
-
- }
- micDataSm = ((micData * 3) + micData)/4; // We'll be passing smoothed micData to the volume routines as the A/D is a bit twitchy.
- vReal[i] = micData; // Store Mic Data in an array
- vImag[i] = 0;
+ // old code - Last sample in vReal is our current mic sample
+ //micDataSm = (uint16_t)vReal[samples - 1]; // will do a this a bit later
- // MIC DATA DEBUGGING
- // DEBUGSR_PRINT("micData: ");
- // DEBUGSR_PRINT(micData);
- // DEBUGSR_PRINT("\tmicDataSm: ");
- // DEBUGSR_PRINT("\t");
- // DEBUGSR_PRINT(micDataSm);
- // DEBUGSR_PRINT("\n");
+ // micDataSm = ((micData * 3) + micData)/4;
- if ((digitalMic && dmEnabled) == false) { while(micros() - microseconds < sampling_period_us){/*empty loop*/} }
-
- microseconds += sampling_period_us;
+ double maxSample = 0.0;
+ for (int i=0; i < samples; i++)
+ {
+ // set imaginary parts to 0
+ vImag[i] = 0;
+ // pick our our current mic sample - we take the max value from all samples that go into FFT
+ if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts
+ {
+ if (fabs(vReal[i]) > maxSample) maxSample = fabs(vReal[i]);
+ }
}
+ micDataSm = (uint16_t)maxSample;
- FFT.Windowing( FFT_WIN_TYP_HAMMING, FFT_FORWARD ); // Weigh data
+ FFT.DCRemoval(); // let FFT lib remove DC component, so we don't need to care about this in getSamples()
+
+ //FFT.Windowing( FFT_WIN_TYP_HAMMING, FFT_FORWARD ); // Weigh data - standard Hamming window
+ //FFT.Windowing( FFT_WIN_TYP_BLACKMAN, FFT_FORWARD ); // Blackman window - better side freq rejection
+ //FFT.Windowing( FFT_WIN_TYP_BLACKMAN_HARRIS, FFT_FORWARD );// Blackman-Harris - excellent sideband rejection
+ FFT.Windowing( FFT_WIN_TYP_FLT_TOP, FFT_FORWARD ); // Flat Top Window - better amplitude accuracy
FFT.Compute( FFT_FORWARD ); // Compute FFT
FFT.ComplexToMagnitude(); // Compute magnitudes
@@ -316,11 +334,70 @@ void FFTcode( void * parameter) {
// vReal[3 .. 255] contain useful data, each a 20Hz interval (60Hz - 5120Hz).
// There could be interesting data at bins 0 to 2, but there are too many artifacts.
//
+#ifdef MAJORPEAK_SUPPRESS_NOISE
+ // teporarily reduce signal strength in the highest + lowest bins
+ xtemp[0] = vReal[0]; vReal[0] *= 0.005;
+ xtemp[1] = vReal[1]; vReal[1] *= 0.005;
+ xtemp[2] = vReal[2]; vReal[2] *= 0.005;
+ xtemp[3] = vReal[3]; vReal[3] *= 0.02;
+ xtemp[4] = vReal[4]; vReal[4] *= 0.02;
+ xtemp[5] = vReal[5]; vReal[5] *= 0.02;
+ xtemp[6] = vReal[6]; vReal[6] *= 0.05;
+ xtemp[7] = vReal[7]; vReal[7] *= 0.08;
+ xtemp[8] = vReal[8]; vReal[8] *= 0.1;
+ xtemp[9] = vReal[9]; vReal[9] *= 0.2;
+ xtemp[10] = vReal[10]; vReal[10] *= 0.2;
+ xtemp[11] = vReal[11]; vReal[11] *= 0.25;
+ xtemp[12] = vReal[12]; vReal[12] *= 0.3;
+ xtemp[13] = vReal[13]; vReal[13] *= 0.3;
+ xtemp[14] = vReal[14]; vReal[14] *= 0.4;
+ xtemp[15] = vReal[15]; vReal[15] *= 0.4;
+ xtemp[16] = vReal[16]; vReal[16] *= 0.4;
+ xtemp[17] = vReal[17]; vReal[17] *= 0.5;
+ xtemp[18] = vReal[18]; vReal[18] *= 0.5;
+ xtemp[19] = vReal[19]; vReal[19] *= 0.6;
+ xtemp[20] = vReal[20]; vReal[20] *= 0.7;
+ xtemp[21] = vReal[21]; vReal[21] *= 0.8;
+
+ xtemp[22] = vReal[samples-2]; vReal[samples-2] =0.0;
+ xtemp[23] = vReal[samples-1]; vReal[samples-1] =0.0;
+#endif
+
FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant
+#ifdef MAJORPEAK_SUPPRESS_NOISE
+ // dirty hack: limit suppressed channel intensities to FFT_Magnitude
+ for (int k=0; k < 24; k++) if(xtemp[k] > FFT_Magnitude) xtemp[k] = FFT_Magnitude;
+ // restore bins
+ vReal[0] = xtemp[0];
+ vReal[1] = xtemp[1];
+ vReal[2] = xtemp[2];
+ vReal[3] = xtemp[3];
+ vReal[4] = xtemp[4];
+ vReal[5] = xtemp[5];
+ vReal[6] = xtemp[6];
+ vReal[7] = xtemp[7];
+ vReal[8] = xtemp[8];
+ vReal[9] = xtemp[9];
+ vReal[10] = xtemp[10];
+ vReal[11] = xtemp[11];
+ vReal[12] = xtemp[12];
+ vReal[13] = xtemp[13];
+ vReal[14] = xtemp[14];
+ vReal[15] = xtemp[15];
+ vReal[16] = xtemp[16];
+ vReal[17] = xtemp[17];
+ vReal[18] = xtemp[18];
+ vReal[19] = xtemp[19];
+ vReal[20] = xtemp[20];
+ vReal[21] = xtemp[21];
+ vReal[samples-2] = xtemp[22];
+ vReal[samples-1] = xtemp[23];
+#endif
+
for (int i = 0; i < samples; i++) { // Values for bins 0 and 1 are WAY too large. Might as well start at 3.
double t = 0.0;
- t = abs(vReal[i]);
+ t = fabs(vReal[i]); // just to be sure - values in fft bins should be positive any way
t = t / 16.0; // Reduce magnitude. Want end result to be linear and ~4096 max.
fftBin[i] = t;
} // for()
@@ -375,7 +452,7 @@ void FFTcode( void * parameter) {
// Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely.
for (int i=0; i < 16; i++) {
// fftResult[i] = (int)fftCalc[i];
- fftResult[i] = constrain((int)fftCalc[i],0,254);
+ fftResult[i] = constrain((int)fftCalc[i],0,254); // question: why do we constrain values to 8bit here ???
fftAvg[i] = (float)fftResult[i]*.05 + (1-.05)*fftAvg[i];
}
@@ -395,12 +472,14 @@ void logAudio() {
#ifdef MIC_LOGGER
-// Serial.print(micIn); Serial.print(" ");
-// Serial.print(sample); Serial.print(" ");
-// Serial.print(sampleAvg); Serial.print(" ");
-// Serial.print(sampleAgc); Serial.print(" ");
-// Serial.print(micData); Serial.print(" ");
-// Serial.print(micDataSm); Serial.print(" ");
+ //Serial.print("micData:"); Serial.print(micData); Serial.print("\t");
+ //Serial.print("micDataSm:"); Serial.print(micDataSm); Serial.print("\t");
+ //Serial.print("micIn:"); Serial.print(micIn); Serial.print("\t");
+ //Serial.print("micLev:"); Serial.print(micLev); Serial.print("\t");
+ Serial.print("sample:"); Serial.print(sample); Serial.print("\t");
+ //Serial.print("sampleAvg:"); Serial.print(sampleAvg); Serial.print("\t");
+ //Serial.print("multAgc:"); Serial.print(multAgc); Serial.print("\t");
+ Serial.print("sampleAgc:"); Serial.print(sampleAgc); Serial.print("\t");
Serial.println(" ");
#endif
diff --git a/wled00/audio_source.h b/wled00/audio_source.h
new file mode 100644
index 0000000000..47b84665d4
--- /dev/null
+++ b/wled00/audio_source.h
@@ -0,0 +1,474 @@
+#pragma once
+
+#include
+#include "wled.h"
+#include
+
+/* ToDo: remove. ES7243 is controlled via compiler defines
+ Until this configuration is moved to the webinterface
+*/
+
+// data type requested from the I2S driver - currently we always use 32bit
+//#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible
+#ifdef I2S_USE_16BIT_SAMPLES
+#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT
+#define I2S_datatype int16_t
+#undef I2S_SAMPLE_DOWNSCALE_TO_16BIT
+#else
+#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_32BIT
+#define I2S_datatype int32_t
+#define I2S_SAMPLE_DOWNSCALE_TO_16BIT
+#endif
+
+#ifndef MCLK_PIN
+ int mclkPin = 0;
+#else
+ int mclkPin = MLCK_PIN;
+#endif
+
+#ifndef ES7243_ADDR
+ int addr_ES7243 = 0x13;
+#else
+ int addr_ES7243 = ES7243_ADDR;
+#endif
+
+#ifndef ES7243_SDAPIN
+ int pin_ES7243_SDA = 18;
+#else
+ int pin_ES7243_SDA = ES7243_SDAPIN;
+#endif
+
+#ifndef ES7243_SDAPIN
+ int pin_ES7243_SCL = 23;
+#else
+ int pin_ES7243_SCL = ES7243_SCLPIN;
+#endif
+
+/* Interface class
+ AudioSource serves as base class for all microphone types
+ This enables accessing all microphones with one single interface
+ which simplifies the caller code
+*/
+class AudioSource {
+public:
+ /* All public methods are virtual, so they can be overridden
+ Everything but the destructor is also removed, to make sure each mic
+ Implementation provides its version of this function
+ */
+ virtual ~AudioSource() {};
+
+ /* Initialize
+ This function needs to take care of anything that needs to be done
+ before samples can be obtained from the microphone.
+ */
+ virtual void initialize() = 0;
+
+ /* Deinitialize
+ Release all resources and deactivate any functionality that is used
+ by this microphone
+ */
+ virtual void deinitialize() = 0;
+
+ /* getSamples
+ Read num_samples from the microphone, and store them in the provided
+ buffer
+ */
+ virtual void getSamples(double *buffer, uint16_t num_samples) = 0;
+
+ /* Get an up-to-date sample without DC offset */
+ virtual int getSampleWithoutDCOffset() = 0;
+
+protected:
+ // Private constructor, to make sure it is not callable except from derived classes
+ AudioSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : _sampleRate(sampleRate), _blockSize(blockSize), _sampleNoDCOffset(0), _dcOffset(0.0f), _shift(lshift), _mask(mask), _initialized(false) {};
+
+ int _sampleRate; /* Microphone sampling rate */
+ int _blockSize; /* I2S block size */
+ volatile int _sampleNoDCOffset; /* Up-to-date sample without DCOffset */
+ float _dcOffset; /* Rolling average DC offset */
+ int16_t _shift; /* Shift obtained samples to the right (positive) or left(negative) by this amount */
+ uint32_t _mask; /* Bitmask for sample data after shifting. Bitmask 0X0FFF means that we need to convert 12bit ADC samples from unsigned to signed*/
+ bool _initialized; /* Gets set to true if initialization is successful */
+};
+
+/* Basic I2S microphone source
+ All functions are marked virtual, so derived classes can replace them
+*/
+class I2SSource : public AudioSource {
+public:
+ I2SSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
+ AudioSource(sampleRate, blockSize, lshift, mask) {
+ _config = {
+ .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
+ .sample_rate = _sampleRate,
+ .bits_per_sample = I2S_SAMPLE_RESOLUTION,
+ .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
+ .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
+ .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
+ .dma_buf_count = 8,
+ .dma_buf_len = _blockSize
+ };
+
+ _pinConfig = {
+ .bck_io_num = i2sckPin,
+ .ws_io_num = i2swsPin,
+ .data_out_num = I2S_PIN_NO_CHANGE,
+ .data_in_num = i2ssdPin
+ };
+ };
+
+
+
+
+ virtual void initialize() {
+
+ if (!pinManager.allocatePin(i2swsPin, true, PinOwner::DigitalMic) ||
+ !pinManager.allocatePin(i2ssdPin, true, PinOwner::DigitalMic)) {
+ return;
+ }
+
+ // i2ssckPin needs special treatment, since it might be unused on PDM mics
+ if (i2sckPin != -1) {
+ if (!pinManager.allocatePin(i2sckPin, true, PinOwner::DigitalMic))
+ return;
+ }
+
+ esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr);
+ if (err != ESP_OK) {
+ Serial.printf("Failed to install i2s driver: %d\n", err);
+ return;
+ }
+
+ err = i2s_set_pin(I2S_NUM_0, &_pinConfig);
+ if (err != ESP_OK) {
+ Serial.printf("Failed to set i2s pin config: %d\n", err);
+ return;
+ }
+
+ _initialized = true;
+ }
+
+ virtual void deinitialize() {
+ _initialized = false;
+ esp_err_t err = i2s_driver_uninstall(I2S_NUM_0);
+ if (err != ESP_OK) {
+ Serial.printf("Failed to uninstall i2s driver: %d\n", err);
+ return;
+ }
+ pinManager.deallocatePin(i2swsPin, PinOwner::DigitalMic);
+ pinManager.deallocatePin(i2ssdPin, PinOwner::DigitalMic);
+ // i2ssckPin needs special treatment, since it might be unused on PDM mics
+ if (i2sckPin != -1) {
+ pinManager.deallocatePin(i2sckPin, PinOwner::DigitalMic);
+ }
+ }
+
+ virtual void getSamples(double *buffer, uint16_t num_samples) {
+ if(_initialized) {
+ esp_err_t err;
+ size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */
+ I2S_datatype samples[num_samples]; /* Intermediary sample storage */
+
+ // Reset dc offset
+ _dcOffset = 0.0f;
+
+ err = i2s_read(I2S_NUM_0, (void *)samples, sizeof(samples), &bytes_read, portMAX_DELAY);
+ if ((err != ESP_OK)){
+ Serial.printf("Failed to get samples: %d\n", err);
+ return;
+ }
+
+ // For correct operation, we need to read exactly sizeof(samples) bytes from i2s
+ if(bytes_read != sizeof(samples)) {
+ Serial.printf("Failed to get enough samples: wanted: %d read: %d\n", sizeof(samples), bytes_read);
+ return;
+ }
+
+ // Store samples in sample buffer and update DC offset
+ for (int i = 0; i < num_samples; i++) {
+ // pre-shift samples down to 16bit
+#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
+ samples[i] >>= 16;
+#endif
+ // pre-processing of ADC samples
+ if (_mask == 0x0FFF) { // 0x0FFF means that we have 12bit unsigned data from ADC -> convert to signed
+ samples[i] = samples[i] - 2048;
+ }
+ // From the old code.
+ // double sample = (double)abs((samples[i] >> _shift));
+ double sample = 0.0;
+ if(_shift > 0)
+ sample = (double) (samples[i] >> _shift);
+ else {
+ if(_shift < 0)
+ sample = (double) (samples[i] << (- _shift)); // need to "pump up" 12bit ADC to full 16bit as delivered by other digital mics
+ else
+ sample = (double) samples[i];
+ }
+ buffer[i] = sample;
+ _dcOffset = ((_dcOffset * 31) + sample) / 32;
+ }
+
+ // Update no-DC sample
+ _sampleNoDCOffset = buffer[num_samples - 1] - _dcOffset;
+ }
+ }
+
+ virtual int getSampleWithoutDCOffset() {
+ return _sampleNoDCOffset;
+ }
+
+protected:
+ i2s_config_t _config;
+ i2s_pin_config_t _pinConfig;
+};
+
+/* I2S microphone with master clock
+ Our version of the IDF does not support setting master clock
+ routing via the provided API, so we have to do it by hand
+*/
+class I2SSourceWithMasterClock : public I2SSource {
+public:
+ I2SSourceWithMasterClock(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
+ I2SSource(sampleRate, blockSize, lshift, mask) {
+ };
+
+ virtual void initialize() {
+ // Reserve the master clock pin
+ if(!pinManager.allocatePin(mclkPin, true, PinOwner::DigitalMic)) {
+ return;
+ }
+ _routeMclk();
+ I2SSource::initialize();
+
+ }
+
+ virtual void deinitialize() {
+ // Release the master clock pin
+ pinManager.deallocatePin(mclkPin, PinOwner::DigitalMic);
+ I2SSource::deinitialize();
+ }
+protected:
+ void _routeMclk() {
+ /* Enable the mclk routing depending on the selected mclk pin
+ Only I2S_NUM_0 is supported
+ */
+ if (mclkPin == GPIO_NUM_0) {
+ PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
+ WRITE_PERI_REG(PIN_CTRL,0xFFF0);
+ } else if (mclkPin == GPIO_NUM_1) {
+ PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3);
+ WRITE_PERI_REG(PIN_CTRL, 0xF0F0);
+ } else {
+ PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2);
+ WRITE_PERI_REG(PIN_CTRL, 0xFF00);
+ }
+ }
+};
+
+/* ES7243 Microphone
+ This is an I2S microphone that requires ininitialization over
+ I2C before I2S data can be received
+*/
+class ES7243 : public I2SSourceWithMasterClock {
+
+private:
+ // I2C initialization functions for ES7243
+ void _es7243I2cBegin() {
+ Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U);
+ }
+
+ void _es7243I2cWrite(uint8_t reg, uint8_t val) {
+ Wire.beginTransmission(addr_ES7243);
+ Wire.write((uint8_t)reg);
+ Wire.write((uint8_t)val);
+ Wire.endTransmission();
+ }
+
+ void _es7243InitAdc() {
+ _es7243I2cBegin();
+ _es7243I2cWrite(0x00, 0x01);
+ _es7243I2cWrite(0x06, 0x00);
+ _es7243I2cWrite(0x05, 0x1B);
+ _es7243I2cWrite(0x01, 0x00); // 0x00 for 24 bit to match INMP441 - not sure if this needs adjustment to get 16bit samples from I2S
+ _es7243I2cWrite(0x08, 0x43);
+ _es7243I2cWrite(0x05, 0x13);
+ }
+
+public:
+
+ ES7243(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
+ I2SSourceWithMasterClock(sampleRate, blockSize, lshift, mask) {
+ _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
+ };
+ void initialize() {
+ // Reserve SDA and SCL pins of the I2C interface
+ if (!pinManager.allocatePin(pin_ES7243_SDA, true, PinOwner::DigitalMic) ||
+ !pinManager.allocatePin(pin_ES7243_SCL, true, PinOwner::DigitalMic)) {
+ return;
+ }
+
+ // First route mclk, then configure ADC over I2C, then configure I2S
+ _es7243InitAdc();
+ I2SSourceWithMasterClock::initialize();
+ }
+
+ void deinitialize() {
+ // Release SDA and SCL pins of the I2C interface
+ pinManager.deallocatePin(pin_ES7243_SDA, PinOwner::DigitalMic);
+ pinManager.deallocatePin(pin_ES7243_SCL, PinOwner::DigitalMic);
+ I2SSourceWithMasterClock::deinitialize();
+ }
+};
+
+/* ADC over I2S Microphone
+ This microphone is an ADC pin sampled via the I2S interval
+ This allows to use the I2S API to obtain ADC samples with high sample rates
+ without the need of manual timing of the samples
+*/
+class I2SAdcSource : public I2SSource {
+public:
+ I2SAdcSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
+ I2SSource(sampleRate, blockSize, lshift, mask){
+ _config = {
+ .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
+ .sample_rate = _sampleRate,
+ .bits_per_sample = I2S_SAMPLE_RESOLUTION,
+ .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
+ .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
+ .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
+ .dma_buf_count = 8,
+ .dma_buf_len = _blockSize
+ };
+ }
+
+ void initialize() {
+
+ if(!pinManager.allocatePin(audioPin, false, PinOwner::AnalogMic)) {
+ return;
+ }
+ // Determine Analog channel. Only Channels on ADC1 are supported
+ int8_t channel = digitalPinToAnalogChannel(audioPin);
+ if (channel > 9) {
+ Serial.printf("Incompatible GPIO used for audio in: %d\n", audioPin);
+ return;
+ } else {
+ adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel));
+ }
+
+ // Install Driver
+ esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr);
+ if (err != ESP_OK) {
+ Serial.printf("Failed to install i2s driver: %d\n", err);
+ return;
+ }
+
+ // Enable I2S mode of ADC
+ err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel));
+ if (err != ESP_OK) {
+ Serial.printf("Failed to set i2s adc mode: %d\n", err);
+ return;
+
+ }
+#if defined(ARDUINO_ARCH_ESP32)
+ // according to docs from espressif, the ADC needs to be started explicitly
+ // fingers crossed
+ err = i2s_adc_enable(I2S_NUM_0);
+ if (err != ESP_OK) {
+ Serial.printf("Failed to enable i2s adc: %d\n", err);
+ //return;
+ }
+#endif
+
+ _initialized = true;
+ }
+
+ void getSamples(double *buffer, uint16_t num_samples) {
+
+ /* Enable ADC. This has to be enabled and disabled directly before and
+ after sampling, otherwise Wifi dies
+ */
+ if (_initialized) {
+#if !defined(ARDUINO_ARCH_ESP32)
+ // old code - works for me without enable/disable, at least on ESP32.
+ esp_err_t err = i2s_adc_enable(I2S_NUM_0);
+ //esp_err_t err = i2s_start(I2S_NUM_0);
+ if (err != ESP_OK) {
+ Serial.printf("Failed to enable i2s adc: %d\n", err);
+ return;
+ }
+#endif
+ I2SSource::getSamples(buffer, num_samples);
+
+#if !defined(ARDUINO_ARCH_ESP32)
+ // old code - works for me without enable/disable, at least on ESP32.
+ err = i2s_adc_disable(I2S_NUM_0);
+ //err = i2s_stop(I2S_NUM_0);
+ if (err != ESP_OK) {
+ Serial.printf("Failed to disable i2s adc: %d\n", err);
+ return;
+ }
+#endif
+ }
+ }
+
+ void deinitialize() {
+ pinManager.deallocatePin(audioPin, PinOwner::AnalogMic);
+ _initialized = false;
+ esp_err_t err;
+#if defined(ARDUINO_ARCH_ESP32)
+ // according to docs from espressif, the ADC needs to be stopped explicitly
+ // fingers crossed
+ err = i2s_adc_disable(I2S_NUM_0);
+ if (err != ESP_OK) {
+ Serial.printf("Failed to disable i2s adc: %d\n", err);
+ //return;
+ }
+#endif
+ err = i2s_driver_uninstall(I2S_NUM_0);
+ if (err != ESP_OK) {
+ Serial.printf("Failed to uninstall i2s driver: %d\n", err);
+ return;
+ }
+ }
+};
+
+/* SPH0645 Microphone
+ This is an I2S microphone with some timing quirks that need
+ special consideration.
+*/
+class SPH0654 : public I2SSource {
+
+public:
+ SPH0654(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
+ I2SSource(sampleRate, blockSize, lshift, mask){}
+
+ void initialize() {
+ I2SSource::initialize();
+ REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9));
+ REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT);
+ }
+};
+
+/* I2S PDM Microphone
+ This is an I2S PDM microphone, these microphones only use a clock and
+ data line, to make it simpler to debug, use the WS pin as CLK and SD
+ pin as DATA
+*/
+
+class I2SPdmSource : public I2SSource {
+
+public:
+ I2SPdmSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
+ I2SSource(sampleRate, blockSize, lshift, mask) {
+
+ _config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm
+
+ _pinConfig = {
+ .bck_io_num = I2S_PIN_NO_CHANGE, // bck is unused in PDM mics
+ .ws_io_num = i2swsPin, // clk pin for PDM mic
+ .data_out_num = I2S_PIN_NO_CHANGE,
+ .data_in_num = i2ssdPin
+ };
+ }
+};
diff --git a/wled00/blynk.cpp b/wled00/blynk.cpp
index ce39044877..b1619d816e 100644
--- a/wled00/blynk.cpp
+++ b/wled00/blynk.cpp
@@ -44,27 +44,27 @@ void updateBlynk()
BLYNK_WRITE(V0)
{
bri = param.asInt();//bri
- colorUpdated(CALL_MODE_BLYNK);
+ stateUpdated(CALL_MODE_BLYNK);
}
BLYNK_WRITE(V1)
{
blHue = param.asInt();//hue
- colorHStoRGB(blHue*10,blSat,(false)? colSec:col);
+ colorHStoRGB(blHue*10,blSat,col);
colorUpdated(CALL_MODE_BLYNK);
}
BLYNK_WRITE(V2)
{
blSat = param.asInt();//sat
- colorHStoRGB(blHue*10,blSat,(false)? colSec:col);
+ colorHStoRGB(blHue*10,blSat,col);
colorUpdated(CALL_MODE_BLYNK);
}
BLYNK_WRITE(V3)
{
bool on = (param.asInt()>0);
- if (!on != !bri) {toggleOnOff(); colorUpdated(CALL_MODE_BLYNK);}
+ if (!on != !bri) {toggleOnOff(); stateUpdated(CALL_MODE_BLYNK);}
}
BLYNK_WRITE(V4)
diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h
index dd1ef9dc7e..7e065f8fa2 100644
--- a/wled00/bus_manager.h
+++ b/wled00/bus_manager.h
@@ -10,6 +10,10 @@
#include "bus_wrapper.h"
#include
+//colors.cpp
+uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
+void colorRGBtoRGBW(byte* rgb);
+
// enable additional debug output
#ifdef WLED_DEBUG
#ifndef ESP8266
@@ -28,13 +32,20 @@
#define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit)))
#define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit))))
+//color mangling macros
+#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
+#define R(c) (byte((c) >> 16))
+#define G(c) (byte((c) >> 8))
+#define B(c) (byte(c))
+#define W(c) (byte((c) >> 24))
+
//temporary struct for passing bus configuration to bus
struct BusConfig {
uint8_t type = TYPE_WS2812_RGB;
- uint16_t count = 1;
- uint16_t start = 0;
- uint8_t colorOrder = COL_ORDER_GRB;
- bool reversed = false;
+ uint16_t count;
+ uint16_t start;
+ uint8_t colorOrder;
+ bool reversed;
uint8_t skipAmount;
bool refreshReq;
uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255};
@@ -62,88 +73,141 @@ struct BusConfig {
}
};
-//parent class of BusDigital and BusPwm
-class Bus {
- public:
- Bus(uint8_t type, uint16_t start) {
- _type = type;
- _start = start;
- };
-
- virtual void show() {}
- virtual bool canShow() { return true; }
-
- virtual void setPixelColor(uint16_t pix, uint32_t c) {};
-
- virtual void setBrightness(uint8_t b) {};
-
- virtual uint32_t getPixelColor(uint16_t pix) { return 0; };
-
- virtual void cleanup() {};
-
- virtual ~Bus() { //throw the bus under the bus
- }
-
- virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
-
- inline uint16_t getStart() {
- return _start;
- }
-
- inline void setStart(uint16_t start) {
- _start = start;
- }
+// Defines an LED Strip and its color ordering.
+struct ColorOrderMapEntry {
+ uint16_t start;
+ uint16_t len;
+ uint8_t colorOrder;
+};
- virtual uint16_t getLength() {
- return 1;
+struct ColorOrderMap {
+ void add(uint16_t start, uint16_t len, uint8_t colorOrder) {
+ if (_count >= WLED_MAX_COLOR_ORDER_MAPPINGS) {
+ return;
+ }
+ if (len == 0) {
+ return;
+ }
+ if (colorOrder > COL_ORDER_MAX) {
+ return;
+ }
+ _mappings[_count].start = start;
+ _mappings[_count].len = len;
+ _mappings[_count].colorOrder = colorOrder;
+ _count++;
}
- virtual void setColorOrder() {}
-
- virtual uint8_t getColorOrder() {
- return COL_ORDER_RGB;
+ uint8_t count() const {
+ return _count;
}
- virtual bool isRgbw() {
- return false;
+ void reset() {
+ _count = 0;
+ memset(_mappings, 0, sizeof(_mappings));
}
- virtual uint8_t skippedLeds() {
- return 0;
+ const ColorOrderMapEntry* get(uint8_t n) const {
+ if (n > _count) {
+ return nullptr;
+ }
+ return &(_mappings[n]);
}
- inline uint8_t getType() {
- return _type;
- }
+ inline uint8_t IRAM_ATTR getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const {
+ if (_count == 0) return defaultColorOrder;
- inline bool isOk() {
- return _valid;
+ for (uint8_t i = 0; i < _count; i++) {
+ if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) {
+ return _mappings[i].colorOrder;
+ }
+ }
+ return defaultColorOrder;
}
- static bool isRgbw(uint8_t type) {
- if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
- if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
- return false;
- }
+ private:
+ uint8_t _count;
+ ColorOrderMapEntry _mappings[WLED_MAX_COLOR_ORDER_MAPPINGS];
+};
- inline bool isOffRefreshRequired() {
- return _needsRefresh;
- }
+//parent class of BusDigital, BusPwm, and BusNetwork
+class Bus {
+ public:
+ Bus(uint8_t type, uint16_t start) {
+ _type = type;
+ _start = start;
+ };
- bool reversed = false;
+ virtual ~Bus() {} //throw the bus under the bus
+
+ virtual void show() {}
+ virtual bool canShow() { return true; }
+ virtual void setStatusPixel(uint32_t c) {}
+ virtual void setPixelColor(uint16_t pix, uint32_t c) {}
+ virtual uint32_t getPixelColor(uint16_t pix) { return 0; }
+ virtual void setBrightness(uint8_t b) {}
+ virtual void cleanup() {}
+ virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
+ virtual uint16_t getLength() { return _len; }
+ virtual void setColorOrder() {}
+ virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
+ virtual uint8_t skippedLeds() { return 0; }
+ inline uint16_t getStart() { return _start; }
+ inline void setStart(uint16_t start) { _start = start; }
+ inline uint8_t getType() { return _type; }
+ inline bool isOk() { return _valid; }
+ inline bool isOffRefreshRequired() { return _needsRefresh; }
+ bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; }
+
+ virtual bool isRgbw() { return Bus::isRgbw(_type); }
+ static bool isRgbw(uint8_t type) {
+ if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
+ if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
+ return false;
+ }
+ static void setCCT(uint16_t cct) {
+ _cct = cct;
+ }
+ static void setCCTBlend(uint8_t b) {
+ if (b > 100) b = 100;
+ _cctBlend = (b * 127) / 100;
+ //compile-time limiter for hardware that can't power both white channels at max
+ #ifdef WLED_MAX_CCT_BLEND
+ if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND;
+ #endif
+ }
+ inline static void setAutoWhiteMode(uint8_t m) { if (m < 4) _autoWhiteMode = m; }
+ inline static uint8_t getAutoWhiteMode() { return _autoWhiteMode; }
+
+ bool reversed = false;
protected:
- uint8_t _type = TYPE_NONE;
- uint8_t _bri = 255;
- uint16_t _start = 0;
- bool _valid = false;
- bool _needsRefresh = false;
+ uint8_t _type = TYPE_NONE;
+ uint8_t _bri = 255;
+ uint16_t _start = 0;
+ uint16_t _len = 1;
+ bool _valid = false;
+ bool _needsRefresh = false;
+ static uint8_t _autoWhiteMode;
+ static int16_t _cct;
+ static uint8_t _cctBlend;
+ uint32_t autoWhiteCalc(uint32_t c) {
+ if (_autoWhiteMode == RGBW_MODE_MANUAL_ONLY) return c;
+ uint8_t w = W(c);
+ //ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0)
+ if (w > 0 && _autoWhiteMode == RGBW_MODE_DUAL) return c;
+ uint8_t r = R(c);
+ uint8_t g = G(c);
+ uint8_t b = B(c);
+ w = r < g ? (r < b ? r : b) : (g < b ? g : b);
+ if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode
+ return RGBW32(r, g, b, w);
+ }
};
class BusDigital : public Bus {
public:
- BusDigital(BusConfig &bc, uint8_t nr) : Bus(bc.type, bc.start) {
+ BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) : Bus(bc.type, bc.start), _colorOrderMap(com) {
if (!IS_DIGITAL(bc.type) || !bc.count) return;
if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return;
_pins[0] = bc.pins[0];
@@ -184,23 +248,34 @@ class BusDigital : public Bus {
PolyBus::setBrightness(_busPtr, _iType, b);
}
+ //If LEDs are skipped, it is possible to use the first as a status LED.
+ //TODO only show if no new show due in the next 50ms
+ void setStatusPixel(uint32_t c) {
+ if (_skip && canShow()) {
+ PolyBus::setPixelColor(_busPtr, _iType, 0, c, _colorOrderMap.getPixelColorOrder(_start, _colorOrder));
+ PolyBus::show(_busPtr, _iType);
+ }
+ }
+
void setPixelColor(uint16_t pix, uint32_t c) {
+ if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814) c = autoWhiteCalc(c);
+ if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
if (reversed) pix = _len - pix -1;
else pix += _skip;
- PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder);
+ PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder));
}
uint32_t getPixelColor(uint16_t pix) {
if (reversed) pix = _len - pix -1;
else pix += _skip;
- return PolyBus::getPixelColor(_busPtr, _iType, pix, _colorOrder);
+ return PolyBus::getPixelColor(_busPtr, _iType, pix, _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder));
}
inline uint8_t getColorOrder() {
return _colorOrder;
}
- inline uint16_t getLength() {
+ uint16_t getLength() {
return _len - _skip;
}
@@ -215,10 +290,6 @@ class BusDigital : public Bus {
_colorOrder = colorOrder;
}
- inline bool isRgbw() {
- return Bus::isRgbw(_type);
- }
-
inline uint8_t skippedLeds() {
return _skip;
}
@@ -245,9 +316,9 @@ class BusDigital : public Bus {
uint8_t _colorOrder = COL_ORDER_GRB;
uint8_t _pins[2] = {255, 255};
uint8_t _iType = I_NONE;
- uint16_t _len = 0;
uint8_t _skip = 0;
void * _busPtr = nullptr;
+ const ColorOrderMap &_colorOrderMap;
};
@@ -273,7 +344,7 @@ class BusPwm : public Bus {
if (!pinManager.allocatePin(currentPin, true, PinOwner::BusPwm)) {
deallocatePins(); return;
}
- _pins[i] = currentPin; // store only after allocatePin() succeeds
+ _pins[i] = currentPin; //store only after allocatePin() succeeds
#ifdef ESP8266
pinMode(_pins[i], OUTPUT);
#else
@@ -287,29 +358,62 @@ class BusPwm : public Bus {
void setPixelColor(uint16_t pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel
- uint8_t r = c >> 16;
- uint8_t g = c >> 8;
- uint8_t b = c ;
- uint8_t w = c >> 24;
+ if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c);
+ if (_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
+ c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
+ }
+ uint8_t r = R(c);
+ uint8_t g = G(c);
+ uint8_t b = B(c);
+ uint8_t w = W(c);
+ uint8_t cct = 0; //0 - full warm white, 255 - full cold white
+ if (_cct > -1) {
+ if (_cct >= 1900) cct = (_cct - 1900) >> 5;
+ else if (_cct < 256) cct = _cct;
+ } else {
+ cct = (approximateKelvinFromRGB(c) - 1900) >> 5;
+ }
- switch (_type) {
- case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value
- _data[0] = max(r, max(g, max(b, w))); break;
+ uint8_t ww, cw;
+ #ifdef WLED_USE_IC_CCT
+ ww = w;
+ cw = cct;
+ #else
+ //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
+ if (cct < _cctBlend) ww = 255;
+ else ww = ((255-cct) * 255) / (255 - _cctBlend);
- case TYPE_ANALOG_2CH: //warm white + cold white, we'll need some nice handling here, for now just R+G channels
- case TYPE_ANALOG_3CH: //standard dumb RGB
- case TYPE_ANALOG_4CH: //RGBW
- case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB
- _data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = 0; break;
+ if ((255-cct) < _cctBlend) cw = 255;
+ else cw = (cct * 255) / (255 - _cctBlend);
+
+ ww = (w * ww) / 255; //brightness scaling
+ cw = (w * cw) / 255;
+ #endif
- default: return;
+ switch (_type) {
+ case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
+ _data[0] = w;
+ break;
+ case TYPE_ANALOG_2CH: //warm white + cold white
+ _data[1] = cw;
+ _data[0] = ww;
+ break;
+ case TYPE_ANALOG_5CH: //RGB + warm white + cold white
+ // perhaps a non-linear adjustment would be in order. need to test
+ _data[4] = cw;
+ w = ww;
+ case TYPE_ANALOG_4CH: //RGBW
+ _data[3] = w;
+ case TYPE_ANALOG_3CH: //standard dumb RGB
+ _data[0] = r; _data[1] = g; _data[2] = b;
+ break;
}
}
//does no index check
uint32_t getPixelColor(uint16_t pix) {
if (!_valid) return 0;
- return ((_data[3] << 24) | (_data[0] << 16) | (_data[1] << 8) | (_data[2]));
+ return RGBW32(_data[0], _data[1], _data[2], _data[3]);
}
void show() {
@@ -333,14 +437,12 @@ class BusPwm : public Bus {
uint8_t getPins(uint8_t* pinArray) {
if (!_valid) return 0;
uint8_t numPins = NUM_PWM_PINS(_type);
- for (uint8_t i = 0; i < numPins; i++) pinArray[i] = _pins[i];
+ for (uint8_t i = 0; i < numPins; i++) {
+ pinArray[i] = _pins[i];
+ }
return numPins;
}
- bool isRgbw() {
- return Bus::isRgbw(_type);
- }
-
inline void cleanup() {
deallocatePins();
}
@@ -351,7 +453,7 @@ class BusPwm : public Bus {
private:
uint8_t _pins[5] = {255, 255, 255, 255, 255};
- uint8_t _data[5] = {255, 255, 255, 255, 255};
+ uint8_t _data[5] = {0};
#ifdef ARDUINO_ARCH_ESP32
uint8_t _ledcStart = 255;
#endif
@@ -397,12 +499,10 @@ class BusNetwork : public Bus {
// break;
// }
_UDPchannels = _rgbw ? 4 : 3;
- //_rgbw |= bc.rgbwOverride; // RGBW override in bit 7 or can have a special type
_data = (byte *)malloc(bc.count * _UDPchannels);
if (_data == nullptr) return;
memset(_data, 0, bc.count * _UDPchannels);
_len = bc.count;
- //_colorOrder = bc.colorOrder;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
_broadcastLock = false;
_valid = true;
@@ -410,22 +510,19 @@ class BusNetwork : public Bus {
void setPixelColor(uint16_t pix, uint32_t c) {
if (!_valid || pix >= _len) return;
+ if (isRgbw()) c = autoWhiteCalc(c);
+ if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
uint16_t offset = pix * _UDPchannels;
- _data[offset] = 0xFF & (c >> 16);
- _data[offset+1] = 0xFF & (c >> 8);
- _data[offset+2] = 0xFF & (c );
- if (_rgbw) _data[offset+3] = 0xFF & (c >> 24);
+ _data[offset] = R(c);
+ _data[offset+1] = G(c);
+ _data[offset+2] = B(c);
+ if (_rgbw) _data[offset+3] = W(c);
}
uint32_t getPixelColor(uint16_t pix) {
if (!_valid || pix >= _len) return 0;
uint16_t offset = pix * _UDPchannels;
- return (
- (_rgbw ? (_data[offset+3] << 24) : 0)
- | (_data[offset] << 16)
- | (_data[offset+1] << 8)
- | (_data[offset+2] )
- );
+ return RGBW32(_data[offset], _data[offset+1], _data[offset+2], _rgbw ? (_data[offset+3] << 24) : 0);
}
void show() {
@@ -472,8 +569,6 @@ class BusNetwork : public Bus {
private:
IPAddress _client;
- uint16_t _len = 0;
- //uint8_t _colorOrder;
uint8_t _bri = 255;
uint8_t _UDPtype;
uint8_t _UDPchannels;
@@ -516,7 +611,7 @@ class BusManager {
if (bc.type >= TYPE_NET_DDP_RGB && bc.type < 96) {
busses[numBusses] = new BusNetwork(bc);
} else if (IS_DIGITAL(bc.type)) {
- busses[numBusses] = new BusDigital(bc, numBusses);
+ busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap);
} else {
busses[numBusses] = new BusPwm(bc);
}
@@ -538,7 +633,13 @@ class BusManager {
}
}
- void setPixelColor(uint16_t pix, uint32_t c) {
+ void setStatusPixel(uint32_t c) {
+ for (uint8_t i = 0; i < numBusses; i++) {
+ busses[i]->setStatusPixel(c);
+ }
+ }
+
+ void IRAM_ATTR setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1) {
for (uint8_t i = 0; i < numBusses; i++) {
Bus* b = busses[i];
uint16_t bstart = b->getStart();
@@ -553,6 +654,15 @@ class BusManager {
}
}
+ void setSegmentCCT(int16_t cct, bool allowWBCorrection = false) {
+ if (cct > 255) cct = 255;
+ if (cct >= 0) {
+ //if white balance correction allowed, save as kelvin value instead of 0-255
+ if (allowWBCorrection) cct = 1900 + (cct << 5);
+ } else cct = -1;
+ Bus::setCCT(cct);
+ }
+
uint32_t getPixelColor(uint16_t pix) {
for (uint8_t i = 0; i < numBusses; i++) {
Bus* b = busses[i];
@@ -579,14 +689,24 @@ class BusManager {
return numBusses;
}
+ //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
uint16_t getTotalLength() {
uint16_t len = 0;
- for (uint8_t i=0; igetLength();
+ for (uint8_t i=0; igetLength();
return len;
}
+ void updateColorOrderMap(const ColorOrderMap &com) {
+ memcpy(&colorOrderMap, &com, sizeof(ColorOrderMap));
+ }
+
+ const ColorOrderMap& getColorOrderMap() const {
+ return colorOrderMap;
+ }
+
private:
uint8_t numBusses = 0;
Bus* busses[WLED_MAX_BUSSES];
+ ColorOrderMap colorOrderMap;
};
#endif
diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h
index a762143f07..95549cb08a 100644
--- a/wled00/bus_wrapper.h
+++ b/wled00/bus_wrapper.h
@@ -98,26 +98,34 @@
#ifdef ARDUINO_ARCH_ESP32
//RGB
#define B_32_RN_NEO_3 NeoPixelBrightnessBus
+#ifndef CONFIG_IDF_TARGET_ESP32C3
#define B_32_I0_NEO_3 NeoPixelBrightnessBus
-#ifndef CONFIG_IDF_TARGET_ESP32S2
+#endif
+#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#define B_32_I1_NEO_3 NeoPixelBrightnessBus
#endif
//RGBW
#define B_32_RN_NEO_4 NeoPixelBrightnessBus
+#ifndef CONFIG_IDF_TARGET_ESP32C3
#define B_32_I0_NEO_4 NeoPixelBrightnessBus
-#ifndef CONFIG_IDF_TARGET_ESP32S2
+#endif
+#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#define B_32_I1_NEO_4 NeoPixelBrightnessBus
#endif
//400Kbps
#define B_32_RN_400_3 NeoPixelBrightnessBus
+#ifndef CONFIG_IDF_TARGET_ESP32C3
#define B_32_I0_400_3 NeoPixelBrightnessBus
-#ifndef CONFIG_IDF_TARGET_ESP32S2
+#endif
+#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#define B_32_I1_400_3 NeoPixelBrightnessBus
#endif
//TM1814 (RGBW)
#define B_32_RN_TM1_4 NeoPixelBrightnessBus
+#ifndef CONFIG_IDF_TARGET_ESP32C3
#define B_32_I0_TM1_4 NeoPixelBrightnessBus
-#ifndef CONFIG_IDF_TARGET_ESP32S2
+#endif
+#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#define B_32_I1_TM1_4 NeoPixelBrightnessBus
#endif
//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S)
@@ -125,7 +133,7 @@
#endif
//APA102
-#define B_HS_DOT_3 NeoPixelBrightnessBus //hardware SPI
+#define B_HS_DOT_3 NeoPixelBrightnessBus //hardware SPI
#define B_SS_DOT_3 NeoPixelBrightnessBus //soft SPI
//LPD8806
@@ -181,23 +189,31 @@ class PolyBus {
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: (static_cast(busPtr))->Begin(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_3: (static_cast(busPtr))->Begin(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_3: (static_cast(busPtr))->Begin(); break;
#endif
case I_32_RN_NEO_4: (static_cast(busPtr))->Begin(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_4: (static_cast(busPtr))->Begin(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_4: (static_cast(busPtr))->Begin(); break;
#endif
case I_32_RN_400_3: (static_cast(busPtr))->Begin(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_400_3: (static_cast(busPtr))->Begin(); break;
#endif
case I_32_RN_TM1_4: beginTM1814(busPtr); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_TM1_4: beginTM1814(busPtr); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_TM1_4: beginTM1814(busPtr); break;
#endif
// ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin()
@@ -236,23 +252,31 @@ class PolyBus {
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_3: busPtr = new B_32_I0_NEO_3(len, pins[0]); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_3: busPtr = new B_32_I1_NEO_3(len, pins[0]); break;
#endif
case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)channel); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_4: busPtr = new B_32_I1_NEO_4(len, pins[0]); break;
#endif
case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)channel); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_400_3: busPtr = new B_32_I1_400_3(len, pins[0]); break;
#endif
case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)channel); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_TM1_4: busPtr = new B_32_I0_TM1_4(len, pins[0]); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_TM1_4: busPtr = new B_32_I1_TM1_4(len, pins[0]); break;
#endif
#endif
@@ -292,23 +316,31 @@ class PolyBus {
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: (static_cast(busPtr))->Show(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_3: (static_cast(busPtr))->Show(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_3: (static_cast(busPtr))->Show(); break;
#endif
case I_32_RN_NEO_4: (static_cast(busPtr))->Show(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_4: (static_cast(busPtr))->Show(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_4: (static_cast(busPtr))->Show(); break;
#endif
case I_32_RN_400_3: (static_cast(busPtr))->Show(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_400_3: (static_cast(busPtr))->Show(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_400_3: (static_cast(busPtr))->Show(); break;
#endif
case I_32_RN_TM1_4: (static_cast(busPtr))->Show(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_TM1_4: (static_cast(busPtr))->Show(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_TM1_4: (static_cast(busPtr))->Show(); break;
#endif
#endif
@@ -345,23 +377,31 @@ class PolyBus {
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_3: return (static_cast(busPtr))->CanShow(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_3: return (static_cast(busPtr))->CanShow(); break;
#endif
case I_32_RN_NEO_4: return (static_cast(busPtr))->CanShow(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_4: return (static_cast(busPtr))->CanShow(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_4: return (static_cast(busPtr))->CanShow(); break;
#endif
case I_32_RN_400_3: return (static_cast(busPtr))->CanShow(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_400_3: return (static_cast(busPtr))->CanShow(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_400_3: return (static_cast(busPtr))->CanShow(); break;
#endif
case I_32_RN_TM1_4: return (static_cast(busPtr))->CanShow(); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_TM1_4: return (static_cast(busPtr))->CanShow(); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_TM1_4: return (static_cast(busPtr))->CanShow(); break;
#endif
#endif
@@ -422,23 +462,31 @@ class PolyBus {
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
#endif
case I_32_RN_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break;
#endif
case I_32_RN_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
#endif
case I_32_RN_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break;
#endif
#endif
@@ -475,23 +523,31 @@ class PolyBus {
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: (static_cast(busPtr))->SetBrightness(b); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_3: (static_cast(busPtr))->SetBrightness(b); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_3: (static_cast(busPtr))->SetBrightness(b); break;
#endif
case I_32_RN_NEO_4: (static_cast(busPtr))->SetBrightness(b); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_4: (static_cast(busPtr))->SetBrightness(b); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_4: (static_cast(busPtr))->SetBrightness(b); break;
#endif
case I_32_RN_400_3: (static_cast(busPtr))->SetBrightness(b); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_400_3: (static_cast(busPtr))->SetBrightness(b); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_400_3: (static_cast(busPtr))->SetBrightness(b); break;
#endif
case I_32_RN_TM1_4: (static_cast(busPtr))->SetBrightness(b); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_TM1_4: (static_cast(busPtr))->SetBrightness(b); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_TM1_4: (static_cast(busPtr))->SetBrightness(b); break;
#endif
#endif
@@ -529,23 +585,31 @@ class PolyBus {
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break;
#endif
case I_32_RN_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break;
#endif
case I_32_RN_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break;
#endif
case I_32_RN_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break;
#endif
#endif
@@ -600,23 +664,31 @@ class PolyBus {
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_RN_NEO_3: delete (static_cast(busPtr)); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_3: delete (static_cast(busPtr)); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_3: delete (static_cast(busPtr)); break;
#endif
case I_32_RN_NEO_4: delete (static_cast(busPtr)); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_NEO_4: delete (static_cast(busPtr)); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_NEO_4: delete (static_cast(busPtr)); break;
#endif
case I_32_RN_400_3: delete (static_cast(busPtr)); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_400_3: delete (static_cast(busPtr)); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_400_3: delete (static_cast(busPtr)); break;
#endif
case I_32_RN_TM1_4: delete (static_cast(busPtr)); break;
+ #ifndef CONFIG_IDF_TARGET_ESP32C3
case I_32_I0_TM1_4: delete (static_cast(busPtr)); break;
- #ifndef CONFIG_IDF_TARGET_ESP32S2
+ #endif
+ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
case I_32_I1_TM1_4: delete (static_cast(busPtr)); break;
#endif
#endif
@@ -692,4 +764,4 @@ class PolyBus {
}
};
-#endif
\ No newline at end of file
+#endif
diff --git a/wled00/button.cpp b/wled00/button.cpp
index ba6a0c9734..31d3022c89 100644
--- a/wled00/button.cpp
+++ b/wled00/button.cpp
@@ -4,18 +4,24 @@
* Physical IO
*/
-#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
+#define WLED_DEBOUNCE_THRESHOLD 50 // only consider button input of at least 50ms as valid (debouncing)
+#define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms
+#define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press
+#define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0
+#define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP
+#define WLED_LONG_FACTORY_RESET 10000 // how long button 0 needs to be held to trigger a factory reset
static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage
void shortPressAction(uint8_t b)
{
- if (!macroButton[b])
- {
- toggleOnOff();
- colorUpdated(CALL_MODE_BUTTON);
+ if (!macroButton[b]) {
+ switch (b) {
+ case 0: toggleOnOff(); stateUpdated(CALL_MODE_BUTTON); break;
+ case 1: ++effectCurrent %= strip.getModeCount(); colorUpdated(CALL_MODE_BUTTON); break;
+ }
} else {
- applyPreset(macroButton[b], CALL_MODE_BUTTON);
+ applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
}
// publish MQTT message
@@ -26,24 +32,64 @@ void shortPressAction(uint8_t b)
}
}
+void longPressAction(uint8_t b)
+{
+ if (!macroLongPress[b]) {
+ switch (b) {
+ case 0: setRandomColor(col); colorUpdated(CALL_MODE_BUTTON); break;
+ case 1: bri += 8; stateUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action
+ }
+ } else {
+ applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
+ }
+
+ // publish MQTT message
+ if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
+ char subuf[64];
+ sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
+ mqtt->publish(subuf, 0, false, "long");
+ }
+}
+
+void doublePressAction(uint8_t b)
+{
+ if (!macroDoublePress[b]) {
+ switch (b) {
+ //case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set
+ case 1: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
+ }
+ } else {
+ applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET);
+ }
+
+ // publish MQTT message
+ if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
+ char subuf[64];
+ sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
+ mqtt->publish(subuf, 0, false, "double");
+ }
+}
+
bool isButtonPressed(uint8_t i)
{
if (btnPin[i]<0) return false;
+ uint8_t pin = btnPin[i];
+
switch (buttonType[i]) {
case BTN_TYPE_NONE:
case BTN_TYPE_RESERVED:
break;
case BTN_TYPE_PUSH:
case BTN_TYPE_SWITCH:
- if (digitalRead(btnPin[i]) == LOW) return true;
+ if (digitalRead(pin) == LOW) return true;
break;
case BTN_TYPE_PUSH_ACT_HIGH:
case BTN_TYPE_PIR_SENSOR:
- if (digitalRead(btnPin[i]) == HIGH) return true;
+ if (digitalRead(pin) == HIGH) return true;
break;
case BTN_TYPE_TOUCH:
- #ifdef ARDUINO_ARCH_ESP32
- if (touchRead(btnPin[i]) <= touchThreshold) return true;
+ #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
+ if (touchRead(pin) <= touchThreshold) return true;
#endif
break;
}
@@ -59,18 +105,18 @@ void handleSwitch(uint8_t b)
}
if (buttonLongPressed[b] == buttonPressedBefore[b]) return;
-
+
if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
if (!buttonPressedBefore[b]) { // on -> off
- if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON);
+ if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
else { //turn on
- if (!bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);}
- }
+ if (!bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);}
+ }
} else { // off -> on
- if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON);
+ if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
else { //turn off
- if (bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);}
- }
+ if (bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);}
+ }
}
// publish MQTT message
@@ -116,36 +162,17 @@ void handleAnalog(uint8_t b)
} else if (macroDoublePress[b] == 249) {
// effect speed
effectSpeed = aRead;
- effectChanged = true;
- for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
- WS2812FX::Segment& seg = strip.getSegment(i);
- if (!seg.isSelected()) continue;
- seg.speed = effectSpeed;
- }
} else if (macroDoublePress[b] == 248) {
// effect intensity
effectIntensity = aRead;
- effectChanged = true;
- for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
- WS2812FX::Segment& seg = strip.getSegment(i);
- if (!seg.isSelected()) continue;
- seg.intensity = effectIntensity;
- }
} else if (macroDoublePress[b] == 247) {
// selected palette
effectPalette = map(aRead, 0, 252, 0, strip.getPaletteCount()-1);
- effectChanged = true;
- for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
- WS2812FX::Segment& seg = strip.getSegment(i);
- if (!seg.isSelected()) continue;
- seg.palette = effectPalette;
- }
} else if (macroDoublePress[b] == 200) {
// primary color, hue, full saturation
colorHStoRGB(aRead*256,255,col);
} else {
// otherwise use "double press" for segment selection
- //uint8_t mainSeg = strip.getMainSegmentId();
WS2812FX::Segment& seg = strip.getSegment(macroDoublePress[b]);
if (aRead == 0) {
seg.setOption(SEG_OPTION_ON, 0); // off
@@ -167,6 +194,7 @@ void handleAnalog(uint8_t b)
void handleButton()
{
static unsigned long lastRead = 0UL;
+ bool analog = false;
for (uint8_t b=0; b 250) { // button is not a button but a potentiometer
- if (b+1 == WLED_MAX_BUTTONS) lastRead = millis();
+ analog = true;
handleAnalog(b); continue;
}
@@ -186,71 +216,63 @@ void handleButton()
}
//momentary button logic
- if (isButtonPressed(b)) //pressed
- {
+ if (isButtonPressed(b)) { //pressed
+
if (!buttonPressedBefore[b]) buttonPressedTime[b] = millis();
buttonPressedBefore[b] = true;
- if (millis() - buttonPressedTime[b] > 600) //long press
- {
- if (!buttonLongPressed[b])
- {
- if (macroLongPress[b]) {applyPreset(macroLongPress[b], CALL_MODE_BUTTON);}
- else _setRandomColor(false,true);
-
- // publish MQTT message
- if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
- char subuf[64];
- sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
- mqtt->publish(subuf, 0, false, "long");
- }
-
- buttonLongPressed[b] = true;
+ if (millis() - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press
+ if (!buttonLongPressed[b]) longPressAction(b);
+ else if (b) { //repeatable action (~3 times per s) on button > 0
+ longPressAction(b);
+ buttonPressedTime[b] = millis() - WLED_LONG_REPEATED_ACTION; //300ms
}
+ buttonLongPressed[b] = true;
}
- }
- else if (!isButtonPressed(b) && buttonPressedBefore[b]) //released
- {
+
+ } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
+
long dur = millis() - buttonPressedTime[b];
if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce
- bool doublePress = buttonWaitTime[b];
+ bool doublePress = buttonWaitTime[b]; //did we have a short press before?
buttonWaitTime[b] = 0;
- if (dur > 6000 && b==0) //long press on button 0
- {
- WLED::instance().initAP(true);
- }
- else if (!buttonLongPressed[b]) { //short press
- if (macroDoublePress[b])
- {
+ if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released)
+ if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds
+ WLED_FS.format();
+ clearEEPROM();
+ doReboot = true;
+ } else {
+ WLED::instance().initAP(true);
+ }
+ } else if (!buttonLongPressed[b]) { //short press
+ if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set
+ shortPressAction(b);
+ } else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0)
if (doublePress) {
- applyPreset(macroDoublePress[b], CALL_MODE_BUTTON);
-
- // publish MQTT message
- if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
- char subuf[64];
- sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
- mqtt->publish(subuf, 0, false, "double");
- }
- } else buttonWaitTime[b] = millis();
- } else shortPressAction(b);
+ doublePressAction(b);
+ } else {
+ buttonWaitTime[b] = millis();
+ }
+ }
}
buttonPressedBefore[b] = false;
buttonLongPressed[b] = false;
}
- if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > 450 && !buttonPressedBefore[b])
- {
+ //if 350ms elapsed since last short press release it is a short press
+ if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) {
buttonWaitTime[b] = 0;
shortPressAction(b);
}
}
+ if (analog) lastRead = millis();
}
void handleIO()
{
handleButton();
-
+
//set relay when LEDs turn on
if (strip.getBrightness())
{
@@ -269,8 +291,11 @@ void handleIO()
#ifdef ESP8266
// turn off built-in LED if strip is turned off
// this will break digital bus so will need to be reinitialised on On
- pinMode(LED_BUILTIN, OUTPUT);
- digitalWrite(LED_BUILTIN, HIGH);
+ PinOwner ledPinOwner = pinManager.getPinOwner(LED_BUILTIN);
+ if (!strip.isOffRefreshRequired() && (ledPinOwner == PinOwner::None || ledPinOwner == PinOwner::BusDigital)) {
+ pinMode(LED_BUILTIN, OUTPUT);
+ digitalWrite(LED_BUILTIN, HIGH);
+ }
#endif
if (rlyPin>=0) {
pinMode(rlyPin, OUTPUT);
@@ -279,4 +304,4 @@ void handleIO()
}
offMode = true;
}
-}
+}
\ No newline at end of file
diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp
index b7764d39f6..a54152dcb5 100644
--- a/wled00/cfg.cpp
+++ b/wled00/cfg.cpp
@@ -14,6 +14,7 @@ void getStringFromJson(char* dest, const char* src, size_t len) {
}
bool deserializeConfig(JsonObject doc, bool fromFS) {
+ bool needsSave = false;
//int rev_major = doc["rev"][0]; // 1
//int rev_minor = doc["rev"][1]; // 0
@@ -63,7 +64,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (apHide > 1) apHide = 1;
CJSON(apBehavior, ap[F("behav")]);
-
/*
JsonArray ap_ip = ap["ip"];
for (byte i = 0; i < 4; i++) {
@@ -79,14 +79,18 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject hw = doc[F("hw")];
// initialize LED pins and lengths prior to other HW (except for ethernet)
- JsonObject hw_led = hw[F("led")];
-
- CJSON(ledCount, hw_led[F("total")]);
- if (ledCount > MAX_LEDS) ledCount = MAX_LEDS;
+ JsonObject hw_led = hw["led"];
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
- CJSON(strip.rgbwMode, hw_led[F("rgbwm")]);
+ CJSON(strip.autoWhiteMode, hw_led[F("rgbwm")]);
+ Bus::setAutoWhiteMode(strip.autoWhiteMode);
+ strip.fixInvalidSegments(); // refreshes segment light capabilities (in case auto white mode changed)
+ CJSON(correctWB, hw_led["cct"]);
+ CJSON(cctFromRgb, hw_led[F("cr")]);
+ CJSON(strip.cctBlending, hw_led[F("cb")]);
+ Bus::setCCTBlend(strip.cctBlending);
+ strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
// 2D Matrix Settings
CJSON(strip.stripOrMatrixPanel, hw_led[F("somp")]);
@@ -108,11 +112,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonArray ins = hw_led["ins"];
- uint16_t lC = 0;
-
if (fromFS || !ins.isNull()) {
uint8_t s = 0; // bus iterator
- busses.removeAll();
+ if (fromFS) busses.removeAll(); // can't safely manipulate busses directly in network callback
uint32_t mem = 0;
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES) break;
@@ -126,7 +128,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (i>4) break;
}
- uint16_t length = elm[F("len")] | 1;
+ uint16_t length = elm["len"] | 1;
uint8_t colorOrder = (int)elm[F("order")];
uint8_t skipFirst = elm[F("skip")];
uint16_t start = elm["start"] | 0;
@@ -134,19 +136,38 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t ledType = elm["type"] | TYPE_WS2812_RGB;
bool reversed = elm["rev"];
bool refresh = elm["ref"] | false;
- ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
+ ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
s++;
- uint16_t busEnd = start + length;
- if (busEnd > lC) lC = busEnd;
- BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
- mem += BusManager::memUsage(bc);
- if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip()
+ if (fromFS) {
+ BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
+ mem += BusManager::memUsage(bc);
+ if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip()
+ } else {
+ if (busConfigs[s] != nullptr) delete busConfigs[s];
+ busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
+ doInitBusses = true;
+ }
}
// finalization done in beginStrip()
}
- if (lC > ledCount) ledCount = lC; // fix incorrect total length (honour analog setup)
if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus
+ // read color order map configuration
+ JsonArray hw_com = hw[F("com")];
+ if (!hw_com.isNull()) {
+ ColorOrderMap com = {};
+ uint8_t s = 0;
+ for (JsonObject entry : hw_com) {
+ if (s > WLED_MAX_COLOR_ORDER_MAPPINGS) break;
+ uint16_t start = entry["start"] | 0;
+ uint16_t len = entry["len"] | 0;
+ uint8_t colorOrder = (int)entry[F("order")];
+ com.add(start, len, colorOrder);
+ s++;
+ }
+ busses.updateColorOrderMap(com);
+ }
+
// read multiple button configuration
JsonObject btn_obj = hw["btn"];
JsonArray hw_btn_ins = btn_obj[F("ins")];
@@ -204,6 +225,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
}
CJSON(irEnabled, hw["ir"]["type"]);
+ CJSON(irApplyToAllSelected, hw["ir"]["sel"]);
JsonObject relay = hw[F("relay")];
int hw_relay_pin = relay["pin"] | -2;
@@ -219,41 +241,48 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
rlyMde = !relay["rev"];
}
+ CJSON(serialBaud, hw[F("baud")]);
+ if (serialBaud < 96 || serialBaud > 15000) serialBaud = 1152;
+ updateBaudRate(serialBaud *100);
+
// Sound Reactive Pin Config
JsonObject analogmic = hw[F("analogmic")]; // analog mic JsonObject
- int hw_amic_pin = analogmic[F("pin")];
- if (pinManager.allocatePin(hw_amic_pin,false)) {
- audioPin = hw_amic_pin;
- } else {
- audioPin = audioPin;
- }
+ // int hw_amic_pin = analogmic[F("pin")];
+ CJSON(audioPin, analogmic[F("pin")]);
+ // if (pinManager.allocatePin(hw_amic_pin,false, PinOwner::AnalogMic)) {
+ // audioPin = hw_amic_pin;
+ // } else {
+ // audioPin = audioPin;
+ // }
JsonObject hw_dmic = hw[F("digitalmic")]; // digital mic JsonObject
- CJSON(dmEnabled, hw_dmic["en"]);
+ CJSON(dmType, hw_dmic["en"]);
JsonObject hw_dmic_pins = hw_dmic["pins"]; // digital mic pins JsonObject
- int hw_i2ssd_pin = hw_dmic_pins[F("i2ssd")];
- if (pinManager.allocatePin(hw_i2ssd_pin,false)) {
- i2ssdPin = hw_i2ssd_pin;
- } else {
- i2ssdPin = i2ssdPin;
- }
-
- int hw_i2sws_pin = hw_dmic_pins[F("i2sws")];
- if (pinManager.allocatePin(hw_i2sws_pin,false)) {
- i2swsPin = hw_i2sws_pin;
- } else {
- i2swsPin = i2swsPin;
- }
-
- int hw_i2sck_pin = hw_dmic_pins[F("i2sck")];
- if (pinManager.allocatePin(hw_i2sck_pin,false)) {
- i2sckPin = hw_i2sck_pin;
- } else {
- i2sckPin = i2sckPin;
- }
+ CJSON(i2ssdPin, hw_dmic_pins[F("i2ssd")]);
+ // int hw_i2ssd_pin = hw_dmic_pins[F("i2ssd")];
+ // if (pinManager.allocatePin(hw_i2ssd_pin,false, PinOwner::DigitalMic)) {
+ // i2ssdPin = hw_i2ssd_pin;
+ // } else {
+ // i2ssdPin = i2ssdPin;
+ // }
+ CJSON(i2swsPin, hw_dmic_pins[F("i2sws")]);
+ // int hw_i2sws_pin = hw_dmic_pins[F("i2sws")];
+ // if (pinManager.allocatePin(hw_i2sws_pin,false, PinOwner::DigitalMic)) {
+ // i2swsPin = hw_i2sws_pin;
+ // } else {
+ // i2swsPin = i2swsPin;
+ // }
+
+ CJSON(i2sckPin, hw_dmic_pins[F("i2sck")]);
+ // int hw_i2sck_pin = hw_dmic_pins[F("i2sck")];
+ // if (pinManager.allocatePin(hw_i2sck_pin,false, PinOwner::DigitalMic)) {
+ // i2sckPin = hw_i2sck_pin;
+ // } else {
+ // i2sckPin = i2sckPin;
+ // }
//int hw_status_pin = hw[F("status")]["pin"]; // -1
@@ -269,30 +298,30 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (light_gc_col > 1.5) strip.gammaCorrectCol = true;
else if (light_gc_col > 0.5) strip.gammaCorrectCol = false;
- JsonObject light_tr = light[F("tr")];
- CJSON(fadeTransition, light_tr[F("mode")]);
+ JsonObject light_tr = light["tr"];
+ CJSON(fadeTransition, light_tr["mode"]);
int tdd = light_tr["dur"] | -1;
if (tdd >= 0) transitionDelayDefault = tdd * 100;
CJSON(strip.paletteFade, light_tr["pal"]);
JsonObject light_nl = light["nl"];
- CJSON(nightlightMode, light_nl[F("mode")]);
+ CJSON(nightlightMode, light_nl["mode"]);
byte prev = nightlightDelayMinsDefault;
- CJSON(nightlightDelayMinsDefault, light_nl[F("dur")]);
+ CJSON(nightlightDelayMinsDefault, light_nl["dur"]);
if (nightlightDelayMinsDefault != prev) nightlightDelayMins = nightlightDelayMinsDefault;
CJSON(nightlightTargetBri, light_nl[F("tbri")]);
CJSON(macroNl, light_nl["macro"]);
- JsonObject def = doc[F("def")];
- CJSON(bootPreset, def[F("ps")]);
+ JsonObject def = doc["def"];
+ CJSON(bootPreset, def["ps"]);
CJSON(turnOnAtBoot, def["on"]); // true
CJSON(briS, def["bri"]); // 128
// if == interfaces
JsonObject interfaces = doc["if"];
- JsonObject if_sync = interfaces[F("sync")];
+ JsonObject if_sync = interfaces["sync"];
CJSON(udpPort, if_sync[F("port0")]); // 21324
CJSON(udpPort2, if_sync[F("port1")]); // 65506
@@ -301,8 +330,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(receiveNotificationColor, if_sync_recv["col"]);
CJSON(receiveNotificationEffects, if_sync_recv["fx"]);
CJSON(receiveGroups, if_sync_recv["grp"]);
+ CJSON(receiveSegmentOptions, if_sync_recv["seg"]);
+ CJSON(receiveSegmentBounds, if_sync_recv["sb"]);
//! following line might be a problem if called after boot
- receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects);
+ receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects || receiveSegmentOptions);
JsonObject if_sync_send = if_sync["send"];
prev = notifyDirectDefault;
@@ -329,7 +360,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(e131Universe, if_live_dmx[F("uni")]);
CJSON(e131SkipOutOfSequence, if_live_dmx[F("seqskip")]);
CJSON(DMXAddress, if_live_dmx[F("addr")]);
- CJSON(DMXMode, if_live_dmx[F("mode")]);
+ CJSON(DMXMode, if_live_dmx["mode"]);
tdd = if_live[F("timeout")] | -1;
if (tdd >= 0) realtimeTimeoutMs = tdd * 100;
@@ -395,10 +426,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
// ol == overlay
JsonObject ol = doc[F("ol")];
- prev = overlayDefault;
- CJSON(overlayDefault ,ol[F("clock")]); // 0
+ CJSON(overlayCurrent ,ol[F("clock")]); // 0
CJSON(countdownMode, ol[F("cntdwn")]);
- if (prev != overlayDefault) overlayCurrent = overlayDefault;
CJSON(overlayMin, ol["min"]);
CJSON(overlayMax, ol[F("max")]);
@@ -428,7 +457,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(timerMinutes[it], timer["min"]);
CJSON(timerMacro[it], timer["macro"]);
- byte dowPrev = timerWeekday[it];
+ byte dowPrev = timerWeekday[it];
//note: act is currently only 0 or 1.
//the reason we are not using bool is that the on-disk type in 0.11.0 was already int
int actPrev = timerWeekday[it] & 0x01;
@@ -438,7 +467,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
int act = timer["en"] | actPrev;
if (act) timerWeekday[it]++;
}
-
+ if (it<8) {
+ JsonObject start = timer["start"];
+ byte startm = start["mon"];
+ if (startm) timerMonth[it] = (startm << 4);
+ CJSON(timerDay[it], start["day"]);
+ JsonObject end = timer["end"];
+ CJSON(timerDayEnd[it], end["day"]);
+ byte endm = end["mon"];
+ if (startm) timerMonth[it] += endm & 0x0F;
+ if (!(timerMonth[it] & 0x0F)) timerMonth[it] += 12; //default end month to 12
+ }
it++;
}
@@ -463,12 +502,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(DMXStartLED,dmx[F("start-led")]);
JsonArray dmx_fixmap = dmx[F("fixmap")];
- it = 0;
- for (int i : dmx_fixmap) {
- if (it > 14) break;
+ for (int i = 0; i < dmx_fixmap.size(); i++) {
+ if (i > 14) break;
CJSON(DMXFixtureMap[i],dmx_fixmap[i]);
- it++;
}
+
+ CJSON(e131ProxyUniverse, dmx[F("e131proxy")]);
#endif
// Begin Sound Reactive specific settings - 1st attempt
@@ -479,10 +518,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(sampleGain, snd_cfg[F("gn")]); // gain
CJSON(soundAgc, snd_cfg[F("agc")]); // agc
- JsonObject snd_fft = sound[F("fft")]; // FFT Settings
- CJSON(effectFFT1, snd_fft[F("f1")]);
- CJSON(effectFFT2, snd_fft[F("f2")]);
- CJSON(effectFFT3, snd_fft[F("f3")]);
+ JsonObject snd_custom = sound[F("custom")]; // Custom settings
+ CJSON(effectCustom1, snd_custom[F("c1")]);
+ CJSON(effectCustom2, snd_custom[F("c2")]);
+ CJSON(effectCustom3, snd_custom[F("c3")]);
JsonObject snd_sync = sound[F("sync")]; // Sound Reactive audio sync
CJSON(audioSyncPort, snd_sync[F("port")]); // 11988
@@ -491,11 +530,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
DEBUG_PRINTLN(F("Starting usermod config."));
JsonObject usermods_settings = doc["um"];
if (!usermods_settings.isNull()) {
- bool allComplete = usermods.readFromConfig(usermods_settings);
- if (!allComplete && fromFS) serializeConfig();
+ needsSave = !usermods.readFromConfig(usermods_settings);
}
- if (fromFS) return false;
+ if (fromFS) return needsSave;
doReboot = doc[F("rb")] | doReboot;
return (doc["sv"] | true);
}
@@ -507,19 +545,27 @@ void deserializeConfigFromFS() {
return;
}
+ #ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
+ #else
+ if (!requestJSONBufferLock(1)) return;
+ #endif
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
success = readObjectFromFile("/cfg.json", nullptr, &doc);
if (!success) { //if file does not exist, try reading from EEPROM
deEEPSettings();
+ releaseJSONBufferLock();
return;
}
// NOTE: This routine deserializes *and* applies the configuration
// Therefore, must also initialize ethernet from this function
- deserializeConfig(doc.as(), true);
+ bool needsSave = deserializeConfig(doc.as(), true);
+ releaseJSONBufferLock();
+
+ if (needsSave) serializeConfig(); // usermods required new prameters
}
void serializeConfig() {
@@ -527,7 +573,11 @@ void serializeConfig() {
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
+ #ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
+ #else
+ if (!requestJSONBufferLock(2)) return;
+ #endif
JsonArray rev = doc.createNestedArray("rev");
rev.add(1); //major settings revision
@@ -602,10 +652,14 @@ void serializeConfig() {
JsonObject hw = doc.createNestedObject("hw");
JsonObject hw_led = hw.createNestedObject("led");
- hw_led[F("total")] = ledCount;
+ hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade
hw_led[F("maxpwr")] = strip.ablMilliampsMax;
hw_led[F("ledma")] = strip.milliampsPerLed;
- hw_led[F("rgbwm")] = strip.rgbwMode;
+ hw_led["cct"] = correctWB;
+ hw_led[F("cr")] = cctFromRgb;
+ hw_led[F("cb")] = strip.cctBlending;
+ hw_led["fps"] = strip.getTargetFps();
+ hw_led[F("rgbwm")] = strip.autoWhiteMode;
// 2D Matrix Settings
hw_led[F("somp")] = strip.stripOrMatrixPanel;
@@ -631,7 +685,7 @@ void serializeConfig() {
if (!bus || bus->getLength()==0) break;
JsonObject ins = hw_led_ins.createNestedObject();
ins["start"] = bus->getStart();
- ins[F("len")] = bus->getLength();
+ ins["len"] = bus->getLength();
JsonArray ins_pin = ins.createNestedArray("pin");
uint8_t pins[5];
uint8_t nPins = bus->getPins(pins);
@@ -639,9 +693,21 @@ void serializeConfig() {
ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->reversed;
ins[F("skip")] = bus->skippedLeds();
- ins["type"] = bus->getType() & 0x7F;;
+ ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired();
- ins[F("rgbw")] = bus->isRgbw();
+ //ins[F("rgbw")] = bus->isRgbw();
+ }
+
+ JsonArray hw_com = hw.createNestedArray(F("com"));
+ const ColorOrderMap& com = busses.getColorOrderMap();
+ for (uint8_t s = 0; s < com.count(); s++) {
+ const ColorOrderMapEntry *entry = com.get(s);
+ if (!entry) break;
+
+ JsonObject co = hw_com.createNestedObject();
+ co["start"] = entry->start;
+ co["len"] = entry->len;
+ co[F("order")] = entry->colorOrder;
}
// button(s)
@@ -667,11 +733,14 @@ void serializeConfig() {
JsonObject hw_ir = hw.createNestedObject("ir");
hw_ir["pin"] = irPin;
hw_ir["type"] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
+ hw_ir["sel"] = irApplyToAllSelected;
JsonObject hw_relay = hw.createNestedObject(F("relay"));
hw_relay["pin"] = rlyPin;
hw_relay["rev"] = !rlyMde;
+ hw[F("baud")] = serialBaud;
+
//JsonObject hw_status = hw.createNestedObject("status");
//hw_status["pin"] = -1;
@@ -680,7 +749,7 @@ void serializeConfig() {
hw_amic["pin"] = audioPin;
JsonObject hw_dmic = hw.createNestedObject("digitalmic");
- hw_dmic["en"] = dmEnabled;
+ hw_dmic["en"] = dmType;
JsonObject hw_dmic_pins = hw_dmic.createNestedObject("pins");
hw_dmic_pins[F("i2ssd")] = i2ssdPin;
@@ -697,18 +766,18 @@ void serializeConfig() {
light_gc["col"] = (strip.gammaCorrectCol) ? 2.8 : 1.0;
JsonObject light_tr = light.createNestedObject("tr");
- light_tr[F("mode")] = fadeTransition;
+ light_tr["mode"] = fadeTransition;
light_tr["dur"] = transitionDelayDefault / 100;
light_tr["pal"] = strip.paletteFade;
JsonObject light_nl = light.createNestedObject("nl");
- light_nl[F("mode")] = nightlightMode;
+ light_nl["mode"] = nightlightMode;
light_nl["dur"] = nightlightDelayMinsDefault;
light_nl[F("tbri")] = nightlightTargetBri;
light_nl["macro"] = macroNl;
JsonObject def = doc.createNestedObject("def");
- def[F("ps")] = bootPreset;
+ def["ps"] = bootPreset;
def["on"] = turnOnAtBoot;
def["bri"] = briS;
@@ -721,8 +790,10 @@ void serializeConfig() {
JsonObject if_sync_recv = if_sync.createNestedObject("recv");
if_sync_recv["bri"] = receiveNotificationBrightness;
if_sync_recv["col"] = receiveNotificationColor;
- if_sync_recv["fx"] = receiveNotificationEffects;
+ if_sync_recv["fx"] = receiveNotificationEffects;
if_sync_recv["grp"] = receiveGroups;
+ if_sync_recv["seg"] = receiveSegmentOptions;
+ if_sync_recv["sb"] = receiveSegmentBounds;
JsonObject if_sync_send = if_sync.createNestedObject("send");
if_sync_send[F("dir")] = notifyDirect;
@@ -746,7 +817,7 @@ void serializeConfig() {
if_live_dmx[F("uni")] = e131Universe;
if_live_dmx[F("seqskip")] = e131SkipOutOfSequence;
if_live_dmx[F("addr")] = DMXAddress;
- if_live_dmx[F("mode")] = DMXMode;
+ if_live_dmx["mode"] = DMXMode;
if_live[F("timeout")] = realtimeTimeoutMs / 100;
if_live[F("maxbri")] = arlsForceMaxBri;
@@ -808,7 +879,7 @@ void serializeConfig() {
if_ntp[F("lt")] = latitude;
JsonObject ol = doc.createNestedObject("ol");
- ol[F("clock")] = overlayDefault;
+ ol[F("clock")] = overlayCurrent;
ol[F("cntdwn")] = countdownMode;
ol["min"] = overlayMin;
@@ -835,6 +906,14 @@ void serializeConfig() {
timers_ins0["min"] = timerMinutes[i];
timers_ins0["macro"] = timerMacro[i];
timers_ins0[F("dow")] = timerWeekday[i] >> 1;
+ if (i<8) {
+ JsonObject start = timers_ins0.createNestedObject("start");
+ start["mon"] = (timerMonth[i] >> 4) & 0xF;
+ start["day"] = timerDay[i];
+ JsonObject end = timers_ins0.createNestedObject("end");
+ end["mon"] = timerMonth[i] & 0xF;
+ end["day"] = timerDayEnd[i];
+ }
}
JsonObject ota = doc.createNestedObject("ota");
@@ -851,8 +930,11 @@ void serializeConfig() {
dmx[F("start-led")] = DMXStartLED;
JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap"));
- for (byte i = 0; i < 15; i++)
+ for (byte i = 0; i < 15; i++) {
dmx_fixmap.add(DMXFixtureMap[i]);
+ }
+
+ dmx[F("e131proxy")] = e131ProxyUniverse;
#endif
// Begin Sound Reactive specific settings - 1st attempt
@@ -863,10 +945,10 @@ void serializeConfig() {
snd_cfg[F("gn")] = sampleGain;
snd_cfg[F("agc")] = soundAgc;
- JsonObject snd_fft = sound.createNestedObject("fft"); // FFT Settings
- snd_fft[F("f1")] = effectFFT1;
- snd_fft[F("f2")] = effectFFT2;
- snd_fft[F("f3")] = effectFFT3;
+ JsonObject snd_custom = sound.createNestedObject("custom"); // Custom settings
+ snd_custom[F("c1")] = effectCustom1;
+ snd_custom[F("c2")] = effectCustom2;
+ snd_custom[F("c3")] = effectCustom3;
JsonObject snd_sync = sound.createNestedObject("sync"); // Sound Reactive audio sync
snd_sync[F("port")] = audioSyncPort; // 11988
@@ -878,16 +960,24 @@ void serializeConfig() {
File f = WLED_FS.open("/cfg.json", "w");
if (f) serializeJson(doc, f);
f.close();
+ releaseJSONBufferLock();
}
//settings in /wsec.json, not accessible via webserver, for passwords and tokens
bool deserializeConfigSec() {
DEBUG_PRINTLN(F("Reading settings from /wsec.json..."));
+ #ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
+ #else
+ if (!requestJSONBufferLock(3)) return false;
+ #endif
bool success = readObjectFromFile("/wsec.json", nullptr, &doc);
- if (!success) return false;
+ if (!success) {
+ releaseJSONBufferLock();
+ return false;
+ }
JsonObject nw_ins_0 = doc["nw"]["ins"][0];
getStringFromJson(clientPass, nw_ins_0["psk"], 65);
@@ -919,13 +1009,18 @@ bool deserializeConfigSec() {
CJSON(wifiLock, ota[F("lock-wifi")]);
CJSON(aOtaEnabled, ota[F("aota")]);
+ releaseJSONBufferLock();
return true;
}
void serializeConfigSec() {
DEBUG_PRINTLN(F("Writing settings to /wsec.json..."));
+ #ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
+ #else
+ if (!requestJSONBufferLock(4)) return;
+ #endif
JsonObject nw = doc.createNestedObject("nw");
@@ -960,4 +1055,5 @@ void serializeConfigSec() {
File f = WLED_FS.open("/wsec.json", "w");
if (f) serializeJson(doc, f);
f.close();
+ releaseJSONBufferLock();
}
diff --git a/wled00/colors.cpp b/wled00/colors.cpp
index dfdd53e076..25cce032ef 100644
--- a/wled00/colors.cpp
+++ b/wled00/colors.cpp
@@ -4,47 +4,10 @@
* Color conversion methods
*/
-void colorFromUint32(uint32_t in, bool secondary)
+void setRandomColor(byte* rgb)
{
- if (secondary) {
- colSec[3] = in >> 24 & 0xFF;
- colSec[0] = in >> 16 & 0xFF;
- colSec[1] = in >> 8 & 0xFF;
- colSec[2] = in & 0xFF;
- } else {
- col[3] = in >> 24 & 0xFF;
- col[0] = in >> 16 & 0xFF;
- col[1] = in >> 8 & 0xFF;
- col[2] = in & 0xFF;
- }
-}
-
-//load a color without affecting the white channel
-void colorFromUint24(uint32_t in, bool secondary)
-{
- if (secondary) {
- colSec[0] = in >> 16 & 0xFF;
- colSec[1] = in >> 8 & 0xFF;
- colSec[2] = in & 0xFF;
- } else {
- col[0] = in >> 16 & 0xFF;
- col[1] = in >> 8 & 0xFF;
- col[2] = in & 0xFF;
- }
-}
-
-//store color components in uint32_t
-uint32_t colorFromRgbw(byte* rgbw) {
- return (rgbw[0] << 16) + (rgbw[1] << 8) + rgbw[2] + (rgbw[3] << 24);
-}
-
-//relatively change white brightness, minumum A=5
-void relativeChangeWhite(int8_t amount, byte lowerBoundary)
-{
- int16_t new_val = (int16_t) col[3] + amount;
- if (new_val > 0xFF) new_val = 0xFF;
- else if (new_val < lowerBoundary) new_val = lowerBoundary;
- col[3] = new_val;
+ lastRandomIndex = strip.get_random_wheel_index(lastRandomIndex);
+ colorHStoRGB(lastRandomIndex*256,255,rgb);
}
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb
@@ -64,9 +27,9 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb
case 4: rgb[0]=t,rgb[1]=p,rgb[2]=255;break;
case 5: rgb[0]=255,rgb[1]=p,rgb[2]=q;
}
- if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col);
}
+//get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html)
void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc
{
float r = 0, g = 0, b = 0;
@@ -84,7 +47,7 @@ void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc
g = round(288.1221695283 * pow((temp - 60), -0.0755148492));
b = 255;
}
- g += 15; //mod by Aircoookie, a bit less accurate but visibly less pinkish
+ //g += 12; //mod by Aircoookie, a bit less accurate but visibly less pinkish
rgb[0] = (uint8_t) constrain(r, 0, 255);
rgb[1] = (uint8_t) constrain(g, 0, 255);
rgb[2] = (uint8_t) constrain(b, 0, 255);
@@ -111,7 +74,6 @@ void colorCTtoRGB(uint16_t mired, byte* rgb) //white spectrum to rgb, bins
} else {
rgb[0]=237;rgb[1]=255;rgb[2]=239;//150
}
- if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col);
}
#ifndef WLED_DISABLE_HUESYNC
@@ -169,7 +131,6 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www
rgb[0] = 255.0*r;
rgb[1] = 255.0*g;
rgb[2] = 255.0*b;
- if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col);
}
void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
@@ -197,10 +158,10 @@ void colorFromDecOrHexString(byte* rgb, char* in)
c = strtoul(in, NULL, 10);
}
- rgb[3] = (c >> 24) & 0xFF;
- rgb[0] = (c >> 16) & 0xFF;
- rgb[1] = (c >> 8) & 0xFF;
- rgb[2] = c & 0xFF;
+ rgb[0] = R(c);
+ rgb[1] = G(c);
+ rgb[2] = B(c);
+ rgb[3] = W(c);
}
//contrary to the colorFromDecOrHexString() function, this uses the more standard RRGGBB / RRGGBBWW order
@@ -212,14 +173,14 @@ bool colorFromHexString(byte* rgb, const char* in) {
uint32_t c = strtoul(in, NULL, 16);
if (inputSize == 6) {
- rgb[0] = (c >> 16) & 0xFF;
- rgb[1] = (c >> 8) & 0xFF;
- rgb[2] = c & 0xFF;
+ rgb[0] = (c >> 16);
+ rgb[1] = (c >> 8);
+ rgb[2] = c ;
} else {
- rgb[0] = (c >> 24) & 0xFF;
- rgb[1] = (c >> 16) & 0xFF;
- rgb[2] = (c >> 8) & 0xFF;
- rgb[3] = c & 0xFF;
+ rgb[0] = (c >> 24);
+ rgb[1] = (c >> 16);
+ rgb[2] = (c >> 8);
+ rgb[3] = c ;
}
return true;
}
@@ -236,11 +197,80 @@ float maxf (float v, float w)
return v;
}
+/*
+uint32_t colorRGBtoRGBW(uint32_t c)
+{
+ byte rgb[4];
+ rgb[0] = R(c);
+ rgb[1] = G(c);
+ rgb[2] = B(c);
+ rgb[3] = W(c);
+ colorRGBtoRGBW(rgb);
+ return RGBW32(rgb[0], rgb[1], rgb[2], rgb[3]);
+}
+
void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY)
{
float low = minf(rgb[0],minf(rgb[1],rgb[2]));
float high = maxf(rgb[0],maxf(rgb[1],rgb[2]));
if (high < 0.1f) return;
- float sat = 100.0f * ((high - low) / high);; // maximum saturation is 100 (corrected from 255)
+ float sat = 100.0f * ((high - low) / high); // maximum saturation is 100 (corrected from 255)
rgb[3] = (byte)((255.0f - sat) / 255.0f * (rgb[0] + rgb[1] + rgb[2]) / 3);
}
+*/
+
+byte correctionRGB[4] = {0,0,0,0};
+uint16_t lastKelvin = 0;
+
+// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance)
+uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb)
+{
+ //remember so that slow colorKtoRGB() doesn't have to run for every setPixelColor()
+ if (lastKelvin != kelvin) colorKtoRGB(kelvin, correctionRGB); // convert Kelvin to RGB
+ lastKelvin = kelvin;
+ byte rgbw[4];
+ rgbw[0] = ((uint16_t) correctionRGB[0] * R(rgb)) /255; // correct R
+ rgbw[1] = ((uint16_t) correctionRGB[1] * G(rgb)) /255; // correct G
+ rgbw[2] = ((uint16_t) correctionRGB[2] * B(rgb)) /255; // correct B
+ rgbw[3] = W(rgb);
+ return RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3]);
+}
+
+//approximates a Kelvin color temperature from an RGB color.
+//this does no check for the "whiteness" of the color,
+//so should be used combined with a saturation check (as done by auto-white)
+//values from http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html (10deg)
+//equation spreadsheet at https://bit.ly/30RkHaN
+//accuracy +-50K from 1900K up to 8000K
+//minimum returned: 1900K, maximum returned: 10091K (range of 8192)
+uint16_t approximateKelvinFromRGB(uint32_t rgb) {
+ //if not either red or blue is 255, color is dimmed. Scale up
+ uint8_t r = R(rgb), b = B(rgb);
+ if (r == b) return 6550; //red == blue at about 6600K (also can't go further if both R and B are 0)
+
+ if (r > b) {
+ //scale blue up as if red was at 255
+ uint16_t scale = 0xFFFF / r; //get scale factor (range 257-65535)
+ b = ((uint16_t)b * scale) >> 8;
+ //For all temps K<6600 R is bigger than B (for full bri colors R=255)
+ //-> Use 9 linear approximations for blackbody radiation blue values from 2000-6600K (blue is always 0 below 2000K)
+ if (b < 33) return 1900 + b *6;
+ if (b < 72) return 2100 + (b-33) *10;
+ if (b < 101) return 2492 + (b-72) *14;
+ if (b < 132) return 2900 + (b-101) *16;
+ if (b < 159) return 3398 + (b-132) *19;
+ if (b < 186) return 3906 + (b-159) *22;
+ if (b < 210) return 4500 + (b-186) *25;
+ if (b < 230) return 5100 + (b-210) *30;
+ return 5700 + (b-230) *34;
+ } else {
+ //scale red up as if blue was at 255
+ uint16_t scale = 0xFFFF / b; //get scale factor (range 257-65535)
+ r = ((uint16_t)r * scale) >> 8;
+ //For all temps K>6600 B is bigger than R (for full bri colors B=255)
+ //-> Use 2 linear approximations for blackbody radiation red values from 6600-10091K (blue is always 0 below 2000K)
+ if (r > 225) return 6600 + (254-r) *50;
+ uint16_t k = 8080 + (225-r) *86;
+ return (k > 10091) ? 10091 : k;
+ }
+}
diff --git a/wled00/const.h b/wled00/const.h
index 70ded1881a..0b907e7111 100644
--- a/wled00/const.h
+++ b/wled00/const.h
@@ -39,6 +39,12 @@
#endif
#endif
+#ifdef ESP8266
+#define WLED_MAX_COLOR_ORDER_MAPPINGS 5
+#else
+#define WLED_MAX_COLOR_ORDER_MAPPINGS 10
+#endif
+
//Usermod IDs
#define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present
#define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID
@@ -63,6 +69,9 @@
#define USERMOD_ID_BH1750 20 //Usermod "usermod_bh1750.h"
#define USERMOD_ID_SEVEN_SEGMENT_DISPLAY 21 //Usermod "usermod_v2_seven_segment_display.h"
#define USERMOD_RGB_ROTARY_ENCODER 22 //Usermod "rgb-rotary-encoder.h"
+#define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h"
+#define USERMOD_ID_SSDR 24 //Usermod "usermod_v2_seven_segment_display_reloaded.h"
+#define USERMOD_ID_CRONIXIE 25 //Usermod "usermod_cronixie.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -73,7 +82,7 @@
//Notifier callMode
#define CALL_MODE_INIT 0 //no updates on init, can be used to disable updates
#define CALL_MODE_DIRECT_CHANGE 1
-#define CALL_MODE_BUTTON 2
+#define CALL_MODE_BUTTON 2 //default button actions applied to selected segments
#define CALL_MODE_NOTIFICATION 3
#define CALL_MODE_NIGHTLIGHT 4
#define CALL_MODE_NO_NOTIFY 5
@@ -83,6 +92,7 @@
#define CALL_MODE_BLYNK 9
#define CALL_MODE_ALEXA 10
#define CALL_MODE_WS_SEND 11 //special call mode, not for notifier, updates websocket only
+#define CALL_MODE_BUTTON_PRESET 12 //button/IR JSON preset/macro
//RGB to RGBW conversion mode
#define RGBW_MODE_MANUAL_ONLY 0 //No automatic white channel calculation. Manual white channel slider
@@ -123,7 +133,7 @@
// - 0b010 (dec. 32-47) analog (PWM)
// - 0b011 (dec. 48-63) digital (data + clock / SPI)
// - 0b100 (dec. 64-79) unused/reserved
-// - 0b101 (dec. 80-95) digital (data + clock / SPI)
+// - 0b101 (dec. 80-95) virtual network busses
// - 0b110 (dec. 96-111) unused/reserved
// - 0b111 (dec. 112-127) unused/reserved
//bit 7 is reserved and set to 0
@@ -167,6 +177,7 @@
#define COL_ORDER_RBG 3
#define COL_ORDER_BGR 4
#define COL_ORDER_GBR 5
+#define COL_ORDER_MAX 5
//Button type
@@ -218,6 +229,7 @@
#define SEG_DIFFERS_FX 0x08
#define SEG_DIFFERS_BOUNDS 0x10
#define SEG_DIFFERS_GSO 0x20
+#define SEG_DIFFERS_SEL 0x80
//Playlist option byte
#define PL_OPTION_SHUFFLE 0x01
@@ -251,7 +263,7 @@
#define NTP_PACKET_SIZE 48
-//maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses
+//maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses
#ifndef MAX_LEDS
#ifdef ESP8266
#define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs
@@ -273,12 +285,20 @@
#endif
// string temp buffer (now stored in stack locally)
-#define OMAX 2048
+#ifdef ESP8266
+#define SETTINGS_STACK_BUF_SIZE 2048
+#else
+#define SETTINGS_STACK_BUF_SIZE 3096
+#endif
#ifdef WLED_USE_ETHERNET
-#define E131_MAX_UNIVERSE_COUNT 20
+ #define E131_MAX_UNIVERSE_COUNT 20
#else
-#define E131_MAX_UNIVERSE_COUNT 10
+ #ifdef ESP8266
+ #define E131_MAX_UNIVERSE_COUNT 9
+ #else
+ #define E131_MAX_UNIVERSE_COUNT 12
+ #endif
#endif
#define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit
@@ -296,11 +316,17 @@
// Size of buffer for API JSON object (increase for more segments)
#ifdef ESP8266
- #define JSON_BUFFER_SIZE 9216
+ #define JSON_BUFFER_SIZE 10240
#else
#define JSON_BUFFER_SIZE 20480
#endif
+#ifdef WLED_USE_DYNAMIC_JSON
+ #define MIN_HEAP_SIZE JSON_BUFFER_SIZE+512
+#else
+ #define MIN_HEAP_SIZE 4096
+#endif
+
// Maximum size of node map (list of other WLED instances)
#ifdef ESP8266
#define WLED_MAX_NODES 24
@@ -313,15 +339,15 @@
#ifdef ESP8266
#define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards
#else
- #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards
+ #define LEDPIN 2 // Changed from 16 to restore compatibility with ESP32-pico
#endif
#endif
#ifdef WLED_ENABLE_DMX
#if (LEDPIN == 2)
#undef LEDPIN
- #define LEDPIN 3
- #warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 3."
+ #define LEDPIN 1
+ #warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 1."
#endif
#endif
@@ -329,4 +355,6 @@
#define DEFAULT_LED_COUNT 30
#endif
+#define INTERFACE_UPDATE_COOLDOWN 2000 //time in ms to wait between websockets, alexa, and MQTT updates
+
#endif
diff --git a/wled00/data/index.css b/wled00/data/index.css
index 7c4162cc55..0421f4426f 100644
--- a/wled00/data/index.css
+++ b/wled00/data/index.css
@@ -45,12 +45,10 @@ body {
text-align: center;
-webkit-touch-callout: none;
-webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
- scrollbar-width: 6px;
- scrollbar-color: var(--c-sb) transparent;
+ scrollbar-width: 6px;
+ scrollbar-color: var(--c-sb) transparent;
}
html,
@@ -93,7 +91,7 @@ button {
bottom: calc(var(--bh) + 6px);
right: 4px;
color: var(--c-6);
- cursor: pointer;
+ cursor: pointer;
writing-mode: vertical-rl;
}
@@ -128,11 +126,6 @@ button {
width: 100%;
}
-.segt {
- table-layout: fixed;
- width: 76%;
-}
-
.segtd {
text-align: center;
text-transform: uppercase;
@@ -167,25 +160,25 @@ button {
}
.edit-icon {
- position: absolute;
- right: -26px;
- top: 10px;
- display: none;
+ position: absolute;
+ right: -26px;
+ top: 10px;
+ display: none;
}
.search-icon {
- position: absolute;
- left: 8px;
- top: 10px;
- pointer-events: none;
+ position: absolute;
+ left: 8px;
+ top: 10px;
+ pointer-events: none;
}
.search-cancel-icon {
- position: absolute;
- right: 8px;
- top: 9px;
- cursor: pointer;
- display: none;
+ position: absolute;
+ right: 8px;
+ top: 9px;
+ cursor: pointer;
+ display: none;
}
.flr {
@@ -286,7 +279,7 @@ button {
padding-top: 0;
margin-top: 11px;
height: calc(100% - 11px);
- -webkit-overflow-scrolling: touch;
+ -webkit-overflow-scrolling: touch;
}
.smooth { transition: transform calc(var(--f, 1)*.5s) ease-out }
@@ -294,6 +287,7 @@ button {
.tab-label {
margin: 0 0 -5px 0;
padding-bottom: 4px;
+ display: var(--bhd);
}
.overlay {
@@ -334,7 +328,7 @@ Effect Speed */
#fxb0 {
margin-bottom: 2px;
- filter: drop-shadow(0 0 1px #000);
+ filter: drop-shadow(0 0 1px #000);
}
.first {
@@ -383,6 +377,10 @@ Effect Speed */
overflow: auto;
}
+.modal button:hover {
+ background-color: var(--c-4);
+}
+
#info {
z-index: 3;
}
@@ -392,7 +390,7 @@ Effect Speed */
}
#ndlt {
- margin: 12px 0;
+ margin: 12px 0;
}
.valtd i {
@@ -422,13 +420,14 @@ Effect Speed */
margin: 8px;
}
-#kv, #kn {
+/* WLEDSR Custom Effects: add kceEditor */
+#kv, #kn, #kceEditor {
max-width: 490px;
display: inline-block;
}
#kn td {
- padding-bottom: 12px;
+ padding-bottom: 12px;
}
#lv {
@@ -461,25 +460,33 @@ img {
.sliderdisplay {
content:'';
position: absolute;
- top: 13px; bottom: 13px;
- left: 10px; right: 10px;
+ top: 12.5px; bottom: 12.5px;
+ left: 13px; right: 13px;
background: var(--c-4);
border-radius: 17px;
pointer-events: none;
z-index: -1;
+ --bg: var(--c-f);
+}
+
+#rwrap .sliderdisplay { --bg: #f00; }
+#gwrap .sliderdisplay { --bg: #0f0; }
+#bwrap .sliderdisplay { --bg: #00f; }
+#wbal .sliderdisplay, #kwrap .sliderdisplay {
+ background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #cbdbff);
}
.sliderbubble {
- width: 36px;
- line-height: 24px;
- background: var(--c-3);
- position: absolute;
- transform: translateX(-50%);
- border-radius: 12px;
- margin-left: 12px;
- margin-top: 3px;
- padding: 0px;
- display: inline;
+ width: 36px;
+ line-height: 24px;
+ background: var(--c-3);
+ position: absolute;
+ transform: translateX(-50%);
+ border-radius: 12px;
+ margin-left: 12px;
+ margin-top: 3px;
+ padding: 0px;
+ display: inline;
}
.hidden {
@@ -494,6 +501,7 @@ input[type=range] {
background-color: transparent;
cursor: pointer;
}
+
input[type=range]:focus {
outline: none;
}
@@ -529,30 +537,30 @@ input[type=range]:active + .sliderbubble {
display: inline;
transform: translateX(-50%);
}
-
-#wwrap {
+/* hide color controls until enabled in updateUI() */
+#pwrap, #wwrap, #wbal, #rgbwrap, #palwrap {
display: none;
}
/* Modified for WLEDSR */
+/* Slider wrapper div */
.sliderwrap {
height: 31px;
width: 240px;
position: relative;
}
+/* Segment power button + brightness slider wrapper div */
.sbs {
margin: 0px -20px 5px -6px;
}
+/* Segment brightness slider wrapper div */
.sws {
- width: 230px;
-}
-
-.sis {
- width: 214px !important;
+ margin-left: -7px;
}
+/* Dynamically hide brightness slider label */
.hd {
display: var(--bhd);
}
@@ -567,10 +575,6 @@ input[type=range]:active + .sliderbubble {
width: 260px;
}
-#rgbwrap {
- display: none;
-}
-
.btn {
padding: 8px;
margin: 10px;
@@ -578,14 +582,22 @@ input[type=range]:active + .sliderbubble {
font-size: 19px;
background-color: var(--c-3);
color: var(--c-f);
- cursor: pointer;
+ cursor: pointer;
border: 0px solid white;
border-radius: 25px;
transition-duration: 0.5s;
- -webkit-backface-visibility: hidden;
- -webkit-transform:translate3d(0,0,0);
+ /*-webkit-backface-visibility: hidden;
+ -webkit-transform:translate3d(0,0,0);*/
+}
+
+/* Small round button (color selectors, icon-only round buttons) */
+.xxs {
+ width: 40px;
+ height: 40px;
+ margin: 6px;
}
+/* Segments/presets auxiliary buttons (Add segment, Create preset, ...) */
.btn-s {
width: 276px;
background-color: var(--c-2);
@@ -597,60 +609,80 @@ input[type=range]:active + .sliderbubble {
margin: 0px 8px 4px 0;
vertical-align: middle;
}
-.btna-icon {
- margin: 0px;
-}
+
+/* Wide button used in presets (Save, playlist test, delete) */
.btn-p {
width: 216px;
}
-.btn-xs {
- width: 39px;
- margin: 2px 0 0 0;
+
+/* Delete preset from playlist button */
+.btn-pl-del {
+ margin: 0 0 0 3px;
}
+
+/* Add preset to playlist "+" button */
.btn-pl-add {
- margin-left: 9px;
+ margin: 3px 0 0 8px;
}
-
+/* Quick color select buttons wrapper div */
#qcs-w {
margin-top: 10px;
+ display: none;
}
+
+/* Quick color select buttons */
.qcs {
padding: 14px;
margin: 2px;
border-radius: 14px;
display: inline-block;
}
+
+/* Quick color select Black button (has white border) */
.qcsb {
padding: 13px;
border: 1px solid var(--c-f);
}
+
+/* Hex color input wrapper div */
#hexw {
margin-top: 5px;
display: none;
}
+
+/* Transition time input */
#tt {
- text-align: center;
+ text-align: center;
}
+/* Color slot select buttons (1,2,3) */
.cl {
- width: 42px;
+ width: 38.5px;
+ height: 38.5px;
+ margin: 7px;
+ background-color: #000;
+ box-shadow: 0 0 0 1.5px #fff;
+}
+.selected.cl {
+ box-shadow: 0 0 0 5px #fff;
}
+/* Playlist preset select */
.sel-pl {
- width: 192px;
- background-position: 168px 16px;
- margin: 8px 7px 0 0;
+ width: 192px;
+ background-position: 168px 16px;
+ margin: 8px 3px 0 0;
}
+/* Playlist end preset select */
.sel-ple {
width: 216px;
- background-position: 192px 16px;
+ background-position: 192px 16px;
}
select {
-webkit-appearance: none;
- -moz-appearance: none;
appearance: none;
background: url("data:image/svg+xml;utf8,") no-repeat;
background-size: 12px;
@@ -681,10 +713,17 @@ input[type=number], input[type=text] {
outline: none;
width: 50px;
-webkit-appearance: textfield;
- -moz-appearance: textfield;
appearance: textfield;
}
+/* WLEDSR Custom Effects */
+.ceTextarea {
+ width: 90%;
+ height: 300px;
+ resize: none;
+ white-space: pre;
+}
+
textarea {
background: var(--c-2);
color: var(--c-f);
@@ -698,7 +737,7 @@ textarea {
}
::selection {
- background: var(--c-b);
+ background: var(--c-b);
}
input[type=text] {
@@ -706,18 +745,18 @@ input[type=text] {
text-align: center;
}
-.ptxt {
- width: 200px !important;
- margin: 26px 0 6px 12px !important;
+input[type=text].ptxt {
+ width: 200px;
+ margin: 26px 0 6px 12px;
}
-.stxt {
- display: none;
- margin-top: 6px !important;
+input[type=text].stxt {
+ display: none;
+ margin-top: 6px;
}
-.qltxt {
- width: 50px !important;
+input[type=text].qltxt {
+ width: 50px;
}
input[type=number]:focus, input[type=text]:focus {
@@ -746,42 +785,50 @@ input[type=number]::-webkit-outer-spin-button {
}
.segn {
- border-radius: 5px !important;
+ border-radius: 5px !important;
margin: 3px 0 6px 0 !important;
}
-.segname {
+.pname, .plname, .segname {
position: absolute;
- top: 0px;
left: 50%;
- padding: 9px 0;
transform: translateX(-50%);
white-space: nowrap;
cursor: pointer;
}
.segntxt {
- max-width: 160px;
- overflow: hidden;
- text-overflow: clip;
+ max-width: 160px;
+ overflow: hidden;
+ text-overflow: clip;
}
-.pname {
+.segname {
+ top: 0px;
+ padding: 9px 0;
+}
+.pname, .plname {
width: 208px;
padding: 8px 0;
text-align: center;
overflow: hidden;
text-overflow: clip;
}
+.pname {
+ top: 1px;
+}
+.plname {
+ top:0;
+}
.pid {
- position: absolute;
- top: 0px;
- left: 0px;
- padding: 11px 0px 0px 11px;
- font-size: 16px;
- width: 20px;
- text-align: center;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ padding: 11px 0px 0px 11px;
+ font-size: 16px;
+ width: 20px;
+ text-align: center;
color: var(--c-b);
}
@@ -793,11 +840,7 @@ input[type=number]::-webkit-outer-spin-button {
padding: 6px 0 0 0;
}
-.xxs {
- width: 40px;
- margin: 6px;
-}
-
+/* Quick preset select buttons */
.psts {
background-color: var(--c-3);
color: var(--c-f);
@@ -806,20 +849,17 @@ input[type=number]::-webkit-outer-spin-button {
height: 40px;
}
+/* Segment apply button (checkmark) */
.cnf {
color: var(--c-f);
cursor: pointer;
background: var(--c-3);
border-radius: 5px;
+ padding: 8.5px 21px 5px;
+ display: inline;
}
-.cnf-s {
- position: absolute;
- bottom: 100px;
- right: 23px;
- padding: 7px 22px;
-}
-
+/* Segment power button icon */
.pwr {
color: var(--c-6);
transform: translate(2px, 3px);
@@ -830,17 +870,10 @@ input[type=number]::-webkit-outer-spin-button {
color: var(--c-f);
}
-.half {
- padding: 7.5px;
- bottom: 35px;
-}
-
.del {
position: absolute;
bottom: 8px;
right: 8px;
- color: var(--c-f);
- cursor: pointer;
}
.check, .radio {
@@ -864,9 +897,9 @@ input[type=number]::-webkit-outer-spin-button {
}
.fxchkl {
- position: absolute;
- top: 0px;
- left: 8px;
+ position: absolute;
+ top: 0px;
+ left: 8px;
}
.check input, .radio input {
@@ -879,19 +912,22 @@ input[type=number]::-webkit-outer-spin-button {
.checkmark, .radiomark {
position: absolute;
- bottom: 0;
left: 0;
- height: 25px;
- width: 25px;
- background-color: var(--c-3);
+ height: 24px;
+ width: 24px;
+ background-color: var(--c-4);
border-radius: 10px;
+ /*border: 1px solid var(--c-2);*/
+}
+
+.checkmark {
+ top: 6px;
}
.radiomark {
- height: 24px;
- width: 24px;
border-radius: 50%;
- background-color: transparent;
+ background-color: transparent;
+ top: 7px;
}
.schk {
@@ -913,7 +949,7 @@ input[type=number]::-webkit-outer-spin-button {
.check:hover input ~ .checkmark {
- background-color: var(--c-4);
+ background-color: var(--c-5);
}
.check input:checked ~ .checkmark {
@@ -931,8 +967,8 @@ input[type=number]::-webkit-outer-spin-button {
}
.check .checkmark:after {
- left: 9px;
- top: 5px;
+ left: 8px;
+ top: 4px;
width: 5px;
height: 10px;
border: solid var(--c-f);
@@ -962,7 +998,7 @@ input[type=number]::-webkit-outer-spin-button {
margin-bottom: 5px;
}
-.seg {
+.seg, .pres {
position: relative;
display: inline-block;
padding: 8px;
@@ -975,29 +1011,41 @@ input[type=number]::-webkit-outer-spin-button {
border-radius: 20px;
text-align: left;
transition: background-color 0.5s;
- filter: brightness(1);
+ filter: brightness(1); /* required for slider background to render? */
+}
+
+.selected {
+ background-color: var(--c-4);
+}
+/* "selected" CSS class is applied to the segment when it is the main segment.
+ By default, do not highlight. Can be overridden by skin.css */
+.selected.seg {
+ background-color: var(--c-2); /* var(--c-4); */
+}
+.selected .checkmark, .selected .radiokmark {
+ background-color: var(--c-4); /* var(--c-6); */
}
.list {
position: relative;
transition: background-color 0.5s;
- margin: auto auto 10px;
- padding-bottom: 10px;
- width: 230px;
+ margin: auto auto 10px;
+ padding-bottom: 10px;
+ width: 230px;
}
.lstI {
- position: sticky;
- overflow: hidden;
+ position: sticky;
+ overflow: hidden;
}
.fxbtn {
- margin: 20px auto;
- padding: 8px 0;
+ margin: 20px auto;
+ padding: 8px 0;
}
.lstI:hover {
- background: var(--c-4);
+ background: var(--c-4);
}
.lstI:last-child {
@@ -1036,26 +1084,26 @@ input[type=number]::-webkit-outer-spin-button {
margin: 3px 0;
white-space: nowrap;
cursor: pointer;
- font-size: 19px;
+ font-size: 19px;
}
.lstIprev {
width: 220px;
height: 5px;
margin: auto;
- position: absolute;
- bottom: 0px;
- left: 5px;
+ position: absolute;
+ bottom: 0px;
+ left: 5px;
}
input[type="text"].search {
display: block;
width: 230px;
box-sizing: border-box;
- padding: 8px 8px 9px 38px;
- margin: 6px auto 0 auto;
+ padding: 8px 8px 9px 38px;
+ margin: 6px auto 0 auto;
text-align: left;
- background-color: var(--c-3);
+ background-color: var(--c-3);
}
input[type="text"].search:focus {
@@ -1093,9 +1141,13 @@ input[type="text"].search:not(:placeholder-shown) {
}
.hrz {
- width: auto;
- height: 2px;
- background-color: var(--c-b);
+ width: auto;
+ height: 2px;
+ background-color: var(--c-b);
+}
+
+.no-margin {
+ margin: 0;
}
::-webkit-scrollbar {
@@ -1115,8 +1167,8 @@ input[type="text"].search:not(:placeholder-shown) {
@media all and (max-width: 335px) {
.sliderbubble {
- display: none;
- }
+ display: none;
+ }
}
@media all and (max-width: 550px) and (min-width: 374px) {
@@ -1141,6 +1193,6 @@ input[type="text"].search:not(:placeholder-shown) {
@media all and (max-width: 1249px) {
#buttonPcm {
- display: none;
+ display: none;
}
}
diff --git a/wled00/data/index.htm b/wled00/data/index.htm
index 43b810f027..9263b032f8 100644
--- a/wled00/data/index.htm
+++ b/wled00/data/index.htm
@@ -43,30 +43,52 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
White channel
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
RGB color
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
White channel
+
+
+
+
+
+
+
White balance
+
+
+
+
+
+
@@ -86,35 +108,29 @@
-
-
-
-
-
- Color palette
-
-
-
-
-
-
-
- Default
-
-
-
-
-
-
- Loading...
-
+
+
+
+
+
+
+ Color palette
+
+
+
+
+
+
+
+ Default
+
+
-
@@ -122,7 +138,7 @@
Effect speed
-
+
@@ -142,7 +158,7 @@
-
+
@@ -151,7 +167,7 @@
-
+
@@ -160,7 +176,7 @@
-
+
@@ -226,6 +242,11 @@
+
+
+
Loading...
+
+
?
@@ -235,7 +256,6 @@
For best performance, it is recommended to turn off the streaming source when not in use.
-
diff --git a/wled00/data/index.js b/wled00/data/index.js
index f4ed47797e..4f108a67f2 100644
--- a/wled00/data/index.js
+++ b/wled00/data/index.js
@@ -1,16 +1,17 @@
//page js
var loc = false, locip;
var noNewSegs = false;
-var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true, isRgbw = false;
+var isOn = false, nlA = false, isLv = false, isInfo = false, isCEEditor = false, isNodes = false, syncSend = false, syncTglRecv = true;
+var hasWhite = false, hasRGB = false, hasCCT = false;
var whites = [0,0,0];
-var selColors;
+var colors = [[0,0,0],[0,0,0],[0,0,0]];
var expanded = [false];
var powered = [true];
var nlDur = 60, nlTar = 0;
var nlMode = false;
var selectedFx = -1; //WLEDSR: used by togglePcMode, init to nonexisting effect
var sliderControl = ""; //WLEDSR: used by togglePcMode
-var csel = 0;
+var csel = 0; // selected color slot (0-2)
var currentPreset = -1;
var lastUpdate = 0;
var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0;
@@ -39,29 +40,16 @@ var hol = [
];
var cpick = new iro.ColorPicker("#picker", {
- width: 260,
- wheelLightness: false,
- wheelAngle: 90,
- layout: [
- {
- component: iro.ui.Wheel,
- options: {}
- },
- {
- component: iro.ui.Slider,
- options: {
- sliderType: 'value'
- }
- },
- {
- component: iro.ui.Slider,
- options: {
- sliderType: 'kelvin',
- minTemperature: 2100,
- maxTemperature: 10000
- }
- }
- ]
+ width: 260,
+ wheelLightness: false,
+ wheelAngle: 270,
+ wheelDirection: "clockwise",
+ layout: [
+ {
+ component: iro.ui.Wheel,
+ options: {}
+ }
+ ]
});
function handleVisibilityChange() {
@@ -74,29 +62,52 @@ function sCol(na, col) {
d.documentElement.style.setProperty(na, col);
}
+function isRgbBlack(a, s) {
+ return (a[s][0] == 0 && a[s][1] == 0 && a[s][2] == 0);
+}
+
+// returns RGB color from a given slot s 0-2 from color array a
+function rgbStr(a, s) {
+ return "rgb(" + a[s][0] + "," + a[s][1] + "," + a[s][2] + ")";
+}
+
+// brightness approximation for selecting white as text color if background bri < 127, and black if higher
+function rgbBri(a, s) {
+ var R = a[s][0], G = a[s][1], B = a[s][2];
+ return 0.2126*R + 0.7152*G + 0.0722*B;
+}
+
+// sets background of color slot selectors
+function setCSL(s) {
+ var cd = d.getElementsByClassName('cl')[s];
+ var w = whites[s];
+ if (hasRGB && !isRgbBlack(colors, s)) {
+ cd.style.background = rgbStr(colors, s);
+ cd.style.color = rgbBri(colors, s) > 127 ? "#000":"#fff";
+ if (hasWhite && w > 0) {
+ cd.style.background = `linear-gradient(180deg, ${rgbStr(colors, s)} 30%, ${rgbStr([[w,w,w]], 0)})`;
+ }
+ } else {
+ if (!hasWhite) w = 0;
+ cd.style.background = rgbStr([[w,w,w]], 0);
+ cd.style.color = w > 127 ? "#000":"#fff";
+ }
+}
+
function applyCfg()
{
- cTheme(cfg.theme.base === "light");
- var bg = cfg.theme.color.bg;
- if (bg) sCol('--c-1', bg);
- var ccfg = cfg.comp.colors;
- d.getElementById('hexw').style.display = ccfg.hex ? "block":"none";
- d.getElementById('picker').style.display = ccfg.picker ? "block":"none";
- d.getElementById('rgbwrap').style.display = ccfg.rgb ? "block":"none";
- d.getElementById('qcs-w').style.display = ccfg.quick ? "block":"none";
- var l = cfg.comp.labels;
- var e = d.querySelectorAll('.tab-label');
- for (var i=0; i {
- //if (!res.ok) showErrorToast();
- return res.json();
- })
- .then(json => {
- if (Array.isArray(json)) hol = json;
- //TODO: do some parsing first
- })
- .catch(function (error) {
- console.log("holidays.json does not contain array of holidays. Defaults loaded.");
- })
- .finally(function(){
- loadBg(cfg.theme.bg.url);
- });
- } else
- loadBg(cfg.theme.bg.url);
- if (cfg.comp.css) loadSkinCSS('skinCss');
-
- var cd = d.getElementById('csl').children;
- for (var i = 0; i < cd.length; i++) {
- cd[i].style.backgroundColor = "rgb(0, 0, 0)";
- }
- selectSlot(0);
- updateTablinks(0);
- resetUtil();
- cpick.on("input:end", function() {
- setColor(1);
- });
- pmtLS = localStorage.getItem('wledPmt');
- setTimeout(function(){requestJson(null, false);}, 50);
- d.addEventListener("visibilitychange", handleVisibilityChange, false);
- size();
- d.getElementById("cv").style.opacity=0;
- if (localStorage.getItem('pcm') == "true") togglePcMode(true);
- var sls = d.querySelectorAll('input[type="range"]');
- for (var sl of sls) {
- sl.addEventListener('input', updateBubble, true);
- sl.addEventListener('touchstart', toggleBubble);
- sl.addEventListener('touchend', toggleBubble);
- }
+ if (window.location.protocol == "file:") {
+ loc = true;
+ locip = localStorage.getItem('locIp');
+ if (!locip) {
+ locip = prompt("File Mode. Please enter WLED IP!");
+ localStorage.setItem('locIp', locip);
+ }
+ }
+ var sett = localStorage.getItem('wledUiCfg');
+ if (sett) cfg = mergeDeep(cfg, JSON.parse(sett));
+
+ resetPUtil();
+
+ applyCfg();
+ if (cfg.comp.hdays) { //load custom holiday list
+ fetch((loc?`http://${locip}`:'.') + "/holidays.json", { // may be loaded from external source
+ method: 'get'
+ })
+ .then(res => {
+ //if (!res.ok) showErrorToast();
+ return res.json();
+ })
+ .then(json => {
+ if (Array.isArray(json)) hol = json;
+ //TODO: do some parsing first
+ })
+ .catch(function (error) {
+ console.log("No array of holidays in holidays.json. Defaults loaded.");
+ })
+ .finally(function(){
+ loadBg(cfg.theme.bg.url);
+ });
+ } else
+ loadBg(cfg.theme.bg.url);
+ if (cfg.comp.css) loadSkinCSS('skinCss');
+
+ selectSlot(0);
+ updateTablinks(0);
+ resetUtil();
+ cpick.on("input:end", function() {
+ setColor(1);
+ });
+ cpick.on("color:change", updatePSliders);
+ pmtLS = localStorage.getItem('wledPmt');
+ setTimeout(function(){requestJson(null, false);}, 50);
+ d.addEventListener("visibilitychange", handleVisibilityChange, false);
+ size();
+ d.getElementById("cv").style.opacity=0;
+ if (localStorage.getItem('pcm') == "true") togglePcMode(true);
+ var sls = d.querySelectorAll('input[type="range"]');
+ for (var sl of sls) {
+ sl.addEventListener('input', updateBubble, true);
+ sl.addEventListener('touchstart', toggleBubble);
+ sl.addEventListener('touchend', toggleBubble);
+ }
}
function updateTablinks(tabI)
@@ -292,7 +299,9 @@ function showToast(text, error = false) {
}
function showErrorToast() {
- showToast('Connection to light failed!', true);
+ // if we received a timeout force WS reconnect
+ reconnectWS();
+ showToast('Connection to light failed!', true);
}
function clearErrorToast() {
d.getElementById("toast").className = d.getElementById("toast").className.replace("error", "");
@@ -320,13 +329,12 @@ function inforow(key, val, unit = "")
function getLowestUnusedP()
{
- var l = 1;
- for (var key in pJson)
- {
- if (key == l) l++;
- }
- if (l > 250) l = 250;
- return l;
+ var l = 1;
+ for (var key in pJson) {
+ if (key == l) l++;
+ }
+ if (l > 250) l = 250;
+ return l;
}
function checkUsed(i) {
@@ -357,19 +365,19 @@ function papiVal(i) {
}
function qlName(i) {
- if (!pJson[i]) return "";
- if (!pJson[i].ql) return "";
- return pJson[i].ql;
+ if (!pJson[i]) return "";
+ if (!pJson[i].ql) return "";
+ return pJson[i].ql;
}
function cpBck() {
var copyText = d.getElementById("bck");
- copyText.select();
- copyText.setSelectionRange(0, 999999);
- d.execCommand("copy");
+ copyText.select();
+ copyText.setSelectionRange(0, 999999);
+ d.execCommand("copy");
- showToast("Copied to clipboard!");
+ showToast("Copied to clipboard!");
}
function presetError(empty)
@@ -380,26 +388,26 @@ function presetError(empty)
if (bckstr.length > 10) hasBackup = true;
} catch (e) {
- }
- var cn = `
`;
- if (empty)
- cn += `You have no presets yet!`;
- else
- cn += `Sorry, there was an issue loading your presets!`;
-
- if (hasBackup) {
- cn += `
`;
- if (empty)
- cn += `However, there is backup preset data of a previous installation available.
- (Saving a preset will hide this and overwrite the backup)`;
- else
- cn += `Here is a backup of the last known good state:`;
- cn += `
- `;
- }
- cn += `
`;
- d.getElementById('pcont').innerHTML = cn;
- if (hasBackup) d.getElementById('bck').value = bckstr;
+ }
+ var cn = `
`;
+ if (empty)
+ cn += `You have no presets yet!`;
+ else
+ cn += `Sorry, there was an issue loading your presets!`;
+
+ if (hasBackup) {
+ cn += `
`;
+ if (empty)
+ cn += `However, there is backup preset data of a previous installation available.
+ (Saving a preset will hide this and overwrite the backup)`;
+ else
+ cn += `Here is a backup of the last known good state:`;
+ cn += `
+ `;
+ }
+ cn += `
`;
+ d.getElementById('pcont').innerHTML = cn;
+ if (hasBackup) d.getElementById('bck').value = bckstr;
}
function loadPresets(callback = null)
@@ -451,93 +459,91 @@ var pQL = [];
function populateQL()
{
- var cn = "";
- if (pQL.length > 0) {
- cn += `
Quick load
`;
-
- var it = 0;
- for (var key of (pQL||[]))
- {
- cn += ``;
- it++;
- if (it > 4) {
- it = 0;
- cn += ' ';
- }
- }
- if (it != 0) cn+= ' ';
-
- cn += `
`;
}
function makePlUtil() {
- if (pNum < 2) {
- showToast("You need at least 2 presets to make a playlist!"); return;
- }
- if (plJson[0].transition[0] < 0) plJson[0].transition[0] = tr;
- d.getElementById('putil').innerHTML = `
-
- New playlist
-
- ${makeP(0,true)}
`;
+ if (pNum < 1) {
+ showToast("Please make a preset first!"); return;
+ }
+ if (plJson[0].transition[0] < 0) plJson[0].transition[0] = tr;
+ d.getElementById('putil').innerHTML = `
+
+ New playlist
+
+ ${makeP(0,true)}
`;
- refreshPlE(0);
+ refreshPlE(0);
}
function resetPUtil() {
- var cn = `Create preset
- Create playlist `;
- d.getElementById('putil').innerHTML = cn;
+ var cn = `Create preset
+Create playlist `;
+ d.getElementById('putil').innerHTML = cn;
}
function tglCs(i){
@@ -1669,17 +1740,19 @@ function tglCs(i){
function tglSegn(s)
{
- d.getElementById(`seg${s}t`).style.display =
- (window.getComputedStyle(d.getElementById(`seg${s}t`)).display === "none") ? "inline":"none";
+ d.getElementById(`seg${s}t`).style.display =
+ (window.getComputedStyle(d.getElementById(`seg${s}t`)).display === "none") ? "inline":"none";
}
+// Select only the clicked segment and unselect all others
function selSegEx(s)
{
- var obj = {"seg":[]};
- for (let i=0; i<=lSeg; i++){
- obj.seg.push({"sel":(i==s)?true:false});
- }
- requestJson(obj);
+ var obj = {"seg":[]};
+ for (let i=0; i<=lSeg; i++) obj.seg.push({"id":i,"sel":(i==s)});
+ // optionally, force mainseg to be first selected
+ // WLED internally regards the first selected as mainseg regardless of this as long as any segment is selected
+ //obj.mainseg = s;
+ requestJson(obj);
}
function selSeg(s){
@@ -1688,22 +1761,45 @@ function selSeg(s){
requestJson(obj, false);
}
+function rptSeg(s)
+{
+ var name = d.getElementById(`seg${s}t`).value;
+ var start = parseInt(d.getElementById(`seg${s}s`).value);
+ var stop = parseInt(d.getElementById(`seg${s}e`).value);
+ if (stop == 0) return;
+ var rev = d.getElementById(`seg${s}rev`).checked;
+ var mi = d.getElementById(`seg${s}mi`).checked;
+ var sel = d.getElementById(`seg${s}sel`).checked;
+ var obj = {"seg": {"id": s, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop, "rev": rev, "mi": mi, "on": powered[s], "bri": parseInt(d.getElementById(`seg${s}bri`).value), "sel": sel}};
+ if (d.getElementById(`seg${s}grp`)) {
+ var grp = parseInt(d.getElementById(`seg${s}grp`).value);
+ var spc = parseInt(d.getElementById(`seg${s}spc`).value);
+ var ofs = parseInt(d.getElementById(`seg${s}of` ).value);
+ obj.seg.grp = grp;
+ obj.seg.spc = spc;
+ obj.seg.of = ofs;
+ }
+ obj.seg.rpt = true;
+ expand(s);
+ requestJson(obj);
+}
+
function setSeg(s){
- var name = d.getElementById(`seg${s}t`).value;
- var start = parseInt(d.getElementById(`seg${s}s`).value);
- var stop = parseInt(d.getElementById(`seg${s}e`).value);
- if (stop <= start) {delSeg(s); return;}
- var obj = {"seg": {"id": s, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop}};
- if (d.getElementById(`seg${s}grp`))
- {
- var grp = parseInt(d.getElementById(`seg${s}grp`).value);
- var spc = parseInt(d.getElementById(`seg${s}spc`).value);
- var ofs = parseInt(d.getElementById(`seg${s}of` ).value);
- obj.seg.grp = grp;
- obj.seg.spc = spc;
- obj.seg.of = ofs;
- }
- requestJson(obj);
+ var name = d.getElementById(`seg${s}t`).value;
+ var start = parseInt(d.getElementById(`seg${s}s`).value);
+ var stop = parseInt(d.getElementById(`seg${s}e`).value);
+ if ((cfg.comp.seglen && stop == 0) || (!cfg.comp.seglen && stop <= start)) {delSeg(s); return;}
+ var obj = {"seg": {"id": s, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop}};
+ if (d.getElementById(`seg${s}grp`))
+ {
+ var grp = parseInt(d.getElementById(`seg${s}grp`).value);
+ var spc = parseInt(d.getElementById(`seg${s}spc`).value);
+ var ofs = parseInt(d.getElementById(`seg${s}of` ).value);
+ obj.seg.grp = grp;
+ obj.seg.spc = spc;
+ obj.seg.of = ofs;
+ }
+ requestJson(obj);
}
function delSeg(s){
@@ -1751,6 +1847,13 @@ function setSegBri(s){
requestJson(obj);
}
+function tglFreeze(s=null)
+{
+ var obj = {"seg": {"frz": "t"}}; // toggle
+ if (s!==null) obj.id = s;
+ requestJson(obj);
+}
+
// WLEDSR: add extra parameter for slider and color control
function setX(ind = null, extra) {
if (ind === null) {
@@ -1805,18 +1908,18 @@ function setLor(i) {
requestJson(obj);
}
-function setFFT1() {
- var obj = {"seg": {"f1x": parseInt(d.getElementById('sliderFFT1').value)}};
+function setCustom1() {
+ var obj = {"seg": {"c1x": parseInt(d.getElementById('sliderCustom1').value)}};
requestJson(obj);
}
-function setFFT2() {
- var obj = {"seg": {"f2x": parseInt(d.getElementById('sliderFFT2').value)}};
+function setCustom2() {
+ var obj = {"seg": {"c2x": parseInt(d.getElementById('sliderCustom2').value)}};
requestJson(obj);
}
-function setFFT3() {
- var obj = {"seg": {"f3x": parseInt(d.getElementById('sliderFFT3').value)}};
+function setCustom3() {
+ var obj = {"seg": {"c3x": parseInt(d.getElementById('sliderCustom3').value)}};
requestJson(obj);
}
@@ -1885,6 +1988,159 @@ function saveP(i,pl) {
resetPUtil();
}
+// WLEDSR Custom Effects
+function fetchAndExecute(name, callback)
+{
+ var url = (loc?`http://${locip}`:'.') + "/" + name;
+
+ fetch
+ (url, {
+ method: 'get'
+ })
+ .then(res => {
+ if (!res.ok) {
+ showToast("File " + name + " not found");
+ return "";
+ }
+ return res.text();
+ })
+ .then(text => {
+ callback(text);
+ })
+ .catch(function (error) {
+ showToast(error, true);
+ console.log(error);
+ presetError(false);
+ })
+ .finally(() => {
+ // if (callback) setTimeout(callback,99);
+ });
+}
+
+// WLEDSR Custom Effects
+function populateCEEditor(name, segID)
+{
+
+ fetchAndExecute(name + ".wled", function(text)
+ {
+ var cn=`
+ Custom Effects Editor
+ ${name}.wled
+
+
+ Close
+ Save and Run
+ Download ${name}.wled
+ Load template
+ Download wled.json
+ Download presets.json
+ Custom Effects Library
+ Custom Effects Help
+ Compile and Run Log
+
+ Run log > 3 seconds is send to Serial Ouput.`;
+
+ d.getElementById('kceEditor').innerHTML = cn;
+
+ var ceLogArea = d.getElementById("ceLogArea");
+ ceLogArea.value = ".";
+ loadLogFile(name + ".wled.log", 1);
+
+ });
+}
+
+//WLEDSR Custom Effects
+function uploadFileWithText(name, text)
+{
+ var req = new XMLHttpRequest();
+ req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
+ req.addEventListener('error', function(e){showToast(e.stack,true);});
+ req.open("POST", "/upload");
+ var formData = new FormData();
+
+ var blob = new Blob([text], {type : 'application/text'});
+ var fileOfBlob = new File([blob], name);
+ formData.append("upload", fileOfBlob);
+
+ req.send(formData);
+}
+
+//WLEDSR Custom Effects
+function saveCE(name, segID) {
+ showToast("Saving " + name);
+
+ var ceProgramArea = d.getElementById("ceProgramArea");
+
+ uploadFileWithText("/" + name, ceProgramArea.value);
+
+ var obj = {"seg": {"id": segID, "reset": true}};
+ requestJson(obj);
+
+ var ceLogArea = d.getElementById("ceLogArea");
+ ceLogArea.value = ".";
+ setTimeout(() =>
+ {
+ loadLogFile(name + ".log", 1);
+ }, 1000);
+}
+
+function loadLogFile(name, attempt) {
+ var ceLogArea = d.getElementById("ceLogArea");
+ fetchAndExecute(name , function(logtext)
+ {
+ if (logtext == "") {
+ if (attempt < 10) {
+ ceLogArea.value = ("...........").substring(0, attempt + 1);
+ setTimeout(() =>
+ {
+ loadLogFile(name, attempt + 1);
+ }, 1000);
+ }
+ else
+ ceLogArea.value = "log not found after 10 seconds";
+ }
+ else
+ ceLogArea.value = logtext;
+ });
+}
+
+//WLEDSR Custom Effects
+function downloadCEFile(name) {
+ var url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/CustomEffects/wled/" + name;
+
+ var request = new XMLHttpRequest();
+ request.onload = function() {
+ if (name == "wled.json" || name == "presets.json") {
+ if (!confirm('Are you sure to download ' + name + '?'))
+ return;
+ uploadFileWithText("/" + name, request.response);
+ }
+ else
+ {
+ var ceProgramArea = d.getElementById("ceProgramArea");
+ ceProgramArea.value = request.response;
+ }
+ }
+ request.open("GET", url);
+ request.send();
+}
+
+//WLEDSR Custom Effects
+function loadCETemplate(name) {
+ var ceProgramArea = d.getElementById("ceProgramArea");
+ ceProgramArea.value = `/*
+ Custom Effects Template
+*/
+program ${name}
+{
+ function renderFrame()
+ {
+ leds[counter] = colorFromPalette(counter, counter)
+ }
+}`;
+
+}
+
function testPl(i,bt) {
if (bt.dataset.test == 1) {
bt.dataset.test = 0;
@@ -1919,103 +2175,132 @@ function delP(i) {
}
function selectSlot(b) {
- csel = b;
- var cd = d.getElementById('csl').children;
- for (let i = 0; i < cd.length; i++) {
- cd[i].style.border="2px solid white";
- cd[i].style.margin="5px";
- cd[i].style.width="42px";
- }
- cd[csel].style.border="5px solid white";
- cd[csel].style.margin="2px";
- cd[csel].style.width="50px";
- cpick.color.set(cd[csel].style.backgroundColor);
- d.getElementById('sliderW').value = whites[csel];
- updateTrail(d.getElementById('sliderW'));
- updateHex();
- updateRgb();
- redrawPalPrev();
+ csel = b;
+ var cd = d.getElementsByClassName('cl');
+ for (var i of cd) {
+ i.classList.remove("selected");
+ }
+ cd[csel].classList.add("selected");
+ setPicker(rgbStr(colors, csel));
+ //force slider update on initial load (picker "color:change" not fired if black)
+ if (cpick.color.value == 0) updatePSliders();
+ d.getElementById('sliderW').value = whites[csel];
+ updateTrail(d.getElementById('sliderW'));
+ redrawPalPrev();
}
+// set the color from a hex string. Used by quick color selectors
var lasth = 0;
function pC(col)
{
- if (col == "rnd")
- {
- col = {h: 0, s: 0, v: 100};
- col.s = Math.floor((Math.random() * 50) + 50);
- do {
- col.h = Math.floor(Math.random() * 360);
- } while (Math.abs(col.h - lasth) < 50);
- lasth = col.h;
- }
- cpick.color.set(col);
- setColor(0);
+ if (col == "rnd")
+ {
+ col = {h: 0, s: 0, v: 100};
+ col.s = Math.floor((Math.random() * 50) + 50);
+ do {
+ col.h = Math.floor(Math.random() * 360);
+ } while (Math.abs(col.h - lasth) < 50);
+ lasth = col.h;
+ }
+ setPicker(col);
+ setColor(0);
+}
+
+function updatePSliders() {
+ //update RGB sliders
+ var col = cpick.color.rgb;
+ var s = d.getElementById('sliderR');
+ s.value = col.r; updateTrail(s,1);
+ s = d.getElementById('sliderG');
+ s.value = col.g; updateTrail(s,2);
+ s = d.getElementById('sliderB');
+ s.value = col.b; updateTrail(s,3);
+
+ // update hex field
+ var str = cpick.color.hexString.substring(1);
+ var w = whites[csel];
+ if (w > 0) str += w.toString(16);
+ d.getElementById('hexc').value = str;
+ d.getElementById('hexcnf').style.backgroundColor = "var(--c-3)";
+
+ // update value slider
+ var v = d.getElementById('sliderV');
+ v.value = cpick.color.value;
+ // background color as if color had full value
+ var hsv = {"h":cpick.color.hue,"s":cpick.color.saturation,"v":100};
+ var c = iro.Color.hsvToRgb(hsv);
+ var cs = 'rgb('+c.r+','+c.g+','+c.b+')';
+ v.parentNode.getElementsByClassName('sliderdisplay')[0].style.setProperty('--bg',cs);
+ updateTrail(v);
+
+ // update Kelvin slider
+ d.getElementById('sliderK').value = cpick.color.kelvin;
+}
+
+// Fired when a key is pressed while in the HEX color input
+function hexEnter() {
+ d.getElementById('hexcnf').style.backgroundColor = "var(--c-6)";
+ if(event.keyCode == 13) fromHex();
}
-function updateRgb()
-{
- var col = cpick.color.rgb;
- var s = d.getElementById('sliderR');
- s.value = col.r; updateTrail(s,1);
- s = d.getElementById('sliderG');
- s.value = col.g; updateTrail(s,2);
- s = d.getElementById('sliderB');
- s.value = col.b; updateTrail(s,3);
+// Fired when a key is pressed while in a segment input
+function segEnter(s) {
+ if(event.keyCode == 13) setSeg(s);
}
-function updateHex()
+function fromHex()
{
- var str = cpick.color.hexString;
- str = str.substring(1);
- var w = whites[csel];
- if (w > 0) str += w.toString(16);
- d.getElementById('hexc').value = str;
- d.getElementById('hexcnf').style.backgroundColor = "var(--c-3)";
+ var str = d.getElementById('hexc').value;
+ whites[csel] = parseInt(str.substring(6), 16);
+ try {
+ setPicker("#" + str.substring(0,6));
+ } catch (e) {
+ setPicker("#ffaa00");
+ }
+ if (isNaN(whites[csel])) whites[csel] = 0;
+ setColor(2);
}
-function hexEnter() {
- d.getElementById('hexcnf').style.backgroundColor = "var(--c-6)";
- if(event.keyCode == 13) fromHex();
+function setPicker(rgb) {
+ var c = new iro.Color(rgb);
+ if (c.value > 0) cpick.color.set(c);
+ else cpick.color.setChannel('hsv', 'v', 0);
}
-function fromHex()
+function fromV()
{
- var str = d.getElementById('hexc').value;
- whites[csel] = parseInt(str.substring(6), 16);
- try {
- cpick.color.set("#" + str.substring(0,6));
- } catch (e) {
- cpick.color.set("#ffaa00");
- }
- if (isNaN(whites[csel])) whites[csel] = 0;
- setColor(2);
+ cpick.color.setChannel('hsv', 'v', d.getElementById('sliderV').value);
+}
+
+function fromK()
+{
+ cpick.color.set({ kelvin: d.getElementById('sliderK').value });
}
function fromRgb()
{
- var r = d.getElementById('sliderR').value;
- var g = d.getElementById('sliderG').value;
- var b = d.getElementById('sliderB').value;
- cpick.color.set(`rgb(${r},${g},${b})`);
- setColor(0);
+ var r = d.getElementById('sliderR').value;
+ var g = d.getElementById('sliderG').value;
+ var b = d.getElementById('sliderB').value;
+ setPicker(`rgb(${r},${g},${b})`);
}
+// sr 0: from RGB sliders, 1: from picker, 2: from hex
function setColor(sr) {
- var cd = d.getElementById('csl').children;
- if (sr == 1 && cd[csel].style.backgroundColor == 'rgb(0, 0, 0)') cpick.color.setChannel('hsv', 'v', 100);
- cd[csel].style.backgroundColor = cpick.color.rgbString;
- if (sr != 2) whites[csel] = d.getElementById('sliderW').value;
- var col = cpick.color.rgb;
- var obj = {"seg": {"col": [[col.r, col.g, col.b, whites[csel]],[],[]]}};
- if (csel == 1) {
- obj = {"seg": {"col": [[],[col.r, col.g, col.b, whites[csel]],[]]}};
- } else if (csel == 2) {
- obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}};
- }
- updateHex();
- updateRgb();
- requestJson(obj);
+ if (sr == 1 && colors[csel][0] == 0 && colors[csel][1] == 0 && colors[csel][2] == 0) cpick.color.setChannel('hsv', 'v', 100);
+ if (sr != 2) whites[csel] = parseInt(d.getElementById('sliderW').value);
+ var col = cpick.color.rgb;
+ colors[csel] = [col.r, col.g, col.b, whites[csel]];
+ setCSL(csel);
+ var obj = {"seg": {"col": [[],[],[]]}};
+ obj.seg.col[csel] = colors[csel];
+ requestJson(obj);
+}
+
+function setBalance(b)
+{
+ var obj = {"seg": {"cct": parseInt(b)}};
+ requestJson(obj);
}
var hc = 0;
@@ -2030,53 +2315,52 @@ function openGH()
var cnfr = false;
function cnfReset()
{
- if (!cnfr)
- {
- var bt = d.getElementById('resetbtn');
- bt.style.color = "#f00";
- bt.innerHTML = "Confirm Reboot";
- cnfr = true; return;
- }
- window.location.href = "/reset";
+ if (!cnfr)
+ {
+ var bt = d.getElementById('resetbtn');
+ bt.style.color = "#f00";
+ bt.innerHTML = "Confirm Reboot";
+ cnfr = true; return;
+ }
+ window.location.href = "/reset";
}
var cnfrS = false;
function rSegs()
{
- var bt = d.getElementById('rsbtn');
- if (!cnfrS)
- {
- bt.style.color = "#f00";
- bt.innerHTML = "Confirm reset";
- cnfrS = true; return;
- }
- cnfrS = false;
- bt.style.color = "#fff";
- bt.innerHTML = "Reset segments";
- var obj = {"seg":[{"start":0,"stop":ledCount,"sel":true}]};
- for (let i=1; i<=lSeg; i++){
- obj.seg.push({"stop":0});
- }
- requestJson(obj);
+ var bt = d.getElementById('rsbtn');
+ if (!cnfrS)
+ {
+ bt.style.color = "#f00";
+ bt.innerHTML = "Confirm reset";
+ cnfrS = true; return;
+ }
+ cnfrS = false;
+ bt.style.color = "#fff";
+ bt.innerHTML = "Reset segments";
+ var obj = {"seg":[{"start":0,"stop":ledCount,"sel":true}]};
+ for (let i=1; i<=lSeg; i++){
+ obj.seg.push({"stop":0});
+ }
+ requestJson(obj);
}
function loadPalettesData(callback = null)
{
- if (palettesData) return;
- const lsKey = "wledPalx";
- var palettesDataJson = localStorage.getItem(lsKey);
- if (palettesDataJson) {
- try {
- palettesDataJson = JSON.parse(palettesDataJson);
- var d = new Date();
- if (palettesDataJson && palettesDataJson.vid == lastinfo.vid) {
- palettesData = palettesDataJson.p;
- //redrawPalPrev() //?
- if (callback) callback();
- return;
- }
- } catch (e) {}
- }
+ if (palettesData) return;
+ const lsKey = "wledPalx";
+ var palettesDataJson = localStorage.getItem(lsKey);
+ if (palettesDataJson) {
+ try {
+ palettesDataJson = JSON.parse(palettesDataJson);
+ var d = new Date();
+ if (palettesDataJson && palettesDataJson.vid == lastinfo.vid) {
+ palettesData = palettesDataJson.p;
+ if (callback) callback();
+ return;
+ }
+ } catch (e) {}
+ }
palettesData = {};
getPalettesData(0, function() {
@@ -2123,25 +2407,25 @@ function getPalettesData(page, callback)
}
function search(searchField) {
- var searchText = searchField.value.toUpperCase();
- searchField.parentElement.getElementsByClassName('search-cancel-icon')[0].style.display = (searchText.length < 1)?"none":"inline";
- var elements = searchField.parentElement.parentElement.querySelectorAll('.lstI');
- for (i = 0; i < elements.length; i++) {
- var item = elements[i];
- var itemText = item.querySelector('.lstIname').innerText.toUpperCase();
- if (itemText.indexOf(searchText) > -1) {
- item.style.display = "";
- } else {
- item.style.display = "none";
- }
- }
+ var searchText = searchField.value.toUpperCase();
+ searchField.parentElement.getElementsByClassName('search-cancel-icon')[0].style.display = (searchText.length < 1)?"none":"inline";
+ var elements = searchField.parentElement.parentElement.querySelectorAll('.lstI');
+ for (i = 0; i < elements.length; i++) {
+ var item = elements[i];
+ var itemText = item.querySelector('.lstIname').innerText.toUpperCase();
+ if (itemText.indexOf(searchText) > -1) {
+ item.style.display = "";
+ } else {
+ item.style.display = "none";
+ }
+ }
}
function cancelSearch(ic) {
- var searchField = ic.parentElement.getElementsByClassName('search')[0];
- searchField.value = "";
- search(searchField);
- searchField.focus();
+ var searchField = ic.parentElement.getElementsByClassName('search')[0];
+ searchField.value = "";
+ search(searchField);
+ searchField.focus();
}
// make sure "dur" and "transition" are arrays with at least the length of "ps"
@@ -2174,43 +2458,43 @@ function formatArr(pl) {
function expand(i,a)
{
- if (!a) expanded[i] = !expanded[i];
- d.getElementById('seg' +i).style.display = (expanded[i]) ? "block":"none";
- d.getElementById('sege' +i).style.transform = (expanded[i]) ? "rotate(180deg)":"rotate(0deg)";
- if (i < 100) {
- d.getElementById(`seg${i}nedit`).style.display = (expanded[i]) ? "inline":"none";
- return; // no preset, we are done
- }
+ if (!a) expanded[i] = !expanded[i];
+ d.getElementById('seg' +i).style.display = (expanded[i]) ? "block":"none";
+ d.getElementById('sege' +i).style.transform = (expanded[i]) ? "rotate(180deg)":"rotate(0deg)";
+ if (i < 100) {
+ d.getElementById(`seg${i}nedit`).style.display = (expanded[i]) ? "inline":"none";
+ return; //no preset, we are done
+ }
- var p = i-100;
- d.getElementById(`p${p}o`).style.background = (expanded[i] || p != currentPreset)?"var(--c-2)":"var(--c-6)";
- if (d.getElementById('seg' +i).innerHTML != "") return;
- if (isPlaylist(p)) {
- plJson[p] = pJson[p].playlist;
- // make sure all keys are present in plJson[p]
- formatArr(plJson[p]);
- if (isNaN(plJson[p].repeat)) plJson[p].repeat = 0;
- if (!plJson[p].r) plJson[p].r = false;
- if (isNaN(plJson[p].end)) plJson[p].end = 0;
-
- d.getElementById('seg' +i).innerHTML = makeP(p,true);
- refreshPlE(p);
- } else {
- d.getElementById('seg' +i).innerHTML = makeP(p);
- }
- var papi = papiVal(p);
- d.getElementById(`p${p}api`).value = papi;
- if (papi.indexOf("Please") == 0) d.getElementById(`p${p}cstgl`).checked = true;
- tglCs(p);
+ var p = i-100;
+ d.getElementById(`p${p}o`).style.background = (expanded[i] || p != currentPreset)?"var(--c-2)":"var(--c-6)";
+ if (d.getElementById('seg' +i).innerHTML != "") return;
+ if (isPlaylist(p)) {
+ plJson[p] = pJson[p].playlist;
+ //make sure all keys are present in plJson[p]
+ formatArr(plJson[p]);
+ if (isNaN(plJson[p].repeat)) plJson[p].repeat = 0;
+ if (!plJson[p].r) plJson[p].r = false;
+ if (isNaN(plJson[p].end)) plJson[p].end = 0;
+
+ d.getElementById('seg' +i).innerHTML = makeP(p,true);
+ refreshPlE(p);
+ } else {
+ d.getElementById('seg' +i).innerHTML = makeP(p);
+ }
+ var papi = papiVal(p);
+ d.getElementById(`p${p}api`).value = papi;
+ if (papi.indexOf("Please") == 0) d.getElementById(`p${p}cstgl`).checked = true;
+ tglCs(p);
}
function unfocusSliders() {
d.getElementById("sliderBri").blur();
d.getElementById("sliderSpeed").blur();
d.getElementById("sliderIntensity").blur();
- d.getElementById("sliderFFT1").blur();
- d.getElementById("sliderFFT2").blur();
- d.getElementById("sliderFFT3").blur();
+ d.getElementById("sliderCustom1").blur();
+ d.getElementById("sliderCustom2").blur();
+ d.getElementById("sliderCustom3").blur();
}
@@ -2251,28 +2535,28 @@ function move(e) {
var s = Math.sign(dx);
var f = +(s*dx/w).toFixed(2);
- if((clientX != 0) &&
- (iSlide > 0 || s < 0) && (iSlide < N - 1 || s > 0) &&
- f > 0.12 &&
- d.getElementsByClassName("tabcontent")[iSlide].scrollTop == scrollS) {
- _C.style.setProperty('--i', iSlide -= s);
- f = 1 - f;
- updateTablinks(iSlide);
- }
- _C.style.setProperty('--f', f);
- _C.classList.toggle('smooth', !(locked = false));
- x0 = null;
+ if ((clientX != 0) &&
+ (iSlide > 0 || s < 0) && (iSlide < N - 1 || s > 0) &&
+ f > 0.12 &&
+ d.getElementsByClassName("tabcontent")[iSlide].scrollTop == scrollS) {
+ _C.style.setProperty('--i', iSlide -= s);
+ f = 1 - f;
+ updateTablinks(iSlide);
+ }
+ _C.style.setProperty('--f', f);
+ _C.classList.toggle('smooth', !(locked = false));
+ x0 = null;
}
function size() {
- w = window.innerWidth;
- d.getElementById('buttonNodes').style.display = (lastinfo.ndc > 0 && w > 770) ? "block":"none";
- var h = d.getElementById('top').clientHeight;
- sCol('--th', h + "px");
- sCol('--bh', d.getElementById('bot').clientHeight + "px");
- if (isLv) h -= 4;
- sCol('--tp', h + "px");
- togglePcMode();
+ w = window.innerWidth;
+ d.getElementById('buttonNodes').style.display = (lastinfo.ndc > 0 && w > 770) ? "block":"none";
+ var h = d.getElementById('top').clientHeight;
+ sCol('--th', h + "px");
+ sCol('--bh', d.getElementById('bot').clientHeight + "px");
+ if (isLv) h -= 4;
+ sCol('--tp', h + "px");
+ togglePcMode();
}
function togglePcMode(fromB = false)
diff --git a/wled00/data/iro.js b/wled00/data/iro.js
index f459e417c7..3d63d04183 100644
--- a/wled00/data/iro.js
+++ b/wled00/data/iro.js
@@ -1,7 +1,7 @@
/*!
- * iro.js v5.3.1
- * 2016-2020 James Daniel
+ * iro.js v5.5.2
+ * 2016-2021 James Daniel
* Licensed under MPL 2.0
* github.com/jaames/iro.js
*/
-!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).iro=n()}(this,function(){"use strict";var k,s,n,i,o,m={},M=[],r=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i;function j(t,n){for(var i in n)t[i]=n[i];return t}function b(t){var n=t.parentNode;n&&n.removeChild(t)}function d(t,n,i){var r,e,u,o,l=arguments;if(n=j({},n),3=r/i?u=n:e=n}return n},function(t,n,i){n&&g(t.prototype,n),i&&g(t,i)}(l,[{key:"hsv",get:function(){var t=this.$;return{h:t.h,s:t.s,v:t.v}},set:function(t){var n=this.$;if(t=p({},n,t),this.onChange){var i={h:!1,v:!1,s:!1,a:!1};for(var r in n)i[r]=t[r]!=n[r];this.$=t,(i.h||i.s||i.v||i.a)&&this.onChange(this,i)}else this.$=t}},{key:"hsva",get:function(){return p({},this.$)},set:function(t){this.hsv=t}},{key:"hue",get:function(){return this.$.h},set:function(t){this.hsv={h:t}}},{key:"saturation",get:function(){return this.$.s},set:function(t){this.hsv={s:t}}},{key:"value",get:function(){return this.$.v},set:function(t){this.hsv={v:t}}},{key:"alpha",get:function(){return this.$.a},set:function(t){this.hsv=p({},this.hsv,{a:t})}},{key:"kelvin",get:function(){return l.rgbToKelvin(this.rgb)},set:function(t){this.rgb=l.kelvinToRgb(t)}},{key:"red",get:function(){return this.rgb.r},set:function(t){this.rgb=p({},this.rgb,{r:t})}},{key:"green",get:function(){return this.rgb.g},set:function(t){this.rgb=p({},this.rgb,{g:t})}},{key:"blue",get:function(){return this.rgb.b},set:function(t){this.rgb=p({},this.rgb,{b:t})}},{key:"rgb",get:function(){var t=l.hsvToRgb(this.$),n=t.r,i=t.g,r=t.b;return{r:U(n),g:U(i),b:U(r)}},set:function(t){this.hsv=p({},l.rgbToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"rgba",get:function(){return p({},this.rgb,{a:this.alpha})},set:function(t){this.rgb=t}},{key:"hsl",get:function(){var t=l.hsvToHsl(this.$),n=t.h,i=t.s,r=t.l;return{h:U(n),s:U(i),l:U(r)}},set:function(t){this.hsv=p({},l.hslToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"hsla",get:function(){return p({},this.hsl,{a:this.alpha})},set:function(t){this.hsl=t}},{key:"rgbString",get:function(){var t=this.rgb;return"rgb("+t.r+", "+t.g+", "+t.b+")"},set:function(t){var n,i,r,e,u=1;if((n=P.exec(t))?(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255)):(n=z.exec(t))&&(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255),u=K(n[4],1)),!n)throw new Error("Invalid rgb string");this.rgb={r:i,g:r,b:e,a:u}}},{key:"rgbaString",get:function(){var t=this.rgba;return"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a+")"},set:function(t){this.rgbString=t}},{key:"hexString",get:function(){var t=this.rgb;return"#"+V(t.r)+V(t.g)+V(t.b)},set:function(t){var n,i,r,e,u=255;if((n=C.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3])):(n=D.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3]),u=17*Q(n[4])):(n=F.exec(t))?(i=Q(n[1]),r=Q(n[2]),e=Q(n[3])):(n=G.exec(t))&&(i=Q(n[1]),r=Q(n[2]),e=Q(n[3]),u=Q(n[4])),!n)throw new Error("Invalid hex string");this.rgb={r:i,g:r,b:e,a:u/255}}},{key:"hex8String",get:function(){var t=this.rgba;return"#"+V(t.r)+V(t.g)+V(t.b)+V(q(255*t.a))},set:function(t){this.hexString=t}},{key:"hslString",get:function(){var t=this.hsl;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%)"},set:function(t){var n,i,r,e,u=1;if((n=H.exec(t))?(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100)):(n=$.exec(t))&&(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100),u=K(n[4],1)),!n)throw new Error("Invalid hsl string");this.hsl={h:i,s:r,l:e,a:u}}},{key:"hslaString",get:function(){var t=this.hsla;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%, "+t.a+")"},set:function(t){this.hslString=t}}]),l}();function Z(t){var n,i=t.width,r=t.sliderSize,e=t.borderWidth,u=t.handleRadius,o=t.padding,l=t.sliderShape,s="horizontal"===t.layoutDirection;return r=null!=(n=r)?n:2*o+2*u+2*e,"circle"===l?{handleStart:t.padding+t.handleRadius,handleRange:i-2*o-2*u-2*e,width:i,height:i,cx:i/2,cy:i/2,radius:i/2-e/2}:{handleStart:r/2,handleRange:i-r,radius:r/2,x:0,y:0,width:s?r:i,height:s?i:r}}function tt(t,n){var i=Z(t),r=i.width,e=i.height,u=i.handleRange,o=i.handleStart,l="horizontal"===t.layoutDirection,s=l?r/2:e/2,c=o+function(t,n){var i=n.hsva,r=n.rgb;switch(t.sliderType){case"red":return r.r/2.55;case"green":return r.g/2.55;case"blue":return r.b/2.55;case"alpha":return 100*i.a;case"kelvin":var e=t.minTemperature,u=t.maxTemperature-e,o=(n.kelvin-e)/u*100;return Math.max(0,Math.min(o,100));case"hue":return i.h/=3.6;case"saturation":return i.s;case"value":default:return i.v}}(t,n)/100*u;return l&&(c=-1*c+u+2*o),{x:l?s:c,y:l?c:s}}function nt(t){var n=t.width/2;return{width:t.width,radius:n-t.borderWidth,cx:n,cy:n}}function it(t,n,i){var r=t.wheelAngle,e=t.wheelDirection;return((n=!i&&"clockwise"===e||i&&"anticlockwise"===e?(i?180:360)-(r-n):r+n)%360+360)%360}function rt(t,n,i){var r=nt(t),e=r.cx,u=r.cy,o=t.width/2-t.padding-t.handleRadius-t.borderWidth;n=e-n,i=u-i;var l=it(t,Math.atan2(-i,-n)*(180/Math.PI)),s=Math.min(Math.sqrt(n*n+i*i),o);return{h:Math.round(l),s:Math.round(100/o*s)}}function et(t){var n=t.width,i=t.boxHeight;return{width:n,height:null!=i?i:n,radius:t.padding+t.handleRadius}}function ut(t,n,i){var r=et(t),e=r.width,u=r.height,o=r.radius,l=(n-o)/(e-2*o)*100,s=(i-o)/(u-2*o)*100;return{s:Math.max(0,Math.min(l,100)),v:Math.max(0,Math.min(100-s,100))}}function ot(t){X=X||document.getElementsByTagName("base");var n=window.navigator.userAgent,i=/^((?!chrome|android).)*safari/i.test(n),r=/iPhone|iPod|iPad/i.test(n),e=window.location;return(i||r)&&0=r/i?u=n:e=n}return n},function(t,n,i){n&&g(t.prototype,n),i&&g(t,i)}(l,[{key:"hsv",get:function(){var t=this.$;return{h:t.h,s:t.s,v:t.v}},set:function(t){var n=this.$;if(t=b({},n,t),this.onChange){var i={h:!1,v:!1,s:!1,a:!1};for(var r in n)i[r]=t[r]!=n[r];this.$=t,(i.h||i.s||i.v||i.a)&&this.onChange(this,i)}else this.$=t}},{key:"hsva",get:function(){return b({},this.$)},set:function(t){this.hsv=t}},{key:"hue",get:function(){return this.$.h},set:function(t){this.hsv={h:t}}},{key:"saturation",get:function(){return this.$.s},set:function(t){this.hsv={s:t}}},{key:"value",get:function(){return this.$.v},set:function(t){this.hsv={v:t}}},{key:"alpha",get:function(){return this.$.a},set:function(t){this.hsv=b({},this.hsv,{a:t})}},{key:"kelvin",get:function(){return l.rgbToKelvin(this.rgb)},set:function(t){this.rgb=l.kelvinToRgb(t)}},{key:"red",get:function(){return this.rgb.r},set:function(t){this.rgb=b({},this.rgb,{r:t})}},{key:"green",get:function(){return this.rgb.g},set:function(t){this.rgb=b({},this.rgb,{g:t})}},{key:"blue",get:function(){return this.rgb.b},set:function(t){this.rgb=b({},this.rgb,{b:t})}},{key:"rgb",get:function(){var t=l.hsvToRgb(this.$),n=t.r,i=t.g,r=t.b;return{r:G(n),g:G(i),b:G(r)}},set:function(t){this.hsv=b({},l.rgbToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"rgba",get:function(){return b({},this.rgb,{a:this.alpha})},set:function(t){this.rgb=t}},{key:"hsl",get:function(){var t=l.hsvToHsl(this.$),n=t.h,i=t.s,r=t.l;return{h:G(n),s:G(i),l:G(r)}},set:function(t){this.hsv=b({},l.hslToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"hsla",get:function(){return b({},this.hsl,{a:this.alpha})},set:function(t){this.hsl=t}},{key:"rgbString",get:function(){var t=this.rgb;return"rgb("+t.r+", "+t.g+", "+t.b+")"},set:function(t){var n,i,r,e,u=1;if((n=_.exec(t))?(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255)):(n=H.exec(t))&&(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255),u=K(n[4],1)),!n)throw new Error("Invalid rgb string");this.rgb={r:i,g:r,b:e,a:u}}},{key:"rgbaString",get:function(){var t=this.rgba;return"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a+")"},set:function(t){this.rgbString=t}},{key:"hexString",get:function(){var t=this.rgb;return"#"+U(t.r)+U(t.g)+U(t.b)},set:function(t){var n,i,r,e,u=255;if((n=D.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3])):(n=F.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3]),u=17*Q(n[4])):(n=L.exec(t))?(i=Q(n[1]),r=Q(n[2]),e=Q(n[3])):(n=B.exec(t))&&(i=Q(n[1]),r=Q(n[2]),e=Q(n[3]),u=Q(n[4])),!n)throw new Error("Invalid hex string");this.rgb={r:i,g:r,b:e,a:u/255}}},{key:"hex8String",get:function(){var t=this.rgba;return"#"+U(t.r)+U(t.g)+U(t.b)+U(Z(255*t.a))},set:function(t){this.hexString=t}},{key:"hslString",get:function(){var t=this.hsl;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%)"},set:function(t){var n,i,r,e,u=1;if((n=P.exec(t))?(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100)):(n=$.exec(t))&&(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100),u=K(n[4],1)),!n)throw new Error("Invalid hsl string");this.hsl={h:i,s:r,l:e,a:u}}},{key:"hslaString",get:function(){var t=this.hsla;return"hsla("+t.h+", "+t.s+"%, "+t.l+"%, "+t.a+")"},set:function(t){this.hslString=t}}]),l}();function X(t){var n,i=t.width,r=t.sliderSize,e=t.borderWidth,u=t.handleRadius,o=t.padding,l=t.sliderShape,s="horizontal"===t.layoutDirection;return r=null!=(n=r)?n:2*o+2*u,"circle"===l?{handleStart:t.padding+t.handleRadius,handleRange:i-2*o-2*u,width:i,height:i,cx:i/2,cy:i/2,radius:i/2-e/2}:{handleStart:r/2,handleRange:i-r,radius:r/2,x:0,y:0,width:s?r:i,height:s?i:r}}function Y(t,n){var i=X(t),r=i.width,e=i.height,u=i.handleRange,o=i.handleStart,l="horizontal"===t.layoutDirection,s=l?r/2:e/2,c=o+function(t,n){var i=n.hsva,r=n.rgb;switch(t.sliderType){case"red":return r.r/2.55;case"green":return r.g/2.55;case"blue":return r.b/2.55;case"alpha":return 100*i.a;case"kelvin":var e=t.minTemperature,u=t.maxTemperature-e,o=(n.kelvin-e)/u*100;return Math.max(0,Math.min(o,100));case"hue":return i.h/=3.6;case"saturation":return i.s;case"value":default:return i.v}}(t,n)/100*u;return l&&(c=-1*c+u+2*o),{x:l?s:c,y:l?c:s}}var tt,nt=2*Math.PI,it=function(t,n){return(t%n+n)%n},rt=function(t,n){return Math.sqrt(t*t+n*n)};function et(t){return t.width/2-t.padding-t.handleRadius-t.borderWidth}function ut(t){var n=t.width/2;return{width:t.width,radius:n-t.borderWidth,cx:n,cy:n}}function ot(t,n,i){var r=t.wheelAngle,e=t.wheelDirection;return i&&"clockwise"===e?n=r+n:"clockwise"===e?n=360-r+n:i&&"anticlockwise"===e?n=r+180-n:"anticlockwise"===e&&(n=r-n),it(n,360)}function lt(t,n,i){var r=ut(t),e=r.cx,u=r.cy,o=et(t);n=e-n,i=u-i;var l=ot(t,Math.atan2(-i,-n)*(360/nt)),s=Math.min(rt(n,i),o);return{h:Math.round(l),s:Math.round(100/o*s)}}function st(t){var n=t.width,i=t.boxHeight;return{width:n,height:null!=i?i:n,radius:t.padding+t.handleRadius}}function ct(t,n,i){var r=st(t),e=r.width,u=r.height,o=r.radius,l=(n-o)/(e-2*o)*100,s=(i-o)/(u-2*o)*100;return{s:Math.max(0,Math.min(l,100)),v:Math.max(0,Math.min(100-s,100))}}function at(t,n,i,r){for(var e=0;e 6) leddata = leddata.substring(2);
- str += "#" + leddata;
- if (i < len -1) str += ","
+ for (i = 2; i < len; i+=3) {
+ str += `rgb(${leds[i]},${leds[i+1]},${leds[i+2]})`;
+ if (i < len -3) str += ","
}
str += ")";
document.getElementById("canv").style.background = str;
}
- function getLiveJson(event) {
+ function getLiveJson(e) {
try {
- var json = JSON.parse(event.data);
- if (json && json.leds) {
- requestAnimationFrame(function () {updatePreview(json.leds);});
- }
+ if (toString.call(e.data) === '[object ArrayBuffer]') {
+ let leds = new Uint8Array(event.data);
+ if (leds[0] != 76) return; //'L'
+ updatePreview(leds);
+ }
}
catch (err) {
- console.error("Live-Preview ws error:",err);
+ console.error("Peek WS error:",err);
}
}
var ws = top.window.ws;
if (ws && ws.readyState === WebSocket.OPEN) {
- console.info("Use top WS for peek");
+ console.info("Peek uses top WS");
ws.send("{'lv':true}");
} else {
- console.info("Peek ws opening");
- ws = new WebSocket("ws://"+document.location.host+"/ws");
+ console.info("Peek WS opening");
+ ws = new WebSocket((window.location.protocol == "https:"?"wss":"ws")+"://"+document.location.host+"/ws");
ws.onopen = function () {
- console.info("Peek WS opened");
+ console.info("Peek WS open");
ws.send("{'lv':true}");
}
}
+ ws.binaryType = "arraybuffer";
ws.addEventListener('message',getLiveJson);
+
diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm
index cd0743d692..c8c2f32278 100644
--- a/wled00/data/settings_leds.htm
+++ b/wled00/data/settings_leds.htm
@@ -3,22 +3,15 @@
+
LED Settings