Merge branch 'segment-api' into audioreactive-prototype

This commit is contained in:
Blaz Kristan 2022-07-27 21:35:29 +02:00
commit a6f31a577a
53 changed files with 8161 additions and 7297 deletions

6
package-lock.json generated
View File

@ -2067,9 +2067,9 @@
"integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw=="
},
"terser": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
"integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
"requires": {
"commander": "^2.20.0",
"source-map": "~0.6.1",

View File

@ -263,120 +263,60 @@ writeChunks(
name: "PAGE_settings",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=0\"></script>"
)
},
{
file: "settings_wifi.htm",
name: "PAGE_settings_wifi",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=1\"></script>"
)
},
{
file: "settings_leds.htm",
name: "PAGE_settings_leds",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=2\"></script>"
)
},
{
file: "settings_dmx.htm",
name: "PAGE_settings_dmx",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=7\"></script>"
)
},
{
file: "settings_ui.htm",
name: "PAGE_settings_ui",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=3\"></script>"
)
},
{
file: "settings_sync.htm",
name: "PAGE_settings_sync",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=4\"></script>"
)
},
{
file: "settings_time.htm",
name: "PAGE_settings_time",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=5\"></script>"
)
},
{
file: "settings_sec.htm",
name: "PAGE_settings_sec",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=6\"></script>"
)
},
{
file: "settings_um.htm",
name: "PAGE_settings_um",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=8\"></script>"
)
},
{
file: "settings_2D.htm",
name: "PAGE_settings_2D",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=10\"></script>"
)
},
{
file: "settings_pin.htm",

View File

@ -103,25 +103,24 @@ class Animated_Staircase : public Usermod {
void updateSegments() {
mainSegmentId = strip.getMainSegmentId();
WS2812FX::Segment* segments = strip.getSegments();
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
if (!segments->isActive()) {
for (int i = 0; i < strip.getSegmentsNum(); i++) {
Segment &seg = strip.getSegment(i);
if (!seg.isActive()) {
maxSegmentId = i - 1;
break;
}
if (i >= onIndex && i < offIndex) {
segments->setOption(SEG_OPTION_ON, 1, i);
seg.setOption(SEG_OPTION_ON, true);
// We may need to copy mode and colors from segment 0 to make sure
// changes are propagated even when the config is changed during a wipe
// segments->mode = mainsegment.mode;
// segments->colors[0] = mainsegment.colors[0];
} else {
segments->setOption(SEG_OPTION_ON, 0, i);
seg.setOption(SEG_OPTION_ON, false);
}
// Always mark segments as "transitional", we are animating the staircase
segments->setOption(SEG_OPTION_TRANSITIONAL, 1, i);
seg.setOption(SEG_OPTION_TRANSITIONAL, true);
}
colorUpdated(CALL_MODE_DIRECT_CHANGE);
}
@ -290,13 +289,13 @@ class Animated_Staircase : public Usermod {
}
} else {
// Restore segment options
WS2812FX::Segment* segments = strip.getSegments();
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
if (!segments->isActive()) {
for (int i = 0; i < strip.getSegmentsNum(); i++) {
Segment &seg = strip.getSegment(i);
if (!seg.isActive()) {
maxSegmentId = i - 1;
break;
}
segments->setOption(SEG_OPTION_ON, 1, i);
seg.setOption(SEG_OPTION_ON, true);
}
colorUpdated(CALL_MODE_DIRECT_CHANGE);
DEBUG_PRINTLN(F("Animated Staircase disabled."));

View File

@ -1,36 +1,60 @@
Hello! I have written a v2 usermod for the BME280/BMP280 sensor based on the [existing v1 usermod](https://github.com/Aircoookie/WLED/blob/master/usermods/Wemos_D1_mini%2BWemos32_mini_shield/usermod_bme280.cpp). It is not just a refactor, there are many changes which I made to fit my use case, and I hope they will fit the use cases of others as well! Most notably, this usermod is *just* for the BME280 and does not control a display like in the v1 usermod designed for the WeMos shield.
# Usermod BME280
This Usermod is designed to read a `BME280` or `BMP280` sensor and output the following:
- Temperature
- Humidity (`BME280` only)
- Pressure
- Heat Index (`BME280` only)
- Dew Point (`BME280` only)
- Requires libraries `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280)) and `Wire`. Please add these under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
- Data is published over MQTT so make sure you've enabled the MQTT sync interface.
Configuration is all completed via the Usermod menu. There are no settings to set in code! The following settings can be configured in the Usermod Menu:
- Temperature Decimals (number of decimal places to output)
- Humidity Decimals
- Pressure Decimals
- Temperature Interval (how many seconds between reads of temperature and humidity)
- Pressure Interval
- Publish Always (turn off to only publish changes, on to publish whether or not value changed)
- Use Celsius (turn off to use Farenheit)
- Home Assistant Discovery (turn on to sent MQTT Discovery entries for Home Assistant)
- SCL/SDA GPIO Pins
Dependencies
- Libraries
- `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280))
- `Wire`
- These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
- Data is published over MQTT - make sure you've enabled the MQTT sync interface.
- This usermod also writes to serial (GPIO1 on ESP8266). Please make sure nothing else listening on the serial TX pin of your board will get confused by log messages!
To enable, compile with `USERMOD_BME280` defined (i.e. `platformio_override.ini`)
In addition to outputting via MQTT, you can read the values from the Info Screen on the dashboard page of the device's web interface.
Methods also exist to read the read/calculated values from other WLED modules through code.
- `getTemperatureC()`
- `getTemperatureF()`
- `getHumidity()`
- `getPressure()`
- `getDewPointC()`
- `getDewPointF()`
- `getHeatIndexC()`
- `getHeatIndexF()`
# Complilation
To enable, compile with `USERMOD_BME280` defined (e.g. in `platformio_override.ini`)
```ini
[env:usermod_bme280_d1_mini]
extends = env:d1_mini
build_flags =
${common.build_flags_esp8266}
-D USERMOD_BME280
```
or define `USERMOD_BME280` in `my_config.h`
```c++
#define USERMOD_BME280
lib_deps =
${esp8266.lib_deps}
BME280@~3.0.0
Wire
```
Changes include:
- Adjustable measure intervals
- Temperature and pressure have separate intervals due to pressure not frequently changing at any constant altitude
- Adjustment of number of decimal places in published sensor values
- Separate adjustment for temperature, humidity and pressure values
- Values are rounded to the specified number of decimal places
- Pressure measured in units of hPa instead of Pa
- Calculation of heat index (apparent temperature) and dew point
- These, along with humidity measurements, are disabled if the sensor is a BMP280
- 16x oversampling of sensor during measurement
- Values are only published if they are different from the previous value
- Values are published on startup (continually until the MQTT broker acknowledges a successful publication)
Adjustments are made through preprocessor definitions at the start of the class definition.
MQTT topics are as follows:
# MQTT
MQTT topics are as follows (`<deviceTopic>` is set in MQTT section of Sync Setup menu):
Measurement type | MQTT topic
--- | ---
Temperature | `<deviceTopic>/temperature`
@ -38,3 +62,29 @@ Humidity | `<deviceTopic>/humidity`
Pressure | `<deviceTopic>/pressure`
Heat index | `<deviceTopic>/heat_index`
Dew point | `<deviceTopic>/dew_point`
If you are using Home Assistant, and `Home Assistant Discovery` is turned on, Home Assistant should automatically detect a new device, provided you have the MQTT integration installed. The device is seperate from the main WLED device and will contain sensors for Pressure, Humidity, Temperature, Dew Point and Heat Index.
# Revision History
Jul 2022
- Added Home Assistant Discovery
- Added API interface to output data
- Removed compile-time variables
- Added usermod menu interface
- Added value outputs to info screen
- Updated `readme.md`
- Registered usermod
- Implemented PinManager for usermod
- Implemented reallocation of pins without reboot
Apr 2021
- Added `Publish Always` option
Dec 2020
- Ported to V2 Usermod format
- Customisable `measure intervals`
- Customisable number of `decimal places` in published sensor values
- Pressure measured in units of hPa instead of Pa
- Calculation of heat index (apparent temperature) and dew point
- `16x oversampling` of sensor during measurement
- Values only published if they are different from the previous value

View File

@ -1,3 +1,6 @@
// force the compiler to show a warning to confirm that this file is included
#warning **** Included USERMOD_BME280 version 2.0 ****
#pragma once
#include "wled.h"
@ -9,43 +12,30 @@
class UsermodBME280 : public Usermod
{
private:
// User-defined configuration
#define Celsius // Show temperature mesaurement in Celcius. Comment out for Fahrenheit
#define TemperatureDecimals 1 // Number of decimal places in published temperaure values
#define HumidityDecimals 2 // Number of decimal places in published humidity values
#define PressureDecimals 2 // Number of decimal places in published pressure values
#define TemperatureInterval 5 // Interval to measure temperature (and humidity, dew point if available) in seconds
#define PressureInterval 300 // Interval to measure pressure in seconds
#define PublishAlways 0 // Publish values even when they have not changed
// Sanity checks
#if !defined(TemperatureDecimals) || TemperatureDecimals < 0
#define TemperatureDecimals 0
#endif
#if !defined(HumidityDecimals) || HumidityDecimals < 0
#define HumidityDecimals 0
#endif
#if !defined(PressureDecimals) || PressureDecimals < 0
#define PressureDecimals 0
#endif
#if !defined(TemperatureInterval) || TemperatureInterval < 0
#define TemperatureInterval 1
#endif
#if !defined(PressureInterval) || PressureInterval < 0
#define PressureInterval TemperatureInterval
#endif
#if !defined(PublishAlways)
#define PublishAlways 0
#endif
// NOTE: Do not implement any compile-time variables, anything the user needs to configure
// should be configurable from the Usermod menu using the methods below
// key settings set via usermod menu
unsigned long TemperatureDecimals = 0; // Number of decimal places in published temperaure values
unsigned long HumidityDecimals = 0; // Number of decimal places in published humidity values
unsigned long PressureDecimals = 0; // Number of decimal places in published pressure values
unsigned long TemperatureInterval = 5; // Interval to measure temperature (and humidity, dew point if available) in seconds
unsigned long PressureInterval = 300; // Interval to measure pressure in seconds
bool PublishAlways = false; // Publish values even when they have not changed
bool UseCelsius = true; // Use Celsius for Reporting
bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information
// set the default pins based on the architecture, these get overridden by Usermod menu settings
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
uint8_t SCL_PIN = 22;
uint8_t SDA_PIN = 21;
#define HW_PIN_SCL 22
#define HW_PIN_SDA 21
#else // ESP8266 boards
uint8_t SCL_PIN = 5;
uint8_t SDA_PIN = 4;
#define HW_PIN_SCL 5
#define HW_PIN_SDA 4
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
#endif
int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
bool initDone = false;
// BME280 sensor settings
BME280I2C::Settings settings{
@ -75,6 +65,7 @@ private:
float sensorHeatIndex;
float sensorDewPoint;
float sensorPressure;
String tempScale;
// Track previous sensor values
float lastTemperature;
float lastHumidity;
@ -82,20 +73,26 @@ private:
float lastDewPoint;
float lastPressure;
// MQTT topic strings for publishing Home Assistant discovery topics
bool mqttInitialized = false;
String mqttTemperatureTopic = "";
String mqttHumidityTopic = "";
String mqttPressureTopic = "";
String mqttHeatIndexTopic = "";
String mqttDewPointTopic = "";
// Store packet IDs of MQTT publications
uint16_t mqttTemperaturePub = 0;
uint16_t mqttPressurePub = 0;
// Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Farenheit being set in Usermod Menu)
void UpdateBME280Data(int SensorType)
{
float _temperature, _humidity, _pressure;
#ifdef Celsius
if (UseCelsius) {
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
#else
BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit);
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Fahrenheit);
#endif
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
@ -103,22 +100,95 @@ private:
sensorTemperature = _temperature;
sensorHumidity = _humidity;
sensorPressure = _pressure;
tempScale = "°C";
if (sensorType == 1)
{
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
}
} else {
BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit);
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Fahrenheit);
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
sensorTemperature = _temperature;
sensorHumidity = _humidity;
sensorPressure = _pressure;
tempScale = "°F";
if (sensorType == 1)
{
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
}
}
}
// Procedure to define all MQTT discovery Topics
void _mqttInitialize()
{
mqttTemperatureTopic = String(mqttDeviceTopic) + F("/temperature");
mqttPressureTopic = String(mqttDeviceTopic) + F("/pressure");
mqttHumidityTopic = String(mqttDeviceTopic) + F("/humidity");
mqttHeatIndexTopic = String(mqttDeviceTopic) + F("/heat_index");
mqttDewPointTopic = String(mqttDeviceTopic) + F("/dew_point");
if (HomeAssistantDiscovery) {
_createMqttSensor(F("Temperature"), mqttTemperatureTopic, F("temperature"), tempScale);
_createMqttSensor(F("Pressure"), mqttPressureTopic, F("pressure"), F("hPa"));
_createMqttSensor(F("Humidity"), mqttHumidityTopic, F("humidity"), F("%"));
_createMqttSensor(F("HeatIndex"), mqttHeatIndexTopic, F("temperature"), tempScale);
_createMqttSensor(F("DewPoint"), mqttDewPointTopic, F("temperature"), tempScale);
}
}
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
{
String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config");
StaticJsonDocument<600> doc;
doc[F("name")] = String(serverDescription) + " " + name;
doc[F("state_topic")] = topic;
doc[F("unique_id")] = String(mqttClientID) + name;
if (unitOfMeasurement != "")
doc[F("unit_of_measurement")] = unitOfMeasurement;
if (deviceClass != "")
doc[F("device_class")] = deviceClass;
doc[F("expire_after")] = 1800;
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
device[F("name")] = serverDescription;
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
device[F("manufacturer")] = F("WLED");
device[F("model")] = F("FOSS");
device[F("sw_version")] = versionString;
String temp;
serializeJson(doc, temp);
DEBUG_PRINTLN(t);
DEBUG_PRINTLN(temp);
mqtt->publish(t.c_str(), 0, true, temp.c_str());
}
public:
void setup()
{
Wire.begin(SDA_PIN, SCL_PIN);
bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used
PinOwner po = PinOwner::UM_BME280; // defaults to being pinowner for SCL/SDA pins
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
if (!pinManager.allocateMultiplePins(pins, 2, po)) { sensorType=0; return; }
Wire.begin(ioPin[1], ioPin[0]);
if (!bme.begin())
{
sensorType = 0;
Serial.println("Could not find BME280I2C sensor!");
DEBUG_PRINTLN(F("Could not find BME280I2C sensor!"));
}
else
{
@ -126,24 +196,25 @@ public:
{
case BME280::ChipModel_BME280:
sensorType = 1;
Serial.println("Found BME280 sensor! Success.");
DEBUG_PRINTLN(F("Found BME280 sensor! Success."));
break;
case BME280::ChipModel_BMP280:
sensorType = 2;
Serial.println("Found BMP280 sensor! No Humidity available.");
DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available."));
break;
default:
sensorType = 0;
Serial.println("Found UNKNOWN sensor! Error!");
DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!"));
}
}
initDone=true;
}
void loop()
{
// BME280 sensor MQTT publishing
// Check if sensor present and MQTT Connected, otherwise it will crash the MCU
if (sensorType != 0 && mqtt != nullptr)
if (sensorType != 0 && WLED_MQTT_CONNECTED)
{
// Timer to fetch new temperature, humidity and pressure data at intervals
timer = millis();
@ -154,9 +225,15 @@ public:
UpdateBME280Data(sensorType);
float temperature = roundf(sensorTemperature * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
float temperature = roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
float humidity, heatIndex, dewPoint;
if (WLED_MQTT_CONNECTED && !mqttInitialized)
{
_mqttInitialize();
mqttInitialized = true;
}
// If temperature has changed since last measure, create string populated with device topic
// from the UI and values read from sensor, then publish to broker
if (temperature != lastTemperature || PublishAlways)
@ -169,25 +246,25 @@ public:
if (sensorType == 1) // Only if sensor is a BME280
{
humidity = roundf(sensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals);
heatIndex = roundf(sensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
dewPoint = roundf(sensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
humidity = roundf(sensorHumidity * powf(10, HumidityDecimals)) / powf(10, HumidityDecimals);
heatIndex = roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
dewPoint = roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
if (humidity != lastHumidity || PublishAlways)
{
String topic = String(mqttDeviceTopic) + "/humidity";
String topic = String(mqttDeviceTopic) + F("/humidity");
mqtt->publish(topic.c_str(), 0, false, String(humidity, HumidityDecimals).c_str());
}
if (heatIndex != lastHeatIndex || PublishAlways)
{
String topic = String(mqttDeviceTopic) + "/heat_index";
String topic = String(mqttDeviceTopic) + F("/heat_index");
mqtt->publish(topic.c_str(), 0, false, String(heatIndex, TemperatureDecimals).c_str());
}
if (dewPoint != lastDewPoint || PublishAlways)
{
String topic = String(mqttDeviceTopic) + "/dew_point";
String topic = String(mqttDeviceTopic) + F("/dew_point");
mqtt->publish(topic.c_str(), 0, false, String(dewPoint, TemperatureDecimals).c_str());
}
@ -201,11 +278,11 @@ public:
{
lastPressureMeasure = timer;
float pressure = roundf(sensorPressure * pow(10, PressureDecimals)) / pow(10, PressureDecimals);
float pressure = roundf(sensorPressure * powf(10, PressureDecimals)) / powf(10, PressureDecimals);
if (pressure != lastPressure || PublishAlways)
{
String topic = String(mqttDeviceTopic) + "/pressure";
String topic = String(mqttDeviceTopic) + F("/pressure");
mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(pressure, PressureDecimals).c_str());
}
@ -213,4 +290,173 @@ public:
}
}
}
/*
* API calls te enable data exchange between WLED modules
*/
inline float getTemperatureC() {
if (UseCelsius) {
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
} else {
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
}
}
inline float getTemperatureF() {
if (UseCelsius) {
return ((float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
} else {
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
}
}
inline float getHumidity() {
return (float)roundf(sensorHumidity * powf(10, HumidityDecimals));
}
inline float getPressure() {
return (float)roundf(sensorPressure * powf(10, PressureDecimals));
}
inline float getDewPointC() {
if (UseCelsius) {
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
} else {
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
}
}
inline float getDewPointF() {
if (UseCelsius) {
return ((float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
} else {
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
}
}
inline float getHeatIndexC() {
if (UseCelsius) {
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
} else {
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
}
}inline float getHeatIndexF() {
if (UseCelsius) {
return ((float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
} else {
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
}
}
// Publish Sensor Information to Info Page
void addToJsonInfo(JsonObject &root)
{
JsonObject user = root[F("u")];
if (user.isNull()) user = root.createNestedObject(F("u"));
if (sensorType==0) //No Sensor
{
// if we sensor not detected, let the user know
JsonArray temperature_json = user.createNestedArray(F("BME/BMP280 Sensor"));
temperature_json.add(F("Not Found"));
}
else if (sensorType==2) //BMP280
{
JsonArray temperature_json = user.createNestedArray(F("Temperature"));
JsonArray pressure_json = user.createNestedArray(F("Pressure"));
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)));
temperature_json.add(tempScale);
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
pressure_json.add(F("hPa"));
}
else if (sensorType==1) //BME280
{
JsonArray temperature_json = user.createNestedArray(F("Temperature"));
JsonArray humidity_json = user.createNestedArray(F("Humidity"));
JsonArray pressure_json = user.createNestedArray(F("Pressure"));
JsonArray heatindex_json = user.createNestedArray(F("Heat Index"));
JsonArray dewpoint_json = user.createNestedArray(F("Dew Point"));
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
temperature_json.add(tempScale);
humidity_json.add(roundf(sensorHumidity * powf(10, HumidityDecimals)));
humidity_json.add(F("%"));
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
pressure_json.add(F("hPa"));
heatindex_json.add(roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
heatindex_json.add(tempScale);
dewpoint_json.add(roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
dewpoint_json.add(tempScale);
}
return;
}
// Save Usermod Config Settings
void addToConfig(JsonObject& root)
{
JsonObject top = root.createNestedObject(F("BME280/BMP280"));
top[F("TemperatureDecimals")] = TemperatureDecimals;
top[F("HumidityDecimals")] = HumidityDecimals;
top[F("PressureDecimals")] = PressureDecimals;
top[F("TemperatureInterval")] = TemperatureInterval;
top[F("PressureInterval")] = PressureInterval;
top[F("PublishAlways")] = PublishAlways;
top[F("UseCelsius")] = UseCelsius;
top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery;
JsonArray io_pin = top.createNestedArray(F("pin"));
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
DEBUG_PRINTLN(F("BME280 config saved."));
}
// Read Usermod Config Settings
bool readFromConfig(JsonObject& root)
{
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
JsonObject top = root[F("BME280/BMP280")];
if (top.isNull()) {
DEBUG_PRINT(F("BME280/BMP280"));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
bool configComplete = !top.isNull();
// A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing
configComplete &= getJsonValue(top[F("TemperatureDecimals")], TemperatureDecimals, 1);
configComplete &= getJsonValue(top[F("HumidityDecimals")], HumidityDecimals, 0);
configComplete &= getJsonValue(top[F("PressureDecimals")], PressureDecimals, 0);
configComplete &= getJsonValue(top[F("TemperatureInterval")], TemperatureInterval, 30);
configComplete &= getJsonValue(top[F("PressureInterval")], PressureInterval, 30);
configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false);
configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true);
configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false);
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
DEBUG_PRINT(FPSTR(F("BME280/BMP280")));
if (!initDone) {
// first run: reading from cfg.json
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
bool pinsChanged = false;
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
PinOwner po = PinOwner::UM_BME280;
if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
setup();
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[F("pin")].isNull();
}
return configComplete;
}
uint16_t getId() {
return USERMOD_ID_BME280;
}
};

View File

@ -355,7 +355,7 @@ public:
// Color in grayscale bitmaps if Segment 1 exists
// TODO If secondary and tertiary are black, color all in primary,
// else color first three from Seg 1 color slots and last three from Seg 2 color slots
WS2812FX::Segment& seg1 = strip.getSegment(tubeSegment);
Segment& seg1 = strip.getSegment(tubeSegment);
if (seg1.isActive()) {
digitColor = strip.getPixelColor(seg1.start + digit);
dimming = seg1.opacity;

View File

@ -63,7 +63,7 @@ class ElekstubeIPSUsermod : public Usermod {
if (!toki.isTick()) return;
updateLocalTime();
WS2812FX::Segment& seg1 = strip.getSegment(tfts.tubeSegment);
Segment& seg1 = strip.getSegment(tfts.tubeSegment);
if (seg1.isActive()) {
bool update = false;
if (seg1.opacity != lastBri) update = true;

View File

@ -132,7 +132,7 @@ static ArduinoFFT<float> FFT = ArduinoFFT<float>( vReal, vImag, samplesFFT, SAMP
static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE);
#endif
static TaskHandle_t FFT_Task;
static TaskHandle_t FFT_Task = nullptr;
float fftAddAvg(int from, int to) {
float result = 0.0f;
@ -1150,6 +1150,7 @@ class AudioReactive : public Usermod {
uiDomString += F("</button>");
infoArr.add(uiDomString);
if (enabled) {
infoArr = user.createNestedArray(F("Input level"));
uiDomString = F("<div class=\"slider\"><div class=\"sliderwrap il\"><input class=\"noslide\" onchange=\"requestJson({");
uiDomString += FPSTR(_name);
@ -1169,18 +1170,22 @@ class AudioReactive : public Usermod {
infoArr.add("ms");
#endif
}
}
/*
* 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)
{
//root["user0"] = userVar0;
if (!initDone) return; // prevent crash on boot applyPreset()
JsonObject usermod = root[FPSTR(_name)];
if (usermod.isNull()) {
usermod = root.createNestedObject(FPSTR(_name));
}
usermod["on"] = enabled;
}
*/
/*
@ -1264,8 +1269,7 @@ class AudioReactive : public Usermod {
JsonObject sync = top.createNestedObject("sync");
sync[F("port")] = audioSyncPort;
sync[F("send")] = (bool) (audioSyncEnabled & 0x01);
sync[F("receive")] = (bool) (audioSyncEnabled & 0x02);
sync[F("mode")] = audioSyncEnabled;
}
@ -1306,12 +1310,7 @@ class AudioReactive : public Usermod {
configComplete &= getJsonValue(top["cfg"][F("AGC")], soundAgc);
configComplete &= getJsonValue(top["sync"][F("port")], audioSyncPort);
bool send = audioSyncEnabled & 0x01;
bool receive = audioSyncEnabled & 0x02;
configComplete &= getJsonValue(top["sync"][F("send")], send);
configComplete &= getJsonValue(top["sync"][F("receive")], receive);
audioSyncEnabled = send | (receive << 1);
configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled);
return configComplete;
}
@ -1331,6 +1330,10 @@ class AudioReactive : public Usermod {
oappend(SET_F("addOption(dd,'Normal',1);"));
oappend(SET_F("addOption(dd,'Vivid',2);"));
oappend(SET_F("addOption(dd,'Lazy',3);"));
oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');"));
oappend(SET_F("addOption(dd,'Off',0);"));
oappend(SET_F("addOption(dd,'Send',1);"));
oappend(SET_F("addOption(dd,'Receive',2);"));
oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'<i>requires reboot!</i>');")); // 0 is field type, 1 is actual field
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'I2S SD');"));
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'I2S WS');"));

View File

@ -96,7 +96,7 @@ class StairwayWipeUsermod : public Usermod {
resetTimebase(); //make sure wipe starts from beginning
//set wipe direction
WS2812FX::Segment& seg = strip.getSegment(0);
Segment& seg = strip.getSegment(0);
bool doReverse = (userVar0 == 2);
seg.setOption(1, doReverse);

View File

@ -89,7 +89,7 @@ void startWipe()
resetTimebase(); //make sure wipe starts from beginning
//set wipe direction
WS2812FX::Segment& seg = strip.getSegment(0);
Segment& seg = strip.getSegment(0);
bool doReverse = (userVar0 == 2);
seg.setOption(1, doReverse);

View File

@ -251,6 +251,7 @@ private:
* Sort either the modes or the palettes using quicksort.
*/
void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) {
if (!modeNames) return;
listBeingSorted = modeNames;
qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp);
listBeingSorted = nullptr;
@ -527,13 +528,13 @@ public:
effectCurrent = modes_alpha_indexes[effectCurrentIndex];
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
strip.setMode(i, effectCurrent);
}
} else {
//WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
//Segment& seg = strip.getSegment(strip.getMainSegmentId());
strip.setMode(strip.getMainSegmentId(), effectCurrent);
}
lampUdated();
@ -555,13 +556,13 @@ public:
effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0);
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.speed = effectSpeed;
}
} else {
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.speed = effectSpeed;
}
lampUdated();
@ -583,13 +584,13 @@ public:
effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0);
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.intensity = effectIntensity;
}
} else {
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.intensity = effectIntensity;
}
lampUdated();
@ -612,22 +613,23 @@ public:
stateChanged = true;
if (applyToAll) {
uint8_t id = strip.getFirstSelectedSegId();
Segment& sid = strip.getSegment(id);
switch (par) {
case 3: val = strip.getSegment(id).custom3 = max(min((increase ? strip.getSegment(id).custom3+fadeAmount : strip.getSegment(id).custom3-fadeAmount), 255), 0); break;
case 2: val = strip.getSegment(id).custom2 = max(min((increase ? strip.getSegment(id).custom2+fadeAmount : strip.getSegment(id).custom2-fadeAmount), 255), 0); break;
default: val = strip.getSegment(id).custom1 = max(min((increase ? strip.getSegment(id).custom1+fadeAmount : strip.getSegment(id).custom1-fadeAmount), 255), 0); break;
case 3: val = sid.custom3 = max(min((increase ? sid.custom3+fadeAmount : sid.custom3-fadeAmount), 255), 0); break;
case 2: val = sid.custom2 = max(min((increase ? sid.custom2+fadeAmount : sid.custom2-fadeAmount), 255), 0); break;
default: val = sid.custom1 = max(min((increase ? sid.custom1+fadeAmount : sid.custom1-fadeAmount), 255), 0); break;
}
for (byte i=0; i<strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || i == id) continue;
switch (par) {
case 3: strip.getSegment(i).custom3 = strip.getSegment(id).custom3; break;
case 2: strip.getSegment(i).custom2 = strip.getSegment(id).custom2; break;
default: strip.getSegment(i).custom1 = strip.getSegment(id).custom1; break;
case 3: seg.custom3 = sid.custom3; break;
case 2: seg.custom2 = sid.custom2; break;
default: seg.custom1 = sid.custom1; break;
}
}
} else {
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
Segment& seg = strip.getMainSegment();
switch (par) {
case 3: val = seg.custom3 = max(min((increase ? seg.custom3+fadeAmount : seg.custom3-fadeAmount), 255), 0); break;
case 2: val = seg.custom2 = max(min((increase ? seg.custom2+fadeAmount : seg.custom2-fadeAmount), 255), 0); break;
@ -656,13 +658,13 @@ public:
effectPalette = palettes_alpha_indexes[effectPaletteIndex];
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.palette = effectPalette;
}
} else {
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.palette = effectPalette;
}
lampUdated();
@ -685,13 +687,13 @@ public:
colorHStoRGB(currentHue1*256, currentSat1, col);
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
}
} else {
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
}
lampUdated();
@ -714,13 +716,13 @@ public:
currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0);
colorHStoRGB(currentHue1*256, currentSat1, col);
if (applyToAll) {
for (byte i=0; i<strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
}
} else {
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
}
lampUdated();
@ -774,13 +776,13 @@ public:
#endif
currentCCT = max(min((increase ? currentCCT+fadeAmount : currentCCT-fadeAmount), 255), 0);
// if (applyToAll) {
for (byte i=0; i<strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.setCCT(currentCCT, i);
seg.setCCT(currentCCT);
}
// } else {
// WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
// Segment& seg = strip.getSegment(strip.getMainSegmentId());
// seg.setCCT(currentCCT, strip.getMainSegmentId());
// }
lampUdated();

