Merge remote-tracking branch 'origin/segment-api' into segment-api

This commit is contained in:
ewowi 2022-07-24 17:13:13 +02:00
commit d3e9f51d6b
18 changed files with 2386 additions and 223 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

@ -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
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
uint8_t SCL_PIN = 22;
uint8_t SDA_PIN = 21;
#else // ESP8266 boards
uint8_t SCL_PIN = 5;
uint8_t SDA_PIN = 4;
// set the default pins based on the architecture, these get overridden by Usermod menu settings
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
#define HW_PIN_SCL 22
#define HW_PIN_SDA 21
#else // ESP8266 boards
#define HW_PIN_SCL 5
#define HW_PIN_SDA 4
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
#endif
#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

@ -3241,7 +3241,7 @@ uint16_t mode_starburst(void) {
return FRAMETIME;
}
#undef STARBURST_MAX_FRAG
static const char *_data_FX_MODE_STARBURST PROGMEM = "Fireworks Starburst;;;mp12=0"; //pixels
static const char *_data_FX_MODE_STARBURST PROGMEM = "Fireworks Starburst@Chance,Fragments;,!,;!=11;mp12=0";
/*
@ -3377,7 +3377,7 @@ uint16_t mode_exploding_fireworks(void)
return FRAMETIME;
}
#undef MAX_SPARKS
static const char *_data_FX_MODE_EXPLODING_FIREWORKS PROGMEM = "Fireworks 1D@Gravity,Firing side;!,!,;!";
static const char *_data_FX_MODE_EXPLODING_FIREWORKS PROGMEM = "Fireworks 1D@Gravity,Firing side=128;!,!,;!=11";
/*
@ -5917,7 +5917,7 @@ uint16_t mode_2Dscrollingtext(void) {
return FRAMETIME;
}
static const char *_data_FX_MODE_2DSCROLLTEXT PROGMEM = "2D Scrolling Text@!,Y Offset,Trail=0,Font size;!,!;!";
static const char *_data_FX_MODE_2DSCROLLTEXT PROGMEM = "2D Scrolling Text@!,Y Offset=128,Trail=0,Font size;!,!;!;rev=0,mi=0,rY=0,mY=0";
////////////////////////////
@ -6257,7 +6257,7 @@ uint16_t mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuli
return FRAMETIME;
} // mode_ripplepeak()
static const char *_data_FX_MODE_RIPPLEPEAK PROGMEM = " ♪ Ripple Peak@Fade rate,Max # of ripples,,Select bin,Volume (minimum)=0;!,!;!;mp12=0;ssim=1"; // Pixel, Beatsin
static const char *_data_FX_MODE_RIPPLEPEAK PROGMEM = " ♪ Ripple Peak@Fade rate,Max # of ripples,,Select bin,Volume (minimum)=0;!,!;!;mp12=0,ssim=1"; // Pixel, Beatsin
#ifndef WLED_DISABLE_2D
@ -6426,7 +6426,7 @@ uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline.
return FRAMETIME;
} // mode_gravcenter()
static const char *_data_FX_MODE_GRAVCENTER PROGMEM = " ♪ Gravcenter@Rate of fall,Sensitivity=128;,!;!;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_GRAVCENTER PROGMEM = " ♪ Gravcenter@Rate of fall,Sensitivity=128;,!;!;mp12=2,ssim=1"; // Circle, Beatsin
///////////////////////
@ -6480,7 +6480,7 @@ uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew
return FRAMETIME;
} // mode_gravcentric()
static const char *_data_FX_MODE_GRAVCENTRIC PROGMEM = " ♪ Gravcentric@Rate of fall,Sensitivity=128;!;!;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_GRAVCENTRIC PROGMEM = " ♪ Gravcentric@Rate of fall,Sensitivity=128;!;!;mp12=2,ssim=1"; // Circle, Beatsin
///////////////////////
@ -6530,7 +6530,7 @@ uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline.
return FRAMETIME;
} // mode_gravimeter()
static const char *_data_FX_MODE_GRAVIMETER PROGMEM = " ♪ Gravimeter@Rate of fall,Sensitivity=128;,!;!;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_GRAVIMETER PROGMEM = " ♪ Gravimeter@Rate of fall,Sensitivity=128;,!;!;mp12=2,ssim=1"; // Circle, Beatsin
#else
// This an abuse of the gravimeter effect for AGC debugging
// instead of sound volume, it uses the AGC gain multiplier as input
@ -6596,7 +6596,7 @@ uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline.
return FRAMETIME;
} // mode_gravimeter()
static const char *_data_FX_MODE_GRAVIMETER PROGMEM = " ♪ Gravimeter@Rate of fall,Sensitivity=128,Input level=128;,!;!;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_GRAVIMETER PROGMEM = " ♪ Gravimeter@Rate of fall,Sensitivity=128,Input level=128;,!;!;mp12=2,ssim=1"; // Circle, Beatsin
#endif
@ -6620,7 +6620,7 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline.
return FRAMETIME;
} // mode_juggles()
static const char *_data_FX_MODE_JUGGLES PROGMEM = " ♪ Juggles@!,# of balls;,!;!;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_JUGGLES PROGMEM = " ♪ Juggles@!,# of balls;,!;!;mp12=0,ssim=1"; // Pixels, Beatsin
//////////////////////
@ -6650,7 +6650,7 @@ uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline.
return FRAMETIME;
} // mode_matripix()
static const char *_data_FX_MODE_MATRIPIX PROGMEM = " ♪ Matripix@!,Brightness=64;,!;!;mp12=2;ssim=2;rev=1"; // Circle, WeWillRockYou, reverseX
static const char *_data_FX_MODE_MATRIPIX PROGMEM = " ♪ Matripix@!,Brightness=64;!,!;!;mp12=2,ssim=2,rev=1,mi=1,rY=1,mY=1"; // Circle, WeWillRockYou, reverseX
//////////////////////
@ -6688,7 +6688,7 @@ uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline.
return FRAMETIME;
} // mode_midnoise()
static const char *_data_FX_MODE_MIDNOISE PROGMEM = " ♪ Midnoise@Fade rate,Maximum length=128;,!;!;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_MIDNOISE PROGMEM = " ♪ Midnoise@Fade rate,Maximum length=128;,!;!;mp12=2,ssim=1"; // Circle, Beatsin
//////////////////////
@ -6722,7 +6722,7 @@ uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline.
return FRAMETIME;
} // mode_noisefire()
static const char *_data_FX_MODE_NOISEFIRE PROGMEM = " ♪ Noisefire@!,!;;;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_NOISEFIRE PROGMEM = " ♪ Noisefire@!,!;;;mp12=2,ssim=1"; // Circle, Beatsin
///////////////////////
@ -6760,7 +6760,7 @@ uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline.
return FRAMETIME;
} // mode_noisemeter()
static const char *_data_FX_MODE_NOISEMETER PROGMEM = " ♪ Noisemeter@Fade rate,Width=128;!,!;!;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_NOISEMETER PROGMEM = " ♪ Noisemeter@Fade rate,Width=128;!,!;!;mp12=2,ssim=1"; // Circle, Beatsin
//////////////////////
@ -6793,7 +6793,7 @@ uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline.
return FRAMETIME;
} // mode_pixelwave()
static const char *_data_FX_MODE_PIXELWAVE PROGMEM = " ♪ Pixelwave@!,Sensitivity=64;!,!;!;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_PIXELWAVE PROGMEM = " ♪ Pixelwave@!,Sensitivity=64;!,!;!;mp12=2,ssim=1"; // Circle, Beatsin
//////////////////////
@ -6837,7 +6837,7 @@ uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline.
return FRAMETIME;
} // mode_plasmoid()
static const char *_data_FX_MODE_PLASMOID PROGMEM = " ♪ Plasmoid@Phase=128,# of pixels=128;,!;!;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_PLASMOID PROGMEM = " ♪ Plasmoid@Phase=128,# of pixels=128;,!;!;mp12=0,ssim=1"; // Pixels, Beatsin
///////////////////////
@ -6881,7 +6881,7 @@ uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline.
return FRAMETIME;
} // mode_puddlepeak()
static const char *_data_FX_MODE_PUDDLEPEAK PROGMEM = " ♪ Puddlepeak@Fade rate,Puddle size,,Select bin,Volume (minimum)=0;!,!;!;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_PUDDLEPEAK PROGMEM = " ♪ Puddlepeak@Fade rate,Puddle size,,Select bin,Volume (minimum)=0;!,!;!;mp12=0,ssim=1"; // Pixels, Beatsin
//////////////////////
@ -6916,7 +6916,7 @@ uint16_t mode_puddles(void) { // Puddles. By Andrew Tuline.
return FRAMETIME;
} // mode_puddles()
static const char *_data_FX_MODE_PUDDLES PROGMEM = " ♪ Puddles@Fade rate,Puddle size;!,!;!;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_PUDDLES PROGMEM = " ♪ Puddles@Fade rate,Puddle size;!,!;!;mp12=0,ssim=1"; // Pixels, Beatsin
///////////////////////////////////////////////////////////////////////////////
@ -6946,7 +6946,7 @@ uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline.
return FRAMETIME;
} // mode_pixels()
static const char *_data_FX_MODE_PIXELS PROGMEM = " ♪ Pixels@Fade rate,# of pixels;,!;!;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_PIXELS PROGMEM = " ♪ Pixels@Fade rate,# of pixels;,!;!;mp12=0,ssim=1"; // Pixels, Beatsin
///////////////////////////////
@ -7028,9 +7028,9 @@ uint16_t mode_binmap(void) {
return FRAMETIME;
} // mode_binmap()
#ifdef SR_DEBUG
static const char *_data_FX_MODE_BINMAP PROGMEM = " ♫ Binmap@,,Input level=128,,Max vol;!,!;!;mp12=0;ssim=1;rY=true"; // Pixels, Beatsin, ReverseY
static const char *_data_FX_MODE_BINMAP PROGMEM = " ♫ Binmap@,,Input level=128,,Max vol;!,!;!;mp12=0,ssim=1,rY=1"; // Pixels, Beatsin, ReverseY
#else
static const char *_data_FX_MODE_BINMAP PROGMEM = " ♫ Binmap@,,Input level=128;!,!;!;mp12=0;ssim=1;rY=true"; // Pixels, Beatsin, ReverseY
static const char *_data_FX_MODE_BINMAP PROGMEM = " ♫ Binmap@,,Input level=128;!,!;!;mp12=0,ssim=1,rY=1"; // Pixels, Beatsin, ReverseY
#endif
//////////////////////
@ -7060,7 +7060,7 @@ uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline.
return FRAMETIME;
} // mode_blurz()
static const char *_data_FX_MODE_BLURZ PROGMEM = " ♫ Blurz@Fade rate,Blur amount;!,Color mix;!;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_BLURZ PROGMEM = " ♫ Blurz@Fade rate,Blur amount;!,Color mix;!;mp12=0,ssim=1"; // Pixels, Beatsin
/////////////////////////
@ -7097,7 +7097,7 @@ uint16_t mode_DJLight(void) { // Written by ??? Adapted by Wil
return FRAMETIME;
} // mode_DJLight()
static const char *_data_FX_MODE_DJLIGHT PROGMEM = " ♫ DJ Light@Speed;;;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_DJLIGHT PROGMEM = " ♫ DJ Light@Speed;;;mp12=2,ssim=1"; // Circle, Beatsin
////////////////////
@ -7134,7 +7134,7 @@ uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN.
return FRAMETIME;
} // mode_freqmap()
static const char *_data_FX_MODE_FREQMAP PROGMEM = " ♫ Freqmap@Fade rate,Starting color;,!;!;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_FREQMAP PROGMEM = " ♫ Freqmap@Fade rate,Starting color;,!;!;mp12=2,ssim=1"; // Circle, Beatsin
///////////////////////
@ -7184,7 +7184,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch
return FRAMETIME;
} // mode_freqmatrix()
static const char *_data_FX_MODE_FREQMATRIX PROGMEM = " ♫ Freqmatrix@Time delay,Sound effect,Low bin,High bin,Sensivity;;;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_FREQMATRIX PROGMEM = " ♫ Freqmatrix@Time delay,Sound effect,Low bin,High bin,Sensivity;;;mp12=0,ssim=1"; // Pixels, Beatsin
//////////////////////
@ -7220,7 +7220,7 @@ uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline.
return FRAMETIME;
} // mode_freqpixels()
static const char *_data_FX_MODE_FREQPIXELS PROGMEM = " ♫ Freqpixels@Fade rate,Starting colour and # of pixels;;;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_FREQPIXELS PROGMEM = " ♫ Freqpixels@Fade rate,Starting colour and # of pixels;;;mp12=0,ssim=1"; // Pixels, Beatsin
//////////////////////
@ -7291,7 +7291,7 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun
return FRAMETIME;
} // mode_freqwave()
static const char *_data_FX_MODE_FREQWAVE PROGMEM = " ♫ Freqwave@Time delay,Sound effect,Low bin,High bin,Pre-amp;;;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_FREQWAVE PROGMEM = " ♫ Freqwave@Time delay,Sound effect,Low bin,High bin,Pre-amp;;;mp12=2,ssim=1"; // Circle, Beatsin
///////////////////////
@ -7344,7 +7344,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline.
return FRAMETIME;
} // mode_gravfreq()
static const char *_data_FX_MODE_GRAVFREQ PROGMEM = " ♫ Gravfreq@Rate of fall,Sensivity=128;,!;!;mp12=2;ssim=1"; // Circle, Beatsin
static const char *_data_FX_MODE_GRAVFREQ PROGMEM = " ♫ Gravfreq@Rate of fall,Sensivity=128;,!;!;mp12=2,ssim=1"; // Circle, Beatsin
//////////////////////
@ -7370,7 +7370,7 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli
return FRAMETIME;
} // mode_noisemove()
static const char *_data_FX_MODE_NOISEMOVE PROGMEM = " ♫ Noisemove@Speed of perlin movement,Fade rate;,!;!;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_NOISEMOVE PROGMEM = " ♫ Noisemove@Speed of perlin movement,Fade rate;,!;!;mp12=0,ssim=1"; // Pixels, Beatsin
//////////////////////
@ -7414,7 +7414,7 @@ uint16_t mode_rocktaves(void) { // Rocktaves. Same note from eac
return FRAMETIME;
} // mode_rocktaves()
static const char *_data_FX_MODE_ROCKTAVES PROGMEM = " ♫ Rocktaves@;,!;!;mp12=0;ssim=1"; // Pixels, Beatsin
static const char *_data_FX_MODE_ROCKTAVES PROGMEM = " ♫ Rocktaves@;,!;!;mp12=0,ssim=1"; // Pixels, Beatsin
///////////////////////
@ -7467,7 +7467,7 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin
return FRAMETIME;
} // mode_waterfall()
static const char *_data_FX_MODE_WATERFALL PROGMEM = " ♫ Waterfall@!,Adjust color,,Select bin, Volume (minimum)=0;!,!;!;mp12=2;ssim=1"; // Circles, Beatsin
static const char *_data_FX_MODE_WATERFALL PROGMEM = " ♫ Waterfall@!,Adjust color,,Select bin, Volume (minimum)=0;!,!;!;mp12=2,ssim=1"; // Circles, Beatsin
#ifndef WLED_DISABLE_2D

View File

@ -4535,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 */
/*

View File

@ -405,7 +405,7 @@ uint32_t Segment::getPixelColor(uint16_t i)
{
#ifndef WLED_DISABLE_2D
if (height() > 1) { // if this does not work use strip.isMatrix
uint16_t vH = virtualHeight(); // segment height in logical pixels
//uint16_t vH = virtualHeight(); // segment height in logical pixels
uint16_t vW = virtualWidth();
switch (map1D2D) {
case M12_Pixels:

View File

@ -78,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

View File

@ -377,7 +377,7 @@ button {
#sliders {
width: 300px;
margin: 0 auto;
margin: 32px auto 0;
position: sticky;
bottom: 0;
}
@ -835,17 +835,19 @@ input[type=range]::-moz-range-thumb {
}
select {
padding: 4px;
padding: 4px 0 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-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);
}
@ -855,16 +857,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 {
@ -1183,8 +1195,10 @@ TD .checkmark, TD .radiomark {
.lbl-s {
display: inline-block;
margin: 10px 4px 0 0;
/* margin: 10px 4px 0 0; */
font-size: 13px;
width: 48%;
text-align: center;
}
/* list wrapper */

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;
@ -697,22 +697,22 @@ function populateSegments(s)
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>`;
}
let map2D = `<div id="seg${i}map2D" data-map="map2D" class="lbl-s hide">Expand 1D FX:<br>
<select class="sel-p" id="seg${i}mp12" onchange="setMp12(${i})">
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':''}>Vertical</option>
<option value="2" ${inst.mp12==2?' selected':''}>Circle</option>
<option value="3" ${inst.mp12==3?' selected':''}>Block</option>
</select>
<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>
<select class="sel-p" id="seg${i}ssim" onchange="setSSim(${i})">
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':''}>Off</option>
<option value="1" ${inst.ssim==1?' selected':''}>BeatSin</option>
<option value="2" ${inst.ssim==2?' selected':''}>WeWillRockYou</option>
<option value="3" ${inst.ssim==3?' selected':''}>U10_3</option>
<option value="4" ${inst.ssim==4?' selected':''}>U14_3</option>
</select>
</select></div>
</div>`;
cn += `<div class="seg lstI ${i==s.mainseg ? 'selected' : ''} ${exp ? "expanded":""}" id="seg${i}">
<label class="check schkl">
@ -1129,15 +1129,15 @@ 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.
setEffectParameters(selectedFx, (fx || ps));
setEffectParameters(selectedFx);
var selectedName = selectedEffect.querySelector(".lstIname").innerText;
var segs = gId("segcont").querySelectorAll(`div[data-map="map2D"]`);
@ -1215,7 +1215,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;
@ -1293,7 +1292,6 @@ 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)
@ -1322,7 +1320,7 @@ 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 setEffectParameters(idx, applyDef=false)
function setEffectParameters(idx)
{
if (!(Array.isArray(fxdata) && fxdata.length>idx)) return;
var controlDefined = (fxdata[idx].substr(0,1) == "@");
@ -1331,14 +1329,6 @@ function setEffectParameters(idx, applyDef=false)
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(",");
var obj = {"seg":{}};
// var obj = {"seg": {"rev": false, "rY": false}};
//assign extra parameters to segment
for (let i=3;i<effectPars.length;i++) {
let keyval = effectPars[i].split("=");
obj.seg[keyval[0]] = keyval[1]=="true"?true:keyval[1]=="false"?false:keyval[1];
}
// set html slider items on/off
var nSliders = Math.min(5,Math.floor(gId("sliders").children.length)); // div for each slider
@ -1351,10 +1341,6 @@ function setEffectParameters(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)] = v}
slOnOff[i] = slOnOff[i].substring(0,dPos);
}
if (slOnOff.length>i && slOnOff[i]!="!") label.innerHTML = slOnOff[i];
@ -1362,18 +1348,15 @@ function setEffectParameters(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 += 28; // 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())
@ -1423,11 +1406,6 @@ function setEffectParameters(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];
@ -1436,14 +1414,11 @@ function setEffectParameters(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;
@ -1658,14 +1633,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>`
plSelContent += `<option value="${arr[i][0]}" ${arr[i][0]==el?"selected":""}>${n}</option>`
}
return plSelContent;
}
@ -1751,11 +1726,11 @@ function makeP(i,pl) {
<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-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 {
@ -1835,9 +1810,9 @@ function makePlEntry(p,i) {
<table>
<tr>
<td width="80%" colspan=2>
<select class="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>
@ -2061,6 +2036,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');
@ -2068,7 +2045,7 @@ function setX(ind = null)
}
d.querySelector(`#fxlist .lstI[data-id="${ind}"]`).classList.add('selected');
*/
var obj = {"seg": {"fx": parseInt(ind)}};
requestJson(obj);
}
@ -2080,11 +2057,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);
}

