diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b7c2149f4..0636b7717b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ ## WLED changelog -### Builds after release 0.13.1 +### WLED release 0.13.3 + +- Version bump to v0.13.3 "Toki" +- Disable ESP watchdog by default (fixes flickering and boot issues on a fresh install) +- Added support for LPD6803 + +### WLED release 0.13.2 + +#### Build 2208140 + +- Version bump to v0.13.2 "Toki" +- Added option to receive live data on the main segment only (PR #2601) +- Enable ESP watchdog by default (PR #2657) +- Fixed race condition when saving bus config +- Better potentiometer filtering (PR #2693) +- More suitable DMX libraries (PR #2652) +- Fixed outgoing serial TPM2 message length (PR #2628) +- Fixed next universe overflow and Art-Net DMX start address (PR #2607) +- Fixed relative segment brightness (PR #2665) + +### Builds between releases 0.13.1 and 0.13.2 #### Build 2203191 diff --git a/package-lock.json b/package-lock.json index 6fba6589a1..11d0c1b3e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.13.2", + "version": "0.13.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wled", - "version": "0.13.2", + "version": "0.13.3", "license": "ISC", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index 7c8f4e128b..11f9447e7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.13.2", + "version": "0.13.3", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index 50004f3dba..87faae1ae9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,6 +39,7 @@ default_envs = soundReactive_esp32dev, soundReactive_esp32_eth ; default_envs = wemos_shield_esp32 ; default_envs = m5atom ; default_envs = esp32_eth +; default_envs = esp32dev_qio80 ; default_envs = esp32_eth_ota1mapp ; default_envs = esp32s2_saola @@ -166,7 +167,7 @@ lib_compat_mode = strict lib_deps = fastled/FastLED @ 3.5.0 IRremoteESP8266 @ 2.8.2 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.4 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For use SSD1306 OLED display uncomment following @@ -229,7 +230,8 @@ lib_deps = https://github.com/softhack007/LITTLEFS-threadsafe.git#master makuna/NeoPixelBus @ 2.6.9 https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 - arduinoFFT @ 1.5.6 + https://github.com/kosme/arduinoFFT#develop @ 1.9.2 + ; arduinoFFT @ 1.5.6 [esp32s2] build_flags = -g @@ -347,6 +349,18 @@ lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +[env:esp32dev_qio80] +board = esp32dev +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_qio80 #-D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET +lib_deps = ${esp32.lib_deps} +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = qio + [env:esp32_eth] board = esp32-poe platform = ${esp32.platform} @@ -454,6 +468,8 @@ build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D WLED_DIS lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = dio [env:soundReactive_esp32_eth] board = esp32-poe @@ -464,6 +480,8 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D WLED_DISABLE_MQTT -D WLED_DISABLE_LOXONE lib_deps = ${esp32.lib_deps} board_build.partitions = ${esp32.default_partitions} +; board_build.f_flash = 80000000L +; board_build.flash_mode = dio [env:soundReactive_lolin_d32] board = lolin_d32 @@ -475,8 +493,10 @@ build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_MQTT -D WLED_DISABLE_L lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = dio -[env:lolin_d32_pro] +[env:soundReactive_lolin_d32_pro] board = lolin_d32_pro platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} @@ -489,6 +509,8 @@ board_build.f_cpu = 240000000L board_upload.flash_size = 16MB board_upload.maximum_size = 16777216 board_build.partitions = tools/SoundReactive_ESP32_16MB.csv +board_build.f_flash = 80000000L +board_build.flash_mode = qio [env:soundReactive_m5atom] board = esp32dev @@ -510,6 +532,8 @@ build_flags = ${common.build_flags_esp32} lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = dio [env:soundReactive_m5stamp-pico] board = esp32dev @@ -531,6 +555,8 @@ build_flags = ${common.build_flags_esp32} lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = dio # ------------------------------------------------------------------------------ # custom board configurations diff --git a/readme.md b/readme.md index d76d03f3d4..aa864ffe0c 100644 --- a/readme.md +++ b/readme.md @@ -11,7 +11,7 @@