View File

@ -27,14 +27,14 @@ saveMacro(1, "&FX=0&R=255&G=255&B=255", false);
//strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true);
//select first two segments (background color + FX settable)
WS2812FX::Segment &seg = strip.getSegment(0);
Segment &seg = strip.getSegment(0);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF)));
strip.getSegment(0).setOption(0, false);
strip.getSegment(0).setOption(2, false);
//other segments are text
for (int i = 1; i < 10; i++)
{
WS2812FX::Segment &seg = strip.getSegment(i);
Segment &seg = strip.getSegment(i);
seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF)));
strip.getSegment(i).setOption(0, true);
strip.setBrightness(128);
@ -50,7 +50,7 @@ void selectWordSegments(bool state)
{
for (int i = 1; i < 10; i++)
{
//WS2812FX::Segment &seg = strip.getSegment(i);
//Segment &seg = strip.getSegment(i);
strip.getSegment(i).setOption(0, state);
// strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true);
//seg.mode = 12;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,7 @@
// but ledmap takes care of that. ledmap is constructed upon initialization
// so matrix should disable regular ledmap processing
void WS2812FX::setUpMatrix() {
#ifndef WLED_DISABLE_2D
// erase old ledmap, just in case.
if (customMappingTable != nullptr) delete[] customMappingTable;
customMappingTable = nullptr;
@ -46,7 +47,7 @@ void WS2812FX::setUpMatrix() {
// safety check
if (matrixWidth * matrixHeight > MAX_LEDS) {
matrixWidth = getLengthTotal();
matrixWidth = _length;
matrixHeight = 1;
isMatrix = false;
return;
@ -94,107 +95,113 @@ void WS2812FX::setUpMatrix() {
#endif
} else {
// memory allocation error
matrixWidth = getLengthTotal();
matrixWidth = _length;
matrixHeight = 1;
isMatrix = false;
return;
}
} else {
// not a matrix set up
matrixWidth = getLengthTotal();
matrixWidth = _length;
matrixHeight = 1;
}
#endif
}
// absolute matrix version of setPixelColor()
void IRAM_ATTR WS2812FX::setPixelColorXY(int x, int y, uint32_t col)
{
#ifndef WLED_DISABLE_2D
if (!isMatrix) return; // not a matrix set-up
uint16_t index = y * matrixWidth + x;
if (index >= _length) return;
if (index < customMappingSize) index = customMappingTable[index];
busses.setPixelColor(index, col);
#endif
}
// returns RGBW values of pixel
uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) {
#ifndef WLED_DISABLE_2D
uint16_t index = (y * matrixWidth + x);
if (index >= _length) return 0;
if (index < customMappingSize) index = customMappingTable[index];
return busses.getPixelColor(index);
#else
return 0;
#endif
}
///////////////////////////////////////////////////////////
// Segment:: routines
///////////////////////////////////////////////////////////
// XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element)
uint16_t IRAM_ATTR WS2812FX::XY(uint16_t x, uint16_t y) {
uint16_t width = SEGMENT.virtualWidth(); // segment width in logical pixels
uint16_t height = SEGMENT.virtualHeight(); // segment height in logical pixels
/*
if (SEGMENT.getOption(SEG_OPTION_TRANSPOSED)) {
uint16_t t;
// swap X & Y if segment transposed
t = x; x = y; y = t;
// swap width & height if segment transposed
t = width; width = height; height = t;
}
*/
uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) {
#ifndef WLED_DISABLE_2D
uint16_t width = virtualWidth(); // segment width in logical pixels
uint16_t height = virtualHeight(); // segment height in logical pixels
return (x%width) + (y%height) * width;
#else
return 0;
#endif
}
// get2DPixelIndex(x,y,seg) - returns an index of segment pixel in a matrix layout
// index still needs to undergo ledmap processing to represent actual physical pixel
// matrix is always organized by matrixHeight number of matrixWidth pixels from top to bottom, left to right
// so: pixel at get2DPixelIndex(5,6) in a 2D segment with [start=10, stop=19, startY=20, stopY=29 : 10x10 pixels]
// corresponds to pixel with logical index of 847 (0 based) if a 2D segment belongs to a 32x32 matrix.
// math: (matrixWidth * (startY + y)) + start + x => (32 * (20+6)) + 10 + 5 = 847
uint16_t IRAM_ATTR WS2812FX::get2DPixelIndex(uint16_t x, uint16_t y, uint8_t seg) {
if (seg == 255) seg = _segment_index;
x %= _segments[seg].width(); // just in case constrain x (wrap around)
y %= _segments[seg].height(); // just in case constrain y (wrap around)
return ((_segments[seg].startY + y) * matrixWidth) + _segments[seg].start + x;
}
void IRAM_ATTR WS2812FX::setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w)
void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col)
{
if (!isMatrix) return; // not a matrix set-up
#ifndef WLED_DISABLE_2D
if (!strip.isMatrix) return; // not a matrix set-up
uint8_t _bri_t = strip._bri_t;
//uint8_t _bri_t = currentBri(getOption(SEG_OPTION_ON) ? opacity : 0);
if (_bri_t < 255) {
r = scale8(r, _bri_t);
g = scale8(g, _bri_t);
b = scale8(b, _bri_t);
w = scale8(w, _bri_t);
byte r = scale8(R(col), _bri_t);
byte g = scale8(G(col), _bri_t);
byte b = scale8(B(col), _bri_t);
byte w = scale8(W(col), _bri_t);
col = RGBW32(r, g, b, w);
}
uint32_t col = RGBW32(r, g, b, w);
if (SEGMENT.getOption(SEG_OPTION_REVERSED) ) x = SEGMENT.virtualWidth() - x - 1;
if (SEGMENT.getOption(SEG_OPTION_REVERSED_Y)) y = SEGMENT.virtualHeight() - y - 1;
if (SEGMENT.getOption(SEG_OPTION_TRANSPOSED)) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed
if (getOption(SEG_OPTION_REVERSED) ) x = virtualWidth() - x - 1;
if (getOption(SEG_OPTION_REVERSED_Y)) y = virtualHeight() - y - 1;
if (getOption(SEG_OPTION_TRANSPOSED)) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed
x *= SEGMENT.groupLength(); // expand to physical pixels
y *= SEGMENT.groupLength(); // expand to physical pixels
if (x >= SEGMENT.width() || y >= SEGMENT.height()) return; // if pixel would fall out of segment just exit
x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels
if (x >= width() || y >= height()) return; // if pixel would fall out of segment just exit
for (uint8_t j = 0; j < SEGMENT.grouping; j++) { // groupping vertically
for (uint8_t g = 0; g < SEGMENT.grouping; g++) { // groupping horizontally
uint16_t index, xX = (x+g), yY = (y+j);
if (xX >= SEGMENT.width() || yY >= SEGMENT.height()) continue; // we have reached one dimension's end
for (int j = 0; j < grouping; j++) { // groupping vertically
for (int g = 0; g < grouping; g++) { // groupping horizontally
uint16_t xX = (x+g), yY = (y+j);
if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end
//if (SEGMENT.getOption(SEG_OPTION_REVERSED) ) xX = SEGMENT.width() - xX - 1;
//if (SEGMENT.getOption(SEG_OPTION_REVERSED_Y)) yY = SEGMENT.height() - yY - 1;
strip.setPixelColorXY(start + xX, startY + yY, col);
index = get2DPixelIndex(xX, yY);
if (index < customMappingSize) index = customMappingTable[index];
busses.setPixelColor(index, col);
if (SEGMENT.getOption(SEG_OPTION_MIRROR)) { //set the corresponding horizontally mirrored pixel
//index = get2DPixelIndex(SEGMENT.width() - xX - 1, yY);
index = SEGMENT.getOption(SEG_OPTION_TRANSPOSED) ? get2DPixelIndex(xX, SEGMENT.height() - yY - 1) : get2DPixelIndex(SEGMENT.width() - xX - 1, yY);
if (index < customMappingSize) index = customMappingTable[index];
busses.setPixelColor(index, col);
if (getOption(SEG_OPTION_MIRROR)) { //set the corresponding horizontally mirrored pixel
if (getOption(SEG_OPTION_TRANSPOSED)) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col);
else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col);
}
if (SEGMENT.getOption(SEG_OPTION_MIRROR_Y)) { //set the corresponding vertically mirrored pixel
//index = get2DPixelIndex(xX, SEGMENT.height() - yY - 1);
index = SEGMENT.getOption(SEG_OPTION_TRANSPOSED) ? get2DPixelIndex(SEGMENT.width() - xX - 1, yY) : get2DPixelIndex(xX, SEGMENT.height() - yY - 1);
if (index < customMappingSize) index = customMappingTable[index];
busses.setPixelColor(index, col);
if (getOption(SEG_OPTION_MIRROR_Y)) { //set the corresponding vertically mirrored pixel
if (getOption(SEG_OPTION_TRANSPOSED)) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col);
else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col);
}
if (SEGMENT.getOption(SEG_OPTION_MIRROR_Y) && SEGMENT.getOption(SEG_OPTION_MIRROR)) { //set the corresponding vertically AND horizontally mirrored pixel
index = get2DPixelIndex(SEGMENT.width() - xX - 1, SEGMENT.height() - yY - 1);
if (index < customMappingSize) index = customMappingTable[index];
busses.setPixelColor(index, col);
if (getOption(SEG_OPTION_MIRROR_Y) && getOption(SEG_OPTION_MIRROR)) { //set the corresponding vertically AND horizontally mirrored pixel
strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, col);
}
}
}
#endif
}
// anti-aliased version of setPixelColorXY()
void /*IRAM_ATTR*/ WS2812FX::setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w, bool aa)
void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
{
#ifndef WLED_DISABLE_2D
if (!strip.isMatrix) return; // not a matrix set-up
if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
float fX = x * (cols-1);
float fY = y * (rows-1);
@ -213,80 +220,59 @@ void /*IRAM_ATTR*/ WS2812FX::setPixelColorXY(float x, float y, byte r, byte g, b
uint32_t cXRYB = getPixelColorXY(xR, yB);
if (xL!=xR && yT!=yB) {
// blend TL pixel
cXLYT = color_blend(RGBW32(r,g,b,w), cXLYT, uint8_t(sqrtf(dL*dT)*255.0f));
setPixelColorXY(xL, yT, R(cXLYT), G(cXLYT), B(cXLYT), W(cXLYT));
// blend TR pixel
cXRYT = color_blend(RGBW32(r,g,b,w), cXRYT, uint8_t(sqrtf(dR*dT)*255.0f));
setPixelColorXY(xR, yT, R(cXRYT), G(cXRYT), B(cXRYT), W(cXRYT));
// blend BL pixel
cXLYB = color_blend(RGBW32(r,g,b,w), cXLYB, uint8_t(sqrtf(dL*dB)*255.0f));
setPixelColorXY(xL, yB, R(cXLYB), G(cXLYB), B(cXLYB), W(cXLYB));
// blend BR pixel
cXRYB = color_blend(RGBW32(r,g,b,w), cXRYB, uint8_t(sqrtf(dR*dB)*255.0f));
setPixelColorXY(xR, yB, R(cXRYB), G(cXRYB), B(cXRYB), W(cXRYB));
setPixelColorXY(xL, yT, color_blend(col, cXLYT, uint8_t(sqrtf(dL*dT)*255.0f))); // blend TL pixel
setPixelColorXY(xR, yT, color_blend(col, cXRYT, uint8_t(sqrtf(dR*dT)*255.0f))); // blend TR pixel
setPixelColorXY(xL, yB, color_blend(col, cXLYB, uint8_t(sqrtf(dL*dB)*255.0f))); // blend BL pixel
setPixelColorXY(xR, yB, color_blend(col, cXRYB, uint8_t(sqrtf(dR*dB)*255.0f))); // blend BR pixel
} else if (xR!=xL && yT==yB) {
// blend L pixel
cXLYT = color_blend(RGBW32(r,g,b,w), cXLYT, uint8_t(dL*255.0f));
setPixelColorXY(xR, yT, R(cXLYT), G(cXLYT), B(cXLYT), W(cXLYT));
// blend R pixel
cXRYT = color_blend(RGBW32(r,g,b,w), cXRYT, uint8_t(dR*255.0f));
setPixelColorXY(xR, yT, R(cXRYT), G(cXRYT), B(cXRYT), W(cXRYT));
setPixelColorXY(xR, yT, color_blend(col, cXLYT, uint8_t(dL*255.0f))); // blend L pixel
setPixelColorXY(xR, yT, color_blend(col, cXRYT, uint8_t(dR*255.0f))); // blend R pixel
} else if (xR==xL && yT!=yB) {
// blend T pixel
cXLYT = color_blend(RGBW32(r,g,b,w), cXLYT, uint8_t(dT*255.0f));
setPixelColorXY(xR, yT, R(cXLYT), G(cXLYT), B(cXLYT), W(cXLYT));
// blend B pixel
cXLYB = color_blend(RGBW32(r,g,b,w), cXLYB, uint8_t(dB*255.0f));
setPixelColorXY(xL, yB, R(cXLYB), G(cXLYB), B(cXLYB), W(cXLYB));
setPixelColorXY(xR, yT, color_blend(col, cXLYT, uint8_t(dT*255.0f))); // blend T pixel
setPixelColorXY(xL, yB, color_blend(col, cXLYB, uint8_t(dB*255.0f))); // blend B pixel
} else {
// exact match (x & y land on a pixel)
setPixelColorXY(xL, yT, r, g, b, w);
setPixelColorXY(xL, yT, col); // exact match (x & y land on a pixel)
}
} else {
setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), r, g, b, w);
setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col);
}
#endif
}
// returns RGBW values of pixel
uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) {
uint16_t index;
if (SEGLEN) {
if (SEGMENT.getOption(SEG_OPTION_REVERSED) ) x = SEGMENT.virtualWidth() - x - 1;
if (SEGMENT.getOption(SEG_OPTION_REVERSED_Y)) y = SEGMENT.virtualHeight() - y - 1;
if (SEGMENT.getOption(SEG_OPTION_TRANSPOSED)) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed
x *= SEGMENT.groupLength(); // expand to physical pixels
y *= SEGMENT.groupLength(); // expand to physical pixels
if (x >= SEGMENT.width() || y >= SEGMENT.height()) return 0;
index = get2DPixelIndex(x, y);
} else {
index = y * matrixWidth + x;
}
if (index < customMappingSize) index = customMappingTable[index];
return busses.getPixelColor(index);
uint32_t Segment::getPixelColorXY(uint16_t x, uint16_t y) {
#ifndef WLED_DISABLE_2D
if (getOption(SEG_OPTION_REVERSED) ) x = virtualWidth() - x - 1;
if (getOption(SEG_OPTION_REVERSED_Y)) y = virtualHeight() - y - 1;
if (getOption(SEG_OPTION_TRANSPOSED)) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed
x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels
if (x >= width() || y >= height()) return 0;
return strip.getPixelColorXY(start + x, startY + y);
#else
return 0;
#endif
}
/*
* Blends the specified color with the existing pixel color.
*/
void WS2812FX::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) {
// Blends the specified color with the existing pixel color.
void Segment::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) {
#ifndef WLED_DISABLE_2D
setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend));
#endif
}
/*
* Adds the specified color with the existing pixel color perserving color balance.
*/
void WS2812FX::addPixelColorXY(uint16_t x, uint16_t y, uint32_t color) {
// Adds the specified color with the existing pixel color perserving color balance.
void Segment::addPixelColorXY(uint16_t x, uint16_t y, uint32_t color) {
#ifndef WLED_DISABLE_2D
setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color));
#endif
}
// blurRow: perform a blur on a row of a rectangular matrix
void WS2812FX::blurRow(uint16_t row, fract8 blur_amount, CRGB* leds) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::blurRow(uint16_t row, fract8 blur_amount, CRGB* leds) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
if (row >= rows) return;
// blur one row
@ -294,13 +280,13 @@ void WS2812FX::blurRow(uint16_t row, fract8 blur_amount, CRGB* leds) {
uint8_t seep = blur_amount >> 1;
CRGB carryover = CRGB::Black;
for (uint16_t x = 0; x < cols; x++) {
CRGB cur = leds ? leds[XY(x,row)] : col_to_crgb(getPixelColorXY(x, row));
CRGB cur = leds ? leds[XY(x,row)] : CRGB(getPixelColorXY(x, row));
CRGB part = cur;
part.nscale8(seep);
cur.nscale8(keep);
cur += carryover;
if (x) {
CRGB prev = (leds ? leds[XY(x-1,row)] : col_to_crgb(getPixelColorXY(x-1, row))) + part;
CRGB prev = (leds ? leds[XY(x-1,row)] : CRGB(getPixelColorXY(x-1, row))) + part;
if (leds) leds[XY(x-1,row)] = prev;
else setPixelColorXY(x-1, row, prev);
}
@ -308,12 +294,14 @@ void WS2812FX::blurRow(uint16_t row, fract8 blur_amount, CRGB* leds) {
else setPixelColorXY(x, row, cur);
carryover = part;
}
#endif
}
// blurCol: perform a blur on a column of a rectangular matrix
void WS2812FX::blurCol(uint16_t col, fract8 blur_amount, CRGB* leds) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::blurCol(uint16_t col, fract8 blur_amount, CRGB* leds) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
if (col >= cols) return;
// blur one column
@ -321,13 +309,13 @@ void WS2812FX::blurCol(uint16_t col, fract8 blur_amount, CRGB* leds) {
uint8_t seep = blur_amount >> 1;
CRGB carryover = CRGB::Black;
for (uint16_t i = 0; i < rows; i++) {
CRGB cur = leds ? leds[XY(col,i)] : col_to_crgb(getPixelColorXY(col, i));
CRGB cur = leds ? leds[XY(col,i)] : CRGB(getPixelColorXY(col, i));
CRGB part = cur;
part.nscale8(seep);
cur.nscale8(keep);
cur += carryover;
if (i) {
CRGB prev = (leds ? leds[XY(col,i-1)] : col_to_crgb(getPixelColorXY(col, i-1))) + part;
CRGB prev = (leds ? leds[XY(col,i-1)] : CRGB(getPixelColorXY(col, i-1))) + part;
if (leds) leds[XY(col,i-1)] = prev;
else setPixelColorXY(col, i-1, prev);
}
@ -335,6 +323,7 @@ void WS2812FX::blurCol(uint16_t col, fract8 blur_amount, CRGB* leds) {
else setPixelColorXY(col, i, cur);
carryover = part;
}
#endif
}
// blur1d: one-dimensional blur filter. Spreads light to 2 line neighbors.
@ -351,15 +340,18 @@ void WS2812FX::blurCol(uint16_t col, fract8 blur_amount, CRGB* leds) {
// eventually all the way to black; this is by design so that
// it can be used to (slowly) clear the LEDs to black.
void WS2812FX::blur1d(CRGB* leds, fract8 blur_amount) {
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::blur1d(CRGB* leds, fract8 blur_amount) {
#ifndef WLED_DISABLE_2D
const uint16_t rows = virtualHeight();
for (uint16_t y = 0; y < rows; y++) blurRow(y, blur_amount, leds);
#endif
}
// 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur])
void WS2812FX::blur1d(uint16_t i, bool vertical, fract8 blur_amount, CRGB* leds) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::blur1d(uint16_t i, bool vertical, fract8 blur_amount, CRGB* leds) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
const uint16_t dim1 = vertical ? rows : cols;
const uint16_t dim2 = vertical ? cols : rows;
if (i >= dim2) return;
@ -374,9 +366,9 @@ void WS2812FX::blur1d(uint16_t i, bool vertical, fract8 blur_amount, CRGB* leds)
uint16_t yp = vertical ? y-1 : y;
uint16_t xn = vertical ? x : x+1;
uint16_t yn = vertical ? y+1 : y;
CRGB curr = leds ? leds[XY(x,y)] : col_to_crgb(getPixelColorXY(x,y));
CRGB prev = (xp<0 || yp<0) ? CRGB::Black : (leds ? leds[XY(xp,yp)] : col_to_crgb(getPixelColorXY(xp,yp)));
CRGB next = ((vertical && yn>=dim1) || (!vertical && xn>=dim1)) ? CRGB::Black : (leds ? leds[XY(xn,yn)] : col_to_crgb(getPixelColorXY(xn,yn)));
CRGB curr = leds ? leds[XY(x,y)] : CRGB(getPixelColorXY(x,y));
CRGB prev = (xp<0 || yp<0) ? CRGB::Black : (leds ? leds[XY(xp,yp)] : CRGB(getPixelColorXY(xp,yp)));
CRGB next = ((vertical && yn>=dim1) || (!vertical && xn>=dim1)) ? CRGB::Black : (leds ? leds[XY(xn,yn)] : CRGB(getPixelColorXY(xn,yn)));
uint16_t r, g, b;
r = (curr.r*keep + (prev.r + next.r)*seep) / 3;
g = (curr.g*keep + (prev.g + next.g)*seep) / 3;
@ -389,63 +381,65 @@ void WS2812FX::blur1d(uint16_t i, bool vertical, fract8 blur_amount, CRGB* leds)
if (leds) leds[XY(x,y)] = tmp[j];
else setPixelColorXY(x, y, tmp[j]);
}
#endif
}
void WS2812FX::blur2d(CRGB* leds, fract8 blur_amount) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::blur2d(CRGB* leds, fract8 blur_amount) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
for (uint16_t i = 0; i < rows; i++) blurRow(i, blur_amount, leds); // blur all rows
for (uint16_t k = 0; k < cols; k++) blurCol(k, blur_amount, leds); // blur all columns
#endif
}
void WS2812FX::moveX(CRGB *leds, int8_t delta) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::moveX(CRGB *leds, int8_t delta) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
if (!delta) return;
if (delta > 0) {
for (uint8_t y = 0; y < rows; y++) for (uint8_t x = 0; x < cols-1; x++) {
if (x + delta >= cols) break;
if (leds) leds[XY(x, y)] = leds[XY((x + delta)%cols, y)];
else setPixelColorXY(x, y, getPixelColorXY((x + delta)%cols, y));
}
} else {
for (uint8_t y = 0; y < rows; y++) for (int16_t x = cols-1; x >= 0; x--) {
if (x + delta < 0) {
if (leds) leds[XY(x, y)] = leds[XY(cols + delta, y)];
else setPixelColorXY(x, y, getPixelColorXY(cols + delta, y));
} else {
if (x + delta < 0) break;
if (leds) leds[XY(x, y)] = leds[XY(x + delta, y)];
else setPixelColorXY(x, y, getPixelColorXY(x + delta, y));
}
}
}
#endif
}
void WS2812FX::moveY(CRGB *leds, int8_t delta) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::moveY(CRGB *leds, int8_t delta) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
if (!delta) return;
if (delta > 0) {
for (uint8_t x = 0; x < cols; x++) for (uint8_t y = 0; y < rows-1; y++) {
if (leds) leds[XY(x, y)] = leds[XY(x, (y + delta)%rows)];
else setPixelColorXY(x, y, getPixelColorXY(x, (y + delta)%rows));
if (y + delta >= rows) break;
if (leds) leds[XY(x, y)] = leds[XY(x, (y + delta))];
else setPixelColorXY(x, y, getPixelColorXY(x, (y + delta)));
}
} else {
for (uint8_t x = 0; x < cols; x++) for (int16_t y = rows-1; y >= 0; y--) {
if (y + delta < 0) {
if (leds) leds[XY(x, y)] = leds[XY(x, rows + delta)];
else setPixelColorXY(x, y, getPixelColorXY(x, rows + delta));
} else {
if (y + delta < 0) break;
if (leds) leds[XY(x, y)] = leds[XY(x, y + delta)];
else setPixelColorXY(x, y, getPixelColorXY(x, y + delta));
}
}
}
#endif
}
// move() - move all pixels in desired direction delta number of pixels
// @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down
// @param delta number of pixels to move
void WS2812FX::move(uint8_t dir, uint8_t delta, CRGB *leds) {
void Segment::move(uint8_t dir, uint8_t delta, CRGB *leds) {
#ifndef WLED_DISABLE_2D
if (delta==0) return;
switch (dir) {
case 0: moveX(leds, delta); break;
@ -457,21 +451,25 @@ void WS2812FX::move(uint8_t dir, uint8_t delta, CRGB *leds) {
case 6: moveY(leds,-delta); break;
case 7: moveX(leds, delta); moveY(leds,-delta); break;
}
#endif
}
void WS2812FX::fill_solid(CRGB* leds, CRGB color) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::fill_solid(CRGB* leds, CRGB color) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) {
if (leds) leds[XY(x,y)] = color;
else setPixelColorXY(x, y, color);
}
#endif
}
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
void WS2812FX::fill_circle(CRGB* leds, uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::fill_circle(CRGB* leds, uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
for (int16_t y = -radius; y <= radius; y++) {
for (int16_t x = -radius; x <= radius; x++) {
if (x * x + y * y <= radius * radius &&
@ -480,31 +478,39 @@ void WS2812FX::fill_circle(CRGB* leds, uint16_t cx, uint16_t cy, uint8_t radius,
leds[XY(cx + x, cy + y)] += col;
}
}
#endif
}
void WS2812FX::fadeToBlackBy(CRGB* leds, uint8_t fadeBy) {
void Segment::fadeToBlackBy(CRGB* leds, uint8_t fadeBy) {
#ifndef WLED_DISABLE_2D
nscale8(leds, 255 - fadeBy);
#endif
}
void WS2812FX::nscale8(CRGB* leds, uint8_t scale) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::nscale8(CRGB* leds, uint8_t scale) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) {
if (leds) leds[XY(x,y)].nscale8(scale);
else setPixelColorXY(x, y, col_to_crgb(getPixelColorXY(x, y)).nscale8(scale));
else setPixelColorXY(x, y, CRGB(getPixelColorXY(x, y)).nscale8(scale));
}
#endif
}
void WS2812FX::setPixels(CRGB* leds) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::setPixels(CRGB* leds) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) setPixelColorXY(x, y, leds[XY(x,y)]);
#endif
}
//line function
void WS2812FX::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, CRGB *leds) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, CRGB *leds) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return;
const int16_t dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
const int16_t dy = abs(y1-y0), sy = y0<y1 ? 1 : -1;
@ -517,8 +523,10 @@ void WS2812FX::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB
if (e2 >-dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
#endif
}
#ifndef WLED_DISABLE_2D
// font curtesy of https://github.com/idispatch/raster-fonts
static const unsigned char console_font_6x8[] PROGMEM = {
@ -4527,10 +4535,10 @@ static const unsigned char console_font_5x8[] PROGMEM = {
0x00, /* 00000 */
0x00, /* 00000 */
0x90, /* 10010 */
0x90, /* 10010 */
0xF0, /* 11110 */
0x90, /* 10010 */
0x90, /* 10010 */
0x90, /* 10010 */
0x00, /* 00000 */
/*
@ -6669,19 +6677,21 @@ static const unsigned char console_font_5x8[] PROGMEM = {
0x00, /* 00000 */
0x00, /* 00000 */
};
#endif
// draws a raster font character on canvas
// only supports 5x8 and 6x8 fonts ATM
void WS2812FX::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color, CRGB *leds) {
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color, CRGB *leds) {
#ifndef WLED_DISABLE_2D
const uint16_t cols = virtualWidth();
const uint16_t rows = virtualHeight();
if (w<5 || w>6 || h!=8) return;
for (uint8_t i = 0; i<h; i++) { // character height
int16_t y0 = y + i;
if (y0 < 0) continue; // drawing off-screen
if (y0 >= rows) break; // drawing off-screen
uint8_t bits;
uint8_t bits = 0;
switch (w) {
case 5: bits = pgm_read_byte_near(&console_font_5x8[(chr * 8) + i]); break;
case 6: bits = pgm_read_byte_near(&console_font_6x8[(chr * 8) + i]); break;
@ -6694,10 +6704,12 @@ void WS2812FX::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
}
}
}
#endif
}
#define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8))
void WS2812FX::wu_pixel(CRGB *leds, uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu
void Segment::wu_pixel(CRGB *leds, uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu
#ifndef WLED_DISABLE_2D
// extract the fractional parts and derive their inverses
uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
// calculate the intensities for each affected pixel
@ -6710,5 +6722,6 @@ void WS2812FX::wu_pixel(CRGB *leds, uint32_t x, uint32_t y, CRGB c) { //awe
leds[xy].g = qadd8(leds[xy].g, c.g * wu[i] >> 8);
leds[xy].b = qadd8(leds[xy].b, c.b * wu[i] >> 8);
}
#endif
}
#undef WU_WEIGHT

File diff suppressed because it is too large Load Diff

View File

@ -195,11 +195,11 @@ void handleAnalog(uint8_t b)
colorHStoRGB(aRead*256,255,col);
} else {
// otherwise use "double press" for segment selection
WS2812FX::Segment& seg = strip.getSegment(macroDoublePress[b]);
Segment& seg = strip.getSegment(macroDoublePress[b]);
if (aRead == 0) {
seg.setOption(SEG_OPTION_ON, 0); // off
} else {
seg.setOpacity(aRead, macroDoublePress[b]);
seg.setOpacity(aRead);
seg.setOption(SEG_OPTION_ON, 1);
}
// this will notify clients of update (websockets,mqtt,etc)

View File

@ -91,6 +91,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
Bus::setCCTBlend(strip.cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
#ifndef WLED_DISABLE_2D
// 2D Matrix Settings
JsonObject matrix = hw_led[F("matrix")];
if (!matrix.isNull()) {
@ -125,6 +126,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
strip.setUpMatrix();
}
#endif
JsonArray ins = hw_led["ins"];
@ -132,6 +134,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t s = 0; // bus iterator
if (fromFS) busses.removeAll(); // can't safely manipulate busses directly in network callback
uint32_t mem = 0;
bool busesChanged = false;
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES) break;
uint8_t pins[5] = {255, 255, 255, 255, 255};
@ -161,10 +164,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
} else {
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode);
doInitBusses = true;
busesChanged = true;
}
s++;
}
doInitBusses = busesChanged;
// finalization done in beginStrip()
}
if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus
@ -618,6 +622,7 @@ void serializeConfig() {
hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getAutoWhiteMode(); // global override
#ifndef WLED_DISABLE_2D
// 2D Matrix Settings
if (strip.isMatrix) {
JsonObject matrix = hw_led.createNestedObject(F("matrix"));
@ -639,6 +644,7 @@ void serializeConfig() {
pnl["s"] = strip.panel[i].serpentine;
}
}
#endif
JsonArray hw_led_ins = hw_led.createNestedArray("ins");

View File

@ -1,12 +1,57 @@
#include "wled.h"
/*
* Color conversion methods
* Color conversion & utility methods
*/
/*
* color blend function
*/
uint32_t IRAM_ATTR 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 = W(color1);
uint32_t r1 = R(color1);
uint32_t g1 = G(color1);
uint32_t b1 = B(color1);
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 RGBW32(r3, g3, b3, w3);
}
/*
* color add function that preserves ratio
* idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule
*/
uint32_t color_add(uint32_t c1, uint32_t c2)
{
uint32_t r = R(c1) + R(c2);
uint32_t g = G(c1) + G(c2);
uint32_t b = B(c1) + B(c2);
uint32_t w = W(c1) + W(c2);
uint16_t max = r;
if (g > max) max = g;
if (b > max) max = b;
if (w > max) max = w;
if (max < 256) return RGBW32(r, g, b, w);
else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max);
}
void setRandomColor(byte* rgb)
{
lastRandomIndex = strip.get_random_wheel_index(lastRandomIndex);
lastRandomIndex = strip.getMainSegment().get_random_wheel_index(lastRandomIndex);
colorHStoRGB(lastRandomIndex*256,255,rgb);
}

