diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index 1655f7ec..59661ff8 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -8,21 +8,23 @@ jobs: name: Gather Environments runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' - name: Install PlatformIO run: pip install -r requirements.txt - name: Get default environments id: envs run: | - echo "::set-output name=environments::$(pio project config --json-output | jq -cr '.[0][1][0][1]')" + echo "environments=$(pio project config --json-output | jq -cr '.[0][1][0][1]')" >> $GITHUB_OUTPUT outputs: environments: ${{ steps.envs.outputs.environments }} @@ -32,24 +34,27 @@ jobs: runs-on: ubuntu-latest needs: get_default_envs strategy: + fail-fast: false matrix: environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Cache PlatformIO - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.platformio key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 + with: + python-version: '3.9' - name: Install PlatformIO run: pip install -r requirements.txt - name: Build firmware diff --git a/CHANGELOG.md b/CHANGELOG.md index 69129061..641568de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## WLED changelog +#### Build 2304090 +- updated Arduino ESP8266 core to 4.1.0 (newer compiler) +- updated NeoPixelBus to 2.7.3 (with support for UCS890x chipset) +- better support for ESP32-C3, ESP32-S2 and ESP32-S3 (Arduino ESP32 core 5.2.0) +- iPad/tablet with 1024 pixels width in landscape orientation PC mode support (#3153) +- fix for Pixel Art Converter (#3155) + #### Build 2303240 - Peek scaling of large 2D matrices - Added 0D (1 pixel) metadata for effects & enhance 0D (analog strip) UI handling diff --git a/package-lock.json b/package-lock.json index 9a2d44e6..7e5c29c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.14.0-b15.23", + "version": "0.14.0-b15.25", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wled", - "version": "0.14.0-b15.23", + "version": "0.14.0-b15.25", "license": "ISC", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index 87922f0b..4d4218ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.0-b15.23", + "version": "0.14.0-b15.25", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index 71fb8d5e..75014436 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,6 +12,9 @@ # Release / CI binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3, esp32s3dev_8MB +# Release binaries +; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB + # Build everything ; default_envs = esp32dev, esp8285_4CH_MagicHome, codm-controller-0_6-rev2, codm-controller-0_6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips @@ -40,37 +43,37 @@ ; =================== default_envs = - esp32_4MB_S + ; esp32_4MB_S esp32_4MB_M ; recommended default esp32_4MB_M_debug esp32_4MB_XL esp32_16MB_M - esp32_16MB_M_debug - esp32_16MB_XL + ; esp32_16MB_M_debug + ; esp32_16MB_XL esp8266_4MB_S - esp8266_4MB_M + ; esp8266_4MB_M wemos_shield_esp32_4MB_M - wemos_shield_esp32_4MB_ICS4343x_M - wemos_shield_esp32_4MB_SPM1423_M - wemos_shield_esp32_4MB_LineIn_M - wemos_shield_esp32_16MB_M - wemos_shield_esp32_16MB_ICS4343x_M - wemos_shield_esp32_16MB_SPM1423_M - wemos_shield_esp32_16MB_SPM1423_XL - wemos_shield_esp32_16MB_LineIn_M + ; wemos_shield_esp32_4MB_ICS4343x_M + ; wemos_shield_esp32_4MB_SPM1423_M + ; wemos_shield_esp32_4MB_LineIn_M + ; wemos_shield_esp32_16MB_M + ; wemos_shield_esp32_16MB_ICS4343x_M + ; wemos_shield_esp32_16MB_SPM1423_M + ; wemos_shield_esp32_16MB_SPM1423_XL + ; wemos_shield_esp32_16MB_LineIn_M esp32_pico_4MB_M esp32_4MB_PSRAM_S - ; esp32S3_8MB_M ;; disabled as github action build has strange problems with this one since today + esp32S3_8MB_M ;; esp32s2_tinyUF2_PSRAM_S ;; experimental - only for adafruit -S2 boards with tinyUF2 bootloader !!! - ; esp32s2_PSRAM_M ;; experimental - disabled as github action build has strange problems with this one since today - ; esp32c3dev_4MB_M ;; experimental - disabled as github action build has strange problems with this one since today - ; seeed_esp32c3_4MB_S ;; experimental - disabled as github action build has strange problems with this one since today + esp32s2_PSRAM_M ;; experimental + esp32c3dev_4MB_M ;; experimental + seeed_esp32c3_4MB_S ;; experimental esp32_4MB_V4_S ;; experimental esp32_16MB_V4_M ;; experimental esp32_16MB_V4_M_debug ;; experimental esp8266pro_16MB_S esp8266pro_16MB_M - esp01_1MB_S + esp01_1MB_S esp32_16MB_M_eth athom_music_esp32_4MB_M @@ -138,6 +141,7 @@ debug_flags = -D DEBUG=1 -D WLED_DEBUG -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT # This reduces the OTA size with ~45KB, so it's especially useful on low memory boards (512k/1m). # ------------------------------------------------------------------------------ build_flags = + -Wno-attributes -DMQTT_MAX_PACKET_SIZE=1024 -DSECURE_CLIENT=SECURE_CLIENT_BEARSSL -DBEARSSL_SSL_BASIC @@ -275,10 +279,14 @@ lib_deps = ;; ** For compiling with latest Frameworks (IDF4.4.x and arduino-esp32 v2.0.x) ** ;;; standard V4 platform platformV4 = espressif32@ ~5.1.1 -platformV4_packages = platformio/framework-arduinoespressif32@ ~3.20004.0 +platformV4_packages = + platformio/framework-arduinoespressif32@ ~3.20004.0 + toolchain-riscv32-esp @ 8.4.0+2021r2-patch5 ; required for platform version < 5.3.0, remove this line when upgrading to platform >=5.3.0 + ;;; newer V4 platform - may help in case you experience flash corruption and boot loops platformV4_new = espressif32@ ~5.2.0 platformV4_new_packages = + toolchain-riscv32-esp @ 8.4.0+2021r2-patch5 ; required for platform version < 5.3.0, remove this line when upgrading to platform >=5.3.0 ;;; tasmota platform ;platformV4 = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.5.1/platform-espressif32-2.0.5.1.zip @@ -342,9 +350,10 @@ build_flags = -g -DCONFIG_IDF_TARGET_ESP32S3 -DCONFIG_LITTLEFS_FOR_IDF_3_2 -DLFS_THREADSAFE ;; WLEDMM -D CONFIG_ASYNC_TCP_USE_WDT=0 + -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0 -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: - ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT, ARDUINO_USB_MSC_ON_BOOT, ARDUINO_USB_DFU_ON_BOOT + ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 @@ -816,7 +825,7 @@ build_flags_S = ; -D WLED_ENABLE_DMX lib_deps_S = - https://github.com/kosme/arduinoFFT#develop @ 1.9.2 ; used for USERMOD_AUDIOREACTIVE + https://github.com/kosme/arduinoFFT#develop @ 1.9.2+sha.e5e4c74 ; used for USERMOD_AUDIOREACTIVE - using "known working" hash build_flags_M = -D WLED_MAX_USERMODS=25 ; default only 4-6, also for _XL configs takes 25 pointers in memory @@ -830,7 +839,9 @@ build_flags_M = -D USERMOD_WEATHER ; WLEDMM usermod -D USERMOD_MPU6050_IMU ; gyro/accelero for USERMOD_GAMES (ONLY WORKS IF USERMOD_FOUR_LINE_DISPLAY NOT INCLUDED - I2C SHARING BUG) -D USERMOD_GAMES ; WLEDMM usermod - ; -D USERMOD_FASTLED ; WLEDMM usermod: CC BY-NC 3.0 licensed effects by Stefan Petrick, include this usermod only if you accept the terms! + -D USERMOD_BATTERY ;; enable Battery usermod + -D USERMOD_BATTERY_USE_LIPO ;; use new "decharging curve" for LiPo cells + -D USERMOD_ANIMARTRIX ; WLEDMM usermod: CC BY-NC 3.0 licensed effects by Stefan Petrick ;WLEDMM: only setting WLED_DEBUG_HOST is enough, ip and port can be defined in sync settings as well -D WLED_DEBUG_HOST='"192.168.x.x"' ;; to send debug messages over network to host 192.168.x.y - FQDN is also possible -D WLED_DEBUG_PORT=1768 ;; port for network debugging. default = 7868 @@ -848,8 +859,6 @@ lib_deps_V4_M = ElectronicCats/MPU6050 @ 0.6.0 ; used for USERMOD_MPU6050_IMU build_flags_XL = - -D USERMOD_BATTERY ;; enable Battery usermod - -D USERMOD_BATTERY_USE_LIPO ;; use new "decharging curve" for LiPo cells -D USERMOD_BH1750 -D USERMOD_ANIMATED_STAIRCASE -D USERMOD_RTC ;; experimental @@ -863,6 +872,7 @@ build_flags_XL = -D USERMOD_SN_PHOTORESISTOR -D USERMOD_BME280 -D USERMOD_DHT + ; -D USERMOD_SHT ;; experimental -D USERMOD_VL53L0X_GESTURES -D WLED_ENABLE_PIXART @@ -873,6 +883,7 @@ lib_deps_XL = ; adafruit/Adafruit Si7021 Library @ 1.4.0 ;; experimental for usermod USERMOD_SENSORSTOMQTT BME280@~3.0.0 ; for usermod USERMOD_BME280 https://github.com/alwynallan/DHT_nonblocking ; for usermod USERMOD_DHT + ; robtillaart/SHT85 @ ~0.4.0 ;; for usermod USERMOD_SHT pololu/VL53L0X @ ^1.3.0 ; for usermod USERMOD_VL53L0X_GESTURES ; end of common @@ -891,6 +902,7 @@ lib_deps = ${esp32.lib_deps} ${common_mm.lib_deps_S} board_build.partitions = ${esp32.default_partitions} board_build.f_flash = 80000000L ; use full 80MHz speed for flash (default = 40Mhz) board_build.flash_mode = dio ; (dio = dual i/o; more compatible than qio = quad i/o) +monitor_filters = esp32_exception_decoder ;common default for all max environments [esp32_4MB_M_base] @@ -1087,7 +1099,7 @@ build_flags = ${common.build_flags_esp8266} -D USERMOD_FOUR_LINE_DISPLAY -D USERMOD_MPU6050_IMU ; gyro/accelero for USERMOD_GAMES (ONLY WORKS IF USERMOD_FOUR_LINE_DISPLAY NOT INCLUDED - I2C SHARING BUG) -D USERMOD_GAMES ; WLEDMM usermod - -D USERMOD_ARTIFX + ; -D USERMOD_ARTIFX ; this is compiling but not working due to low memory on 8266 -D USERMOD_BATTERY ;; enable Battery usermod -D USERMOD_BATTERY_USE_LIPO ;; use new "decharging curve" for LiPo cells -D WLED_DEBUG_HOST='"192.168.x.x"' ;; to send debug messages over network to host 192.168.x.y - FQDN is also possible @@ -1320,7 +1332,6 @@ build_flags = ${esp32_4MB_V4_M_base.build_flags} ; -D SR_DEBUG ; -D MIC_LOGGER ;lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation -;monitor_filters = esp32_exception_decoder ;; RAM: [== ] 24.9% (used 81484 bytes from 327680 bytes) ;; Flash: [======== ] 84.6% (used 1607857 bytes from 1900544 bytes) @@ -1522,7 +1533,6 @@ lib_ignore = ;IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation OneWire ; not needed as we don't include USERMOD_DALLASTEMPERATURE U8g2 ; not needed as we don't include USERMOD_FOUR_LINE_DISPLAY -;monitor_filters = esp32_exception_decoder ; RAM: [== ] 23.1% (used 75636 bytes from 327680 bytes) ; Flash: [==========] 96.0% (used 1510452 bytes from 1572864 bytes) diff --git a/requirements.txt b/requirements.txt index e484d7bc..da65486c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,63 +4,59 @@ # # pip-compile # -aiofiles==0.8.0 +aiofiles==22.1.0 # via platformio ajsonrpc==1.2.0 # via platformio -anyio==3.6.1 +anyio==3.6.2 # via starlette -async-timeout==4.0.2 - # via zeroconf -bottle==0.12.23 +bottle==0.12.25 # via platformio certifi==2022.12.7 # via requests -charset-normalizer==2.1.1 +charset-normalizer==3.1.0 # via requests click==8.1.3 # via # platformio # uvicorn -colorama==0.4.5 - # via platformio -h11==0.13.0 +colorama==0.4.6 + # via + # click + # platformio +h11==0.14.0 # via # uvicorn # wsproto -idna==3.3 +idna==3.4 # via # anyio # requests -ifaddr==0.2.0 - # via zeroconf -marshmallow==3.17.0 +marshmallow==3.19.0 # via platformio -packaging==21.3 +packaging==23.1 # via marshmallow -platformio==6.1.4 +platformio==6.1.6 # via -r requirements.in pyelftools==0.29 # via platformio -pyparsing==3.0.9 - # via packaging pyserial==3.5 # via platformio -requests==2.28.1 +requests==2.28.2 # via platformio semantic-version==2.10.0 # via platformio -sniffio==1.2.0 +sniffio==1.3.0 # via anyio -starlette==0.20.4 +starlette==0.23.1 # via platformio -tabulate==0.8.10 +tabulate==0.9.0 # via platformio -urllib3==1.26.11 +typing-extensions==4.5.0 + # via starlette +urllib3==1.26.15 # via requests -uvicorn==0.18.2 +uvicorn==0.20.0 # via platformio -wsproto==1.1.0 - # via platformio -zeroconf==0.39.0 +wsproto==1.2.0 # via platformio diff --git a/tools/cdata.js b/tools/cdata.js index 55b04e13..707360d6 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -221,6 +221,7 @@ function writeChunks(srcDir, specs, resultFile) { writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index'); writeHtmlGzipped("wled00/data/simple.htm", "wled00/html_simple.h", 'simple'); writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart'); +writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal'); /* writeChunks( "wled00/data", diff --git a/usermods/BH1750_v2/usermod_bh1750.h b/usermods/BH1750_v2/usermod_bh1750.h index 77a57efb..0cb4bf18 100644 --- a/usermods/BH1750_v2/usermod_bh1750.h +++ b/usermods/BH1750_v2/usermod_bh1750.h @@ -48,11 +48,11 @@ private: bool getLuminanceComplete = false; // flag set at startup - bool enabled = true; + //bool enabled = true; //WLEDMM not needed as we use global attributes // strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _enabled[]; + //static const char _name[]; //WLEDMM not needed as we use global attributes + //static const char _enabled[]; //WLEDMM not needed as we use global attributes static const char _maxReadInterval[]; static const char _minReadInterval[]; static const char _offset[]; @@ -76,7 +76,7 @@ private: bool sensorFound = false; // Home Assistant and MQTT - String mqttLuminanceTopic = F(""); + String mqttLuminanceTopic = FPSTR(""); bool mqttInitialized = false; bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages @@ -132,6 +132,8 @@ private: } public: + Usermod_BH1750(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM + void setup() { #if 0 @@ -157,22 +159,22 @@ public: if (!pinManager.joinWire()) { // WLEDMM - this allocates global I2C pins, then starts Wire - if not started previously sensorFound = false; //enabled = false; - USER_PRINTLN(F("BH1750: failed to join I2C bus.")); + USER_PRINTLN(F("[BH1750]: failed to join I2C bus.")); return; } - sensorFound = lightMeter.begin(); - if (sensorFound) { USER_PRINTLN(F("BH1750 sensor found.")); } - else{ USER_PRINTLN(F("BH1750 sensor not found.")); } + sensorFound = lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE); // WLEDMM set mode explicitly + if (sensorFound) { USER_PRINTLN(F("[BH1750] sensor found.")); } + else{ USER_PRINTLN(F("[BH1750] sensor not found.")); } initDone = true; } void loop() { - if ((!enabled) || strip.isUpdating()) + if (!sensorFound || !initDone) return; // WLEDMM bugfix + if ((!enabled) || (strip.isUpdating() && (millis() - lastMeasurement < 450))) // WLEDMM be nice, but not too nice return; - if (!sensorFound) return; // WLEDMM bugfix unsigned long now = millis(); // check to see if we are due for taking a measurement @@ -184,10 +186,12 @@ public: } bool shouldUpdate = now - lastSend > maxReadingInterval; - - float lux = lightMeter.readLightLevel(); - lastMeasurement = millis(); - getLuminanceComplete = true; + float lux = lastLux; + if (lightMeter.measurementReady()) { //WLEDMM do not block in case the sensor is still busy + lux = lightMeter.readLightLevel(); + lastMeasurement = millis(); + getLuminanceComplete = true; + } if (shouldUpdate || checkBoundSensor(lux, lastLux, offset)) { @@ -255,7 +259,7 @@ public: { // we add JSON object. JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname - top[FPSTR(_enabled)] = enabled; + top[F("enabled")] = enabled; top[FPSTR(_maxReadInterval)] = maxReadingInterval; top[FPSTR(_minReadInterval)] = minReadingInterval; top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; @@ -269,7 +273,7 @@ public: // top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page - DEBUG_PRINTLN(F("BH1750 config saved.")); + DEBUG_PRINTLN(F("[BH1750] config saved.")); } // called before setup() to populate properties from values stored in cfg.json @@ -283,13 +287,13 @@ public: if (top.isNull()) { DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINT(F("BH1750")); + DEBUG_PRINT(F("[BH1750]")); DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); return false; } bool configComplete = !top.isNull(); - configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled, false); + configComplete &= getJsonValue(top[F("enabled")], enabled, false); configComplete &= getJsonValue(top[FPSTR(_maxReadInterval)], maxReadingInterval, 10000); //ms configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); @@ -335,8 +339,8 @@ public: }; // strings to reduce flash memory usage (used more than twice) -const char Usermod_BH1750::_name[] PROGMEM = "BH1750"; -const char Usermod_BH1750::_enabled[] PROGMEM = "enabled"; +//const char Usermod_BH1750::_name[] PROGMEM = "BH1750"; //WLEDMM not needed as we use global attributes +//const char Usermod_BH1750::_enabled[] PROGMEM = "enabled"; //WLEDMM not needed as we use global attributes const char Usermod_BH1750::_maxReadInterval[] PROGMEM = "max-read-interval-ms"; const char Usermod_BH1750::_minReadInterval[] PROGMEM = "min-read-interval-ms"; const char Usermod_BH1750::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscoveryLux"; diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index 71c73b3b..c1e3c6bb 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -26,6 +26,15 @@ #endif #endif +//the default ratio for the voltage divider +#ifndef USERMOD_BATTERY_VOLTAGE_MULTIPLIER + #ifdef ARDUINO_ARCH_ESP32 + #define USERMOD_BATTERY_VOLTAGE_MULTIPLIER 2.0f + #else //ESP8266 boards + #define USERMOD_BATTERY_VOLTAGE_MULTIPLIER 4.2f + #endif +#endif + #ifndef USERMOD_BATTERY_MAX_VOLTAGE #define USERMOD_BATTERY_MAX_VOLTAGE 4.2f #endif diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 98aeda43..3d519724 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -29,6 +29,10 @@ class UsermodBattery : public Usermod float rawValue = 0.0f; // calculated voltage float voltage = maxBatteryVoltage; + // between 0 and 1, to control strength of voltage smoothing filter + float alpha = 0.05f; + // multiplier for the voltage divider that is in place between ADC pin and battery, default will be 2 but might be adapted to readout voltages over ~5v ESP32 or ~6.6v ESP8266 + float voltageMultiplier = USERMOD_BATTERY_VOLTAGE_MULTIPLIER; // mapped battery level based on voltage int8_t batteryLevel = 100; // offset or calibration value to fine tune the calculated voltage @@ -110,6 +114,17 @@ class UsermodBattery : public Usermod } } + float readVoltage() + { + #ifdef ARDUINO_ARCH_ESP32 + // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value + return (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; + #else + // use analog read on esp8266 ( 0V ~ 1V no attenuation options) and divide by ADC precision 1023 and multiply by voltage multiplier and apply calibration value + return (analogRead(batteryPin) / 1023.0f) * voltageMultiplier + calibration; + #endif + } + public: //Functions called by WLED @@ -126,6 +141,7 @@ class UsermodBattery : public Usermod if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; + voltage = readVoltage(); } if (!success) { @@ -135,8 +151,8 @@ class UsermodBattery : public Usermod pinMode(batteryPin, INPUT); } #else //ESP8266 boards have only one analog input pin A0 - pinMode(batteryPin, INPUT); + voltage = readVoltage(); #endif nextReadTime = millis() + readingInterval; @@ -176,22 +192,12 @@ class UsermodBattery : public Usermod initializing = false; -#ifdef ARDUINO_ARCH_ESP32 - // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV) - rawValue = analogReadMilliVolts(batteryPin); - // calculate the voltage - voltage = (rawValue / 1000.0f) + calibration; - // usually a voltage divider (50%) is used on ESP32, so we need to multiply by 2 - voltage *= 2.0f; -#else - // read battery raw input - rawValue = analogRead(batteryPin); + rawValue = readVoltage(); + // filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout + voltage = voltage + alpha * (rawValue - voltage); - // calculate the voltage - voltage = ((rawValue / getAdcPrecision()) * maxBatteryVoltage) + calibration; -#endif - // check if voltage is within specified voltage range, allow 10% over/under voltage - voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; + // check if voltage is within specified voltage range, allow 10% over/under voltage - removed cause this just makes it hard for people to troubleshoot as the voltage in the web gui will say invalid instead of displaying a voltage + //voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; // translate battery voltage into percentage /* @@ -363,6 +369,7 @@ class UsermodBattery : public Usermod battery[F("max-voltage")] = maxBatteryVoltage; battery[F("capacity")] = totalBatteryCapacity; battery[F("calibration")] = calibration; + battery[F("voltage-multiplier")] = voltageMultiplier; battery[FPSTR(_readInterval)] = readingInterval; JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section @@ -375,6 +382,9 @@ class UsermodBattery : public Usermod lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold; lp[FPSTR(_duration)] = lowPowerIndicatorDuration; + // read voltage in case calibration or voltage multiplier changed to see immediate effect + voltage = readVoltage(); + DEBUG_PRINTLN(F("Battery config saved.")); } @@ -441,6 +451,7 @@ class UsermodBattery : public Usermod setMaxBatteryVoltage(battery[F("max-voltage")] | maxBatteryVoltage); setTotalBatteryCapacity(battery[F("capacity")] | totalBatteryCapacity); setCalibration(battery[F("calibration")] | calibration); + setVoltageMultiplier(battery[F("voltage-multiplier")] | voltageMultiplier); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); JsonObject ao = battery[F("auto-off")]; @@ -597,21 +608,7 @@ class UsermodBattery : public Usermod totalBatteryCapacity = capacity; } - /* - * Get the choosen adc precision - * esp8266 = 10bit resolution = 1024.0f - * esp32 = 12bit resolution = 4095.0f - */ - float getAdcPrecision() - { - #ifdef ARDUINO_ARCH_ESP32 - // esp32 - return 4096.0f; - #else - // esp8266 - return 1024.0f; - #endif - } + /* * Get the calculated voltage @@ -649,6 +646,23 @@ class UsermodBattery : public Usermod calibration = offset; } + /* + * Set the voltage multiplier value + * A multiplier that may need adjusting for different voltage divider setups + */ + void setVoltageMultiplier(float multiplier) + { + voltageMultiplier = multiplier; + } + + /* + * Get the voltage multiplier value + * A multiplier that may need adjusting for different voltage divider setups + */ + float getVoltageMultiplier() + { + return voltageMultiplier; + } /* * Get auto-off feature enabled status diff --git a/usermods/Temperature/platformio_override.ini b/usermods/Temperature/platformio_override.ini index d9e3fbac..0e354da9 100644 --- a/usermods/Temperature/platformio_override.ini +++ b/usermods/Temperature/platformio_override.ini @@ -1,13 +1,12 @@ ; Options ; ------- ; USERMOD_DALLASTEMPERATURE - define this to have this user mod included wled00\usermods_list.cpp -; USERMOD_DALLASTEMPERATURE_CELSIUS - define this to report temperatures in degrees celsius, otherwise fahrenheit will be reported ; USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds -; USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 20 seconds ; [env:d1_mini_usermod_dallas_temperature_C] extends = env:d1_mini -build_flags = ${common.build_flags_esp8266} -D USERMOD_DALLASTEMPERATURE -D USERMOD_DALLASTEMPERATURE_CELSIUS +build_flags = ${common.build_flags_esp8266} -D USERMOD_DALLASTEMPERATURE lib_deps = ${env.lib_deps} - milesburton/DallasTemperature@^3.9.0 - OneWire@~2.3.5 + paulstoffregen/OneWire@~2.3.7 +# you may want to use following with ESP32 +; https://github.com/blazoncek/OneWire.git # fixes Sensor error on ESP32 \ No newline at end of file diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md index c917461a..2657c6c8 100644 --- a/usermods/Temperature/readme.md +++ b/usermods/Temperature/readme.md @@ -7,6 +7,8 @@ May be expanded with support for different sensor types in the future. If temperature sensor is not detected during boot, this usermod will be disabled. +Maintained by @blazoncek + ## Installation Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`. @@ -14,7 +16,7 @@ Copy the example `platformio_override.ini` to the root directory. This file sho ### Define Your Options * `USERMOD_DALLASTEMPERATURE` - enables this user mod wled00/usermods_list.cpp -* `USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT` - number of milliseconds after boot to take first measurement, defaults to 20000 ms +* `USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL` - number of milliseconds between measurements, defaults to 60000 ms (60s) All parameters can be configured at runtime via the Usermods settings page, including pin, temperature in degrees Celsius or Farenheit and measurement interval. @@ -27,7 +29,6 @@ All parameters can be configured at runtime via the Usermods settings page, incl If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:d1_mini_usermod_dallas_temperature_C`. - If you are not using `platformio_override.ini`, you might have to uncomment `OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: ```ini @@ -43,8 +44,9 @@ default_envs = d1_mini lib_deps = ... #For Dallas sensor uncomment following line - OneWire@~2.3.5 -... + OneWire@~2.3.7 + # ... or you may want to use following with ESP32 +; https://github.com/blazoncek/OneWire.git # fixes Sensor error on ESP32... ``` ## Change Log @@ -56,3 +58,6 @@ lib_deps = * Report the number of seconds until the first read in the info screen instead of sensor error 2021-04 * Adaptation for runtime configuration. +2023-05 +* Rewrite to conform to newer recommendations. +* Recommended @blazoncek fork of OneWire for ESP32 to avoid Sensor error \ No newline at end of file diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 1c49163a..c591ae56 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -52,348 +52,378 @@ class UsermodTemperature : public Usermod { static const char _parasitePin[]; //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 - float readDallas() { - byte data[9]; - int16_t result; // raw data from sensor - float retVal = -127.0f; - if (oneWire->reset()) { // if reset() fails there are no OneWire devices - oneWire->skip(); // skip ROM - oneWire->write(0xBE); // read (temperature) from EEPROM - oneWire->read_bytes(data, 9); // first 2 bytes contain temperature - #ifdef WLED_DEBUG - if (OneWire::crc8(data,8) != data[8]) { - DEBUG_PRINTLN(F("CRC error reading temperature.")); - for (byte i=0; i < 9; i++) DEBUG_PRINTF("0x%02X ", data[i]); - DEBUG_PRINT(F(" => ")); - DEBUG_PRINTF("0x%02X\n", OneWire::crc8(data,8)); - } - #endif - switch(sensorFound) { - case 0x10: // DS18S20 has 9-bit precision - result = (data[1] << 8) | data[0]; - retVal = float(result) * 0.5f; - break; - case 0x22: // DS18B20 - case 0x28: // DS1822 - case 0x3B: // DS1825 - case 0x42: // DS28EA00 - result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning - if (data[1] & 0x80) result |= 0xF000; // fix negative value - retVal = float(result) + ((data[0] & 0x08) ? 0.5f : 0.0f); - break; - } - } - for (byte i=1; i<9; i++) data[0] &= data[i]; - return data[0]==0xFF ? -127.0f : retVal; - } - - void requestTemperatures() { - DEBUG_PRINTLN(F("Requesting temperature.")); - oneWire->reset(); - oneWire->skip(); // skip ROM - oneWire->write(0x44,parasite); // request new temperature reading - if (parasite && parasitePin >=0 ) digitalWrite(parasitePin, HIGH); // has to happen within 10us (open MOSFET) - lastTemperaturesRequest = millis(); - waitingForConversion = true; - } - - void readTemperature() { - if (parasite && parasitePin >=0 ) digitalWrite(parasitePin, LOW); // deactivate power (close MOSFET) - temperature = readDallas(); - lastMeasurement = millis(); - waitingForConversion = false; - //DEBUG_PRINTF("Read temperature %2.1f.\n", temperature); // does not work properly on 8266 - DEBUG_PRINT(F("Read temperature ")); - DEBUG_PRINTLN(temperature); - } - - bool findSensor() { - DEBUG_PRINTLN(F("Searching for sensor...")); - uint8_t deviceAddress[8] = {0,0,0,0,0,0,0,0}; - // find out if we have DS18xxx sensor attached - oneWire->reset_search(); - delay(10); - while (oneWire->search(deviceAddress)) { - DEBUG_PRINTLN(F("Found something...")); - if (oneWire->crc8(deviceAddress, 7) == deviceAddress[7]) { - switch (deviceAddress[0]) { - case 0x10: // DS18S20 - case 0x22: // DS18B20 - case 0x28: // DS1822 - case 0x3B: // DS1825 - case 0x42: // DS28EA00 - DEBUG_PRINTLN(F("Sensor found.")); - sensorFound = deviceAddress[0]; - DEBUG_PRINTF("0x%02X\n", sensorFound); - return true; - } - } - } - DEBUG_PRINTLN(F("Sensor NOT found.")); - return false; - } - + float readDallas(); + void requestTemperatures(); + void readTemperature(); + bool findSensor(); #ifndef WLED_DISABLE_MQTT - void publishHomeAssistantAutodiscovery() { - if (!WLED_MQTT_CONNECTED) return; - - char json_str[1024], buf[128]; - size_t payload_size; - StaticJsonDocument<1024> json; - - sprintf_P(buf, PSTR("%s Temperature"), serverDescription); - json[F("name")] = buf; - strcpy(buf, mqttDeviceTopic); - strcat_P(buf, PSTR("/temperature")); - json[F("state_topic")] = buf; - json[F("device_class")] = F("temperature"); - json[F("unique_id")] = escapedMac.c_str(); - json[F("unit_of_measurement")] = F("°C"); - payload_size = serializeJson(json, json_str); - - sprintf_P(buf, PSTR("homeassistant/sensor/%s/config"), escapedMac.c_str()); - mqtt->publish(buf, 0, true, json_str, payload_size); - HApublished = true; - } + void publishHomeAssistantAutodiscovery(); #endif public: + UsermodTemperature(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM: this shouldn't be necessary (passthrough of constructor), maybe because Usermod is an abstract class - - void setup() { - int retries = 10; - sensorFound = 0; - temperature = -127.0f; // default to -127, DS18B20 only goes down to -50C - if (enabled) { - // config says we are enabled - USER_PRINTLN(F("Finding temperature pin...")); - // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { - oneWire = new OneWire(temperaturePin); - if (oneWire->reset()) { - while (!findSensor() && retries--) { - delay(25); // try to find sensor - } - } - if (parasite && pinManager.allocatePin(parasitePin, true, PinOwner::UM_Temperature)) { - pinMode(parasitePin, OUTPUT); - digitalWrite(parasitePin, LOW); // deactivate power (close MOSFET) - } else { - parasitePin = -1; - } - } else { - if (temperaturePin >= 0) { - USER_PRINTLN(F("Temperature pin allocation failed.")); - } - temperaturePin = -1; // allocation failed - } - } - lastMeasurement = millis() - readingInterval + 10000; - initDone = true; - USER_PRINTLN(F("temperature usermod initialized.")); - } - - void loop() { - if (!enabled || !sensorFound || strip.isUpdating()) return; - - static uint8_t errorCount = 0; - unsigned long now = millis(); - - // check to see if we are due for taking a measurement - // lastMeasurement will not be updated until the conversion - // is complete the the reading is finished - if (now - lastMeasurement < readingInterval) return; - - // we are due for a measurement, if we are not already waiting - // for a conversion to complete, then make a new request for temps - if (!waitingForConversion) { - requestTemperatures(); - return; - } - - // we were waiting for a conversion to complete, have we waited log enough? - if (now - lastTemperaturesRequest >= 750 /* 93.75ms per the datasheet but can be up to 750ms */) { - readTemperature(); - if (getTemperatureC() < -100.0f) { - if (++errorCount > 10) sensorFound = 0; - lastMeasurement = now - readingInterval + 300; // force new measurement in 300ms - return; - } - errorCount = 0; - -#ifndef WLED_DISABLE_MQTT - if (WLED_MQTT_CONNECTED) { - char subuf[64]; - strcpy(subuf, mqttDeviceTopic); - if (temperature > -100.0f) { - // dont publish super low temperature as the graph will get messed up - // the DallasTemperature library returns -127C or -196.6F when problem - // reading the sensor - strcat_P(subuf, PSTR("/temperature")); - mqtt->publish(subuf, 0, false, String(getTemperatureC()).c_str()); - strcat_P(subuf, PSTR("_f")); - mqtt->publish(subuf, 0, false, String(getTemperatureF()).c_str()); - } else { - // publish something else to indicate status? - } - } -#endif - } - } - - /** - * connected() is called every time the WiFi is (re)connected - * Use it to initialize network interfaces - */ - //void connected() {} - -#ifndef WLED_DISABLE_MQTT - /** - * subscribe to MQTT topic if needed - */ - void onMqttConnect(bool sessionPresent) { - //(re)subscribe to required topics - //char subuf[64]; - if (mqttDeviceTopic[0] != 0) { - publishHomeAssistantAutodiscovery(); - } - } -#endif - /* * API calls te enable data exchange between WLED modules */ - inline float getTemperatureC() { - return (float)temperature; + inline float getTemperatureC() { return temperature; } + inline float getTemperatureF() { return temperature * 1.8f + 32.0f; } + float getTemperature(); + const char *getTemperatureUnit(); + uint16_t getId() { return USERMOD_ID_TEMPERATURE; } + + void setup(); + void loop(); + //void connected(); +#ifndef WLED_DISABLE_MQTT + void onMqttConnect(bool sessionPresent); +#endif + //void onUpdateBegin(bool init); + + //bool handleButton(uint8_t b); + //void handleOverlayDraw(); + + void addToJsonInfo(JsonObject& root); + //void addToJsonState(JsonObject &root); + //void readFromJsonState(JsonObject &root); + void addToConfig(JsonObject &root); + bool readFromConfig(JsonObject &root); + + void appendConfigData(); +}; + +//Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 +float UsermodTemperature::readDallas() { + byte data[9]; + int16_t result; // raw data from sensor + float retVal = -127.0f; + if (oneWire->reset()) { // if reset() fails there are no OneWire devices + oneWire->skip(); // skip ROM + oneWire->write(0xBE); // read (temperature) from EEPROM + oneWire->read_bytes(data, 9); // first 2 bytes contain temperature + #ifdef WLED_DEBUG + if (OneWire::crc8(data,8) != data[8]) { + DEBUG_PRINTLN(F("CRC error reading temperature.")); + for (byte i=0; i < 9; i++) DEBUG_PRINTF("0x%02X ", data[i]); + DEBUG_PRINT(F(" => ")); + DEBUG_PRINTF("0x%02X\n", OneWire::crc8(data,8)); } - inline float getTemperatureF() { - return (float)temperature * 1.8f + 32; + #endif + switch(sensorFound) { + case 0x10: // DS18S20 has 9-bit precision + result = (data[1] << 8) | data[0]; + retVal = float(result) * 0.5f; + break; + case 0x22: // DS18B20 + case 0x28: // DS1822 + case 0x3B: // DS1825 + case 0x42: // DS28EA00 + result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning + if (data[1] & 0x80) result |= 0xF000; // fix negative value + retVal = float(result) + ((data[0] & 0x08) ? 0.5f : 0.0f); + break; } + } + for (byte i=1; i<9; i++) data[0] &= data[i]; + return data[0]==0xFF ? -127.0f : retVal; +} - /* - * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. - * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. - * Below it is shown how this could be used for e.g. a light sensor - */ - void addToJsonInfo(JsonObject& root) { - // dont add temperature to info if we are disabled - if (!enabled) return; +void UsermodTemperature::requestTemperatures() { + DEBUG_PRINTLN(F("Requesting temperature.")); + oneWire->reset(); + oneWire->skip(); // skip ROM + oneWire->write(0x44,parasite); // request new temperature reading + if (parasite && parasitePin >=0 ) digitalWrite(parasitePin, HIGH); // has to happen within 10us (open MOSFET) + lastTemperaturesRequest = millis(); + waitingForConversion = true; +} - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); +void UsermodTemperature::readTemperature() { + if (parasite && parasitePin >=0 ) digitalWrite(parasitePin, LOW); // deactivate power (close MOSFET) + temperature = readDallas(); + lastMeasurement = millis(); + waitingForConversion = false; + //DEBUG_PRINTF("Read temperature %2.1f.\n", temperature); // does not work properly on 8266 + DEBUG_PRINT(F("Read temperature ")); + DEBUG_PRINTLN(temperature); +} - JsonArray temp = user.createNestedArray(FPSTR(_name)); - - if (temperature <= -100.0f) { - temp.add(0); - temp.add(F(" Sensor Error!")); - return; +bool UsermodTemperature::findSensor() { + DEBUG_PRINTLN(F("Searching for sensor...")); + uint8_t deviceAddress[8] = {0,0,0,0,0,0,0,0}; + // find out if we have DS18xxx sensor attached + oneWire->reset_search(); + delay(10); + while (oneWire->search(deviceAddress)) { + DEBUG_PRINTLN(F("Found something...")); + if (oneWire->crc8(deviceAddress, 7) == deviceAddress[7]) { + switch (deviceAddress[0]) { + case 0x10: // DS18S20 + case 0x22: // DS18B20 + case 0x28: // DS1822 + case 0x3B: // DS1825 + case 0x42: // DS28EA00 + DEBUG_PRINTLN(F("Sensor found.")); + sensorFound = deviceAddress[0]; + DEBUG_PRINTF("0x%02X\n", sensorFound); + return true; } - - temp.add(degC ? getTemperatureC() : getTemperatureF()); - temp.add(degC ? F("°C") : F("°F")); - - JsonObject sensor = root[F("sensor")]; - if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); - temp = sensor.createNestedArray(F("temp")); - temp.add(degC ? temperature : (float)temperature * 1.8f + 32); - temp.add(degC ? F("°C") : F("°F")); } + } + DEBUG_PRINTLN(F("Sensor NOT found.")); + return false; +} - /** - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - //void addToJsonState(JsonObject &root) - //{ - //} +#ifndef WLED_DISABLE_MQTT +void UsermodTemperature::publishHomeAssistantAutodiscovery() { + if (!WLED_MQTT_CONNECTED) return; - /** - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - * Read "_" from json state and and change settings (i.e. GPIO pin) used. - */ - //void readFromJsonState(JsonObject &root) { - // if (!initDone) return; // prevent crash on boot applyPreset() - //} + char json_str[1024], buf[128]; + size_t payload_size; + StaticJsonDocument<1024> json; - /** - * addToConfig() (called from set.cpp) stores persistent properties to cfg.json - */ - void addToConfig(JsonObject &root) { - Usermod::addToConfig(root); - JsonObject top = root[FPSTR(_name)]; + sprintf_P(buf, PSTR("%s Temperature"), serverDescription); + json[F("name")] = buf; + strcpy(buf, mqttDeviceTopic); + strcat_P(buf, PSTR("/temperature")); + json[F("state_topic")] = buf; + json[F("device_class")] = F("temperature"); + json[F("unique_id")] = escapedMac.c_str(); + json[F("unit_of_measurement")] = F("°C"); + payload_size = serializeJson(json, json_str); - // we add JSON object: {"Temperature": {"pin": 0, "degC": true}} - top["pin"] = temperaturePin; // usermodparam - top["degC"] = degC; // usermodparam - top[FPSTR(_readInterval)] = readingInterval / 1000; - top[FPSTR(_parasite)] = parasite; - top[FPSTR(_parasitePin)] = parasitePin; - DEBUG_PRINTLN(F("Temperature config saved.")); - } + sprintf_P(buf, PSTR("homeassistant/sensor/%s/config"), escapedMac.c_str()); + mqtt->publish(buf, 0, true, json_str, payload_size); + HApublished = true; +} +#endif - /** - * readFromConfig() is called before setup() to populate properties from values stored in cfg.json - * - * The function should return true if configuration was successfully loaded or false if there was no configuration. - */ - bool readFromConfig(JsonObject &root) { - /*bool configComplete = */Usermod::readFromConfig(root); //WLEDMM: configComplete not implemented here (todo?) - JsonObject top = root[FPSTR(_name)]; - DEBUG_PRINT(FPSTR(_name)); - - // we look for JSON object: {"Temperature": {"pin": 0, "degC": true}} - int8_t newTemperaturePin = temperaturePin; - - if (top.isNull()) { - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - - newTemperaturePin = top["pin"] | newTemperaturePin; - degC = top["degC"] | degC; - readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000; - readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms - parasite = top[FPSTR(_parasite)] | parasite; - parasitePin = top[FPSTR(_parasitePin)] | parasitePin; - - if (!initDone) { - // first run: reading from cfg.json - temperaturePin = newTemperaturePin; - DEBUG_PRINTLN(F(" config loaded.")); - } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); - // changing paramters from settings page - if (newTemperaturePin != temperaturePin) { - DEBUG_PRINTLN(F("Re-init temperature.")); - // deallocate pin and release memory - delete oneWire; - pinManager.deallocatePin(temperaturePin, PinOwner::UM_Temperature); - temperaturePin = newTemperaturePin; - pinManager.deallocatePin(parasitePin, PinOwner::UM_Temperature); - // initialise - setup(); +void UsermodTemperature::setup() { + int retries = 10; + sensorFound = 0; + temperature = -127.0f; // default to -127, DS18B20 only goes down to -50C + if (enabled) { + // config says we are enabled + DEBUG_PRINTLN(F("Allocating temperature pin...")); + // pin retrieved from cfg.json (readFromConfig()) prior to running setup() + if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { + oneWire = new OneWire(temperaturePin); + if (oneWire->reset()) { + while (!findSensor() && retries--) { + delay(25); // try to find sensor } } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_parasitePin)].isNull(); + if (parasite && pinManager.allocatePin(parasitePin, true, PinOwner::UM_Temperature)) { + pinMode(parasitePin, OUTPUT); + digitalWrite(parasitePin, LOW); // deactivate power (close MOSFET) + } else { + parasitePin = -1; + } + } else { + if (temperaturePin >= 0) { + DEBUG_PRINTLN(F("Temperature pin allocation failed.")); + } + temperaturePin = -1; // allocation failed } + } + lastMeasurement = millis() - readingInterval + 10000; + initDone = true; +} - void appendConfigData() - { - oappend(SET_F("addHB('Temperature');")); // WLEDMM - oappend(SET_F("addInfo('Temperature:parasite-pwr")); //WLEDMM use literals - oappend(SET_F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('Temperature:parasite-pwr-pin")); //WLEDMM use literals - oappend(SET_F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field - } +void UsermodTemperature::loop() { + if (!enabled || !sensorFound || strip.isUpdating()) return; - uint16_t getId() - { - return USERMOD_ID_TEMPERATURE; + static uint8_t errorCount = 0; + unsigned long now = millis(); + + // check to see if we are due for taking a measurement + // lastMeasurement will not be updated until the conversion + // is complete the the reading is finished + if (now - lastMeasurement < readingInterval) return; + + // we are due for a measurement, if we are not already waiting + // for a conversion to complete, then make a new request for temps + if (!waitingForConversion) { + requestTemperatures(); + return; + } + + // we were waiting for a conversion to complete, have we waited log enough? + if (now - lastTemperaturesRequest >= 750 /* 93.75ms per the datasheet but can be up to 750ms */) { + readTemperature(); + if (getTemperatureC() < -100.0f) { + if (++errorCount > 10) sensorFound = 0; + lastMeasurement = now - readingInterval + 300; // force new measurement in 300ms + return; } -}; + errorCount = 0; + +#ifndef WLED_DISABLE_MQTT + if (WLED_MQTT_CONNECTED) { + char subuf[64]; + strcpy(subuf, mqttDeviceTopic); + if (temperature > -100.0f) { + // dont publish super low temperature as the graph will get messed up + // the DallasTemperature library returns -127C or -196.6F when problem + // reading the sensor + strcat_P(subuf, PSTR("/temperature")); + mqtt->publish(subuf, 0, false, String(getTemperatureC()).c_str()); + strcat_P(subuf, PSTR("_f")); + mqtt->publish(subuf, 0, false, String(getTemperatureF()).c_str()); + } else { + // publish something else to indicate status? + } + } +#endif + } +} + +/** + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ +//void UsermodTemperature::connected() {} + +#ifndef WLED_DISABLE_MQTT +/** + * subscribe to MQTT topic if needed + */ +void UsermodTemperature::onMqttConnect(bool sessionPresent) { + //(re)subscribe to required topics + //char subuf[64]; + if (mqttDeviceTopic[0] != 0) { + publishHomeAssistantAutodiscovery(); + } +} +#endif + +/* + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. + * Below it is shown how this could be used for e.g. a light sensor + */ +void UsermodTemperature::addToJsonInfo(JsonObject& root) { + // dont add temperature to info if we are disabled + if (!enabled) return; + + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray temp = user.createNestedArray(FPSTR(_name)); + + if (temperature <= -100.0f) { + temp.add(0); + temp.add(F(" Sensor Error!")); + return; + } + + temp.add(getTemperature()); + temp.add(getTemperatureUnit()); + + JsonObject sensor = root[F("sensor")]; + if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); + temp = sensor.createNestedArray(F("temperature")); + temp.add(getTemperature()); + temp.add(getTemperatureUnit()); +} + +/** + * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ +//void UsermodTemperature::addToJsonState(JsonObject &root) +//{ +//} + +/** + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + * Read "_" from json state and and change settings (i.e. GPIO pin) used. + */ +//void UsermodTemperature::readFromJsonState(JsonObject &root) { +// if (!initDone) return; // prevent crash on boot applyPreset() +//} + +/** + * addToConfig() (called from set.cpp) stores persistent properties to cfg.json + */ +void UsermodTemperature::addToConfig(JsonObject &root) { + // we add JSON object: {"Temperature": {"pin": 0, "degC": true}} + //WLEDMM: call superclass + Usermod::addToConfig(root); + JsonObject top = root[FPSTR(_name)]; + + top["pin"] = temperaturePin; // usermodparam + top["degC"] = degC; // usermodparam + top[FPSTR(_readInterval)] = readingInterval / 1000; + top[FPSTR(_parasite)] = parasite; + top[FPSTR(_parasitePin)] = parasitePin; + DEBUG_PRINTLN(F("Temperature config saved.")); +} + +/** + * readFromConfig() is called before setup() to populate properties from values stored in cfg.json + * + * The function should return true if configuration was successfully loaded or false if there was no configuration. + */ +bool UsermodTemperature::readFromConfig(JsonObject &root) { + // we look for JSON object: {"Temperature": {"pin": 0, "degC": true}} + //WLEDMM call superclass + Usermod::readFromConfig(root); //WLEDMM: configComplete not implemented here (todo?) + JsonObject top = root[FPSTR(_name)]; + DEBUG_PRINT(FPSTR(_name)); + + int8_t newTemperaturePin = temperaturePin; + + if (top.isNull()) { + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + newTemperaturePin = top["pin"] | newTemperaturePin; + degC = top["degC"] | degC; + readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000; + readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms + parasite = top[FPSTR(_parasite)] | parasite; + parasitePin = top[FPSTR(_parasitePin)] | parasitePin; + + if (!initDone) { + // first run: reading from cfg.json + temperaturePin = newTemperaturePin; + DEBUG_PRINTLN(F(" config loaded.")); + } else { + DEBUG_PRINTLN(F(" config (re)loaded.")); + // changing paramters from settings page + if (newTemperaturePin != temperaturePin) { + DEBUG_PRINTLN(F("Re-init temperature.")); + // deallocate pin and release memory + delete oneWire; + pinManager.deallocatePin(temperaturePin, PinOwner::UM_Temperature); + temperaturePin = newTemperaturePin; + pinManager.deallocatePin(parasitePin, PinOwner::UM_Temperature); + // initialise + setup(); + } + } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !top[FPSTR(_parasitePin)].isNull(); +} + +void UsermodTemperature::appendConfigData() { + oappend(SET_F("addHB('Temperature');")); // WLEDMM + oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasite)).c_str()); + oappend(SET_F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); + oappend(SET_F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field +} + +float UsermodTemperature::getTemperature() { + return degC ? getTemperatureC() : getTemperatureF(); +} + +const char *UsermodTemperature::getTemperatureUnit() { + return degC ? "°C" : "°F"; +} // strings to reduce flash memory usage (used more than twice) const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s"; diff --git a/usermods/Temperature/usermods_list.cpp b/usermods/Temperature/usermods_list.cpp deleted file mode 100644 index 50dd7816..00000000 --- a/usermods/Temperature/usermods_list.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "wled.h" -/* - * Register your v2 usermods here! - */ - -/* - * Add/uncomment your usermod filename here (and once more below) - * || || || - * \/ \/ \/ - */ -//#include "usermod_v2_example.h" -#ifdef USERMOD_DALLASTEMPERATURE -#include "../usermods/Temperature/usermod_temperature.h" -#endif - -//#include "usermod_v2_empty.h" - -void registerUsermods() -{ - /* - * Add your usermod class name here - * || || || - * \/ \/ \/ - */ - //usermods.add(new MyExampleUsermod()); -#ifdef USERMOD_DALLASTEMPERATURE - usermods.add(new UsermodTemperature()); -#endif - - //usermods.add(new UsermodRenameMe()); -} \ No newline at end of file diff --git a/usermods/artifx/artifx.js b/usermods/artifx/artifx.js index 892afb5c..d1658077 100644 --- a/usermods/artifx/artifx.js +++ b/usermods/artifx/artifx.js @@ -9,33 +9,9 @@ function toggleCEEditor(name, segID) { d.getElementById('ceEditor').style.transform = (isCEEditor) ? "translateY(0px)":"translateY(100%)"; } -function fetchAndExecute(url, name, callback, callError) -{ - fetch - (url+name, { - method: 'get' - }) - .then(res => { - if (!res.ok) { - callError("File " + name + " not found"); - return ""; - } - return res.text(); - }) - .then(text => { - callback(text); - }) - .catch(function (error) { - callError("Error getting " + name); - }) - .finally(() => { - // if (callback) setTimeout(callback,99); - }); -} - function loadLogFile(name, attempt) { var ceLogArea = d.getElementById("ceLogArea"); - fetchAndExecute((loc?`http://${locip}`:'.') + "/", name , function(logtext) + fetchAndExecute((loc?`http://${locip}`:'.') + "/", name, null, function(parms,logtext) { if (logtext == "") { if (attempt < 10) { @@ -50,7 +26,7 @@ function loadLogFile(name, attempt) { } else ceLogArea.value = logtext; - }, function(error){ + }, function(parms,error){ showToast(error); console.log(error); }); @@ -91,7 +67,7 @@ function saveCE(name, segID) { function populateCEEditor(name, segID) { - fetchAndExecute((loc?`http://${locip}`:'.') + "/", name + ".wled", function(text) + fetchAndExecute((loc?`http://${locip}`:'.') + "/", name + ".wled", null, function(parms,text) { var cn=`ARTI-FX Editor
${name}.wled
@@ -117,7 +93,7 @@ function populateCEEditor(name, segID) ceLogArea.value = "."; loadLogFile(name + ".log", 1); - }, function(error){ + }, function(parms,error){ showToast(error); console.log(error); }); @@ -129,7 +105,7 @@ function downloadGHFile(url, name, save=false, warn=false) { //Githubfile if (url == "HBE") url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/Presets/HB_PresetPack210808_32x32_16seg/Effects%20pack/"; if (url == "LM") url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/Ledmaps/"; - fetchAndExecute(url, name, function(text) { + fetchAndExecute(url, name, null, function(parms,text) { if (save) { if (warn && !confirm('Are you sure to download/overwrite ' + name + '?')) return; @@ -140,7 +116,7 @@ function downloadGHFile(url, name, save=false, warn=false) { //Githubfile var ceProgramArea = d.getElementById("ceProgramArea"); ceProgramArea.value = text; } - }, function(error){ + }, function(parms,error){ showToast(error); console.log(url + name,error); }); diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 10e62a3b..8695d273 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -72,7 +72,7 @@ * if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case. */ -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 3)) +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 4)) // espressif bug: only_left has no sound, left and right are swapped // https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138) // https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918) diff --git a/usermods/sht/usermod_sht.h b/usermods/sht/usermod_sht.h index 1123a10a..bf99afd2 100644 --- a/usermods/sht/usermod_sht.h +++ b/usermods/sht/usermod_sht.h @@ -1,5 +1,5 @@ #ifndef WLED_ENABLE_MQTT -#error "This user mod requires MQTT to be enabled." +#warning "This user mod expects MQTT to be enabled." #endif #pragma once @@ -14,7 +14,7 @@ class ShtUsermod : public Usermod { private: - bool enabled = false; // Is usermod enabled or not + //bool enabled = false; // Is usermod enabled or not //WLEDMM use public attribute of class UserMod bool firstRunDone = false; // Remembers if the first config load run had been done bool pinAllocDone = true; // Remembers if we have allocated pins bool initDone = false; // Remembers if the mod has been completely initialised @@ -22,7 +22,7 @@ class ShtUsermod : public Usermod bool haMqttDiscoveryDone = false; // Remembers if we already published the HA discovery topics // SHT vars - SHT *shtTempHumidSensor; // Instance of SHT lib + SHT *shtTempHumidSensor = nullptr; // Instance of SHT lib byte shtType = 0; // SHT sensor type to be used. Default: SHT30 byte unitOfTemp = 0; // Temperature unit to be used. Default: Celsius (0 = Celsius, 1 = Fahrenheit) bool shtInitDone = false; // Remembers if SHT sensor has been initialised @@ -37,16 +37,17 @@ class ShtUsermod : public Usermod void initShtTempHumiditySensor(); void cleanupShtTempHumiditySensor(); void cleanup(); - bool isShtReady(); + inline bool isShtReady() { return shtInitDone; } // Checks if the SHT sensor has been initialised. void publishTemperatureAndHumidityViaMqtt(); void publishHomeAssistantAutodiscovery(); void appendDeviceToMqttDiscoveryMessage(JsonDocument& root); public: + ShtUsermod(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM // Strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _enabled[]; + //static const char _name[]; //WLEDMM use public attribute of class UserMod + //static const char _enabled[]; //WLEDMM not needed static const char _shtType[]; static const char _unitOfTemp[]; static const char _haMqttDiscovery[]; @@ -71,8 +72,8 @@ class ShtUsermod : public Usermod }; // Strings to reduce flash memory usage (used more than twice) -const char ShtUsermod::_name[] PROGMEM = "SHT-Sensor"; -const char ShtUsermod::_enabled[] PROGMEM = "Enabled"; +//const char ShtUsermod::_name[] PROGMEM = "SHT-Sensor"; //WLEDMM use public attribute of class UserMod +//const char ShtUsermod::_enabled[] PROGMEM = "Enabled"; //WLEDMM not needed const char ShtUsermod::_shtType[] PROGMEM = "SHT-Type"; const char ShtUsermod::_unitOfTemp[] PROGMEM = "Unit"; const char ShtUsermod::_haMqttDiscovery[] PROGMEM = "Add-To-HA-MQTT-Discovery"; @@ -93,10 +94,13 @@ void ShtUsermod::initShtTempHumiditySensor() case USERMOD_SHT_TYPE_SHT35: shtTempHumidSensor = (SHT *) new SHT35(); break; case USERMOD_SHT_TYPE_SHT85: shtTempHumidSensor = (SHT *) new SHT85(); break; } - +#if 0 shtTempHumidSensor->begin(shtI2cAddress, i2c_sda, i2c_scl); +#else + shtTempHumidSensor->begin((uint8_t)shtI2cAddress); // WLEDMM this connects to an existing Wire (I2C) object, instead starting a new driver +#endif if (shtTempHumidSensor->readStatus() == 0xFFFF) { - DEBUG_PRINTF("[%s] SHT init failed!\n", _name); + USER_PRINTF("[%s] SHT init failed, Sensor not found!\n", _name); cleanup(); return; } @@ -113,8 +117,11 @@ void ShtUsermod::initShtTempHumiditySensor() */ void ShtUsermod::cleanupShtTempHumiditySensor() { - if (isShtReady()) shtTempHumidSensor->reset(); - delete shtTempHumidSensor; + if (isShtReady()) { + shtTempHumidSensor->reset(); + delete shtTempHumidSensor; + shtTempHumidSensor = nullptr; + } shtInitDone = false; } @@ -131,22 +138,15 @@ void ShtUsermod::cleanup() cleanupShtTempHumiditySensor(); if (pinAllocDone) { +#if 0 // WLEDMM not needed PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } }; pinManager.deallocateMultiplePins(pins, 2, PinOwner::HW_I2C); +#endif pinAllocDone = false; } enabled = false; -} - -/** - * Checks if the SHT sensor has been initialised. - * - * @return bool - */ -bool ShtUsermod::isShtReady() -{ - return shtInitDone; + shtInitDone = false; // WLEDMM bugfix } /** @@ -158,6 +158,7 @@ bool ShtUsermod::isShtReady() * @return void */ void ShtUsermod::publishTemperatureAndHumidityViaMqtt() { +#ifdef WLED_ENABLE_MQTT if (!WLED_MQTT_CONNECTED) return; char buf[128]; @@ -165,6 +166,7 @@ void ShtUsermod::publishTemperatureAndHumidityViaMqtt() { mqtt->publish(buf, 0, false, String(getTemperature()).c_str()); snprintf_P(buf, 127, PSTR("%s/humidity"), mqttDeviceTopic); mqtt->publish(buf, 0, false, String(getHumidity()).c_str()); +#endif } /** @@ -177,6 +179,7 @@ void ShtUsermod::publishTemperatureAndHumidityViaMqtt() { * @return void */ void ShtUsermod::publishHomeAssistantAutodiscovery() { +#ifdef WLED_ENABLE_MQTT if (!WLED_MQTT_CONNECTED) return; char json_str[1024], buf[128]; @@ -214,6 +217,7 @@ void ShtUsermod::publishHomeAssistantAutodiscovery() { mqtt->publish(buf, 0, true, json_str, payload_size); haMqttDiscoveryDone = true; +#endif } /** @@ -221,6 +225,7 @@ void ShtUsermod::publishHomeAssistantAutodiscovery() { * * @return void */ +#ifdef WLED_ENABLE_MQTT void ShtUsermod::appendDeviceToMqttDiscoveryMessage(JsonDocument& root) { JsonObject device = root.createNestedObject(F("dev")); device[F("ids")] = escapedMac.c_str(); @@ -229,6 +234,7 @@ void ShtUsermod::appendDeviceToMqttDiscoveryMessage(JsonDocument& root) { device[F("mdl")] = ESP.getChipModel(); device[F("mf")] = F("espressif"); } +#endif /** * Setup the mod. @@ -246,19 +252,35 @@ void ShtUsermod::setup() if (enabled) { PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } }; // GPIOs can be set to -1 and allocateMultiplePins() will return true, so check they're gt zero +#if 0 // WLEDMM done by pinManager.joinWire() if (i2c_sda < 0 || i2c_scl < 0 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { +#else + if (i2c_sda < 0 || i2c_scl < 0) { +#endif DEBUG_PRINTF("[%s] SHT pin allocation failed!\n", _name); cleanup(); return; } - pinAllocDone = true; + // WLEDMM join hardware I2C + if (pinManager.joinWire()) { // WLEDMM - this allocates global I2C pins, then starts Wire - if not started previously + pinAllocDone = true; - initShtTempHumiditySensor(); + initShtTempHumiditySensor(); - initDone = true; + initDone = true; + } else { + DEBUG_PRINTF("[%s] SHT I2C pin allocation failed!\n", _name); + return; + } } firstRunDone = true; + + if (enabled && initDone && pinAllocDone && isShtReady()) { + USER_PRINTF(PSTR("[%s] SHT sensor ready.\n"), _name); + } else { + USER_PRINTF(PSTR("[%s] SHT sensor not ready.\n"), _name); + } } /** @@ -276,7 +298,9 @@ void ShtUsermod::setup() */ void ShtUsermod::loop() { - if (!enabled || !initDone || strip.isUpdating()) return; + unsigned long last_runtime = 0; // WLEDMM ensure that strip.isUpdating() will not block longer that 1000ms + if (!enabled || !initDone || !pinAllocDone || (strip.isUpdating() && (millis()-last_runtime < 1000))) return; // WLEDMM be nice, but not too nice + last_runtime = millis(); if (isShtReady()) { if (millis() - shtLastTimeUpdated > 30000 && !shtDataRequested) { @@ -315,7 +339,9 @@ void ShtUsermod::loop() * @return void */ void ShtUsermod::onMqttConnect(bool sessionPresent) { +#ifdef WLED_ENABLE_MQTT if (haMqttDiscovery && !haMqttDiscoveryDone) publishHomeAssistantAutodiscovery(); +#endif } /** @@ -357,7 +383,7 @@ void ShtUsermod::addToConfig(JsonObject &root) { JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname - top[FPSTR(_enabled)] = enabled; + top[F("enabled")] = enabled; top[FPSTR(_shtType)] = shtType; top[FPSTR(_unitOfTemp)] = unitOfTemp; top[FPSTR(_haMqttDiscovery)] = haMqttDiscovery; @@ -388,7 +414,7 @@ bool ShtUsermod::readFromConfig(JsonObject &root) byte oldUnitOfTemp = unitOfTemp; bool oldHaMqttDiscovery = haMqttDiscovery; - getJsonValue(top[FPSTR(_enabled)], enabled); + getJsonValue(top[F("enabled")], enabled); getJsonValue(top[FPSTR(_shtType)], shtType); getJsonValue(top[FPSTR(_unitOfTemp)], unitOfTemp); getJsonValue(top[FPSTR(_haMqttDiscovery)], haMqttDiscovery); @@ -463,7 +489,19 @@ void ShtUsermod::addToJsonInfo(JsonObject& root) jsonHumidity.add(F(" RH")); jsonTemp.add(getTemperature()); - jsonTemp.add(unitOfTemp ? "°F" : "°C"); + jsonTemp.add(getUnitString()); + + // sensor object + JsonObject sensor = root[F("sensor")]; + if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); + + jsonTemp = sensor.createNestedArray(F("temp")); + jsonTemp.add(getTemperature()); + jsonTemp.add(getUnitString()); + + jsonHumidity = sensor.createNestedArray(F("humidity")); + jsonHumidity.add(getHumidity()); + jsonHumidity.add(F(" RH")); } /** diff --git a/usermods/usermod_v2_fastled/readme.md b/usermods/usermod_v2_animartrix/readme.md similarity index 84% rename from usermods/usermod_v2_fastled/readme.md rename to usermods/usermod_v2_animartrix/readme.md index 93707655..d12dd1b0 100644 --- a/usermods/usermod_v2_fastled/readme.md +++ b/usermods/usermod_v2_animartrix/readme.md @@ -4,7 +4,7 @@ In this usermod file you can find the documentation on how to take advantage of ## Installation -Copy `usermod_v2_fastled.h` to the wled00 directory. +Copy `usermod_v2_animartrix.h` to the wled00 directory. Uncomment the corresponding lines in `usermods_list.cpp` and compile! _(You shouldn't need to actually install this, it does nothing useful)_ diff --git a/usermods/usermod_v2_fastled/usermod_v2_fastled.h b/usermods/usermod_v2_animartrix/usermod_v2_animartrix.h similarity index 97% rename from usermods/usermod_v2_fastled/usermod_v2_fastled.h rename to usermods/usermod_v2_animartrix/usermod_v2_animartrix.h index c105396b..ad21ae63 100644 --- a/usermods/usermod_v2_fastled/usermod_v2_fastled.h +++ b/usermods/usermod_v2_animartrix/usermod_v2_animartrix.h @@ -20,7 +20,7 @@ //based on: https://gist.github.com/StefanPetrick/9c091d9a28a902af5a7b540e40442c64 -class StefanPetrickCore { +class AnimartrixCore { private: public: @@ -60,11 +60,11 @@ class StefanPetrickCore { float noise_angle_c, noise_angle_d, noise_angle_e, noise_angle_f; // angles based on linear noise travel float dir_c, dir_d, dir_e, dir_f; // direction multiplicators - StefanPetrickCore() { - USER_PRINTLN("StefanPetrickCore constructor"); + AnimartrixCore() { + USER_PRINTLN("AnimartrixCore constructor"); } - ~StefanPetrickCore() { - USER_PRINTLN("StefanPetrickCore destructor"); + ~AnimartrixCore() { + USER_PRINTLN("AnimartrixCore destructor"); } void init() { @@ -177,7 +177,7 @@ class StefanPetrickCore { } }; -class PolarBasics:public StefanPetrickCore { +class PolarBasics:public AnimartrixCore { private: public: @@ -262,7 +262,7 @@ class PolarBasics:public StefanPetrickCore { void calculate_oscillators() { - StefanPetrickCore::calculate_oscillators(); + AnimartrixCore::calculate_oscillators(); uint16_t noi; noi = inoise16(10000 + linear_c * 100000); // some noise controlled angular offsets @@ -369,7 +369,7 @@ class PolarBasics:public StefanPetrickCore { //based on https://gist.github.com/StefanPetrick/35ffd8467df22a77067545cfb889aa4f //and Fastled podcast nr 3: https://www.youtube.com/watch?v=3tfjP7GJnZo -class CircularBlobs:public StefanPetrickCore { +class CircularBlobs:public AnimartrixCore { private: float fade(float t){ return t * t * t * (t * (t * 6 - 15) + 10); } @@ -498,7 +498,7 @@ class CircularBlobs:public StefanPetrickCore { void calculate_oscillators() { - StefanPetrickCore::calculate_oscillators(); + AnimartrixCore::calculate_oscillators(); float n; @@ -615,11 +615,11 @@ uint16_t mode_CircularBlobs(void) { static const char _data_FX_mode_CircularBlobs[] PROGMEM = "💡CircularBlobs ☾@AngleDist,AngleMult;;!;2;sx=51,ix=51,c1=0,c2=0,c3=0"; -class FastledUsermod : public Usermod { +class AnimartrixUsermod : public Usermod { public: - FastledUsermod(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM + AnimartrixUsermod(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM void setup() { strip.addEffect(255, &mode_PolarBasics, _data_FX_mode_PolarBasics); @@ -640,7 +640,7 @@ class FastledUsermod : public Usermod { uint16_t getId() { - return USERMOD_ID_FASTLED; + return USERMOD_ID_ANIMARTRIX; } }; 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 85862888..354af71b 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 @@ -93,10 +93,11 @@ typedef enum { SSD1306_64, // U8X8_SSD1306_128X64_NONAME_HW_I2C SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C - SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI + SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI SSD1306_SPI64=7, // U8X8_SSD1306_128X64_NONAME_HW_SPI SSD1309_SPI64=8, // U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI - SSD1327_SPI128=9 // U8X8_SSD1327_WS_128X128_4W_SW_SPI + SSD1327_SPI128=9,// U8X8_SSD1327_WS_128X128_4W_SW_SPI + SSD1309_64=10 // U8X8_SSD1309_128X64_NONAME2_HW_I2C } DisplayType; @@ -213,6 +214,20 @@ class FourLineDisplayUsermod : public Usermod { // or check the gallery: // https://github.com/olikraus/u8g2/wiki/gallery + // is this display using SPI? + bool displayIsSPI(DisplayType disp) { + switch(disp) { + case SSD1306_SPI: // falls thru + case SSD1306_SPI64: // falls thru + case SSD1309_SPI64: // falls thru + case SSD1327_SPI128: + return true; // yes its SPI + break; // makes compiler happy + default: + return false; // no anything else is I2C + } + } + // some displays need this to properly apply contrast void setVcomh(bool highContrast) { if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it @@ -418,7 +433,8 @@ class FourLineDisplayUsermod : public Usermod { void setup() { if (!enabled) return; // typeOK = true will be set after successful setup - bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type > 7); + bool isHW = false; + bool isSPI = displayIsSPI(type); PinOwner po = PinOwner::UM_FourLineDisplay; if (isSPI) { if (ioPin[0] < 0 || ioPin[1] < 0) { @@ -529,6 +545,10 @@ class FourLineDisplayUsermod : public Usermod { if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA break; + case SSD1309_64: + if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1309_128X64_NONAME2_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else u8x8 = (U8X8 *) new U8X8_SSD1309_128X64_NONAME2_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA + break; case SSD1305: if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset else u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA @@ -1259,13 +1279,14 @@ class FourLineDisplayUsermod : public Usermod { oappend(SET_F("addOption(dd,'SSD1306',1);")); oappend(SET_F("addOption(dd,'SH1106',2);")); oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); + oappend(SET_F("addOption(dd,'SSD1309 128x64',10);")); oappend(SET_F("addOption(dd,'SSD1305',4);")); oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);")); oappend(SET_F("addOption(dd,'SSD1327 SPI 128x128',9);")); - bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type > 7); + bool isSPI = displayIsSPI(type); // WLEDMM add defaults oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','I2C/SPI CLK');")); oappend(SET_F("dRO('4LineDisplay:pin[]',0);")); // disable read only pins @@ -1328,7 +1349,7 @@ class FourLineDisplayUsermod : public Usermod { void addToConfig(JsonObject& root) { // determine if we are using global HW pins (data & clock) int8_t hw_dta, hw_clk; - if ((type == SSD1306_SPI || type == SSD1306_SPI64) || (type > 7)) { + if (displayIsSPI(type)) { hw_clk = spi_sclk; hw_dta = spi_mosi; } else { @@ -1394,7 +1415,7 @@ class FourLineDisplayUsermod : public Usermod { clockMode = top[FPSTR(_clockMode)] | clockMode; showSeconds = top[FPSTR(_showSeconds)] | showSeconds; contrastFix = top[FPSTR(_contrastFix)] | contrastFix; - if (newType == SSD1306_SPI || newType == SSD1306_SPI64 || newType > 7) + if (displayIsSPI(newType)) ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency else ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency @@ -1424,7 +1445,7 @@ class FourLineDisplayUsermod : public Usermod { USER_PRINTLN(F("Display terminated.")); } PinOwner po = PinOwner::UM_FourLineDisplay; - bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type > 7); + bool isSPI = displayIsSPI(type); if (isSPI) { pinManager.deallocateMultiplePins((const uint8_t *)(&oldPin[2]), 3, po); bool isHW = (oldPin[0]==spi_sclk && oldPin[1]==spi_mosi); diff --git a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h index c825a7af..058b8318 100644 --- a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h +++ b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h @@ -40,39 +40,39 @@ class WordClockUsermod : public Usermod // Normal wiring const int maskMinutes[14][maskSizeMinutes] = { - {107, 108, 109, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // :00 - { 7, 8, 9, 10, 40, 41, 42, 43, -1, -1, -1, -1}, // :05 fünf nach - { 11, 12, 13, 14, 40, 41, 42, 43, -1, -1, -1, -1}, // :10 zehn nach - { 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1}, // :15 viertel - { 15, 16, 17, 18, 19, 20, 21, 40, 41, 42, 43, -1}, // :20 zwanzig nach - { 7, 8, 9, 10, 33, 34, 35, 44, 45, 46, 47, -1}, // :25 fünf vor halb - { 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1}, // :30 halb - { 7, 8, 9, 10, 40, 41, 42, 43, 44, 45, 46, 47}, // :35 fünf nach halb - { 15, 16, 17, 18, 19, 20, 21, 33, 34, 35, -1, -1}, // :40 zwanzig vor - { 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1}, // :45 dreiviertel - { 11, 12, 13, 14, 33, 34, 35, -1, -1, -1, -1, -1}, // :50 zehn vor - { 7, 8, 9, 10, 33, 34, 35, -1, -1, -1, -1, -1}, // :55 fünf vor - { 26, 27, 28, 29, 30, 31, 32, 40, 41, 42, 43, -1}, // :15 alternative viertel nach - { 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1} // :45 alternative viertel vor + {107, 108, 109, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // 0 - 00 + { 7, 8, 9, 10, 40, 41, 42, 43, -1, -1, -1, -1}, // 1 - 05 fünf nach + { 11, 12, 13, 14, 40, 41, 42, 43, -1, -1, -1, -1}, // 2 - 10 zehn nach + { 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1}, // 3 - 15 viertel + { 15, 16, 17, 18, 19, 20, 21, 40, 41, 42, 43, -1}, // 4 - 20 zwanzig nach + { 7, 8, 9, 10, 33, 34, 35, 44, 45, 46, 47, -1}, // 5 - 25 fünf vor halb + { 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1}, // 6 - 30 halb + { 7, 8, 9, 10, 40, 41, 42, 43, 44, 45, 46, 47}, // 7 - 35 fünf nach halb + { 15, 16, 17, 18, 19, 20, 21, 33, 34, 35, -1, -1}, // 8 - 40 zwanzig vor + { 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1}, // 9 - 45 dreiviertel + { 11, 12, 13, 14, 33, 34, 35, -1, -1, -1, -1, -1}, // 10 - 50 zehn vor + { 7, 8, 9, 10, 33, 34, 35, -1, -1, -1, -1, -1}, // 11 - 55 fünf vor + { 26, 27, 28, 29, 30, 31, 32, 40, 41, 42, 43, -1}, // 12 - 15 alternative viertel nach + { 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1} // 13 - 45 alternative viertel vor }; // Meander wiring const int maskMinutesMea[14][maskSizeMinutesMea] = { - { 99, 100, 101, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // :00 - { 7, 8, 9, 10, 33, 34, 35, 36, -1, -1, -1, -1}, // :05 fünf nach - { 18, 19, 20, 21, 33, 34, 35, 36, -1, -1, -1, -1}, // :10 zehn nach - { 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1}, // :15 viertel - { 11, 12, 13, 14, 15, 16, 17, 33, 34, 35, 36, -1}, // :20 zwanzig nach - { 7, 8, 9, 10, 41, 42, 43, 44, 45, 46, 47, -1}, // :25 fünf vor halb - { 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1}, // :30 halb - { 7, 8, 9, 10, 33, 34, 35, 36, 44, 45, 46, 47}, // :35 fünf nach halb - { 11, 12, 13, 14, 15, 16, 17, 41, 42, 43, -1, -1}, // :40 zwanzig vor - { 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1}, // :45 dreiviertel - { 18, 19, 20, 21, 41, 42, 43, -1, -1, -1, -1, -1}, // :50 zehn vor - { 7, 8, 9, 10, 41, 42, 43, -1, -1, -1, -1, -1}, // :55 fünf vor - { 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1}, // :15 alternative viertel nach - { 26, 27, 28, 29, 30, 31, 32, 41, 42, 43, -1, -1} // :45 alternative viertel vor + { 99, 100, 101, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // 0 - 00 + { 7, 8, 9, 10, 33, 34, 35, 36, -1, -1, -1, -1}, // 1 - 05 fünf nach + { 18, 19, 20, 21, 33, 34, 35, 36, -1, -1, -1, -1}, // 2 - 10 zehn nach + { 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1}, // 3 - 15 viertel + { 11, 12, 13, 14, 15, 16, 17, 33, 34, 35, 36, -1}, // 4 - 20 zwanzig nach + { 7, 8, 9, 10, 41, 42, 43, 44, 45, 46, 47, -1}, // 5 - 25 fünf vor halb + { 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1}, // 6 - 30 halb + { 7, 8, 9, 10, 33, 34, 35, 36, 44, 45, 46, 47}, // 7 - 35 fünf nach halb + { 11, 12, 13, 14, 15, 16, 17, 41, 42, 43, -1, -1}, // 8 - 40 zwanzig vor + { 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1}, // 9 - 45 dreiviertel + { 18, 19, 20, 21, 41, 42, 43, -1, -1, -1, -1, -1}, // 10 - 50 zehn vor + { 7, 8, 9, 10, 41, 42, 43, -1, -1, -1, -1, -1}, // 11 - 55 fünf vor + { 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1}, // 12 - 15 alternative viertel nach + { 26, 27, 28, 29, 30, 31, 32, 41, 42, 43, -1, -1} // 13 - 45 alternative viertel vor }; @@ -284,12 +284,13 @@ class WordClockUsermod : public Usermod setHours(hours + 1, false); break; case 9: - // viertel vor bzw dreiviertel + // viertel vor if (nord) { - setMinutes(9); + setMinutes(13); } + // dreiviertel else { - setMinutes(12); + setMinutes(9); } setHours(hours + 1, false); break; @@ -422,12 +423,18 @@ class WordClockUsermod : public Usermod */ void addToConfig(JsonObject& root) { - JsonObject top = root.createNestedObject("WordClockUsermod"); - top["active"] = usermodActive; - top["displayItIs"] = displayItIs; - top["ledOffset"] = ledOffset; - top["Meander wiring?"] = meander; - top["Norddeutsch"] = nord; + JsonObject top = root.createNestedObject(F("WordClockUsermod")); + top[F("active")] = usermodActive; + top[F("displayItIs")] = displayItIs; + top[F("ledOffset")] = ledOffset; + top[F("Meander wiring?")] = meander; + top[F("Norddeutsch")] = nord; + } + + void appendConfigData() + { + oappend(SET_F("addInfo('WordClockUsermod:ledOffset', 1, 'Number of LEDs before the letters');")); + oappend(SET_F("addInfo('WordClockUsermod:Norddeutsch', 1, 'Viertel vor instead of Dreiviertel');")); } /* @@ -450,15 +457,15 @@ class WordClockUsermod : public Usermod // 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) - JsonObject top = root["WordClockUsermod"]; + JsonObject top = root[F("WordClockUsermod")]; bool configComplete = !top.isNull(); - configComplete &= getJsonValue(top["active"], usermodActive); - configComplete &= getJsonValue(top["displayItIs"], displayItIs); - configComplete &= getJsonValue(top["ledOffset"], ledOffset); - configComplete &= getJsonValue(top["Meander wiring?"], meander); - configComplete &= getJsonValue(top["Norddeutsch"], nord); + configComplete &= getJsonValue(top[F("active")], usermodActive); + configComplete &= getJsonValue(top[F("displayItIs")], displayItIs); + configComplete &= getJsonValue(top[F("ledOffset")], ledOffset); + configComplete &= getJsonValue(top[F("Meander wiring?")], meander); + configComplete &= getJsonValue(top[F("Norddeutsch")], nord); return configComplete; } diff --git a/wled00/FX.cpp b/wled00/FX.cpp index eebcee66..7b61f489 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1247,10 +1247,10 @@ uint16_t mode_rain() { if (SEGENV.call && SEGENV.step > SPEED_FORMULA_L) { SEGENV.step = 1; if (strip.isMatrix) { - uint32_t ctemp[width]; - for (int i = 0; i> 6; //div 64 + const uint32_t it = strip.now >> 5; //div 32 struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { @@ -1987,28 +1987,24 @@ uint16_t mode_fire_2012() { // Step 1. Cool down every cell a little for (int i = 0; i < SEGLEN; i++) { - uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random(8); - uint8_t minTemp = 0; - if (i 1; k--) { heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 } - } - // Step 3. Randomly ignite new 'sparks' of heat near the bottom - if (random8() <= SEGMENT.intensity) { - uint8_t y = random8(ignition); - uint8_t boost = (32+SEGMENT.custom3*2) * (2*ignition-y) / (2*ignition); - heat[y] = qadd8(heat[y], random8(64+boost,128+boost)); + // Step 3. Randomly ignite new 'sparks' of heat near the bottom + if (random8() <= SEGMENT.intensity) { + uint8_t y = random8(ignition); + uint8_t boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! + heat[y] = qadd8(heat[y], random8(96+2*boost,207+boost)); + } } // Step 4. Map from heat cells to LED colors @@ -2028,7 +2024,7 @@ uint16_t mode_fire_2012() { return FRAMETIME; } -static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,,Boost;;!;1.5d;pal=0,sx=120,ix=64,m12=1"; // bars WLEDMM 1.5d, pal = 0 +static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,,Boost;;!;1.5d;sx=64,ix=160,m12=1"; // bars WLEDMM 1.5d, // ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb @@ -2765,7 +2761,7 @@ uint16_t mode_spots() { return spots_base((255 - SEGMENT.speed) << 8); } -static const char _data_FX_MODE_SPOTS[] PROGMEM = "Spots@,Width,,,,,Overlay;!,!;!"; +static const char _data_FX_MODE_SPOTS[] PROGMEM = "Spots@Spread,Width,,,,,Overlay;!,!;!"; //Intensity slider sets number of "lights", LEDs per light fade in and out @@ -2809,7 +2805,7 @@ uint16_t mode_bouncing_balls(void) { // number of balls based on intensity setting to max of 7 (cycles colors) // non-chosen color is a random color uint16_t numBalls = (SEGMENT.intensity * (maxNumBalls - 1)) / 255 + 1; // minimum 1 ball - const float gravity = -9.81; // standard value of gravity + constexpr float gravity = -9.81; // standard value of gravity const bool hasCol2 = SEGCOLOR(2); const unsigned long time = millis(); @@ -4111,7 +4107,7 @@ uint16_t mode_dancing_shadows(void) spotlights[i].type = random8(SPOT_TYPES_COUNT); } - uint32_t color = SEGMENT.color_from_palette(spotlights[i].colorIdx, false, false, 0); + uint32_t color = SEGMENT.color_from_palette(spotlights[i].colorIdx, false, false, 255); int start = spotlights[i].position; if (spotlights[i].width <= 1) { @@ -4922,7 +4918,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } // i,j // Rules of Life - uint32_t col = prevLeds[XY(x,y)]; + uint32_t col = uint32_t(prevLeds[XY(x,y)]); // softhack007: explicit conversion added - needed with newer fastled releases uint32_t bgc = RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0); if ((col != bgc) && (neighbors < 2)) SEGMENT.setPixelColorXY(x,y, bgc); // Loneliness else if ((col != bgc) && (neighbors > 3)) SEGMENT.setPixelColorXY(x,y, bgc); // Overpopulation @@ -5530,22 +5526,30 @@ uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.so SEGMENT.fill(BLACK); } - uint8_t hue; + uint8_t hue, bri; + size_t intensity; int offsetX = beatsin16(3, -360, 360); int offsetY = beatsin16(2, -360, 360); + int sharpness = SEGMENT.custom3 / 8; // 0-3 for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { hue = x * beatsin16(10, 1, 10) + offsetY; - SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, sin8(x * SEGMENT.speed + offsetX) * sin8(x * SEGMENT.speed + offsetX) / 255, LINEARBLEND)); + intensity = bri = sin8(x * SEGMENT.speed/2 + offsetX); + for (int i=0; i>= 8*sharpness; + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, intensity, LINEARBLEND)); hue = y * 3 + offsetX; - SEGMENT.addPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, sin8(y * SEGMENT.intensity + offsetY) * sin8(y * SEGMENT.intensity + offsetY) / 255, LINEARBLEND)); + intensity = bri = sin8(y * SEGMENT.intensity/2 + offsetY); + for (int i=0; i>= 8*sharpness; + SEGMENT.addPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, intensity, LINEARBLEND)); } } return FRAMETIME; } // mode_2DTartan() -static const char _data_FX_MODE_2DTARTAN[] PROGMEM = "Tartan@X scale,Y scale;;!;2"; +static const char _data_FX_MODE_2DTARTAN[] PROGMEM = "Tartan@X scale,Y scale,,,Sharpness;;!;2"; ///////////////////////// @@ -5880,9 +5884,10 @@ uint16_t mode_2Dscrollingtext(void) { } const int yoffset = map(SEGMENT.intensity, 0, 255, -rows/2, rows/2) + (rows-letterHeight)/2; char text[33] = {'\0'}; - if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; + unsigned maxLen = (SEGMENT.name) ? min(32, (int)strlen(SEGMENT.name)) : 0; // WLEDMM make it robust against too long segment names + if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; - if (!strlen(text) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#DDMM"),5) || !strncmp_P(text,PSTR("#MMDD"),5) || !strncmp_P(text,PSTR("#TIME"),5) || !strncmp_P(text,PSTR("#HHMM"),5)) { // fallback if empty segment name: display date and time + if (!strlen(text) || !strncmp_P(text,PSTR("#F"),2) || !strncmp_P(text,PSTR("#P"),2) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#DDMM"),5) || !strncmp_P(text,PSTR("#MMDD"),5) || !strncmp_P(text,PSTR("#TIME"),5) || !strncmp_P(text,PSTR("#HHMM"),5)) { // fallback if empty segment name: display date and time char sec[5]; byte AmPmHour = hour(localTime); boolean isitAM = true; @@ -5897,6 +5902,8 @@ uint16_t mode_2Dscrollingtext(void) { else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, PSTR("%d/%d"), month(localTime), day(localTime)); else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, PSTR("%2d:%02d"), AmPmHour, minute(localTime)); + else if (!strncmp_P(text,PSTR("#FPS"),4)) sprintf_P(text, PSTR("%2d"), (int) strip.getFps()); // WLEDMM + else if (!strncmp_P(text,PSTR("#POW"),4)) sprintf_P(text, PSTR("%3.2fA"), float(strip.currentMilliamps)/1000.0f); // WLEDMM else sprintf_P(text, PSTR("%s %d, %d %2d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); } const int numberOfLetters = strlen(text); @@ -5996,8 +6003,12 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; // a few constants needed for AudioReactive effects // for 22Khz sampling -#define MAX_FREQUENCY 11025 // sample frequency / 2 (as per Nyquist criterion) -#define MAX_FREQ_LOG10 4.04238f // log10(MAX_FREQUENCY) +#define MIN_FREQUENCY 80 // 80 HZ - due to lower resolution +#define MIN_FREQ_LOG10 1.90309f // log10(MIN_FREQUENCY) +#define MIN_FREQ_LOG 4.38202663467f // log(MIN_FREQUENCY) +#define MAX_FREQUENCY 11025 // sample frequency / 2 (as per Nyquist criterion) +#define MAX_FREQ_LOG10 4.04238f // log10(MAX_FREQUENCY) +#define MAX_FREQ_LOG 9.30792070f // log(MAX_FREQUENCY) // for 20Khz sampling //#define MAX_FREQUENCY 10240 @@ -6403,7 +6414,7 @@ static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!; ////////////////////// // * MATRIPIX // ////////////////////// -uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. +uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. With some enhancements by @softhack007 // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment um_data_t *um_data; @@ -6412,6 +6423,9 @@ uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. um_data = simulateSound(SEGMENT.soundSim); } int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; + float volumeSmth = *(float*) um_data->u_data[0]; + float soundPressure = *(float*) um_data->u_data[9]; + float FFT_MajorPeak = *(float*) um_data->u_data[8]; // 8 = smooth 4=normal if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -6419,17 +6433,38 @@ uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. } uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; - if(SEGENV.aux0 != secondHand) { + if((SEGMENT.speed > 254) || (SEGENV.aux0 != secondHand)) { // WLEDMM allow run run at full speed SEGENV.aux0 = secondHand; - int pixBri = volumeRaw * SEGMENT.intensity / 64; + float rawPixel = (float)volumeRaw; + if (SEGENV.check2) rawPixel = soundPressure; // WLEDMM use Sound Pressure + if ((volumeSmth < 1.0f) || (rawPixel < 1)) rawPixel = 0; + rawPixel = rawPixel*rawPixel / 256.0f; // WLEDMM square scaling to emphasize peaks + + float pixOffset = 0; + if (SEGMENT.check2 && SEGMENT.intensity > 159) pixOffset = float(SEGMENT.intensity - 160) * 0.45f; // slightly raise lower limit, to show more details (sound pressure only) + rawPixel -= pixOffset; if (rawPixel < 0) rawPixel = 0; + + unsigned pixBri = (unsigned)rawPixel * (unsigned)(SEGMENT.intensity+1) / 160; for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left - SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0), pixBri)); + + if (!SEGENV.check1) { + // classic mode: Use Volume for colors + SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(millis()/10, false, PALETTE_SOLID_WRAP, 0), pixBri)); + } else { + // Frequency Colors: select palette entry based on log(MajorPeak) + int16_t palLocn = (volumeSmth > 0.5f) ? 255 : 0; + if((volumeSmth > 0.5f) && (FFT_MajorPeak > MIN_FREQUENCY) && (FFT_MajorPeak < MAX_FREQUENCY)) { + palLocn = (logf(FFT_MajorPeak) - MIN_FREQ_LOG) * (255.0f/(MAX_FREQ_LOG - MIN_FREQ_LOG)) *1.1f; // Scale log frequency values to the 255 colour index. "* 1.1" for some overshoot + if (palLocn < 0) palLocn = 0; // just to be safe + } + SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint16_t)palLocn, false, PALETTE_SOLID_WRAP, 0), pixBri)); + } } return FRAMETIME; } // mode_matripix() -static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix@!,Brightness;!,!;!;1v;ix=64,m12=2,si=1"; //,rev=1,mi=1,rY=1,mY=1 Arc, WeWillRockYou, reverseX +static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix ☾@!,Brightness,,,,Frequency Colors,Sound Pressure;!,!;!;1v;ix=96,m12=2,si=1"; //,rev=1,mi=1,rY=1,mY=1 Arc, WeWillRockYou, reverseX ////////////////////// @@ -6464,7 +6499,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,Max. length;!,!;!;1v;ix=128,m12=1,si=0"; // Bar, Beatsin +static const char _data_FX_MODE_MIDNOISE[] PROGMEM = "Midnoise@Fade rate,Max. length;!,!;!;1v;sx=206,ix=128,m12=1,si=0"; // Bar, Beatsin ////////////////////// @@ -6532,7 +6567,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;!,!;!;1v;ix=128,m12=2,si=0"; // Arc, Beatsin +static const char _data_FX_MODE_NOISEMETER[] PROGMEM = "Noisemeter@Fade rate,Width;!,!;!;1v;;sx=248,ix=128,m12=2,si=0"; // Arc, Beatsin ////////////////////// @@ -6554,12 +6589,14 @@ uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 16; - if (SEGENV.aux0 != secondHand) { + if((SEGMENT.speed > 254) || (SEGENV.aux0 != secondHand)) { // WLEDMM allow run run at full speed SEGENV.aux0 = secondHand; - int pixBri = volumeRaw * SEGMENT.intensity / 64; + float rawPixel = (float)volumeRaw; + rawPixel = rawPixel*rawPixel / 256.0f; // WLEDMM square scaling to emphasize peaks + int pixBri = rawPixel * (SEGMENT.intensity+1) / 96; - SEGMENT.setPixelColor(SEGLEN/2, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0), pixBri)); + SEGMENT.setPixelColor(SEGLEN/2, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(millis()/5, false, PALETTE_SOLID_WRAP, 0), pixBri)); for (int i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } @@ -6607,7 +6644,7 @@ uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. return FRAMETIME; } // mode_plasmoid() -static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels;!,!;!;1v;sx=128,ix=128,m12=0,si=0"; // Pixels, Beatsin +static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels;!,!;!;1v;sx=128,ix=80,pal=8,m12=0,si=0"; // Pixels, Beatsin, Lava Palette /////////////////////// @@ -6756,7 +6793,7 @@ uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. } // mode_blurz() static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color mix;!;1f;m12=0,si=0"; // Pixels, Beatsin -#else // original version from SR 0.13, with minor enhancements by @softhack007 +#else // original version from SR 0.13, with some enhancements by @softhack007 uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. // Hint: Looks best with segment brightness set to max (use global brightness to reduce brightness) // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment @@ -6769,39 +6806,45 @@ uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. float volumeSmth = *(float*)um_data->u_data[0]; if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); // not sure if necessary SEGMENT.fill(BLACK); SEGENV.aux0 = 0; + SEGENV.aux1 = 65535; // last pixel postion. 65535 = none + SEGENV.step = 0; // last pixel color } int fadeoutDelay = (256 - SEGMENT.speed) / 24; if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) - SEGMENT.fade_out(SEGMENT.speed); - else SEGMENT.blur(32); + SEGMENT.fadeToBlackBy(max(SEGMENT.speed, (uint8_t)1)); + else { + SEGMENT.blur(8 + SEGMENT.intensity/8 + fadeoutDelay*4); + } + if ((SEGENV.aux1 < SEGLEN) && (volumeSmth > 1.0f)) SEGMENT.setPixelColor(SEGENV.aux1,SEGENV.step); // "repaint" last pixel after blur uint16_t segLoc = random16(SEGLEN); unsigned pixColor = 2*fftResult[SEGENV.aux0%16]*240/(SEGLEN-1); // WLEDMM avoid uint8 overflow, and preserve pixel parameters for redraw - unsigned pixIntensity = min((unsigned)(1.5f*fftResult[SEGENV.aux0%16]), 255U); // WLEDMM original effect had "2*fftResult" instead of 1.5 - - if (volumeSmth > 1.0f) - SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint16_t)pixColor, false, PALETTE_SOLID_WRAP, 0),(uint8_t)pixIntensity)); - - SEGMENT.blur(SEGMENT.intensity); - SEGENV.aux0 ++; - SEGENV.aux0 %= 16; // make sure it doesn't cross 16 + unsigned pixIntensity = min((unsigned)(2.0f*fftResult[SEGENV.aux0%16]), 255U); if (volumeSmth > 1.0f) { + SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint16_t)pixColor, false, PALETTE_SOLID_WRAP, 0),(uint8_t)pixIntensity)); + SEGENV.step = SEGMENT.getPixelColor(segLoc); // remember last color + SEGENV.aux1 = segLoc; // remember last position + + SEGMENT.blur(max(SEGMENT.intensity, (uint8_t)1)); + SEGENV.aux0 ++; + SEGENV.aux0 %= 16; // make sure it doesn't cross 16 SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint16_t)pixColor, false, PALETTE_SOLID_WRAP, 0),(uint8_t)pixIntensity)); // repaint center pixel after blur - } + } else SEGMENT.blur(max(SEGMENT.intensity, (uint8_t)1)); // silence - just blur it again return FRAMETIME; } // mode_blurz() -static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz ☾@Fade rate,Blur;!,Color mix;!;1f;sx=196,ix=172,m12=0,si=0"; // Pixels, Beatsin +static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz ☾@Fade rate,Blur;!,Color mix;!;1f;sx=48,ix=127,m12=0,si=0"; // Pixels, Beatsin #endif ///////////////////////// // ** DJLight // ///////////////////////// -uint16_t mode_DJLight(void) { // Written by ??? Adapted by Will Tatam. +uint16_t mode_DJLight(void) { // Written by Stefan Petrick, Adapted by Will Tatam. const int mid = SEGLEN / 2; um_data_t *um_data; @@ -6818,7 +6861,7 @@ uint16_t mode_DJLight(void) { // Written by ??? Adapted by Wil } uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 64; - if (SEGENV.aux0 != secondHand) { // Triggered millis timing. + if((SEGMENT.speed > 254) || (SEGENV.aux0 != secondHand)) { // WLEDMM allow run run at full speed SEGENV.aux0 = secondHand; CRGB color = CRGB(0,0,0); @@ -6927,15 +6970,14 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch SEGMENT.fill(BLACK); } - uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; - if(SEGENV.aux0 != secondHand) { + uint8_t secondHand = (SEGMENT.speed < 255) ? (micros()/(256-SEGMENT.speed)/500 % 16) : 0; + if((SEGMENT.speed > 254) || (SEGENV.aux0 != secondHand)) { // WLEDMM allow run run at full speed SEGENV.aux0 = secondHand; - uint8_t sensitivity = map(SEGMENT.custom3, 0, 31, 1, 10); // reduced resolution slider - int pixVal = (volumeSmth * SEGMENT.intensity * sensitivity) / 256.0f; - if (pixVal > 255) pixVal = 255; - - float intensity = map(pixVal, 0, 255, 0, 100) / 100.0f; // make a brightness from the last avg + // Pixel brightness (value) based on volume * sensitivity * intensity + uint_fast8_t sensitivity10 = map(SEGMENT.custom3, 0, 31, 10, 100); // reduced resolution slider // WLEDMM sensitivity * 10, to avoid losing precision + int pixVal = volumeSmth * (float)SEGMENT.intensity * (float)sensitivity10 / 2560.0f; // WLEDMM 2560 due to sensitivity * 10 + if (pixVal > 255) pixVal = 255; // make a brightness from the last avg CRGB color = CRGB::Black; @@ -6944,15 +6986,15 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch // 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 - if (FFT_MajorPeak < 80) { - color = CRGB::Black; - } else { + if ((FFT_MajorPeak > 80.0f) && (volumeSmth > 0.25f)) { // WLEDMM + // Pixel color (hue) based on major frequency int upperLimit = 80 + 42 * SEGMENT.custom2; int lowerLimit = 80 + 3 * SEGMENT.custom1; - uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t - uint16_t b = 255 * intensity; - if (b > 255) b = 255; - color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED + //uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // (original formula) may under/overflow - so we enforce uint8_t + int freqMapped = lowerLimit!=upperLimit ? mapf(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // WLEDMM preserve overflows + uint8_t i = abs(freqMapped) & 0xFF; // WLEDMM we embrace overflow ;-) by "modulo 256" + + color = CHSV(i, 240, (uint8_t)pixVal); // implicit conversion to RGB supplied by FastLED } // shift the pixels one pixel up @@ -6962,7 +7004,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;;;1f;m12=3,si=0"; // Corner, Beatsin +static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Time delay,Sound effect,Low bin,High bin,Sensivity;;;1f;c1=18,c2=48,c3=6,m12=3,si=0"; // Corner, Beatsin; notes range C3 to C7 ////////////////////// @@ -7015,7 +7057,7 @@ static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Sta // // As a compromise between speed and accuracy we are currently sampling with 10240Hz, from which we can then determine with a 512bin FFT our max frequency is 5120Hz. // Depending on the music stream you have you might find it useful to change the frequency mapping. -uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung. +uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung. With some enhancements by @softhack007 um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -7030,14 +7072,14 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun } uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; - if(SEGENV.aux0 != secondHand) { + if((SEGMENT.speed > 254) || (SEGENV.aux0 != secondHand)) { // WLEDMM allow run run at full speed SEGENV.aux0 = secondHand; - float sensitivity = mapf(SEGMENT.custom3, 1, 31, 1, 10); // reduced resolution slider + float sensitivity = 0.5f * mapf(SEGMENT.custom3, 1, 31, 0.5, 10); // reduced resolution slider float pixVal = volumeSmth * (float)SEGMENT.intensity / 256.0f * sensitivity; if (pixVal > 255) pixVal = 255; - float intensity = mapf(pixVal, 0, 255, 0, 100) / 100.0f; // make a brightness from the last avg + float intensity = mapf(pixVal, 0, 255, 0, 100) / 100.0f; // make a brightness from the last avg - softhack007 note: completely over-complicated mappings. simplify! CRGB color = 0; @@ -7046,15 +7088,26 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun // 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 - if (FFT_MajorPeak < 80) { + if ((FFT_MajorPeak < 80) || (volumeSmth < 1.0f) || (FFT_MajorPeak > 10800)) { // silence or out-of-range --> black color = CRGB::Black; } else { - int upperLimit = 80 + 42 * SEGMENT.custom2; - int lowerLimit = 80 + 3 * SEGMENT.custom1; - uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t + uint8_t i = 0; + if (!SEGENV.check1) { + // direct frequency scaling + int upperLimit = 80 + 42 * SEGMENT.custom2; // max 80hz-10Khz + int lowerLimit = 80 + 3 * SEGMENT.custom1; // min 80hz-850hz + i = lowerLimit!=upperLimit ? mapf(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t + } else { + // Musical Scale (logarythmic scaling) + float upperLimit = logf(80 + 42 * SEGMENT.custom2); // max 80hz-10Khz + float lowerLimit = logf(80 + 3 * SEGMENT.custom1); // min 80hz-850hz + float peakMapped = fabsf(lowerLimit - upperLimit)>0.05f ? mapf(logf(FFT_MajorPeak), lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow + if (peakMapped > 255) intensity = constrain((320-peakMapped), 0, intensity*100) / 100.0f; // too high: fade away + i = constrain(peakMapped, 0, 255); // fix over / underflow + } uint16_t b = 255.0 * intensity; if (b > 255) b=255; - color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED + color = CHSV(i, 176+(uint8_t)b/4, (uint8_t)b); // implicit conversion to RGB supplied by FastLED } SEGMENT.setPixelColor(SEGLEN/2, color); @@ -7066,7 +7119,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;;;1f;m12=2,si=0"; // Arc, Beatsin +static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Time delay,Sound effect,Low bin,High bin,Pre-amp,Musical Scale ☾;;;1f;c1=18,c2=48,m12=2,si=0"; // notes range C3 to C7, Arc, Beatsin /////////////////////// @@ -7087,7 +7140,11 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. float volumeSmth = *(float*)um_data->u_data[0]; if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) - SEGMENT.fade_out(250); + if (SEGENV.call == 0) { + SEGMENT.fill(BLACK); + SEGENV.aux0 = 0; // WLEDMM: last color index, to perform some color smoothing + } + SEGMENT.fadeToBlackBy(96); float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling @@ -7096,13 +7153,13 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. int tempsamp = constrain(mySampleAvg,0,SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; + int indexNew = (logf(FFT_MajorPeak) - MIN_FREQ_LOG) * 255.0f/(MAX_FREQ_LOG - MIN_FREQ_LOG); // softhack007: previous formula was full of math accidents. this one works. + indexNew = constrain(indexNew, 0, 255); + int palIndex = (indexNew + SEGENV.aux0) / 2; // smooth it a bit + for (int i=0; i= gravcen->topLED) @@ -7110,15 +7167,16 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. else if (gravcen->gravityCounter % gravity == 0) gravcen->topLED--; - if (gravcen->topLED >= 0) { + if ((gravcen->topLED >= 0) && (SEGMENT.speed < 255)) { SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, CRGB::Gray); SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, CRGB::Gray); } gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + SEGENV.aux0 = indexNew; return FRAMETIME; } // mode_gravfreq() -static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sensivity;!,!;!;1f;ix=128,m12=0,si=0"; // Pixels, Beatsin +static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq ☾@Rate of fall,Sensivity;!,!;!;1f;ix=128,m12=0,si=0"; // Pixels, Beatsin ////////////////////// @@ -7224,7 +7282,7 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin *maxVol = SEGMENT.custom2 / 2; // Our volume comparator. uint8_t secondHand = micros() / (256-SEGMENT.speed)/500 + 1 % 16; - if (SEGENV.aux0 != secondHand) { // Triggered millis timing. + if((SEGMENT.speed > 254) || (SEGENV.aux0 != secondHand)) { // WLEDMM allow run run at full speed SEGENV.aux0 = secondHand; //uint8_t pixCol = (log10f((float)FFT_MajorPeak) - 2.26f) * 177; // 10Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.7 (5012hz). Let's scale accordingly. @@ -7373,7 +7431,7 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil } uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 64; - if (SEGENV.aux0 != secondHand) { // Triggered millis timing. + if((SEGMENT.speed > 254) || (SEGENV.aux0 != secondHand)) { // WLEDMM allow run run at full speed SEGENV.aux0 = secondHand; // display values of @@ -7507,7 +7565,7 @@ static const char _data_FX_MODE_2DAKEMI[] PROGMEM = "Akemi@Color speed,Dance;Hea // Distortion waves - ldirko // https://editor.soulmatelights.com/gallery/1089-distorsion-waves -// apated for WLD by @blazoncek +// adapted for WLED by @blazoncek uint16_t mode_2Ddistortionwaves() { if (!strip.isMatrix) return mode_static(); // not a 2D set-up @@ -7556,7 +7614,198 @@ uint16_t mode_2Ddistortionwaves() { return FRAMETIME; } -static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@!,Scale;;;2;"; +static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@!,Scale;;;2"; + + +//Soap +//@Stepko +//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick +// adapted for WLED by @blazoncek +uint16_t mode_2Dsoap() { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + const size_t dataSize = SEGMENT.width() * SEGMENT.height() * sizeof(uint8_t); // prevent reallocation if mirrored or grouped + if (!SEGENV.allocateData(dataSize + sizeof(uint32_t)*3)) return mode_static(); //allocation failed + + uint8_t *noise3d = reinterpret_cast(SEGENV.data); + uint32_t *noise32_x = reinterpret_cast(SEGENV.data + dataSize); + uint32_t *noise32_y = reinterpret_cast(SEGENV.data + dataSize + sizeof(uint32_t)); + uint32_t *noise32_z = reinterpret_cast(SEGENV.data + dataSize + sizeof(uint32_t)*2); + const uint32_t scale32_x = 160000U/cols; + const uint32_t scale32_y = 160000U/rows; + const uint32_t mov = MIN(cols,rows)*(SEGMENT.speed+2)/2; + const uint8_t smoothness = MIN(250,SEGMENT.intensity); // limit as >250 produces very little changes + + // init + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + *noise32_x = random16(); + *noise32_y = random16(); + *noise32_z = random16(); + } else { + *noise32_x += mov; + *noise32_y += mov; + *noise32_z += mov; + } + + for (int i = 0; i < cols; i++) { + int32_t ioffset = scale32_x * (i - cols / 2); + for (int j = 0; j < rows; j++) { + int32_t joffset = scale32_y * (j - rows / 2); + uint8_t data = inoise16(*noise32_x + ioffset, *noise32_y + joffset, *noise32_z) >> 8; + noise3d[XY(i,j)] = scale8(noise3d[XY(i,j)], smoothness) + scale8(data, 255 - smoothness); + } + } + // init also if dimensions changed + if (SEGENV.call == 0 || SEGMENT.aux0 != cols || SEGMENT.aux1 != rows) { + SEGMENT.aux0 = cols; + SEGMENT.aux1 = rows; + for (int i = 0; i < cols; i++) { + for (int j = 0; j < rows; j++) { + SEGMENT.setPixelColorXY(i, j, ColorFromPalette(SEGPALETTE,~noise3d[XY(i,j)]*3)); + } + } + } + + int zD; + int zF; + int amplitude; + int8_t shiftX = 0; //(SEGMENT.custom1 - 128) / 4; + int8_t shiftY = 0; //(SEGMENT.custom2 - 128) / 4; + + amplitude = (cols >= 16) ? (cols-8)/8 : 1; + for (int y = 0; y < rows; y++) { + int amount = ((int)noise3d[XY(0,y)] - 128) * 2 * amplitude + 256*shiftX; + int delta = abs(amount) >> 8; + int fraction = abs(amount) & 255; + for (int x = 0; x < cols; x++) { + if (amount < 0) { + zD = x - delta; + zF = zD - 1; + } else { + zD = x + delta; + zF = zD + 1; + } + CRGB PixelA = CRGB::Black; + if ((zD >= 0) && (zD < cols)) PixelA = SEGMENT.getPixelColorXY(zD, y); + else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[XY(abs(zD),y)]*3); + CRGB PixelB = CRGB::Black; + if ((zF >= 0) && (zF < cols)) PixelB = SEGMENT.getPixelColorXY(zF, y); + else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[XY(abs(zF),y)]*3); + CRGB pix = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); + SEGMENT.setPixelColorXY(x, y, pix); + } + } + + amplitude = (rows >= 16) ? (rows-8)/8 : 1; + for (int x = 0; x < cols; x++) { + int amount = ((int)noise3d[XY(x,0)] - 128) * 2 * amplitude + 256*shiftY; + int delta = abs(amount) >> 8; + int fraction = abs(amount) & 255; + for (int y = 0; y < rows; y++) { + if (amount < 0) { + zD = y - delta; + zF = zD - 1; + } else { + zD = y + delta; + zF = zD + 1; + } + CRGB PixelA = CRGB::Black; + if ((zD >= 0) && (zD < rows)) PixelA = SEGMENT.getPixelColorXY(x, zD); + else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[XY(x,abs(zD))]*3); + CRGB PixelB = CRGB::Black; + if ((zF >= 0) && (zF < rows)) PixelB = SEGMENT.getPixelColorXY(x, zF); + else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[XY(x,abs(zF))]*3); + CRGB pix = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); + SEGMENT.setPixelColorXY(x, y, pix); + } + } + + return FRAMETIME; +} +static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness;;!;2"; + + +//Idea from https://www.youtube.com/watch?v=HsA-6KIbgto&ab_channel=GreatScott%21 +//Octopus (https://editor.soulmatelights.com/gallery/671-octopus) +//Stepko and Sutaburosu +// adapted for WLED by @blazoncek +uint16_t mode_2Doctopus() { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + const uint8_t mapp = 180 / MAX(cols,rows); + + typedef struct { + uint8_t angle; + uint8_t radius; + } map_t; + + const size_t dataSize = SEGMENT.width() * SEGMENT.height() * sizeof(map_t); // prevent reallocation if mirrored or grouped + if (!SEGENV.allocateData(dataSize + 2)) return mode_static(); //allocation failed + + map_t *rMap = reinterpret_cast(SEGENV.data); + uint8_t *offsX = reinterpret_cast(SEGENV.data + dataSize); + uint8_t *offsY = reinterpret_cast(SEGENV.data + dataSize + 1); + + // re-init if SEGMENT dimensions or offset changed + if (SEGENV.call == 0 || SEGENV.aux0 != cols || SEGENV.aux1 != rows || SEGMENT.custom1 != *offsX || SEGMENT.custom2 != *offsY) { + SEGENV.step = 0; // t + SEGENV.aux0 = cols; + SEGENV.aux1 = rows; + *offsX = SEGMENT.custom1; + *offsY = SEGMENT.custom2; + const uint8_t C_X = cols / 2 + (SEGMENT.custom1 - 128)*cols/255; + const uint8_t C_Y = rows / 2 + (SEGMENT.custom2 - 128)*rows/255; + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + rMap[XY(x, y)].angle = 40.7436f * atan2f(y - C_Y, x - C_X); // avoid 128*atan2()/PI + rMap[XY(x, y)].radius = hypotf(x - C_X, y - C_Y) * mapp; //thanks Sutaburosu + } + } + } + + SEGENV.step += SEGMENT.speed / 32 + 1; // 1-4 range + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + byte angle = rMap[XY(x,y)].angle; + byte radius = rMap[XY(x,y)].radius; + //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); + uint16_t intensity = sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); + intensity = map(intensity*intensity, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display + CRGB c = ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity); + SEGMENT.setPixelColorXY(x, y, c); + } + } + return FRAMETIME; +} +static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs;;!;2;"; + + +//Waving Cell +//@Stepko (https://editor.soulmatelights.com/gallery/1704-wavingcells) +// adapted for WLED by @blazoncek +uint16_t mode_2Dwavingcell() { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + uint32_t t = millis()/(257-SEGMENT.speed); + uint8_t aX = SEGMENT.custom1/16 + 9; + uint8_t aY = SEGMENT.custom2/16 + 1; + uint8_t aZ = SEGMENT.custom3 + 1; + for (int x = 0; x < cols; x++) for (int y = 0; y > n) & 0x01); } @@ -567,9 +571,9 @@ typedef struct Segment { void fadeToBlackBy(uint8_t fadeBy); void blendPixelColor(int n, uint32_t color, uint8_t blend); void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } - void addPixelColor(int n, uint32_t color); - void addPixelColor(int n, byte r, byte g, byte b, byte w = 0) { addPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline - void addPixelColor(int n, CRGB c) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } // automatically inline + void addPixelColor(int n, uint32_t color, bool fast = false); + void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } // automatically inline + void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } // automatically inline void fadePixelColor(uint16_t n, uint8_t fade); uint8_t get_random_wheel_index(uint8_t pos); uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255); @@ -593,16 +597,16 @@ typedef struct Segment { // 2D support functions void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend); void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } - void addPixelColorXY(int x, int y, uint32_t color); - void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColorXY(x, y, RGBW32(r,g,b,w)); } // automatically inline - void addPixelColorXY(int x, int y, CRGB c) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + void addPixelColorXY(int x, int y, uint32_t color, bool fast = false); + void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } // automatically inline + void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade); void box_blur(uint16_t i, bool vertical, fract8 blur_amount); // 1D box blur (with weight) void blurRow(uint16_t row, fract8 blur_amount); void blurCol(uint16_t col, fract8 blur_amount); - void moveX(int8_t delta); - void moveY(int8_t delta); - void move(uint8_t dir, uint8_t delta); + void moveX(int8_t delta, bool wrap = false); + void moveY(int8_t delta, bool wrap = false); + void move(uint8_t dir, uint8_t delta, bool wrap = false); void draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c); @@ -616,7 +620,7 @@ typedef struct Segment { void blur2d(fract8 blur_amount) { blur(blur_amount); } void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } void nscale8(uint8_t scale); - bool jsonToPixels(char *name, uint8_t fileNr); + bool jsonToPixels(char *name, uint8_t fileNr); //WLEDMM for artifx #else uint16_t XY(uint16_t x, uint16_t y) { return x; } void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } @@ -628,16 +632,16 @@ typedef struct Segment { uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(x); } void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } - void addPixelColorXY(int x, int y, uint32_t color) { addPixelColor(x, color); } - void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColor(x, RGBW32(r,g,b,w)); } - void addPixelColorXY(int x, int y, CRGB c) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); } + void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); } + void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } void box_blur(uint16_t i, bool vertical, fract8 blur_amount) {} void blurRow(uint16_t row, fract8 blur_amount) {} void blurCol(uint16_t col, fract8 blur_amount) {} - void moveX(int8_t delta) {} - void moveY(int8_t delta) {} - void move(uint8_t dir, uint8_t delta) {} + void moveX(int8_t delta, bool wrap = false) {} + void moveY(int8_t delta, bool wrap = false) {} + void move(uint8_t dir, uint8_t delta, bool wrap = false) {} void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c) {} void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {} void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) {} @@ -688,6 +692,10 @@ class WS2812FX { // 96 bytes _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), _cumulativeFps(2), +#ifdef ARDUINO_ARCH_ESP32 + _cumulativeFps500(2*500), // WLEDMM more accurate FPS measurement for ESP32 + _lastShow500(0), +#endif _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), @@ -695,6 +703,7 @@ class WS2812FX { // 96 bytes _modeCount(MODE_COUNT), _callback(nullptr), customMappingTable(nullptr), + customMappingTableSize(0), //WLEDMM customMappingSize(0), _lastShow(0), _segment_index(0), @@ -906,6 +915,10 @@ class WS2812FX { // 96 bytes uint8_t _targetFps; uint16_t _frametime; uint16_t _cumulativeFps; +#ifdef ARDUINO_ARCH_ESP32 + uint64_t _cumulativeFps500; // WLEDMM more accurate FPS measurement for ESP32 + uint64_t _lastShow500; +#endif // will require only 1 byte struct { @@ -922,9 +935,10 @@ class WS2812FX { // 96 bytes show_callback _callback; uint16_t* customMappingTable; + uint16_t customMappingTableSize; //WLEDMM uint16_t customMappingSize; - uint32_t _lastShow; + /*uint32_t*/ unsigned long _lastShow; // WLEDMM avoid losing precision uint8_t _segment_index; uint8_t _mainSegment; diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 45eb6ac1..32671788 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -36,11 +36,6 @@ // so matrix should disable regular ledmap processing void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D - // erase old ledmap, just in case. - if (customMappingTable != nullptr) delete[] customMappingTable; - customMappingTable = nullptr; - customMappingSize = 0; - // isMatrix is set in cfg.cpp or set.cpp if (isMatrix) { // calculate width dynamically because it will have gaps @@ -68,13 +63,19 @@ void WS2812FX::setUpMatrix() { return; } - customMappingTable = new uint16_t[Segment::maxWidth * Segment::maxHeight]; + //WLEDMM recreate customMappingTable if more space needed + if (Segment::maxWidth * Segment::maxHeight > customMappingTableSize) { + USER_PRINTF("setupmatrix customMappingTable alloc %d from %d\n", Segment::maxWidth * Segment::maxHeight, customMappingTableSize); + if (customMappingTable != nullptr) delete[] customMappingTable; + customMappingTable = new uint16_t[Segment::maxWidth * Segment::maxHeight]; + if (customMappingTable != nullptr) customMappingTableSize = Segment::maxWidth * Segment::maxHeight; + } if (customMappingTable != nullptr) { customMappingSize = Segment::maxWidth * Segment::maxHeight; // fill with empty in case we don't fill the entire matrix - for (size_t i = 0; i< customMappingSize; i++) { + for (size_t i = 0; i< customMappingTableSize; i++) { //WLEDMM use customMappingTableSize customMappingTable[i] = (uint16_t)-1; } @@ -199,7 +200,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: if (Segment::maxHeight==1) return; // not a matrix set-up if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - if (leds) leds[XY(x,y)] = col; + if (ledsrgb) ledsrgb[XY(x,y)] = col; uint8_t _bri_t = currentBri(on ? opacity : 0); if (!_bri_t && !transitional) return; @@ -288,7 +289,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) // returns RGBW values of pixel uint32_t Segment::getPixelColorXY(uint16_t x, uint16_t y) { int i = XY(x,y); - if (leds) return RGBW32(leds[i].r, leds[i].g, leds[i].b, 0); + if (ledsrgb) return RGBW32(ledsrgb[i].r, ledsrgb[i].g, ledsrgb[i].b, 0); if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed @@ -304,8 +305,22 @@ void Segment::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t } // Adds the specified color with the existing pixel color perserving color balance. -void Segment::addPixelColorXY(int x, int y, uint32_t color) { - setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color)); +void Segment::addPixelColorXY(int x, int y, uint32_t color, bool fast) { + uint32_t col = getPixelColorXY(x,y); + uint8_t r = R(col); + uint8_t g = G(col); + uint8_t b = B(col); + uint8_t w = W(col); + if (fast) { + r = qadd8(r, R(color)); + g = qadd8(g, G(color)); + b = qadd8(b, B(color)); + w = qadd8(w, W(color)); + col = RGBW32(r,g,b,w); + } else { + col = color_add(col, color); + } + setPixelColorXY(x, y, col); } void Segment::fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { @@ -416,54 +431,55 @@ void Segment::blur1d(fract8 blur_amount) { for (uint16_t y = 0; y < rows; y++) blurRow(y, blur_amount); } -void Segment::moveX(int8_t delta) { +void Segment::moveX(int8_t delta, bool wrap) { const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); - if (!delta) return; - if (delta > 0) { - for (uint8_t y = 0; y < rows; y++) for (uint8_t x = 0; x < cols-1; x++) { - if (x + delta >= cols) break; - setPixelColorXY(x, y, getPixelColorXY((x + delta)%cols, y)); - } - } else { - for (uint8_t y = 0; y < rows; y++) for (int16_t x = cols-1; x >= 0; x--) { - if (x + delta < 0) break; - setPixelColorXY(x, y, getPixelColorXY(x + delta, y)); + if (!delta || abs(delta) >= cols) return; + uint32_t newPxCol[cols]; + for (int y = 0; y < rows; y++) { + if (delta > 0) { + for (int x = 0; x < cols-delta; x++) newPxCol[x] = getPixelColorXY((x + delta), y); + for (int x = cols-delta; x < cols; x++) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) - cols : x, y); + } else { + for (int x = cols-1; x >= -delta; x--) newPxCol[x] = getPixelColorXY((x + delta), y); + for (int x = -delta-1; x >= 0; x--) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) + cols : x, y); } + for (int x = 0; x < cols; x++) setPixelColorXY(x, y, newPxCol[x]); } } -void Segment::moveY(int8_t delta) { +void Segment::moveY(int8_t delta, bool wrap) { const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); - if (!delta) return; - if (delta > 0) { - for (uint8_t x = 0; x < cols; x++) for (uint8_t y = 0; y < rows-1; y++) { - if (y + delta >= rows) break; - setPixelColorXY(x, y, getPixelColorXY(x, (y + delta))); - } - } else { - for (uint8_t x = 0; x < cols; x++) for (int16_t y = rows-1; y >= 0; y--) { - if (y + delta < 0) break; - setPixelColorXY(x, y, getPixelColorXY(x, y + delta)); + if (!delta || abs(delta) >= rows) return; + uint32_t newPxCol[rows]; + for (int x = 0; x < cols; x++) { + if (delta > 0) { + for (int y = 0; y < rows-delta; y++) newPxCol[y] = getPixelColorXY(x, (y + delta)); + for (int y = rows-delta; y < rows; y++) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) - rows : y); + } else { + for (int y = rows-1; y >= -delta; y--) newPxCol[y] = getPixelColorXY(x, (y + delta)); + for (int y = -delta-1; y >= 0; y--) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) + rows : y); } + for (int y = 0; y < rows; y++) setPixelColorXY(x, y, newPxCol[y]); } } // move() - move all pixels in desired direction delta number of pixels // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down // @param delta number of pixels to move -void Segment::move(uint8_t dir, uint8_t delta) { +// @param wrap around +void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { if (delta==0) return; switch (dir) { - case 0: moveX( delta); break; - case 1: moveX( delta); moveY( delta); break; - case 2: moveY( delta); break; - case 3: moveX(-delta); moveY( delta); break; - case 4: moveX(-delta); break; - case 5: moveX(-delta); moveY(-delta); break; - case 6: moveY(-delta); break; - case 7: moveX( delta); moveY(-delta); break; + case 0: moveX( delta, wrap); break; + case 1: moveX( delta, wrap); moveY( delta, wrap); break; + case 2: moveY( delta, wrap); break; + case 3: moveX(-delta, wrap); moveY( delta, wrap); break; + case 4: moveX(-delta, wrap); break; + case 5: moveX(-delta, wrap); moveY(-delta, wrap); break; + case 6: moveY(-delta, wrap); break; + case 7: moveX( delta, wrap); moveY(-delta, wrap); break; } } @@ -552,6 +568,7 @@ void Segment::drawArc(uint16_t x0, uint16_t y0, uint16_t radius, uint32_t color, } } +//WLEDMM for artifx bool Segment::jsonToPixels(char * name, uint8_t fileNr) { char fileName[32]; //WLEDMM: als support segment name ledmaps diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 14afd344..e068171e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -26,6 +26,9 @@ #include "wled.h" #include "FX.h" #include "palettes.h" +#ifdef ARDUINO_ARCH_ESP32 +#include // WLEDMM to get esp_timer_get_time() +#endif /* Custom per-LED mapping has moved! @@ -86,11 +89,11 @@ Segment::Segment(const Segment &orig) { data = nullptr; _dataLen = 0; _t = nullptr; - if (leds && !Segment::_globalLeds) leds = nullptr; + if (ledsrgb && !Segment::_globalLeds) ledsrgb = nullptr; //WLEDMM ledsrgb not freed as still used by orig! if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); } - if (orig.leds && !Segment::_globalLeds) { leds = (CRGB*)malloc(sizeof(CRGB)*length()); if (leds) memcpy(leds, orig.leds, sizeof(CRGB)*length()); } + if (orig.ledsrgb && !Segment::_globalLeds) { ledsrgb = (CRGB*)malloc(sizeof(CRGB)*length()); if (ledsrgb) memcpy(ledsrgb, orig.ledsrgb, sizeof(CRGB)*length()); } jMap = nullptr; //WLEDMM jMap } @@ -102,7 +105,7 @@ Segment::Segment(Segment &&orig) noexcept { orig.data = nullptr; orig._dataLen = 0; orig._t = nullptr; - orig.leds = nullptr; + orig.ledsrgb = nullptr; //WLEDMM: do not free as moved to here orig.jMap = nullptr; //WLEDMM jMap } @@ -113,7 +116,7 @@ Segment& Segment::operator= (const Segment &orig) { // clean destination if (name) delete[] name; if (_t) delete _t; - if (leds && !Segment::_globalLeds) free(leds); + if (ledsrgb && !Segment::_globalLeds) free(ledsrgb); //WLEDMM: nullify below! deallocateData(); // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); @@ -122,12 +125,12 @@ Segment& Segment::operator= (const Segment &orig) { data = nullptr; _dataLen = 0; _t = nullptr; - if (!Segment::_globalLeds) leds = nullptr; + if (!Segment::_globalLeds) ledsrgb = nullptr; // copy source data if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); } - if (orig.leds && !Segment::_globalLeds) { leds = (CRGB*)malloc(sizeof(CRGB)*length()); if (leds) memcpy(leds, orig.leds, sizeof(CRGB)*length()); } + if (orig.ledsrgb && !Segment::_globalLeds) { ledsrgb = (CRGB*)malloc(sizeof(CRGB)*length()); if (ledsrgb) memcpy(ledsrgb, orig.ledsrgb, sizeof(CRGB)*length()); } jMap = nullptr; //WLEDMM jMap } return *this; @@ -140,13 +143,13 @@ Segment& Segment::operator= (Segment &&orig) noexcept { if (name) delete[] name; // free old name deallocateData(); // free old runtime data if (_t) delete _t; - if (leds && !Segment::_globalLeds) free(leds); + if (ledsrgb && !Segment::_globalLeds) free(ledsrgb); //WLEDMM: no need to nullify ledsrgb as it gets new value in memcpy memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; orig._t = nullptr; - orig.leds = nullptr; + orig.ledsrgb = nullptr; //WLEDMM: do not free as moved to here orig.jMap = nullptr; //WLEDMM jMap } return *this; @@ -187,7 +190,7 @@ void Segment::deallocateData() { */ void Segment::resetIfRequired() { if (reset) { - if (leds && !Segment::_globalLeds) { free(leds); leds = nullptr; } + if (ledsrgb && !Segment::_globalLeds) { free(ledsrgb); ledsrgb = nullptr; } if (transitional && _t) { transitional = false; delete _t; _t = nullptr; } deallocateData(); next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; @@ -199,17 +202,17 @@ void Segment::setUpLeds() { // deallocation happens in resetIfRequired() as it is called when segment changes or in destructor if (Segment::_globalLeds) #ifndef WLED_DISABLE_2D - leds = &Segment::_globalLeds[start + startY*Segment::maxWidth]; + ledsrgb = &Segment::_globalLeds[start + startY*Segment::maxWidth]; #else leds = &Segment::_globalLeds[start]; #endif - else if (!leds) { - #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) - if (psramFound()) - leds = (CRGB*)ps_malloc(sizeof(CRGB)*length()); - else - #endif - leds = (CRGB*)malloc(sizeof(CRGB)*length()); + else if ((ledsrgb == nullptr) && (length() > 0)) { //softhack007 quickfix - avoid malloc(0) which is undefined behaviour (should not happen, but i've seen it) + //#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) + //if (psramFound()) + // ledsrgb = (CRGB*)ps_malloc(sizeof(CRGB)*length()); // softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards + //else + //#endif + ledsrgb = (CRGB*)malloc(sizeof(CRGB)*length()); } } @@ -232,6 +235,7 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { case FX_MODE_GLITTER : pal = 11; break; // rainbow colors case FX_MODE_SUNRISE : pal = 35; break; // heat palette case FX_MODE_RAILWAY : pal = 3; break; // prim + sec + case FX_MODE_2DSOAP : pal = 11; break; // rainbow colors } switch (pal) { case 0: //default palette. Exceptions for specific effects above @@ -588,7 +592,7 @@ class JMapC { if (jVectorMap.size() > 0) { USER_PRINTLN("delete jVectorMap"); for (size_t i=0; i> 1; float mappedRate = float(rate) +1.1; @@ -1136,7 +1156,7 @@ void Segment::fade_out(uint8_t rate) { int g2 = G(color); int b2 = B(color); - for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + for (uint_fast16_t y = 0; y < rows; y++) for (uint_fast16_t x = 0; x < cols; x++) { color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); int w1 = W(color); int r1 = R(color); @@ -1154,19 +1174,19 @@ void Segment::fade_out(uint8_t rate) { gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; - if (is2D()) setPixelColorXY(x, y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); - else setPixelColor(x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); + if (is2D()) setPixelColorXY((uint16_t)x, (uint16_t)y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); + else setPixelColor((uint16_t)x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); } } // fades all pixels to black using nscale8() void Segment::fadeToBlackBy(uint8_t fadeBy) { - const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); - const uint16_t rows = virtualHeight(); // will be 1 for 1D + const uint_fast16_t cols = is2D() ? virtualWidth() : virtualLength(); // WLEDMM use fast int types + const uint_fast16_t rows = virtualHeight(); // will be 1 for 1D - for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { - if (is2D()) setPixelColorXY(x, y, CRGB(getPixelColorXY(x,y)).nscale8(255-fadeBy)); - else setPixelColor(x, CRGB(getPixelColor(x)).nscale8(255-fadeBy)); + for (uint_fast16_t y = 0; y < rows; y++) for (uint_fast16_t x = 0; x < cols; x++) { + if (is2D()) setPixelColorXY((uint16_t)x, (uint16_t)y, CRGB(getPixelColorXY(x,y)).nscale8(255-fadeBy)); + else setPixelColor((uint16_t)x, CRGB(getPixelColor(x)).nscale8(255-fadeBy)); } } @@ -1178,17 +1198,18 @@ void Segment::blur(uint8_t blur_amount) #ifndef WLED_DISABLE_2D if (is2D()) { // compatibility with 2D - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); - for (uint16_t i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows - for (uint16_t k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns + const uint_fast16_t cols = virtualWidth(); // WLEDMM use fast int types + const uint_fast16_t rows = virtualHeight(); + for (uint_fast16_t i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows + for (uint_fast16_t k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns return; } #endif uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; CRGB carryover = CRGB::Black; - for(uint16_t i = 0; i < virtualLength(); i++) + uint_fast16_t vlength = virtualLength(); + for(uint_fast16_t i = 0; i < vlength; i++) { CRGB cur = CRGB(getPixelColor(i)); CRGB part = cur; @@ -1200,9 +1221,9 @@ void Segment::blur(uint8_t blur_amount) uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); - setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue)); + setPixelColor((uint16_t)(i-1), qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue)); } - setPixelColor(i,cur.red, cur.green, cur.blue); + setPixelColor((uint16_t)i,cur.red, cur.green, cur.blue); carryover = part; } } @@ -1337,12 +1358,17 @@ void WS2812FX::enumerateLedmaps() { #ifndef ESP8266 if (requestJSONBufferLock(21)) { - if (readObjectFromFile(fileName, nullptr, &doc)) { + //WLEDMM: upstream code loops over all ledmap files, read them all, every byte (!!!!) and only get the name of the file!!! + File f; + f = WLED_FS.open(fileName, "r"); + if (f) { + f.find("\"n\":"); + const char *name = f.readStringUntil('\n').c_str(); + USER_PRINTF("enumerateLedmaps %s %s\n", fileName, name); + size_t len = 0; - if (!doc["n"].isNull()) { - // name field exists - const char *name = doc["n"].as(); - if (name != nullptr) len = strlen(name); + if (name != nullptr) { + len = strlen(name); if (len > 0 && len < 33) { ledmapNames[i-1] = new char[len+1]; if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33); @@ -1356,6 +1382,7 @@ void WS2812FX::enumerateLedmaps() { if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33); } } + f.close(); releaseJSONBufferLock(); } #endif @@ -1443,13 +1470,14 @@ void WS2812FX::finalizeInit(void) } if (useLedsArray) { size_t arrSize = sizeof(CRGB) * getLengthTotal(); - #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) - if (psramFound()) - Segment::_globalLeds = (CRGB*) ps_malloc(arrSize); - else - #endif - Segment::_globalLeds = (CRGB*) malloc(arrSize); - memset(Segment::_globalLeds, 0, arrSize); + // softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards (see setUpLeds()) + //#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) + //if (psramFound()) + // Segment::_globalLeds = (CRGB*) ps_malloc(arrSize); + //else + //#endif + if (arrSize > 0) Segment::_globalLeds = (CRGB*) malloc(arrSize); // WLEDMM avoid malloc(0) + if (Segment::_globalLeds != nullptr) memset(Segment::_globalLeds, 0, arrSize); // WLEDMM avoid dereferencing nullptr } //segments are created in makeAutoSegments(); @@ -1460,7 +1488,7 @@ void WS2812FX::finalizeInit(void) } void WS2812FX::service() { - uint32_t nowUp = millis(); // Be aware, millis() rolls over every 49 days + unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days // WLEDMM avoid losing precision now = nowUp + timebase; if (nowUp - _lastShow < MIN_SHOW_DELAY) return; bool doShow = false; @@ -1478,7 +1506,7 @@ void WS2812FX::service() { { if (seg.grouping == 0) seg.grouping = 1; //sanity check doShow = true; - uint16_t delay = FRAMETIME; + uint16_t frameDelay = FRAMETIME; // WLEDMM avoid name clash with "delay" function if (!seg.freeze) { //only run effect function if not frozen _virtualSegmentLength = seg.virtualLength(); @@ -1493,14 +1521,14 @@ void WS2812FX::service() { // effect blending (execute previous effect) // actual code may be a bit more involved as effects have runtime data including allocated memory //if (seg.transitional && seg._modeP) (*_mode[seg._modeP])(progress()); - delay = (*_mode[seg.currentMode(seg.mode)])(); + frameDelay = (*_mode[seg.currentMode(seg.mode)])(); if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; - if (seg.transitional && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + if (seg.transitional && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition seg.handleTransition(); } - seg.next_time = nowUp + delay; + seg.next_time = nowUp + frameDelay; } _segment_index++; } @@ -1630,6 +1658,16 @@ void WS2812FX::show(void) { if (diff > 0) fpsCurr = 1000 / diff; _cumulativeFps = (3 * _cumulativeFps + fpsCurr) >> 2; _lastShow = now; +#ifdef ARDUINO_ARCH_ESP32 // WLEDMM more accurate FPS measurement for ESP32 + uint64_t now500 = esp_timer_get_time() / 2; // native timer; micros /2 -> millis * 500 + int64_t diff500 = now500 - _lastShow500; + if ((diff500 > 300) && (diff500 < 800000)) { // exclude stupid values (timer rollover, major hickups) + float fpcCurr500 = 500000.0f / float(diff500); + if (fpcCurr500 > 2) + _cumulativeFps500 = (3 * _cumulativeFps500 + (500.0 * fpcCurr500)) / 4; // average for some smoothing + } + _lastShow500 = now500; +#endif } /** @@ -1646,12 +1684,17 @@ bool WS2812FX::isUpdating() { */ uint16_t WS2812FX::getFps() { if (millis() - _lastShow > 2000) return 0; +#ifdef ARDUINO_ARCH_ESP32 + return ((_cumulativeFps500 + 250) / 500); // +250 for proper rounding +#else return _cumulativeFps +1; +#endif } void WS2812FX::setTargetFps(uint8_t fps) { - if (fps > 0 && fps <= 120) _targetFps = fps; + if (fps > 0 && fps <= 251) _targetFps = fps; // WLEDMM allow higher framerates _frametime = 1000 / _targetFps; + if (_frametime < 1) _frametime = 1; // WLEDMM better safe than sorry } void WS2812FX::setMode(uint8_t segid, uint8_t m) { @@ -1745,7 +1788,7 @@ uint16_t WS2812FX::getLengthTotal(void) { uint16_t WS2812FX::getLengthPhysical(void) { uint16_t len = 0; - for (size_t b = 0; b < busses.getNumBusses(); b++) { + for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types Bus *bus = busses.getBus(b); if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses len += bus->getLength(); @@ -1757,7 +1800,7 @@ uint16_t WS2812FX::getLengthPhysical(void) { //returns if there is an RGBW bus (supports RGB and White, not only white) //not influenced by auto-white mode, also true if white slider does not affect output white channel bool WS2812FX::hasRGBWBus(void) { - for (size_t b = 0; b < busses.getNumBusses(); b++) { + for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types Bus *bus = busses.getBus(b); if (bus == nullptr || bus->getLength()==0) break; if (bus->hasRGB() && bus->hasWhite()) return true; @@ -1767,7 +1810,7 @@ bool WS2812FX::hasRGBWBus(void) { bool WS2812FX::hasCCTBus(void) { if (cctFromRgb && !correctWB) return false; - for (size_t b = 0; b < busses.getNumBusses(); b++) { + for (unsigned b = 0; b < busses.getNumBusses(); b++) { // WLEDMM use native (fast) types Bus *bus = busses.getBus(b); if (bus == nullptr || bus->getLength()==0) break; switch (bus->getType()) { @@ -1963,10 +2006,10 @@ uint8_t WS2812FX::setPixelSegment(uint8_t n) { void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { if (i2 >= i) { - for (uint16_t x = i; x <= i2; x++) setPixelColor(x, col); + for (uint_fast16_t x = i; x <= i2; x++) setPixelColor((uint16_t)x, col); // WLEDMM use fast int types } else { - for (uint16_t x = i2; x <= i; x++) setPixelColor(x, col); + for (uint_fast16_t x = i2; x <= i; x++) setPixelColor((uint16_t)x, col); } } @@ -2060,11 +2103,9 @@ bool WS2812FX::deserializeMap(uint8_t n) { if (!isFile) { // erase custom mapping if selecting nonexistent ledmap.json (n==0) - //WLEDM: doubt this is necessary as return false causes setupMatrix to deal with this - if (!isMatrix && !n && customMappingTable != nullptr) { + //WLEDM: doubt this is necessary as return false causes setupMatrix to deal with this !!!! + if (!isMatrix && !n) { customMappingSize = 0; - delete[] customMappingTable; - customMappingTable = nullptr; loadedLedmap = 0; //WLEDMM } return false; @@ -2072,7 +2113,11 @@ bool WS2812FX::deserializeMap(uint8_t n) { if (!requestJSONBufferLock(7)) return false; - if (!readObjectFromFile(fileName, nullptr, &doc)) { + //WLEDMM: change upstream code: do not load complete ledmaps in json as this blows up memory, use file read instead + //read the file + File f; + f = WLED_FS.open(fileName, "r"); + if (!f) { releaseJSONBufferLock(); return false; //if file does not exist just exit } @@ -2080,31 +2125,46 @@ bool WS2812FX::deserializeMap(uint8_t n) { USER_PRINT(F("Reading LED map from ")); //WLEDMM use USER_PRINT USER_PRINTLN(fileName); - // erase old custom ledmap - if (customMappingTable != nullptr) { - customMappingSize = 0; - delete[] customMappingTable; - customMappingTable = nullptr; - loadedLedmap = 0; + //WLEDMM: read width and height (mandatory in file!!) + f.find("\"width\":"); + uint16_t maxWidth = f.readStringUntil('\n').toInt(); + + f.find("\"height\":"); + uint16_t maxHeight = f.readStringUntil('\n').toInt(); + + USER_PRINTF("deserializeMap %d x %d\n", maxWidth, maxHeight); + if (maxWidth * maxHeight <= 0) { + releaseJSONBufferLock(); + return false; } - JsonArray map = doc[F("map")]; - if (!map.isNull() && map.size()) { // not an empty map + //WLEDMM: support ledmap file properties width and height + Segment::maxWidth = maxWidth; + Segment::maxHeight = maxHeight; + resetSegments(true); //WLEDMM not makeAutoSegments() as we only want to change bounds - //WLEDMM: support ledmap file properties width and height - if (doc[F("width")]>0 && doc[F("height")]>0) { - Segment::maxWidth = doc[F("width")];; - Segment::maxHeight = doc[F("height")];; - resetSegments(true); //WLEDMM not makeAutoSegments() as we only want to change bounds - } + //WLEDMM recreate customMappingTable if more space needed + if (Segment::maxWidth * Segment::maxHeight > customMappingTableSize) { + USER_PRINTF("deserializemap customMappingTable alloc %d from %d\n", Segment::maxWidth * Segment::maxHeight, customMappingTableSize); + if (customMappingTable != nullptr) delete[] customMappingTable; + customMappingTable = new uint16_t[Segment::maxWidth * Segment::maxHeight]; + if (customMappingTable != nullptr) customMappingTableSize = Segment::maxWidth * Segment::maxHeight; + } - customMappingSize = map.size(); - customMappingTable = new uint16_t[customMappingSize]; + if (customMappingTable != nullptr) { + customMappingSize = maxWidth * maxHeight; - for (uint16_t i=0; i ANALOG_BTN_READ_CYCLE) { // button is not a button but a potentiometer - analog = true; - handleAnalog(b); continue; + if (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { // button is not a button but a potentiometer + if (now - lastRead > ANALOG_BTN_READ_CYCLE) { + handleAnalog(b); + lastRead = now; + } + continue; } //button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0) if (buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_PIR_SENSOR) { - handleSwitch(b); continue; + handleSwitch(b); + continue; } //momentary button logic @@ -305,7 +308,6 @@ void handleButton() shortPressAction(b); } } - if (analog) lastRead = now; } // If enabled, RMT idle level is set to HIGH when off diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index d0a731b0..32712fb5 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -19,6 +19,8 @@ void getStringFromJson(char* dest, const char* src, size_t len) { bool deserializeConfig(JsonObject doc, bool fromFS) { //WLEDMM add USER_PRINT + // String temp; + // serializeJson(doc, temp); USER_PRINTF("deserializeConfig\n"); bool needsSave = false; @@ -177,10 +179,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; bool reversed = elm["rev"]; bool refresh = elm["ref"] | false; + uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully) ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh uint8_t AWmode = elm[F("rgbwm")] | autoWhiteMode; if (fromFS) { - BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode); + BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode); //WLEDMM to do bus , freqkHz mem += BusManager::memUsage(bc); if (mem <= MAX_LED_MEMORY) if (busses.add(bc) == -1) break; // finalization will be done in WLED::beginStrip() } else { @@ -784,6 +787,7 @@ void serializeConfig() { ins["type"] = bus->getType() & 0x7F; ins["ref"] = bus->isOffRefreshRequired(); ins[F("rgbwm")] = bus->getAutoWhiteMode(); + // ins[F("freq")] = bus->getFrequency(); WLEDMM to do bus } JsonArray hw_com = hw.createNestedArray(F("com")); diff --git a/wled00/const.h b/wled00/const.h index 7d6264d9..aace524a 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -136,7 +136,7 @@ #define USERMOD_ID_ARTIFX 90 //Usermod "usermod_v2_artifx.h" #define USERMOD_ID_WEATHER 91 //Usermod "usermod_v2_weather.h" #define USERMOD_ID_GAMES 92 //Usermod "usermod_v2_games.h" -#define USERMOD_ID_FASTLED 93 //Usermod "usermod_v2_fastled.h" +#define USERMOD_ID_ANIMARTRIX 93 //Usermod "usermod_v2_animartrix.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -221,6 +221,8 @@ #define TYPE_GS8608 23 //same driver as WS2812, but will require signal 2x per second (else displays test pattern) #define TYPE_WS2811_400KHZ 24 //half-speed WS2812 protocol, used by very old WS2811 units #define TYPE_TM1829 25 +#define TYPE_UCS8903 26 +#define TYPE_UCS8904 29 #define TYPE_SK6812_RGBW 30 #define TYPE_TM1814 31 //"Analog" types (PWM) (32-47) @@ -269,7 +271,7 @@ #define BTN_TYPE_ANALOG_INVERTED 8 //Ethernet board types -#define WLED_NUM_ETH_TYPES 9 +#define WLED_NUM_ETH_TYPES 11 #define WLED_ETH_NONE 0 #define WLED_ETH_WT32_ETH01 1 @@ -278,6 +280,10 @@ #define WLED_ETH_QUINLED 4 #define WLED_ETH_TWILIGHTLORD 5 #define WLED_ETH_ESP32DEUX 6 +#define WLED_ETH_ESP32ETHKITVE 7 +#define WLED_ETH_QUINLED_OCTA 8 +#define WLED_ETH_ABCWLEDV43ETH 9 +#define WLED_ETH_SERG74 10 //Hue error codes #define HUE_ERROR_INACTIVE 0 diff --git a/wled00/data/404.htm b/wled00/data/404.htm index 87244a94..260253a3 100644 --- a/wled00/data/404.htm +++ b/wled00/data/404.htm @@ -1,47 +1,47 @@ - - - - - Not found - - - - -

404 Not Found

-Akemi does not know where you are headed...

- - + img { + width: 400px; + max-width: 50%; + image-rendering: pixelated; + image-rendering: crisp-edges; + margin: 25px 0 -10px 0; + } + + button { + outline: none; + cursor: pointer; + padding: 8px; + margin: 10px; + width: 230px; + text-transform: uppercase; + font-family: helvetica; + font-size: 19px; + background-color: #333; + color: white; + border: 0px solid white; + border-radius: 25px; + } + + + + +

404 Not Found

+ Akemi does not know where you are headed...

+ + \ No newline at end of file diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm new file mode 100644 index 00000000..5a8c801e --- /dev/null +++ b/wled00/data/cpal/cpal.htm @@ -0,0 +1,689 @@ + + + + + + + WLED Custom Palette Editor + + + + + +
+
+

+ + + + + + + WLED Custom Palette Editor +

+
+ +
+
+
+
+
+
+ Currently in use custom palettes +
+
+
+ +
+
+ Click on the gradient editor to add new color slider, then the colored box below the slider to change its color. + Click the red box below indicator (and confirm) to delete. + Once finished, click the arrow icon to upload into the desired slot. + To edit existing palette, click the pencil icon. +
+
+
+
+
+ Available static palettes +
+
+
+ + + + + diff --git a/wled00/data/index.css b/wled00/data/index.css index d12942b6..6e68cd84 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -151,7 +151,7 @@ button { } .segt TD { - padding: 2px !important; + padding: 2px 0 !important; text-align: center; /*text-transform: uppercase;*/ } @@ -174,7 +174,10 @@ button { } .slider-icon { - transform: translate(3px,3px); + /*transform: translate(3px,3px);*/ + position: absolute; + left: 8px; + bottom: 5px; } .e-icon { @@ -314,14 +317,14 @@ button { overflow: auto; height: 100%; overscroll-behavior: none; -} - -#Effects { + padding: 0 4px; -webkit-overflow-scrolling: touch; } -#segutil, #segutil2, #segcont, #putil, #pcont, #pql { - width: 280px; +#segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw, +.fnd { + max-width: 280px; + font-size: 19px; } #putil, #segutil, #segutil2 { @@ -333,7 +336,8 @@ button { padding-top: 12px; } -#pql, #segcont, #pcont { +#fx, #pql, #segcont, #pcont, #sliders, #picker, #qcs-w, #hexw, #pall, #ledmap, +.slider, .filter, .option, .segname, .pname, .fnd { margin: 0 auto; } @@ -380,8 +384,7 @@ button { } #sliders { - width: 300px; - margin: 0 auto; + position: -webkit-sticky; position: sticky; bottom: 0; } @@ -393,32 +396,44 @@ button { .slider { max-width: 300px; - min-width: 260px; - margin: 0 auto; /* add 5px; if you want some vertical space but looks ugly */ + /* margin: 5px auto; add 5px; if you want some vertical space but looks ugly */ border-radius: 24px; position: relative; padding-bottom: 2px; } +/* Slider wrapper div */ +.sliderwrap { + height: 30px; + width: 230px; + max-width: 230px; + position: relative; + z-index: 0; +} + #sliders .slider, #info .slider { background-color: var(--c-2); } +#sliders .sliderwrap, .sbs .sliderwrap { + left: 16px; /* offset for icon */ +} + .filter, .option { background-color: var(--c-4); border-radius: 26px; height: 26px; - margin: 0 auto; /* add 4-8px if you want space at the bottom */ + max-width: 300px; + /* margin: 0 auto 4px; add 4-8px if you want space at the bottom */ padding: 4px 2px; position: relative; - z-index: 1; opacity: 1; transition: opacity 0.5s linear, height 0.5s, transform 0.5s; - transform: scaleY(1); } -.option { - z-index: unset; +.filter { + z-index: 1; + overflow: hidden; } /* Tooltip text */ @@ -530,6 +545,7 @@ button { } .close { + position: -webkit-sticky; position: sticky; top: 0; float: right; @@ -575,11 +591,17 @@ button { width: 200px; } -#info table, #nodes table { +#info table { table-layout: fixed; width: 100%; } +/*WLEDMM nodes table auto*/ +#nodes table { + table-layout: auto; + width: 100%; +} + #info td, #nodes td { padding-bottom: 8px; } @@ -590,20 +612,21 @@ button { #info table .btn, #nodes table .btn { margin: 0; } -#info div, #nodes div { +#info div { max-width: 490px; margin: 0 auto; } +/*WLEDMM nodes div width must be bigger*/ +#nodes div { + max-width: 980px; + margin: 0 auto; +} + #info #imgw { - /*display: inline-block;*/ margin: 8px auto; } -/* -#kv, #kn { - display: inline-block; -} -*/ + #lv { max-width: 600px; display: inline-block; @@ -668,6 +691,7 @@ img { transition: visibility 0.25s ease, opacity 0.25s ease; opacity: 0; visibility: hidden; + left: 8px; } output.sliderbubbleshow { @@ -725,41 +749,24 @@ input[type=range]::-moz-range-thumb { border: 2px solid var(--c-1); } -/* Slider wrapper div */ -.sliderwrap { - height: 30px; - width: 230px; - position: relative; - z-index: 0; -} - #Colors .sliderwrap { - width: 260px; margin: 4px 0 0; } -/* #Colors { - padding-top: 18px; -} - */ /* Dynamically hide brightness slider label */ .hd { display: var(--bhd); } #briwrap { + min-width: 267px; float: right; margin-top: var(--bmt); } -#picker, #qcs-w, #hexw, #pall, #ledmap { - margin: 0 auto; - width: 260px; - /*background-color: unset;*/ -} - #picker { - margin-top: -10px !important; + margin-top: 8px !important; + max-width: 260px; } /* buttons */ @@ -994,14 +1001,12 @@ textarea { .segname, .pname { white-space: nowrap; - /*cursor: pointer;*/ text-align: center; overflow: clip; text-overflow: ellipsis; line-height: 24px; padding: 8px 24px; max-width: 170px; - margin: 0 auto; position: relative; } @@ -1012,7 +1017,7 @@ textarea { /* segment power wrapper */ .sbs { - padding: 4px 0 4px 8px; + padding: 1px 0 1px 20px; display: var(--sgp); } @@ -1208,10 +1213,7 @@ TD .checkmark, TD .radiomark { border: 0px solid var(--c-f); text-align: left; transition: background-color 0.5s; - /*filter: brightness(1);*/ - font-size: 19px; border-radius: 21px; - /*min-width: 264px;*/ } .seg { @@ -1254,10 +1256,8 @@ TD .checkmark, TD .radiomark { /* list wrapper */ .list { position: relative; - width: 280px; transition: background-color 0.5s; - margin: auto auto 20px; - font-size: 19px; + margin: auto auto 10px; line-height: 24px; } @@ -1267,6 +1267,7 @@ TD .checkmark, TD .radiomark { cursor: pointer; background-color: var(--c-2); overflow: hidden; + position: -webkit-sticky; position: sticky; border-radius: 21px; margin: 13px auto 0; @@ -1303,6 +1304,7 @@ TD .checkmark, TD .radiomark { .lstI.selected { top: 0; bottom: 0; + border: 1px solid var(--c-4); } .lstI.sticky, @@ -1358,8 +1360,6 @@ TD .checkmark, TD .radiomark { /* find/search element */ .fnd { - width: 280px; - margin: 0 auto; position: relative; } @@ -1391,7 +1391,6 @@ TD .checkmark, TD .radiomark { .presin { padding: 8px; position: relative; - width: 264px; } .btn-s, @@ -1469,6 +1468,16 @@ TD .checkmark, TD .radiomark { } } +@media all and (max-width: 1023px) { + .top button { + width: 8%; + padding: 10px 0 8px 0; + } + #buttonPcm { + display: none; + } +} + @media all and (max-width: 335px) { .sliderbubble { display: none; @@ -1487,38 +1496,84 @@ TD .checkmark, TD .radiomark { } } -@media all and (max-width: 540px) { - .top button { - width: 16.6%; - padding: 8px 0 4px 0; - } -} - -@media all and (min-width: 541px) and (max-width: 719px) { - .top button { - width: 14.2%; - padding: 8px 0 4px 0; - } -} - -@media all and (max-width: 719px) { - .hd { - display: none !important; - } - #briwrap { - margin-top: 0px !important; - float: none; - } -} - -@media all and (max-width: 798px) { +@media all and (max-width: 420px) { #buttonNodes { display: none; } } -@media all and (max-width: 1024px) { - #buttonPcm { +@media all and (max-width: 639px) { + .top button { + width: 16.6%; + padding: 8px 0 4px 0; + } + #briwrap { + margin: 0 auto !important; + float: none; + display: inline-block; + } + .hd { + display: none !important; + } +} + +@media all and (min-width: 420px) and (max-width: 639px) { + .top button { + width: 14.28%; + padding: 8px 0 4px 0; + } +} + +@media all and (min-width: 640px) and (max-width: 767px) { + #buttonNodes { display: none; } } + +/* small screen & tablet "PC mode" support */ +@media all and (min-width: 1024px) and (max-width: 1249px) { + #segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw, #psFind, #sliders { + width: 100%; + max-width: 280px; + font-size: 18px; + } + #picker { + width: 230px; + } + #putil .btn-s { + width: 114px; + } + #sliders .sliderbubble { + display: none; + } + .sliderwrap { + width: calc(100% - 28px); + } + #sliders .sliderwrap { + left: 12px; + } + .segname { + padding: 8px 16px; + max-width: 140px; + } + .segname, .pname { + max-width: 134px; + } + .segt TD { + padding: 0 !important; + } + input[type="number"], input[type=text], select, textarea { + font-size: 18px; + } + input[type="number"] { + width: 32px; + } + .lstIcontent { + padding-left: 8px; + } + .revchkl { + max-width: 183px; + text-overflow: ellipsis; + overflow-x: clip; + } +} diff --git a/wled00/data/index.htm b/wled00/data/index.htm index ebf0d711..b3093816 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -74,8 +74,8 @@

Brightness

-
- +
+
@@ -89,7 +89,7 @@
-
+

@@ -203,11 +203,15 @@
+
+ + +
-
+
- +
@@ -265,7 +273,7 @@ Effect speed
- +
@@ -318,7 +326,7 @@
-
+ ` + ); + } + } } function redrawPalPrev() @@ -994,18 +1010,16 @@ function genPalPrevCss(id) function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', effectPar = '') { - return `
- - ${extraHtml} -
`; + return `
`+ + ``+ + extraHtml + + `
`; } function btype(b) @@ -1026,17 +1040,111 @@ function bname(o) return o.name; } +//WLEDMM call a node with json api command +function callNode(ip, type, json) { + console.log("callNode", ip, json); + + fetch('http://'+ip+'/json/'+type, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + //'Content-Type': 'text/html; charset=UTF-8' + }, + body: JSON.stringify(json) + }) + .then((res)=>{ + console.log("then res", res); + loadNodes(); //reload nodes + }) + .then((json)=>{ + console.log("then json", json); + }); +} + +function ddpAll() { + ins = []; + start = 0; + order = 0; + for (var node of extendedNodes) { + console.log(node); + output = {}; + output.start = start; //increase with count + output.len = node.count; + output.pin = node.ip.split("."); + output.order = order++; + output.rev = false; + output.skip = 0; + output.type = 80; + output.ref = false; + output.rgbm = 0; + // "ins":[{"start":0,"len":24,"pin":[2],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0}, + // {"start":24,"len":241,"pin":[192,168,121,57],"order":1,"rev":false,"skip":0,"type":80,"ref":false,"rgbwm":0}] + ins.push(output); + start+=node.count; + } + console.log("ins", lastinfo.ip,JSON.stringify({"hw":{"led":{"ins":ins}}})); + callNode(lastinfo.ip, "cfg", {"hw":{"led":{"ins":ins}}}); +} + function populateNodes(i,n) { + //WLEDMM helper: add html to element + function addEl(element, html) { + let k = d.createElement(element); + k.innerHTML = html; + return k; + } + var cn=""; var urows=""; var nnodes = 0; + extendedNodes = []; //reset nodes if (n.nodes) { + //WLEDMM add this node to nodes + let thisNode = {}; + thisNode.name = i.name; + thisNode.ip = i.ip; + n.nodes.push(thisNode); + n.nodes.sort((a,b) => (a.name).localeCompare(b.name)); + // console.log("populateNodes",i,n); + //loop over nodes e.g. {name: "MM 32 L", type: 32, ip: "192.168.121.249", age: 1, vid: 2305080} for (var o of n.nodes) { if (o.name) { var url = ``; - urows += inforow(url,`${btype(o.type)}
${o.vid==0?"N/A":o.vid}`); + // urows += inforow(url,`${btype(o.type)}
${o.vid==0?"N/A":o.vid} ${o.mode}`); + + //WLEDMM fetch json from nodes and add in table rows + urows += `${url}${btype(o.type)}
${o.vid==0?"N/A":o.vid}`; + // console.log("Node", o); + if (o.ip) { //in ap mode no ip... + //fetch the rest of the nodes info + fetchAndExecute(`http://${o.ip}/`, "json", nnodes, function(nnodes,text) { + // console.log(text); + let state = JSON.parse(text)["state"]; + let info = JSON.parse(text)["info"]; + let effects = JSON.parse(text)["effects"]; + let nodeInfo = {}; + // console.log(nnodes, state, info, effects); + nodeInfo.ip = info.ip; + nodeInfo.count = info.leds.count; + //append to table row + + // gId(`node${nnodes}`).appendChild(addEl('td', ``)); + gId(`node${nnodes}`).appendChild(addEl('td', "")); + // ${i.opt&0x100?inforow("Net Print ☾",""):''} + gId(`node${nnodes}`).appendChild(addEl('td', info["ip"])); + gId(`node${nnodes}`).appendChild(addEl('td', info["rel"])); + gId(`node${nnodes}`).appendChild(addEl('td', info["ver"])); + gId(`node${nnodes}`).appendChild(addEl('td', info["leds"]["count"])); + gId(`node${nnodes}`).appendChild(addEl('td', effects[state["seg"][0]["fx"]])); + if (info["leds"]["matrix"]) + gId(`node${nnodes}`).appendChild(addEl('td', info["leds"]["matrix"]["w"] + "x" + info["leds"]["matrix"]["h"])); + if (nodeInfo.ip != thisNode.ip) + extendedNodes.push(nodeInfo); + }); + } + nnodes++; } } @@ -1044,10 +1152,11 @@ function populateNodes(i,n) if (i.ndc < 0) cn += `Instance List is disabled.`; else if (nnodes == 0) cn += `No other instances found.`; cn += ` - ${inforow("Current instance:",i.name)} ${urows}
`; + cn += "" gId('kn').innerHTML = cn; + // ${inforow("Current instance:",i.name)} //WLEDMM current instance is now also shown as node } function loadNodes() @@ -1165,9 +1274,8 @@ function updateLen(s, draw=true) //WLEDMM conditonally draw segment view gId(`seg${s}len`).innerHTML = out; + // console.log("drawSegmentView","updateLen"); if (draw && isM) drawSegmentView(); //WLEDMM draw new segmentview if something changes in a segment - gId("effectGFX").style.display = isM? "inline":"none"; - gId("segGFX").style.display = isM? "inline":"none"; } //WLEDMM @@ -1203,7 +1311,6 @@ function drawSegmentView() { //WLEDMM: add canvas, initialize and set UI var canvas = gId("canvasSegments"); ctx = canvas.getContext('2d'); - peek(canvasPeek); } let segments = gId("Segments"); @@ -1339,7 +1446,7 @@ function drawSegmentView() { else fileName = ledmapFileNames[ledmapNr-10]; - fetchAndExecute((loc?`http://${locip}`:'.') + "/", fileName , function(text) { + fetchAndExecute((loc?`http://${locip}`:'.') + "/", fileName, null, function(parms,text) { var ledmapJson = JSON.parse(text); var counter = 0; var noMap = []; @@ -1383,8 +1490,8 @@ function drawSegmentView() { ctx.fill(); } post(); - }, function(error) { //error handling - console.log(error); + }, function(parms,error) { //error handling + // console.log("drawledmap error fetching " + fileName +": ", error); // downloadGHFile("LM", fileName, true, false); WLEDMM: remove as this has too much impact post(); }); @@ -1421,7 +1528,6 @@ function updateUI() gId('buttonPower').className = (isOn) ? 'active':''; gId('buttonNl').className = (nlA) ? 'active':''; gId('buttonSync').className = (syncSend) ? 'active':''; - showNodes(); updateSelectedFx(); updateSelectedPalette(selectedPal); // must be after updateSelectedFx() to un-hide color slots for * palettes @@ -1505,7 +1611,7 @@ function updateSelectedFx() if (fx.dataset.id>0) { if (segLmax==0) fx.classList.add('hide'); // none of the segments selected (hide all effects) else { - if (segLmax==1 && (!opts[3] || opts[3].indexOf("0")<0)) fx.classList.add('hide'); + if ((segLmax==1 && (!opts[3] || opts[3].indexOf("0")<0)) || (!isM && opts[3] && ((opts[3].indexOf("2")>=0 && opts[3].indexOf("1")<0)))) fx.classList.add('hide'); else fx.classList.remove('hide'); } } @@ -1558,13 +1664,14 @@ function makeWS() { var i = json.info; if (i) { parseInfo(i); - showNodes(); if (isInfo) populateInfo(i); } else i = lastinfo; var s = json.state ? json.state : json; displayRover(i, s); readState(s); + // console.log("drawSegmentView","websocket", json); + // if (isM) drawSegmentView(); }; ws.onclose = (e)=>{ gId('connind').style.backgroundColor = "var(--c-r)"; @@ -1866,26 +1973,20 @@ function requestJson(command=null) // console.log("requestJson url return", json); //WLEDMM Debug if (json.info) { let i = json.info; - // append custom palettes (when loading for the 1st time) - if (!command && isEmpty(lastinfo) && i.cpalcount) { - for (let j = 0; j
` - ); - } - } parseInfo(i); + populatePalettes(i); if (isInfo) populateInfo(i); } var s = json.state ? json.state : json; readState(s); + //WLEDMM init, gfx default on upon web page load + if (isM) { + // console.log("drawSegmentView","requestjson"); + drawSegmentView(); + toggleLiveview(); + } + //load presets and open websocket sequentially if (!pJson || isEmpty(pJson)) setTimeout(()=>{ loadPresets(()=>{ @@ -1939,7 +2040,7 @@ function toggleLiveview() if (isM) { //WLEDMM adding liveview2D support on main ui isLv = !isLv; - gId("colorGFX").style.display = isLv? "inline":"none"; + gId("colorGFX").style.display = isLv? "inline":"none"; //WLEDMM: set off if explicitly gfx pushed gId("effectGFX").style.display = isLv? "inline":"none"; gId("segGFX").style.display = isLv? "inline":"none"; @@ -2018,29 +2119,29 @@ function makeSeg() behavior: 'smooth', block: 'start', }); - var cn = `
-
- - - - - - - - - - - - - - - - -
${isM?'Start X':'Start LED'}${isM?(cfg.comp.seglen?"Width":"Stop X"):(cfg.comp.seglen?"LED count":"Stop LED")}
Start Y${cfg.comp.seglen?'Height':'Stop Y'}
-
${ledCount - ns} LEDs
-
-
-
`; + var cn = `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
${isM?'Start X':'Start LED'}${isM?(cfg.comp.seglen?"Width":"Stop X"):(cfg.comp.seglen?"LED count":"Stop LED")}
Start Y${cfg.comp.seglen?'Height':'Stop Y'}
`+ + `
${ledCount - ns} LEDs
`+ + `
`+ + `
`+ + `
`; gId('segutil').innerHTML = cn; } @@ -2954,7 +3055,7 @@ function genPresets() } //WLEDMM: utility function to load contents of file from FS (used in draw) -function fetchAndExecute(url, name, callback, callError) +function fetchAndExecute(url, name, parms, callback, callError = null) { fetch (url+name, { @@ -2968,11 +3069,10 @@ function fetchAndExecute(url, name, callback, callError) return res.text(); }) .then(text => { - console.log("text", text); - callback(text); + callback(parms, text); }) .catch(function (error) { - callError("Error getting " + name); + if (callError) callError(parms, "Error getting " + name); console.log(error); }) .finally(() => { @@ -3054,7 +3154,7 @@ function getPalettesData(page, callback) showToast(error, true); }); } - +/* function hideModes(txt) { for (let e of (gId('fxlist').querySelectorAll('.lstI')||[])) { @@ -3065,7 +3165,7 @@ function hideModes(txt) if (f) e.classList.add('hide'); //else e.classList.remove('hide'); } } - +*/ function search(f,l=null) { f.nextElementSibling.style.display=(f.value!=='')?'block':'none'; @@ -3230,20 +3330,16 @@ function move(e) x0 = null; } -function showNodes() { - gId('buttonNodes').style.display = (lastinfo.ndc > 0 && (wW > 797 || (wW > 539 && wW < 720))) ? "block":"none"; -} - function size() { wW = window.innerWidth; - showNodes(); var h = gId('top').clientHeight; sCol('--th', h + "px"); sCol('--bh', gId('bot').clientHeight + "px"); if (isLv && !isM) h -= 4; //WLEDMM: no for matrices sCol('--tp', h + "px"); togglePcMode(); + lastw = wW; } function togglePcMode(fromB = false) @@ -3251,21 +3347,18 @@ function togglePcMode(fromB = false) if (fromB) { pcModeA = !pcModeA; localStorage.setItem('pcm', pcModeA); - pcMode = pcModeA; } - if (wW <= 1024 && !pcMode) return; - if (!fromB && ((wW <= 1024 && lastw <= 1024) || (wW > 1024 && lastw > 1024))) return; + pcMode = (wW >= 1024) && pcModeA; + if (cpick) cpick.resize(pcMode && wW>1023 && wW<1250 ? 230 : 260); // for tablet in landscape + if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size() openTab(0, true); - if (wW <= 1024) {pcMode = false;} - else if (pcModeA && !fromB) pcMode = pcModeA; updateTablinks(0); gId('buttonPcm').className = (pcMode) ? "active":""; gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto"; sCol('--bh', gId('bot').clientHeight + "px"); _C.style.width = (pcMode)?'100%':'400%'; - lastw = wW; - //WLEDMM resize segmentview + // console.log("drawSegmentView","togglePCMode"); if (isM) drawSegmentView(); } @@ -3290,7 +3383,7 @@ function mergeDeep(target, ...sources) size(); _C.style.setProperty('--n', N); -window.addEventListener('resize', size, false); +window.addEventListener('resize', size, true); _C.addEventListener('mousedown', lock, false); _C.addEventListener('touchstart', lock, false); diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index b2aab7d4..c3f2892c 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -126,19 +126,19 @@ let len = parseInt(d.getElementsByName("LC"+n)[0].value); len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too if (t < 32) { + if (t==26 || t==29) len *= 2; // 16 bit LEDs if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem - if (t > 29) return len*20; //RGBW + if (t > 28) return len*20; //RGBW return len*15; } else if (maxM >= 10000) //ESP32 RMT uses double buffer? { - if (t > 29) return len*8; //RGBW + if (t > 28) return len*8; //RGBW return len*6; } - if (t > 29) return len*4; //RGBW + if (t > 28) return len*4; //RGBW return len*3; } if (t > 31 && t < 48) return 5; - if (t == 44 || t == 45) return len*4; //RGBW return len*3; } @@ -147,6 +147,7 @@ let isRGBW = false, gRGBW = false, memu = 0; gId('ampwarning').style.display = (d.Sf.MA.value > 7200) ? 'inline':'none'; + gId('fpswarning').style.display = (d.Sf.FR.value > 69) ? 'block':'none'; // WLEDMM if (d.Sf.LA.value == 255) laprev = 12; else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value; @@ -185,15 +186,16 @@ if (t > 31 && t < 48) d.getElementsByName("LC"+n)[0].value = 1; // for sanity change analog count just to 1 LED } gId("rf"+n).onclick = (t == 31) ? (()=>{return false}) : (()=>{}); // prevent change for TM1814 - gRGBW |= isRGBW = ((t > 17 && t < 22) || t == 30 || t == 31 || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h + gRGBW |= isRGBW = ((t > 17 && t < 22) || (t > 28 && t < 32) || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h gId("co"+n).style.display = ((t >= 80 && t < 96) || (t >= 40 && t < 48)) ? "none":"inline"; // hide color order for PWM - gId("dig"+n+"w").style.display = (t == 30 || t == 31) ? "inline":"none"; // show swap channels dropdown - if (!(t == 30 || t == 31)) d.getElementsByName("WO"+n)[0].value = 0; // reset swapping + gId("dig"+n+"w").style.display = (t > 28 && t < 32) ? "inline":"none"; // show swap channels dropdown + if (!(t > 28 && t < 32)) d.getElementsByName("WO"+n)[0].value = 0; // reset swapping gId("dig"+n+"c").style.display = (t >= 40 && t < 48) ? "none":"inline"; // hide count for analog gId("dig"+n+"r").style.display = (t >= 80 && t < 96) ? "none":"inline"; // hide reversed for virtual gId("dig"+n+"s").style.display = ((t >= 80 && t < 96) || (t >= 40 && t < 48)) ? "none":"inline"; // hide skip 1st for virtual & analog gId("dig"+n+"f").style.display = ((t >= 16 && t < 32) || (t >= 50 && t < 64)) ? "inline":"none"; // hide refresh gId("dig"+n+"a").style.display = (isRGBW && t != 40) ? "inline":"none"; // auto calculate white + gId("dig"+n+"l").style.display = (t > 48 && t < 64) ? "inline":"none"; // bus clock speed gId("rev"+n).innerHTML = (t >= 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog gId("psd"+n).innerHTML = (t >= 40 && t < 48) ? "Index:":"Start:"; // change analog start description } @@ -331,6 +333,8 @@ ${i+1}: \ \ \ +\ +\ \ \ \ @@ -359,6 +363,7 @@ ${i+1}:
+
Start:  
Length:

@@ -690,7 +695,12 @@ Length: Linear (never wrap)
- Target refresh rate: FPS + Target refresh rate: FPS +
Config template:

diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 15092ea5..68024f34 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -186,11 +186,13 @@

Ethernet Type