# Stable Branch -This is the SR `master` branch - the source code for our latest release version [SR WLED v0.13.2](https://github.com/atuline/WLED/releases/tag/v0.13.2). +This is the SR `master` branch - the source code for our latest release version [SR WLED v0.13.3](https://github.com/atuline/WLED/releases/tag/v0.13.3). - For ESP32 devices (8266 no longer supported) - *This* branch (`master`) can be a stable baseline for your own project. Use it. diff --git a/requirements.txt b/requirements.txt index 06b2d535e1..820ecdefc0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,54 +1,70 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # pip-compile # -aiofiles==0.6.0 +aiofiles==0.8.0 # via platformio -ajsonrpc==1.1.0 +ajsonrpc==1.2.0 # via platformio -bottle==0.12.20 +anyio==3.6.1 + # via starlette +async-timeout==4.0.2 + # via zeroconf +bottle==0.12.23 # via platformio -certifi==2020.12.5 +certifi==2022.6.15 # via requests -chardet==4.0.0 +charset-normalizer==2.1.1 # via requests -click==7.1.2 +click==8.1.3 # via # platformio # uvicorn -colorama==0.4.4 - # via platformio -h11==0.12.0 +colorama==0.4.5 + # via + # click + # platformio +h11==0.13.0 # via # uvicorn # wsproto -idna==2.10 - # via requests -ifaddr==0.1.7 +idna==3.3 + # via + # anyio + # requests +ifaddr==0.2.0 # via zeroconf -marshmallow==3.11.1 +marshmallow==3.17.0 # via platformio -platformio==5.1.1 +packaging==21.3 + # via marshmallow +platformio==6.1.4 # via -r requirements.in -pyelftools==0.27 +pyelftools==0.29 # via platformio +pyparsing==3.0.9 + # via packaging pyserial==3.5 # via platformio -requests==2.25.1 +requests==2.28.1 # via platformio -semantic-version==2.8.5 +semantic-version==2.10.0 # via platformio -starlette==0.14.2 +sniffio==1.2.0 + # via anyio +starlette==0.20.4 # via platformio -tabulate==0.8.9 +tabulate==0.8.10 # via platformio -urllib3==1.26.5 +typing-extensions==4.3.0 + # via starlette +urllib3==1.26.11 # via requests -uvicorn==0.13.4 +uvicorn==0.18.2 # via platformio -wsproto==1.0.0 +wsproto==1.1.0 # via platformio -zeroconf==0.28.8 +zeroconf==0.39.0 # via platformio diff --git a/usermods/BME280_v2/README.md b/usermods/BME280_v2/README.md index 216ca63000..b8718e0e32 100644 --- a/usermods/BME280_v2/README.md +++ b/usermods/BME280_v2/README.md @@ -1,40 +1,90 @@ -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 +lib_deps = + ${esp8266.lib_deps} + BME280@~3.0.0 + Wire ``` -or define `USERMOD_BME280` in `my_config.h` -```c++ -#define USERMOD_BME280 -``` - -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 (`` is set in MQTT section of Sync Setup menu): Measurement type | MQTT topic --- | --- Temperature | `/temperature` Humidity | `/humidity` Pressure | `/pressure` Heat index | `/heat_index` -Dew point | `/dew_point` \ No newline at end of file +Dew point | `/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 \ No newline at end of file diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index 82eb08871e..742066f7b2 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -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 - -#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; - //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 -#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 + #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 + 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 @@ class UsermodBME280 : public Usermod float sensorHeatIndex; float sensorDewPoint; float sensorPressure; + String tempScale; // Track previous sensor values float lastTemperature; float lastHumidity; @@ -82,43 +73,122 @@ class UsermodBME280 : public Usermod 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::PresUnit presUnit(BME280::PresUnit_hPa); + + bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit); + + 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); - #endif - BME280::PresUnit presUnit(BME280::PresUnit_hPa); + BME280::PresUnit presUnit(BME280::PresUnit_hPa); - bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit); + bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit); - sensorTemperature = _temperature; - sensorHumidity = _humidity; - sensorPressure = _pressure; - if (sensorType == 1) - { - sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit); - sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit); + 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 @@ class UsermodBME280 : public Usermod { 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 @@ class UsermodBME280 : public Usermod 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 @@ class UsermodBME280 : public Usermod 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 @@ class UsermodBME280 : public Usermod { 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 @@ class UsermodBME280 : public Usermod } } } + + /* + * 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; + } }; \ No newline at end of file diff --git a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h index a0299bc94f..8de9b2e7ea 100644 --- a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h +++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h @@ -226,6 +226,11 @@ class FourLineDisplayUsermod : public Usermod { isHW = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins +#ifdef ARDUINO_ARCH_ESP32 + isHW = true; // WLEDSR - always use hardware I2C in ESP32 + po = PinOwner::HW_I2C; +#endif + if ((ioPin[0] < 0) || (ioPin[1] <0)) { type=NONE; return; } // WLEDSR bugfix: avoid crash due to invalid PINS if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; } } diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 383accc52c..40d534f8e8 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -212,6 +212,11 @@ class FourLineDisplayUsermod : public Usermod { isHW = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins +#ifdef ARDUINO_ARCH_ESP32 + isHW = true; // WLEDSR - always use hardware I2C in ESP32 + po = PinOwner::HW_I2C; +#endif + if ((ioPin[0] < 0) || (ioPin[1] <0)) { type=NONE; return; } // WLEDSR bugfix: avoid crash due to invalid PINS if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; } } diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 60f57f373d..c6b84f535d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1012,7 +1012,7 @@ uint16_t WS2812FX::mode_running_random(void) { uint8_t z = it % zoneSize; bool nzone = (!z && it != SEGENV.aux1); - for (uint16_t i=SEGLEN-1; i > 0; i--) { + for (int i=SEGLEN-1; i >= 0; i--) { // WLEDSR bugfix if (nzone || z >= zoneSize) { uint8_t lastrand = PRNG16 >> 8; int16_t diff = 0; @@ -1616,7 +1616,7 @@ uint16_t WS2812FX::mode_random_chase(void) uint32_t color = SEGENV.step; random16_set_seed(SEGENV.aux0); - for(uint16_t i = SEGLEN -1; i > 0; i--) { + for (int i=SEGLEN-1; i >= 0; i--) { // WLEDSR bugfix uint8_t r = random8(6) != 0 ? (color >> 16 & 0xFF) : random8(); uint8_t g = random8(6) != 0 ? (color >> 8 & 0xFF) : random8(); uint8_t b = random8(6) != 0 ? (color & 0xFF) : random8(); @@ -2717,12 +2717,12 @@ uint16_t WS2812FX::sinelon_base(bool dual, bool rainbow=false) { } if (SEGENV.aux0 != pos) { if (SEGENV.aux0 < pos) { - for (uint16_t i = SEGENV.aux0; i < pos ; i++) { + for (int i = SEGENV.aux0; i < pos ; i++) { // WLEDSR bugfix setPixelColor(i, color1); if (dual) setPixelColor(SEGLEN-1-i, color2); } } else { - for (uint16_t i = SEGENV.aux0; i > pos ; i--) { + for (int i = SEGENV.aux0; i > pos ; i--) { // WLEDSR bugfix setPixelColor(i, color1); if (dual) setPixelColor(SEGLEN-1-i, color2); } @@ -4281,9 +4281,9 @@ extern float sampleReal; // "sample" as float, to provide bit extern float multAgc; // sampleReal * multAgc = sampleAgc. Our multiplier // FFT based variables -extern double FFT_MajorPeak; -extern double FFT_Magnitude; -extern double fftBin[]; // raw FFT data +extern float FFT_MajorPeak; +extern float FFT_Magnitude; +extern float fftBin[]; // raw FFT data extern int fftResult[]; // summary of bins array. 16 summary bins. extern float fftAvg[]; @@ -4784,7 +4784,7 @@ for (int j=0; j < SEGMENT.width; j++) { uint16_t WS2812FX::mode_2DFrizzles(void) { // By: Stepko https://editor.soulmatelights.com/gallery/640-color-frizzles , Modified by: Andrew Tuline fadeToBlackBy(leds, 16); - for (byte i = 8; i > 0; i--) { + for (int i = 8; i > 0; i--) { // WLEDSR bugfix leds[XY(beatsin8(SEGMENT.speed/8 + i, 0, SEGMENT.width - 1), beatsin8(SEGMENT.intensity/8 - i, 0, SEGMENT.height - 1))] += ColorFromPalette(currentPalette, beatsin8(12, 0, 255), 255, LINEARBLEND); } blur2d(leds, 16); @@ -5674,7 +5674,7 @@ uint16_t WS2812FX::mode_gravimeter(void) { // Gravmeter. By Andre float tmpSound = multAgc; // AGC gain if (soundAgc == 0) { if ((sampleAvg> 1.0) && (sampleReal > 0.05)) - tmpSound = (float)sample / sampleReal; // current non-AGC gain + tmpSound = (float)sampleRaw / sampleReal; // current non-AGC gain else tmpSound = ((float)sampleGain/40.0 * (float)inputLevel/128.0) + 1.0/16.0; // non-AGC gain from presets } @@ -6024,7 +6024,7 @@ uint16_t WS2812FX::mode_ripplepeak(void) { // * Ripple peak. By A ripples[i].pos = random16(SEGLEN); #ifdef ESP32 - ripples[i].color = (int)(log10(FFT_MajorPeak)*128); + ripples[i].color = (int)(log10f(FFT_MajorPeak)*128); #else ripples[i].color = random8(); #endif @@ -6218,7 +6218,7 @@ uint16_t WS2812FX::mode_freqmatrix(void) { // Freqmatrix. By Andr CRGB color = 0; CHSV c; - if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0; + if (FFT_MajorPeak > 5120) FFT_MajorPeak = 1.0f; // MajorPeak holds the freq. value which is most abundant in the last sample. // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz // we will treat everything with less than 65Hz as 0 @@ -6327,7 +6327,7 @@ uint16_t WS2812FX::mode_freqwave(void) { // Freqwave. By Andrea CRGB color = 0; CHSV c; - if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0; + if (FFT_MajorPeak > 5120) FFT_MajorPeak = 1.0f; // MajorPeak holds the freq. value which is most abundant in the last sample. // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz // we will treat everything with less than 65Hz as 0 @@ -6338,6 +6338,7 @@ uint16_t WS2812FX::mode_freqwave(void) { // Freqwave. By Andrea int upperLimit = 20 * SEGMENT.custom2; int lowerLimit = 2 * SEGMENT.custom1; int i = lowerLimit!=upperLimit?map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255):FFT_MajorPeak; + if (i<0) i = 0; uint16_t b = 255.0 * intensity; if (b > 255) b=255; c = CHSV(i, 240, (uint8_t)b); @@ -6385,7 +6386,8 @@ uint16_t WS2812FX::mode_gravfreq(void) { // Gravfreq. By Andrew for (int i=0; i mH) { + if (mH < mD) + mH ++; + else + mD ++; + mW = strip.matrixWidth / mH / mD; + } + + + for (int z=0; z mH) { + if (mH < mD) + mH ++; + else + mD ++; + mW = strip.matrixWidth / mH / mD; + } + + origin_x = 3.5+sinf(interval)*2.5; + origin_y = 3.5+cosf(interval)*2.5; + origin_z = 3.5+cosf(interval)*2.0; + + diameter = 2.0+sinf(interval/3.0); + + for (int x=0; xdiameter && d>16), byte(c>>8), byte(c), byte(c>>24));} + inline void setPixelColor(int n, uint32_t c) {setPixelColor(n, byte(c>>16), byte(c>>8), byte(c), byte(c>>24));} bool gammaCorrectBri = false, @@ -1134,7 +1138,9 @@ class WS2812FX { mode_2DDrift(void), mode_2DColoredBursts(void), mode_2DJulia(void), - mode_customEffect(void); //WLEDSR Custom Effects + mode_customEffect(void), //WLEDSR Custom Effects + mode_3DRipples(void), + mode_3DSphereMove(void); // mode_2DPoolnoise(void), // mode_2DTwister(void); // mode_2DCAElementary(void); @@ -1462,7 +1468,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Wavesins@Speed,Brightness variation,Starting Color,Range of Colors,Color variation;;!", " ♫ Rocktaves@;,!;!", " ♫ 2D Akemi@Color speed,Dance ☑;Head palette,Arms & Legs,Eyes & Mouth;Face palette", -" ⚙️ Custom Effect@Speed,Intensity,Custom 1, Custom 2, Custom 3;!;!" +" ⚙️ Custom Effect@Speed,Intensity,Custom 1, Custom 2, Custom 3;!;!", +"3D Ripples@Speed=128,Interval=128;!;!", +"3D Sphere Move@Speed=128,Interval=128;!;!" ])====="; //WLEDSR: second part (not SR specific, but in latest SR, not in AC (Pallettes added in WLEDSR from Retro Clown->END)) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 839bb998f1..19e0d83e23 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -210,7 +210,7 @@ uint16_t IRAM_ATTR WS2812FX::segmentToLogical(uint16_t i) { // ewowi20210703: wi /* reverse just an individual segment */ int16_t logicalIndex = iGroup; - if (IS_REVERSE && stripOrMatrixPanel == 0) { // in case of 1D + if (IS_REVERSE && stripOrMatrixPanel != 1) { // in case of 1D or 3D if (IS_MIRROR) { logicalIndex = (SEGMENT.length() -1) / 2 - iGroup; // only need to index half the pixels } else { @@ -253,10 +253,14 @@ uint16_t IRAM_ATTR WS2812FX::segmentToLogical(uint16_t i) { // ewowi20210703: wi return logicalIndex; } -void IRAM_ATTR WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) +void IRAM_ATTR WS2812FX::setPixelColor(int i, byte r, byte g, byte b, byte w) { uint8_t segIdx; + //if ((i<0) || (i>=SEGMENT.virtualLength())) return; // if pixel would fall out of segment just exit - some effects, like bouncing balls, get negative values resulting at i = maxint - something + // softhack007: full check disabled, as it seems to cause problems with DDP + if (i<0) return; // some effects, like bouncing balls, produce negative values + if (SEGLEN) { // SEGLEN!=0 -> from segment/FX //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments) if (_bri_t < 255) { @@ -308,7 +312,7 @@ void IRAM_ATTR WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte } } else { if (i < customMappingSize) i = customMappingTable[i]; - busses.setPixelColor(i, RGBW32(r, g, b, w)); + // busses.setPixelColor(i, RGBW32(r, g, b, w)); busses.setPixelColor(logicalToPhysical(i), RGBW32(r, g, b, w)); // ewowi20210624: logicalToPhysical: Maps logical led index to physical led index. } } @@ -765,7 +769,7 @@ void WS2812FX::set2DSegment(uint8_t n) { //WLEDSR, for strips we need ledCountx1 dimension void WS2812FX::setStripOrPanelWidthAndHeight() { - if (stripOrMatrixPanel == 0) { //strip + if (stripOrMatrixPanel != 1) { //strip or 3D cube // ledCount was removed, replaced by strip.getLengthTotal() matrixWidth = strip.getLengthTotal(); matrixHeight = 1; @@ -806,7 +810,6 @@ void WS2812FX::setStripOrPanelWidthAndHeight() { // panelTranspose = 0; // } - // Serial.printf("setStripOrPanelWidthAndHeight %d %d %d", strip.stripOrMatrixPanel, strip.matrixWidth, strip.matrixHeight);Serial.println(); } bool WS2812FX::hasCCTBus(void) { diff --git a/wled00/audio_reactive.h b/wled00/audio_reactive.h index 9cbccc16db..0f0892cd85 100644 --- a/wled00/audio_reactive.h +++ b/wled00/audio_reactive.h @@ -51,11 +51,16 @@ static volatile bool disableSoundProcessing = false; // if true, sound proc constexpr i2s_port_t I2S_PORT = I2S_NUM_0; constexpr int BLOCK_SIZE = 128; -constexpr int SAMPLE_RATE = 10240; // Base sample rate in Hz +constexpr int SAMPLE_RATE = 10240; // Base sample rate in Hz - standard. Physical sample time -> 50ms +//constexpr int SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms +//constexpr int SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms + +#define FFT_MIN_CYCLE 45 // minimum time before FFT task is repeated. Must be less than time for reading 512 samples at SAMPLE_RATE. //Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) #define UDP_SYNC_HEADER "00001" +#define UDP_SYNC_HEADER_V2 "00002" uint8_t maxVol = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger uint8_t binNum = 8; // Used to select the bin for FFT based beat detection. @@ -118,7 +123,7 @@ float sampleAgc = 0.0; // AGC sample, smoothed uint16_t micData; // Analog input for FFT uint16_t micDataSm; // Smoothed mic data, as it's a bit twitchy float micDataReal = 0.0; // future support - this one has the full 24bit MicIn data - lowest 8bit after decimal point -long timeOfPeak = 0; +static unsigned long timeOfPeak = 0; static unsigned long lastTime = 0; static double micLev = 0.0; // Used to convert returned value to have '0' as minimum. A leveller float multAgc = 1.0; // sample * multAgc = sampleAgc. Our multiplier @@ -134,29 +139,32 @@ constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - Thi unsigned int sampling_period_us; unsigned long microseconds; -double FFT_MajorPeak = 0; -double FFT_Magnitude = 0; -uint16_t mAvg = 0; +float FFT_MajorPeak = 1.0f; +float FFT_Magnitude = 0.0001; // These are the input and output vectors. Input vectors receive computed results from FFT. -static double vReal[samplesFFT]; -static double vImag[samplesFFT]; -double fftBin[samplesFFT]; +static float vReal[samplesFFT]; +static float vImag[samplesFFT]; +static float windowWeighingFactors[samplesFFT]; +float fftBin[samplesFFT]; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. // Oh, and bins 0,1,2 are no good, so we'll zero them out. -double fftCalc[16]; +float fftCalc[16]; int fftResult[16]; // Our calculated result table, which we feed to the animations. -double fftResultMax[16]; // A table used for testing to determine how our post-processing is working. +float fftResultMax[16]; // A table used for testing to determine how our post-processing is working. float fftAvg[16]; +#define FFTBIN_DOWNSCALE 0.65 // scale down FFT results, so we end up at ~128 average + // Table of linearNoise results to be multiplied by soundSquelch in order to reduce squelch across fftResult bins. -int linearNoise[16] = { 34, 28, 26, 25, 20, 12, 9, 6, 4, 4, 3, 2, 2, 2, 2, 2 }; +static int linearNoise[16] = { 34, 28, 26, 25, 20, 12, 9, 6, 4, 4, 3, 2, 2, 2, 2, 2 }; // Table of multiplication factors so that we can even out the frequency response. -double fftResultPink[16] = {1.70,1.71,1.73,1.78,1.68,1.56,1.55,1.63,1.79,1.62,1.80,2.06,2.47,3.35,6.83,9.55}; +static float fftResultPink[16] = {1.70,1.71,1.73,1.78,1.68,1.56,1.55,1.63,1.79,1.62,1.80,2.06,2.47,3.35,6.83,9.55}; +// default "V1" SR 0.13.x audiosync struct - 83 Bytes struct audioSyncPacket { char header[6] = UDP_SYNC_HEADER; uint8_t myVals[32]; // 32 Bytes @@ -169,6 +177,18 @@ struct audioSyncPacket { double FFT_MajorPeak; // 08 Bytes }; +// new "V2" AC 0.14.0 audiosync struct - 40 Bytes +struct audioSyncPacket_v2 { + char header[6] = UDP_SYNC_HEADER_V2; // 06 bytes + float sampleRaw; // 04 Bytes - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting + float sampleSmth; // 04 Bytes - either "sampleAvg" or "sampleAgc" depending on soundAgc setting + uint8_t samplePeak; // 01 Bytes - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude + uint8_t reserved1; // 01 Bytes - reserved for future extensions like loudness + uint8_t fftResult[16]; // 16 Bytes - FFT results + float FFT_Magnitude; // 04 Bytes + float FFT_MajorPeak; // 04 Bytes +}; + double mapf(double x, double in_min, double in_max, double out_min, double out_max); bool isValidUdpSyncVersion(char header[6]) { @@ -179,9 +199,16 @@ bool isValidUdpSyncVersion(char header[6]) { } } +bool isValidUdpSyncVersion2(char header[6]) { + if (strncmp(header, UDP_SYNC_HEADER_V2, 5) == 0) { + return true; + } else { + return false; + } +} + /* get current max sample ("published" by the I2S and FFT thread) and perform some sound processing */ void getSample() { - static long peakTime; const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function #ifdef WLED_DISABLE_SOUND @@ -197,9 +224,9 @@ void getSample() { micIn -= micLev; // Let's center it to 0 now // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. - float micInNoDC = fabs(micDataReal - micLev); + float micInNoDC = fabsf(micDataReal - micLev); expAdjF = weighting * micInNoDC + ((1.0-weighting) * expAdjF); - expAdjF = fabs(expAdjF); // Now (!) take the absolute value + expAdjF = fabsf(expAdjF); // Now (!) take the absolute value expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF; // simple noise gate if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0; @@ -216,6 +243,12 @@ void getSample() { // keep "peak" sample, but decay value if current sample is below peak if ((sampleMax < sampleReal) && (sampleReal > 0.5)) { sampleMax = sampleMax + 0.5 * (sampleReal - sampleMax); // new peak - with some filtering + if (((maxVol < 6) || (binNum < 9)) && (millis() - timeOfPeak > 80)) { // another simple way to detect samplePeak + samplePeak = 1; + timeOfPeak = millis(); + udpSamplePeak = 1; + userVar1 = samplePeak; + } } else { if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0)) sampleMax = sampleMax + 0.5 * (sampleReal - sampleMax); // over AGC Zone - get back quickly @@ -225,6 +258,7 @@ void getSample() { if (sampleMax < 0.5) sampleMax = 0.0; sampleAvg = ((sampleAvg * 15.0) + sampleAdj) / 16.0; // Smooth it out over the last 16 samples. + sampleAvg = fabsf(sampleAvg); // make sure we have a positive value // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC uint16_t MinShowDelay = strip.getMinShowDelay(); @@ -236,14 +270,13 @@ void getSample() { if (userVar1 == 0) samplePeak = 0; // Poor man's beat detection by seeing if sample > Average + some value. - if ((fftBin[binNum] > maxVol) && (millis() > (peakTime + 100))) { // This goe through ALL of the 255 bins + if ((maxVol > 1) && (binNum > 4) && (fftBin[binNum] > maxVol) && (millis() - timeOfPeak > 100)) { // This goes through ALL of the 255 bins - but ignores stupid settings // if (sample > (sampleAvg + maxVol) && millis() > (peakTime + 200)) { // Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync. samplePeak = 1; timeOfPeak = millis(); udpSamplePeak = 1; userVar1 = samplePeak; - peakTime=millis(); } } // getSample() @@ -281,7 +314,7 @@ void agcAvg(unsigned long the_time) { if (time_now - last_time > 2) { last_time = time_now; - if((fabs(sampleReal) < 2.0) || (sampleMax < 1.0)) { + if((fabsf(sampleReal) < 2.0) || (sampleMax < 1.0)) { // MIC signal is "squelched" - deliver silence multAgcTemp = multAgc; // keep old control value (no change) tmpAgc = 0; @@ -325,7 +358,7 @@ void agcAvg(unsigned long the_time) { // NOW finally amplify the signal tmpAgc = sampleReal * multAgcTemp; // apply gain to signal - if(fabs(sampleReal) < 2.0) tmpAgc = 0; // apply squelch threshold + if(fabsf(sampleReal) < 2.0) tmpAgc = 0; // apply squelch threshold if (tmpAgc > 255) tmpAgc = 255; // limit to 8bit if (tmpAgc < 1) tmpAgc = 0; // just to be sure @@ -335,11 +368,12 @@ void agcAvg(unsigned long the_time) { // update smoothed AGC sample - if(fabs(tmpAgc) < 1.0) + if(fabsf(tmpAgc) < 1.0) sampleAgc = 0.5 * tmpAgc + 0.5 * sampleAgc; // fast path to zero else sampleAgc = sampleAgc + agcSampleSmooth[AGC_preset] * (tmpAgc - sampleAgc); // smooth path + sampleAgc = fabsf(sampleAgc); userVar0 = sampleAvg * 4; if (userVar0 > 255) userVar0 = 255; @@ -394,6 +428,11 @@ void limitSampleDynamics(void) { // Begin FFT Code // //////////////////// +// using latest AruinoFFT lib, because it supportd float and its much faster! +// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 +#define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc), and an a few other speedups +#define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt +//#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 50% on ESP32 (as alternative to FFT_SQRT_APPROXIMATION) #include "arduinoFFT.h" void transmitAudioData() { @@ -418,6 +457,10 @@ void transmitAudioData() { transmitData.FFT_Magnitude = FFT_Magnitude; transmitData.FFT_MajorPeak = FFT_MajorPeak; + if (sampleAvg < 1) { // silence - noise gate closed + transmitData.samplePeak = false; // don't claim "peak" where we have silence. + } + fftUdp.beginMulticastPacket(); fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); fftUdp.endPacket(); @@ -425,14 +468,64 @@ void transmitAudioData() { } // transmitAudioData() +static void extract_v2_packet(int packetSize, uint8_t *fftBuff) +{ + // extract v2 packet - assuming a valid packet, as this check was checked alreaedy done in userloop() + static audioSyncPacket_v2 receivedPacket; + memcpy(&receivedPacket, fftBuff, MIN(sizeof(receivedPacket), packetSize)); // don't copy more that what fits into audioSyncPacket + receivedPacket.header[5] = '\0'; // ensure string termination + + // update samples for effects + float my_volumeSmth = receivedPacket.sampleSmth; + float my_volumeRaw = receivedPacket.sampleRaw; + if (my_volumeSmth < 0) my_volumeSmth = 0.0f; + if (my_volumeRaw < 0) my_volumeRaw = 0; + // update internal samples + sampleRaw = my_volumeRaw; + sampleAvg = my_volumeSmth; + rawSampleAgc = my_volumeRaw; + sampleAgc = my_volumeSmth; + multAgc = 1.0f; + + // auto-reset sample peak. Need to do it here, because getSample() is not running + uint16_t MinShowDelay = strip.getMinShowDelay(); + if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. + samplePeak = 0; + udpSamplePeak = 0; + } + if (userVar1 == 0) samplePeak = 0; + // Only change samplePeak IF it's currently false. + // If it's true already, then the animation still needs to respond. + if (!samplePeak) { + samplePeak = receivedPacket.samplePeak; + if (samplePeak) timeOfPeak = millis(); + udpSamplePeak = samplePeak; + userVar1 = samplePeak; + } + //These values are only available on the ESP32 + for (int i = 0; i < 16; i++) fftResult[i] = receivedPacket.fftResult[i]; + FFT_Magnitude = fabsf(receivedPacket.FFT_Magnitude); + FFT_MajorPeak = constrain(receivedPacket.FFT_MajorPeak, 1.0f, 5120.0f); // restrict value to range expected by effects + + // fake myVals + static unsigned int myvals_index = 0; + myVals[myvals_index] = receivedPacket.sampleRaw; + myvals_index = (myvals_index +1) % 32; + myVals[myvals_index] = receivedPacket.sampleSmth; + myvals_index = (myvals_index +1) % 32; + myVals[random8() % 32] = receivedPacket.sampleSmth; + myVals[random8() % 32] = receivedPacket.fftResult[2]; + myVals[random8() % 32] = receivedPacket.fftResult[7]; + myVals[random8() % 32] = receivedPacket.fftResult[12]; +} // Create FFT object -static arduinoFFT FFT = arduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE ); +static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); -double fftAdd( int from, int to) { +float fftAdd( int from, int to) { int i = from; - double result = 0; + float result = 0; while ( i <= to) { result += fftBin[i++]; } @@ -443,25 +536,38 @@ double fftAdd( int from, int to) { void FFTcode( void * parameter) { DEBUG_PRINT("FFT running on core: "); DEBUG_PRINTLN(xPortGetCoreID()); + // see https://www.freertos.org/vtaskdelayuntil.html + //constexpr TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; + constexpr TickType_t xFrequency_2 = (FFT_MIN_CYCLE * portTICK_PERIOD_MS) / 2; + for(;;) { + TickType_t xLastWakeTime = xTaskGetTickCount(); delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. // Only run the FFT computing code if we're not in "realime mode" or in Receive mode if (disableSoundProcessing || (audioSyncEnabled & (1 << 1))) { - delay(7); // release CPU - delay is implemeted using vTaskDelay() + //delay(7); // release CPU - delay is implemeted using vTaskDelay() + vTaskDelayUntil( &xLastWakeTime, xFrequency_2); // release CPU continue; } + + #if !defined(I2S_GRAB_ADC1_COMPLETELY) + if (dmType > 0) // the "delay trick" does not help for analog, because I2S ADC is disabled outside of getSamples() + #endif + vTaskDelayUntil( &xLastWakeTime, xFrequency_2); // release CPU, and give I2S some time to fill its buffers. Might not work well with ADC analog sources. + audioSource->getSamples(vReal, samplesFFT); + xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay // old code - Last sample in vReal is our current mic sample //micDataSm = (uint16_t)vReal[samples - 1]; // will do a this a bit later // micDataSm = ((micData * 3) + micData)/4; const int halfSamplesFFT = samplesFFT / 2; // samplesFFT divided by 2 - double maxSample1 = 0.0; // max sample from first half of FFT batch - double maxSample2 = 0.0; // max sample from second half of FFT batch + float maxSample1 = 0.0; // max sample from first half of FFT batch + float maxSample2 = 0.0; // max sample from second half of FFT batch for (int i=0; i < samplesFFT; i++) { // set imaginary parts to 0 @@ -470,9 +576,9 @@ void FFTcode( void * parameter) { if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts { if (i <= halfSamplesFFT) { - if (fabs(vReal[i]) > maxSample1) maxSample1 = fabs(vReal[i]); + if (fabsf(vReal[i]) > maxSample1) maxSample1 = fabsf(vReal[i]); } else { - if (fabs(vReal[i]) > maxSample2) maxSample2 = fabs(vReal[i]); + if (fabsf(vReal[i]) > maxSample2) maxSample2 = fabsf(vReal[i]); } } } @@ -480,26 +586,24 @@ void FFTcode( void * parameter) { micDataSm = (uint16_t)maxSample1; micDataReal = maxSample1; - FFT.DCRemoval(); // let FFT lib remove DC component, so we don't need to care about this in getSamples() - - //FFT.Windowing( FFT_WIN_TYP_HAMMING, FFT_FORWARD ); // Weigh data - standard Hamming window - //FFT.Windowing( FFT_WIN_TYP_BLACKMAN, FFT_FORWARD ); // Blackman window - better side freq rejection - //FFT.Windowing( FFT_WIN_TYP_BLACKMAN_HARRIS, FFT_FORWARD );// Blackman-Harris - excellent sideband rejection - FFT.Windowing( FFT_WIN_TYP_FLT_TOP, FFT_FORWARD ); // Flat Top Window - better amplitude accuracy - FFT.Compute( FFT_FORWARD ); // Compute FFT - FFT.ComplexToMagnitude(); // Compute magnitudes - + FFT.dcRemoval(); // remove DC offset + //FFT.windowing(FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" window - better amplitude accuracy + FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection + FFT.compute(FFTDirection::Forward ); // Compute FFT + FFT.complexToMagnitude(); // Compute magnitudes // // vReal[3 .. 255] contain useful data, each a 20Hz interval (60Hz - 5120Hz). // There could be interesting data at bins 0 to 2, but there are too many artifacts. // - FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant + FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant + FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 5120.0f); // restrict value to range expected by effects + FFT_Magnitude = fabsf(FFT_Magnitude); for (int i = 0; i < samplesFFT; i++) { // Values for bins 0 and 1 are WAY too large. Might as well start at 3. - double t = 0.0; - t = fabs(vReal[i]); // just to be sure - values in fft bins should be positive any way - t = t / 16.0; // Reduce magnitude. Want end result to be linear and ~4096 max. + float t = 0.0; + t = fabsf(vReal[i]); // just to be sure - values in fft bins should be positive any way + t = t / 16.0f; // Reduce magnitude. Want end result to be linear and ~4096 max. fftBin[i] = t; } // for() @@ -542,6 +646,7 @@ void FFTcode( void * parameter) { // Adjustment for frequency curves. for (int i=0; i < 16; i++) { fftCalc[i] = fftCalc[i] * fftResultPink[i]; + //fftCalc[i] *= FFTBIN_DOWNSCALE; // correct magnitutude to fit into [0 ... 255] } // Manual linear adjustment of gain using sampleGain adjustment for different input types. @@ -549,7 +654,7 @@ void FFTcode( void * parameter) { if (soundAgc) fftCalc[i] = fftCalc[i] * multAgc; else - fftCalc[i] = fftCalc[i] * (double)sampleGain / 40.0 * inputLevel/128 + (double)fftCalc[i]/16.0; //with inputLevel adjustment + fftCalc[i] = fftCalc[i] * (float)sampleGain / 40.0 * inputLevel/128 + (float)fftCalc[i]/16.0; //with inputLevel adjustment } @@ -560,8 +665,13 @@ void FFTcode( void * parameter) { fftAvg[i] = (float)fftResult[i]*.05 + (1-.05)*fftAvg[i]; } -// release second sample to volume reactive effects. - // The FFT process currently takes ~20ms, so releasing a second sample now effectively doubles the "sample rate" + #if !defined(I2S_GRAB_ADC1_COMPLETELY) + if (dmType > 0) // the "delay trick" does not help for analog + #endif + vTaskDelayUntil( &xLastWakeTime, xFrequency_2); // release CPU, by waiting until FFT_MIN_CYCLE is over + + // release second sample to volume reactive effects. + // Releasing a second sample now effectively doubles the "sample rate" micDataSm = (uint16_t)maxSample2; micDataReal = maxSample2; diff --git a/wled00/audio_source.h b/wled00/audio_source.h index 35c506995a..dbb2dc45e8 100644 --- a/wled00/audio_source.h +++ b/wled00/audio_source.h @@ -104,7 +104,7 @@ class AudioSource { Read num_samples from the microphone, and store them in the provided buffer */ - virtual void getSamples(double *buffer, uint16_t num_samples) = 0; + virtual void getSamples(float *buffer, uint16_t num_samples) = 0; /* Get an up-to-date sample without DC offset */ virtual int getSampleWithoutDCOffset() = 0; @@ -208,7 +208,7 @@ class I2SSource : public AudioSource { } } - virtual void getSamples(double *buffer, uint16_t num_samples) { + virtual void getSamples(float *buffer, uint16_t num_samples) { if(_initialized) { esp_err_t err; size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */ @@ -251,17 +251,17 @@ class I2SSource : public AudioSource { if (_shift != 0) newSamples[i] >>= 16; #endif - double currSample = 0.0; + float currSample = 0.0; if(_shift > 0) - currSample = (double) (newSamples[i] >> _shift); + currSample = (float) (newSamples[i] >> _shift); else { if(_shift < 0) - currSample = (double) (newSamples[i] << (- _shift)); // need to "pump up" 12bit ADC to full 16bit as delivered by other digital mics + currSample = (float) (newSamples[i] << (- _shift)); // need to "pump up" 12bit ADC to full 16bit as delivered by other digital mics else #ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT - currSample = (double) newSamples[i] / 65536.0; // _shift == 0 -> use the chance to keep lower 16bits + currSample = (float) newSamples[i] / 65536.0f; // _shift == 0 -> use the chance to keep lower 16bits #else - currSample = (double) newSamples[i]; + currSample = (float) newSamples[i]; #endif } buffer[i] = currSample; @@ -473,6 +473,8 @@ class I2SAdcSource : public I2SSource { return; } + //adc1_config_width(ADC_WIDTH_BIT_12); // ensure that ADC1 runs at 12bit resolution - should not be needed, because i2s_set_adc_mode does that anyway + // Enable I2S mode of ADC err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel)); if (err != ESP_OK) { @@ -480,6 +482,10 @@ class I2SAdcSource : public I2SSource { return; } + + // see example in https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino + adc1_config_channel_atten(adc1_channel_t(channel), ADC_ATTEN_DB_11); // configure ADC input amplification + #if defined(I2S_GRAB_ADC1_COMPLETELY) // according to docs from espressif, the ADC needs to be started explicitly // fingers crossed @@ -489,7 +495,7 @@ class I2SAdcSource : public I2SSource { //return; } #else - err = i2s_adc_disable(I2S_NUM_0); + //err = i2s_adc_disable(I2S_NUM_0); // seems that disable without previous enable causes a crash/bootloop on some boards //err = i2s_stop(I2S_NUM_0); if (err != ESP_OK) { Serial.printf("Failed to initially disable i2s adc: %d\n", err); @@ -498,7 +504,7 @@ class I2SAdcSource : public I2SSource { _initialized = true; } - void getSamples(double *buffer, uint16_t num_samples) { + void getSamples(float *buffer, uint16_t num_samples) { /* Enable ADC. This has to be enabled and disabled directly before and after sampling, otherwise Wifi dies and analogRead() hangs diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 95549cb08a..5131f589d2 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -69,6 +69,10 @@ #define I_HS_P98_3 35 #define I_SS_P98_3 36 +//LPD6803 +#define I_HS_LPO_3 37 +#define I_SS_LPO_3 38 + /*** ESP8266 Neopixel methods ***/ #ifdef ESP8266 @@ -140,6 +144,10 @@ #define B_HS_LPD_3 NeoPixelBrightnessBus #define B_SS_LPD_3 NeoPixelBrightnessBus +//LPD6803 +#define B_HS_LPO_3 NeoPixelBrightnessBus +#define B_SS_LPO_3 NeoPixelBrightnessBus + //WS2801 //#define B_HS_WS1_3 NeoPixelBrightnessBus //#define B_HS_WS1_3 NeoPixelBrightnessBus @@ -184,6 +192,7 @@ class PolyBus { case I_8266_BB_TM1_4: beginTM1814(busPtr); break; case I_HS_DOT_3: (static_cast(busPtr))->Begin(); break; case I_HS_LPD_3: (static_cast(busPtr))->Begin(); break; + case I_HS_LPO_3: (static_cast(busPtr))->Begin(); break; case I_HS_WS1_3: (static_cast(busPtr))->Begin(); break; case I_HS_P98_3: (static_cast(busPtr))->Begin(); break; #endif @@ -219,11 +228,13 @@ class PolyBus { // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() case I_HS_DOT_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; case I_HS_LPD_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; + case I_HS_LPO_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; case I_HS_WS1_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; case I_HS_P98_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; #endif case I_SS_DOT_3: (static_cast(busPtr))->Begin(); break; case I_SS_LPD_3: (static_cast(busPtr))->Begin(); break; + case I_SS_LPO_3: (static_cast(busPtr))->Begin(); break; case I_SS_WS1_3: (static_cast(busPtr))->Begin(); break; case I_SS_P98_3: (static_cast(busPtr))->Begin(); break; } @@ -285,6 +296,8 @@ class PolyBus { case I_SS_DOT_3: busPtr = new B_SS_DOT_3(len, pins[1], pins[0]); break; case I_HS_LPD_3: busPtr = new B_HS_LPD_3(len, pins[1], pins[0]); break; case I_SS_LPD_3: busPtr = new B_SS_LPD_3(len, pins[1], pins[0]); break; + case I_HS_LPO_3: busPtr = new B_HS_LPO_3(len, pins[1], pins[0]); break; + case I_SS_LPO_3: busPtr = new B_SS_LPO_3(len, pins[1], pins[0]); break; case I_HS_WS1_3: busPtr = new B_HS_WS1_3(len, pins[1], pins[0]); break; case I_SS_WS1_3: busPtr = new B_SS_WS1_3(len, pins[1], pins[0]); break; case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break; @@ -348,6 +361,8 @@ class PolyBus { case I_SS_DOT_3: (static_cast(busPtr))->Show(); break; case I_HS_LPD_3: (static_cast(busPtr))->Show(); break; case I_SS_LPD_3: (static_cast(busPtr))->Show(); break; + case I_HS_LPO_3: (static_cast(busPtr))->Show(); break; + case I_SS_LPO_3: (static_cast(busPtr))->Show(); break; case I_HS_WS1_3: (static_cast(busPtr))->Show(); break; case I_SS_WS1_3: (static_cast(busPtr))->Show(); break; case I_HS_P98_3: (static_cast(busPtr))->Show(); break; @@ -409,6 +424,8 @@ class PolyBus { case I_SS_DOT_3: return (static_cast(busPtr))->CanShow(); break; case I_HS_LPD_3: return (static_cast(busPtr))->CanShow(); break; case I_SS_LPD_3: return (static_cast(busPtr))->CanShow(); break; + case I_HS_LPO_3: return (static_cast(busPtr))->CanShow(); break; + case I_SS_LPO_3: return (static_cast(busPtr))->CanShow(); break; case I_HS_WS1_3: return (static_cast(busPtr))->CanShow(); break; case I_SS_WS1_3: return (static_cast(busPtr))->CanShow(); break; case I_HS_P98_3: return (static_cast(busPtr))->CanShow(); break; @@ -494,6 +511,8 @@ class PolyBus { case I_SS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; case I_HS_LPD_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; case I_SS_LPD_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_HS_LPO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_SS_LPO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; case I_HS_WS1_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; case I_SS_WS1_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; case I_HS_P98_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; @@ -555,6 +574,8 @@ class PolyBus { case I_SS_DOT_3: (static_cast(busPtr))->SetBrightness(b); break; case I_HS_LPD_3: (static_cast(busPtr))->SetBrightness(b); break; case I_SS_LPD_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_HS_LPO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_SS_LPO_3: (static_cast(busPtr))->SetBrightness(b); break; case I_HS_WS1_3: (static_cast(busPtr))->SetBrightness(b); break; case I_SS_WS1_3: (static_cast(busPtr))->SetBrightness(b); break; case I_HS_P98_3: (static_cast(busPtr))->SetBrightness(b); break; @@ -617,6 +638,8 @@ class PolyBus { case I_SS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_HS_LPD_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_SS_LPD_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_HS_LPO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_SS_LPO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_HS_WS1_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_SS_WS1_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_HS_P98_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -696,6 +719,8 @@ class PolyBus { case I_SS_DOT_3: delete (static_cast(busPtr)); break; case I_HS_LPD_3: delete (static_cast(busPtr)); break; case I_SS_LPD_3: delete (static_cast(busPtr)); break; + case I_HS_LPO_3: delete (static_cast(busPtr)); break; + case I_SS_LPO_3: delete (static_cast(busPtr)); break; case I_HS_WS1_3: delete (static_cast(busPtr)); break; case I_SS_WS1_3: delete (static_cast(busPtr)); break; case I_HS_P98_3: delete (static_cast(busPtr)); break; @@ -717,6 +742,7 @@ class PolyBus { switch (busType) { case TYPE_APA102: t = I_SS_DOT_3; break; case TYPE_LPD8806: t = I_SS_LPD_3; break; + case TYPE_LPD6803: t = I_SS_LPO_3; break; case TYPE_WS2801: t = I_SS_WS1_3; break; case TYPE_P9813: t = I_SS_P98_3; break; default: t=I_NONE; diff --git a/wled00/button.cpp b/wled00/button.cpp index 4fcebf2b46..74ee61daf5 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -18,7 +18,7 @@ void shortPressAction(uint8_t b) if (!macroButton[b]) { switch (b) { case 0: toggleOnOff(); stateUpdated(CALL_MODE_BUTTON); break; - case 1: ++effectCurrent %= strip.getModeCount(); colorUpdated(CALL_MODE_BUTTON); break; + case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break; } } else { applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET); @@ -145,7 +145,10 @@ void handleAnalog(uint8_t b) #ifdef ESP8266 rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit #else - rawReading = analogRead(btnPin[b]); // collect at full 12bit resolution + if (digitalPinToAnalogChannel(btnPin[b]) >= 0) // WLEDSR bugfix: check if GPIO is an ADC pin + rawReading = analogRead(btnPin[b]); // collect at full 12bit resolution + else + rawReading = 0; #endif yield(); // keep WiFi task running - analog read may take several millis on ESP8266 @@ -197,10 +200,10 @@ void handleAnalog(uint8_t b) // otherwise use "double press" for segment selection WS2812FX::Segment& seg = strip.getSegment(macroDoublePress[b]); if (aRead == 0) { - seg.setOption(SEG_OPTION_ON, 0); // off + seg.setOption(SEG_OPTION_ON, false); // off } else { seg.setOpacity(aRead, macroDoublePress[b]); - seg.setOption(SEG_OPTION_ON, 1); + seg.setOption(SEG_OPTION_ON, true); } // this will notify clients of update (websockets,mqtt,etc) updateInterfaces(CALL_MODE_BUTTON); @@ -216,9 +219,13 @@ void handleAnalog(uint8_t b) void handleButton() { static unsigned long lastRead = 0UL; + static unsigned long lastRun = 0UL; bool analog = false; + unsigned long now = millis(); - if (strip.isUpdating()) return; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle) + //if (strip.isUpdating()) return; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle) + if (strip.isUpdating() && (millis() - lastRun < 200)) return; // be niced, but avoid button starvation + lastRun = millis(); for (uint8_t b=0; b ANALOG_BTN_READ_CYCLE) { // button is not a button but a potentiometer + if ((buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) && ((now - lastRead) > ANALOG_BTN_READ_CYCLE)) { // button is not a button but a potentiometer analog = true; handleAnalog(b); continue; } @@ -242,21 +249,21 @@ void handleButton() //momentary button logic if (isButtonPressed(b)) { //pressed - if (!buttonPressedBefore[b]) buttonPressedTime[b] = millis(); + if (!buttonPressedBefore[b]) buttonPressedTime[b] = now; buttonPressedBefore[b] = true; - if (millis() - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press + if (now - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press if (!buttonLongPressed[b]) longPressAction(b); else if (b) { //repeatable action (~3 times per s) on button > 0 longPressAction(b); - buttonPressedTime[b] = millis() - WLED_LONG_REPEATED_ACTION; //300ms + buttonPressedTime[b] = now - WLED_LONG_REPEATED_ACTION; //333ms } buttonLongPressed[b] = true; } } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released - long dur = millis() - buttonPressedTime[b]; + long dur = now - buttonPressedTime[b]; if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce bool doublePress = buttonWaitTime[b]; //did we have a short press before? buttonWaitTime[b] = 0; @@ -270,13 +277,14 @@ void handleButton() WLED::instance().initAP(true); } } else if (!buttonLongPressed[b]) { //short press - if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set + //NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling + if ((b != 1) && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set shortPressAction(b); } else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0) if (doublePress) { doublePressAction(b); } else { - buttonWaitTime[b] = millis(); + buttonWaitTime[b] = now; } } } @@ -285,12 +293,12 @@ void handleButton() } //if 350ms elapsed since last short press release it is a short press - if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) { + if (buttonWaitTime[b] && ((now - buttonWaitTime[b]) > WLED_DOUBLE_PRESS) && !buttonPressedBefore[b]) { buttonWaitTime[b] = 0; shortPressAction(b); } } - if (analog) lastRead = millis(); + if (analog) lastRead = now; } void handleIO() diff --git a/wled00/const.h b/wled00/const.h index d93bc20e31..e6fd232b59 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -76,6 +76,7 @@ #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_BME280 30 //Usermod "usermod_bme280.h //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -164,6 +165,7 @@ #define TYPE_APA102 51 #define TYPE_LPD8806 52 #define TYPE_P9813 53 +#define TYPE_LPD6803 54 //Network types (master broadcast) (80-95) #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) #define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus) diff --git a/wled00/data/edit.htm b/wled00/data/edit.htm index 4f06642331..90f11b3534 100644 --- a/wled00/data/edit.htm +++ b/wled00/data/edit.htm @@ -5,7 +5,7 @@ -ESP8266 SPIFFS File Editor +ESP SPIFFS File Editor diff --git a/wled00/data/index.css b/wled00/data/index.css old mode 100644 new mode 100755 index 59c679d6f0..c4f9d74a94 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -213,6 +213,10 @@ button { width: 90%; border: 0px; margin: 0 auto; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%,-50%); } .tab { diff --git a/wled00/data/index.js b/wled00/data/index.js index 941ce4c653..a4e0dc0946 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -15,6 +15,7 @@ var sliderControl = ""; //WLEDSR var selectedPal = 0; //WLEDSR Blazoncek default slider values var csel = 0; // selected color slot (0-2) var currentPreset = -1; prevPS = -1; //WLEDSR Blazoncek default slider values +var somp = 0;//WLEDSR: stripOrMatrixPanel var lastUpdate = 0; var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0; var pcMode = false, pcModeA = false, lastw = 0; @@ -368,6 +369,13 @@ function inforow(key, val, unit = "") return `${key}${val}${unit}`; } +// WLEDSR begin +function inforow1(key) +{ + return `${key}`; +} +// WLEDSR end + function getLowestUnusedP() { var l = 1; @@ -571,6 +579,7 @@ function populateInfo(i) var cn=""; var heap = i.freeheap/1000; heap = heap.toFixed(1); + var theap = (i.totalheap>0)?i.totalheap/1000:-1; theap = theap.toFixed(1); //WLEDSR - total heap is not available on 8266 var pwr = i.leds.pwr; var pwru = "Not calculated"; if (pwr > 1000) {pwr /= 1000; pwr = pwr.toFixed((pwr > 10) ? 0 : 1); pwru = pwr + " A";} @@ -587,21 +596,40 @@ function populateInfo(i) } var vcn = "Kuuhaku"; - if (i.ver.startsWith("0.13.")) vcn = "Toki-SR"; + if (i.ver.startsWith("0.13.")) vcn = "Toki+SR"; if (i.cn) vcn = i.cn; cn += `v${i.ver} "${vcn}"