View File

@ -5,6 +5,8 @@
* Readability defines and their associated numerical values + compile-time constants
*/
#define GRADIENT_PALETTE_COUNT 58
//Defaults
#define DEFAULT_CLIENT_SSID "Your_Network"
#define DEFAULT_AP_PASS "wled1234"
@ -76,7 +78,8 @@
#define USERMOD_ID_WORDCLOCK 27 //Usermod "usermod_v2_word_clock.h"
#define USERMOD_ID_MY9291 28 //Usermod "usermod_MY9291.h"
#define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h"
#define USERMOD_ID_AUDIOREACTIVE 30 //Usermod "audioreactive.h"
#define USERMOD_ID_BME280 30 //Usermod "usermod_bme280.h
#define USERMOD_ID_AUDIOREACTIVE 31 //Usermod "audioreactive.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@ -223,6 +226,7 @@
#define SEG_OPTION_MIRROR 3 //Indicates that the effect will be mirrored within the segment
#define SEG_OPTION_NONUNITY 4 //Indicates that the effect does not use FRAMETIME or needs getPixelColor
#define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed
#define SEG_OPTION_RESET 6 //Segment runtime requires reset
#define SEG_OPTION_TRANSITIONAL 7
#define SEG_OPTION_REVERSED_Y 8
#define SEG_OPTION_MIRROR_Y 9
@ -358,4 +362,46 @@
#define INTERFACE_UPDATE_COOLDOWN 2000 //time in ms to wait between websockets, alexa, and MQTT updates
#if defined(ESP8266) && defined(HW_PIN_SCL)
#undef HW_PIN_SCL
#endif
#if defined(ESP8266) && defined(HW_PIN_SDA)
#undef HW_PIN_SDA
#endif
#ifndef HW_PIN_SCL
#ifdef ESP8266
#define HW_PIN_SCL 5
#else
#define HW_PIN_SCL 22
#endif
#endif
#ifndef HW_PIN_SDA
#ifdef ESP8266
#define HW_PIN_SDA 4
#else
#define HW_PIN_SDA 21
#endif
#endif
#if defined(ESP8266) && defined(HW_PIN_CLOCKSPI)
#undef HW_PIN_CLOCKSPI
#endif
#if defined(ESP8266) && defined(HW_PIN_DATASPI)
#undef HW_PIN_DATASPI
#endif
#ifndef HW_PIN_CLOCKSPI
#ifdef ESP8266
#define HW_PIN_CLOCKSPI 14
#else
#define HW_PIN_CLOCKSPI 18
#endif
#endif
#ifndef HW_PIN_DATASPI
#ifdef ESP8266
#define HW_PIN_DATASPI 13
#else
#define HW_PIN_DATASPI 23
#endif
#endif
#endif