View File

@ -299,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
@ -324,7 +324,8 @@ 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);
//wled_eeprom.cpp

File diff suppressed because it is too large Load Diff

View File

@ -162,11 +162,10 @@ 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 ));
#ifndef WLED_DISABLE_2D
// 2D options
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));
@ -178,6 +177,37 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
strip.setMode(id, 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()) {
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);
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) 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.setOption(SEG_OPTION_REVERSED, (bool)sOpt);
sOpt = extractModeDefaults(fx, SET_F("mi")); if (sOpt >= 0) seg.setOption(SEG_OPTION_MIRROR, (bool)sOpt);
sOpt = extractModeDefaults(fx, SET_F("rY")); if (sOpt >= 0) seg.setOption(SEG_OPTION_REVERSED_Y, (bool)sOpt);
sOpt = extractModeDefaults(fx, SET_F("mY")); if (sOpt >= 0) seg.setOption(SEG_OPTION_MIRROR_Y, (bool)sOpt);
}
//getVal also supports inc/decrementing and random
getVal(elem[F("sx")], &seg.speed);
getVal(elem[F("ix")], &seg.intensity);

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

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

View File

@ -281,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
@ -291,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;
@ -311,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
@ -324,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);
@ -332,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;

View File

@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2207211
#define VERSION 2207241
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG

View File

@ -305,8 +305,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

@ -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(",");