+ ${inforow("SR Build",i.vid)} + + ${inforow("Audio Source",i.audioType,i.audioStatus)} + ${i.audioWarning?inforow1("Please "+i.audioWarning+"(s) !"):""} + ${i.audioGain?(i.soundAgc>0?inforow("AGC Gain",i.audioGain,"x"):inforow("Manual Gain",i.audioGain,"x")):""} + ${i.audioProcess?inforow("Sound Processing",i.audioProcess,""):""} + ${inforow("UDP Sound Sync",i.ssyncMode,i.ssyncStatus)} ${urows} - ${inforow("Build",i.vid)} + + ${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")} ${inforow("Uptime",getRuntimeStr(i.uptime))} - ${inforow("Free heap",heap," kB")} ${inforow("Estimated current",pwru)} ${inforow("Frames / second",i.leds.fps)} - ${inforow("MAC address",i.mac)} + + ${inforow("MAC address",i.mac)} ${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")} ${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} -


`; + + ${theap>0?inforow("Total heap",theap," kB"):""} + ${theap>0?inforow("Used heap",((i.totalheap-i.freeheap)/1000).toFixed(1)," kB"):inforow("Free heap",heap," kB")} + ${i.minfreeheap?inforow("Max heap used",((i.totalheap-i.minfreeheap)/1000).toFixed(1)," kB"):""} + ${i.tpram?inforow("Total PSRAM",(i.tpram/1024).toFixed(1)," kB"):""} + ${i.psram?((i.tpram-i.psram)>16383?inforow("Used PSRAM",((i.tpram-i.psram)/1024).toFixed(1)," kB"):inforow("Used PSRAM",(i.tpram-i.psram)," B")):""} + ${i.psusedram?((i.tpram-i.psusedram)>16383?inforow("Max PSRAM used",((i.tpram-i.psusedram)/1024).toFixed(1)," kB"):inforow("Max PSRAM used",(i.tpram-i.psusedram)," B")):""} +
+ ${i.e32model?inforow(i.e32model,i.e32cores +" core(s)"," "+i.e32speed+" Mhz"):""} + ${i.e32flash?inforow("Flash "+i.e32flash+"MB"+" mode "+i.e32flashmode+i.e32flashtext,i.e32flashspeed," Mhz"):""} + +
`; d.getElementById('kv').innerHTML = cn; } @@ -1556,7 +1584,9 @@ function requestJson(command, rinfo = true) { syncTglRecv = info.str; maxSeg = info.leds.maxseg; pmt = info.fs.pmt; - + //WLEDSR + somp = info.leds.somp; + if (!command && rinfo) setTimeout(loadPresets, 99); d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none"; @@ -1615,8 +1645,7 @@ function toggleLiveview() { isLv = !isLv; var lvID = "liveview"; - var isM = true; //tbd: check if matrix, now always assumed - if (isM) { + if (somp != 0) { //not 1D lvID = "liveview2D" if (isLv) { var cn = ''; @@ -1790,6 +1819,7 @@ function makeP(i,pl) { var content = ""; if (pl) { var rep = plJson[i].repeat ? plJson[i].repeat : 0; + //WLEDSR: change qltxt to maxlength=3 (from 2, for HB effects) content = `
Playlist Entries
`; return `
-
Quick load label:
+
Quick load label:
(leave empty for no Quick load button)
`; f.insertAdjacentHTML("beforeend", cn); @@ -541,6 +542,7 @@

LED & Hardware setup


Strip or panel:

2D Matrix

diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 069b8b87b6..02988b608a 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -5,6 +5,18 @@ { return d.getElementById(s); } +function doHide(s){gId(s).style.display="none";} +function doShow(s){gId(s).style.display="block";} + +function hideALEXA(){gId("aleOnOff").style.display="none";} +function hideNoALEXA(){gId("aleOnOff2").style.display="none";} +function hideBLYNK(){gId("blyOnOff").style.display="none";} +function hideNoBLYNK(){gId("blyOnOff2").style.display="none";} +function hideMQTT(){gId("mqtOnOff").style.display="none";} +function hideNoMQTT(){gId("mqtOnOff2").style.display="none";} +function hideHUE(){gId("hueOnOff").style.display="none";} +function hideNoHUE(){gId("hueOnOff2").style.display="none";} + 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;} @@ -126,18 +138,32 @@

Realtime

Disable realtime gamma correction:
Realtime LED offset:

Alexa Voice Assistant

+
+ This firmware build does not include Alexa support.
+
+
Emulate Alexa device:
Alexa invocation name: +

Blynk

+
+ This firmware build does not include Blynk support.
+
Blynk, MQTT and Hue sync all connect to external hosts!
-This may impact the responsiveness of the ESP8266.

+This may impact the responsiveness of the ESP microcontroller.
For best results, only use one of these services at a time.
(alternatively, connect a second ESP to them and use the UDP sync)

-Host: +
+ Host: Port:
Device Auth token:
Clear the token field to disable. Setup info +

MQTT

+
+ This firmware build does not include MQTT support.
+
+
Enable MQTT:
Broker: Port:
@@ -150,7 +176,12 @@

MQTT

Group Topic:
Publish on button press:
Reboot required to apply changes. MQTT info +

Philips Hue

+
+ This firmware build does not include Philips Hue support.
+
+
You can find the bridge IP and the light number in the 'About' section of the hue app.
Poll Hue light every ms:
Then, receive On/Off, Brightness, and Color
@@ -161,6 +192,7 @@

Philips Hue


Press the pushlink button on the bridge, after that save this page!
(when first connecting)
+
Hue status: Disabled in this build

Serial

Baud rate: diff --git a/wled00/data/settings_time.htm b/wled00/data/settings_time.htm index 4cbbc9eb03..ff5f78309c 100644 --- a/wled00/data/settings_time.htm +++ b/wled00/data/settings_time.htm @@ -175,6 +175,7 @@

Time setup

+
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index cef5e9d75a..5ab669a92c 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -14,11 +14,6 @@ void alexaInit(); void handleAlexa(); void onAlexaChange(EspalexaDevice* dev); -//audio_reactive.cpp // Which functions do we declare here??? -//void agcAvg(); -//void FFTcode(void * parameter); -//void getSample(); - //blynk.cpp #ifndef WLED_DISABLE_BLYNK void initBlynk(const char* auth, const char* host, uint16_t port); diff --git a/wled00/html_other.h b/wled00/html_other.h index 1bfa0bca54..cb1d58c15f 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -42,7 +42,7 @@ function B(){window.history.back()}function U(){document.getElementById("uf").st .bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none}

Sound Reactive WLED Software Update

Installed version: SR 0.13.2
Download the latest binary: Installed version: SR 0.13.3
Download the latest binary:


WLED Live Preview
)====="; @@ -94,8 +94,10 @@ const char PAGE_liveviewws2D[] PROGMEM = R"=====( WLED Live PreviewLiveView)====="; diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 1591903657..6fb2122088 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -77,7 +77,7 @@ const char PAGE_settings_leds[] PROGMEM = R"=====(LED Settings