View File

@ -193,7 +193,7 @@ button {
position: absolute;
top: 8px;
left: 12px;
pointer-events: none;
/*pointer-events: none;*/
width: 24px;
height: 24px;
}
@ -368,7 +368,7 @@ button {
}
#sliders {
width: 310px;
width: 300px;
margin: 0 auto;
position: sticky;
bottom: 0;
@ -380,18 +380,34 @@ button {
}
.slider {
background: var(--c-2);
max-width: 310px;
background-color: var(--c-2);
max-width: 300px;
min-width: 280px;
margin: 0 auto; /* add 5px; if you want some vertical space but looks ugly */
border-radius: 15px;
border-radius: 24px;
position: relative;
}
.filter {
background-color: var(--c-4);
box-shadow: 0px 0px 6px 6px var(--c-1);
border-radius: 26px;
height: 26px;
margin: 0 auto 4px; /* 8px if you want space */
padding: 8px 2px;
position: relative;
/*width: 260px;*/
/*transition: opacity 1s;*/
/*opacity: 1;*/
z-index: 1;
}
/* Tooltip text */
.slider .tooltiptext {
visibility: hidden;
background-color: var(--c-5);
/*border: 2px solid var(--c-2);*/
box-shadow: 4px 4px 10px 4px var(--c-1);
color: var(--c-f);
text-align: center;
padding: 5px 10px;
@ -447,6 +463,10 @@ button {
.hide {
display: none !important;
}
.fade {
visibility: hidden;
opacity: 0;
}
.first {
margin-top: 10px;
@ -827,19 +847,22 @@ input[type=range]::-moz-range-thumb {
}
select {
padding: 4px;
padding: 4px 8px;
margin: 0;
font-size: 19px;
background-color: var(--c-3);
color: var(--c-d);
cursor: pointer;
border: 1px solid var(--c-2);
border-radius: 5px;
border: 0 solid var(--c-2);
border-radius: 20px;
transition-duration: 0.5s;
-webkit-backface-visibility: hidden;
-webkit-transform:translate3d(0,0,0);
-webkit-appearance: none;
-moz-appearance: none;
backface-visibility: hidden;
transform:translate3d(0,0,0);
text-overflow: ellipsis;
}
#tt {
text-align: center;
@ -847,16 +870,26 @@ select {
.cl {
background-color: #000;
}
.sel-p {
margin: 5px 0 10px;
width: 5em;
}
.sel-pl {
select.sel-p, select.sel-pl, select.sel-ple {
margin: 5px 0;
width: 100%;
background-position: 141px 16px;
height: 40px;
}
.sel-ple {
width: 100%;
div.sel-p {
position: relative;
}
div.sel-p:after {
content: "";
position: absolute;
right: 10px;
top: 22px;
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 8px solid var(--c-f);
}
select.sel-ple {
text-align: center;
}
option {
@ -1173,6 +1206,25 @@ TD .checkmark, TD .radiomark {
left: 9px;
}
.filter .fchkl {
display: inline-block;
min-width: 0.7em;
padding: 4px 4px 4px 32px;
text-align: left;
line-height: 24px;
vertical-align: middle;
-webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */
filter: grayscale(100%);
}
.lbl-s {
display: inline-block;
/* margin: 10px 4px 0 0; */
font-size: 13px;
width: 48%;
text-align: center;
}
/* list wrapper */
.list {
position: relative;
@ -1226,6 +1278,7 @@ TD .checkmark, TD .radiomark {
.lstI.sticky,
.lstI.selected {
z-index: 1;
box-shadow: 0px 0px 10px 4px var(--c-1);
}
#pcont .selected:not([class*="expanded"]) {
@ -1258,6 +1311,9 @@ TD .checkmark, TD .radiomark {
/* list item name (for sorting) */
.lstIname {
white-space: nowrap;
text-overflow: ellipsis;
-webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */
filter: grayscale(100%);
}
/* list item palette preview */
@ -1287,6 +1343,8 @@ TD .checkmark, TD .radiomark {
border-radius: 21px;
background: var(--c-2);
border: 1px solid var(--c-3);
-webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */
filter: grayscale(100%);
}
.fnd input[type="text"]:focus {

View File

@ -202,7 +202,7 @@
<div class="staytop fnd" id="fxFind">
<input type="text" placeholder="Search" oninput="search(this,'fxlist')" onfocus="search(this,'fxlist')" />
<i class="icons clear-icon" onclick="clean(this);">&#xe38f;</i>
<i class="icons search-icon">&#xe0a1;</i>
<i class="icons search-icon" onclick="gId('filters').classList.toggle('hide');" style="cursor:pointer;">&#xe0a1;</i>
</div>
<div id="fxlist" class="list">
<div class="lstI">
@ -217,6 +217,28 @@
</div>
</div>
<div id="sliders">
<div id="filters" class="filter">
<label id="filterPal" class="check fchkl">&#127912;
<input type="checkbox" data-flt="&#127912;" onchange="filterFx(this)">
<span class="checkmark"></span>
</label>
<label id="filter1D" class="check fchkl">1D
<input type="checkbox" data-flt="*" onchange="filterFx(this)">
<span class="checkmark"></span>
</label>
<label id="filter2D" class="check fchkl">2D<!-- &#8862; -->
<input type="checkbox" data-flt="2D" onchange="filterFx(this)">
<span class="checkmark"></span>
</label>
<label id="filterVol" class="check fchkl">&#9834;
<input type="checkbox" data-flt="&#9834;" onchange="filterFx(this)">
<span class="checkmark"></span>
</label>
<label id="filterFreq" class="check fchkl">&#9835;
<input type="checkbox" data-flt="&#9835;" onchange="filterFx(this)">
<span class="checkmark"></span>
</label>
</div>
<div id="slider0" class="slider">
<i class="icons slider-icon" style="cursor: pointer;" onclick="tglFreeze()">&#xe325;</i>
<div class="sliderwrap il">

View File

@ -5,10 +5,10 @@ var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, sy
var hasWhite = false, hasRGB = false, hasCCT = false;
var nlDur = 60, nlTar = 0;
var nlMode = false;
var selectedFx = 0, prevFx = -1;
var selectedFx = 0;
var selectedPal = 0;
var csel = 0; // selected color slot (0-2)
var currentPreset = -1, prevPS = -1;
var currentPreset = -1;
var lastUpdate = 0;
var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0;
var pcMode = false, pcModeA = false, lastw = 0, wW;
@ -25,7 +25,7 @@ var ws, cpick, ranges;
var cfg = {
theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
comp :{colors:{picker: true, rgb: false, quick: true, hex: false},
labels:true, pcmbot:false, pid:true, seglen:false, segpwr:false, css:true, hdays:false}
labels:true, pcmbot:false, pid:true, seglen:false, segpwr:false, segexp:false, css:true, hdays:false}
};
var hol = [
[0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas
@ -167,12 +167,12 @@ function loadBg(iUrl)
img.src = iUrl;
if (iUrl == "" || iUrl==="https://picsum.photos/1920/1080") {
var today = new Date();
for (var i=0; i<hol.length; i++) {
var yr = hol[i][0]==0 ? today.getFullYear() : hol[i][0];
var hs = new Date(yr,hol[i][1],hol[i][2]);
for (var h of (hol||[])) {
var yr = h[0]==0 ? today.getFullYear() : h[0];
var hs = new Date(yr,h[1],h[2]);
var he = new Date(hs);
he.setDate(he.getDate() + hol[i][3]);
if (today>=hs && today<=he) img.src = hol[i][4];
he.setDate(he.getDate() + h[3]);
if (today>=hs && today<=he) img.src = h[4];
}
}
img.addEventListener('load', (e) => {
@ -605,7 +605,7 @@ function parseInfo(i) {
mh = i.leds.matrix ? i.leds.matrix.h : 0;
isM = mw>0 && mh>0;
if (!isM) hideModes("2D ");
if (!i.u || !i.u.AudioReactive) { /*hideModes("♪ ");*/ hideModes("♫ "); } // hide /*audio*/ frequency reactive effects
//if (!i.u || !i.u.AudioReactive) { /*hideModes(" ♪");*/ hideModes(" ♫"); } // hide /*audio*/ frequency reactive effects
}
//https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml
@ -621,6 +621,19 @@ function parseInfo(i) {
//}
//setInnerHTML(obj, html);
//https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml
//var setInnerHTML = function(elm, html) {
// elm.innerHTML = html;
// Array.from(elm.querySelectorAll("script")).forEach( oldScript => {
// const newScript = document.createElement("script");
// Array.from(oldScript.attributes)
// .forEach( attr => newScript.setAttribute(attr.name, attr.value) );
// newScript.appendChild(document.createTextNode(oldScript.innerHTML));
// oldScript.parentNode.replaceChild(newScript, oldScript);
// });
//}
//setInnerHTML(obj, html);
function populateInfo(i)
{
var cn="";
@ -671,17 +684,15 @@ function populateSegments(s)
let li = lastinfo;
segCount = 0; lowestUnused = 0; lSeg = 0;
for (var y = 0; y < (s.seg||[]).length; y++)
{
for (var inst of (s.seg||[])) {
segCount++;
var inst = s.seg[y];
let i = parseInt(inst.id);
if (i == lowestUnused) lowestUnused = i+1;
if (i > lSeg) lSeg = i;
let sg = gId(`seg${i}`);
let exp = sg ? sg.classList.contains("expanded") : false;
let exp = sg ? (sg.classList.contains("expanded") || (i===0 && cfg.comp.segexp)) : false;
let segp = `<div id="segp${i}" class="sbs">
<i class="icons e-icon pwr ${inst.on ? "act":""}" id="seg${i}pwr" onclick="setSegPwr(${i})">&#xe08f;</i>
@ -690,17 +701,33 @@ function populateSegments(s)
<div class="sliderdisplay"></div>
</div>
</div>`;
let rvXck = `<label class="check revchkl">Reverse ${isM?'':'direction'}<input type="checkbox" id="seg${i}rev" onchange="setRev(${i})" ${inst.rev?"checked":""}><span class="checkmark schk"></span></label>`;
let miXck = `<label class="check revchkl">Mirror<input type="checkbox" id="seg${i}mi" onchange="setMi(${i})" ${inst.mi?"checked":""}><span class="checkmark schk"></span></label>`;
let rvXck = `<label class="check revchkl">Reverse ${isM?'':'direction'}<input type="checkbox" id="seg${i}rev" onchange="setRev(${i})" ${inst.rev?"checked":""}><span class="checkmark"></span></label>`;
let miXck = `<label class="check revchkl">Mirror<input type="checkbox" id="seg${i}mi" onchange="setMi(${i})" ${inst.mi?"checked":""}><span class="checkmark"></span></label>`;
let rvYck = "", miYck ="";
if (isM) {
rvYck = `<label class="check revchkl">Reverse<input type="checkbox" id="seg${i}rY" onchange="setRevY(${i})" ${inst.rY?"checked":""}><span class="checkmark schk"></span></label>`;
miYck = `<label class="check revchkl">Mirror<input type="checkbox" id="seg${i}mY" onchange="setMiY(${i})" ${inst.mY?"checked":""}><span class="checkmark schk"></span></label>`;
rvYck = `<label class="check revchkl">Reverse<input type="checkbox" id="seg${i}rY" onchange="setRevY(${i})" ${inst.rY?"checked":""}><span class="checkmark"></span></label>`;
miYck = `<label class="check revchkl">Mirror<input type="checkbox" id="seg${i}mY" onchange="setMiY(${i})" ${inst.mY?"checked":""}><span class="checkmark"></span></label>`;
}
let map2D = `<div id="seg${i}map2D" data-map="map2D" class="lbl-s hide">Expand 1D FX<br>
<div class="sel-p"><select class="sel-p" id="seg${i}mp12" onchange="setMp12(${i})">
<option value="0" ${inst.mp12==0?' selected':''}>None</option>
<option value="1" ${inst.mp12==1?' selected':''}>Bar</option>
<option value="2" ${inst.mp12==2?' selected':''}>Arc</option>
<option value="3" ${inst.mp12==3?' selected':''}>Corner</option>
</select></div>
</div>`;
let sndSim = `<div data-snd="ssim" class="lbl-s hide">Sound sim<br>
<div class="sel-p"><select class="sel-p" id="seg${i}ssim" onchange="setSSim(${i})">
<option value="0" ${inst.ssim==0?' selected':''}>BeatSin</option>
<option value="1" ${inst.ssim==1?' selected':''}>WeWillRockYou</option>
<option value="2" ${inst.ssim==2?' selected':''}>U10_3</option>
<option value="3" ${inst.ssim==3?' selected':''}>U14_3</option>
</select></div>
</div>`;
cn += `<div class="seg lstI ${i==s.mainseg ? 'selected' : ''} ${exp ? "expanded":""}" id="seg${i}">
<label class="check schkl">
<input type="checkbox" id="seg${i}sel" onchange="selSeg(${i})" ${inst.sel ? "checked":""}>
<span class="checkmark schk"></span>
<span class="checkmark"></span>
</label>
<i class="icons e-icon frz" id="seg${i}frz" onclick="event.preventDefault();tglFreeze(${i});">&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};</i>
<div class="segname" onclick="selSegEx(${i})">
@ -741,10 +768,12 @@ function populateSegments(s)
</table>
<div class="h bp" id="seg${i}len"></div>
${!isM?rvXck:''}
${map2D}
${s.AudioReactive && s.AudioReactive.on ? "" : sndSim}
<label class="check revchkl">
${isM?'Transpose':'Mirror effect'}
<input type="checkbox" id="seg${i}${isM?'tp':'mi'}" onchange="${(isM?'setTp(':'setMi(')+i})" ${isM?(inst.tp?"checked":""):(inst.mi?"checked":"")}>
<span class="checkmark schk"></span>
<span class="checkmark"></span>
</label>
<div class="del">
<button class="btn btn-xs" id="segr${i}" title="Repeat until end" onclick="rptSeg(${i})"><i class="icons btn-icon">&#xe22d;</i></button>
@ -779,27 +808,34 @@ function populateEffects()
var effects = eJson;
var html = "";
effects.shift(); // remove solid
for (let i = 0; i < effects.length; i++) effects[i] = {id: effects[i][0], name:effects[i][1]};
effects.shift(); // temporary remove solid
for (let i = 0; i < effects.length; i++) {
effects[i] = {
id: effects[i][0],
name:effects[i][1]
};
}
effects.sort((a,b) => (a.name).localeCompare(b.name));
effects.unshift({
"id": 0,
"name": "Solid"
});
for (let i = 0; i < effects.length; i++) {
for (let ef of effects) {
// WLEDSR: add slider and color control to setX (used by requestjson)
if (effects[i].name.indexOf("Reserved") < 0) {
let id = effects[i].id;
html += generateListItemHtml(
'fx',
id,
effects[i].name,
'setX',
'',
!(Array.isArray(fxdata) && fxdata.length>id) ? '' : fxdata[id].substr(1)
);
let id = ef.id;
let nm = ef.name;
let fd = "";
if (ef.name.indexOf("Reserved") < 0) {
if (Array.isArray(fxdata) && fxdata.length>id) {
fd = fxdata[id].substr(1);
var eP = (fd == '')?[]:fd.split(";");
var p = (eP.length<3 || eP[2]==='')?[]:eP[2].split(",");
var m = (eP.length<4 || eP[3]==='')?[]:eP[3].split(",");
if (isM && m.length>0) for (let r of m) { if (r.substring(0,4)=="mp12") nm += " *"; } // 1D effects with defined mapping
if (p.length>0 && p[0].substring(0,1) === "!") nm += " 🎨";
}
html += generateListItemHtml('fx',id,nm,'setX','',fd);
}
}
@ -809,7 +845,7 @@ function populateEffects()
function populatePalettes()
{
var palettes = lJson;
palettes.shift(); // remove default
palettes.shift(); // temporary remove default
for (let i = 0; i < palettes.length; i++) {
palettes[i] = {
"id": palettes[i][0],
@ -817,20 +853,19 @@ function populatePalettes()
};
}
palettes.sort((a,b) => (a.name).localeCompare(b.name));
palettes.unshift({
"id": 0,
"name": "Default"
});
var html = "";
for (let i = 0; i < palettes.length; i++) {
for (let pa of palettes) {
html += generateListItemHtml(
'palette',
palettes[i].id,
palettes[i].name,
pa.id,
pa.name,
'setPalette',
`<div class="lstIprev" style="${genPalPrevCss(palettes[i].id)}"></div>`
`<div class="lstIprev" style="${genPalPrevCss(pa.id)}"></div>`
);
}
@ -895,12 +930,12 @@ function genPalPrevCss(id)
return `background: linear-gradient(to right,${gradient.join()});`;
}
function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', extraPar = '')
function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', effectPar = '')
{
return `<div class="lstI${id==0?' sticky':''}" data-id="${id}" data-opt="${extraPar}" onClick="${clickAction}(${id})">
return `<div class="lstI${id==0?' sticky':''}" data-id="${id}" data-opt="${effectPar}" onClick="${clickAction}(${id})">
<label class="radio schkl" onclick="event.preventDefault()">
<input type="radio" value="${id}" name="${listName}">
<span class="radiomark schk"></span>
<span class="radiomark"></span>
<div class="lstIcontent">
<span class="lstIname">
${name}
@ -933,8 +968,7 @@ function populateNodes(i,n)
var nnodes = 0;
if (n.nodes) {
n.nodes.sort((a,b) => (a.name).localeCompare(b.name));
for (var x=0;x<n.nodes.length;x++) {
var o = n.nodes[x];
for (var o of n.nodes) {
if (o.name) {
var url = `<button class="btn" title="${o.ip}" onclick="location.assign('http://${o.ip}');">${bname(o)}</button>`;
urows += inforow(url,`${btype(o.type)}<br><i>${o.vid==0?"N/A":o.vid}</i>`);
@ -979,7 +1013,7 @@ function updateTrail(e)
var max = e.hasAttribute('max') ? e.attributes.max.value : 255;
var perc = Math.round(e.value * 100 / max);
if (perc < 50) perc += 2;
var val = `linear-gradient(90deg, var(--bg) ${perc}%, var(--c-4) ${perc}%)`;
var val = `linear-gradient(90deg, var(--bg) ${perc}%, var(--c-6) ${perc}%)`;
sd.style.backgroundImage = val;
}
var b = e.parentNode.parentNode.getElementsByTagName('output')[0];
@ -1004,6 +1038,7 @@ function updateLen(s)
start = parseInt(gId(`seg${s}sY`).value);
stop = parseInt(gId(`seg${s}eY`).value);
len *= (stop-(cfg.comp.seglen?0:start));
if (stop-start>1) gId(`seg${s}map2D`).classList.remove("hide"); else gId(`seg${s}map2D`).classList.add("hide");
}
var out = "(delete)";
if (len > 1) {
@ -1109,15 +1144,21 @@ function updateSelectedFx()
if (selEffectInput) selEffectInput.checked = true;
var selElement = parent.querySelector('.selected');
if (selElement) selElement.classList.remove('selected');
if (selElement) {
selElement.classList.remove('selected');
selElement.style.bottom = null; // remove element style added in slider handling
}
var selectedEffect = parent.querySelector(`.lstI[data-id="${selectedFx}"]`);
if (selectedEffect) {
selectedEffect.classList.add('selected');
var fx = (selectedFx != prevFx) && currentPreset==-1; // effect changed & preset==none
var ps = (prevPS != currentPreset) && currentPreset==-1; // preset changed & preset==none
// WLEDSR: extract the Slider and color control string from the HTML element and set it.
setSliderAndColorControl(selectedFx, (fx || ps));
setEffectParameters(selectedFx);
var selectedName = selectedEffect.querySelector(".lstIname").innerText;
var segs = gId("segcont").querySelectorAll(`div[data-map="map2D"]`);
for (const seg of segs) if (selectedName.indexOf("2D ")<0) seg.classList.remove("hide"); else seg.classList.add("hide");
var segs = gId("segcont").querySelectorAll(`div[data-snd="ssim"]`);
for (const seg of segs) if (selectedName.indexOf(" ♪")<0 && selectedName.indexOf(" ♫")<0) seg.classList.add("hide"); else seg.classList.remove("hide"); // also "♫ "?
}
}
@ -1180,6 +1221,7 @@ function makeWS() {
function readState(s,command=false)
{
if (!s) return false;
if (s.success) return true; // no data to process
isOn = s.on;
gId('sliderBri').value = s.bri;
@ -1188,7 +1230,6 @@ function readState(s,command=false)
nlTar = s.nl.tbri;
nlFade = s.nl.fade;
syncSend = s.udpn.send;
prevPS = currentPreset;
if (s.pl<0) currentPreset = s.ps;
else currentPreset = s.pl;
@ -1224,7 +1265,7 @@ function readState(s,command=false)
if (!i) {
showToast('No Segments!', true);
updateUI();
return;
return true;
}
var cd = gId('csl').children;
@ -1266,11 +1307,11 @@ function readState(s,command=false)
showToast('Error ' + s.error + ": " + errstr, true);
}
prevFx = selectedFx;
selectedPal = i.pal;
selectedFx = i.fx;
redrawPalPrev(); // if any color changed (random palette did at least)
updateUI();
return true;
}
// WLEDSR: control HTML elements for Slider and Color Control
@ -1294,16 +1335,15 @@ function readState(s,command=false)
// Note: Effects can override default pattern behaviour
// - FadeToBlack can override the background setting
// - Defining SEGCOL(<i>) can override a specific palette using these values (e.g. Color Gradient)
function setSliderAndColorControl(idx, applyDef=false)
function setEffectParameters(idx)
{
if (!(Array.isArray(fxdata) && fxdata.length>idx)) return;
var controlDefined = (fxdata[idx].substr(0,1) == "@");
var extra = fxdata[idx].substr(1);
var extras = (extra == '')?[]:extra.split(";");
var slOnOff = (extras.length==0 || extras[0]=='')?[]:extras[0].split(",");
var coOnOff = (extras.length<2 || extras[1]=='')?[]:extras[1].split(",");
var paOnOff = (extras.length<3 || extras[2]=='')?[]:extras[2].split(",");
var obj = {"seg":{}};
var effectPar = fxdata[idx].substr(1);
var effectPars = (effectPar == '')?[]:effectPar.split(";");
var slOnOff = (effectPars.length==0 || effectPars[0]=='')?[]:effectPars[0].split(",");
var coOnOff = (effectPars.length<2 || effectPars[1]=='')?[]:effectPars[1].split(",");
var paOnOff = (effectPars.length<3 || effectPars[2]=='')?[]:effectPars[2].split(",");
// set html slider items on/off
var nSliders = Math.min(5,Math.floor(gId("sliders").children.length)); // div for each slider
@ -1316,10 +1356,6 @@ function setSliderAndColorControl(idx, applyDef=false)
if (slOnOff.length>i && slOnOff[i].indexOf("=")>0) {
// embeded default values
var dPos = slOnOff[i].indexOf("=");
var v = Math.max(0,Math.min(255,parseInt(slOnOff[i].substr(dPos+1))));
if (i==0) { if (applyDef) gId("sliderSpeed").value = v; obj.seg.sx = v; }
else if (i==1) { if (applyDef) gId("sliderIntensity").value = v; obj.seg.ix = v; }
else { if (applyDef) gId("sliderC"+(i-1)).value = v; obj.seg["c"+(i-1)+"x"] = v}
slOnOff[i] = slOnOff[i].substring(0,dPos);
}
if (slOnOff.length>i && slOnOff[i]!="!") label.innerHTML = slOnOff[i];
@ -1327,18 +1363,15 @@ function setSliderAndColorControl(idx, applyDef=false)
else if (i==1) label.innerHTML = "Effect intensity";
else label.innerHTML = "Custom" + (i-1);
sldCnt++;
//if (sldCnt++===0) slider.classList.add("top");
slider.classList.remove("hide");
//slider.setAttribute('title',label.innerHTML);
} else {
slider.classList.add("hide");
//slider.classList.remove("top");
}
}
// set the bottom position of selected effect (sticky) as the top of sliders div
let top = parseInt(getComputedStyle(gId("sliders")).height);
/*if (sldCnt===1)*/ top += 28; // size of tooltip
//top += 5; // size of tooltip
let sel = d.querySelector('#fxlist .selected');
if (sel) sel.style.bottom = top + "px"; // we will need to remove this when unselected (in setX())
@ -1346,7 +1379,7 @@ function setSliderAndColorControl(idx, applyDef=false)
var cslLabel = '';
var sep = '';
var hide = true;
var cslCnt = 0;
var cslCnt = 0, oCsel = csel;
for (let i=0; i<gId("csl").children.length; i++) {
var btn = gId("csl" + i);
// if no controlDefined or coOnOff has a value
@ -1364,13 +1397,13 @@ function setSliderAndColorControl(idx, applyDef=false)
else if (i==1) btn.innerHTML = "Bg";
else btn.innerHTML = "Cs";
hide = false;
if (!cslCnt) selectSlot(i); // select 1st displayed slot
if (!cslCnt || oCsel==i) selectSlot(i); // select 1st displayed slot or old one
cslCnt++;
} else if (!controlDefined /*|| paOnOff.length>0*/) { // if no controls then all buttons should be shown for color 1..3
btn.style.display = "inline";
btn.innerHTML = `${i+1}`;
hide = false;
if (!cslCnt) selectSlot(i); // select 1st displayed slot
if (!cslCnt || oCsel==i) selectSlot(i); // select 1st displayed slot or old one
cslCnt++;
} else {
btn.style.display = "none";
@ -1388,11 +1421,6 @@ function setSliderAndColorControl(idx, applyDef=false)
// embeded default values
var dPos = paOnOff[0].indexOf("=");
var v = Math.max(0,Math.min(255,parseInt(paOnOff[0].substr(dPos+1))));
var p = d.querySelector(`#pallist input[name="palette"][value="${v}"]`);
if (applyDef && p) {
p.checked = true;
obj.seg.pal = v;
}
paOnOff[0] = paOnOff[0].substring(0,dPos);
}
if (paOnOff.length>0 && paOnOff[0] != "!") pall.innerHTML = paOnOff[0];
@ -1401,14 +1429,11 @@ function setSliderAndColorControl(idx, applyDef=false)
// disable palett list
pall.innerHTML = '<i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i> Color palette not used';
palw.style.display = "none";
// if numeric set as selected palette
if (paOnOff.length>0 && paOnOff[0]!="" && !isNaN(paOnOff[0]) && parseInt(paOnOff[0])!=selectedPal) obj.seg.pal = parseInt(paOnOff[0]);
}
// not all color selectors shown, hide palettes created from color selectors
for (let e of (gId('pallist').querySelectorAll('.lstI')||[])) {
if (cslCnt < 3 && e.querySelector('.lstIname').innerText.indexOf("* C")>=0) e.classList.add('hide'); else e.classList.remove('hide');
}
if (!isEmpty(obj.seg) && applyDef) requestJson(obj); // update default values (may need throttling on ESP8266)
}
var jsonTimeout;
@ -1588,7 +1613,7 @@ function resetUtil()
{
// gId('segutil').innerHTML = '<button class="btn btn-s" onclick="makeSeg()"><i class="icons btn-icon">&#xe18a;</i>segment</button>';
gId('segutil').innerHTML = '<div class="seg btn btn-s" style="border-radius:24px;padding:0;">'
+ '<label class="check schkl"><input type="checkbox" id="selall" onchange="selSegAll(this)"><span class="checkmark schk"></span></label>'
+ '<label class="check schkl"><input type="checkbox" id="selall" onchange="selSegAll(this)"><span class="checkmark"></span></label>'
+ '<div class="segname" onclick="makeSeg()"><i class="icons btn-icon">&#xe18a;</i>segment</div></div>';
}
@ -1601,14 +1626,14 @@ var plJson = {"0":{
"end": 0
}};
function makePlSel(incPl=false) {
function makePlSel(el, incPl=false) {
var plSelContent = "";
delete pJson["0"]; // remove filler preset
var arr = Object.entries(pJson);
for (var i = 0; i < arr.length; i++) {
var n = arr[i][1].n ? arr[i][1].n : "Preset " + arr[i][0];
if (!incPl && arr[i][1].playlist && arr[i][1].playlist.ps) continue; // remove playlists, sub-playlists not yet supported
plSelContent += `<option value="${arr[i][0]}">${n}</option>`
for (var a of arr) {
var n = a[1].n ? a[1].n : "Preset " + a[0];
if (!incPl && a[1].playlist && a[1].playlist.ps) continue; // remove playlists, sub-playlists not yet supported
plSelContent += `<option value="${a[0]}" ${a[0]==el?"selected":""}>${n}</option>`
}
return plSelContent;
}
@ -1685,20 +1710,20 @@ function makeP(i,pl) {
content =
`<div id="ple${i}" style="margin-top:10px;"></div><label class="check revchkl">Shuffle
<input type="checkbox" id="pl${i}rtgl" onchange="plR(${i})" ${plJson[i].r||rep<0?"checked":""}>
<span class="checkmark schk"></span>
<span class="checkmark"></span>
</label>
<label class="check revchkl">Repeat indefinitely
<input type="checkbox" id="pl${i}rptgl" onchange="plR(${i})" ${rep>0?"":"checked"}>
<span class="checkmark schk"></span>
<span class="checkmark"></span>
</label>
<div id="pl${i}o1" style="display:${rep>0?"block":"none"}">
<div class="c">Repeat <input class="noslide" type="number" id="pl${i}rp" oninput="plR(${i})" max=127 min=0 value=${rep>0?rep:1}> times</div>
<div class="sel">End preset:<br>
<select class="sel sel-ple" id="pl${i}selEnd" onchange="plR(${i})" data-val=${plJson[i].end?plJson[i].end:0}>
<div class="sel-p"><select class="sel-ple" id="pl${i}selEnd" onchange="plR(${i})" data-val=${plJson[i].end?plJson[i].end:0}>
<option value="0">None</option>
<option value="255">Restore preset</option>
${makePlSel(true)}
</select></div>
${makePlSel(plJson[i].end?plJson[i].end:0, true)}
</select></div></div>
</div>
<div class="c"><button class="btn btn-p" onclick="testPl(${i}, this)"><i class='icons btn-icon'>&#xe139;</i>Test</button></div>`;
} else {
@ -1708,24 +1733,24 @@ ${makePlSel(true)}
Include brightness
</span>
<input type="checkbox" id="p${i}ibtgl" checked>
<span class="checkmark schk"></span>
<span class="checkmark"></span>
</label>
<label class="check revchkl">
<span class="lstIname">
Save segment bounds
</span>
<input type="checkbox" id="p${i}sbtgl" checked>
<span class="checkmark schk"></span>
<span class="checkmark"></span>
</label>
<label class="check revchkl">
<span class="lstIname">
Checked segments only
</span>
<input type="checkbox" id="p${i}sbchk">
<span class="checkmark schk"></span>
<span class="checkmark"></span>
</label>`;
if (Array.isArray(lastinfo.maps) && lastinfo.maps.length>0) {
content += `<div class="sel">Ledmap:&nbsp;<select class="sel sel-p" id="p${i}lmp"><option value="">None</option>`;
content += `<div class="sel">Ledmap:&nbsp;<select class="sel-p" id="p${i}lmp"><option value="">None</option>`;
for (const k of (lastinfo.maps||[])) content += `<option value="${k}"${(i>0 && pJson[i].ledmap==k)?" selected":""}>${k}</option>`;
content += "</select></div>";
}
@ -1740,7 +1765,7 @@ ${makePlSel(true)}
${pl?"Show playlist editor":(i>0)?"Overwrite with state":"Use current state"}
</span>
<input type="checkbox" id="p${i}cstgl" onchange="tglCs(${i})" ${(i==0||pl)?"checked":""}>
<span class="checkmark schk"></span>
<span class="checkmark"></span>
</label>
</div>
<div class="po2" id="p${i}o2">
@ -1778,9 +1803,9 @@ function makePlEntry(p,i) {
<table>
<tr>
<td width="80%" colspan=2>
<select class="sel sel-pl" onchange="plePs(${p},${i},this)" data-val="${plJson[p].ps[i]}" data-index="${i}">
${makePlSel()}
</select>
<div class="sel-p"><select class="sel-pl" onchange="plePs(${p},${i},this)" data-val="${plJson[p].ps[i]}" data-index="${i}">
${makePlSel(plJson[p].ps[i])}
</select></div>
</td>
<td class="c"><button class="btn btn-pl-add" onclick="addPl(${p},${i})"><i class="icons btn-icon">&#xe18a;</i></button></td>
</tr>
@ -1952,6 +1977,20 @@ function setMiY(s)
requestJson(obj);
}
function setMp12(s)
{
var value = gId(`seg${s}mp12`).selectedIndex;
var obj = {"seg": {"id": s, "mp12": value}};
requestJson(obj);
}
function setSSim(s)
{
var value = gId(`seg${s}ssim`).selectedIndex;
var obj = {"seg": {"id": s, "ssim": value}};
requestJson(obj);
}
function setTp(s)
{
var tp = gId(`seg${s}tp`).checked;
@ -1990,6 +2029,8 @@ function setX(ind = null)
} else {
d.querySelector(`#fxlist input[name="fx"][value="${ind}"]`).checked = true;
}
/*
// this code also in updateSelectedFx
var selElement = d.querySelector('#fxlist .selected');
if (selElement) {
selElement.classList.remove('selected');
@ -1997,7 +2038,7 @@ function setX(ind = null)
}
d.querySelector(`#fxlist .lstI[data-id="${ind}"]`).classList.add('selected');
*/
var obj = {"seg": {"fx": parseInt(ind)}};
requestJson(obj);
}
@ -2009,11 +2050,13 @@ function setPalette(paletteId = null)
} else {
d.querySelector(`#pallist input[name="palette"][value="${paletteId}"]`).checked = true;
}
/*
var selElement = d.querySelector('#pallist .selected');
if (selElement) {
selElement.classList.remove('selected')
}
d.querySelector(`#pallist .lstI[data-id="${paletteId}"]`).classList.add('selected');
*/
var obj = {"seg": {"pal": paletteId}};
requestJson(obj);
}
@ -2127,6 +2170,7 @@ function saveP(i,pl)
}
populatePresets();
resetPUtil();
setTimeout(()=>{pmtLast=0; loadPresets();}, 500); // force reloading of presets
}
function testPl(i,bt) {
@ -2362,6 +2406,11 @@ function rSegs()
bt.style.color = "var(--c-f)";
bt.innerHTML = "Reset segments";
var obj = {"seg":[{"start":0,"stop":ledCount,"sel":true}]};
if (isM) {
obj.seg[0].stop = mw;
obj.seg[0].startX = 0;
obj.seg[0].stopY = mh;
}
for (let i=1; i<=lSeg; i++) obj.seg.push({"stop":0});
requestJson(obj);
}
@ -2422,6 +2471,10 @@ function hideModes(txt)
for (let e of (gId('fxlist').querySelectorAll('.lstI')||[])) {
if (e.querySelector('.lstIname').innerText.indexOf(txt) >= 0) e.classList.add("hide"); //else e.classList.remove("hide");
}
if (txt==="2D ") {
gId("filter1D").classList.add("hide");
gId("filter2D").classList.add("hide");
}
}
function search(f,l=null)
@ -2444,6 +2497,19 @@ function clean(c)
i.value='';
i.focus();
i.dispatchEvent(new Event('input'));
if (i.parentElement.id=='fxFind') {
gId("filters").querySelectorAll("input[type=checkbox]").forEach((e)=>{e.checked=false;});
}
}
function filterFx(o)
{
if (!o) return;
let i = gId('fxFind').children[0];
i.value=!o.checked?'':o.dataset.flt;
i.focus();
i.dispatchEvent(new Event('input'));
gId("filters").querySelectorAll("input[type=checkbox]").forEach((e)=>{if(e!==o)e.checked=false;});
}
// make sure "dur" and "transition" are arrays with at least the length of "ps"

View File

@ -6,9 +6,38 @@
<title>WLED Settings</title>
<script>
var d=document;
var loc = false, locip;
function gId(n){return d.getElementById(n);}
function S(){GetV();}
function GetV(){}
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
scE.setAttribute("src", FILE_URL);
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
//console.log("File loaded");
GetV();
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
function S(){
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 url = (loc?`http://${locip}`:'') + '/settings/s.js?p=0';
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
}
</script>
<style>
body {

View File

@ -41,8 +41,6 @@
}
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=10';
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
//if (loc) loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
//else { GetV(); UI(); }
}
var maxPanels=64;
@ -104,9 +102,6 @@ Serpentine: <input type="checkbox" name="P${i}S"></div>`;
gId("pnl_add").style.display = (i<maxPanels) ? "inline":"none";
gId("pnl_rem").style.display = (i>1) ? "inline":"none";
}
//values injected by server while sending HTML
//fun-ction GetV() {}
</script>
<style>@import url("style.css");</style>
</head>

View File

@ -6,15 +6,17 @@
<meta charset="utf-8">
<title>DMX Settings</title>
<script>
var d=document;
var loc = false, locip;
function H(){window.open("https://github.com/Aircoookie/WLED/wiki/DMX");}
function B(){window.history.back();}
function GCH(num) {
d=document;
d.getElementById('dmxchannels').innerHTML += "";
for (i=0;i<num;i++) {
d.getElementById('dmxchannels').innerHTML += "<span id=CH" + (i+1) + "s >Channel " + (i+1) + ": <select name=CH" + (i+1) + " id=\"CH" + (i+1) + "\"><option value=0>Set to 0</option><option value=1>Red</option><option value=2>Green</option><option value=3>Blue</option><option value=4>White</option><option value=5>Shutter (Brightness)</option><option value=6>Set to 255</option></select></span><br />\n";
}
}
function mMap(){
d=document;
numCh=document.Sf.CN.value;
numGap=document.Sf.CG.value;
if (parseInt(numCh)>parseInt(numGap)) {
@ -33,8 +35,36 @@ function mMap(){
}
}
}
function S(){GCH(15);GetV();mMap();}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/DMX");}function B(){window.history.back();}
function GetV(){}
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
scE.setAttribute("src", FILE_URL);
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
//console.log("File loaded");
GCH(15);GetV();mMap();
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
function S(){
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 url = (loc?`http://${locip}`:'') + '/settings/s.js?p=7';
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
}
</script>
<style>@import url("style.css");</style>
</head>

View File

@ -8,10 +8,29 @@
<script>
var d=document,laprev=55,maxB=1,maxM=4000,maxPB=4096,maxL=1333,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[],maxCOOverrides=5;
var loc = false, locip;
function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");}
function B(){window.open("/settings","_self");}
function gId(n){return d.getElementById(n);}
function off(n){d.getElementsByName(n)[0].value = -1;}
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
scE.setAttribute("src", FILE_URL);
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
//console.log("File loaded");
GetV();checkSi();setABL();
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
var timeout;
function showToast(text, error = false)
{
@ -506,12 +525,17 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
}
}
}
function S(){GetV();checkSi();setABL();}
function GetV()
{
//values injected by server while sending HTML
//d.um_p=[6,7,8,9,10,11,1];bLimits(3,4096,4000,1664);d.Sf.MS.checked=1;addLEDs(1);d.Sf.L00.value=2;d.Sf.LC0.value=30;d.Sf.LT0.value=22;d.Sf.CO0.value=0;d.Sf.LS0.value=15;d.Sf.CV0.checked=1;d.Sf.SL0.checked=0;addLEDs(1);d.Sf.L01.value=10;d.Sf.L11.value=10;d.Sf.L21.value=1;d.Sf.L31.value=10;d.Sf.LC1.value=60;d.Sf.LT1.value=80;d.Sf.CO1.value=1;d.Sf.LS1.value=0;d.Sf.CV1.checked=0;d.Sf.SL1.checked=0;d.Sf.MA.value=850;d.Sf.LA.value=0;d.Sf.CA.value=56;d.Sf.AW.value=3;d.Sf.BO.checked=1;d.Sf.BP.value=80;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=0;d.Sf.BF.value=100;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=0;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=12;d.Sf.RM.checked=1;addBtn(0,0,0);addBtn(1,-1,0);d.Sf.TT.value=32;d.Sf.IR.value=-1;d.Sf.IT.value=0;
//d.um_p=[6,7,8,9,10,11,14,15,13,1,21,19,22,25,26,27,5,23,18,17];bLimits(10,2048,64000,8192);d.Sf.MS.checked=1;d.Sf.CCT.checked=0;addLEDs(1);d.Sf.L00.value=192;d.Sf.L10.value=168;d.Sf.L20.value=0;d.Sf.L30.value=61;d.Sf.LC0.value=421;d.Sf.LT0.value=80;d.Sf.CO0.value=1;d.Sf.LS0.value=0;d.Sf.CV0.checked=0;d.Sf.SL0.checked=0;d.Sf.RF0.checked=0;d.Sf.MA.value=850;d.Sf.LA.value=0;d.Sf.CA.value=127;d.Sf.AW.value=3;d.Sf.BO.checked=0;d.Sf.BP.value=0;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=1;d.Sf.BF.value=100;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=1;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=-1;d.Sf.RM.checked=1;addBtn(0,-1,0);addBtn(1,-1,0);addBtn(2,-1,0);addBtn(3,-1,0);d.Sf.TT.value=32;d.Sf.IR.value=-1;d.Sf.IT.value=8;
function S(){
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 url = (loc?`http://${locip}`:'') + '/settings/s.js?p=2';
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
}
</script>
<style>@import url("style.css");</style>

View File

@ -7,11 +7,30 @@
<title>Misc Settings</title>
<script>
var d = document;
var loc = false, locip;
function H() { window.open("https://kno.wled.ge/features/settings/#security-settings"); }
function B() { window.open("/settings","_self"); }
function U() { window.open("/update","_self"); }
function gId(s) { return d.getElementById(s); }
function isObj(o) { return (o && typeof o === 'object' && !Array.isArray(o)); }
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
scE.setAttribute("src", FILE_URL);
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
//console.log("File loaded");
GetV();
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
var timeout;
function showToast(text, error = false)
{
@ -40,16 +59,24 @@
event.preventDefault();
return false;
}
function GetV()
{
//values injected by server while sending HTML
function S() {
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 url = (loc?`http://${locip}`:'') + '/settings/s.js?p=6';
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
}
</script>
<style>
@import url("style.css");
</style>
</head>
<body onload="GetV()">
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<div class="toprow">
<div class="helpB"><button type="button" onclick="H()">?</button></div>

View File

@ -6,6 +6,7 @@
<meta charset="utf-8">
<title>Sync Settings</title>
<script>var d=document;
var loc = false, locip;
function gId(s)
{
return d.getElementById(s);
@ -14,6 +15,24 @@ function H(){window.open("https://kno.wled.ge/interfaces/udp-notifier/");}
function B(){window.open("/settings","_self");}
function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.DA.value == 1) d.Sf.DA.value = 0; if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;}
else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} }
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
scE.setAttribute("src", FILE_URL);
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
//console.log("File loaded");
GetV();SetVal();
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
function FC()
{
for(j=0;j<8;j++)
@ -38,8 +57,18 @@ function GC()
}
function SP(){var p = d.Sf.DI.value; gId("xp").style.display = (p > 0)?"none":"block"; if (p > 0) d.Sf.EP.value = p;}
function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();FC();}
function S(){GetV();SetVal();}
function GetV(){var d=document;}
function S(){
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 url = (loc?`http://${locip}`:'') + '/settings/s.js?p=4';
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
}
</script>
<style>@import url("style.css");</style>
</head>

View File

@ -7,26 +7,42 @@
<title>Time Settings</title>
<script>
var d=document;
var loc = false, locip;
var el=false;
var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
function H()
{
window.open("https://kno.wled.ge/features/settings/#time-settings");
}
function B()
{
window.open("/settings","_self");
}
function S()
{
function H() { window.open("https://kno.wled.ge/features/settings/#time-settings"); }
function B() { window.open("/settings","_self"); }
function gId(s) { return d.getElementById(s); }
function gN(s) { return d.getElementsByName(s)[0]; }
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
scE.setAttribute("src", FILE_URL);
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
//console.log("File loaded");
BTa();GetV();updLoc();Cs();FC();
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
function gId(s)
{
return d.getElementById(s);
function S() {
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);
}
function gN(s) {
return d.getElementsByName(s)[0];
}
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=5';
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
}
function expand(o,i)
{
@ -34,10 +50,7 @@
t.style.display = t.style.display!=="none" ? "none" : "";
o.innerHTML = t.style.display==="none" ? "&#128197;" : "&#x2715;";
}
function Cs()
{
gId("cac").style.display=(gN("OL").checked)?"block":"none";
}
function Cs() { gId("cac").style.display=(gN("OL").checked)?"block":"none"; }
function BTa()
{
var ih="<thead><tr><th>En.</th><th>Hour</th><th>Minute</th><th>Preset</th><th></th></tr></thead>";
@ -132,10 +145,6 @@
if (parseFloat(d.Sf.LT.value)<0) { d.Sf.LTR.value = "S"; d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); } else d.Sf.LTR.value = "N";
if (parseFloat(d.Sf.LN.value)<0) { d.Sf.LNR.value = "W"; d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); } else d.Sf.LNR.value = "E";
}
function GetV()
{
//values injected by server while sending HTML
}
</script>
<style>@import url("style.css");</style>
</head>

View File

@ -7,6 +7,7 @@
<title>UI Settings</title>
<script>
var d = document;
var loc = false, locip;
var initial_ds, initial_st, initial_su;
var sett = null;
var l = {
@ -23,6 +24,7 @@
"pid": "Show preset IDs",
"seglen": "Set segment length instead of stop LED",
"segpwr": "Hide segment power &amp; brightness",
"segexp" : "Always expand first segment",
"css": "Enable custom CSS",
"hdays": "Enable custom Holidays list"
},
@ -40,10 +42,7 @@
}
}
};
function gId(s)
{
return d.getElementById(s);
}
function gId(s) { return d.getElementById(s); }
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
@ -163,22 +162,43 @@
if (d.Sf.DS.value != initial_ds || d.Sf.ST.checked != initial_st || d.Sf.SU.checked != initial_su) d.Sf.submit();
}
function S()
{
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
scE.setAttribute("src", FILE_URL);
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
//console.log("File loaded");
GetV();
initial_ds = d.Sf.DS.value;
initial_st = d.Sf.ST.checked;
initial_su = d.Sf.SU.checked;
GetLS();
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
function H()
function S()
{
window.open("https://kno.wled.ge/features/settings/#user-interface-settings");
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);
}
function B()
{
window.open("/settings","_self");
}
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=3';
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
}
function H() { window.open("https://kno.wled.ge/features/settings/#user-interface-settings"); }
function B() { window.open("/settings","_self"); }
function UI()
{
gId('idonthateyou').style.display = (gId('dm').checked) ? 'inline':'none';
@ -213,7 +233,6 @@
fO.value = '';
return false;
}
function GetV(){var d=document;}
</script>
<style>@import url("style.css");</style>
</head>
@ -241,6 +260,7 @@
<span class="l"></span>: <input type="checkbox" id="comp_pid" class="agi cb"><br>
<span class="l"></span>: <input type="checkbox" id="comp_seglen" class="agi cb"><br>
<span class="l"></span>: <input type="checkbox" id="comp_segpwr" class="agi cb"><br>
<span class="l"></span>: <input type="checkbox" id="comp_segexp" class="agi cb"><br>
I hate dark mode: <input type="checkbox" id="dm" onchange="UI()"><br>
<span id="idonthateyou" style="display:none"><i>Why would you? </i>&#x1F97A;<br></span>
<span class="l"></span>: <input type="number" min=0.0 max=1.0 step=0.01 id="theme_alpha_tab" class="agi"><br>

View File

@ -81,6 +81,7 @@
}
function addField(k,f,o,a=false) { //key, field, (sub)object, isArray
if (isO(o)) {
urows += '<hr style="width:260px">';
for (const [s,v] of Object.entries(o)) {
// possibility to nest objects (only 1 level)
if (f!=='unknown' && !k.includes(":")) addField(k+":"+f,s,v);
@ -191,7 +192,6 @@
e.preventDefault();
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
}
//fun-ction GetV() {} // replaced during 'npm run build'
</script>
<style>@import url("style.css");</style>
</head>

View File

@ -6,16 +6,44 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>WiFi Settings</title>
<script>
var d=document;
var loc = false, locip;
function H(){window.open("https://kno.wled.ge/features/settings/#wifi-settings");}
function B(){window.open("/settings","_self");}
function GetV()
{
//values injected by server while sending HTML
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
scE.setAttribute("src", FILE_URL);
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
//console.log("File loaded");
GetV();
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
function S() {
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 url = (loc?`http://${locip}`:'') + '/settings/s.js?p=1';
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
}
</script>
<style>@import url("style.css");</style>
</head>
<body onload="GetV()">
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<div class="toprow">
<div class="helpB"><button type="button" onclick="H()">?</button></div>

View File

@ -58,6 +58,9 @@ bool getJsonValue(const JsonVariant& element, DestType& destination, const Defau
//colors.cpp
uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false);
uint32_t color_add(uint32_t,uint32_t);
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb
void colorKtoRGB(uint16_t kelvin, byte* rgb);
@ -129,7 +132,7 @@ void handleIR();
void deserializeSegment(JsonObject elem, byte it, byte presetId = 0);
bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, byte presetId = 0);
void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true);
void serializeInfo(JsonObject root);
void serializeModeNames(JsonArray arr, const char *qstring);
@ -296,7 +299,7 @@ class UsermodManager {
void onUpdateBegin(bool);
bool add(Usermod* um);
Usermod* lookup(uint16_t mod_id);
byte getModCount();
byte getModCount() {return numMods;};
};
//usermods_list.cpp
@ -321,8 +324,10 @@ bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t module=255);
void releaseJSONBufferLock();
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen);
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen);
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr);
int16_t extractModeDefaults(uint8_t mode, const char *segVar);
uint16_t crc16(const unsigned char* data_p, size_t length);
um_data_t* simulateSound(uint8_t simulationId);
//wled_eeprom.cpp
void applyMacro(byte index);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -89,8 +89,8 @@ byte relativeChange(byte property, int8_t amount, byte lowerBoundary, byte highe
void changeEffect(uint8_t fx)
{
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
strip.setMode(i, fx);
}
@ -105,8 +105,8 @@ void changeEffect(uint8_t fx)
void changePalette(uint8_t pal)
{
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.palette = pal;
}
@ -124,8 +124,8 @@ void changeEffectSpeed(int8_t amount)
int16_t new_val = (int16_t) effectSpeed + amount;
effectSpeed = (byte)constrain(new_val,0,255);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.speed = effectSpeed;
}
@ -135,7 +135,7 @@ void changeEffectSpeed(int8_t amount)
setValuesFromMainSeg();
}
} else { // if Effect == "solid Color", change the hue of the primary color
WS2812FX::Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col;
fastled_col.red = R(sseg.colors[0]);
fastled_col.green = G(sseg.colors[0]);
@ -147,8 +147,8 @@ void changeEffectSpeed(int8_t amount)
prim_hsv.h = (byte)new_val;
hsv2rgb_rainbow(prim_hsv, fastled_col);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
}
@ -171,8 +171,8 @@ void changeEffectIntensity(int8_t amount)
int16_t new_val = (int16_t) effectIntensity + amount;
effectIntensity = (byte)constrain(new_val,0,255);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.intensity = effectIntensity;
}
@ -182,7 +182,7 @@ void changeEffectIntensity(int8_t amount)
setValuesFromMainSeg();
}
} else { // if Effect == "solid Color", change the saturation of the primary color
WS2812FX::Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col;
fastled_col.red = R(sseg.colors[0]);
fastled_col.green = G(sseg.colors[0]);
@ -192,8 +192,8 @@ void changeEffectIntensity(int8_t amount)
prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255
hsv2rgb_rainbow(prim_hsv, fastled_col);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
}
@ -214,8 +214,8 @@ void changeColor(uint32_t c, int16_t cct=-1)
{
if (irApplyToAllSelected) {
// main segment may not be selected!
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
byte capabilities = seg.getLightCapabilities();
uint32_t mask = 0;
@ -226,14 +226,14 @@ void changeColor(uint32_t c, int16_t cct=-1)
if (isRGB) mask |= 0x00FFFFFF; // RGB
if (hasW) mask |= 0xFF000000; // white
if (hasW && !wSlider && (c & 0xFF000000)) { // segment has white channel & white channel is auto calculated & white specified
seg.setColor(0, c | 0xFFFFFF, i); // for accurate/brighter mode we fake white (since button may not set white color to 0xFFFFFF)
} else if (c & mask) seg.setColor(0, c & mask, i); // only apply if not black
if (isCCT && cct >= 0) seg.setCCT(cct, i);
seg.setColor(0, c | 0xFFFFFF); // for accurate/brighter mode we fake white (since button may not set white color to 0xFFFFFF)
} else if (c & mask) seg.setColor(0, c & mask); // only apply if not black
if (isCCT && cct >= 0) seg.setCCT(cct);
}
setValuesFromFirstSelectedSeg();
} else {
byte i = strip.getMainSegmentId();
WS2812FX::Segment& seg = strip.getSegment(i);
Segment& seg = strip.getSegment(i);
byte capabilities = seg.getLightCapabilities();
uint32_t mask = 0;
bool isRGB = GET_BIT(capabilities, 0); // is segment RGB capable
@ -243,9 +243,9 @@ void changeColor(uint32_t c, int16_t cct=-1)
if (isRGB) mask |= 0x00FFFFFF; // RGB
if (hasW) mask |= 0xFF000000; // white
if (hasW && !wSlider && (c & 0xFF000000)) { // segment has white channel & white channel is auto calculated & white specified
seg.setColor(0, c | 0xFFFFFF, i); // for accurate/brighter mode we fake white (since button may not set white color to 0xFFFFFF)
} else if (c & mask) seg.setColor(0, c & mask, i); // only apply if not black
if (isCCT && cct >= 0) seg.setCCT(cct, i);
seg.setColor(0, c | 0xFFFFFF); // for accurate/brighter mode we fake white (since button may not set white color to 0xFFFFFF)
} else if (c & mask) seg.setColor(0, c & mask); // only apply if not black
if (isCCT && cct >= 0) seg.setCCT(cct);
setValuesFromMainSeg();
}
stateChanged = true;
@ -253,7 +253,7 @@ void changeColor(uint32_t c, int16_t cct=-1)
void changeWhite(int8_t amount, int16_t cct=-1)
{
WS2812FX::Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
byte r = R(seg.colors[0]);
byte g = G(seg.colors[0]);
byte b = B(seg.colors[0]);
@ -424,7 +424,7 @@ void decodeIR24CT(uint32_t code)
void decodeIR40(uint32_t code)
{
WS2812FX::Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
byte r = R(seg.colors[0]);
byte g = G(seg.colors[0]);
byte b = B(seg.colors[0]);
@ -712,7 +712,7 @@ void initIR()
void handleIR()
{
if (irEnabled > 0 && millis() - irCheckedTime > 120)
if (irEnabled > 0 && millis() - irCheckedTime > 120 && !strip.isUpdating())
{
irCheckedTime = millis();
if (irEnabled > 0)

View File

@ -11,11 +11,19 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
byte id = elem["id"] | it;
if (id >= strip.getMaxSegments()) return;
WS2812FX::Segment& seg = strip.getSegment(id);
WS2812FX::Segment prev = seg; //make a backup so we can tell if something changed
int stop = elem["stop"] | -1;
// if using vectors use this code to append segment
if (id >= strip.getSegmentsNum()) {
if (stop <= 0) return; // ignore empty/inactive segments
strip.appendSegment(Segment(0, strip.getLengthTotal()));
id = strip.getSegmentsNum()-1; // segments are added at the end of list
}
Segment& seg = strip.getSegment(id);
Segment prev = seg; //make a backup so we can tell if something changed
uint16_t start = elem["start"] | seg.start;
int stop = elem["stop"] | -1;
if (stop < 0) {
uint16_t len = elem["len"];
stop = (len > 0) ? start + len : seg.stop;
@ -31,7 +39,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
elem.remove("rpt"); // remove for recursive call
elem.remove("n"); // remove for recursive call
uint16_t len = stop - start;
for (byte i=id+1; i<strip.getMaxSegments(); i++) {
for (size_t i=id+1; i<strip.getMaxSegments(); i++) {
start = start + len;
if (start >= strip.getLengthTotal()) break;
//TODO: add support for 2D
@ -71,8 +79,13 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
uint16_t grp = elem["grp"] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing;
uint16_t of = seg.offset;
uint8_t soundSim = elem[F("ssim")] | seg.soundSim;
uint8_t map1D2D = elem[F("mp12")] | seg.map1D2D;
if (spc>0 && spc!=seg.spacing) strip.fill(BLACK, id); // clear spacing gaps
if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps
seg.map1D2D = map1D2D & 0x03;
seg.soundSim = soundSim & 0x07;
uint16_t len = 1;
if (stop > start) len = stop - start;
@ -88,23 +101,23 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
byte segbri = seg.opacity;
if (getVal(elem["bri"], &segbri)) {
if (segbri > 0) seg.setOpacity(segbri, id);
seg.setOption(SEG_OPTION_ON, segbri, id);
if (segbri > 0) seg.setOpacity(segbri);
seg.setOption(SEG_OPTION_ON, segbri);
}
bool on = elem["on"] | seg.getOption(SEG_OPTION_ON);
if (elem["on"].is<const char*>() && elem["on"].as<const char*>()[0] == 't') on = !on;
seg.setOption(SEG_OPTION_ON, on, id);
seg.setOption(SEG_OPTION_ON, on);
bool frz = elem["frz"] | seg.getOption(SEG_OPTION_FREEZE);
if (elem["frz"].is<const char*>() && elem["frz"].as<const char*>()[0] == 't') frz = !seg.getOption(SEG_OPTION_FREEZE);
seg.setOption(SEG_OPTION_FREEZE, frz, id);
seg.setOption(SEG_OPTION_FREEZE, frz);
seg.setCCT(elem["cct"] | seg.cct, id);
seg.setCCT(elem["cct"] | seg.cct);
JsonArray colarr = elem["col"];
if (!colarr.isNull())
{
for (uint8_t i = 0; i < 3; i++)
for (size_t i = 0; i < 3; i++)
{
int rgbw[] = {0,0,0,0};
bool colValid = false;
@ -115,13 +128,13 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
if (hexCol == nullptr) { //Kelvin color temperature (or invalid), e.g 2400
int kelvin = colarr[i] | -1;
if (kelvin < 0) continue;
if (kelvin == 0) seg.setColor(i, 0, id);
if (kelvin == 0) seg.setColor(i, 0);
if (kelvin > 0) colorKtoRGB(kelvin, brgbw);
colValid = true;
} else { //HEX string, e.g. "FFAA00"
colValid = colorFromHexString(brgbw, hexCol);
}
for (uint8_t c = 0; c < 4; c++) rgbw[c] = brgbw[c];
for (size_t c = 0; c < 4; c++) rgbw[c] = brgbw[c];
} else { //Array of ints (RGB or RGBW color), e.g. [255,160,0]
byte sz = colX.size();
if (sz == 0) continue; //do nothing on empty array
@ -132,7 +145,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
if (!colValid) continue;
seg.setColor(i, RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3]), id);
seg.setColor(i, RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3]));
if (seg.mode == FX_MODE_STATIC) strip.trigger(); //instant refresh
}
}
@ -149,18 +162,61 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
}
#endif
seg.setOption(SEG_OPTION_SELECTED, elem[F("sel")] | seg.getOption(SEG_OPTION_SELECTED));
seg.setOption(SEG_OPTION_SELECTED, elem["sel"] | seg.getOption(SEG_OPTION_SELECTED));
seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED));
seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR ));
// 2D options
#ifndef WLED_DISABLE_2D
seg.setOption(SEG_OPTION_REVERSED_Y, elem[F("rY")] | seg.getOption(SEG_OPTION_REVERSED_Y));
seg.setOption(SEG_OPTION_MIRROR_Y , elem[F("mY")] | seg.getOption(SEG_OPTION_MIRROR_Y ));
seg.setOption(SEG_OPTION_TRANSPOSED, elem[F("tp")] | seg.getOption(SEG_OPTION_TRANSPOSED));
#endif
byte fx = seg.mode;
if (getVal(elem["fx"], &fx, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 1-255 exact value)
if (getVal(elem["fx"], &fx, 0, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 0-255 exact value)
if (!presetId && currentPlaylist>=0) unloadPlaylist();
strip.setMode(id, fx);
if (fx != seg.mode) {
//seg.startTransition(strip.getTransition()); // set effect transitions
seg.markForReset();
seg.mode = fx;
// load default values from effect string if effect is selected without
// any other effect parameter (i.e. effect clicked in UI)
if ( elem[F("sx")].isNull()
&& elem[F("ix")].isNull()
&& elem["pal"].isNull()
&& elem[F("c1")].isNull()
&& elem[F("c2")].isNull()
&& elem[F("c3")].isNull() )
{
// compatibility mode begin
char buf[5]; // dummy buffer
for (int i=0; i<5; i++) {
uint8_t *var;
switch (i) {
case 0: var = &seg.speed; break;
case 1: var = &seg.intensity; break;
case 2: var = &seg.custom1; break;
case 3: var = &seg.custom2; break;
case 4: var = &seg.custom3; break;
}
extractModeSlider(fx, i, buf, 4, var);
}
extractModeSlider(fx, 255, buf, 4, &seg.palette);
//end compatibility mode
int16_t sOpt;
sOpt = extractModeDefaults(fx, SET_F("sx")); if (sOpt >= 0) seg.speed = sOpt;
sOpt = extractModeDefaults(fx, SET_F("ix")); if (sOpt >= 0) seg.intensity = sOpt;
sOpt = extractModeDefaults(fx, SET_F("c1")); if (sOpt >= 0) seg.custom1 = sOpt;
sOpt = extractModeDefaults(fx, SET_F("c2")); if (sOpt >= 0) seg.custom2 = sOpt;
sOpt = extractModeDefaults(fx, SET_F("c3")); if (sOpt >= 0) seg.custom3 = sOpt;
sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0 && sOpt < strip.getPaletteCount()) seg.palette = sOpt;
sOpt = extractModeDefaults(fx, SET_F("mp12")); if (sOpt >= 0) seg.map1D2D = sOpt & 0x03;
sOpt = extractModeDefaults(fx, SET_F("ssim")); if (sOpt >= 0) seg.soundSim = sOpt & 0x07;
sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) seg.reverse = (bool)sOpt; // setOption(SEG_OPTION_REVERSED, (bool)sOpt); // NOTE: setting this option is a risky business
sOpt = extractModeDefaults(fx, SET_F("mi")); if (sOpt >= 0) seg.mirror = (bool)sOpt; // setOption(SEG_OPTION_MIRROR, (bool)sOpt); // NOTE: setting this option is a risky business
sOpt = extractModeDefaults(fx, SET_F("rY")); if (sOpt >= 0) seg.reverse_y = (bool)sOpt; // setOption(SEG_OPTION_REVERSED_Y, (bool)sOpt); // NOTE: setting this option is a risky business
sOpt = extractModeDefaults(fx, SET_F("mY")); if (sOpt >= 0) seg.mirror_y = (bool)sOpt; // setOption(SEG_OPTION_MIRROR_Y, (bool)sOpt); // NOTE: setting this option is a risky business
}
}
}
//getVal also supports inc/decrementing and random
@ -173,7 +229,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
JsonArray iarr = elem[F("i")]; //set individual LEDs
if (!iarr.isNull()) {
uint8_t oldSegId = strip.setPixelSegment(id);
//uint8_t oldSegId = strip.setPixelSegment(id);
// set brightness immediately and disable transition
transitionDelayTemp = 0;
@ -183,13 +239,13 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
// freeze and init to black
if (!seg.getOption(SEG_OPTION_FREEZE)) {
seg.setOption(SEG_OPTION_FREEZE, true);
strip.fill(0);
seg.fill(BLACK);
}
uint16_t start = 0, stop = 0;
byte set = 0; //0 nothing set, 1 start set, 2 range set
for (uint16_t i = 0; i < iarr.size(); i++) {
for (size_t i = 0; i < iarr.size(); i++) {
if(iarr[i].is<JsonInteger>()) {
if (!set) {
start = iarr[i];
@ -208,29 +264,28 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
byte brgbw[] = {0,0,0,0};
const char* hexCol = iarr[i];
if (colorFromHexString(brgbw, hexCol)) {
for (uint8_t c = 0; c < 4; c++) rgbw[c] = brgbw[c];
for (size_t c = 0; c < 4; c++) rgbw[c] = brgbw[c];
}
}
if (set < 2) stop = start + 1;
for (uint16_t i = start; i < stop; i++) {
for (int i = start; i < stop; i++) {
if (strip.gammaCorrectCol) {
strip.setPixelColor(i, strip.gamma8(rgbw[0]), strip.gamma8(rgbw[1]), strip.gamma8(rgbw[2]), strip.gamma8(rgbw[3]));
seg.setPixelColor(i, strip.gamma8(rgbw[0]), strip.gamma8(rgbw[1]), strip.gamma8(rgbw[2]), strip.gamma8(rgbw[3]));
} else {
strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
seg.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
}
}
if (!set) start++;
set = 0;
}
}
strip.setPixelSegment(oldSegId);
//strip.setPixelSegment(oldSegId);
strip.trigger();
}
// send UDP if not in preset and something changed that is not just selection
// send UDP if something changed that is not just selection or segment power/opacity
if ((seg.differs(prev) & 0x7E) && seg.getOption(SEG_OPTION_ON)==prev.getOption(SEG_OPTION_ON)) stateChanged = true;
return;
}
// deserializes WLED state (fileDoc points to doc object if called from web server)
@ -248,11 +303,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (root["on"].is<const char*>() && root["on"].as<const char*>()[0] == 't') toggleOnOff();
if (bri && !onBefore) { // unfreeze all segments when turning on
for (uint8_t s=0; s < strip.getMaxSegments(); s++) {
strip.getSegment(s).setOption(SEG_OPTION_FREEZE, false, s);
for (size_t s=0; s < strip.getSegmentsNum(); s++) {
strip.getSegment(s).setOption(SEG_OPTION_FREEZE, false);
}
if (realtimeMode && !realtimeOverride && useMainSegmentOnly) { // keep live segment frozen if live
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, true, strip.getMainSegmentId());
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, true);
}
}
@ -304,7 +359,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
realtimeOverride = root[F("lor")] | realtimeOverride;
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride, strip.getMainSegmentId());
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride);
}
if (root.containsKey("live")) {
@ -326,15 +381,13 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (id < 0) {
//apply all selected segments
//bool didSet = false;
for (byte s = 0; s < strip.getMaxSegments(); s++) {
WS2812FX::Segment &sg = strip.getSegment(s);
if (sg.isActive()) {
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
Segment &sg = strip.getSegment(s);
if (sg.isSelected()) {
deserializeSegment(segVar, s, presetId);
//didSet = true;
}
}
}
//TODO: not sure if it is good idea to change first active but unselected segment
//if (!didSet) deserializeSegment(segVar, strip.getMainSegmentId(), presetId);
} else {
@ -342,8 +395,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
}
} else {
JsonArray segs = segVar.as<JsonArray>();
for (JsonObject elem : segs)
{
for (JsonObject elem : segs) {
deserializeSegment(elem, it, presetId);
it++;
}
@ -396,7 +448,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
return stateResponse;
}
void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset, bool segmentBounds)
void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset, bool segmentBounds)
{
root["id"] = id;
if (segmentBounds) {
@ -423,7 +475,7 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool fo
// this will reduce RAM footprint from ~300 bytes to 84 bytes per segment
char colstr[70]; colstr[0] = '['; colstr[1] = '\0'; //max len 68 (5 chan, all 255)
const char *format = strip.hasWhiteChannel() ? PSTR("[%u,%u,%u,%u]") : PSTR("[%u,%u,%u]");
for (uint8_t i = 0; i < 3; i++)
for (size_t i = 0; i < 3; i++)
{
byte segcol[4]; byte* c = segcol;
segcol[0] = R(seg.colors[i]);
@ -452,6 +504,8 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool fo
root[F("mY")] = seg.getOption(SEG_OPTION_MIRROR_Y);
root[F("tp")] = seg.getOption(SEG_OPTION_TRANSPOSED);
}
root[F("ssim")] = seg.soundSim;
root[F("mp12")] = seg.map1D2D;
}
void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segmentBounds)
@ -493,9 +547,17 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
bool selectedSegmentsOnly = root[F("sc")] | false;
JsonArray seg = root.createNestedArray("seg");
for (byte s = 0; s < strip.getMaxSegments(); s++) {
WS2812FX::Segment &sg = strip.getSegment(s);
if (selectedSegmentsOnly && !sg.isSelected()) continue;
for (size_t s = 0; s < strip.getMaxSegments(); s++) {
if (s >= strip.getSegmentsNum()) {
if (forPreset && segmentBounds) { //disable segments not part of preset
JsonObject seg0 = seg.createNestedObject();
seg0["stop"] = 0;
continue;
} else
break;
}
Segment &sg = strip.getSegment(s);
if (!forPreset && selectedSegmentsOnly && !sg.isSelected()) continue;
if (sg.isActive()) {
JsonObject seg0 = seg.createNestedObject();
serializeSegment(seg0, sg, s, forPreset, segmentBounds);
@ -518,18 +580,22 @@ void serializeInfo(JsonObject root)
leds["fps"] = strip.getFps();
leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0;
leds[F("maxseg")] = strip.getMaxSegments();
//leds[F("actseg")] = strip.getActiveSegmentsNum();
//leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config
#ifndef WLED_DISABLE_2D
if (strip.isMatrix) {
JsonObject matrix = leds.createNestedObject("matrix");
matrix["w"] = strip.matrixWidth;
matrix["h"] = strip.matrixHeight;
}
#endif
uint8_t totalLC = 0;
JsonArray lcarr = leds.createNestedArray(F("seglc"));
uint8_t nSegs = strip.getLastActiveSegmentId();
for (byte s = 0; s <= nSegs; s++) {
size_t nSegs = strip.getSegmentsNum();
for (size_t s = 0; s < nSegs; s++) {
if (!strip.getSegment(s).isActive()) continue;
uint8_t lc = strip.getSegment(s).getLightCapabilities();
totalLC |= lc;
lcarr.add(lc);
@ -577,7 +643,7 @@ void serializeInfo(JsonObject root)
root[F("palcount")] = strip.getPaletteCount();
JsonArray ledmaps = root.createNestedArray(F("maps"));
for (uint8_t i=0; i<10; i++) {
for (size_t i=0; i<10; i++) {
char fileName[16];
strcpy_P(fileName, PSTR("/ledmap"));
if (i) sprintf(fileName +7, "%d", i);
@ -956,7 +1022,7 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient)
obuf = buffer;
olen = 9;
for (uint16_t i= 0; i < used; i += n)
for (size_t i= 0; i < used; i += n)
{
uint32_t c = strip.getPixelColor(i);
uint8_t r = qadd8(W(c), R(c)); //add white channel to RGB channels as a simple RGBW -> RGB map

View File

@ -8,7 +8,7 @@ void setValuesFromMainSeg() { setValuesFromSegment(strip.getMainSegment
void setValuesFromFirstSelectedSeg() { setValuesFromSegment(strip.getFirstSelectedSegId()); }
void setValuesFromSegment(uint8_t s)
{
WS2812FX::Segment& seg = strip.getSegment(s);
Segment& seg = strip.getSegment(s);
col[0] = R(seg.colors[0]);
col[1] = G(seg.colors[0]);
col[2] = B(seg.colors[0]);
@ -30,9 +30,9 @@ void applyValuesToSelectedSegs()
{
// copy of first selected segment to tell if value was updated
uint8_t firstSel = strip.getFirstSelectedSegId();
WS2812FX::Segment selsegPrev = strip.getSegment(firstSel);
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
Segment selsegPrev = strip.getSegment(firstSel);
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (i != firstSel && (!seg.isActive() || !seg.isSelected())) continue;
if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;}
@ -41,8 +41,8 @@ void applyValuesToSelectedSegs()
if (effectCurrent != selsegPrev.mode) {strip.setMode(i, effectCurrent); stateChanged = true;}
uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]);
uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]);
if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0, i); stateChanged = true;}
if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1, i); stateChanged = true;}
if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0); stateChanged = true;}
if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1); stateChanged = true;}
}
}

View File

@ -69,7 +69,7 @@ void parseLxJson(int lxValue, byte segId, bool secondary)
} else {
DEBUG_PRINT(F("LX: segment "));
DEBUG_PRINTLN(segId);
strip.getSegment(segId).setColor(secondary, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]), segId);
strip.getSegment(segId).setColor(secondary, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
}
}
}

View File

@ -13,8 +13,6 @@
#ifndef PalettesWLED_h
#define PalettesWLED_h
#define GRADIENT_PALETTE_COUNT 58
const byte ib_jul01_gp[] PROGMEM = {
0, 194, 1, 1,
94, 1, 29, 18,

View File

@ -56,7 +56,8 @@ enum struct PinOwner : uint8_t {
// #define USERMOD_ID_SN_PHOTORESISTOR // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager
UM_RGBRotaryEncoder = USERMOD_RGB_ROTARY_ENCODER, // 0x16 // Usermod "rgb-rotary-encoder.h"
UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h"
UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE // 0x1E // Usermod: "audio_reactive.h"
UM_BME280 = USERMOD_ID_BME280, // 0x18 // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins
UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE // 0x1E // Usermod "audio_reactive.h"
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");

View File

@ -84,7 +84,7 @@ void handlePresets(bool force)
presetToApply = 0; //clear request for preset
callModeToApply = 0;
DEBUG_PRINTLN(F("Applying preset: "));
DEBUG_PRINT(F("Applying preset: "));
DEBUG_PRINTLN(tmpPreset);
#ifdef ARDUINO_ARCH_ESP32

View File

@ -86,6 +86,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
Bus::setAutoWhiteMode(request->arg(F("AW")).toInt());
strip.setTargetFps(request->arg(F("FR")).toInt());
bool busesChanged = false;
for (uint8_t s = 0; s < WLED_MAX_BUSSES; s++) {
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
@ -98,7 +99,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //channel swap
if (!request->hasArg(lp)) {
DEBUG_PRINTLN(F("No data.")); break;
DEBUG_PRINT(F("No data for "));
DEBUG_PRINTLN(s);
break;
}
for (uint8_t i = 0; i < 5; i++) {
lp[1] = 48+i;
@ -118,10 +121,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
awmode = request->arg(aw).toInt();
channelSwap = (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) ? request->arg(wo).toInt() : 0;
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode);
doInitBusses = true;
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
ColorOrderMap com = {};
for (uint8_t s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
@ -197,6 +202,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (t >= 0 && t < 4) strip.paletteBlend = t;
t = request->arg(F("BF")).toInt();
if (t > 0) briMultiplier = t;
doInitBusses = busesChanged;
}
//UI
@ -547,6 +554,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
releaseJSONBufferLock();
}
#ifndef WLED_DISABLE_2D
//2D panels
if (subPage == 10)
{
@ -570,6 +578,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
strip.setUpMatrix(); // will check limits
}
#endif
lastEditTime = millis();
if (subPage != 2 && !doReboot) serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init)
@ -599,17 +608,17 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("SS="));
if (pos > 0) {
byte t = getNumVal(&req, pos);
if (t < strip.getMaxSegments()) {
if (t < strip.getSegmentsNum()) {
selectedSeg = t;
singleSegment = true;
}
}
WS2812FX::Segment& selseg = strip.getSegment(selectedSeg);
Segment& selseg = strip.getSegment(selectedSeg);
pos = req.indexOf(F("SV=")); //segment selected
if (pos > 0) {
byte t = getNumVal(&req, pos);
if (t == 2) for (uint8_t i = 0; i < strip.getMaxSegments(); i++) strip.getSegment(i).setOption(SEG_OPTION_SELECTED, 0); // unselect other segments
if (t == 2) for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) strip.getSegment(i).setOption(SEG_OPTION_SELECTED, 0); // unselect other segments
selseg.setOption(SEG_OPTION_SELECTED, t);
}
@ -657,9 +666,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("SB=")); //Segment brightness/opacity
if (pos > 0) {
byte segbri = getNumVal(&req, pos);
selseg.setOption(SEG_OPTION_ON, segbri, selectedSeg);
selseg.setOption(SEG_OPTION_ON, segbri);
if (segbri) {
selseg.setOpacity(segbri, selectedSeg);
selseg.setOpacity(segbri);
}
}
@ -762,7 +771,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (pos > 0) {
colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str());
uint32_t col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]);
selseg.setColor(2, col2, selectedSeg); // defined above (SS= or main)
selseg.setColor(2, col2); // defined above (SS= or main)
stateChanged = true;
if (!singleSegment) strip.setColor(2, col2); // will set color to all active & selected segments
}
@ -791,14 +800,14 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (col0Changed) {
stateChanged = true;
uint32_t colIn0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]);
selseg.setColor(0, colIn0, selectedSeg);
selseg.setColor(0, colIn0);
if (!singleSegment) strip.setColor(0, colIn0); // will set color to all active & selected segments
}
if (col1Changed) {
stateChanged = true;
uint32_t colIn1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]);
selseg.setColor(1, colIn1, selectedSeg);
selseg.setColor(1, colIn1);
if (!singleSegment) strip.setColor(1, colIn1); // will set color to all active & selected segments
}
@ -815,8 +824,8 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
stateChanged |= (fxModeChanged || speedChanged || intensityChanged || paletteChanged);
// apply to main and all selected segments to prevent #1618.
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (i != selectedSeg && (singleSegment || !seg.isActive() || !seg.isSelected())) continue; // skip non main segments if not applying to all
if (fxModeChanged) strip.setMode(i, effectIn);
if (speedChanged) seg.speed = speedIn;
@ -918,7 +927,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
realtimeOverride = getNumVal(&req, pos);
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride, strip.getMainSegmentId());
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride);
}
}

View File

@ -28,7 +28,7 @@ void notify(byte callMode, bool followUp)
default: return;
}
byte udpOut[WLEDPACKETSIZE];
WS2812FX::Segment& mainseg = strip.getMainSegment();
Segment& mainseg = strip.getMainSegment();
udpOut[0] = 0; //0: wled notifier protocol 1: WARLS protocol
udpOut[1] = callMode;
udpOut[2] = bri;
@ -89,12 +89,14 @@ void notify(byte callMode, bool followUp)
udpOut[37] = strip.hasCCTBus() ? 0 : 255; //check this is 0 for the next value to be significant
udpOut[38] = mainseg.cct;
udpOut[39] = strip.getMaxSegments();
udpOut[39] = strip.getActiveSegmentsNum();
udpOut[40] = UDP_SEG_SIZE; //size of each loop iteration (one segment)
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment &selseg = strip.getSegment(i);
uint16_t ofs = 41 + i*UDP_SEG_SIZE; //start of segment offset byte
udpOut[0 +ofs] = i;
size_t s = 0, nsegs = strip.getSegmentsNum();
for (size_t i = 0; i < nsegs; i++) {
Segment &selseg = strip.getSegment(i);
if (!selseg.isActive()) continue;
uint16_t ofs = 41 + s*UDP_SEG_SIZE; //start of segment offset byte
udpOut[0 +ofs] = s;
udpOut[1 +ofs] = selseg.start >> 8;
udpOut[2 +ofs] = selseg.start & 0xFF;
udpOut[3 +ofs] = selseg.stop >> 8;
@ -122,6 +124,7 @@ void notify(byte callMode, bool followUp)
udpOut[25+ofs] = B(selseg.colors[2]);
udpOut[26+ofs] = W(selseg.colors[2]);
udpOut[27+ofs] = selseg.cct;
++s;
}
//uint16_t offs = SEG_OFFSET;
@ -143,20 +146,20 @@ void realtimeLock(uint32_t timeoutMs, byte md)
if (!realtimeMode && !realtimeOverride) {
uint16_t stop, start;
if (useMainSegmentOnly) {
WS2812FX::Segment& mainseg = strip.getMainSegment();
Segment& mainseg = strip.getMainSegment();
start = mainseg.start;
stop = mainseg.stop;
mainseg.setOption(SEG_OPTION_FREEZE, true, strip.getMainSegmentId());
mainseg.setOption(SEG_OPTION_FREEZE, true);
} else {
start = 0;
stop = strip.getLengthTotal();
}
// clear strip/segment
for (uint16_t i = start; i < stop; i++) strip.setPixelColor(i,0,0,0,0);
for (size_t i = start; i < stop; i++) strip.setPixelColor(i,0,0,0,0);
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (useMainSegmentOnly && bri == 0) {
for (uint8_t s=0; s < strip.getMaxSegments(); s++) {
strip.getSegment(s).setOption(SEG_OPTION_FREEZE, true, s);
for (size_t s=0; s < strip.getSegmentsNum(); s++) {
strip.getSegment(s).setOption(SEG_OPTION_FREEZE, true);
}
}
}
@ -183,7 +186,7 @@ void exitRealtime() {
realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately
realtimeIP[0] = 0;
if (useMainSegmentOnly) { // unfreeze live segment again
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, false, strip.getMainSegmentId());
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, false);
}
updateInterfaces(CALL_MODE_WS_SEND);
}
@ -221,7 +224,7 @@ void handleNotifications()
if (!udpConnected) return;
bool isSupp = false;
uint16_t packetSize = notifierUdp.parsePacket();
size_t packetSize = notifierUdp.parsePacket();
if (!packetSize && udp2Connected) {
packetSize = notifier2Udp.parsePacket();
isSupp = true;
@ -241,7 +244,7 @@ void handleNotifications()
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
uint16_t id = 0;
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 0; i < packetSize -2; i += 3)
for (size_t i = 0; i < packetSize -2; i += 3)
{
setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0);
id++; if (id >= totalLen) break;
@ -275,7 +278,7 @@ void handleNotifications()
}
if (it != Nodes.end()) {
for (byte x = 0; x < 4; x++) {
for (size_t x = 0; x < 4; x++) {
it->second.ip[x] = udpIn[x + 2];
}
it->second.age = 0; // reset 'age counter'
@ -287,7 +290,7 @@ void handleNotifications()
it->second.nodeType = udpIn[38];
uint32_t build = 0;
if (len >= 44)
for (byte i=0; i<sizeof(uint32_t); i++)
for (size_t i=0; i<sizeof(uint32_t); i++)
build |= udpIn[40+i]<<(8*i);
it->second.build = build;
}
@ -339,11 +342,11 @@ void handleNotifications()
if (applyEffects && currentPlaylist >= 0) unloadPlaylist();
if (version > 10 && (receiveSegmentOptions || receiveSegmentBounds)) {
uint8_t numSrcSegs = udpIn[39];
for (uint8_t i = 0; i < numSrcSegs; i++) {
for (size_t i = 0; i < numSrcSegs; i++) {
uint16_t ofs = 41 + i*udpIn[40]; //start of segment offset byte
uint8_t id = udpIn[0 +ofs];
if (id > strip.getMaxSegments()) break;
WS2812FX::Segment& selseg = strip.getSegment(id);
if (id > strip.getSegmentsNum()) break;
Segment& selseg = strip.getSegment(id);
uint16_t start = (udpIn[1+ofs] << 8 | udpIn[2+ofs]);
uint16_t stop = (udpIn[3+ofs] << 8 | udpIn[4+ofs]);
uint16_t offset = (udpIn[7+ofs] << 8 | udpIn[8+ofs]);
@ -351,8 +354,8 @@ void handleNotifications()
strip.setSegment(id, start, stop, selseg.grouping, selseg.spacing, offset);
continue;
}
for (uint8_t j = 0; j<4; j++) selseg.setOption(j, (udpIn[9 +ofs] >> j) & 0x01, id); //only take into account mirrored, selected, on, reversed
selseg.setOpacity(udpIn[10+ofs], id);
for (size_t j = 0; j<4; j++) selseg.setOption(j, (udpIn[9 +ofs] >> j) & 0x01); //only take into account mirrored, selected, on, reversed
selseg.setOpacity(udpIn[10+ofs]);
if (applyEffects) {
strip.setMode(id, udpIn[11+ofs]);
selseg.speed = udpIn[12+ofs];
@ -360,10 +363,10 @@ void handleNotifications()
selseg.palette = udpIn[14+ofs];
}
if (receiveNotificationColor || !someSel) {
selseg.setColor(0, RGBW32(udpIn[15+ofs],udpIn[16+ofs],udpIn[17+ofs],udpIn[18+ofs]), id);
selseg.setColor(1, RGBW32(udpIn[19+ofs],udpIn[20+ofs],udpIn[21+ofs],udpIn[22+ofs]), id);
selseg.setColor(2, RGBW32(udpIn[23+ofs],udpIn[24+ofs],udpIn[25+ofs],udpIn[26+ofs]), id);
selseg.setCCT(udpIn[27+ofs], id);
selseg.setColor(0, RGBW32(udpIn[15+ofs],udpIn[16+ofs],udpIn[17+ofs],udpIn[18+ofs]));
selseg.setColor(1, RGBW32(udpIn[19+ofs],udpIn[20+ofs],udpIn[21+ofs],udpIn[22+ofs]));
selseg.setColor(2, RGBW32(udpIn[23+ofs],udpIn[24+ofs],udpIn[25+ofs],udpIn[26+ofs]));
selseg.setCCT(udpIn[27+ofs]);
}
//setSegment() also properly resets segments
if (receiveSegmentBounds) {
@ -377,8 +380,8 @@ void handleNotifications()
// simple effect sync, applies to all selected segments
if (applyEffects && (version < 11 || !receiveSegmentOptions)) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
for (size_t i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
if (udpIn[8] < strip.getModeCount()) strip.setMode(i, udpIn[8]);
seg.speed = udpIn[9];
@ -458,7 +461,7 @@ void handleNotifications()
uint16_t id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 6; i < tpmPayloadFrameSize + 4; i += 3)
for (size_t i = 6; i < tpmPayloadFrameSize + 4U; i += 3)
{
if (id < totalLen)
{
@ -494,14 +497,14 @@ void handleNotifications()
uint16_t totalLen = strip.getLengthTotal();
if (udpIn[0] == 1) //warls
{
for (uint16_t i = 2; i < packetSize -3; i += 4)
for (size_t i = 2; i < packetSize -3; i += 4)
{
setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0);
}
} else if (udpIn[0] == 2) //drgb
{
uint16_t id = 0;
for (uint16_t i = 2; i < packetSize -2; i += 3)
for (size_t i = 2; i < packetSize -2; i += 3)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
@ -510,7 +513,7 @@ void handleNotifications()
} else if (udpIn[0] == 3) //drgbw
{
uint16_t id = 0;
for (uint16_t i = 2; i < packetSize -3; i += 4)
for (size_t i = 2; i < packetSize -3; i += 4)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
@ -519,7 +522,7 @@ void handleNotifications()
} else if (udpIn[0] == 4) //dnrgb
{
uint16_t id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (uint16_t i = 4; i < packetSize -2; i += 3)
for (size_t i = 4; i < packetSize -2; i += 3)
{
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
@ -528,7 +531,7 @@ void handleNotifications()
} else if (udpIn[0] == 5) //dnrgbw
{
uint16_t id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (uint16_t i = 4; i < packetSize -2; i += 4)
for (size_t i = 4; i < packetSize -2; i += 4)
{
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
@ -618,7 +621,7 @@ void sendSysInfoUDP()
data[0] = 255;
data[1] = 1;
for (byte x = 0; x < 4; x++) {
for (size_t x = 0; x < 4; x++) {
data[x + 2] = ip[x];
}
memcpy((byte *)data + 6, serverDescription, 32);
@ -632,7 +635,7 @@ void sendSysInfoUDP()
data[39] = ip[3]; // unit ID == last IP number
uint32_t build = VERSION;
for (byte i=0; i<sizeof(uint32_t); i++)
for (size_t i=0; i<sizeof(uint32_t); i++)
data[40+i] = (build>>(8*i)) & 0xFF;
IPAddress broadcastIP(255, 255, 255, 255);
@ -684,15 +687,15 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8
case 0: // DDP
{
// calculate the number of UDP packets we need to send
uint16_t channelCount = length * 3; // 1 channel for every R,G,B value
uint16_t packetCount = ((channelCount-1) / DDP_CHANNELS_PER_PACKET) +1;
size_t channelCount = length * 3; // 1 channel for every R,G,B value
size_t packetCount = ((channelCount-1) / DDP_CHANNELS_PER_PACKET) +1;
// there are 3 channels per RGB pixel
uint32_t channel = 0; // TODO: allow specifying the start channel
// the current position in the buffer
uint16_t bufferOffset = 0;
size_t bufferOffset = 0;
for (uint16_t currentPacket = 0; currentPacket < packetCount; currentPacket++) {
for (size_t currentPacket = 0; currentPacket < packetCount; currentPacket++) {
if (sequenceNumber > 15) sequenceNumber = 0;
if (!ddpUdp.beginPacket(client, DDP_DEFAULT_PORT)) { // port defined in ESPAsyncE131.h
@ -701,10 +704,10 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8
}
// the amount of data is AFTER the header in the current packet
uint16_t packetSize = DDP_CHANNELS_PER_PACKET;
size_t packetSize = DDP_CHANNELS_PER_PACKET;
uint8_t flags = DDP_FLAGS1_VER1;
if (currentPacket == (packetCount - 1)) {
if (currentPacket == (packetCount - 1U)) {
// last packet, set the push flag
// TODO: determine if we want to send an empty push packet to each destination after sending the pixel data
flags = DDP_FLAGS1_VER1 | DDP_FLAGS1_PUSH;
@ -729,7 +732,7 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8
// write the colors, the write write(const uint8_t *buffer, size_t size)
// function is just a loop internally too
for (uint16_t i = 0; i < packetSize; i += 3) {
for (size_t i = 0; i < packetSize; i += 3) {
ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // R
ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // G
ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // B

View File

@ -59,5 +59,3 @@ bool UsermodManager::add(Usermod* um)
ums[numMods++] = um;
return true;
}
byte UsermodManager::getModCount() {return numMods;}

View File

@ -238,14 +238,13 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
char lineBuffer[256];
//strcpy_P(lineBuffer, (const char*)pgm_read_dword(&(WS2812FX::_modeData[mode])));
strcpy_P(lineBuffer, strip.getModeData(mode));
if (strlen(lineBuffer) > 0) {
size_t len = strlen(lineBuffer);
size_t j = 0;
for (; j < maxLen; j++) {
for (; j < maxLen && j < len; j++) {
if (lineBuffer[j] == '\0' || lineBuffer[j] == '@') break;
dest[j] = lineBuffer[j];
}
dest[j] = 0; // terminate string
}
return strlen(dest);
} else return 0;
}
@ -282,7 +281,7 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
// extracts effect slider data (1st group after @)
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen)
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var)
{
dest[0] = '\0'; // start by clearing buffer
@ -292,15 +291,21 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
int16_t start = lineBuffer.indexOf('@');
int16_t stop = lineBuffer.indexOf(';', start);
if (start>0 && stop>0) {
String names = lineBuffer.substring(start+1, stop);
int16_t nameBegin = 0, nameEnd;
String names = lineBuffer.substring(start, stop); // include @
int16_t nameBegin = 1, nameEnd, nameDefault;
if (slider < 10) {
for (size_t i=0; i<=slider; i++) {
const char *tmpstr;
dest[0] = '\0'; //clear dest buffer
if (i > 0 && nameBegin == 0) break; // there are no more names
if (nameBegin == 0) break; // there are no more names
nameEnd = names.indexOf(',', nameBegin);
if (i == slider) {
nameDefault = names.indexOf('=', nameBegin); // find default value
if (nameDefault > 0 && var && ((nameEnd>0 && nameDefault<nameEnd) || nameEnd<0)) {
*var = (uint8_t)atoi(names.substring(nameDefault+1).c_str());
}
if (names.charAt(nameBegin) == '!') {
switch (i) {
switch (slider) {
case 0: tmpstr = PSTR("FX Speed"); break;
case 1: tmpstr = PSTR("FX Intensity"); break;
case 2: tmpstr = PSTR("FX Custom 1"); break;
@ -312,10 +317,24 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
if (nameEnd<0) tmpstr = names.substring(nameBegin).c_str(); // did not find ",", last name?
else tmpstr = names.substring(nameBegin, nameEnd).c_str();
}
strncpy(dest, tmpstr, maxLen); // copy the name into buffer (replacing previous)
strlcpy(dest, tmpstr, maxLen); // copy the name into buffer (replacing previous)
}
nameBegin = nameEnd+1; // next name (if "," is not found it will be 0)
} // next slider
} else if (slider == 255) {
// palette
strlcpy(dest, "pal", maxLen);
names = lineBuffer.substring(stop+1); // stop has index of color slot names
nameBegin = names.indexOf(';'); // look for palette
if (nameBegin >= 0) {
nameEnd = names.indexOf(';', nameBegin+1);
if (!isdigit(names[nameBegin+1])) nameBegin = names.indexOf('=', nameBegin+1); // look for default value
if (nameEnd >= 0 && nameBegin > nameEnd) nameBegin = -1;
if (nameBegin >= 0 && var) {
*var = (uint8_t)atoi(names.substring(nameBegin+1).c_str());
}
}
}
// we have slider name (including default value) in the dest buffer
for (size_t i=0; i<strlen(dest); i++) if (dest[i]=='=') { dest[i]='\0'; break; } // truncate default value
@ -325,6 +344,7 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
case 0: strncpy_P(dest, PSTR("FX Speed"), maxLen); break;
case 1: strncpy_P(dest, PSTR("FX Intensity"), maxLen); break;
}
dest[maxLen] = '\0'; // strncpy does not necessarily null terminate string
}
}
return strlen(dest);
@ -333,6 +353,23 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
}
int16_t extractModeDefaults(uint8_t mode, const char *segVar)
{
if (mode < strip.getModeCount()) {
String lineBuffer = strip.getModeData(mode);
if (lineBuffer.length() > 0) {
int16_t start = lineBuffer.lastIndexOf(';');
if (start<0) return -1;
int16_t stop = lineBuffer.indexOf(segVar, start+1);
if (stop<0) return -1;
return atoi(lineBuffer.substring(stop+strlen(segVar)+1).c_str());
}
}
return -1;
}
uint16_t crc16(const unsigned char* data_p, size_t length) {
uint8_t x;
uint16_t crc = 0xFFFF;
@ -344,3 +381,154 @@ uint16_t crc16(const unsigned char* data_p, size_t length) {
}
return crc;
}
///////////////////////////////////////////////////////////////////////////////
// Begin simulateSound (to enable audio enhanced effects to display something)
///////////////////////////////////////////////////////////////////////////////
// Currently 4 types defined, to be fine tuned and new types added
typedef enum UM_SoundSimulations {
UMS_BeatSin = 0,
UMS_WeWillRockYou,
UMS_10_3,
UMS_14_3
} um_soundSimulations_t;
// this is still work in progress
um_data_t* simulateSound(uint8_t simulationId)
{
static float sampleAvg;
static uint8_t soundAgc;
static float sampleAgc;
static int16_t sampleRaw;
static int16_t rawSampleAgc;
static uint8_t samplePeak;
static float FFT_MajorPeak;
static float FFT_Magnitude;
static uint8_t maxVol;
static uint8_t binNum;
static float multAgc;
float sampleGain;
uint8_t soundSquelch;
uint8_t inputLevel;
//arrays
uint8_t *fftResult;
uint8_t *myVals;
float *fftBin;
static um_data_t* um_data = nullptr;
if (!um_data) {
//claim storage for arrays
fftResult = (uint8_t *)malloc(sizeof(uint8_t) * 16);
myVals = (uint8_t *)malloc(sizeof(uint8_t) * 32);
fftBin = (float *)malloc(sizeof(float) * 256); // not used (for debugging purposes)
// initialize um_data pointer structure
// NOTE!!!
// This may change as AudioReactive usermod may change
um_data = new um_data_t;
um_data->u_size = 18;
um_data->u_type = new um_types_t[um_data->u_size];
um_data->u_data = new void*[um_data->u_size];
um_data->u_data[ 0] = &sampleAvg;
um_data->u_data[ 1] = &soundAgc;
um_data->u_data[ 2] = &sampleAgc;
um_data->u_data[ 3] = &sampleRaw;
um_data->u_data[ 4] = &rawSampleAgc;
um_data->u_data[ 5] = &samplePeak;
um_data->u_data[ 6] = &FFT_MajorPeak;
um_data->u_data[ 7] = &FFT_Magnitude;
um_data->u_data[ 8] = fftResult;
um_data->u_data[ 9] = &maxVol;
um_data->u_data[10] = &binNum;
um_data->u_data[11] = &multAgc;
um_data->u_data[14] = myVals; //*used (only once, Pixels)
um_data->u_data[13] = &sampleGain;
um_data->u_data[15] = &soundSquelch;
um_data->u_data[16] = fftBin; //only used in binmap
um_data->u_data[17] = &inputLevel;
} else {
// get arrays from um_data
fftResult = (uint8_t*)um_data->u_data[8];
myVals = (uint8_t*)um_data->u_data[14];
fftBin = (float*)um_data->u_data[16];
}
uint32_t ms = millis();
switch (simulationId) {
default:
case UMS_BeatSin:
for (int i = 0; i<16; i++)
fftResult[i] = beatsin8(120 / (i+1), 0, 255);
// fftResult[i] = (beatsin8(120, 0, 255) + (256/16 * i)) % 256;
sampleAvg = fftResult[8];
break;
case UMS_WeWillRockYou:
if (ms%2000 < 200) {
sampleAvg = random8(255);
for (int i = 0; i<5; i++)
fftResult[i] = random8(255);
}
else if (ms%2000 < 400) {
sampleAvg = 0;
for (int i = 0; i<16; i++)
fftResult[i] = 0;
}
else if (ms%2000 < 600) {
sampleAvg = random8(255);
for (int i = 5; i<11; i++)
fftResult[i] = random8(255);
}
else if (ms%2000 < 800) {
sampleAvg = 0;
for (int i = 0; i<16; i++)
fftResult[i] = 0;
}
else if (ms%2000 < 1000) {
sampleAvg = random8(255);
for (int i = 11; i<16; i++)
fftResult[i] = random8(255);
}
else {
sampleAvg = 0;
for (int i = 0; i<16; i++)
fftResult[i] = 0;
}
break;
case UMS_10_3:
for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
sampleAvg = fftResult[8];
break;
case UMS_14_3:
for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
sampleAvg = fftResult[8];
break;
}
//derive other vars from sampleAvg
//sampleAvg = mapf(sampleAvg, 0, 255, 0, 255); // help me out here
soundAgc = 0; //only avg in simulations
sampleAgc = sampleAvg;
sampleRaw = sampleAvg;
sampleRaw = map(sampleRaw, 50, 190, 0, 224);
rawSampleAgc = sampleAvg;
samplePeak = random8() > 250;
FFT_MajorPeak = sampleAvg;
FFT_Magnitude = sampleAvg;
multAgc = sampleAvg;
myVals[millis()%32] = sampleAvg; // filling values semi randomly (why?)
sampleGain = 40;
soundSquelch = 10;
maxVol = 10; // this gets feedback fro UI
binNum = 8; // this gets feedback fro UI
inputLevel = 128; // this gets feedback fro UI
return um_data;
}

View File

@ -118,6 +118,7 @@ void WLED::loop()
if (stripMillis > maxStripMillis) maxStripMillis = stripMillis;
#endif
}
yield();
#ifdef ESP8266
MDNS.update();
@ -692,6 +693,7 @@ void WLED::handleConnection()
DEBUG_PRINT(F("Heap too low! "));
DEBUG_PRINTLN(heap);
forceReconnect = true;
strip.purgeSegments(); // remove inactive segments from memory
}
lastHeap = heap;
heapTime = now;

View File

@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2206281
#define VERSION 2207271
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@ -152,6 +152,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#define PSRAMDynamicJsonDocument DynamicJsonDocument
#endif
#include "const.h"
#include "fcn_declare.h"
#include "html_ui.h"
#ifdef WLED_ENABLE_SIMPLE_UI
@ -159,12 +160,11 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#endif
#include "html_settings.h"
#include "html_other.h"
#include "FX.h"
#include "ir_codes.h"
#include "const.h"
#include "NodeStruct.h"
#include "pin_manager.h"
#include "bus_manager.h"
#include "FX.h"
#ifndef CLIENT_SSID
#define CLIENT_SSID DEFAULT_CLIENT_SSID

View File

@ -418,7 +418,7 @@ void deEEP() {
segObj[F("ix")] = EEPROM.read(i+16);
segObj["pal"] = EEPROM.read(i+17);
} else {
WS2812FX::Segment* seg = strip.getSegments();
Segment* seg = strip.getSegments();
memcpy(seg, EEPROM.getDataPtr() +i+2, 240);
if (ver == 2) { //versions before 2004230 did not have opacity
for (byte j = 0; j < strip.getMaxSegments(); j++)

View File

@ -297,8 +297,8 @@ void initServer()
DEBUG_PRINTLN(F("Update Success"));
} else {
DEBUG_PRINTLN(F("Update Failed"));
WLED::instance().enableWatchdog();
usermods.onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
WLED::instance().enableWatchdog();
}
}
});

View File

@ -58,6 +58,10 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
if (verboseResponse) {
sendDataWs(client);
lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500);
} else {
// we have to send something back otherwise WS connection closes
client->text(F("{\"success\":true}"));
lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500);
}
}
} else {
@ -96,8 +100,6 @@ void sendDataWs(AsyncWebSocketClient * client)
if (!ws.count()) return;
AsyncWebSocketMessageBuffer * buffer;
while (strip.isUpdating()) yield();
if (!requestJSONBufferLock(12)) return;
JsonObject state = doc.createNestedObject("state");

View File

@ -308,6 +308,12 @@ void getSettingsJS(byte subPage, char* dest)
// set limits
oappend(SET_F("bLimits("));
#ifdef ESP32
// requested by @softhack007 https://github.com/blazoncek/WLED/issues/33
if (usermods.lookup(USERMOD_ID_AUDIOREACTIVE))
oappend(itoa(WLED_MAX_BUSSES-2,nS,10)); // prevent use of I2S buses if audio installed
else
#endif
oappend(itoa(WLED_MAX_BUSSES,nS,10)); oappend(",");
oappend(itoa(MAX_LEDS_PER_BUS,nS,10)); oappend(",");
oappend(itoa(MAX_LED_MEMORY,nS,10)); oappend(",");
@ -636,6 +642,7 @@ void getSettingsJS(byte subPage, char* dest)
if (subPage == 10) // 2D matrices
{
sappend('v',SET_F("SOMP"),strip.isMatrix);
#ifndef WLED_DISABLE_2D
oappend(SET_F("resetPanels();"));
if (strip.isMatrix) {
sappend('v',SET_F("PH"),strip.panelH);
@ -660,5 +667,8 @@ void getSettingsJS(byte subPage, char* dest)
pO[l] = 'S'; sappend('c',pO,strip.panel[i].serpentine);
}
}
#else
oappend(SET_F("gId(\"somp\").remove(1);")); // remove 2D option from dropdown
#endif
}
}