diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index c28446e61..2b599e6f6 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 69129061f..3d792e73b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,82 @@ ## WLED changelog +#### Build 2307130 +- larger `oappend()` stack buffer (3.5k) for ESP32 +- Preset cycle bugfix (#3262) +- Rotary encoder ALT fix for large LED count (#3276) +- effect updates (2D Plasmaball), `blur()` speedup +- On/Off toggle from nodes view (may show unknow device type on older versions) (#3291) +- various fixes and improvements (ABL, crashes when changing presets with different segments) + +#### Build 2306270 +- ESP-NOW remote support (#3237) +- Pixel Magic tool (display pixel art) (#3249) +- Websocket (peek) fallback when connection cannot be established, WS retries (#3267) +- Add WiFi network scan RPC command to Improv Serial (#3271) +- Longer (custom option available) segment name for ESP32 +- various fixes and improvements + +#### Build 2306210 +- 0.14.0-b3 release +- respect global I2C in all usermods (no local initilaisation of I2C bus) +- Multi relay usermod compile-time enabled option (-D MULTI_RELAY_ENABLED=true|false) + +#### Build 2306180 +- Added client-side option for applying effect defaults from metadata +- Improved ESP8266 stability by reducing WebSocket response resends +- Updated ESP8266 core to 3.1.2 + +#### Build 2306141 +- Lissajous improvements +- Scrolling Text improvements (leading 0) + +#### Build 2306140 +- Add settings PIN (un)locking to JSON post API + +#### Build 2306130 +- Bumped version to 0.14-b3 (beta 3) +- added pin dropdowns in LED preferences (not for LED pins) and usermods +- introduced (unused ATM) NeoGammaWLEDMethod class +- Reverse proxy support +- PCF8754 support for Rotary encoder (requires wiring INT pin to ESP GPIO) +- Rely on global I2C pins for usermods (breaking change) +- various fixes and enhancements + +#### Build 2306020 +- Support for segment sets (PR #3171) +- Reduce sound simulation modes to 2 to facilitiate segment sets +- Trigger button immediately on press if all configured presets are the same (PR #3226) +- Changes for allowing Alexa to change light color to White when auto-calculating from RGB (PR #3211) + +#### Build 2305280 +- DDP protocol update (#3193) +- added PCF8574 I2C port expander support for Multi relay usermod +- MQTT multipacket (fragmented) message fix +- added option to retain MQTT brightness and color messages +- new ethernet board: @srg74 Ethernet Shield +- new 2D effects: Soap (#3184) & Octopus & Waving cell (credit @St3P40 https://github.com/80Stepko08) +- various fixes and enhancements + +#### Build 2305090 +- new ethernet board: @Wladi ABC! WLED Eth +- Battery usermod voltage calculation (#3116) +- custom palette editor (#3164) +- improvements in Dancing Shadows and Tartan effects +- UCS389x support +- switched to NeoPixelBus 2.7.5 (replaced NeoPixelBrightnessBus with NeoPixelBusLg) +- SPI bus clock selection (for LEDs) (#3173) +- DMX mode preset fix (#3134) +- iOS fix for scroll (#3182) +- Wordclock "Norddeutsch" fix (#3161) +- various fixes and enhancements + +#### 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 2ec2d887a..c4fe9b1c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.0-b2", + "version": "0.14.0-b3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1409,9 +1409,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" }, "semver-diff": { "version": "2.1.0", diff --git a/package.json b/package.json index 5c4cfde40..d57c87d5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.0-b2", + "version": "0.14.0-b3", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index 22d0bbc0d..d3b71d3c4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,11 +9,15 @@ # (use `platformio_override.ini` when building for your own board; see `platformio_override.ini.sample` for an example) # ------------------------------------------------------------------------------ -# Release / CI binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3, esp32s3dev_8MB +# CI binaries +; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # ESP32 variant builds are temporarily excluded from CI due to toolchain issues on the GitHub Actions Linux environment +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, 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 +; 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 # Single binaries (uncomment your board) ; default_envs = elekstube_ips @@ -36,6 +40,8 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s ; default_envs = esp32dev_qio80 ; default_envs = esp32_eth_ota1mapp ; default_envs = esp32s2_saola +; default_envs = esp32c3dev +; default_envs = lolin_s2_mini src_dir = ./wled00 data_dir = ./wled00/data @@ -55,18 +61,29 @@ arduino_core_2_6_3 = espressif8266@2.3.3 arduino_core_2_7_4 = espressif8266@2.6.2 arduino_core_3_0_0 = espressif8266@3.0.0 arduino_core_3_2_0 = espressif8266@3.2.0 +arduino_core_4_1_0 = espressif8266@4.1.0 +arduino_core_3_1_2 = espressif8266@4.2.0 # Development platforms arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage # Platform to use for ESP8266 -platform_wled_default = ${common.arduino_core_3_2_0} +platform_wled_default = ${common.arduino_core_3_1_2} # We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization -platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 - platformio/toolchain-xtensa @ ~2.40802.200502 - platformio/tool-esptool @ ~1.413.0 - platformio/tool-esptoolpy @ ~1.30000.0 +#platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 +platform_packages = platformio/framework-arduinoespressif8266 + platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 + platformio/tool-esptool #@ ~1.413.0 + platformio/tool-esptoolpy #@ ~1.30000.0 + +## previous platform for 8266, in case of problems with the new one +## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_2_0, which does not support Ucs890x +;; platform_wled_default = ${common.arduino_core_3_2_0} +;; platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 +;; platformio/toolchain-xtensa @ ~2.40802.200502 +;; platformio/tool-esptool @ ~1.413.0 +;; platformio/tool-esptoolpy @ ~1.30000.0 # ------------------------------------------------------------------------------ # FLAGS: DEBUG @@ -99,11 +116,13 @@ 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 -D CORE_DEBUG_LEVEL=0 -D NDEBUG + -Wno-attributes ;; silence warnings about unknown attribute 'maybe_unused' in NeoPixelBus #build_flags for the IRremoteESP8266 library (enabled decoders have to appear here) -D _IR_ENABLE_DEFAULT_=false -D DECODE_HASH=true @@ -111,7 +130,7 @@ build_flags = -D DECODE_SONY=true -D DECODE_SAMSUNG=true -D DECODE_LG=true - ; -Dregister= # remove warnings in C++17 due to use of deprecated register keyword by the FastLED library + ;-Dregister= # remove warnings in C++17 due to use of deprecated register keyword by the FastLED library ;; warning: this breaks framework code on ESP32-C3 and ESP32-S2 -DWLED_USE_MY_CONFIG ; -D USERMOD_SENSORSTOMQTT #For ADS1115 sensor uncomment following @@ -121,6 +140,7 @@ build_unflags = build_flags_esp8266 = ${common.build_flags} ${esp8266.build_flags} build_flags_esp32 = ${common.build_flags} ${esp32.build_flags} +build_flags_esp32_V4= ${common.build_flags} ${esp32_idf_V4.build_flags} ldscript_1m128k = eagle.flash.1m128.ld ldscript_2m512k = eagle.flash.2m512.ld @@ -150,24 +170,23 @@ upload_speed = 115200 # LIBRARIES: required dependencies # Please note that we don't always use the latest version of a library. # -# The following libraries have been included (and some of them changd) in the source: +# The following libraries have been included (and some of them changed) in the source: # ArduinoJson@5.13.5, E131@1.0.0(changed), Time@1.5, Timezone@1.2.1 # ------------------------------------------------------------------------------ lib_compat_mode = strict lib_deps = fastled/FastLED @ 3.5.0 IRremoteESP8266 @ 2.8.2 + makuna/NeoPixelBus @ 2.7.5 https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI - #For use SSD1306 OLED display uncomment following - #U8g2@~2.28.8 - #U8g2@~2.32.10 - #For Dallas sensor uncomment following 2 lines - #OneWire@~2.3.5 - #milesburton/DallasTemperature@^3.9.0 + #For compatible OLED display uncomment following + #U8g2 #@ ~2.33.15 + #For Dallas sensor uncomment following + #OneWire @ ~2.3.7 #For BME280 sensor uncomment following - #BME280@~3.0.0 + #BME280 @ ~3.0.0 ; adafruit/Adafruit BMP280 Library @ 2.1.0 ; adafruit/Adafruit CCS811 Library @ 1.0.4 ; adafruit/Adafruit Si7021 Library @ 1.4.0 @@ -182,25 +201,25 @@ build_flags = -DESP8266 -DFP_IN_IROM ;-Wno-deprecated-declarations - ;-Wno-register - ;-Wno-misleading-indentation -; NONOSDK22x_190703 = 2.2.2-dev(38a443e) + -Wno-register ;; leaves some warnings when compiling C files: command-line option '-Wno-register' is valid for C++/ObjC++ but not for C + ;-Dregister= # remove warnings in C++17 due to use of deprecated register keyword by the FastLED library ;; warning: this can be dangerous + -Wno-misleading-indentation + ; NONOSDK22x_190703 = 2.2.2-dev(38a443e) -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 -; lwIP 2 - Higher Bandwidth no Features -; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH -; lwIP 1.4 - Higher Bandwidth (Aircoookie has) - -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH -; VTABLES in Flash + ; lwIP 2 - Higher Bandwidth no Features + ; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH + ; lwIP 1.4 - Higher Bandwidth (Aircoookie has) + -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH + ; VTABLES in Flash -DVTABLES_IN_FLASH -; restrict to minimal mime-types + ; restrict to minimal mime-types -DMIMETYPE_MINIMAL -lib_deps = - ${env.lib_deps} +lib_deps = #https://github.com/lorol/LITTLEFS.git ESPAsyncTCP @ 1.2.2 ESPAsyncUDP - makuna/NeoPixelBus @ 2.6.9 + ${env.lib_deps} [esp32] #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip @@ -212,39 +231,67 @@ build_flags = -g -DARDUINO_ARCH_ESP32 #-DCONFIG_LITTLEFS_FOR_IDF_3_2 -D CONFIG_ASYNC_TCP_USE_WDT=0 -#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x + #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x -D LOROL_LITTLEFS - ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when builing with arduino-esp32 >=2.0.3 + ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv lib_deps = - ${env.lib_deps} https://github.com/lorol/LITTLEFS.git - makuna/NeoPixelBus @ 2.6.9 https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${env.lib_deps} + + +[esp32_idf_V4] +;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 +;; very similar to the normal ESP32 flags, but omitting Lorol LittleFS, as littlefs is included in the new framework already. +;; +;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. +;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. +platform = espressif32@5.2.0 +platform_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 +build_flags = -g + -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one + -DARDUINO_ARCH_ESP32 -DESP32 + #-DCONFIG_LITTLEFS_FOR_IDF_3_2 + -D CONFIG_ASYNC_TCP_USE_WDT=0 + -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 +default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +lib_deps = + https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${env.lib_deps} [esp32s2] +;; generic definitions for all ESP32-S2 boards +platform = espressif32@5.2.0 +platform_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 build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32S2 - -DCONFIG_IDF_TARGET_ESP32S2 + -DCONFIG_IDF_TARGET_ESP32S2=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 + -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DCO -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! ;; 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_CDC_ON_BOOT, ARDUINO_USB_MSC_ON_BOOT, ARDUINO_USB_DFU_ON_BOOT + ;; ARDUINO_USB_CDC_ON_BOOT lib_deps = - ${env.lib_deps} - makuna/NeoPixelBus @ 2.6.9 https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${env.lib_deps} [esp32c3] +;; generic definitions for all ESP32-C3 boards +platform = espressif32@5.2.0 +platform_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 build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32C3 - -DCONFIG_IDF_TARGET_ESP32C3 + -DCONFIG_IDF_TARGET_ESP32C3=1 -D CONFIG_ASYNC_TCP_USE_WDT=0 -DCO -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 @@ -252,27 +299,28 @@ build_flags = -g ;; ARDUINO_USB_CDC_ON_BOOT lib_deps = - ${env.lib_deps} - makuna/NeoPixelBus @ 2.6.9 https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${env.lib_deps} [esp32s3] ;; generic definitions for all ESP32-S3 boards +platform = espressif32@5.2.0 +platform_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 build_flags = -g -DESP32 -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32S3 - -DCONFIG_IDF_TARGET_ESP32S3 + -DCONFIG_IDF_TARGET_ESP32S3=1 -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 = - ${env.lib_deps} - ;; NeoPixelBus 2.7.1 is the first that has official support for ESP32-S3 - makuna/NeoPixelBus @ ~2.7.1 https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + ${env.lib_deps} # ------------------------------------------------------------------------------ @@ -367,6 +415,21 @@ board_build.partitions = ${esp32.default_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio +[env:esp32dev_V4_dio80] +;; experimental ESP32 env using ESP-IDF V4.4.x +;; Warning: this build environment is not stable!! +;; please erase your device before installing. +board = esp32dev +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_V4_qio80 #-D WLED_DISABLE_BROWNOUT_DET +lib_deps = ${esp32_idf_V4.lib_deps} +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32_idf_V4.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = dio + [env:esp32_eth] board = esp32-poe platform = ${esp32.platform} @@ -387,17 +450,20 @@ board_build.flash_mode = qio upload_speed = 460800 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=S2_saola + -DARDUINO_USB_CDC_ON_BOOT=1 lib_deps = ${esp32s2.lib_deps} -[env:esp32c3] -platform = espressif32@5.1.1 ;; well-tested on -C3, good compatibility with WLED -; platform = espressif32@~5.2.0 ;; might help in case you experience bootloops due to corrupted flash filesystem +[env:esp32c3dev] +extends = esp32c3 +platform = ${esp32c3.platform} +platform_packages = ${esp32c3.platform_packages} framework = arduino board = esp32-c3-devkitm-1 board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv build_flags = ${common.build_flags} ${esp32c3.build_flags} #-D WLED_RELEASE_NAME=ESP32-C3 -D WLED_WATCHDOG_TIMEOUT=0 - ; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual USB + -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB + ;-DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip upload_speed = 460800 build_unflags = ${common.build_unflags} lib_deps = ${esp32c3.lib_deps} @@ -405,14 +471,14 @@ lib_deps = ${esp32c3.lib_deps} [env:esp32s3dev_8MB] ;; ESP32-S3-DevKitC-1 development board, with 8MB FLASH, no PSRAM (flash_mode: qio) board = esp32-s3-devkitc-1 -platform = espressif32@5.1.1 -platform_packages = +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 ; or 460800 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 - -D ARDUINO_USB_CDC_ON_BOOT=0 -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_DFU_ON_BOOT=0 -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip - ;-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_DFU_ON_BOOT=0 ; -D ARDUINO_USB_MODE=0 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + ;-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=0 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ;-D WLED_DEBUG lib_deps = ${esp32s3.lib_deps} board_build.partitions = tools/WLED_ESP32_8MB.csv @@ -426,13 +492,14 @@ monitor_filters = esp32_exception_decoder ;board = um_tinys3 ; -> needs workaround from https://github.com/Aircoookie/WLED/pull/2905#issuecomment-1328049860 ;board = esp32s3box ; -> error: 'esp32_adc2gpio' was not declared in this scope board = esp32-s3-devkitc-1 ; -> compiles, but does not support PSRAM -platform = espressif32 @ ~5.2.0 -platform_packages = +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 - -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_MSC_ON_BOOT=0 ; -D ARDUINO_USB_CDC_ON_BOOT=0 + ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + -D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=0 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used lib_deps = ${esp32s3.lib_deps} @@ -504,13 +571,17 @@ build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 lib_deps = ${esp8266.lib_deps} [env:lolin_s2_mini] -platform = espressif32@5.1.1 +platform = ${esp32s2.platform} +platform_packages = ${esp32s2.platform_packages} board = lolin_s2_mini board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv -build_unflags = ${common.build_unflags} +build_unflags = ${common.build_unflags} #-DARDUINO_USB_CDC_ON_BOOT=1 build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=LolinS2 -DBOARD_HAS_PSRAM - -D ARDUINO_USB_CDC_ON_BOOT + -DARDUINO_USB_CDC_ON_BOOT=1 # try disabling and enabling unflag above in case of board-specific issues, will disable Serial + -DARDUINO_USB_MSC_ON_BOOT=0 + -DARDUINO_USB_DFU_ON_BOOT=0 + -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_USE_PSRAM -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 @@ -530,9 +601,28 @@ lib_deps = ${esp32s2.lib_deps} # custom board configurations # ------------------------------------------------------------------------------ +[env:esp32c3dev_2MB] +;; for ESP32-C3 boards with 2MB flash (instead of 4MB). +;; this board need a specific partition file. OTA not possible. +extends = esp32c3 +platform = ${esp32c3.platform} +platform_packages = ${esp32c3.platform_packages} +board = esp32-c3-devkitm-1 +build_flags = ${common.build_flags} ${esp32c3.build_flags} #-D WLED_RELEASE_NAME=ESP32-C3 + -D WLED_WATCHDOG_TIMEOUT=0 + -D WLED_DISABLE_OTA + ; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB + -DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip +build_unflags = ${common.build_unflags} +upload_speed = 115200 +lib_deps = ${esp32c3.lib_deps} +board_build.partitions = tools/WLED_ESP32_2MB_noOTA.csv +board_build.flash_mode = dio + [env:wemos_shield_esp32] board = esp32dev -platform = espressif32@3.2 +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} upload_speed = 460800 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} @@ -557,7 +647,8 @@ board = esp32dev build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39 lib_deps = ${esp32.lib_deps} -platform = espressif32@3.2 +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} board_build.partitions = ${esp32.default_partitions} [env:sp501e] @@ -637,10 +728,10 @@ lib_deps = ${esp8266.lib_deps} # ------------------------------------------------------------------------------ # codm pixel controller board configurations -# codm-controller-0.6 can also be used for the TYWE3S controller +# codm-controller-0_6 can also be used for the TYWE3S controller # ------------------------------------------------------------------------------ -[env:codm-controller-0.6] +[env:codm-controller-0_6] board = esp_wroom_02 platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} @@ -649,7 +740,7 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} lib_deps = ${esp8266.lib_deps} -[env:codm-controller-0.6-rev2] +[env:codm-controller-0_6-rev2] board = esp_wroom_02 platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} @@ -663,7 +754,8 @@ lib_deps = ${esp8266.lib_deps} # ------------------------------------------------------------------------------ [env:elekstube_ips] board = esp32dev -platform = espressif32@3.2 +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED -D USERMOD_RTC diff --git a/requirements.txt b/requirements.txt index e484d7bc9..bc536ed07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,63 +4,55 @@ # # 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 +colorama==0.4.6 # via platformio -h11==0.13.0 +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.31.0 # 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 +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/WLED_ESP32_2MB_noOTA.csv b/tools/WLED_ESP32_2MB_noOTA.csv new file mode 100644 index 000000000..7a1cf15f8 --- /dev/null +++ b/tools/WLED_ESP32_2MB_noOTA.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 20K, +otadata, data, ota, 0xe000, 8K, +app0, app, ota_0, 0x10000, 1536K, +spiffs, data, spiffs, 0x190000, 384K, diff --git a/tools/cdata.js b/tools/cdata.js index bf5a65e84..90619ba67 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -221,6 +221,8 @@ 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'); +writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic'); /* writeChunks( "wled00/data", @@ -388,12 +390,6 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; method: "gzip", filter: "html-minify", }, - { - file: "liveviewws.htm", - name: "PAGE_liveviewws", - method: "gzip", - filter: "html-minify", - }, { file: "liveviewws2D.htm", name: "PAGE_liveviewws2D", diff --git a/usermods/BH1750_v2/usermod_bh1750.h b/usermods/BH1750_v2/usermod_bh1750.h index b65332bc3..5e597d015 100644 --- a/usermods/BH1750_v2/usermod_bh1750.h +++ b/usermods/BH1750_v2/usermod_bh1750.h @@ -8,7 +8,6 @@ #pragma once #include "wled.h" -#include #include // the max frequency to check photoresistor, 10 seconds @@ -56,15 +55,6 @@ private: static const char _offset[]; static const char _HomeAssistantDiscovery[]; - // set the default pins based on the architecture, these get overridden by Usermod menu settings - #ifdef ARDUINO_ARCH_ESP32 // ESP32 boards - #define HW_PIN_SCL 22 - #define HW_PIN_SDA 21 - #else // ESP8266 boards - #define HW_PIN_SCL 5 - #define HW_PIN_SDA 4 - #endif - int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup() bool initDone = false; bool sensorFound = false; @@ -123,14 +113,7 @@ private: public: void setup() { - bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used - PinOwner po = PinOwner::UM_BH1750; // defaults to being pinowner for SCL/SDA pins - PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins - if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - if (!pinManager.allocateMultiplePins(pins, 2, po)) return; - - Wire.begin(ioPin[1], ioPin[0]); - + if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; } sensorFound = lightMeter.begin(); initDone = true; } @@ -190,7 +173,9 @@ public: user = root.createNestedObject(F("u")); JsonArray lux_json = user.createNestedArray(F("Luminance")); - if (!sensorFound) { + if (!enabled) { + lux_json.add(F("disabled")); + } else if (!sensorFound) { // if no sensor lux_json.add(F("BH1750 ")); lux_json.add(F("Not Found")); @@ -216,9 +201,6 @@ public: top[FPSTR(_minReadInterval)] = minReadingInterval; top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; top[FPSTR(_offset)] = offset; - JsonArray io_pin = top.createNestedArray(F("pin")); - for (byte i=0; i<2; i++) io_pin.add(ioPin[i]); - top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page DEBUG_PRINTLN(F("BH1750 config saved.")); } @@ -226,8 +208,6 @@ public: // called before setup() to populate properties from values stored in cfg.json bool readFromConfig(JsonObject &root) { - int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins - // we look for JSON object. JsonObject top = root[FPSTR(_name)]; if (top.isNull()) @@ -244,27 +224,12 @@ public: configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1); - for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]); DEBUG_PRINT(FPSTR(_name)); if (!initDone) { - // first run: reading from cfg.json - for (byte i=0; i<2; i++) ioPin[i] = newPin[i]; DEBUG_PRINTLN(F(" config loaded.")); } else { DEBUG_PRINTLN(F(" config (re)loaded.")); - // changing parameters from settings page - bool pinsChanged = false; - for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed - if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones - PinOwner po = PinOwner::UM_BH1750; - if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins - for (byte i=0; i<2; i++) ioPin[i] = newPin[i]; - setup(); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[F("pin")].isNull(); } return configComplete; diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index e643e62b5..c7d25ec15 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -9,7 +9,6 @@ #include "wled.h" #include -#include #include // BME280 sensor #include // BME280 extended measurements @@ -34,7 +33,6 @@ private: #ifdef ESP8266 //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 #endif - int8_t ioPin[2] = {i2c_scl, i2c_sda}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup() bool initDone = false; // BME280 sensor settings @@ -186,14 +184,8 @@ private: public: void setup() { - bool HW_Pins_Used = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda); // note whether architecture-based hardware SCL/SDA pins used - PinOwner po = PinOwner::UM_BME280; // defaults to being pinowner for SCL/SDA pins - PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins - if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - if (!pinManager.allocateMultiplePins(pins, 2, po)) { sensorType=0; return; } + if (i2c_scl<0 || i2c_sda<0) { enabled = false; sensorType = 0; return; } - Wire.begin(ioPin[1], ioPin[0]); - if (!bme.begin()) { sensorType = 0; @@ -415,9 +407,6 @@ public: top[F("PublishAlways")] = PublishAlways; top[F("UseCelsius")] = UseCelsius; top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery; - JsonArray io_pin = top.createNestedArray(F("pin")); - for (byte i=0; i<2; i++) io_pin.add(ioPin[i]); - top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page DEBUG_PRINTLN(F("BME280 config saved.")); } @@ -427,8 +416,6 @@ public: // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) - int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins - JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { DEBUG_PRINT(F(_name)); @@ -447,27 +434,14 @@ public: configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false); configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true); configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false); - for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]); DEBUG_PRINT(FPSTR(_name)); if (!initDone) { // first run: reading from cfg.json - for (byte i=0; i<2; i++) ioPin[i] = newPin[i]; DEBUG_PRINTLN(F(" config loaded.")); } else { DEBUG_PRINTLN(F(" config (re)loaded.")); // changing parameters from settings page - bool pinsChanged = false; - for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed - if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones - PinOwner po = PinOwner::UM_BME280; - if (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins - for (byte i=0; i<2; i++) ioPin[i] = newPin[i]; - setup(); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[F("pin")].isNull(); } return configComplete; diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index c682cb45d..958bfe526 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 2dc854243..be3d8748b 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.")); } @@ -439,6 +449,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")]; @@ -595,21 +606,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 @@ -647,6 +644,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/EleksTube_IPS/TFTs.h b/usermods/EleksTube_IPS/TFTs.h index e614704f5..030ec23ad 100644 --- a/usermods/EleksTube_IPS/TFTs.h +++ b/usermods/EleksTube_IPS/TFTs.h @@ -133,13 +133,13 @@ private: return false; } - read32(bmpFS); // filesize in bytes - read32(bmpFS); // reserved + (void) read32(bmpFS); // filesize in bytes + (void) read32(bmpFS); // reserved seekOffset = read32(bmpFS); // start of bitmap headerSize = read32(bmpFS); // header size w = read32(bmpFS); // width h = read32(bmpFS); // height - read16(bmpFS); // color planes (must be 1) + (void) read16(bmpFS); // color planes (must be 1) bitDepth = read16(bmpFS); if (read32(bmpFS) != 0 || (bitDepth != 24 && bitDepth != 1 && bitDepth != 4 && bitDepth != 8)) { @@ -151,9 +151,9 @@ private: uint32_t palette[256]; if (bitDepth <= 8) // 1,4,8 bit bitmap: read color palette { - read32(bmpFS); read32(bmpFS); read32(bmpFS); // size, w resolution, h resolution + (void) read32(bmpFS); (void) read32(bmpFS); (void) read32(bmpFS); // size, w resolution, h resolution paletteSize = read32(bmpFS); - if (paletteSize == 0) paletteSize = bitDepth * bitDepth; //if 0, size is 2^bitDepth + if (paletteSize == 0) paletteSize = 1 << bitDepth; //if 0, size is 2^bitDepth bmpFS.seek(14 + headerSize); // start of color palette for (uint16_t i = 0; i < paletteSize; i++) { palette[i] = read32(bmpFS); @@ -198,7 +198,7 @@ private: } b = c; g = c >> 8; r = c >> 16; } - if (dimming != 255) { // only dimm when needed + if (dimming != 255) { // only dim when needed r *= dimming; g *= dimming; b *= dimming; r = r >> 8; g = g >> 8; b = b >> 8; } diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 7903fc9e0..8a4b9a608 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -20,7 +20,7 @@ * This usermod handles PIR sensor states. * The strip will be switched on and the off timer will be resetted when the sensor goes HIGH. * When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off. - * + * Maintained by: @blazoncek * * Usermods allow you to add own functionality to WLED more easily * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality @@ -38,21 +38,21 @@ public: ~PIRsensorSwitch() {} //Enable/Disable the PIR sensor - void EnablePIRsensor(bool en) { enabled = en; } + inline void EnablePIRsensor(bool en) { enabled = en; } // Get PIR sensor enabled/disabled state - bool PIRsensorEnabled() { return enabled; } + inline bool PIRsensorEnabled() { return enabled; } private: byte prevPreset = 0; byte prevPlaylist = 0; - uint32_t offTimerStart = 0; // off timer start time + volatile unsigned long offTimerStart = 0; // off timer start time + volatile bool PIRtriggered = false; // did PIR trigger? byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE byte sensorPinState = LOW; // current PIR sensor pin state bool initDone = false; // status of initialization - bool PIRtriggered = false; unsigned long lastLoop = 0; // configurable parameters @@ -66,6 +66,7 @@ private: // flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR) bool m_offOnly = false; bool m_offMode = offMode; + bool m_override = false; // Home Assistant bool HomeAssistantDiscovery = false; // is HA discovery turned on @@ -81,173 +82,33 @@ private: static const char _offOnly[]; static const char _haDiscovery[]; static const char _notify[]; + static const char _override[]; /** * check if it is daytime * if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime */ - bool isDayTime() { - updateLocalTime(); - uint8_t hr = hour(localTime); - uint8_t mi = minute(localTime); - - if (sunrise && sunset) { - if (hour(sunrise)
hr) { - return true; - } else { - if (hour(sunrise)==hr && minute(sunrise)mi) { - return true; - } - } - } - return false; - } + static bool isDayTime(); /** * switch strip on/off */ - void switchStrip(bool switchOn) - { - if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing - if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing - PIRtriggered = switchOn; - DEBUG_PRINT(F("PIR: strip=")); DEBUG_PRINTLN(switchOn?"on":"off"); - if (switchOn) { - if (m_onPreset) { - if (currentPlaylist>0 && !offMode) { - prevPlaylist = currentPlaylist; - unloadPlaylist(); - } else if (currentPreset>0 && !offMode) { - prevPreset = currentPreset; - } else { - saveTemporaryPreset(); - prevPlaylist = 0; - prevPreset = 255; - } - applyPreset(m_onPreset, NotifyUpdateMode); - return; - } - // preset not assigned - if (bri == 0) { - bri = briLast; - stateUpdated(NotifyUpdateMode); - } - } else { - if (m_offPreset) { - applyPreset(m_offPreset, NotifyUpdateMode); - return; - } else if (prevPlaylist) { - if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, NotifyUpdateMode); - prevPlaylist = 0; - return; - } else if (prevPreset) { - if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, NotifyUpdateMode); } - else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); } - prevPreset = 0; - return; - } - // preset not assigned - if (bri != 0) { - briLast = bri; - bri = 0; - stateUpdated(NotifyUpdateMode); - } - } - } - - void publishMqtt(const char* state) - { - #ifndef WLED_DISABLE_MQTT - //Check if MQTT Connected, otherwise it will crash the 8266 - if (WLED_MQTT_CONNECTED) { - char subuf[64]; - strcpy(subuf, mqttDeviceTopic); - strcat_P(subuf, PSTR("/motion")); - mqtt->publish(subuf, 0, false, state); - } - #endif - } + void switchStrip(bool switchOn); + void publishMqtt(const char* state); // Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. - void publishHomeAssistantAutodiscovery() - { - #ifndef WLED_DISABLE_MQTT - if (WLED_MQTT_CONNECTED) { - StaticJsonDocument<600> doc; - char uid[24], json_str[1024], buf[128]; - - sprintf_P(buf, PSTR("%s Motion"), serverDescription); //max length: 33 + 7 = 40 - doc[F("name")] = buf; - sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40 - doc[F("stat_t")] = buf; - doc[F("pl_on")] = "on"; - doc[F("pl_off")] = "off"; - sprintf_P(uid, PSTR("%s_motion"), escapedMac.c_str()); - doc[F("uniq_id")] = uid; - doc[F("dev_cla")] = F("motion"); - doc[F("exp_aft")] = 1800; - - JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device - device[F("name")] = serverDescription; - device[F("ids")] = String(F("wled-sensor-")) + mqttClientID; - device[F("mf")] = "WLED"; - device[F("mdl")] = F("FOSS"); - device[F("sw")] = versionString; - - sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid); - DEBUG_PRINTLN(buf); - size_t payload_size = serializeJson(doc, json_str); - DEBUG_PRINTLN(json_str); - - mqtt->publish(buf, 0, true, json_str, payload_size); // do we really need to retain? - } - #endif - } + void publishHomeAssistantAutodiscovery(); /** * Read and update PIR sensor state. * Initilize/reset switch off timer */ - bool updatePIRsensorState() - { - bool pinState = digitalRead(PIRsensorPin); - if (pinState != sensorPinState) { - sensorPinState = pinState; // change previous state - - if (sensorPinState == HIGH) { - offTimerStart = 0; - if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); - else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); - publishMqtt("on"); - } else { - // start switch off timer - offTimerStart = millis(); - if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); - } - return true; - } - return false; - } + bool updatePIRsensorState(); /** * switch off the strip if the delay has elapsed */ - bool handleOffTimer() - { - if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) { - offTimerStart = 0; - if (enabled == true) { - if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()) || PIRtriggered)) switchStrip(false); - else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); - publishMqtt("off"); - } - return true; - } - return false; - } + bool handleOffTimer(); public: //Functions called by WLED @@ -256,186 +117,57 @@ public: * setup() is called once at boot. WiFi is not yet connected at this point. * You can use it to initialize variables, sensors or similar. */ - void setup() - { - if (enabled) { - // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { - // PIR Sensor mode INPUT_PULLUP - pinMode(PIRsensorPin, INPUT_PULLUP); - sensorPinState = digitalRead(PIRsensorPin); - } else { - if (PIRsensorPin >= 0) { - DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed.")); - } - PIRsensorPin = -1; // allocation failed - enabled = false; - } - } - initDone = true; - } + void setup(); /** * connected() is called every time the WiFi is (re)connected * Use it to initialize network interfaces */ - void connected() - { - } + //void connected(); /** * onMqttConnect() is called when MQTT connection is established */ - void onMqttConnect(bool sessionPresent) { - if (HomeAssistantDiscovery) { - publishHomeAssistantAutodiscovery(); - } - } + void onMqttConnect(bool sessionPresent); /** * loop() is called continuously. Here you can check for events, read sensors, etc. */ - void loop() - { - // only check sensors 4x/s - if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; - lastLoop = millis(); - - if (!updatePIRsensorState()) { - handleOffTimer(); - } - } + void loop(); /** * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * * Add PIR sensor state and switch off timer duration to jsoninfo */ - void addToJsonInfo(JsonObject &root) - { - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - - JsonArray infoArr = user.createNestedArray(FPSTR(_name)); - - String uiDomString; - if (enabled) { - if (offTimerStart > 0) - { - uiDomString = ""; - unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000; - if (offSeconds >= 3600) - { - uiDomString += (offSeconds / 3600); - uiDomString += F("h "); - offSeconds %= 3600; - } - if (offSeconds >= 60) - { - uiDomString += (offSeconds / 60); - offSeconds %= 60; - } - else if (uiDomString.length() > 0) - { - uiDomString += 0; - } - if (uiDomString.length() > 0) - { - uiDomString += F("min "); - } - uiDomString += (offSeconds); - infoArr.add(uiDomString + F("s")); - } else { - infoArr.add(sensorPinState ? F("sensor on") : F("inactive")); - } - } else { - infoArr.add(F("disabled")); - } - - uiDomString = F(" "); - infoArr.add(uiDomString); - - JsonObject sensor = root[F("sensor")]; - if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); - sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; - } + void addToJsonInfo(JsonObject &root); /** * onStateChanged() is used to detect WLED state change */ - void onStateChange(uint8_t mode) { - if (!initDone) return; - DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart); - if (PIRtriggered && offTimerStart) { - // checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger - DEBUG_PRINTLN(F("PIR: Canceled.")); - offTimerStart = 0; - PIRtriggered = false; - } - } + void onStateChange(uint8_t mode); /** * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ -/* - void addToJsonState(JsonObject &root) - { - } -*/ + //void addToJsonState(JsonObject &root); /** * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - - void readFromJsonState(JsonObject &root) - { - if (!initDone) return; // prevent crash on boot applyPreset() - JsonObject usermod = root[FPSTR(_name)]; - if (!usermod.isNull()) { - if (usermod[FPSTR(_enabled)].is()) { - enabled = usermod[FPSTR(_enabled)].as(); - } - } - } - + void readFromJsonState(JsonObject &root); /** * provide the changeable values */ - void addToConfig(JsonObject &root) - { - JsonObject top = root.createNestedObject(FPSTR(_name)); - top[FPSTR(_enabled)] = enabled; - top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000; - top["pin"] = PIRsensorPin; - top[FPSTR(_onPreset)] = m_onPreset; - top[FPSTR(_offPreset)] = m_offPreset; - top[FPSTR(_nightTime)] = m_nightTimeOnly; - top[FPSTR(_mqttOnly)] = m_mqttOnly; - top[FPSTR(_offOnly)] = m_offOnly; - top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery; - top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY); - DEBUG_PRINTLN(F("PIR config saved.")); - } + void addToConfig(JsonObject &root); - void appendConfigData() - { - oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field - } + /** + * provide UI information and allow extending UI options + */ + void appendConfigData(); /** * restore the changeable values @@ -443,72 +175,13 @@ public: * * The function should return true if configuration was successfully loaded or false if there was no configuration. */ - bool readFromConfig(JsonObject &root) - { - bool oldEnabled = enabled; - int8_t oldPin = PIRsensorPin; - - DEBUG_PRINT(FPSTR(_name)); - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - - PIRsensorPin = top["pin"] | PIRsensorPin; - - enabled = top[FPSTR(_enabled)] | enabled; - - m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000; - - m_onPreset = top[FPSTR(_onPreset)] | m_onPreset; - m_onPreset = max(0,min(250,(int)m_onPreset)); - m_offPreset = top[FPSTR(_offPreset)] | m_offPreset; - m_offPreset = max(0,min(250,(int)m_offPreset)); - - m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly; - m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly; - m_offOnly = top[FPSTR(_offOnly)] | m_offOnly; - HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery; - - NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY; - - if (!initDone) { - // reading config prior to setup() - DEBUG_PRINTLN(F(" config loaded.")); - } else { - if (oldPin != PIRsensorPin || oldEnabled != enabled) { - // check if pin is OK - if (oldPin != PIRsensorPin && oldPin >= 0) { - // if we are changing pin in settings page - // deallocate old pin - pinManager.deallocatePin(oldPin, PinOwner::UM_PIR); - if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { - pinMode(PIRsensorPin, INPUT_PULLUP); - } else { - // allocation failed - PIRsensorPin = -1; - enabled = false; - } - } - if (enabled) { - sensorPinState = digitalRead(PIRsensorPin); - } - } - DEBUG_PRINTLN(F(" config (re)loaded.")); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_haDiscovery)].isNull(); - } + bool readFromConfig(JsonObject &root); /** * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). * This could be used in the future for the system to determine whether your usermod is installed. */ - uint16_t getId() - { - return USERMOD_ID_PIRSWITCH; - } + uint16_t getId() { return USERMOD_ID_PIRSWITCH; } }; // strings to reduce flash memory usage (used more than twice) @@ -522,3 +195,359 @@ const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only"; const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only"; const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery"; const char PIRsensorSwitch::_notify[] PROGMEM = "notifications"; +const char PIRsensorSwitch::_override[] PROGMEM = "override"; + +bool PIRsensorSwitch::isDayTime() { + updateLocalTime(); + uint8_t hr = hour(localTime); + uint8_t mi = minute(localTime); + + if (sunrise && sunset) { + if (hour(sunrise)
hr) { + return true; + } else { + if (hour(sunrise)==hr && minute(sunrise)mi) { + return true; + } + } + } + return false; +} + +void PIRsensorSwitch::switchStrip(bool switchOn) +{ + if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing + if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing + PIRtriggered = switchOn; + DEBUG_PRINT(F("PIR: strip=")); DEBUG_PRINTLN(switchOn?"on":"off"); + if (switchOn) { + if (m_onPreset) { + if (currentPlaylist>0 && !offMode) { + prevPlaylist = currentPlaylist; + unloadPlaylist(); + } else if (currentPreset>0 && !offMode) { + prevPreset = currentPreset; + } else { + saveTemporaryPreset(); + prevPlaylist = 0; + prevPreset = 255; + } + applyPreset(m_onPreset, NotifyUpdateMode); + return; + } + // preset not assigned + if (bri == 0) { + bri = briLast; + stateUpdated(NotifyUpdateMode); + } + } else { + if (m_offPreset) { + applyPreset(m_offPreset, NotifyUpdateMode); + return; + } else if (prevPlaylist) { + if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, NotifyUpdateMode); + prevPlaylist = 0; + return; + } else if (prevPreset) { + if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, NotifyUpdateMode); } + else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); } + prevPreset = 0; + return; + } + // preset not assigned + if (bri != 0) { + briLast = bri; + bri = 0; + stateUpdated(NotifyUpdateMode); + } + } +} + +void PIRsensorSwitch::publishMqtt(const char* state) +{ +#ifndef WLED_DISABLE_MQTT + //Check if MQTT Connected, otherwise it will crash the 8266 + if (WLED_MQTT_CONNECTED) { + char buf[64]; + sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40 + mqtt->publish(buf, 0, false, state); + } +#endif +} + +void PIRsensorSwitch::publishHomeAssistantAutodiscovery() +{ +#ifndef WLED_DISABLE_MQTT + if (WLED_MQTT_CONNECTED) { + StaticJsonDocument<600> doc; + char uid[24], json_str[1024], buf[128]; + + sprintf_P(buf, PSTR("%s Motion"), serverDescription); //max length: 33 + 7 = 40 + doc[F("name")] = buf; + sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40 + doc[F("stat_t")] = buf; + doc[F("pl_on")] = "on"; + doc[F("pl_off")] = "off"; + sprintf_P(uid, PSTR("%s_motion"), escapedMac.c_str()); + doc[F("uniq_id")] = uid; + doc[F("dev_cla")] = F("motion"); + doc[F("exp_aft")] = 1800; + + JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device + device[F("name")] = serverDescription; + device[F("ids")] = String(F("wled-sensor-")) + mqttClientID; + device[F("mf")] = "WLED"; + device[F("mdl")] = F("FOSS"); + device[F("sw")] = versionString; + + sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid); + DEBUG_PRINTLN(buf); + size_t payload_size = serializeJson(doc, json_str); + DEBUG_PRINTLN(json_str); + + mqtt->publish(buf, 0, true, json_str, payload_size); // do we really need to retain? + } +#endif +} + +bool PIRsensorSwitch::updatePIRsensorState() +{ + bool pinState = digitalRead(PIRsensorPin); + if (pinState != sensorPinState) { + sensorPinState = pinState; // change previous state + + if (sensorPinState == HIGH) { + offTimerStart = 0; + if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); + else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); + publishMqtt("on"); + } else { + // start switch off timer + offTimerStart = millis(); + if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); + } + return true; + } + return false; +} + +bool PIRsensorSwitch::handleOffTimer() +{ + if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) { + offTimerStart = 0; + if (enabled == true) { + if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()) || PIRtriggered)) switchStrip(false); + else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); + publishMqtt("off"); + } + return true; + } + return false; +} + +//Functions called by WLED + +void PIRsensorSwitch::setup() +{ + if (enabled) { + // pin retrieved from cfg.json (readFromConfig()) prior to running setup() + if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { + // PIR Sensor mode INPUT_PULLUP + pinMode(PIRsensorPin, INPUT_PULLUP); + sensorPinState = digitalRead(PIRsensorPin); + } else { + if (PIRsensorPin >= 0) { + DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed.")); + } + PIRsensorPin = -1; // allocation failed + enabled = false; + } + } + initDone = true; +} + +void PIRsensorSwitch::onMqttConnect(bool sessionPresent) +{ + if (HomeAssistantDiscovery) { + publishHomeAssistantAutodiscovery(); + } +} + +void PIRsensorSwitch::loop() +{ + // only check sensors 4x/s + if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; + lastLoop = millis(); + + if (!updatePIRsensorState()) { + handleOffTimer(); + } +} + +void PIRsensorSwitch::addToJsonInfo(JsonObject &root) +{ + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + + String uiDomString; + if (enabled) { + if (offTimerStart > 0) + { + uiDomString = ""; + unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000; + if (offSeconds >= 3600) + { + uiDomString += (offSeconds / 3600); + uiDomString += F("h "); + offSeconds %= 3600; + } + if (offSeconds >= 60) + { + uiDomString += (offSeconds / 60); + offSeconds %= 60; + } + else if (uiDomString.length() > 0) + { + uiDomString += 0; + } + if (uiDomString.length() > 0) + { + uiDomString += F("min "); + } + uiDomString += (offSeconds); + infoArr.add(uiDomString + F("s")); + } else { + infoArr.add(sensorPinState ? F("sensor on") : F("inactive")); + } + } else { + infoArr.add(F("disabled")); + } + + uiDomString = F(" "); + infoArr.add(uiDomString); + + JsonObject sensor = root[F("sensor")]; + if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); + sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; +} + +void PIRsensorSwitch::onStateChange(uint8_t mode) { + if (!initDone) return; + DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart); + if (m_override && PIRtriggered && offTimerStart) { // debounce + // checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger + DEBUG_PRINTLN(F("PIR: Canceled.")); + offTimerStart = 0; + PIRtriggered = false; + } +} + +void PIRsensorSwitch::readFromJsonState(JsonObject &root) +{ + if (!initDone) return; // prevent crash on boot applyPreset() + JsonObject usermod = root[FPSTR(_name)]; + if (!usermod.isNull()) { + if (usermod[FPSTR(_enabled)].is()) { + enabled = usermod[FPSTR(_enabled)].as(); + } + } +} + +void PIRsensorSwitch::addToConfig(JsonObject &root) +{ + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000; + top["pin"] = PIRsensorPin; + top[FPSTR(_onPreset)] = m_onPreset; + top[FPSTR(_offPreset)] = m_offPreset; + top[FPSTR(_nightTime)] = m_nightTimeOnly; + top[FPSTR(_mqttOnly)] = m_mqttOnly; + top[FPSTR(_offOnly)] = m_offOnly; + top[FPSTR(_override)] = m_override; + top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery; + top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY); + DEBUG_PRINTLN(F("PIR config saved.")); +} + +void PIRsensorSwitch::appendConfigData() +{ + oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field +} + +bool PIRsensorSwitch::readFromConfig(JsonObject &root) +{ + bool oldEnabled = enabled; + int8_t oldPin = PIRsensorPin; + + DEBUG_PRINT(FPSTR(_name)); + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + PIRsensorPin = top["pin"] | PIRsensorPin; + + enabled = top[FPSTR(_enabled)] | enabled; + + m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000; + + m_onPreset = top[FPSTR(_onPreset)] | m_onPreset; + m_onPreset = max(0,min(250,(int)m_onPreset)); + m_offPreset = top[FPSTR(_offPreset)] | m_offPreset; + m_offPreset = max(0,min(250,(int)m_offPreset)); + + m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly; + m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly; + m_offOnly = top[FPSTR(_offOnly)] | m_offOnly; + m_override = top[FPSTR(_override)] | m_override; + HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery; + + NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY; + + if (!initDone) { + // reading config prior to setup() + DEBUG_PRINTLN(F(" config loaded.")); + } else { + if (oldPin != PIRsensorPin || oldEnabled != enabled) { + // check if pin is OK + if (oldPin != PIRsensorPin && oldPin >= 0) { + // if we are changing pin in settings page + // deallocate old pin + pinManager.deallocatePin(oldPin, PinOwner::UM_PIR); + if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { + pinMode(PIRsensorPin, INPUT_PULLUP); + } else { + // allocation failed + PIRsensorPin = -1; + enabled = false; + } + } + if (enabled) { + sensorPinState = digitalRead(PIRsensorPin); + } + } + DEBUG_PRINTLN(F(" config (re)loaded.")); + } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !top[FPSTR(_override)].isNull(); +} diff --git a/usermods/RTC/usermod_rtc.h b/usermods/RTC/usermod_rtc.h index fd9a40544..42965e3af 100644 --- a/usermods/RTC/usermod_rtc.h +++ b/usermods/RTC/usermod_rtc.h @@ -12,8 +12,7 @@ class RTCUsermod : public Usermod { public: void setup() { - PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { disabled = true; return; } + if (i2c_scl<0 || i2c_sda<0) { disabled = true; return; } RTC.begin(); time_t rtcTime = RTC.get(); if (rtcTime) { @@ -25,8 +24,8 @@ class RTCUsermod : public Usermod { } void loop() { - if (strip.isUpdating()) return; - if (!disabled && toki.isTick()) { + if (disabled || strip.isUpdating()) return; + if (toki.isTick()) { time_t t = toki.second(); if (t != RTC.get()) RTC.set(t); //set RTC to NTP/UI-provided value } diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 93700c918..144cccbfa 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -17,12 +17,6 @@ #ifndef TFT_HEIGHT #error Please define TFT_HEIGHT #endif - #ifndef TFT_MOSI - #error Please define TFT_MOSI - #endif - #ifndef TFT_SCLK - #error Please define TFT_SCLK - #endif #ifndef TFT_DC #error Please define TFT_DC #endif @@ -140,8 +134,14 @@ class St7789DisplayUsermod : public Usermod { */ void setup() { - PinManagerPinType pins[] = { { TFT_MOSI, true }, { TFT_MISO, false}, { TFT_SCLK, true }, { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; - if (!pinManager.allocateMultiplePins(pins, 7, PinOwner::UM_FourLineDisplay)) { enabled = false; return; } + PinManagerPinType spiPins[] = { { spi_mosi, true }, { spi_miso, false}, { spi_sclk, true } }; + if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; } + PinManagerPinType displayPins[] = { { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; + if (!pinManager.allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { + pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); + enabled = false; + return; + } tft.init(); tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip. @@ -365,9 +365,6 @@ class St7789DisplayUsermod : public Usermod { { JsonObject top = root.createNestedObject("ST7789"); JsonArray pins = top.createNestedArray("pin"); - pins.add(TFT_MOSI); - pins.add(TFT_MISO); - pins.add(TFT_SCLK); pins.add(TFT_CS); pins.add(TFT_DC); pins.add(TFT_RST); @@ -376,6 +373,13 @@ class St7789DisplayUsermod : public Usermod { } + void appendConfigData() { + oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');")); + oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');")); + oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');")); + oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI BL');")); + } + /* * readFromConfig() can be used to read back the custom settings you added with addToConfig(). * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) diff --git a/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h b/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h index 4a42a7d5a..bdf784844 100644 --- a/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h +++ b/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h @@ -13,14 +13,6 @@ Adafruit_Si7021 si7021; -#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards -uint8_t SCL_PIN = 22; -uint8_t SDA_PIN = 21; -#else //ESP8266 boards -uint8_t SCL_PIN = 5; -uint8_t SDA_PIN = 4; -#endif - class Si7021_MQTT_HA : public Usermod { private: @@ -184,7 +176,6 @@ class Si7021_MQTT_HA : public Usermod { if (enabled) { Serial.println("Si7021_MQTT_HA: Starting!"); - Wire.begin(SDA_PIN, SCL_PIN); Serial.println("Si7021_MQTT_HA: Initializing sensors.. "); _initializeSensor(); } diff --git a/usermods/Temperature/platformio_override.ini b/usermods/Temperature/platformio_override.ini index d9e3fbace..0e354da9e 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 c917461ab..2657c6c8f 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 b55076c73..a15baf878 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -57,343 +57,372 @@ 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: - void setup() { - int retries = 10; - sensorFound = 0; - temperature = -127.0f; // default to -127, DS18B20 only goes down to -50C - if (enabled) { - // config says we are enabled - DEBUG_PRINTLN(F("Allocating temperature pin...")); - // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { - oneWire = new OneWire(temperaturePin); - if (oneWire->reset()) { - 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) { - DEBUG_PRINTLN(F("Temperature pin allocation failed.")); - } - temperaturePin = -1; // allocation failed - } - } - lastMeasurement = millis() - readingInterval + 10000; - initDone = true; - } - - 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) { - // we add JSON object: {"Temperature": {"pin": 0, "degC": true}} - JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname - top[FPSTR(_enabled)] = enabled; - 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("%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); - /** - * 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) { - // we look for JSON object: {"Temperature": {"pin": 0, "degC": true}} - int8_t newTemperaturePin = temperaturePin; - DEBUG_PRINT(FPSTR(_name)); + sprintf_P(buf, PSTR("homeassistant/sensor/%s/config"), escapedMac.c_str()); + mqtt->publish(buf, 0, true, json_str, payload_size); + HApublished = true; +} +#endif - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - - enabled = top[FPSTR(_enabled)] | enabled; - 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("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 - } +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}} + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + top[FPSTR(_enabled)] = enabled; + 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}} + int8_t newTemperaturePin = temperaturePin; + DEBUG_PRINT(FPSTR(_name)); + + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + enabled = top[FPSTR(_enabled)] | enabled; + 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("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::_name[] PROGMEM = "Temperature"; diff --git a/usermods/Temperature/usermods_list.cpp b/usermods/Temperature/usermods_list.cpp deleted file mode 100644 index 50dd7816b..000000000 --- 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/VL53L0X_gestures/usermod_vl53l0x_gestures.h b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h index 39c2c3ef7..fe6b958f5 100644 --- a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h +++ b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h @@ -50,9 +50,7 @@ class UsermodVL53L0XGestures : public Usermod { public: void setup() { - PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } - Wire.begin(); + if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; } sensor.setTimeout(150); if (!sensor.init()) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 09cc931c9..837668ee3 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -569,16 +569,6 @@ class AudioReactive : public Usermod { #else int8_t i2sckPin = I2S_CKPIN; #endif - #ifndef ES7243_SDAPIN - int8_t sdaPin = -1; - #else - int8_t sdaPin = ES7243_SDAPIN; - #endif - #ifndef ES7243_SCLPIN - int8_t sclPin = -1; - #else - int8_t sclPin = ES7243_SCLPIN; - #endif #ifndef MCLK_PIN int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/ #else @@ -1136,7 +1126,7 @@ class AudioReactive : public Usermod { DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); delay(100); - if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; case 3: DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); @@ -1657,8 +1647,6 @@ class AudioReactive : public Usermod { pinArray.add(i2swsPin); pinArray.add(i2sckPin); pinArray.add(mclkPin); - pinArray.add(sdaPin); - pinArray.add(sclPin); JsonObject cfg = top.createNestedObject("config"); cfg[F("squelch")] = soundSquelch; @@ -1719,8 +1707,6 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][4], sdaPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][5], sclPin); configComplete &= getJsonValue(top["config"][F("squelch")], soundSquelch); configComplete &= getJsonValue(top["config"][F("gain")], sampleGain); @@ -1784,8 +1770,6 @@ class AudioReactive : public Usermod { #else oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); #endif - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',4,'','I2C SDA');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',5,'','I2C SCL');")); } diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 16bbbb658..4aa057166 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -1,6 +1,5 @@ #pragma once -#include #include "wled.h" #include #include @@ -383,21 +382,12 @@ class I2SSource : public AudioSource { */ class ES7243 : public I2SSource { private: - // I2C initialization functions for ES7243 - void _es7243I2cBegin() { - bool i2c_initialized = Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U); - if (i2c_initialized == false) { - DEBUGSR_PRINTLN(F("AR: ES7243 failed to initialize I2C bus driver.")); - } - } void _es7243I2cWrite(uint8_t reg, uint8_t val) { -#ifndef ES7243_ADDR - Wire.beginTransmission(0x13); - #define ES7243_ADDR 0x13 // default address -#else + #ifndef ES7243_ADDR + #define ES7243_ADDR 0x13 // default address + #endif Wire.beginTransmission(ES7243_ADDR); -#endif Wire.write((uint8_t)reg); Wire.write((uint8_t)val); uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK @@ -407,7 +397,6 @@ class ES7243 : public I2SSource { } void _es7243InitAdc() { - _es7243I2cBegin(); _es7243I2cWrite(0x00, 0x01); _es7243I2cWrite(0x06, 0x00); _es7243I2cWrite(0x05, 0x1B); @@ -422,44 +411,20 @@ public: _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; }; - void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { - // check that pins are valid - if ((sdaPin < 0) || (sclPin < 0)) { - DEBUGSR_PRINTF("\nAR: invalid ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin); - return; - } - + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { if ((i2sckPin < 0) || (mclkPin < 0)) { DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); return; } - // Reserve SDA and SCL pins of the I2C interface - PinManagerPinType es7243Pins[2] = { { sdaPin, true }, { sclPin, true } }; - if (!pinManager.allocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C)) { - pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C); - DEBUGSR_PRINTF("\nAR: Failed to allocate ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin); - return; - } - - pin_ES7243_SDA = sdaPin; - pin_ES7243_SCL = sclPin; - // First route mclk, then configure ADC over I2C, then configure I2S _es7243InitAdc(); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); } void deinitialize() { - // Release SDA and SCL pins of the I2C interface - PinManagerPinType es7243Pins[2] = { { pin_ES7243_SDA, true }, { pin_ES7243_SCL, true } }; - pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C); I2SSource::deinitialize(); } - - private: - int8_t pin_ES7243_SDA; - int8_t pin_ES7243_SCL; }; diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h index b4dc01a4d..748ddf1a6 100644 --- a/usermods/mpu6050_imu/usermod_mpu6050_imu.h +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -85,12 +85,9 @@ class MPU6050Driver : public Usermod { * setup() is called once at boot. WiFi is not yet connected at this point. */ void setup() { - PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } - // join I2C bus (I2Cdev library doesn't do this automatically) + if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; } #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE - Wire.begin(); - Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties + Wire.setClock(400000U); // 400kHz I2C clock. Comment this line if having compilation difficulties #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE Fastwire::setup(400, true); #endif diff --git a/usermods/multi_relay/readme.md b/usermods/multi_relay/readme.md index 2906b8606..71a540701 100644 --- a/usermods/multi_relay/readme.md +++ b/usermods/multi_relay/readme.md @@ -1,6 +1,9 @@ # Multi Relay This usermod-v2 modification allows the connection of multiple relays, each with individual delay and on/off mode. +Usermod supports PCF8574 I2C port expander to reduce GPIO use. +PCF8574 supports 8 outputs and each output corresponds to a relay in WLED (relay 0 = port 0, etc). I you are using more than 8 relays with multiple PCF8574 make sure their addresses are set conscutively (e.g. 0x20 and 0x21). You can set address of first expander in settings. +(**NOTE:** Will require Wire library and global I2C pins defined.) ## HTTP API All responses are returned in JSON format. @@ -81,13 +84,15 @@ void registerUsermods() Usermod can be configured via the Usermods settings page. * `enabled` - enable/disable usermod +* `use-PCF8574` - use PCF8574 port expander instead of GPIO pins +* `first-PCF8574` - I2C address of first expander (WARNING: enter *decimal* value) +* `broadcast`- time in seconds between MQTT relay-state broadcasts +* `HA-discovery`- enable Home Assistant auto discovery * `pin` - ESP GPIO pin the relay is connected to (can be configured at compile time `-D MULTI_RELAY_PINS=xx,xx,...`) * `delay-s` - delay in seconds after on/off command is received * `active-high` - assign high/low activation of relay (can be used to reverse relay states) * `external` - if enabled, WLED does not control relay, it can only be triggered by an external command (MQTT, HTTP, JSON or button) * `button` - button (from LED Settings) that controls this relay -* `broadcast`- time in seconds between MQTT relay-state broadcasts -* `HA-discovery`- enable Home Assistant auto discovery If there is no MultiRelay section, just save current configuration and re-open Usermods settings page. @@ -100,3 +105,6 @@ Have fun - @blazoncek 2021-11 * Added information about dynamic configuration options * Added button support. + +2023-05 +* Added support for PCF8574 I2C port expander (multiple) \ No newline at end of file diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index de68e11b0..7234df908 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -4,10 +4,19 @@ #ifndef MULTI_RELAY_MAX_RELAYS #define MULTI_RELAY_MAX_RELAYS 4 +#else + #if MULTI_RELAY_MAX_RELAYS>8 + #undef MULTI_RELAY_MAX_RELAYS + #define MULTI_RELAY_MAX_RELAYS 8 + #warning Maximum relays set to 8 + #endif #endif #ifndef MULTI_RELAY_PINS #define MULTI_RELAY_PINS -1 + #define MULTI_RELAY_ENABLED false +#else + #define MULTI_RELAY_ENABLED true #endif #define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing) @@ -15,21 +24,37 @@ #define ON true #define OFF false +#ifndef USERMOD_USE_PCF8574 + #undef USE_PCF8574 + #define USE_PCF8574 false +#else + #undef USE_PCF8574 + #define USE_PCF8574 true +#endif + +#ifndef PCF8574_ADDRESS + #define PCF8574_ADDRESS 0x20 // some may start at 0x38 +#endif + /* * This usermod handles multiple relay outputs. * These outputs complement built-in relay output in a way that the activation can be delayed. * They can also activate/deactivate in reverse logic independently. + * + * Written and maintained by @blazoncek */ typedef struct relay_t { int8_t pin; - bool active; - bool mode; - bool state; - bool external; - uint16_t delay; - int8_t button; + struct { // reduces memory footprint + bool active : 1; // is the relay waiting to be switched + bool invert : 1; // does On mean 1 or 0 + bool state : 1; // 1 relay is On, 0 relay is Off + bool external : 1; // is the relay externally controlled + int8_t button : 4; // which button triggers relay + }; + uint16_t delay; // amount of ms to wait after it is activated } Relay; @@ -37,22 +62,17 @@ class MultiRelay : public Usermod { private: // array of relays - Relay _relay[MULTI_RELAY_MAX_RELAYS]; + Relay _relay[MULTI_RELAY_MAX_RELAYS]; - // switch timer start time - uint32_t _switchTimerStart = 0; - // old brightness - bool _oldMode; - - // usermod enabled - bool enabled = false; // needs to be configured (no default config) - // status of initialisation - bool initDone = false; - - bool HAautodiscovery = false; - - uint16_t periodicBroadcastSec = 60; - unsigned long lastBroadcast = 0; + uint32_t _switchTimerStart; // switch timer start time + bool _oldMode; // old brightness + bool enabled; // usermod enabled + bool initDone; // status of initialisation + bool usePcf8574; + uint8_t addrPcf8574; + bool HAautodiscovery; + uint16_t periodicBroadcastSec; + unsigned long lastBroadcast; // strings to reduce flash memory usage (used more than twice) static const char _name[]; @@ -64,160 +84,53 @@ class MultiRelay : public Usermod { static const char _button[]; static const char _broadcast[]; static const char _HAautodiscovery[]; + static const char _pcf8574[]; + static const char _pcfAddress[]; - void publishMqtt(int relay) { + void handleOffTimer(); + void InitHtmlAPIHandle(); + int getValue(String data, char separator, int index); + uint8_t getActiveRelayCount(); + + byte IOexpanderWrite(byte address, byte _data); + byte IOexpanderRead(int address); + + void publishMqtt(int relay); #ifndef WLED_DISABLE_MQTT - //Check if MQTT Connected, otherwise it will crash the 8266 - if (WLED_MQTT_CONNECTED){ - char subuf[64]; - sprintf_P(subuf, PSTR("%s/relay/%d"), mqttDeviceTopic, relay); - mqtt->publish(subuf, 0, false, _relay[relay].state ? "on" : "off"); - } + void publishHomeAssistantAutodiscovery(); #endif - } - - /** - * switch off the strip if the delay has elapsed - */ - void handleOffTimer() { - unsigned long now = millis(); - bool activeRelays = false; - for (int i=0; i 0 && now - _switchTimerStart > (_relay[i].delay*1000)) { - if (!_relay[i].external) toggleRelay(i); - _relay[i].active = false; - } else if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) { - if (_relay[i].pin>=0) publishMqtt(i); - } - activeRelays = activeRelays || _relay[i].active; - } - if (!activeRelays) _switchTimerStart = 0; - if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) lastBroadcast = now; - } - - /** - * HTTP API handler - * borrowed from: - * https://github.com/gsieben/WLED/blob/master/usermods/GeoGab-Relays/usermod_GeoGab.h - */ - #define GEOGABVERSION "0.1.3" - void InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer - DEBUG_PRINTLN(F("Relays: Initialize HTML API")); - - server.on("/relays", HTTP_GET, [this](AsyncWebServerRequest *request) { - DEBUG_PRINTLN("Relays: HTML API"); - String janswer; - String error = ""; - //int params = request->params(); - janswer = F("{\"NoOfRelays\":"); - janswer += String(MULTI_RELAY_MAX_RELAYS) + ","; - - if (getActiveRelayCount()) { - // Commands - if(request->hasParam("switch")) { - /**** Switch ****/ - AsyncWebParameter* p = request->getParam("switch"); - // Get Values - for (int i=0; ivalue(), ',', i); - if (value==-1) { - error = F("There must be as many arguments as relays"); - } else { - // Switch - if (_relay[i].external) switchRelay(i, (bool)value); - } - } - } else if(request->hasParam("toggle")) { - /**** Toggle ****/ - AsyncWebParameter* p = request->getParam("toggle"); - // Get Values - for (int i=0;ivalue(), ',', i); - if (value==-1) { - error = F("There must be as many arguments as relays"); - } else { - // Toggle - if (value && _relay[i].external) toggleRelay(i); - } - } - } else { - error = F("No valid command found"); - } - } else { - error = F("No active relays"); - } - - // Status response - char sbuf[16]; - for (int i=0; isend(200, "application/json", janswer); - }); - } - - int getValue(String data, char separator, int index) { - int found = 0; - int strIndex[] = {0, -1}; - int maxIndex = data.length()-1; - - for(int i=0; i<=maxIndex && found<=index; i++){ - if(data.charAt(i)==separator || i==maxIndex){ - found++; - strIndex[0] = strIndex[1]+1; - strIndex[1] = (i == maxIndex) ? i+1 : i; - } - } - return found>index ? data.substring(strIndex[0], strIndex[1]).toInt() : -1; - } public: /** * constructor */ - MultiRelay() { - const int8_t defPins[] = {MULTI_RELAY_PINS}; - for (size_t i=0; i=MULTI_RELAY_MAX_RELAYS || _relay[relay].pin<0) return; - _relay[relay].state = mode; - pinMode(_relay[relay].pin, OUTPUT); - digitalWrite(_relay[relay].pin, mode ? !_relay[relay].mode : _relay[relay].mode); - publishMqtt(relay); - } + void switchRelay(uint8_t relay, bool mode); /** * toggle relay @@ -226,354 +139,58 @@ class MultiRelay : public Usermod { switchRelay(relay, !_relay[relay].state); } - uint8_t getActiveRelayCount() { - uint8_t count = 0; - for (int i=0; i=0) count++; - return count; - } - - //Functions called by WLED - -#ifndef WLED_DISABLE_MQTT - /** - * handling of MQTT message - * topic only contains stripped topic (part after /wled/MAC) - * topic should look like: /relay/X/command; where X is relay number, 0 based - */ - bool onMqttMessage(char* topic, char* payload) { - if (strlen(topic) > 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, PSTR("/command"), 8) == 0) { - uint8_t relay = strtoul(topic+7, NULL, 10); - if (relaysubscribe(subuf, 0); - if (HAautodiscovery) publishHomeAssistantAutodiscovery(); - for (int i=0; i= 0 && _relay[i].external) { - StaticJsonDocument<1024> json; - sprintf_P(buf, PSTR("%s Switch %d"), serverDescription, i); //max length: 33 + 8 + 3 = 44 - json[F("name")] = buf; - - sprintf_P(buf, PSTR("%s/relay/%d"), mqttDeviceTopic, i); //max length: 33 + 7 + 3 = 43 - json["~"] = buf; - strcat_P(buf, PSTR("/command")); - mqtt->subscribe(buf, 0); - - json[F("stat_t")] = "~"; - json[F("cmd_t")] = F("~/command"); - json[F("pl_off")] = "off"; - json[F("pl_on")] = "on"; - json[F("uniq_id")] = uid; - - strcpy(buf, mqttDeviceTopic); //max length: 33 + 7 = 40 - strcat_P(buf, PSTR("/status")); - json[F("avty_t")] = buf; - json[F("pl_avail")] = F("online"); - json[F("pl_not_avail")] = F("offline"); - //TODO: dev - payload_size = serializeJson(json, json_str); - } else { - //Unpublish disabled or internal relays - json_str[0] = 0; - payload_size = 0; - } - sprintf_P(buf, PSTR("homeassistant/switch/%s/config"), uid); - mqtt->publish(buf, 0, true, json_str, payload_size); - } - } -#endif - /** * setup() is called once at boot. WiFi is not yet connected at this point. * You can use it to initialize variables, sensors or similar. */ - void setup() { - // pins retrieved from cfg.json (readFromConfig()) prior to running setup() - for (int i=0; i=0 && !_relay[i].external) _relay[i].active = true; - } - } - - handleOffTimer(); - } +#ifndef WLED_DISABLE_MQTT + bool onMqttMessage(char* topic, char* payload); + void onMqttConnect(bool sessionPresent); +#endif /** * handleButton() can be used to override default button behaviour. Returning true * will prevent button working in a default way. * Replicating button.cpp */ - bool handleButton(uint8_t b) { - yield(); - if (!enabled - || buttonType[b] == BTN_TYPE_NONE - || buttonType[b] == BTN_TYPE_RESERVED - || buttonType[b] == BTN_TYPE_PIR_SENSOR - || buttonType[b] == BTN_TYPE_ANALOG - || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { - return false; - } + bool handleButton(uint8_t b); - bool handled = false; - for (int i=0; i WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) - for (int i=0; i 600) { //long press - //longPressAction(b); //not exposed - //handled = false; //use if you want to pass to default behaviour - buttonLongPressed[b] = true; - } - - } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released - - long dur = now - buttonPressedTime[b]; - if (dur < WLED_DEBOUNCE_THRESHOLD) { - buttonPressedBefore[b] = false; - return handled; - } //too short "press", debounce - bool doublePress = buttonWaitTime[b]; //did we have short press before? - buttonWaitTime[b] = 0; - - if (!buttonLongPressed[b]) { //short press - // if this is second release within 350ms it is a double press (buttonWaitTime!=0) - if (doublePress) { - //doublePressAction(b); //not exposed - //handled = false; //use if you want to pass to default behaviour - } else { - buttonWaitTime[b] = now; - } - } - buttonPressedBefore[b] = false; - buttonLongPressed[b] = false; - } - // if 350ms elapsed since last press/release it is a short press - if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) { - buttonWaitTime[b] = 0; - //shortPressAction(b); //not exposed - for (int i=0; i"); - uiDomString += F(""); - infoArr.add(uiDomString); - } - } - } + void addToJsonInfo(JsonObject &root); /** * 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) { - if (!initDone || !enabled) return; // prevent crash on boot applyPreset() - JsonObject multiRelay = root[FPSTR(_name)]; - if (multiRelay.isNull()) { - multiRelay = root.createNestedObject(FPSTR(_name)); - } - #if MULTI_RELAY_MAX_RELAYS > 1 - JsonArray rel_arr = multiRelay.createNestedArray(F("relays")); - for (int i=0; i() && usermod[FPSTR(_relay_str)].as()>=0) { - int rly = usermod[FPSTR(_relay_str)].as(); - if (usermod["on"].is()) { - switchRelay(rly, usermod["on"].as()); - } else if (usermod["on"].is() && usermod["on"].as()[0] == 't') { - toggleRelay(rly); - } - } - } else if (root[FPSTR(_name)].is()) { - JsonArray relays = root[FPSTR(_name)].as(); - for (JsonVariant r : relays) { - if (r[FPSTR(_relay_str)].is() && r[FPSTR(_relay_str)].as()>=0) { - int rly = r[FPSTR(_relay_str)].as(); - if (r["on"].is()) { - switchRelay(rly, r["on"].as()); - } else if (r["on"].is() && r["on"].as()[0] == 't') { - toggleRelay(rly); - } - } - } - } - } + void readFromJsonState(JsonObject &root); /** * provide the changeable values */ - void addToConfig(JsonObject &root) { - JsonObject top = root.createNestedObject(FPSTR(_name)); + void addToConfig(JsonObject &root); - top[FPSTR(_enabled)] = enabled; - top[FPSTR(_broadcast)] = periodicBroadcastSec; - for (int i=0; i=0) { - pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay); - } - // allocate new pins - for (int i=0; i=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) { - if (!_relay[i].external) { - _relay[i].state = !offMode; - switchRelay(i, _relay[i].state); - _oldMode = offMode; - } - } else { - _relay[i].pin = -1; - } - _relay[i].active = false; - } - DEBUG_PRINTLN(F(" config (re)loaded.")); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_HAautodiscovery)].isNull(); - } - - /** - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - * This could be used in the future for the system to determine whether your usermod is installed. - */ - uint16_t getId() - { - return USERMOD_ID_MULTI_RELAY; - } + bool readFromConfig(JsonObject &root); }; + +// class implementetion + +void MultiRelay::publishMqtt(int relay) { +#ifndef WLED_DISABLE_MQTT + //Check if MQTT Connected, otherwise it will crash the 8266 + if (WLED_MQTT_CONNECTED){ + char subuf[64]; + sprintf_P(subuf, PSTR("%s/relay/%d"), mqttDeviceTopic, relay); + mqtt->publish(subuf, 0, false, _relay[relay].state ? "on" : "off"); + } +#endif +} + +/** + * switch off the strip if the delay has elapsed + */ +void MultiRelay::handleOffTimer() { + unsigned long now = millis(); + bool activeRelays = false; + for (int i=0; i 0 && now - _switchTimerStart > (_relay[i].delay*1000)) { + if (!_relay[i].external) switchRelay(i, !offMode); + _relay[i].active = false; + } else if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) { + if (_relay[i].pin>=0) publishMqtt(i); + } + activeRelays = activeRelays || _relay[i].active; + } + if (!activeRelays) _switchTimerStart = 0; + if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) lastBroadcast = now; +} + +/** + * HTTP API handler + * borrowed from: + * https://github.com/gsieben/WLED/blob/master/usermods/GeoGab-Relays/usermod_GeoGab.h + */ +#define GEOGABVERSION "0.1.3" +void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer + DEBUG_PRINTLN(F("Relays: Initialize HTML API")); + + server.on("/relays", HTTP_GET, [this](AsyncWebServerRequest *request) { + DEBUG_PRINTLN("Relays: HTML API"); + String janswer; + String error = ""; + //int params = request->params(); + janswer = F("{\"NoOfRelays\":"); + janswer += String(MULTI_RELAY_MAX_RELAYS) + ","; + + if (getActiveRelayCount()) { + // Commands + if(request->hasParam("switch")) { + /**** Switch ****/ + AsyncWebParameter* p = request->getParam("switch"); + // Get Values + for (int i=0; ivalue(), ',', i); + if (value==-1) { + error = F("There must be as many arguments as relays"); + } else { + // Switch + if (_relay[i].external) switchRelay(i, (bool)value); + } + } + } else if(request->hasParam("toggle")) { + /**** Toggle ****/ + AsyncWebParameter* p = request->getParam("toggle"); + // Get Values + for (int i=0;ivalue(), ',', i); + if (value==-1) { + error = F("There must be as many arguments as relays"); + } else { + // Toggle + if (value && _relay[i].external) toggleRelay(i); + } + } + } else { + error = F("No valid command found"); + } + } else { + error = F("No active relays"); + } + + // Status response + char sbuf[16]; + for (int i=0; isend(200, "application/json", janswer); + }); +} + +int MultiRelay::getValue(String data, char separator, int index) { + int found = 0; + int strIndex[] = {0, -1}; + int maxIndex = data.length()-1; + + for(int i=0; i<=maxIndex && found<=index; i++){ + if(data.charAt(i)==separator || i==maxIndex){ + found++; + strIndex[0] = strIndex[1]+1; + strIndex[1] = (i == maxIndex) ? i+1 : i; + } + } + return found>index ? data.substring(strIndex[0], strIndex[1]).toInt() : -1; +} + +//Write a byte to the IO expander +byte MultiRelay::IOexpanderWrite(byte address, byte _data ) { + Wire.beginTransmission(address); + Wire.write(_data); + return Wire.endTransmission(); +} + +//Read a byte from the IO expander +byte MultiRelay::IOexpanderRead(int address) { + byte _data = 0; + Wire.requestFrom(address, 1); + if (Wire.available()) { + _data = Wire.read(); + } + return _data; +} + + +// public methods + +MultiRelay::MultiRelay() + : _switchTimerStart(0) + , enabled(MULTI_RELAY_ENABLED) + , initDone(false) + , usePcf8574(USE_PCF8574) + , addrPcf8574(PCF8574_ADDRESS) + , HAautodiscovery(false) + , periodicBroadcastSec(60) + , lastBroadcast(0) +{ + const int8_t defPins[] = {MULTI_RELAY_PINS}; + for (size_t i=0; i=MULTI_RELAY_MAX_RELAYS || _relay[relay].pin<0) return; + _relay[relay].state = mode; + if (usePcf8574 && _relay[relay].pin >= 100) { + // we need to send all ouputs at the same time + uint8_t state = 0; + for (int i=0; i=0) count++; + return count; +} + + +//Functions called by WLED + +#ifndef WLED_DISABLE_MQTT +/** + * handling of MQTT message + * topic only contains stripped topic (part after /wled/MAC) + * topic should look like: /relay/X/command; where X is relay number, 0 based + */ +bool MultiRelay::onMqttMessage(char* topic, char* payload) { + if (strlen(topic) > 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, PSTR("/command"), 8) == 0) { + uint8_t relay = strtoul(topic+7, NULL, 10); + if (relaysubscribe(subuf, 0); + if (HAautodiscovery) publishHomeAssistantAutodiscovery(); + for (int i=0; i= 0 && _relay[i].external) { + StaticJsonDocument<1024> json; + sprintf_P(buf, PSTR("%s Switch %d"), serverDescription, i); //max length: 33 + 8 + 3 = 44 + json[F("name")] = buf; + + sprintf_P(buf, PSTR("%s/relay/%d"), mqttDeviceTopic, i); //max length: 33 + 7 + 3 = 43 + json["~"] = buf; + strcat_P(buf, PSTR("/command")); + mqtt->subscribe(buf, 0); + + json[F("stat_t")] = "~"; + json[F("cmd_t")] = F("~/command"); + json[F("pl_off")] = "off"; + json[F("pl_on")] = "on"; + json[F("uniq_id")] = uid; + + strcpy(buf, mqttDeviceTopic); //max length: 33 + 7 = 40 + strcat_P(buf, PSTR("/status")); + json[F("avty_t")] = buf; + json[F("pl_avail")] = F("online"); + json[F("pl_not_avail")] = F("offline"); + //TODO: dev + payload_size = serializeJson(json, json_str); + } else { + //Unpublish disabled or internal relays + json_str[0] = 0; + payload_size = 0; + } + sprintf_P(buf, PSTR("homeassistant/switch/%s/config"), uid); + mqtt->publish(buf, 0, true, json_str, payload_size); + } +} +#endif + +/** + * setup() is called once at boot. WiFi is not yet connected at this point. + * You can use it to initialize variables, sensors or similar. + */ +void MultiRelay::setup() { + // pins retrieved from cfg.json (readFromConfig()) prior to running setup() + // if we want PCF8574 expander I2C pins need to be valid + if (i2c_sda<0 || i2c_scl<0) usePcf8574 = false; + + uint8_t state = 0; + for (int i=0; i= 100) { + uint8_t pin = _relay[i].pin - 100; + if (!_relay[i].external) _relay[i].state = !offMode; + state |= (uint8_t)(_relay[i].invert ? !_relay[i].state : _relay[i].state) << pin; + } else if (_relay[i].pin<100 && _relay[i].pin>=0) { + if (pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) { + if (!_relay[i].external) _relay[i].state = !offMode; + switchRelay(i, _relay[i].state); + _relay[i].active = false; + } else { + _relay[i].pin = -1; // allocation failed + } + } + } + if (usePcf8574) { + IOexpanderWrite(addrPcf8574, state); // init expander (set all outputs) + DEBUG_PRINTLN(F("PCF8574(s) inited.")); + } + _oldMode = offMode; + initDone = true; +} + +/** + * loop() is called continuously. Here you can check for events, read sensors, etc. + */ +void MultiRelay::loop() { + yield(); + if (!enabled || strip.isUpdating()) return; + + static unsigned long lastUpdate = 0; + if (millis() - lastUpdate < 100) return; // update only 10 times/s + lastUpdate = millis(); + + //set relay when LEDs turn on + if (_oldMode != offMode) { + _oldMode = offMode; + _switchTimerStart = millis(); + for (int i=0; i=0) && !_relay[i].external) _relay[i].active = true; + } + } + + handleOffTimer(); +} + +/** + * handleButton() can be used to override default button behaviour. Returning true + * will prevent button working in a default way. + * Replicating button.cpp + */ +bool MultiRelay::handleButton(uint8_t b) { + yield(); + if (!enabled + || buttonType[b] == BTN_TYPE_NONE + || buttonType[b] == BTN_TYPE_RESERVED + || buttonType[b] == BTN_TYPE_PIR_SENSOR + || buttonType[b] == BTN_TYPE_ANALOG + || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { + return false; + } + + bool handled = false; + for (int i=0; i WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) + for (int i=0; i 600) { //long press + //longPressAction(b); //not exposed + //handled = false; //use if you want to pass to default behaviour + buttonLongPressed[b] = true; + } + + } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released + + long dur = now - buttonPressedTime[b]; + if (dur < WLED_DEBOUNCE_THRESHOLD) { + buttonPressedBefore[b] = false; + return handled; + } //too short "press", debounce + bool doublePress = buttonWaitTime[b]; //did we have short press before? + buttonWaitTime[b] = 0; + + if (!buttonLongPressed[b]) { //short press + // if this is second release within 350ms it is a double press (buttonWaitTime!=0) + if (doublePress) { + //doublePressAction(b); //not exposed + //handled = false; //use if you want to pass to default behaviour + } else { + buttonWaitTime[b] = now; + } + } + buttonPressedBefore[b] = false; + buttonLongPressed[b] = false; + } + // if 350ms elapsed since last press/release it is a short press + if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) { + buttonWaitTime[b] = 0; + //shortPressAction(b); //not exposed + for (int i=0; i"); + uiDomString += F(""); + infoArr.add(uiDomString); + } + } +} + +/** + * 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 MultiRelay::addToJsonState(JsonObject &root) { + if (!initDone || !enabled) return; // prevent crash on boot applyPreset() + JsonObject multiRelay = root[FPSTR(_name)]; + if (multiRelay.isNull()) { + multiRelay = root.createNestedObject(FPSTR(_name)); + } + #if MULTI_RELAY_MAX_RELAYS > 1 + JsonArray rel_arr = multiRelay.createNestedArray(F("relays")); + for (int i=0; i() && usermod[FPSTR(_relay_str)].as()>=0) { + int rly = usermod[FPSTR(_relay_str)].as(); + if (usermod["on"].is()) { + switchRelay(rly, usermod["on"].as()); + } else if (usermod["on"].is() && usermod["on"].as()[0] == 't') { + toggleRelay(rly); + } + } + } else if (root[FPSTR(_name)].is()) { + JsonArray relays = root[FPSTR(_name)].as(); + for (JsonVariant r : relays) { + if (r[FPSTR(_relay_str)].is() && r[FPSTR(_relay_str)].as()>=0) { + int rly = r[FPSTR(_relay_str)].as(); + if (r["on"].is()) { + switchRelay(rly, r["on"].as()); + } else if (r["on"].is() && r["on"].as()[0] == 't') { + toggleRelay(rly); + } + } + } + } +} + +/** + * provide the changeable values + */ +void MultiRelay::addToConfig(JsonObject &root) { + JsonObject top = root.createNestedObject(FPSTR(_name)); + + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_pcf8574)] = usePcf8574; + top[FPSTR(_pcfAddress)] = addrPcf8574; + top[FPSTR(_broadcast)] = periodicBroadcastSec; + top[FPSTR(_HAautodiscovery)] = HAautodiscovery; + for (int i=0; i(not hex!)');")); + oappend(SET_F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');")); + //oappend(SET_F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');")); + oappend(SET_F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); +} + +/** + * restore the changeable values + * 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 MultiRelay::readFromConfig(JsonObject &root) { + int8_t oldPin[MULTI_RELAY_MAX_RELAYS]; + + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINT(FPSTR(_name)); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + //bool configComplete = !top.isNull(); + //configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); + enabled = top[FPSTR(_enabled)] | enabled; + usePcf8574 = top[FPSTR(_pcf8574)] | usePcf8574; + addrPcf8574 = top[FPSTR(_pcfAddress)] | addrPcf8574; + // if I2C is not globally initialised just ignore + if (i2c_sda<0 || i2c_scl<0) usePcf8574 = false; + periodicBroadcastSec = top[FPSTR(_broadcast)] | periodicBroadcastSec; + periodicBroadcastSec = min(900,max(0,(int)periodicBroadcastSec)); + HAautodiscovery = top[FPSTR(_HAautodiscovery)] | HAautodiscovery; + + for (int i=0; i=0 && oldPin[i]<100) { + pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay); + } + // allocate new pins + setup(); + DEBUG_PRINTLN(F(" config (re)loaded.")); + } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !top[FPSTR(_pcf8574)].isNull(); +} + // strings to reduce flash memory usage (used more than twice) -const char MultiRelay::_name[] PROGMEM = "MultiRelay"; -const char MultiRelay::_enabled[] PROGMEM = "enabled"; -const char MultiRelay::_relay_str[] PROGMEM = "relay"; -const char MultiRelay::_delay_str[] PROGMEM = "delay-s"; -const char MultiRelay::_activeHigh[] PROGMEM = "active-high"; -const char MultiRelay::_external[] PROGMEM = "external"; -const char MultiRelay::_button[] PROGMEM = "button"; -const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec"; +const char MultiRelay::_name[] PROGMEM = "MultiRelay"; +const char MultiRelay::_enabled[] PROGMEM = "enabled"; +const char MultiRelay::_relay_str[] PROGMEM = "relay"; +const char MultiRelay::_delay_str[] PROGMEM = "delay-s"; +const char MultiRelay::_activeHigh[] PROGMEM = "active-high"; +const char MultiRelay::_external[] PROGMEM = "external"; +const char MultiRelay::_button[] PROGMEM = "button"; +const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec"; const char MultiRelay::_HAautodiscovery[] PROGMEM = "HA-autodiscovery"; +const char MultiRelay::_pcf8574[] PROGMEM = "use-PCF8574"; +const char MultiRelay::_pcfAddress[] PROGMEM = "PCF8574-address"; diff --git a/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h index 972e2c866..4f51750ac 100644 --- a/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h +++ b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h @@ -6,7 +6,6 @@ #include "wled.h" #include -#include #include #include #include @@ -16,14 +15,6 @@ Adafruit_BMP280 bmp; Adafruit_Si7021 si7021; Adafruit_CCS811 ccs811; -#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards -uint8_t SCL_PIN = 22; -uint8_t SDA_PIN = 21; -#else //ESP8266 boards -uint8_t SCL_PIN = 5; -uint8_t SDA_PIN = 4; -#endif - class UserMod_SensorsToMQTT : public Usermod { private: @@ -231,7 +222,6 @@ public: void setup() { Serial.println("Starting!"); - Wire.begin(SDA_PIN, SCL_PIN); Serial.println("Initializing sensors.. "); _initialize(); } diff --git a/usermods/sht/usermod_sht.h b/usermods/sht/usermod_sht.h index 1123a10a5..56cea2195 100644 --- a/usermods/sht/usermod_sht.h +++ b/usermods/sht/usermod_sht.h @@ -16,13 +16,12 @@ class ShtUsermod : public Usermod private: bool enabled = false; // Is usermod enabled or not 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 bool haMqttDiscovery = false; // Is MQTT discovery enabled or not 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,7 +36,7 @@ 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(); @@ -94,7 +93,7 @@ void ShtUsermod::initShtTempHumiditySensor() case USERMOD_SHT_TYPE_SHT85: shtTempHumidSensor = (SHT *) new SHT85(); break; } - shtTempHumidSensor->begin(shtI2cAddress, i2c_sda, i2c_scl); + shtTempHumidSensor->begin(shtI2cAddress); // uses &Wire if (shtTempHumidSensor->readStatus() == 0xFFFF) { DEBUG_PRINTF("[%s] SHT init failed!\n", _name); cleanup(); @@ -113,8 +112,11 @@ void ShtUsermod::initShtTempHumiditySensor() */ void ShtUsermod::cleanupShtTempHumiditySensor() { - if (isShtReady()) shtTempHumidSensor->reset(); - delete shtTempHumidSensor; + if (isShtReady()) { + shtTempHumidSensor->reset(); + delete shtTempHumidSensor; + shtTempHumidSensor = nullptr; + } shtInitDone = false; } @@ -129,26 +131,9 @@ void ShtUsermod::cleanupShtTempHumiditySensor() void ShtUsermod::cleanup() { cleanupShtTempHumiditySensor(); - - if (pinAllocDone) { - PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } }; - pinManager.deallocateMultiplePins(pins, 2, PinOwner::HW_I2C); - pinAllocDone = false; - } - enabled = false; } -/** - * Checks if the SHT sensor has been initialised. - * - * @return bool - */ -bool ShtUsermod::isShtReady() -{ - return shtInitDone; -} - /** * Publish temperature and humidity to WLED device topic. * @@ -244,14 +229,12 @@ void ShtUsermod::appendDeviceToMqttDiscoveryMessage(JsonDocument& root) { 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 (i2c_sda < 0 || i2c_scl < 0 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { - DEBUG_PRINTF("[%s] SHT pin allocation failed!\n", _name); + // GPIOs can be set to -1 , so check they're gt zero + if (i2c_sda < 0 || i2c_scl < 0) { + DEBUG_PRINTF("[%s] I2C bus not initialised!\n", _name); cleanup(); return; } - pinAllocDone = true; initShtTempHumiditySensor(); @@ -463,7 +446,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_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 ab244ef28..5a99c3cdb 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 @@ -1,20 +1,24 @@ #pragma once #include "wled.h" +#undef U8X8_NO_HW_I2C // borrowed from WLEDMM: we do want I2C hardware drivers - if possible #include // from https://github.com/olikraus/u8g2/ #include "4LD_wled_fonts.c" +#ifndef FLD_ESP32_NO_THREADS + #define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!! +#endif + // -// Insired by the usermod_v2_four_line_display +// Inspired by the usermod_v2_four_line_display // // v2 usermod for using 128x32 or 128x64 i2c // OLED displays to provide a four line display // for WLED. // // Dependencies -// * This usermod REQURES the ModeSortUsermod // * This Usermod works best, by far, when coupled -// with RotaryEncoderUIUsermod. +// with RotaryEncoderUI ALT Usermod. // // Make sure to enable NTP and set your time zone in WLED Config | Time. // @@ -23,22 +27,14 @@ // REQUIREMENT: * U8g2 (the version already in platformio.ini is fine) // REQUIREMENT: * Wire // +// If display does not work or looks corrupted check the +// constructor reference: +// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp +// or check the gallery: +// https://github.com/olikraus/u8g2/wiki/gallery -//The SCL and SDA pins are defined here. -#ifndef FLD_PIN_SCL - #define FLD_PIN_SCL i2c_scl -#endif -#ifndef FLD_PIN_SDA - #define FLD_PIN_SDA i2c_sda -#endif -#ifndef FLD_PIN_CLOCKSPI - #define FLD_PIN_CLOCKSPI spi_sclk -#endif - #ifndef FLD_PIN_DATASPI - #define FLD_PIN_DATASPI spi_mosi -#endif #ifndef FLD_PIN_CS - #define FLD_PIN_CS spi_cs + #define FLD_PIN_CS 15 #endif #ifdef ARDUINO_ARCH_ESP32 @@ -86,20 +82,23 @@ void DisplayTaskCode(void * parameter); typedef enum { NONE = 0, - SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C - SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C - 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_SPI64 // U8X8_SSD1306_128X64_NONAME_HW_SPI + SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C + SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C + 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_SPI64, // U8X8_SSD1306_128X64_NONAME_HW_SPI + SSD1309_SPI64 // U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI } DisplayType; class FourLineDisplayUsermod : public Usermod { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) public: FourLineDisplayUsermod() { if (!instance) instance = this; } static FourLineDisplayUsermod* getInstance(void) { return instance; } +#endif private: @@ -112,10 +111,10 @@ class FourLineDisplayUsermod : public Usermod { U8X8 *u8x8 = nullptr; // pointer to U8X8 display object #ifndef FLD_SPI_DEFAULT - int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA + int8_t ioPin[3] = {-1, -1, -1}; // I2C pins: SCL, SDA uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000) #else - int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST + int8_t ioPin[3] = {FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // custom SPI pins: CS, DC, RST uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz) #endif @@ -178,561 +177,78 @@ class FourLineDisplayUsermod : public Usermod { // https://github.com/olikraus/u8g2/wiki/gallery // some displays need this to properly apply contrast - void setVcomh(bool highContrast) { - u8x8_t *u8x8_struct = u8x8->getU8x8(); - u8x8_cad_StartTransfer(u8x8_struct); - u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value - u8x8_cad_SendArg(u8x8_struct, highContrast ? 0x000 : 0x040); //value 0 for fix, reboot resets default back to 64 - u8x8_cad_EndTransfer(u8x8_struct); - } + void setVcomh(bool highContrast); + void startDisplay(); /** * Wrappers for screen drawing */ - void setFlipMode(uint8_t mode) { - if (type == NONE || !enabled) return; - u8x8->setFlipMode(mode); - } - void setContrast(uint8_t contrast) { - if (type == NONE || !enabled) return; - u8x8->setContrast(contrast); - } - void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->setFont(u8x8_font_chroma48medium8_r); - if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); - else u8x8->drawString(col, row, string); - drawing = false; - } - void draw2x2String(uint8_t col, uint8_t row, const char *string) { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->setFont(u8x8_font_chroma48medium8_r); - u8x8->draw2x2String(col, row, string); - drawing = false; - } - void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->setFont(font); - if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); - else u8x8->drawGlyph(col, row, glyph); - drawing = false; - } - void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->setFont(font); - u8x8->draw2x2Glyph(col, row, glyph); - drawing = false; - } - uint8_t getCols() { - if (type==NONE || !enabled) return 0; - return u8x8->getCols(); - } - void clear() { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->clear(); - drawing = false; - } - void setPowerSave(uint8_t save) { - if (type == NONE || !enabled) return; - u8x8->setPowerSave(save); - } - - void center(String &line, uint8_t width) { - int len = line.length(); - if (len0; i--) line = ' ' + line; - for (byte i=line.length(); i 11) { AmPmHour -= 12; isitAM = false; } - if (AmPmHour == 0) { AmPmHour = 12; } - } - if (knownHour != hourCurrent) { - // only update date when hour changes - sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); - draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day - } - sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent); - draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds - if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time - - drawStatusIcons(); //icons power, wifi, timer, etc - - knownMinute = minuteCurrent; - knownHour = hourCurrent; - } - if (showSeconds && secondCurrent != lastSecond) { - lastSecond = secondCurrent; - draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":"); - sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); - drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line - } - } + void showTime(); /** * Enable sleep (turn the display off) or clock mode. */ - void sleepOrClock(bool enabled) { - if (enabled) { - displayTurnedOff = true; - if (clockMode && ntpEnabled) { - knownMinute = knownHour = 99; - showTime(); - } else - setPowerSave(1); - } else { - displayTurnedOff = false; - setPowerSave(0); - } - } + void sleepOrClock(bool enabled); public: // gets called once at boot. Do all initialization that doesn't depend on // network here - void setup() { - if (type == NONE || !enabled) return; - - bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); - PinOwner po = PinOwner::UM_FourLineDisplay; - if (isSPI) { - uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; - uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; - if (ioPin[0] < 0 || ioPin[1] < 0) { - ioPin[0] = hw_sclk; - ioPin[1] = hw_mosi; - } - isHW = (ioPin[0]==hw_sclk && ioPin[1]==hw_mosi); - PinManagerPinType cspins[3] = { { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true } }; - if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } - if (isHW) po = PinOwner::HW_SPI; // allow multiple allocations of HW I2C bus pins - PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; - if (!pinManager.allocateMultiplePins(pins, 2, po)) { - pinManager.deallocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay); - type = NONE; - return; - } - } else { - uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; - uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; - if (ioPin[0] < 0 || ioPin[1] < 0) { - ioPin[0] = hw_scl; - ioPin[1] = hw_sda; - } - isHW = (ioPin[0]==hw_scl && ioPin[1]==hw_sda); - if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - PinManagerPinType pins[2] = { {ioPin[0], true }, { ioPin[1], true } }; - if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; } - } - - DEBUG_PRINTLN(F("Allocating display.")); -/* -// At some point it may be good to not new/delete U8X8 object but use this instead -// (does not currently work) -//------------------------------------------------------------------------------- - switch (type) { - case SSD1306: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SH1106: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_sh1106_128x64_winstar, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SSD1306_64: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SSD1305: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1305_128x32_adafruit, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SSD1305_64: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SSD1306_SPI: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_4wire_sw_spi, u8x8_gpio_and_delay_arduino); - break; - case SSD1306_SPI64: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, u8x8_byte_arduino_4wire_sw_spi, u8x8_gpio_and_delay_arduino); - break; - default: - type = NONE; - return; - } - if (isSPI) { - if (!isHW) u8x8_SetPin_4Wire_SW_SPI(u8x8.getU8x8(), ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); - else u8x8_SetPin_4Wire_HW_SPI(u8x8.getU8x8(), ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset - } else { - if (!isHW) u8x8_SetPin_SW_I2C(u8x8.getU8x8(), ioPin[0], ioPin[1], U8X8_PIN_NONE); // SCL, SDA, reset - else u8x8_SetPin_HW_I2C(u8x8.getU8x8(), U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - } -*/ - switch (type) { - case SSD1306: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SH1106: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SSD1306_64: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SSD1305: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SSD1305_64: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SSD1306_SPI: - // u8x8 uses global SPI variable that is attached to VSPI bus on ESP32 - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset - break; - case SSD1306_SPI64: - // u8x8 uses global SPI variable that is attached to VSPI bus on ESP32 - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset - break; - default: - u8x8 = nullptr; - } - - if (nullptr == u8x8) { - DEBUG_PRINTLN(F("Display init failed.")); - pinManager.deallocateMultiplePins((const uint8_t*)ioPin, isSPI ? 5 : 2, po); - type = NONE; - return; - } - - lineHeight = u8x8->getRows() > 4 ? 2 : 1; - DEBUG_PRINTLN(F("Starting display.")); - u8x8->setBusClock(ioFrequency); // can be used for SPI too - u8x8->begin(); - setFlipMode(flip); - setVcomh(contrastFix); - setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 - setPowerSave(0); - //drawString(0, 0, "Loading..."); - overlayLogo(3500); - onUpdateBegin(false); // create Display task - initDone = true; - } + void setup(); // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here - void connected() { - knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : - knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); - networkOverlay(PSTR("NETWORK INFO"),7000); - } + void connected(); /** * Da loop. */ - void loop() { - #ifndef ARDUINO_ARCH_ESP32 - if (!enabled || strip.isUpdating()) return; - unsigned long now = millis(); - if (now < nextUpdate) return; - nextUpdate = now + ((displayTurnedOff && clockMode && showSeconds) ? 1000 : refreshRate); - redraw(false); - #endif - } + void loop(); //function to update lastredraw - void updateRedrawTime() { - lastRedraw = millis(); - } + inline void updateRedrawTime() { lastRedraw = millis(); } /** * Redraw the screen (but only if things have changed * or if forceRedraw). */ - void redraw(bool forceRedraw) { - bool needRedraw = false; - unsigned long now = millis(); - - if (type == NONE || !enabled) return; - if (overlayUntil > 0) { - if (now >= overlayUntil) { - // Time to display the overlay has elapsed. - overlayUntil = 0; - forceRedraw = true; - } else { - // We are still displaying the overlay - // Don't redraw. - return; - } - } + void redraw(bool forceRedraw); - while (drawing && millis()-now < 25) delay(1); // wait if someone else is drawing - if (drawing || lockRedraw) return; - - if (apActive && WLED_WIFI_CONFIGURED && now<15000) { - knownSsid = apSSID; - networkOverlay(PSTR("NETWORK INFO"),30000); - return; - } - - // Check if values which are shown on display changed from the last time. - if (forceRedraw) { - needRedraw = true; - clear(); - } else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon - powerON = !powerON; - drawStatusIcons(); - return; - } else if (knownnightlight != nightlightActive) { //trigger moon icon - knownnightlight = nightlightActive; - drawStatusIcons(); - if (knownnightlight) { - String timer = PSTR("Timer On"); - center(timer,LINE_BUFFER_SIZE-1); - overlay(timer.c_str(), 2500, 6); - } - return; - } else if (wificonnected != interfacesInited) { //trigger wifi icon - wificonnected = interfacesInited; - drawStatusIcons(); - return; - } else if (knownMode != effectCurrent || knownPalette != effectPalette) { - if (displayTurnedOff) needRedraw = true; - else { - if (knownPalette != effectPalette) { showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); knownPalette = effectPalette; } - if (knownMode != effectCurrent) { showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); knownMode = effectCurrent; } - lastRedraw = now; - return; - } - } else if (knownBrightness != bri) { - if (displayTurnedOff && nightlightActive) { knownBrightness = bri; } - else if (!displayTurnedOff) { updateBrightness(); lastRedraw = now; return; } - } else if (knownEffectSpeed != effectSpeed) { - if (displayTurnedOff) needRedraw = true; - else { updateSpeed(); lastRedraw = now; return; } - } else if (knownEffectIntensity != effectIntensity) { - if (displayTurnedOff) needRedraw = true; - else { updateIntensity(); lastRedraw = now; return; } - } - - if (!needRedraw) { - // Nothing to change. - // Turn off display after 1 minutes with no change. - if (sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) { - // We will still check if there is a change in redraw() - // and turn it back on if it changed. - clear(); - sleepOrClock(true); - } else if (displayTurnedOff && ntpEnabled) { - showTime(); - } - return; - } - - lastRedraw = now; - - // Turn the display back on - wakeDisplay(); - - // Update last known values. - knownBrightness = bri; - knownMode = effectCurrent; - knownPalette = effectPalette; - knownEffectSpeed = effectSpeed; - knownEffectIntensity = effectIntensity; - knownnightlight = nightlightActive; - wificonnected = interfacesInited; - - // Do the actual drawing - // First row: Icons - draw2x2GlyphIcons(); - drawArrow(); - drawStatusIcons(); - - // Second row - updateBrightness(); - updateSpeed(); - updateIntensity(); - - // Third row - showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 2); //Palette info - - // Fourth row - showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info - } - - void updateBrightness() { - knownBrightness = bri; - if (overlayUntil == 0) { - lockRedraw = true; - brightness100 = ((uint16_t)bri*100)/255; - char lineBuffer[4]; - sprintf_P(lineBuffer, PSTR("%-3d"), brightness100); - drawString(1, lineHeight, lineBuffer); - lockRedraw = false; - } - } - - void updateSpeed() { - knownEffectSpeed = effectSpeed; - if (overlayUntil == 0) { - lockRedraw = true; - fxspeed100 = ((uint16_t)effectSpeed*100)/255; - char lineBuffer[4]; - sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100); - drawString(5, lineHeight, lineBuffer); - lockRedraw = false; - } - } - - void updateIntensity() { - knownEffectIntensity = effectIntensity; - if (overlayUntil == 0) { - lockRedraw = true; - fxintensity100 = ((uint16_t)effectIntensity*100)/255; - char lineBuffer[4]; - sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100); - drawString(9, lineHeight, lineBuffer); - lockRedraw = false; - } - } - - void drawStatusIcons() { - uint8_t col = 15; - uint8_t row = 0; - lockRedraw = true; - drawGlyph(col, row, (wificonnected ? 20 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // wifi icon - if (lineHeight==2) { col--; } else { row++; } - drawGlyph(col, row, (bri > 0 ? 9 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // power icon - if (lineHeight==2) { col--; } else { col = row = 0; } - drawGlyph(col, row, (nightlightActive ? 6 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // moon icon for nighlight mode - lockRedraw = false; - } + void updateBrightness(); + void updateSpeed(); + void updateIntensity(); + void drawStatusIcons(); /** * marks the position of the arrow showing * the current setting being changed * pass line and colum info */ - void setMarkLine(byte newMarkLineNum, byte newMarkColNum) { - markLineNum = newMarkLineNum; - markColNum = newMarkColNum; - } + void setMarkLine(byte newMarkLineNum, byte newMarkColNum); //Draw the arrow for the current setting beiong changed - void drawArrow() { - lockRedraw = true; - if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1); - lockRedraw = false; - } + void drawArrow(); //Display the current effect or palette (desiredEntry) // on the appropriate line (row). - void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { - char lineBuffer[MAX_JSON_CHARS]; - if (overlayUntil == 0) { - lockRedraw = true; - // Find the mode name in JSON - uint8_t printedChars = extractModeName(inputEffPal, qstring, lineBuffer, MAX_JSON_CHARS-1); - if (lineBuffer[0]=='*' && lineBuffer[1]==' ') { - // remove "* " from dynamic palettes - for (byte i=2; i<=printedChars; i++) lineBuffer[i-2] = lineBuffer[i]; //include '\0' - printedChars -= 2; - } else if ((lineBuffer[0]==' ' && lineBuffer[1]>127)) { - // remove note symbol from effect names - for (byte i=5; i<=printedChars; i++) lineBuffer[i-5] = lineBuffer[i]; //include '\0' - printedChars -= 5; - } - if (lineHeight == 2) { // use this code for 8 line display - char smallBuffer1[MAX_MODE_LINE_SPACE]; - char smallBuffer2[MAX_MODE_LINE_SPACE]; - uint8_t smallChars1 = 0; - uint8_t smallChars2 = 0; - if (printedChars < MAX_MODE_LINE_SPACE) { // use big font if the text fits - while (printedChars < (MAX_MODE_LINE_SPACE-1)) lineBuffer[printedChars++]=' '; - lineBuffer[printedChars] = 0; - drawString(1, row*lineHeight, lineBuffer); - } else { // for long names divide the text into 2 lines and print them small - bool spaceHit = false; - for (uint8_t i = 0; i < printedChars; i++) { - switch (lineBuffer[i]) { - case ' ': - if (i > 4 && !spaceHit) { - spaceHit = true; - break; - } - if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; - else smallBuffer1[smallChars1++] = lineBuffer[i]; - break; - default: - if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; - else smallBuffer1[smallChars1++] = lineBuffer[i]; - break; - } - } - while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' '; - smallBuffer1[smallChars1] = 0; - drawString(1, row*lineHeight, smallBuffer1, true); - while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' '; - smallBuffer2[smallChars2] = 0; - drawString(1, row*lineHeight+1, smallBuffer2, true); - } - } else { // use this code for 4 ling displays - char smallBuffer3[MAX_MODE_LINE_SPACE+1]; // uses 1x1 icon for mode/palette - uint8_t smallChars3 = 0; - for (uint8_t i = 0; i < MAX_MODE_LINE_SPACE; i++) smallBuffer3[smallChars3++] = (i >= printedChars) ? ' ' : lineBuffer[i]; - smallBuffer3[smallChars3] = 0; - drawString(1, row*lineHeight, smallBuffer3, true); - } - lockRedraw = false; - } - } + void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row); /** * If there screen is off or in clock is displayed, @@ -740,330 +256,59 @@ class FourLineDisplayUsermod : public Usermod { * the first input from the rotary encoder but * to wake up the screen. */ - bool wakeDisplay() { - if (type == NONE || !enabled) return false; - if (displayTurnedOff) { - unsigned long now = millis(); - while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing - if (drawing) return false; - lockRedraw = true; - clear(); - // Turn the display back on - sleepOrClock(false); - lockRedraw = false; - return true; - } - return false; - } + bool wakeDisplay(); /** * Allows you to show one line and a glyph as overlay for a period of time. * Clears the screen and prints. * Used in Rotary Encoder usermod. */ - void overlay(const char* line1, long showHowLong, byte glyphType) { - unsigned long now = millis(); - while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; - lockRedraw = true; - // Turn the display back on - if (!wakeDisplay()) clear(); - // Print the overlay - if (glyphType>0 && glyphType<255) { - if (lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_4LineDisplay_WLED_icons_6x6, true); // use 3x3 font with draw2x2Glyph() if flash runs short and comment out 6x6 font - else drawGlyph(6, 0, glyphType, u8x8_4LineDisplay_WLED_icons_3x3, true); - } - if (line1) { - String buf = line1; - center(buf, getCols()); - drawString(0, (glyphType<255?3:0)*lineHeight, buf.c_str()); - } - overlayUntil = millis() + showHowLong; - lockRedraw = false; - } + void overlay(const char* line1, long showHowLong, byte glyphType); /** * Allows you to show Akemi WLED logo overlay for a period of time. * Clears the screen and prints. */ - void overlayLogo(long showHowLong) { - unsigned long now = millis(); - while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; - lockRedraw = true; - // Turn the display back on - if (!wakeDisplay()) clear(); - // Print the overlay - if (lineHeight == 2) { - //add a bit of randomness - switch (millis()%3) { - case 0: - //WLED - draw2x2Glyph( 0, 2, 1, u8x8_wled_logo_2x2); - draw2x2Glyph( 4, 2, 2, u8x8_wled_logo_2x2); - draw2x2Glyph( 8, 2, 3, u8x8_wled_logo_2x2); - draw2x2Glyph(12, 2, 4, u8x8_wled_logo_2x2); - break; - case 1: - //WLED Akemi - drawGlyph( 2, 2, 1, u8x8_wled_logo_akemi_4x4, true); - drawGlyph( 6, 2, 2, u8x8_wled_logo_akemi_4x4, true); - drawGlyph(10, 2, 3, u8x8_wled_logo_akemi_4x4, true); - break; - case 2: - //Akemi - //draw2x2Glyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_3x3); // use this if flash runs short and comment out 6x6 font - drawGlyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_6x6, true); - drawString(6, 6, "WLED"); - break; - } - } else { - switch (millis()%3) { - case 0: - //WLED - draw2x2Glyph( 0, 0, 1, u8x8_wled_logo_2x2); - draw2x2Glyph( 4, 0, 2, u8x8_wled_logo_2x2); - draw2x2Glyph( 8, 0, 3, u8x8_wled_logo_2x2); - draw2x2Glyph(12, 0, 4, u8x8_wled_logo_2x2); - break; - case 1: - //WLED Akemi - drawGlyph( 2, 0, 1, u8x8_wled_logo_akemi_4x4); - drawGlyph( 6, 0, 2, u8x8_wled_logo_akemi_4x4); - drawGlyph(10, 0, 3, u8x8_wled_logo_akemi_4x4); - break; - case 2: - //Akemi - //drawGlyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_4x4); // a bit nicer, but uses extra 1.5k flash - draw2x2Glyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_2x2); - break; - } - } - overlayUntil = millis() + showHowLong; - lockRedraw = false; - } + void overlayLogo(long showHowLong); /** * Allows you to show two lines as overlay for a period of time. * Clears the screen and prints. * Used in Auto Save usermod */ - void overlay(const char* line1, const char* line2, long showHowLong) { - unsigned long now = millis(); - while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; - lockRedraw = true; - // Turn the display back on - if (!wakeDisplay()) clear(); - // Print the overlay - if (line1) { - String buf = line1; - center(buf, getCols()); - drawString(0, 1*lineHeight, buf.c_str()); - } - if (line2) { - String buf = line2; - center(buf, getCols()); - drawString(0, 2*lineHeight, buf.c_str()); - } - overlayUntil = millis() + showHowLong; - lockRedraw = false; - } - - void networkOverlay(const char* line1, long showHowLong) { - unsigned long now = millis(); - while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; - lockRedraw = true; - - String line; - // Turn the display back on - if (!wakeDisplay()) clear(); - // Print the overlay - if (line1) { - line = line1; - center(line, getCols()); - drawString(0, 0, line.c_str()); - } - // Second row with Wifi name - line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); - if (line.length() < getCols()) center(line, getCols()); - drawString(0, lineHeight, line.c_str()); - // Print `~` char to indicate that SSID is longer, than our display - if (knownSsid.length() > getCols()) { - drawString(getCols() - 1, 0, "~"); - } - // Third row with IP and Password in AP Mode - line = knownIp.toString(); - center(line, getCols()); - drawString(0, lineHeight*2, line.c_str()); - line = ""; - if (apActive) { - line = apPass; - } else if (strcmp(serverDescription, "WLED") != 0) { - line = serverDescription; - } - center(line, getCols()); - drawString(0, lineHeight*3, line.c_str()); - overlayUntil = millis() + showHowLong; - lockRedraw = false; - } + void overlay(const char* line1, const char* line2, long showHowLong); + void networkOverlay(const char* line1, long showHowLong); /** * handleButton() can be used to override default button behaviour. Returning true * will prevent button working in a default way. * Replicating button.cpp */ - bool handleButton(uint8_t b) { - yield(); - if (!enabled - || b // butto 0 only - || buttonType[b] == BTN_TYPE_SWITCH - || buttonType[b] == BTN_TYPE_NONE - || buttonType[b] == BTN_TYPE_RESERVED - || buttonType[b] == BTN_TYPE_PIR_SENSOR - || buttonType[b] == BTN_TYPE_ANALOG - || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { - return false; - } + bool handleButton(uint8_t b); - unsigned long now = millis(); - static bool buttonPressedBefore = false; - static bool buttonLongPressed = false; - static unsigned long buttonPressedTime = 0; - static unsigned long buttonWaitTime = 0; - bool handled = true; - - //momentary button logic - if (isButtonPressed(b)) { //pressed - - if (!buttonPressedBefore) buttonPressedTime = now; - buttonPressedBefore = true; - - if (now - buttonPressedTime > 600) { //long press - buttonLongPressed = true; - //TODO: handleButton() handles button 0 without preset in a different way for double click - //so we need to override with same behaviour - longPressAction(0); - //handled = false; - } - - } else if (!isButtonPressed(b) && buttonPressedBefore) { //released - - long dur = now - buttonPressedTime; - if (dur < 50) { - buttonPressedBefore = false; - return true; - } //too short "press", debounce - - bool doublePress = buttonWaitTime; //did we have short press before? - buttonWaitTime = 0; - - if (!buttonLongPressed) { //short press - // if this is second release within 350ms it is a double press (buttonWaitTime!=0) - //TODO: handleButton() handles button 0 without preset in a different way for double click - if (doublePress) { - networkOverlay(PSTR("NETWORK INFO"),7000); - handled = true; - } else { - buttonWaitTime = now; - } - } - buttonPressedBefore = false; - buttonLongPressed = false; - } - // if 350ms elapsed since last press/release it is a short press - if (buttonWaitTime && now - buttonWaitTime > 350 && !buttonPressedBefore) { - buttonWaitTime = 0; - //TODO: handleButton() handles button 0 without preset in a different way for double click - //so we need to override with same behaviour - shortPressAction(0); - //handled = false; - } - return handled; - } - - #if CONFIG_FREERTOS_UNICORE - #define ARDUINO_RUNNING_CORE 0 - #else - #define ARDUINO_RUNNING_CORE 1 - #endif - void onUpdateBegin(bool init) { - #ifdef ARDUINO_ARCH_ESP32 - if (init && Display_Task) { - vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash - } else { - // update has failed or create task requested - if (Display_Task) - vTaskResume(Display_Task); - else - xTaskCreatePinnedToCore( - [](void * par) { // Function to implement the task - // see https://www.freertos.org/vtaskdelayuntil.html - const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2; - TickType_t xLastWakeTime = xTaskGetTickCount(); - for(;;) { - delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. - // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. - vTaskDelayUntil(&xLastWakeTime, xFrequency); // release CPU, by doing nothing for REFRESH_RATE_MS millis - FourLineDisplayUsermod::getInstance()->redraw(false); - } - }, - "4LD", // Name of the task - 3072, // Stack size in words - NULL, // Task input parameter - 1, // Priority of the task (not idle) - &Display_Task, // Task handle - ARDUINO_RUNNING_CORE - ); - } - #endif - } + void onUpdateBegin(bool init); /* * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Below it is shown how this could be used for e.g. a light sensor */ - //void addToJsonInfo(JsonObject& root) { - //JsonObject user = root["u"]; - //if (user.isNull()) user = root.createNestedObject("u"); - //JsonArray data = user.createNestedArray(F("4LineDisplay")); - //data.add(F("Loaded.")); - //} + //void addToJsonInfo(JsonObject& root); /* * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - //void addToJsonState(JsonObject& root) { - //} + //void addToJsonState(JsonObject& root); /* * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - //void readFromJsonState(JsonObject& root) { - // if (!initDone) return; // prevent crash on boot applyPreset() - //} + //void readFromJsonState(JsonObject& root); - void appendConfigData() { - oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); - oappend(SET_F("addOption(dd,'None',0);")); - 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,'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("addInfo('4LineDisplay:pin[]',0,'-1 use global','I2C/SPI CLK');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'-1 use global','I2C/SPI DTA');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI CS');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',3,'','SPI DC');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',4,'','SPI RST');")); - } + void appendConfigData(); /* * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. @@ -1079,40 +324,7 @@ class FourLineDisplayUsermod : public Usermod { * * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ - 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)) { - hw_clk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; - hw_dta = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; - } else { - hw_clk = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; - hw_dta = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; - } - - JsonObject top = root.createNestedObject(FPSTR(_name)); - top[FPSTR(_enabled)] = enabled; - - JsonArray io_pin = top.createNestedArray("pin"); - for (int i=0; i<5; i++) { - if (i==0 && ioPin[i]==hw_clk) io_pin.add(-1); // do not store global HW pin - else if (i==1 && ioPin[i]==hw_dta) io_pin.add(-1); // do not store global HW pin - else io_pin.add(ioPin[i]); - } - top["type"] = type; - top[FPSTR(_flip)] = (bool) flip; - top[FPSTR(_contrast)] = contrast; - top[FPSTR(_contrastFix)] = (bool) contrastFix; - #ifndef ARDUINO_ARCH_ESP32 - top[FPSTR(_refreshRate)] = refreshRate; - #endif - top[FPSTR(_screenTimeOut)] = screenTimeout/1000; - top[FPSTR(_sleepMode)] = (bool) sleepMode; - top[FPSTR(_clockMode)] = (bool) clockMode; - top[FPSTR(_showSeconds)] = (bool) showSeconds; - top[FPSTR(_busClkFrequency)] = ioFrequency/1000; - DEBUG_PRINTLN(F("4 Line Display config saved.")); - } + void addToConfig(JsonObject& root); /* * readFromConfig() can be used to read back the custom settings you added with addToConfig(). @@ -1122,80 +334,7 @@ class FourLineDisplayUsermod : public Usermod { * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) */ - bool readFromConfig(JsonObject& root) { - bool needsRedraw = false; - DisplayType newType = type; - int8_t oldPin[5]; for (byte i=0; i<5; i++) oldPin[i] = ioPin[i]; - - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - - enabled = top[FPSTR(_enabled)] | enabled; - newType = top["type"] | newType; - for (byte i=0; i<5; i++) ioPin[i] = top["pin"][i] | ioPin[i]; - flip = top[FPSTR(_flip)] | flip; - contrast = top[FPSTR(_contrast)] | contrast; - #ifndef ARDUINO_ARCH_ESP32 - refreshRate = top[FPSTR(_refreshRate)] | refreshRate; - refreshRate = min(5000, max(250, (int)refreshRate)); - #endif - screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; - sleepMode = top[FPSTR(_sleepMode)] | sleepMode; - clockMode = top[FPSTR(_clockMode)] | clockMode; - showSeconds = top[FPSTR(_showSeconds)] | showSeconds; - contrastFix = top[FPSTR(_contrastFix)] | contrastFix; - if (newType == SSD1306_SPI || newType == SSD1306_SPI64) - ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency - else - ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency - - DEBUG_PRINT(FPSTR(_name)); - if (!initDone) { - // first run: reading from cfg.json - type = newType; - DEBUG_PRINTLN(F(" config loaded.")); - } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); - // changing parameters from settings page - bool pinsChanged = false; - for (byte i=0; i<5; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; } - if (pinsChanged || type!=newType) { - if (type != NONE) delete u8x8; - PinOwner po = PinOwner::UM_FourLineDisplay; - bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); - if (isSPI) { - pinManager.deallocateMultiplePins((const uint8_t *)(&oldPin[2]), 3, po); - uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; - uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; - bool isHW = (oldPin[0]==hw_sclk && oldPin[1]==hw_mosi); - if (isHW) po = PinOwner::HW_SPI; - } else { - uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; - uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; - bool isHW = (oldPin[0]==hw_scl && oldPin[1]==hw_sda); - if (isHW) po = PinOwner::HW_I2C; - } - pinManager.deallocateMultiplePins((const uint8_t *)oldPin, 2, po); - type = newType; - setup(); - needsRedraw |= true; - } else { - u8x8->setBusClock(ioFrequency); // can be used for SPI too - setVcomh(contrastFix); - setContrast(contrast); - setFlipMode(flip); - } - knownHour = 99; - if (needsRedraw && !wakeDisplay()) redraw(true); - else overlayLogo(3500); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_contrastFix)].isNull(); - } + bool readFromConfig(JsonObject& root); /* * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). @@ -1219,4 +358,1021 @@ const char FourLineDisplayUsermod::_showSeconds[] PROGMEM = "showSeconds"; const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz"; const char FourLineDisplayUsermod::_contrastFix[] PROGMEM = "contrastFix"; +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) FourLineDisplayUsermod *FourLineDisplayUsermod::instance = nullptr; +#endif + +// some displays need this to properly apply contrast +void FourLineDisplayUsermod::setVcomh(bool highContrast) { + if (type == NONE || !enabled) return; + u8x8_t *u8x8_struct = u8x8->getU8x8(); + u8x8_cad_StartTransfer(u8x8_struct); + u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value + u8x8_cad_SendArg(u8x8_struct, highContrast ? 0x000 : 0x040); //value 0 for fix, reboot resets default back to 64 + u8x8_cad_EndTransfer(u8x8_struct); +} + +void FourLineDisplayUsermod::startDisplay() { + if (type == NONE || !enabled) return; + lineHeight = u8x8->getRows() > 4 ? 2 : 1; + DEBUG_PRINTLN(F("Starting display.")); + u8x8->setBusClock(ioFrequency); // can be used for SPI too + u8x8->begin(); + setFlipMode(flip); + setVcomh(contrastFix); + setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 + setPowerSave(0); + //drawString(0, 0, "Loading..."); + overlayLogo(3500); +} + +/** + * Wrappers for screen drawing + */ +void FourLineDisplayUsermod::setFlipMode(uint8_t mode) { + if (type == NONE || !enabled) return; + u8x8->setFlipMode(mode); +} +void FourLineDisplayUsermod::setContrast(uint8_t contrast) { + if (type == NONE || !enabled) return; + u8x8->setContrast(contrast); +} +void FourLineDisplayUsermod::drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH) { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->setFont(u8x8_font_chroma48medium8_r); + if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); + else u8x8->drawString(col, row, string); + drawing = false; +} +void FourLineDisplayUsermod::draw2x2String(uint8_t col, uint8_t row, const char *string) { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->setFont(u8x8_font_chroma48medium8_r); + u8x8->draw2x2String(col, row, string); + drawing = false; +} +void FourLineDisplayUsermod::drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH) { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->setFont(font); + if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); + else u8x8->drawGlyph(col, row, glyph); + drawing = false; +} +void FourLineDisplayUsermod::draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->setFont(font); + u8x8->draw2x2Glyph(col, row, glyph); + drawing = false; +} +uint8_t FourLineDisplayUsermod::getCols() { + if (type==NONE || !enabled) return 0; + return u8x8->getCols(); +} +void FourLineDisplayUsermod::clear() { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->clear(); + drawing = false; +} +void FourLineDisplayUsermod::setPowerSave(uint8_t save) { + if (type == NONE || !enabled) return; + u8x8->setPowerSave(save); +} + +void FourLineDisplayUsermod::center(String &line, uint8_t width) { + int len = line.length(); + if (len0; i--) line = ' ' + line; + for (byte i=line.length(); i 11) { AmPmHour -= 12; isitAM = false; } + if (AmPmHour == 0) { AmPmHour = 12; } + } + if (knownHour != hourCurrent) { + // only update date when hour changes + sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); + draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day + } + sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent); + draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds + if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time + + drawStatusIcons(); //icons power, wifi, timer, etc + + knownMinute = minuteCurrent; + knownHour = hourCurrent; + } + if (showSeconds && secondCurrent != lastSecond) { + lastSecond = secondCurrent; + draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":"); + sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); + drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line + } +} + +/** + * Enable sleep (turn the display off) or clock mode. + */ +void FourLineDisplayUsermod::sleepOrClock(bool enabled) { + if (enabled) { + displayTurnedOff = true; + if (clockMode && ntpEnabled) { + knownMinute = knownHour = 99; + showTime(); + } else + setPowerSave(1); + } else { + displayTurnedOff = false; + setPowerSave(0); + } +} + +// gets called once at boot. Do all initialization that doesn't depend on +// network here +void FourLineDisplayUsermod::setup() { + bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type == SSD1309_SPI64); + + // check if pins are -1 and disable usermod as PinManager::allocateMultiplePins() will accept -1 as a valid pin + if (isSPI) { + if (spi_sclk<0 || spi_mosi<0 || ioPin[0]<0 || ioPin[1]<0 || ioPin[1]<0) { + type = NONE; + } else { + PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; + if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type = NONE; } + } + } else { + if (i2c_scl<0 || i2c_sda<0) { type=NONE; } + } + + DEBUG_PRINTLN(F("Allocating display.")); + switch (type) { + // U8X8 uses Wire (or Wire1 with 2ND constructor) and will use existing Wire properties (calls Wire.begin() though) + case SSD1306: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(); break; + case SH1106: u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(); break; + case SSD1306_64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(); break; + case SSD1305: u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(); break; + case SSD1305_64: u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(); break; + // U8X8 uses global SPI variable that is attached to VSPI bus on ESP32 + case SSD1306_SPI: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset + case SSD1306_SPI64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset + case SSD1309_SPI64: u8x8 = (U8X8 *) new U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset + // catchall + default: u8x8 = (U8X8 *) new U8X8_NULL(); enabled = false; break; // catchall to create U8x8 instance + } + + if (nullptr == u8x8) { + DEBUG_PRINTLN(F("Display init failed.")); + if (isSPI) { + pinManager.deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay); + } + type = NONE; + return; + } + + startDisplay(); + onUpdateBegin(false); // create Display task + initDone = true; +} + +// gets called every time WiFi is (re-)connected. Initialize own network +// interfaces here +void FourLineDisplayUsermod::connected() { + knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : + knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); + networkOverlay(PSTR("NETWORK INFO"),7000); +} + +/** + * Da loop. + */ +void FourLineDisplayUsermod::loop() { +#if !(defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)) + if (!enabled || strip.isUpdating()) return; + unsigned long now = millis(); + if (now < nextUpdate) return; + nextUpdate = now + ((displayTurnedOff && clockMode && showSeconds) ? 1000 : refreshRate); + redraw(false); +#endif +} + +/** + * Redraw the screen (but only if things have changed + * or if forceRedraw). + */ +void FourLineDisplayUsermod::redraw(bool forceRedraw) { + bool needRedraw = false; + unsigned long now = millis(); + + if (type == NONE || !enabled) return; + if (overlayUntil > 0) { + if (now >= overlayUntil) { + // Time to display the overlay has elapsed. + overlayUntil = 0; + forceRedraw = true; + } else { + // We are still displaying the overlay + // Don't redraw. + return; + } + } + + while (drawing && millis()-now < 25) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; + + if (apActive && WLED_WIFI_CONFIGURED && now<15000) { + knownSsid = apSSID; + networkOverlay(PSTR("NETWORK INFO"),30000); + return; + } + + // Check if values which are shown on display changed from the last time. + if (forceRedraw) { + needRedraw = true; + clear(); + } else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon + powerON = !powerON; + drawStatusIcons(); + return; + } else if (knownnightlight != nightlightActive) { //trigger moon icon + knownnightlight = nightlightActive; + drawStatusIcons(); + if (knownnightlight) { + String timer = PSTR("Timer On"); + center(timer,LINE_BUFFER_SIZE-1); + overlay(timer.c_str(), 2500, 6); + } + return; + } else if (wificonnected != interfacesInited) { //trigger wifi icon + wificonnected = interfacesInited; + drawStatusIcons(); + return; + } else if (knownMode != effectCurrent || knownPalette != effectPalette) { + if (displayTurnedOff) needRedraw = true; + else { + if (knownPalette != effectPalette) { showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); knownPalette = effectPalette; } + if (knownMode != effectCurrent) { showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); knownMode = effectCurrent; } + lastRedraw = now; + return; + } + } else if (knownBrightness != bri) { + if (displayTurnedOff && nightlightActive) { knownBrightness = bri; } + else if (!displayTurnedOff) { updateBrightness(); lastRedraw = now; return; } + } else if (knownEffectSpeed != effectSpeed) { + if (displayTurnedOff) needRedraw = true; + else { updateSpeed(); lastRedraw = now; return; } + } else if (knownEffectIntensity != effectIntensity) { + if (displayTurnedOff) needRedraw = true; + else { updateIntensity(); lastRedraw = now; return; } + } + + if (!needRedraw) { + // Nothing to change. + // Turn off display after 1 minutes with no change. + if (sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) { + // We will still check if there is a change in redraw() + // and turn it back on if it changed. + clear(); + sleepOrClock(true); + } else if (displayTurnedOff && ntpEnabled) { + showTime(); + } + return; + } + + lastRedraw = now; + + // Turn the display back on + wakeDisplay(); + + // Update last known values. + knownBrightness = bri; + knownMode = effectCurrent; + knownPalette = effectPalette; + knownEffectSpeed = effectSpeed; + knownEffectIntensity = effectIntensity; + knownnightlight = nightlightActive; + wificonnected = interfacesInited; + + // Do the actual drawing + // First row: Icons + draw2x2GlyphIcons(); + drawArrow(); + drawStatusIcons(); + + // Second row + updateBrightness(); + updateSpeed(); + updateIntensity(); + + // Third row + showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 2); //Palette info + + // Fourth row + showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info +} + +void FourLineDisplayUsermod::updateBrightness() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + knownBrightness = bri; + if (overlayUntil == 0) { + lockRedraw = true; + brightness100 = ((uint16_t)bri*100)/255; + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), brightness100); + drawString(1, lineHeight, lineBuffer); + lockRedraw = false; + } +} + +void FourLineDisplayUsermod::updateSpeed() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + knownEffectSpeed = effectSpeed; + if (overlayUntil == 0) { + lockRedraw = true; + fxspeed100 = ((uint16_t)effectSpeed*100)/255; + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100); + drawString(5, lineHeight, lineBuffer); + lockRedraw = false; + } +} + +void FourLineDisplayUsermod::updateIntensity() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + knownEffectIntensity = effectIntensity; + if (overlayUntil == 0) { + lockRedraw = true; + fxintensity100 = ((uint16_t)effectIntensity*100)/255; + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100); + drawString(9, lineHeight, lineBuffer); + lockRedraw = false; + } +} + +void FourLineDisplayUsermod::drawStatusIcons() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + uint8_t col = 15; + uint8_t row = 0; + lockRedraw = true; + drawGlyph(col, row, (wificonnected ? 20 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // wifi icon + if (lineHeight==2) { col--; } else { row++; } + drawGlyph(col, row, (bri > 0 ? 9 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // power icon + if (lineHeight==2) { col--; } else { col = row = 0; } + drawGlyph(col, row, (nightlightActive ? 6 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // moon icon for nighlight mode + lockRedraw = false; +} + +/** + * marks the position of the arrow showing + * the current setting being changed + * pass line and colum info + */ +void FourLineDisplayUsermod::setMarkLine(byte newMarkLineNum, byte newMarkColNum) { + markLineNum = newMarkLineNum; + markColNum = newMarkColNum; +} + +//Draw the arrow for the current setting beiong changed +void FourLineDisplayUsermod::drawArrow() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + lockRedraw = true; + if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1); + lockRedraw = false; +} + +//Display the current effect or palette (desiredEntry) +// on the appropriate line (row). +void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + char lineBuffer[MAX_JSON_CHARS]; + if (overlayUntil == 0) { + lockRedraw = true; + // Find the mode name in JSON + uint8_t printedChars = extractModeName(inputEffPal, qstring, lineBuffer, MAX_JSON_CHARS-1); + if (lineBuffer[0]=='*' && lineBuffer[1]==' ') { + // remove "* " from dynamic palettes + for (byte i=2; i<=printedChars; i++) lineBuffer[i-2] = lineBuffer[i]; //include '\0' + printedChars -= 2; + } else if ((lineBuffer[0]==' ' && lineBuffer[1]>127)) { + // remove note symbol from effect names + for (byte i=5; i<=printedChars; i++) lineBuffer[i-5] = lineBuffer[i]; //include '\0' + printedChars -= 5; + } + if (lineHeight == 2) { // use this code for 8 line display + char smallBuffer1[MAX_MODE_LINE_SPACE]; + char smallBuffer2[MAX_MODE_LINE_SPACE]; + uint8_t smallChars1 = 0; + uint8_t smallChars2 = 0; + if (printedChars < MAX_MODE_LINE_SPACE) { // use big font if the text fits + while (printedChars < (MAX_MODE_LINE_SPACE-1)) lineBuffer[printedChars++]=' '; + lineBuffer[printedChars] = 0; + drawString(1, row*lineHeight, lineBuffer); + } else { // for long names divide the text into 2 lines and print them small + bool spaceHit = false; + for (uint8_t i = 0; i < printedChars; i++) { + switch (lineBuffer[i]) { + case ' ': + if (i > 4 && !spaceHit) { + spaceHit = true; + break; + } + if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; + else smallBuffer1[smallChars1++] = lineBuffer[i]; + break; + default: + if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; + else smallBuffer1[smallChars1++] = lineBuffer[i]; + break; + } + } + while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' '; + smallBuffer1[smallChars1] = 0; + drawString(1, row*lineHeight, smallBuffer1, true); + while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' '; + smallBuffer2[smallChars2] = 0; + drawString(1, row*lineHeight+1, smallBuffer2, true); + } + } else { // use this code for 4 ling displays + char smallBuffer3[MAX_MODE_LINE_SPACE+1]; // uses 1x1 icon for mode/palette + uint8_t smallChars3 = 0; + for (uint8_t i = 0; i < MAX_MODE_LINE_SPACE; i++) smallBuffer3[smallChars3++] = (i >= printedChars) ? ' ' : lineBuffer[i]; + smallBuffer3[smallChars3] = 0; + drawString(1, row*lineHeight, smallBuffer3, true); + } + lockRedraw = false; + } +} + +/** + * If there screen is off or in clock is displayed, + * this will return true. This allows us to throw away + * the first input from the rotary encoder but + * to wake up the screen. + */ +bool FourLineDisplayUsermod::wakeDisplay() { + if (type == NONE || !enabled) return false; + if (displayTurnedOff) { + #if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return false; + #endif + lockRedraw = true; + clear(); + // Turn the display back on + sleepOrClock(false); + lockRedraw = false; + return true; + } + return false; +} + +/** + * Allows you to show one line and a glyph as overlay for a period of time. + * Clears the screen and prints. + * Used in Rotary Encoder usermod. + */ +void FourLineDisplayUsermod::overlay(const char* line1, long showHowLong, byte glyphType) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + lockRedraw = true; + // Turn the display back on + if (!wakeDisplay()) clear(); + // Print the overlay + if (glyphType>0 && glyphType<255) { + if (lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_4LineDisplay_WLED_icons_6x6, true); // use 3x3 font with draw2x2Glyph() if flash runs short and comment out 6x6 font + else drawGlyph(6, 0, glyphType, u8x8_4LineDisplay_WLED_icons_3x3, true); + } + if (line1) { + String buf = line1; + center(buf, getCols()); + drawString(0, (glyphType<255?3:0)*lineHeight, buf.c_str()); + } + overlayUntil = millis() + showHowLong; + lockRedraw = false; +} + +/** + * Allows you to show Akemi WLED logo overlay for a period of time. + * Clears the screen and prints. + */ +void FourLineDisplayUsermod::overlayLogo(long showHowLong) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + lockRedraw = true; + // Turn the display back on + if (!wakeDisplay()) clear(); + // Print the overlay + if (lineHeight == 2) { + //add a bit of randomness + switch (millis()%3) { + case 0: + //WLED + draw2x2Glyph( 0, 2, 1, u8x8_wled_logo_2x2); + draw2x2Glyph( 4, 2, 2, u8x8_wled_logo_2x2); + draw2x2Glyph( 8, 2, 3, u8x8_wled_logo_2x2); + draw2x2Glyph(12, 2, 4, u8x8_wled_logo_2x2); + break; + case 1: + //WLED Akemi + drawGlyph( 2, 2, 1, u8x8_wled_logo_akemi_4x4, true); + drawGlyph( 6, 2, 2, u8x8_wled_logo_akemi_4x4, true); + drawGlyph(10, 2, 3, u8x8_wled_logo_akemi_4x4, true); + break; + case 2: + //Akemi + //draw2x2Glyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_3x3); // use this if flash runs short and comment out 6x6 font + drawGlyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_6x6, true); + drawString(6, 6, "WLED"); + break; + } + } else { + switch (millis()%3) { + case 0: + //WLED + draw2x2Glyph( 0, 0, 1, u8x8_wled_logo_2x2); + draw2x2Glyph( 4, 0, 2, u8x8_wled_logo_2x2); + draw2x2Glyph( 8, 0, 3, u8x8_wled_logo_2x2); + draw2x2Glyph(12, 0, 4, u8x8_wled_logo_2x2); + break; + case 1: + //WLED Akemi + drawGlyph( 2, 0, 1, u8x8_wled_logo_akemi_4x4); + drawGlyph( 6, 0, 2, u8x8_wled_logo_akemi_4x4); + drawGlyph(10, 0, 3, u8x8_wled_logo_akemi_4x4); + break; + case 2: + //Akemi + //drawGlyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_4x4); // a bit nicer, but uses extra 1.5k flash + draw2x2Glyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_2x2); + break; + } + } + overlayUntil = millis() + showHowLong; + lockRedraw = false; +} + +/** + * Allows you to show two lines as overlay for a period of time. + * Clears the screen and prints. + * Used in Auto Save usermod + */ +void FourLineDisplayUsermod::overlay(const char* line1, const char* line2, long showHowLong) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + lockRedraw = true; + // Turn the display back on + if (!wakeDisplay()) clear(); + // Print the overlay + if (line1) { + String buf = line1; + center(buf, getCols()); + drawString(0, 1*lineHeight, buf.c_str()); + } + if (line2) { + String buf = line2; + center(buf, getCols()); + drawString(0, 2*lineHeight, buf.c_str()); + } + overlayUntil = millis() + showHowLong; + lockRedraw = false; +} + +void FourLineDisplayUsermod::networkOverlay(const char* line1, long showHowLong) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif + lockRedraw = true; + + String line; + // Turn the display back on + if (!wakeDisplay()) clear(); + // Print the overlay + if (line1) { + line = line1; + center(line, getCols()); + drawString(0, 0, line.c_str()); + } + // Second row with Wifi name + line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); + if (line.length() < getCols()) center(line, getCols()); + drawString(0, lineHeight, line.c_str()); + // Print `~` char to indicate that SSID is longer, than our display + if (knownSsid.length() > getCols()) { + drawString(getCols() - 1, 0, "~"); + } + // Third row with IP and Password in AP Mode + line = knownIp.toString(); + center(line, getCols()); + drawString(0, lineHeight*2, line.c_str()); + line = ""; + if (apActive) { + line = apPass; + } else if (strcmp(serverDescription, "WLED") != 0) { + line = serverDescription; + } + center(line, getCols()); + drawString(0, lineHeight*3, line.c_str()); + overlayUntil = millis() + showHowLong; + lockRedraw = false; +} + + +/** + * handleButton() can be used to override default button behaviour. Returning true + * will prevent button working in a default way. + * Replicating button.cpp + */ +bool FourLineDisplayUsermod::handleButton(uint8_t b) { + yield(); + if (!enabled + || b // butto 0 only + || buttonType[b] == BTN_TYPE_SWITCH + || buttonType[b] == BTN_TYPE_NONE + || buttonType[b] == BTN_TYPE_RESERVED + || buttonType[b] == BTN_TYPE_PIR_SENSOR + || buttonType[b] == BTN_TYPE_ANALOG + || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { + return false; + } + + unsigned long now = millis(); + static bool buttonPressedBefore = false; + static bool buttonLongPressed = false; + static unsigned long buttonPressedTime = 0; + static unsigned long buttonWaitTime = 0; + bool handled = true; + + //momentary button logic + if (isButtonPressed(b)) { //pressed + + if (!buttonPressedBefore) buttonPressedTime = now; + buttonPressedBefore = true; + + if (now - buttonPressedTime > 600) { //long press + buttonLongPressed = true; + //TODO: handleButton() handles button 0 without preset in a different way for double click + //so we need to override with same behaviour + longPressAction(0); + //handled = false; + } + + } else if (!isButtonPressed(b) && buttonPressedBefore) { //released + + long dur = now - buttonPressedTime; + if (dur < 50) { + buttonPressedBefore = false; + return true; + } //too short "press", debounce + + bool doublePress = buttonWaitTime; //did we have short press before? + buttonWaitTime = 0; + + if (!buttonLongPressed) { //short press + // if this is second release within 350ms it is a double press (buttonWaitTime!=0) + //TODO: handleButton() handles button 0 without preset in a different way for double click + if (doublePress) { + networkOverlay(PSTR("NETWORK INFO"),7000); + handled = true; + } else { + buttonWaitTime = now; + } + } + buttonPressedBefore = false; + buttonLongPressed = false; + } + // if 350ms elapsed since last press/release it is a short press + if (buttonWaitTime && now - buttonWaitTime > 350 && !buttonPressedBefore) { + buttonWaitTime = 0; + //TODO: handleButton() handles button 0 without preset in a different way for double click + //so we need to override with same behaviour + shortPressAction(0); + //handled = false; + } + return handled; +} + +#if CONFIG_FREERTOS_UNICORE +#define ARDUINO_RUNNING_CORE 0 +#else +#define ARDUINO_RUNNING_CORE 1 +#endif +void FourLineDisplayUsermod::onUpdateBegin(bool init) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + if (init && Display_Task) { + vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash + } else { + // update has failed or create task requested + if (Display_Task) + vTaskResume(Display_Task); + else + xTaskCreatePinnedToCore( + [](void * par) { // Function to implement the task + // see https://www.freertos.org/vtaskdelayuntil.html + const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2; + TickType_t xLastWakeTime = xTaskGetTickCount(); + for(;;) { + delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. + // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. + vTaskDelayUntil(&xLastWakeTime, xFrequency); // release CPU, by doing nothing for REFRESH_RATE_MS millis + FourLineDisplayUsermod::getInstance()->redraw(false); + } + }, + "4LD", // Name of the task + 3072, // Stack size in words + NULL, // Task input parameter + 1, // Priority of the task (not idle) + &Display_Task, // Task handle + ARDUINO_RUNNING_CORE + ); + } +#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 FourLineDisplayUsermod::addToJsonInfo(JsonObject& root) { + //JsonObject user = root["u"]; + //if (user.isNull()) user = root.createNestedObject("u"); + //JsonArray data = user.createNestedArray(F("4LineDisplay")); + //data.add(F("Loaded.")); +//} + +/* + * 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 FourLineDisplayUsermod::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 + */ +//void FourLineDisplayUsermod::readFromJsonState(JsonObject& root) { +// if (!initDone) return; // prevent crash on boot applyPreset() +//} + +void FourLineDisplayUsermod::appendConfigData() { + oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); + oappend(SET_F("addOption(dd,'None',0);")); + 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,'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("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); +} + +/* + * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will also not yet add your setting to one of the settings pages automatically. + * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ +void FourLineDisplayUsermod::addToConfig(JsonObject& root) { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + + top["type"] = type; + JsonArray io_pin = top.createNestedArray("pin"); + for (int i=0; i<3; i++) io_pin.add(ioPin[i]); + top[FPSTR(_flip)] = (bool) flip; + top[FPSTR(_contrast)] = contrast; + top[FPSTR(_contrastFix)] = (bool) contrastFix; + #ifndef ARDUINO_ARCH_ESP32 + top[FPSTR(_refreshRate)] = refreshRate; + #endif + top[FPSTR(_screenTimeOut)] = screenTimeout/1000; + top[FPSTR(_sleepMode)] = (bool) sleepMode; + top[FPSTR(_clockMode)] = (bool) clockMode; + top[FPSTR(_showSeconds)] = (bool) showSeconds; + top[FPSTR(_busClkFrequency)] = ioFrequency/1000; + DEBUG_PRINTLN(F("4 Line Display config saved.")); +} + +/* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + */ +bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { + bool needsRedraw = false; + DisplayType newType = type; + int8_t oldPin[3]; for (byte i=0; i<3; i++) oldPin[i] = ioPin[i]; + + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINT(FPSTR(_name)); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + enabled = top[FPSTR(_enabled)] | enabled; + newType = top["type"] | newType; + for (byte i=0; i<3; i++) ioPin[i] = top["pin"][i] | ioPin[i]; + flip = top[FPSTR(_flip)] | flip; + contrast = top[FPSTR(_contrast)] | contrast; + #ifndef ARDUINO_ARCH_ESP32 + refreshRate = top[FPSTR(_refreshRate)] | refreshRate; + refreshRate = min(5000, max(250, (int)refreshRate)); + #endif + screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; + sleepMode = top[FPSTR(_sleepMode)] | sleepMode; + clockMode = top[FPSTR(_clockMode)] | clockMode; + showSeconds = top[FPSTR(_showSeconds)] | showSeconds; + contrastFix = top[FPSTR(_contrastFix)] | contrastFix; + if (newType == SSD1306_SPI || newType == SSD1306_SPI64) + ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency + else + ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency + + DEBUG_PRINT(FPSTR(_name)); + if (!initDone) { + // first run: reading from cfg.json + type = newType; + DEBUG_PRINTLN(F(" config loaded.")); + } else { + DEBUG_PRINTLN(F(" config (re)loaded.")); + // changing parameters from settings page + bool pinsChanged = false; + for (byte i=0; i<3; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; } + if (pinsChanged || type!=newType) { + bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type == SSD1309_SPI64); + bool newSPI = (newType == SSD1306_SPI || newType == SSD1306_SPI64 || newType == SSD1309_SPI64); + if (isSPI) { + if (pinsChanged || !newSPI) pinManager.deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay); + if (!newSPI) { + // was SPI but is no longer SPI + if (i2c_scl<0 || i2c_sda<0) { newType=NONE; } + } else { + // still SPI but pins changed + PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; + if (ioPin[0]<0 || ioPin[1]<0 || ioPin[1]<0) { newType=NONE; } + else if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + } + } else if (newSPI) { + // was I2C but is now SPI + if (spi_sclk<0 || spi_mosi<0) { + newType=NONE; + } else { + PinManagerPinType pins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; + if (ioPin[0]<0 || ioPin[1]<0 || ioPin[1]<0) { newType=NONE; } + else if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + } + } else { + // just I2C type changed + } + type = newType; + switch (type) { + case SSD1306: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SH1106: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_sh1106_128x64_winstar, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SSD1306_64: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SSD1305: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x32_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SSD1305_64: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SSD1306_SPI: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset + break; + case SSD1306_SPI64: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset + break; + case SSD1309_SPI64: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1309_128x64_noname0, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset + default: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_null_cb, u8x8_cad_empty, u8x8_byte_empty, u8x8_dummy_cb); + enabled = false; + break; + } + startDisplay(); + needsRedraw |= true; + } else { + u8x8->setBusClock(ioFrequency); // can be used for SPI too + setVcomh(contrastFix); + setContrast(contrast); + setFlipMode(flip); + } + knownHour = 99; + if (needsRedraw && !wakeDisplay()) redraw(true); + else overlayLogo(3500); + } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !top[FPSTR(_contrastFix)].isNull(); +} diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 35d3e6f32..b142f9037 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -45,7 +45,27 @@ #define ENCODER_SW_PIN 19 #endif -// The last UI state, remove color and saturation option if diplay not active(too many options) +#ifndef ENCODER_MAX_DELAY_MS // max delay between polling encoder pins +#define ENCODER_MAX_DELAY_MS 8 // 8 milliseconds => max 120 change impulses in 1 second, for full turn of a 30/30 encoder (4 changes per segment, 30 segments for one turn) +#endif + +#ifndef USERMOD_USE_PCF8574 + #undef USE_PCF8574 + #define USE_PCF8574 false +#else + #undef USE_PCF8574 + #define USE_PCF8574 true +#endif + +#ifndef PCF8574_ADDRESS + #define PCF8574_ADDRESS 0x20 // some may start at 0x38 +#endif + +#ifndef PCF8574_INT_PIN + #define PCF8574_INT_PIN -1 // GPIO connected to INT pin on PCF8574 +#endif + +// The last UI state, remove color and saturation option if display not active (too many options) #ifdef USERMOD_FOUR_LINE_DISPLAY #define LAST_UI_STATE 11 #else @@ -113,166 +133,370 @@ static int re_qstringCmp(const void *ap, const void *bp) { } +static volatile uint8_t pcfPortData = 0; // port expander port state +static volatile uint8_t addrPcf8574 = PCF8574_ADDRESS; // has to be accessible in ISR + +// Interrupt routine to read I2C rotary state +// if we are to use PCF8574 port expander we will need to rely on interrupts as polling I2C every 2ms +// is a waste of resources and causes 4LD to fail. +// in such case rely on ISR to read pin values and store them into static variable +static void IRAM_ATTR i2cReadingISR() { + Wire.requestFrom(addrPcf8574, 1U); + if (Wire.available()) { + pcfPortData = Wire.read(); + } +} + + class RotaryEncoderUIUsermod : public Usermod { -private: - int8_t fadeAmount = 5; // Amount to change every step (brightness) - unsigned long loopTime; - unsigned long buttonPressedTime = 0; - unsigned long buttonWaitTime = 0; - bool buttonPressedBefore = false; - bool buttonLongPressed = false; + private: - int8_t pinA = ENCODER_DT_PIN; // DT from encoder - int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder - int8_t pinC = ENCODER_SW_PIN; // SW from encoder + const int8_t fadeAmount; // Amount to change every step (brightness) + unsigned long loopTime; - unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed, ... + unsigned long buttonPressedTime; + unsigned long buttonWaitTime; + bool buttonPressedBefore; + bool buttonLongPressed; - uint16_t currentHue1 = 16; // default boot color - byte currentSat1 = 255; - -#ifdef USERMOD_FOUR_LINE_DISPLAY - FourLineDisplayUsermod *display; -#else - void* display = nullptr; + int8_t pinA; // DT from encoder + int8_t pinB; // CLK from encoder + int8_t pinC; // SW from encoder + + unsigned char select_state; // 0: brightness, 1: effect, 2: effect speed, ... + + uint16_t currentHue1; // default boot color + byte currentSat1; + uint8_t currentCCT; + + #ifdef USERMOD_FOUR_LINE_DISPLAY + FourLineDisplayUsermod *display; + #else + void* display; + #endif + + // Pointers the start of the mode names within JSON_mode_names + const char **modes_qstrings; + + // Array of mode indexes in alphabetical order. + byte *modes_alpha_indexes; + + // Pointers the start of the palette names within JSON_palette_names + const char **palettes_qstrings; + + // Array of palette indexes in alphabetical order. + byte *palettes_alpha_indexes; + + struct { // reduce memory footprint + bool Enc_A : 1; + bool Enc_B : 1; + bool Enc_A_prev : 1; + }; + + bool currentEffectAndPaletteInitialized; + uint8_t effectCurrentIndex; + uint8_t effectPaletteIndex; + uint8_t knownMode; + uint8_t knownPalette; + + byte presetHigh; + byte presetLow; + + bool applyToAll; + + bool initDone; + bool enabled; + + bool usePcf8574; + int8_t pinIRQ; + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _DT_pin[]; + static const char _CLK_pin[]; + static const char _SW_pin[]; + static const char _presetHigh[]; + static const char _presetLow[]; + static const char _applyToAll[]; + static const char _pcf8574[]; + static const char _pcfAddress[]; + static const char _pcfINTpin[]; + + /** + * readPin() - read rotary encoder pin value + */ + byte readPin(uint8_t pin); + + /** + * Sort the modes and palettes to the index arrays + * modes_alpha_indexes and palettes_alpha_indexes. + */ + void sortModesAndPalettes(); + byte *re_initIndexArray(int numModes); + + /** + * Return an array of mode or palette names from the JSON string. + * They don't end in '\0', they end in '"'. + */ + const char **re_findModeStrings(const char json[], int numModes); + + /** + * Sort either the modes or the palettes using quicksort. + */ + void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip); + + public: + + RotaryEncoderUIUsermod() + : fadeAmount(5) + , buttonPressedTime(0) + , buttonWaitTime(0) + , buttonPressedBefore(false) + , buttonLongPressed(false) + , pinA(ENCODER_DT_PIN) + , pinB(ENCODER_CLK_PIN) + , pinC(ENCODER_SW_PIN) + , select_state(0) + , currentHue1(16) + , currentSat1(255) + , currentCCT(128) + , display(nullptr) + , modes_qstrings(nullptr) + , modes_alpha_indexes(nullptr) + , palettes_qstrings(nullptr) + , palettes_alpha_indexes(nullptr) + , currentEffectAndPaletteInitialized(false) + , effectCurrentIndex(0) + , effectPaletteIndex(0) + , knownMode(0) + , knownPalette(0) + , presetHigh(0) + , presetLow(0) + , applyToAll(true) + , initDone(false) + , enabled(true) + , usePcf8574(USE_PCF8574) + , pinIRQ(PCF8574_INT_PIN) + {} + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() { return USERMOD_ID_ROTARY_ENC_UI; } + /** + * Enable/Disable the usermod + */ + inline void enable(bool enable) { if (!(pinA<0 || pinB<0 || pinC<0)) enabled = enable; } + + /** + * Get usermod enabled/disabled state + */ + inline bool isEnabled() { return enabled; } + + /** + * setup() is called once at boot. WiFi is not yet connected at this point. + * You can use it to initialize variables, sensors or similar. + */ + void setup(); + + /** + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + //void connected(); + + /** + * loop() is called continuously. Here you can check for events, read sensors, etc. + */ + void loop(); + +#ifndef WLED_DISABLE_MQTT + //bool onMqttMessage(char* topic, char* payload); + //void onMqttConnect(bool sessionPresent); #endif - // Pointers the start of the mode names within JSON_mode_names - const char **modes_qstrings = nullptr; + /** + * handleButton() can be used to override default button behaviour. Returning true + * will prevent button working in a default way. + * Replicating button.cpp + */ + //bool handleButton(uint8_t b); - // Array of mode indexes in alphabetical order. - byte *modes_alpha_indexes = nullptr; + /** + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + */ + //void addToJsonInfo(JsonObject &root); - // Pointers the start of the palette names within JSON_palette_names - const char **palettes_qstrings = nullptr; + /** + * 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); - // Array of palette indexes in alphabetical order. - byte *palettes_alpha_indexes = nullptr; + /** + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + //void readFromJsonState(JsonObject &root); - unsigned char Enc_A; - unsigned char Enc_B; - unsigned char Enc_A_prev = 0; + /** + * provide the changeable values + */ + void addToConfig(JsonObject &root); - bool currentEffectAndPaletteInitialized = false; - uint8_t effectCurrentIndex = 0; - uint8_t effectPaletteIndex = 0; - uint8_t knownMode = 0; - uint8_t knownPalette = 0; + void appendConfigData(); - uint8_t currentCCT = 128; + /** + * restore the changeable values + * 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); - byte presetHigh = 0; - byte presetLow = 0; + // custom methods + void displayNetworkInfo(); + void findCurrentEffectAndPalette(); + bool changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph); + void lampUdated(); + void changeBrightness(bool increase); + void changeEffect(bool increase); + void changeEffectSpeed(bool increase); + void changeEffectIntensity(bool increase); + void changeCustom(uint8_t par, bool increase); + void changePalette(bool increase); + void changeHue(bool increase); + void changeSat(bool increase); + void changePreset(bool increase); + void changeCCT(bool increase); +}; - bool applyToAll = true; - bool initDone = false; - bool enabled = true; - - // strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _enabled[]; - static const char _DT_pin[]; - static const char _CLK_pin[]; - static const char _SW_pin[]; - static const char _presetHigh[]; - static const char _presetLow[]; - static const char _applyToAll[]; - - /** - * Sort the modes and palettes to the index arrays - * modes_alpha_indexes and palettes_alpha_indexes. - */ - void sortModesAndPalettes() { - //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); - modes_qstrings = strip.getModeDataSrc(); - modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); - re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); - - palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()); - palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); // only use internal palettes - - // How many palette names start with '*' and should not be sorted? - // (Also skipping the first one, 'Default'). - int skipPaletteCount = 1; - while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount++]) == '*') ; - re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); +/** + * readPin() - read rotary encoder pin value + */ +byte RotaryEncoderUIUsermod::readPin(uint8_t pin) { + if (usePcf8574) { + if (pin >= 100) pin -= 100; // PCF I/O ports + return (pcfPortData>>pin) & 1; + } else { + return digitalRead(pin); } +} - byte *re_initIndexArray(int numModes) { - byte *indexes = (byte *)malloc(sizeof(byte) * numModes); - for (byte i = 0; i < numModes; i++) { - indexes[i] = i; +/** + * Sort the modes and palettes to the index arrays + * modes_alpha_indexes and palettes_alpha_indexes. + */ +void RotaryEncoderUIUsermod::sortModesAndPalettes() { + DEBUG_PRINTLN(F("Sorting modes and palettes.")); + //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); + modes_qstrings = strip.getModeDataSrc(); + modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); + re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); + + palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()); + palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); // only use internal palettes + + // How many palette names start with '*' and should not be sorted? + // (Also skipping the first one, 'Default'). + int skipPaletteCount = 1; + while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount++]) == '*') ; + re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); +} + +byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) { + byte *indexes = (byte *)malloc(sizeof(byte) * numModes); + for (byte i = 0; i < numModes; i++) { + indexes[i] = i; + } + return indexes; +} + +/** + * Return an array of mode or palette names from the JSON string. + * They don't end in '\0', they end in '"'. + */ +const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int numModes) { + const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes); + uint8_t modeIndex = 0; + bool insideQuotes = false; + // advance past the mark for markLineNum that may exist. + char singleJsonSymbol; + + // Find the mode name in JSON + bool complete = false; + for (size_t i = 0; i < strlen_P(json); i++) { + singleJsonSymbol = pgm_read_byte_near(json + i); + if (singleJsonSymbol == '\0') break; + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + if (insideQuotes) { + // We have a new mode or palette + modeStrings[modeIndex] = (char *)(json + i + 1); + } + break; + case '[': + break; + case ']': + if (!insideQuotes) complete = true; + break; + case ',': + if (!insideQuotes) modeIndex++; + default: + if (!insideQuotes) break; } - return indexes; + if (complete) break; } + return modeStrings; +} - /** - * Return an array of mode or palette names from the JSON string. - * They don't end in '\0', they end in '"'. - */ - const char **re_findModeStrings(const char json[], int numModes) { - const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes); - uint8_t modeIndex = 0; - bool insideQuotes = false; - // advance past the mark for markLineNum that may exist. - char singleJsonSymbol; +/** + * Sort either the modes or the palettes using quicksort. + */ +void RotaryEncoderUIUsermod::re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) { + if (!modeNames) return; + listBeingSorted = modeNames; + qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp); + listBeingSorted = nullptr; +} - // Find the mode name in JSON - bool complete = false; - for (size_t i = 0; i < strlen_P(json); i++) { - singleJsonSymbol = pgm_read_byte_near(json + i); - if (singleJsonSymbol == '\0') break; - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - if (insideQuotes) { - // We have a new mode or palette - modeStrings[modeIndex] = (char *)(json + i + 1); - } - break; - case '[': - break; - case ']': - if (!insideQuotes) complete = true; - break; - case ',': - if (!insideQuotes) modeIndex++; - default: - if (!insideQuotes) break; + +// public methods + + +/* + * setup() is called once at boot. WiFi is not yet connected at this point. + * You can use it to initialize variables, sensors or similar. + */ +void RotaryEncoderUIUsermod::setup() +{ + DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); + + if (usePcf8574) { + if (i2c_sda < 0 || i2c_scl < 0 || pinA < 0 || pinB < 0 || pinC < 0) { + DEBUG_PRINTLN(F("I2C and/or PCF8574 pins unused, disabling.")); + enabled = false; + return; + } else { + if (pinIRQ >= 0 && pinManager.allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) { + pinMode(pinIRQ, INPUT_PULLUP); + attachInterrupt(pinIRQ, i2cReadingISR, FALLING); // RISING, FALLING, CHANGE, ONLOW, ONHIGH + DEBUG_PRINTLN(F("Interrupt attached.")); + } else { + DEBUG_PRINTLN(F("Unable to allocate interrupt pin, disabling.")); + pinIRQ = -1; + enabled = false; + return; } - if (complete) break; } - return modeStrings; - } - - /** - * Sort either the modes or the palettes using quicksort. - */ - void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) { - if (!modeNames) return; - listBeingSorted = modeNames; - qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp); - listBeingSorted = nullptr; - } - - -public: - /* - * setup() is called once at boot. WiFi is not yet connected at this point. - * You can use it to initialize variables, sensors or similar. - */ - void setup() - { - DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); + } else { PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { - // BUG: configuring this usermod with conflicting pins - // will cause it to de-allocate pins it does not own - // (at second config) - // This is the exact type of bug solved by pinManager - // tracking the owner tags.... pinA = pinB = pinC = -1; enabled = false; return; @@ -284,638 +508,648 @@ public: pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO); + } - loopTime = millis(); + loopTime = millis(); - currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5; + currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5; - if (!initDone) sortModesAndPalettes(); + if (!initDone) sortModesAndPalettes(); #ifdef USERMOD_FOUR_LINE_DISPLAY - // This Usermod uses FourLineDisplayUsermod for the best experience. - // But it's optional. But you want it. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); - if (display != nullptr) { - display->setMarkLine(1, 0); - } + // This Usermod uses FourLineDisplayUsermod for the best experience. + // But it's optional. But you want it. + display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + if (display != nullptr) { + display->setMarkLine(1, 0); + } #endif - initDone = true; - Enc_A = digitalRead(pinA); // Read encoder pins - Enc_B = digitalRead(pinB); - Enc_A_prev = Enc_A; + initDone = true; + Enc_A = readPin(pinA); // Read encoder pins + Enc_B = readPin(pinB); + Enc_A_prev = Enc_A; +} + +/* + * loop() is called continuously. Here you can check for events, read sensors, etc. + * + * Tips: + * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. + * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. + * + * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. + * Instead, use a timer check as shown here. + */ +void RotaryEncoderUIUsermod::loop() +{ + if (!enabled) return; + unsigned long currentTime = millis(); // get the current elapsed time + if (strip.isUpdating() && ((currentTime - loopTime) < ENCODER_MAX_DELAY_MS)) return; // be nice, but not too nice + + // Initialize effectCurrentIndex and effectPaletteIndex to + // current state. We do it here as (at least) effectCurrent + // is not yet initialized when setup is called. + + if (!currentEffectAndPaletteInitialized) { + findCurrentEffectAndPalette(); } - /* - * connected() is called every time the WiFi is (re)connected - * Use it to initialize network interfaces - */ - void connected() + if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) { + DEBUG_PRINTLN(F("Current mode or palette changed.")); + currentEffectAndPaletteInitialized = false; + } + + if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz { - //Serial.println("Connected to WiFi!"); + bool buttonPressed = !readPin(pinC); //0=pressed, 1=released + if (buttonPressed) { + if (!buttonPressedBefore) buttonPressedTime = currentTime; + buttonPressedBefore = true; + if (currentTime-buttonPressedTime > 3000) { + if (!buttonLongPressed) displayNetworkInfo(); //long press for network info + buttonLongPressed = true; + } + } else if (!buttonPressed && buttonPressedBefore) { + bool doublePress = buttonWaitTime; + buttonWaitTime = 0; + if (!buttonLongPressed) { + if (doublePress) { + toggleOnOff(); + lampUdated(); + } else { + buttonWaitTime = currentTime; + } + } + buttonLongPressed = false; + buttonPressedBefore = false; + } + if (buttonWaitTime && currentTime-buttonWaitTime>350 && !buttonPressedBefore) { //same speed as in button.cpp + buttonWaitTime = 0; + char newState = select_state + 1; + bool changedState = false; + char lineBuffer[64]; + do { + // finde new state + switch (newState) { + case 0: strcpy_P(lineBuffer, PSTR("Brightness")); changedState = true; break; + case 1: if (!extractModeSlider(effectCurrent, 0, lineBuffer, 63)) newState++; else changedState = true; break; // speed + case 2: if (!extractModeSlider(effectCurrent, 1, lineBuffer, 63)) newState++; else changedState = true; break; // intensity + case 3: strcpy_P(lineBuffer, PSTR("Color Palette")); changedState = true; break; + case 4: strcpy_P(lineBuffer, PSTR("Effect")); changedState = true; break; + case 5: strcpy_P(lineBuffer, PSTR("Main Color")); changedState = true; break; + case 6: strcpy_P(lineBuffer, PSTR("Saturation")); changedState = true; break; + case 7: + if (!(strip.getSegment(applyToAll ? strip.getFirstSelectedSegId() : strip.getMainSegmentId()).getLightCapabilities() & 0x04)) newState++; + else { strcpy_P(lineBuffer, PSTR("CCT")); changedState = true; } + break; + case 8: if (presetHigh==0 || presetLow == 0) newState++; else { strcpy_P(lineBuffer, PSTR("Preset")); changedState = true; } break; + case 9: + case 10: + case 11: if (!extractModeSlider(effectCurrent, newState-7, lineBuffer, 63)) newState++; else changedState = true; break; // custom + } + if (newState > LAST_UI_STATE) newState = 0; + } while (!changedState); + if (display != nullptr) { + switch (newState) { + case 0: changedState = changeState(lineBuffer, 1, 0, 1); break; //1 = sun + case 1: changedState = changeState(lineBuffer, 1, 4, 2); break; //2 = skip forward + case 2: changedState = changeState(lineBuffer, 1, 8, 3); break; //3 = fire + case 3: changedState = changeState(lineBuffer, 2, 0, 4); break; //4 = custom palette + case 4: changedState = changeState(lineBuffer, 3, 0, 5); break; //5 = puzzle piece + case 5: changedState = changeState(lineBuffer, 255, 255, 7); break; //7 = brush + case 6: changedState = changeState(lineBuffer, 255, 255, 8); break; //8 = contrast + case 7: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + case 8: changedState = changeState(lineBuffer, 255, 255, 11); break; //11 = heart + case 9: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + case 10: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + case 11: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + } + } + if (changedState) select_state = newState; + } + + Enc_A = readPin(pinA); // Read encoder pins + Enc_B = readPin(pinB); + if ((Enc_A) && (!Enc_A_prev)) + { // A has gone from high to low + if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse + { // B is high so clockwise + switch(select_state) { + case 0: changeBrightness(true); break; + case 1: changeEffectSpeed(true); break; + case 2: changeEffectIntensity(true); break; + case 3: changePalette(true); break; + case 4: changeEffect(true); break; + case 5: changeHue(true); break; + case 6: changeSat(true); break; + case 7: changeCCT(true); break; + case 8: changePreset(true); break; + case 9: changeCustom(1,true); break; + case 10: changeCustom(2,true); break; + case 11: changeCustom(3,true); break; + } + } + else if (Enc_B == HIGH) + { // B is low so counter-clockwise + switch(select_state) { + case 0: changeBrightness(false); break; + case 1: changeEffectSpeed(false); break; + case 2: changeEffectIntensity(false); break; + case 3: changePalette(false); break; + case 4: changeEffect(false); break; + case 5: changeHue(false); break; + case 6: changeSat(false); break; + case 7: changeCCT(false); break; + case 8: changePreset(false); break; + case 9: changeCustom(1,false); break; + case 10: changeCustom(2,false); break; + case 11: changeCustom(3,false); break; + } + } + } + Enc_A_prev = Enc_A; // Store value of A for next time + loopTime = currentTime; // Updates loopTime } +} - /* - * loop() is called continuously. Here you can check for events, read sensors, etc. - * - * Tips: - * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. - * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. - * - * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. - * Instead, use a timer check as shown here. - */ - void loop() - { - if (!enabled || strip.isUpdating()) return; - unsigned long currentTime = millis(); // get the current elapsed time - - // Initialize effectCurrentIndex and effectPaletteIndex to - // current state. We do it here as (at least) effectCurrent - // is not yet initialized when setup is called. - - if (!currentEffectAndPaletteInitialized) { - findCurrentEffectAndPalette(); - } - - if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) { - currentEffectAndPaletteInitialized = false; - } - - if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz - { - loopTime = currentTime; // Updates loopTime - - bool buttonPressed = !digitalRead(pinC); //0=pressed, 1=released - if (buttonPressed) { - if (!buttonPressedBefore) buttonPressedTime = currentTime; - buttonPressedBefore = true; - if (currentTime-buttonPressedTime > 3000) { - if (!buttonLongPressed) displayNetworkInfo(); //long press for network info - buttonLongPressed = true; - } - } else if (!buttonPressed && buttonPressedBefore) { - bool doublePress = buttonWaitTime; - buttonWaitTime = 0; - if (!buttonLongPressed) { - if (doublePress) { - toggleOnOff(); - lampUdated(); - } else { - buttonWaitTime = currentTime; - } - } - buttonLongPressed = false; - buttonPressedBefore = false; - } - if (buttonWaitTime && currentTime-buttonWaitTime>350 && !buttonPressedBefore) { //same speed as in button.cpp - buttonWaitTime = 0; - char newState = select_state + 1; - bool changedState = false; - char lineBuffer[64]; - do { - // finde new state - switch (newState) { - case 0: strcpy_P(lineBuffer, PSTR("Brightness")); changedState = true; break; - case 1: if (!extractModeSlider(effectCurrent, 0, lineBuffer, 63)) newState++; else changedState = true; break; // speed - case 2: if (!extractModeSlider(effectCurrent, 1, lineBuffer, 63)) newState++; else changedState = true; break; // intensity - case 3: strcpy_P(lineBuffer, PSTR("Color Palette")); changedState = true; break; - case 4: strcpy_P(lineBuffer, PSTR("Effect")); changedState = true; break; - case 5: strcpy_P(lineBuffer, PSTR("Main Color")); changedState = true; break; - case 6: strcpy_P(lineBuffer, PSTR("Saturation")); changedState = true; break; - case 7: - if (!(strip.getSegment(applyToAll ? strip.getFirstSelectedSegId() : strip.getMainSegmentId()).getLightCapabilities() & 0x04)) newState++; - else { strcpy_P(lineBuffer, PSTR("CCT")); changedState = true; } - break; - case 8: if (presetHigh==0 || presetLow == 0) newState++; else { strcpy_P(lineBuffer, PSTR("Preset")); changedState = true; } break; - case 9: - case 10: - case 11: if (!extractModeSlider(effectCurrent, newState-7, lineBuffer, 63)) newState++; else changedState = true; break; // custom - } - if (newState > LAST_UI_STATE) newState = 0; - } while (!changedState); - if (display != nullptr) { - switch (newState) { - case 0: changedState = changeState(lineBuffer, 1, 0, 1); break; //1 = sun - case 1: changedState = changeState(lineBuffer, 1, 4, 2); break; //2 = skip forward - case 2: changedState = changeState(lineBuffer, 1, 8, 3); break; //3 = fire - case 3: changedState = changeState(lineBuffer, 2, 0, 4); break; //4 = custom palette - case 4: changedState = changeState(lineBuffer, 3, 0, 5); break; //5 = puzzle piece - case 5: changedState = changeState(lineBuffer, 255, 255, 7); break; //7 = brush - case 6: changedState = changeState(lineBuffer, 255, 255, 8); break; //8 = contrast - case 7: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star - case 8: changedState = changeState(lineBuffer, 255, 255, 11); break; //11 = heart - case 9: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star - case 10: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star - case 11: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star - } - } - if (changedState) select_state = newState; - } - - Enc_A = digitalRead(pinA); // Read encoder pins - Enc_B = digitalRead(pinB); - if ((Enc_A) && (!Enc_A_prev)) - { // A has gone from high to low - if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse - { // B is high so clockwise - switch(select_state) { - case 0: changeBrightness(true); break; - case 1: changeEffectSpeed(true); break; - case 2: changeEffectIntensity(true); break; - case 3: changePalette(true); break; - case 4: changeEffect(true); break; - case 5: changeHue(true); break; - case 6: changeSat(true); break; - case 7: changeCCT(true); break; - case 8: changePreset(true); break; - case 9: changeCustom(1,true); break; - case 10: changeCustom(2,true); break; - case 11: changeCustom(3,true); break; - } - } - else if (Enc_B == HIGH) - { // B is low so counter-clockwise - switch(select_state) { - case 0: changeBrightness(false); break; - case 1: changeEffectSpeed(false); break; - case 2: changeEffectIntensity(false); break; - case 3: changePalette(false); break; - case 4: changeEffect(false); break; - case 5: changeHue(false); break; - case 6: changeSat(false); break; - case 7: changeCCT(false); break; - case 8: changePreset(false); break; - case 9: changeCustom(1,false); break; - case 10: changeCustom(2,false); break; - case 11: changeCustom(3,false); break; - } - } - } - Enc_A_prev = Enc_A; // Store value of A for next time - } - } - - void displayNetworkInfo() { - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->networkOverlay(PSTR("NETWORK INFO"), 10000); - #endif - } - - void findCurrentEffectAndPalette() { - currentEffectAndPaletteInitialized = true; - for (uint8_t i = 0; i < strip.getModeCount(); i++) { - if (modes_alpha_indexes[i] == effectCurrent) { - effectCurrentIndex = i; - break; - } - } - - for (uint8_t i = 0; i < strip.getPaletteCount(); i++) { - if (palettes_alpha_indexes[i] == effectPalette) { - effectPaletteIndex = i; - break; - } - } - } - - boolean changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) { +void RotaryEncoderUIUsermod::displayNetworkInfo() { #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display != nullptr) { - if (display->wakeDisplay()) { - // Throw away wake up input - display->redraw(true); - return false; - } - display->overlay(stateName, 750, glyph); - display->setMarkLine(markedLine, markedCol); - } + display->networkOverlay(PSTR("NETWORK INFO"), 10000); #endif - return true; - } +} - void lampUdated() { - //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) - // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa - //setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required) - stateUpdated(CALL_MODE_BUTTON); - updateInterfaces(CALL_MODE_BUTTON); +void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() { + DEBUG_PRINTLN(F("Finding current mode and palette.")); + currentEffectAndPaletteInitialized = true; + for (uint8_t i = 0; i < strip.getModeCount(); i++) { + if (modes_alpha_indexes[i] == effectCurrent) { + effectCurrentIndex = i; + break; + } } + DEBUG_PRINTLN(F("Found current mode.")); - void changeBrightness(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); + for (uint8_t i = 0; i < strip.getPaletteCount(); i++) { + if (palettes_alpha_indexes[i] == effectPalette) { + effectPaletteIndex = i; + break; + } + } + DEBUG_PRINTLN(F("Found palette.")); +} + +bool RotaryEncoderUIUsermod::changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display != nullptr) { + if (display->wakeDisplay()) { // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0); - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->updateBrightness(); - #endif - } - - - void changeEffect(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { display->redraw(true); - // Throw away wake up input - return; + return false; } - display->updateRedrawTime(); - #endif - effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0); - effectCurrent = modes_alpha_indexes[effectCurrentIndex]; - stateChanged = true; - if (applyToAll) { - for (byte i=0; ioverlay(stateName, 750, glyph); + display->setMarkLine(markedLine, markedCol); + } +#endif + return true; +} + +void RotaryEncoderUIUsermod::lampUdated() { + //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) + // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa + //setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required) + stateUpdated(CALL_MODE_BUTTON); + updateInterfaces(CALL_MODE_BUTTON); +} + +void RotaryEncoderUIUsermod::changeBrightness(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0); + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateBrightness(); +#endif +} + + +void RotaryEncoderUIUsermod::changeEffect(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0); + effectCurrent = modes_alpha_indexes[effectCurrentIndex]; + stateChanged = true; + if (applyToAll) { + for (byte i=0; ishowCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.setMode(effectCurrent); } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); +#endif +} - void changeEffectSpeed(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0); - stateChanged = true; - if (applyToAll) { - for (byte i=0; iwakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0); + stateChanged = true; + if (applyToAll) { + for (byte i=0; iupdateSpeed(); - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.speed = effectSpeed; } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateSpeed(); +#endif +} - void changeEffectIntensity(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0); - stateChanged = true; - if (applyToAll) { - for (byte i=0; iwakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0); + stateChanged = true; + if (applyToAll) { + for (byte i=0; iupdateIntensity(); - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.intensity = effectIntensity; } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateIntensity(); +#endif +} - void changeCustom(uint8_t par, bool increase) { - uint8_t val = 0; - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - stateChanged = true; - if (applyToAll) { - uint8_t id = strip.getFirstSelectedSegId(); - Segment& sid = strip.getSegment(id); - switch (par) { - case 3: val = sid.custom3 = max(min((increase ? sid.custom3+fadeAmount : sid.custom3-fadeAmount), 255), 0); break; - case 2: val = sid.custom2 = max(min((increase ? sid.custom2+fadeAmount : sid.custom2-fadeAmount), 255), 0); break; - default: val = sid.custom1 = max(min((increase ? sid.custom1+fadeAmount : sid.custom1-fadeAmount), 255), 0); break; - } - for (byte i=0; ioverlay(lineBuffer, 500, 10); // use star - #endif +void RotaryEncoderUIUsermod::changeCustom(uint8_t par, bool increase) { + uint8_t val = 0; +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; } - - - void changePalette(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; + display->updateRedrawTime(); +#endif + stateChanged = true; + if (applyToAll) { + uint8_t id = strip.getFirstSelectedSegId(); + Segment& sid = strip.getSegment(id); + switch (par) { + case 3: val = sid.custom3 = max(min((increase ? sid.custom3+fadeAmount : sid.custom3-fadeAmount), 255), 0); break; + case 2: val = sid.custom2 = max(min((increase ? sid.custom2+fadeAmount : sid.custom2-fadeAmount), 255), 0); break; + default: val = sid.custom1 = max(min((increase ? sid.custom1+fadeAmount : sid.custom1-fadeAmount), 255), 0); break; } - display->updateRedrawTime(); - #endif - effectPaletteIndex = max(min((increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()-1), 0); - effectPalette = palettes_alpha_indexes[effectPaletteIndex]; - stateChanged = true; - if (applyToAll) { - for (byte i=0; ioverlay(lineBuffer, 500, 10); // use star +#endif +} + + +void RotaryEncoderUIUsermod::changePalette(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + effectPaletteIndex = max(min((increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()-1), 0); + effectPalette = palettes_alpha_indexes[effectPaletteIndex]; + stateChanged = true; + if (applyToAll) { + for (byte i=0; ishowCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.setPalette(effectPalette); } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); +#endif +} - void changeHue(bool increase){ - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0); - colorHStoRGB(currentHue1*256, currentSat1, col); - stateChanged = true; - if (applyToAll) { - for (byte i=0; iwakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0); + colorHStoRGB(currentHue1*256, currentSat1, col); + stateChanged = true; + if (applyToAll) { + for (byte i=0; ioverlay(lineBuffer, 500, 7); // use brush - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]); } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + char lineBuffer[64]; + sprintf(lineBuffer, "%d", currentHue1); + display->overlay(lineBuffer, 500, 7); // use brush +#endif +} - void changeSat(bool increase){ - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0); - colorHStoRGB(currentHue1*256, currentSat1, col); - if (applyToAll) { - for (byte i=0; iwakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0); + colorHStoRGB(currentHue1*256, currentSat1, col); + if (applyToAll) { + for (byte i=0; ioverlay(lineBuffer, 500, 8); // use contrast - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]); } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + char lineBuffer[64]; + sprintf(lineBuffer, "%d", currentSat1); + display->overlay(lineBuffer, 500, 8); // use contrast +#endif +} - void changePreset(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - if (presetHigh && presetLow && presetHigh > presetLow) { - StaticJsonDocument<64> root; - char str[64]; - sprintf_P(str, PSTR("%d~%d~%s"), presetLow, presetHigh, increase?"":"-"); - root["ps"] = str; - deserializeState(root.as(), CALL_MODE_BUTTON_PRESET); +void RotaryEncoderUIUsermod::changePreset(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + if (presetHigh && presetLow && presetHigh > presetLow) { + StaticJsonDocument<64> root; + char str[64]; + sprintf_P(str, PSTR("%d~%d~%s"), presetLow, presetHigh, increase?"":"-"); + root["ps"] = str; + deserializeState(root.as(), CALL_MODE_BUTTON_PRESET); /* - String apireq = F("win&PL=~"); - if (!increase) apireq += '-'; - apireq += F("&P1="); - apireq += presetLow; - apireq += F("&P2="); - apireq += presetHigh; - handleSet(nullptr, apireq, false); + String apireq = F("win&PL=~"); + if (!increase) apireq += '-'; + apireq += F("&P1="); + apireq += presetLow; + apireq += F("&P2="); + apireq += presetHigh; + handleSet(nullptr, apireq, false); */ - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - sprintf(str, "%d", currentPreset); - display->overlay(str, 500, 11); // use heart - #endif - } - } - - void changeCCT(bool increase){ + lampUdated(); #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); + sprintf(str, "%d", currentPreset); + display->overlay(str, 500, 11); // use heart #endif - currentCCT = max(min((increase ? currentCCT+fadeAmount : currentCCT-fadeAmount), 255), 0); + } +} + +void RotaryEncoderUIUsermod::changeCCT(bool increase){ +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + currentCCT = max(min((increase ? currentCCT+fadeAmount : currentCCT-fadeAmount), 255), 0); // if (applyToAll) { - for (byte i=0; ioverlay(lineBuffer, 500, 10); // use star - #endif - } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + char lineBuffer[64]; + sprintf(lineBuffer, "%d", currentCCT); + display->overlay(lineBuffer, 500, 10); // use star +#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 addToJsonInfo(JsonObject& root) - { - int reading = 20; - //this code adds "u":{"Light":[20," lux"]} to the info object - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - JsonArray lightArr = user.createNestedArray("Light"); //name - lightArr.add(reading); //value - lightArr.add(" lux"); //unit - } +/* + * 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 RotaryEncoderUIUsermod::addToJsonInfo(JsonObject& root) +{ + int reading = 20; + //this code adds "u":{"Light":[20," lux"]} to the info object + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + JsonArray lightArr = user.createNestedArray("Light"); //name + lightArr.add(reading); //value + lightArr.add(" lux"); //unit +} +*/ - /* - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - /* - void addToJsonState(JsonObject &root) - { - //root["user0"] = userVar0; - } +/* + * 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 RotaryEncoderUIUsermod::addToJsonState(JsonObject &root) +{ + //root["user0"] = userVar0; +} +*/ - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - /* - void readFromJsonState(JsonObject &root) - { - //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value - //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); - } +/* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients */ +/* +void RotaryEncoderUIUsermod::readFromJsonState(JsonObject &root) +{ + //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value + //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); +} +*/ - /** - * addToConfig() (called from set.cpp) stores persistent properties to cfg.json - */ - void addToConfig(JsonObject &root) { - // we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} - JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname - top[FPSTR(_enabled)] = enabled; - top[FPSTR(_DT_pin)] = pinA; - top[FPSTR(_CLK_pin)] = pinB; - top[FPSTR(_SW_pin)] = pinC; - top[FPSTR(_presetLow)] = presetLow; - top[FPSTR(_presetHigh)] = presetHigh; - top[FPSTR(_applyToAll)] = applyToAll; - DEBUG_PRINTLN(F("Rotary Encoder config saved.")); - } +/** + * addToConfig() (called from set.cpp) stores persistent properties to cfg.json + */ +void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) { + // we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_DT_pin)] = pinA; + top[FPSTR(_CLK_pin)] = pinB; + top[FPSTR(_SW_pin)] = pinC; + top[FPSTR(_presetLow)] = presetLow; + top[FPSTR(_presetHigh)] = presetHigh; + top[FPSTR(_applyToAll)] = applyToAll; + top[FPSTR(_pcf8574)] = usePcf8574; + top[FPSTR(_pcfAddress)] = addrPcf8574; + top[FPSTR(_pcfINTpin)] = pinIRQ; + DEBUG_PRINTLN(F("Rotary Encoder 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 readFromConfig(JsonObject &root) { - // we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; - int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; - int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; - - presetHigh = top[FPSTR(_presetHigh)] | presetHigh; - presetLow = top[FPSTR(_presetLow)] | presetLow; - presetHigh = MIN(250,MAX(0,presetHigh)); - presetLow = MIN(250,MAX(0,presetLow)); - - enabled = top[FPSTR(_enabled)] | enabled; - applyToAll = top[FPSTR(_applyToAll)] | applyToAll; +void RotaryEncoderUIUsermod::appendConfigData() { + oappend(SET_F("addInfo('Rotary-Encoder:PCF8574-address',1,'(not hex!)');")); + oappend(SET_F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); +} +/** + * 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 RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) { + // we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { DEBUG_PRINT(FPSTR(_name)); - if (!initDone) { - // first run: reading from cfg.json - pinA = newDTpin; - pinB = newCLKpin; - pinC = newSWpin; - DEBUG_PRINTLN(F(" config loaded.")); - } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); - // changing parameters from settings page - if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) { + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; + int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; + int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; + int8_t newIRQpin = top[FPSTR(_pcfINTpin)] | pinIRQ; + bool oldPcf8574 = usePcf8574; + + presetHigh = top[FPSTR(_presetHigh)] | presetHigh; + presetLow = top[FPSTR(_presetLow)] | presetLow; + presetHigh = MIN(250,MAX(0,presetHigh)); + presetLow = MIN(250,MAX(0,presetLow)); + + enabled = top[FPSTR(_enabled)] | enabled; + applyToAll = top[FPSTR(_applyToAll)] | applyToAll; + + usePcf8574 = top[FPSTR(_pcf8574)] | usePcf8574; + addrPcf8574 = top[FPSTR(_pcfAddress)] | addrPcf8574; + + DEBUG_PRINT(FPSTR(_name)); + if (!initDone) { + // first run: reading from cfg.json + pinA = newDTpin; + pinB = newCLKpin; + pinC = newSWpin; + DEBUG_PRINTLN(F(" config loaded.")); + } else { + DEBUG_PRINTLN(F(" config (re)loaded.")); + // changing parameters from settings page + if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin || pinIRQ!=newIRQpin) { + if (oldPcf8574) { + if (pinIRQ >= 0) { + detachInterrupt(pinIRQ); + pinManager.deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI); + DEBUG_PRINTLN(F("Deallocated old IRQ pin.")); + } + pinIRQ = newIRQpin<100 ? newIRQpin : -1; // ignore PCF8574 pins + } else { pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); - pinA = newDTpin; - pinB = newCLKpin; - pinC = newSWpin; - if (pinA<0 || pinB<0 || pinC<0) { - enabled = false; - return true; - } - setup(); + DEBUG_PRINTLN(F("Deallocated old pins.")); } + pinA = newDTpin; + pinB = newCLKpin; + pinC = newSWpin; + if (pinA<0 || pinB<0 || pinC<0) { + enabled = false; + return true; + } + setup(); } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_applyToAll)].isNull(); } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !top[FPSTR(_pcfINTpin)].isNull(); +} - /* - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - * This could be used in the future for the system to determine whether your usermod is installed. - */ - uint16_t getId() - { - return USERMOD_ID_ROTARY_ENC_UI; - } -}; // strings to reduce flash memory usage (used more than twice) const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder"; @@ -926,3 +1160,6 @@ const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin"; const char RotaryEncoderUIUsermod::_presetHigh[] PROGMEM = "preset-high"; const char RotaryEncoderUIUsermod::_presetLow[] PROGMEM = "preset-low"; const char RotaryEncoderUIUsermod::_applyToAll[] PROGMEM = "apply-2-all-seg"; +const char RotaryEncoderUIUsermod::_pcf8574[] PROGMEM = "use-PCF8574"; +const char RotaryEncoderUIUsermod::_pcfAddress[] PROGMEM = "PCF8574-address"; +const char RotaryEncoderUIUsermod::_pcfINTpin[] PROGMEM = "PCF8574-INT-pin"; 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 c825a7af5..058b8318b 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.sln b/wled00.sln deleted file mode 100644 index b2f12b835..000000000 --- a/wled00.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28010.2046 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wled00", "wled00\wled00.vcxproj", "{C5F80730-F44F-4478-BDAE-6634EFC2CA88}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x86 = Debug|x86 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.ActiveCfg = Debug|Win32 - {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.Build.0 = Debug|Win32 - {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.ActiveCfg = Release|Win32 - {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9A679C2B-61D3-400B-B96F-06E604E9CED2} - EndGlobalSection -EndGlobal diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2f5e9fdc6..8c7bf38d3 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -278,7 +278,7 @@ uint16_t mode_random_color(void) { SEGMENT.fill(color_blend(SEGMENT.color_wheel(SEGENV.aux1), SEGMENT.color_wheel(SEGENV.aux0), fade)); return FRAMETIME; } -static const char _data_FX_MODE_RANDOM_COLOR[] PROGMEM = "Random Colors@!,Fade time;;!"; +static const char _data_FX_MODE_RANDOM_COLOR[] PROGMEM = "Random Colors@!,Fade time;;!;01"; /* @@ -433,7 +433,7 @@ uint16_t mode_rainbow(void) { return FRAMETIME; } -static const char _data_FX_MODE_RAINBOW[] PROGMEM = "Colorloop@!,Saturation;;!"; +static const char _data_FX_MODE_RAINBOW[] PROGMEM = "Colorloop@!,Saturation;;!;01"; /* @@ -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;sx=120,ix=64,m12=1"; // bars +static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,,Boost;;!;1;sx=64,ix=160,m12=1"; // bars // 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 + const float gravity = -9.81f; // standard value of gravity const bool hasCol2 = SEGCOLOR(2); const unsigned long time = millis(); @@ -4109,7 +4105,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) { @@ -4179,11 +4175,9 @@ static const char _data_FX_MODE_DANCING_SHADOWS[] PROGMEM = "Dancing Shadows@!,# By Stefan Seegel */ uint16_t mode_washing_machine(void) { - float speed = tristate_square8(strip.now >> 7, 90, 15); - float quot = 32.0f - ((float)SEGMENT.speed / 16.0f); - speed /= quot; + int speed = tristate_square8(strip.now >> 7, 90, 15); - SEGENV.step += (speed * 128.0f); + SEGENV.step += (speed * 2048) / (512 - SEGMENT.speed); for (int i = 0; i < SEGLEN; i++) { uint8_t col = sin8(((SEGMENT.intensity / 25 + 1) * 255 * i / SEGLEN) + (SEGENV.step >> 7)); @@ -4597,7 +4591,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma } SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails - float t = (float)(millis())/128; // timebase + unsigned long t = millis()/128; // timebase // outer stars for (size_t i = 0; i < 8; i++) { x = beatsin8(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + t * i); @@ -4653,8 +4647,8 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so byte ysteps = abs8(x2 - y2) + 1; byte steps = xsteps >= ysteps ? xsteps : ysteps; //Draw gradient line - for (size_t i = 1; i <= steps; i++) { - uint8_t rate = i * 255 / steps; + for (size_t j = 1; j <= steps; j++) { + uint8_t rate = j * 255 / steps; byte dx = lerp8by8(x1, y1, rate); byte dy = lerp8by8(x2, y2, rate); //SEGMENT.setPixelColorXY(dx, dy, grad ? color.nscale8_video(255-rate) : color); // use addPixelColorXY for different look @@ -5101,21 +5095,22 @@ uint16_t mode_2DLissajous(void) { // By: Andrew Tuline const uint16_t rows = SEGMENT.virtualHeight(); SEGMENT.fadeToBlackBy(SEGMENT.intensity); + uint_fast16_t phase = (millis() * (1 + SEGENV.custom3)) /32; // allow user to control rotation speed //for (int i=0; i < 4*(cols+rows); i ++) { for (int i=0; i < 256; i ++) { //float xlocn = float(sin8(now/4+i*(SEGMENT.speed>>5))) / 255.0f; //float ylocn = float(cos8(now/4+i*2)) / 255.0f; - uint8_t xlocn = sin8(millis()/4+i*(SEGMENT.speed>>5)); - uint8_t ylocn = cos8(millis()/4+i*2); - xlocn = map(xlocn,0,255,0,cols-1); - ylocn = map(ylocn,0,255,0,rows-1); - SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(millis()/100+i, false, PALETTE_SOLID_WRAP, 0)); + uint_fast8_t xlocn = sin8(phase/2 + (i*SEGMENT.speed)/32); + uint_fast8_t ylocn = cos8(phase/2 + i*2); + xlocn = (cols < 2) ? 1 : (map(2*xlocn, 0,511, 0,2*(cols-1)) +1) /2; // softhack007: "(2* ..... +1) /2" for proper rounding + ylocn = (rows < 2) ? 1 : (map(2*ylocn, 0,511, 0,2*(rows-1)) +1) /2; // "rows > 1" is needed to avoid div/0 in map() + SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(millis()/100+i, false, PALETTE_SOLID_WRAP, 0)); } return FRAMETIME; } // mode_2DLissajous() -static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,Fade rate;!;!;2"; +static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,Fade rate,,,Speed;!;!;2;;c3=15"; /////////////////////// @@ -5279,7 +5274,7 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito SEGMENT.fadeToBlackBy(SEGMENT.custom1>>2); - float t = millis() / (33 - SEGMENT.speed/8); + uint_fast32_t t = (millis() * 8) / (256 - SEGMENT.speed); // optimized to avoid float for (int i = 0; i < cols; i++) { uint16_t thisVal = inoise8(i * 30, t, t); uint16_t thisMax = map(thisVal, 0, 255, 0, cols-1); @@ -5526,22 +5521,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"; ///////////////////////// @@ -5874,11 +5877,17 @@ uint16_t mode_2Dscrollingtext(void) { case 4: letterWidth = 7; letterHeight = 9; break; case 5: letterWidth = 5; letterHeight = 12; break; } + const bool zero = SEGMENT.check3; const int yoffset = map(SEGMENT.intensity, 0, 255, -rows/2, rows/2) + (rows-letterHeight)/2; - char text[33] = {'\0'}; + char text[WLED_MAX_SEGNAME_LEN+1] = {'\0'}; 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("#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; @@ -5888,12 +5897,12 @@ uint16_t mode_2Dscrollingtext(void) { } if (useAMPM) sprintf_P(sec, PSTR(" %2s"), (isitAM ? "AM" : "PM")); else sprintf_P(sec, PSTR(":%02d"), second(localTime)); - if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); - else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, PSTR("%d.%d"), day(localTime), month(localTime)); - 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 sprintf_P(text, PSTR("%s %d, %d %2d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); + if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, zero?PSTR("%02d.%02d.%04d"):PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); + else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, zero?PSTR("%02d.%02d"):PSTR("%d.%d"), day(localTime), month(localTime)); + else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, zero?PSTR("%02d/%02d"):PSTR("%d/%d"), month(localTime), day(localTime)); + else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, zero?PSTR("%02d:%02d%s"):PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); + else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d"):PSTR("%d:%02d"), AmPmHour, minute(localTime)); + else sprintf_P(text, zero?PSTR("%s %02d, %04d %02d:%02d%s"):PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); } const int numberOfLetters = strlen(text); @@ -5920,7 +5929,7 @@ uint16_t mode_2Dscrollingtext(void) { return FRAMETIME; } -static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,,Gradient,Overlay;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; +static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,,Gradient,Overlay,0;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; //////////////////////////// @@ -7333,7 +7342,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 @@ -7382,7 +7391,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 start) ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) + inline uint16_t height(void) const { return (stopY > startY) ? (stopY - startY) : 0; } // segment height (if 2D) in physical pixels // softhack007: make sure its always > 0 + inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels inline uint16_t groupLength(void) const { return grouping + spacing; } inline uint8_t getLightCapabilities(void) const { return _capabilities; } static uint16_t getUsedSegmentData(void) { return _usedSegmentData; } static void addUsedSegmentData(int len) { _usedSegmentData += len; } - void set(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); + void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); bool setColor(uint8_t slot, uint32_t c); //returns true if changed void setCCT(uint16_t k); void setOpacity(uint8_t o); @@ -562,9 +566,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); @@ -586,16 +590,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); @@ -619,16 +623,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) {} @@ -706,7 +710,7 @@ class WS2812FX { // 96 bytes panel.clear(); #endif customPalettes.clear(); - if (useLedsArray && Segment::_globalLeds) free(Segment::_globalLeds); + if (useLedsArray && Segment::_globalLeds) {free(Segment::_globalLeds); Segment::_globalLeds = nullptr;} // reset to nullptr, to avoid race conditions } static WS2812FX* getInstance(void) { return instance; } @@ -768,6 +772,7 @@ class WS2812FX { // 96 bytes getActiveSegmentsNum(void), getFirstSelectedSegId(void), getLastActiveSegmentId(void), + getActiveSegsLightCapabilities(bool selectedOnly = false), setPixelSegment(uint8_t n); inline uint8_t getBrightness(void) { return _brightness; } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 30e0fa19b..778a28f9a 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -191,6 +191,8 @@ uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) { uint16_t /*IRAM_ATTR*/ Segment::XY(uint16_t x, uint16_t y) { uint16_t width = virtualWidth(); // segment width in logical pixels uint16_t height = virtualHeight(); // segment height in logical pixels + if (width == 0) return 0; // softhack007 avoid div/0 + if (height == 0) return (x%width); // softhack007 avoid div/0 return (x%width) + (y%height) * width; } @@ -202,7 +204,6 @@ void /*IRAM_ATTR*/ Segment::setPixelColorXY(int x, int y, uint32_t col) if (leds) leds[XY(x,y)] = col; uint8_t _bri_t = currentBri(on ? opacity : 0); - if (!_bri_t && !transitional) return; if (_bri_t < 255) { byte r = scale8(R(col), _bri_t); byte g = scale8(G(col), _bri_t); @@ -304,8 +305,23 @@ 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) { + if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit + 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) { @@ -315,50 +331,54 @@ void Segment::fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { // blurRow: perform a blur on a row of a rectangular matrix void Segment::blurRow(uint16_t row, fract8 blur_amount) { - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); + const uint_fast16_t cols = virtualWidth(); + const uint_fast16_t rows = virtualHeight(); if (row >= rows) return; // blur one row uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; CRGB carryover = CRGB::Black; - for (uint16_t x = 0; x < cols; x++) { + for (uint_fast16_t x = 0; x < cols; x++) { CRGB cur = getPixelColorXY(x, row); + uint32_t before = uint32_t(cur); // remember color before blur CRGB part = cur; part.nscale8(seep); cur.nscale8(keep); cur += carryover; - if (x) { + if (x>0) { CRGB prev = CRGB(getPixelColorXY(x-1, row)) + part; setPixelColorXY(x-1, row, prev); } - setPixelColorXY(x, row, cur); + if (before != uint32_t(cur)) // optimization: only set pixel if color has changed + setPixelColorXY(x, row, cur); carryover = part; } } // blurCol: perform a blur on a column of a rectangular matrix void Segment::blurCol(uint16_t col, fract8 blur_amount) { - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); + const uint_fast16_t cols = virtualWidth(); + const uint_fast16_t rows = virtualHeight(); if (col >= cols) return; // blur one column uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; CRGB carryover = CRGB::Black; - for (uint16_t i = 0; i < rows; i++) { - CRGB cur = getPixelColorXY(col, i); + for (uint_fast16_t y = 0; y < rows; y++) { + CRGB cur = getPixelColorXY(col, y); CRGB part = cur; + uint32_t before = uint32_t(cur); // remember color before blur part.nscale8(seep); cur.nscale8(keep); cur += carryover; - if (i) { - CRGB prev = CRGB(getPixelColorXY(col, i-1)) + part; - setPixelColorXY(col, i-1, prev); + if (y>0) { + CRGB prev = CRGB(getPixelColorXY(col, y-1)) + part; + setPixelColorXY(col, y-1, prev); } - setPixelColorXY(col, i, cur); + if (before != uint32_t(cur)) // optimization: only set pixel if color has changed + setPixelColorXY(col, y, cur); carryover = part; } } @@ -377,8 +397,8 @@ void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { for (uint16_t j = 0; j < dim1; j++) { uint16_t x = vertical ? i : j; uint16_t y = vertical ? j : i; - uint16_t xp = vertical ? x : x-1; - uint16_t yp = vertical ? y-1 : y; + int16_t xp = vertical ? x : x-1; // "signed" to prevent underflow + int16_t yp = vertical ? y-1 : y; // "signed" to prevent underflow uint16_t xn = vertical ? x : x+1; uint16_t yn = vertical ? y+1 : y; CRGB curr = getPixelColorXY(x,y); @@ -416,54 +436,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; } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a9dabafee..4224f2545 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -90,18 +90,18 @@ Segment::Segment(const Segment &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.leds && !Segment::_globalLeds && length() > 0) { leds = (CRGB*)malloc(sizeof(CRGB)*length()); if (leds) memcpy(leds, orig.leds, sizeof(CRGB)*length()); } } // move constructor Segment::Segment(Segment &&orig) noexcept { //DEBUG_PRINTLN(F("-- Move segment constructor --")); memcpy((void*)this, (void*)&orig, sizeof(Segment)); + orig.leds = nullptr; orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; orig._t = nullptr; - orig.leds = nullptr; } // copy assignment @@ -111,7 +111,7 @@ Segment& Segment::operator= (const Segment &orig) { // clean destination if (name) delete[] name; if (_t) delete _t; - if (leds && !Segment::_globalLeds) free(leds); + if (leds && !Segment::_globalLeds) {free(leds); leds=nullptr;} deallocateData(); // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); @@ -125,7 +125,7 @@ Segment& Segment::operator= (const Segment &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.leds && !Segment::_globalLeds && length() > 0) { leds = (CRGB*)malloc(sizeof(CRGB)*length()); if (leds) memcpy(leds, orig.leds, sizeof(CRGB)*length()); } } return *this; } @@ -137,7 +137,7 @@ 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 (leds && !Segment::_globalLeds) {free(leds); leds=nullptr;} memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig.name = nullptr; orig.data = nullptr; @@ -152,12 +152,12 @@ bool Segment::allocateData(size_t len) { if (data && _dataLen == len) return true; //already allocated deallocateData(); if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) return false; //not enough memory - // if possible use SPI RAM on ESP32 - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) - if (psramFound()) - data = (byte*) ps_malloc(len); - else - #endif + // do not use SPI RAM on ESP32 since it is slow + //#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) + //if (psramFound()) + // data = (byte*) ps_malloc(len); + //else + //#endif data = (byte*) malloc(len); if (!data) return false; //allocation failed Segment::addUsedSegmentData(len); @@ -184,7 +184,7 @@ void Segment::deallocateData() { void Segment::resetIfRequired() { if (reset) { if (leds && !Segment::_globalLeds) { free(leds); leds = nullptr; } - if (transitional && _t) { transitional = false; delete _t; _t = nullptr; } + //if (transitional && _t) { transitional = false; delete _t; _t = nullptr; } deallocateData(); next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; // setOption(SEG_OPTION_RESET, false); @@ -199,12 +199,12 @@ void Segment::setUpLeds() { #else leds = &Segment::_globalLeds[start]; #endif - else if (!leds) { - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) - if (psramFound()) - leds = (CRGB*)ps_malloc(sizeof(CRGB)*length()); - else - #endif + else if (leds == 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(WLED_USE_PSRAM) + //if (psramFound()) + // leds = (CRGB*)ps_malloc(sizeof(CRGB)*length()); // softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards + //else + //#endif leds = (CRGB*)malloc(sizeof(CRGB)*length()); } } @@ -228,6 +228,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 @@ -304,23 +305,26 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { } void Segment::startTransition(uint16_t dur) { - if (transitional || _t) return; // already in transition no need to store anything + if (!dur) { + transitional = false; + if (_t) { + delete _t; + _t = nullptr; + } + return; + } + if (transitional && _t) return; // already in transition no need to store anything // starting a transition has to occur before change so we get current values 1st - uint8_t _briT = currentBri(on ? opacity : 0); - uint8_t _cctT = currentBri(cct, true); - CRGBPalette16 _palT = CRGBPalette16(DEFAULT_COLOR); loadPalette(_palT, palette); - uint8_t _modeP = mode; - uint32_t _colorT[NUM_COLORS]; - for (size_t i=0; i_briT = _briT; - _t->_cctT = _cctT; + + CRGBPalette16 _palT = CRGBPalette16(DEFAULT_COLOR); loadPalette(_palT, palette); + _t->_briT = on ? opacity : 0; + _t->_cctT = cct; _t->_palT = _palT; - _t->_modeP = _modeP; - for (size_t i=0; i_colorT[i] = _colorT[i]; + _t->_modeP = mode; + for (size_t i=0; i_colorT[i] = colors[i]; transitional = true; // setOption(SEG_OPTION_TRANSITIONAL, true); } @@ -333,10 +337,10 @@ uint16_t Segment::progress() { } uint8_t Segment::currentBri(uint8_t briNew, bool useCct) { - if (transitional && _t) { - uint32_t prog = progress() + 1; - if (useCct) return ((briNew * prog) + _t->_cctT * (0x10000 - prog)) >> 16; - else return ((briNew * prog) + _t->_briT * (0x10000 - prog)) >> 16; + uint32_t prog = progress(); + if (transitional && _t && prog < 0xFFFFU) { + if (useCct) return ((briNew * prog) + _t->_cctT * (0xFFFFU - prog)) >> 16; + else return ((briNew * prog) + _t->_briT * (0xFFFFU - prog)) >> 16; } else { return briNew; } @@ -366,19 +370,18 @@ CRGBPalette16 &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal void Segment::handleTransition() { if (!transitional) return; - unsigned long maxWait = millis() + 20; - if (mode == FX_MODE_STATIC && next_time > maxWait) next_time = maxWait; - if (progress() == 0xFFFFU) { - if (_t) { - if (_t->_modeP != mode) markForReset(); + uint16_t _progress = progress(); + if (_progress == 0xFFFFU) transitional = false; // finish transitioning segment + if (_t) { // thanks to @nXm AKA https://github.com/NMeirer + if (_progress >= 32767U && _t->_modeP != mode) markForReset(); + if (_progress == 0xFFFFU) { delete _t; _t = nullptr; } - transitional = false; // finish transitioning segment } } -void Segment::set(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { +void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { //return if neither bounds nor grouping have changed bool boundsUnchanged = (start == i1 && stop == i2); #ifndef WLED_DISABLE_2D @@ -473,7 +476,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { sOpt = extractModeDefaults(fx, "o2"); check2 = (sOpt >= 0) ? (bool)sOpt : false; sOpt = extractModeDefaults(fx, "o3"); check3 = (sOpt >= 0) ? (bool)sOpt : false; sOpt = extractModeDefaults(fx, "m12"); if (sOpt >= 0) map1D2D = constrain(sOpt, 0, 7); - sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) soundSim = constrain(sOpt, 0, 7); + sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) soundSim = constrain(sOpt, 0, 1); sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) reverse = (bool)sOpt; sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt; @@ -544,6 +547,7 @@ uint16_t Segment::virtualLength() const { } #endif uint16_t groupLen = groupLength(); + if (groupLen < 1) groupLen = 1; // prevent division by zero - better safe than sorry ... uint16_t vLength = (length() + groupLen - 1) / groupLen; if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED return vLength; @@ -622,7 +626,6 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) uint16_t len = length(); uint8_t _bri_t = currentBri(on ? opacity : 0); - if (!_bri_t && !transitional) return; if (_bri_t < 255) { byte r = scale8(R(col), _bri_t); byte g = scale8(G(col), _bri_t); @@ -727,7 +730,7 @@ uint32_t Segment::getPixelColor(int i) i += start; /* offset/phase */ i += offset; - if (i >= stop) i -= length(); + if ((i >= stop) && (stop>0)) i -= length(); // avoids negative pixel index (stop = 0 is a possible value) return strip.getPixelColor(i); } @@ -749,7 +752,7 @@ uint8_t Segment::differs(Segment& b) const { if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; - //bit pattern: (msb first) sound:3, mapping:3, transposed, mirrorY, reverseY, [transitional, reset,] paused, mirrored, on, reverse, [selected] + //bit pattern: (msb first) set:2, sound:1, mapping:3, transposed, mirrorY, reverseY, [transitional, reset,] paused, mirrored, on, reverse, [selected] if ((options & 0b1111111110011110U) != (b.options & 0b1111111110011110U)) d |= SEG_DIFFERS_OPT; if ((options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL; for (uint8_t i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; @@ -820,8 +823,22 @@ void Segment::blendPixelColor(int n, uint32_t color, uint8_t blend) { } // Adds the specified color with the existing pixel color perserving color balance. -void Segment::addPixelColor(int n, uint32_t color) { - setPixelColor(n, color_add(getPixelColor(n), color)); +void Segment::addPixelColor(int n, uint32_t color, bool fast) { + uint32_t col = getPixelColor(n); + 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); + } + setPixelColor(n, col); } void Segment::fadePixelColor(uint16_t n, uint8_t fade) { @@ -870,6 +887,7 @@ void Segment::fade_out(uint8_t rate) { // fades all pixels to black using nscale8() void Segment::fadeToBlackBy(uint8_t fadeBy) { + if (fadeBy == 0) return; // optimization - no scaling to apply const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); const uint16_t rows = virtualHeight(); // will be 1 for 1D @@ -884,23 +902,26 @@ void Segment::fadeToBlackBy(uint8_t fadeBy) { */ void Segment::blur(uint8_t blur_amount) { + if (blur_amount == 0) return; // optimization: 0 means "don't blur" #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(); + 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; + uint32_t before = uint32_t(cur); // remember color before blur part.nscale8(seep); cur.nscale8(keep); cur += carryover; @@ -909,9 +930,10 @@ 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); + if (before != uint32_t(cur)) // optimization: only set pixel if color has changed + setPixelColor((uint16_t)i,cur.red, cur.green, cur.blue); carryover = part; } } @@ -1054,13 +1076,14 @@ void WS2812FX::finalizeInit(void) } if (useLedsArray) { size_t arrSize = sizeof(CRGB) * getLengthTotal(); - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) - if (psramFound()) - Segment::_globalLeds = (CRGB*) ps_malloc(arrSize); - else - #endif + // softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards (see setUpLeds()) + //#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) + //if (psramFound()) + // Segment::_globalLeds = (CRGB*) ps_malloc(arrSize); + //else + //#endif Segment::_globalLeds = (CRGB*) malloc(arrSize); - memset(Segment::_globalLeds, 0, arrSize); + if (Segment::_globalLeds && (arrSize > 0)) memset(Segment::_globalLeds, 0, arrSize); } //segments are created in makeAutoSegments(); @@ -1079,6 +1102,8 @@ void WS2812FX::service() { _isServicing = true; _segment_index = 0; for (segment &seg : _segments) { + // process transition (mode changes in the middle of transition) + seg.handleTransition(); // reset the segment runtime data if needed seg.resetIfRequired(); @@ -1107,8 +1132,6 @@ void WS2812FX::service() { delay = (*_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 - - seg.handleTransition(); } seg.next_time = nowUp + delay; @@ -1181,12 +1204,12 @@ void WS2812FX::estimateCurrentAndLimitBri() { uint32_t powerSum = 0; - for (uint8_t b = 0; b < busses.getNumBusses(); b++) { - Bus *bus = busses.getBus(b); + for (uint_fast8_t bNum = 0; bNum < busses.getNumBusses(); bNum++) { + Bus *bus = busses.getBus(bNum); if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses uint16_t len = bus->getLength(); uint32_t busPowerSum = 0; - for (uint16_t i = 0; i < len; i++) { //sum up the usage of each LED + for (uint_fast16_t i = 0; i < len; i++) { //sum up the usage of each LED uint32_t c = bus->getPixelColor(i); byte r = R(c), g = G(c), b = B(c), w = W(c); @@ -1205,7 +1228,8 @@ void WS2812FX::estimateCurrentAndLimitBri() { } uint32_t powerSum0 = powerSum; - powerSum *= _brightness; + //powerSum *= _brightness; // for NPBrightnessBus + powerSum *= 255; // no need to scale down powerSum - NPB-LG getPixelColor returns colors scaled down by brightness if (powerSum > powerBudget) //scale brightness down to stay in current limit { @@ -1213,11 +1237,14 @@ void WS2812FX::estimateCurrentAndLimitBri() { uint16_t scaleI = scale * 255; uint8_t scaleB = (scaleI > 255) ? 255 : scaleI; uint8_t newBri = scale8(_brightness, scaleB); - busses.setBrightness(newBri); //to keep brightness uniform, sets virtual busses too - currentMilliamps = (powerSum0 * newBri) / puPerMilliamp; + // to keep brightness uniform, sets virtual busses too - softhack007: apply reductions immediately + if (scaleB < 255) busses.setBrightness(scaleB, true); // NPB-LG has already applied brightness, so its suffifient to post-apply scaling ==> use scaleB instead of newBri + busses.setBrightness(newBri, false); // set new brightness for next frame + //currentMilliamps = (powerSum0 * newBri) / puPerMilliamp; // for NPBrightnessBus + currentMilliamps = (powerSum0 * scaleB) / puPerMilliamp; // for NPBus-LG } else { currentMilliamps = powerSum / puPerMilliamp; - busses.setBrightness(_brightness); + busses.setBrightness(_brightness, false); // set new brightness for next frame } currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate currentMilliamps += pLen; //add standby power back to estimate @@ -1314,6 +1341,14 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { } } +uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) { + uint8_t totalLC = 0; + for (segment &seg : _segments) { + if (seg.isActive() && (!selectedOnly || seg.isSelected())) totalLC |= seg.getLightCapabilities(); + } + return totalLC; +} + uint8_t WS2812FX::getFirstSelectedSegId(void) { size_t i = 0; @@ -1411,7 +1446,7 @@ Segment& WS2812FX::getSegment(uint8_t id) { void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { if (n >= _segments.size()) return; - _segments[n].set(i1, i2, grouping, spacing, offset, startY, stopY); + _segments[n].setUp(i1, i2, grouping, spacing, offset, startY, stopY); } void WS2812FX::restartRuntime() { @@ -1567,7 +1602,7 @@ void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { } void WS2812FX::setTransitionMode(bool t) { - for (segment &seg : _segments) if (!seg.transitional) seg.startTransition(t ? _transitionDur : 0); + for (segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } #ifdef WLED_DEBUG diff --git a/wled00/NodeStruct.h b/wled00/NodeStruct.h index 916c5a9a3..2d4d3609b 100644 --- a/wled00/NodeStruct.h +++ b/wled00/NodeStruct.h @@ -9,9 +9,9 @@ #include #define NODE_TYPE_ID_UNDEFINED 0 -#define NODE_TYPE_ID_ESP8266 82 -#define NODE_TYPE_ID_ESP32 32 -#define NODE_TYPE_ID_ESP32S2 33 +#define NODE_TYPE_ID_ESP8266 82 // should be 1 +#define NODE_TYPE_ID_ESP32 32 // should be 2 +#define NODE_TYPE_ID_ESP32S2 33 // etc #define NODE_TYPE_ID_ESP32S3 34 #define NODE_TYPE_ID_ESP32C3 35 @@ -23,7 +23,13 @@ struct NodeStruct String nodeName; IPAddress ip; uint8_t age; - uint8_t nodeType; + union { + uint8_t nodeType; // a waste of space as we only have 5 types + struct { + uint8_t type : 7; // still a waste of space (4 bits would be enough and future-proof) + bool on : 1; + }; + }; uint32_t build; NodeStruct() : age(0), nodeType(0), build(0) diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index c122402a4..179a522c0 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -101,20 +101,27 @@ void onAlexaChange(EspalexaDevice* dev) { byte rgbw[4]; uint16_t ct = dev->getCt(); - if (!ct) return; - uint16_t k = 1000000 / ct; //mireds to kelvin - - if (strip.hasCCTBus()) { - strip.setCCT(k); - rgbw[0]= 0; rgbw[1]= 0; rgbw[2]= 0; rgbw[3]= 255; - } else if (strip.hasWhiteChannel()) { + if (!ct) return; + uint16_t k = 1000000 / ct; //mireds to kelvin + + if (strip.hasCCTBus()) { + bool hasManualWhite = strip.getActiveSegsLightCapabilities(true) & SEG_CAPABILITY_W; + + strip.setCCT(k); + if (hasManualWhite) { + rgbw[0] = 0; rgbw[1] = 0; rgbw[2] = 0; rgbw[3] = 255; + } else { + rgbw[0] = 255; rgbw[1] = 255; rgbw[2] = 255; rgbw[3] = 0; + dev->setValue(255); + } + } else if (strip.hasWhiteChannel()) { switch (ct) { //these values empirically look good on RGBW case 199: rgbw[0]=255; rgbw[1]=255; rgbw[2]=255; rgbw[3]=255; break; case 234: rgbw[0]=127; rgbw[1]=127; rgbw[2]=127; rgbw[3]=255; break; case 284: rgbw[0]= 0; rgbw[1]= 0; rgbw[2]= 0; rgbw[3]=255; break; case 350: rgbw[0]=130; rgbw[1]= 90; rgbw[2]= 0; rgbw[3]=255; break; case 383: rgbw[0]=255; rgbw[1]=153; rgbw[2]= 0; rgbw[3]=255; break; - default : colorKtoRGB(k, rgbw); + default : colorKtoRGB(k, rgbw); } } else { colorKtoRGB(k, rgbw); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index c1cf2d469..d11182aa5 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -95,12 +95,14 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) { BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) : Bus(bc.type, bc.start, bc.autoWhite), _colorOrderMap(com) { if (!IS_DIGITAL(bc.type) || !bc.count) return; if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; + _frequencykHz = 0U; _pins[0] = bc.pins[0]; if (IS_2PIN(bc.type)) { if (!pinManager.allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { cleanup(); return; } _pins[1] = bc.pins[1]; + _frequencykHz = bc.frequency ? bc.frequency : 2000U; // 2MHz clock if undefined } reversed = bc.reversed; _needsRefresh = bc.refreshReq || bc.type == TYPE_TM1814; @@ -110,7 +112,7 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) : Bu if (_iType == I_NONE) return; uint16_t lenToCreate = _len; if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus - _busPtr = PolyBus::create(_iType, _pins, lenToCreate, nr); + _busPtr = PolyBus::create(_iType, _pins, lenToCreate, nr, _frequencykHz); _valid = (_busPtr != nullptr); _colorOrder = bc.colorOrder; DEBUG_PRINTF("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n", _valid?"S":"Uns", nr, _len, bc.type, _pins[0],_pins[1],_iType); @@ -124,15 +126,15 @@ bool BusDigital::canShow() { return PolyBus::canShow(_busPtr, _iType); } -void BusDigital::setBrightness(uint8_t b) { +void BusDigital::setBrightness(uint8_t b, bool immediate) { //Fix for turning off onboard LED breaking bus #ifdef LED_BUILTIN if (_bri == 0 && b > 0) { if (_pins[0] == LED_BUILTIN || _pins[1] == LED_BUILTIN) PolyBus::begin(_busPtr, _iType, _pins); } #endif - Bus::setBrightness(b); - PolyBus::setBrightness(_busPtr, _iType, b); + Bus::setBrightness(b, immediate); + PolyBus::setBrightness(_busPtr, _iType, b, immediate); } //If LEDs are skipped, it is possible to use the first as a status LED. @@ -212,10 +214,11 @@ BusPwm::BusPwm(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { _valid = false; if (!IS_PWM(bc.type)) return; uint8_t numPins = NUM_PWM_PINS(bc.type); + _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; #ifdef ESP8266 analogWriteRange(255); //same range as one RGB channel - analogWriteFreq(WLED_PWM_FREQ); + analogWriteFreq(_frequency); #else _ledcStart = pinManager.allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels @@ -232,7 +235,7 @@ BusPwm::BusPwm(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { #ifdef ESP8266 pinMode(_pins[i], OUTPUT); #else - ledcSetup(_ledcStart + i, WLED_PWM_FREQ, 8); + ledcSetup(_ledcStart + i, _frequency, 8); ledcAttachPin(_pins[i], _ledcStart + i); #endif } @@ -450,21 +453,21 @@ void BusNetwork::cleanup() { uint32_t BusManager::memUsage(BusConfig &bc) { uint8_t type = bc.type; uint16_t len = bc.count + bc.skipAmount; - if (type > 15 && type < 32) { + if (type > 15 && type < 32) { // digital types + if (type == TYPE_UCS8903 || type == TYPE_UCS8904) len *= 2; // 16-bit LEDs #ifdef ESP8266 if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem - if (type > 29) return len*20; //RGBW + if (type > 28) return len*20; //RGBW return len*15; } - if (type > 29) return len*4; //RGBW + if (type > 28) return len*4; //RGBW return len*3; #else //ESP32 RMT uses double buffer? - if (type > 29) return len*8; //RGBW + if (type > 28) return len*8; //RGBW return len*6; #endif } - if (type > 31 && type < 48) return 5; - if (type == 44 || type == 45) return len*4; //RGBW + if (type > 31 && type < 48) return 5; return len*3; //RGB } @@ -512,9 +515,9 @@ void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c, int16_t cct) } } -void BusManager::setBrightness(uint8_t b) { +void BusManager::setBrightness(uint8_t b, bool immediate) { for (uint8_t i = 0; i < numBusses; i++) { - busses[i]->setBrightness(b); + busses[i]->setBrightness(b, immediate); } } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index ffb3bd140..4a8da6051 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -29,10 +29,11 @@ struct BusConfig { bool refreshReq; uint8_t autoWhite; uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY) { + uint16_t frequency; + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U) { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) - count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; + count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; frequency = clock_kHz; uint8_t nPins = 1; if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address else if (type > 47) nPins = 2; @@ -107,13 +108,14 @@ class Bus { virtual void setStatusPixel(uint32_t c) {} virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; virtual uint32_t getPixelColor(uint16_t pix) { return 0; } - virtual void setBrightness(uint8_t b) { _bri = b; }; + virtual void setBrightness(uint8_t b, bool immediate=false) { _bri = b; }; virtual void cleanup() = 0; virtual uint8_t getPins(uint8_t* pinArray) { return 0; } virtual uint16_t getLength() { return _len; } virtual void setColorOrder() {} virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } virtual uint8_t skippedLeds() { return 0; } + virtual uint16_t getFrequency() { return 0U; } inline uint16_t getStart() { return _start; } inline void setStart(uint16_t start) { _start = start; } inline uint8_t getType() { return _type; } @@ -179,7 +181,7 @@ class BusDigital : public Bus { bool canShow(); - void setBrightness(uint8_t b); + void setBrightness(uint8_t b, bool immediate); void setStatusPixel(uint32_t c); @@ -203,6 +205,8 @@ class BusDigital : public Bus { return _skip; } + uint16_t getFrequency() { return _frequencykHz; } + void reinit(); void cleanup(); @@ -216,6 +220,7 @@ class BusDigital : public Bus { uint8_t _pins[2] = {255, 255}; uint8_t _iType = 0; //I_NONE; uint8_t _skip = 0; + uint16_t _frequencykHz = 0U; void * _busPtr = nullptr; const ColorOrderMap &_colorOrderMap; }; @@ -234,6 +239,8 @@ class BusPwm : public Bus { uint8_t getPins(uint8_t* pinArray); + uint16_t getFrequency() { return _frequency; } + void cleanup() { deallocatePins(); } @@ -248,6 +255,7 @@ class BusPwm : public Bus { #ifdef ARDUINO_ARCH_ESP32 uint8_t _ledcStart = 255; #endif + uint16_t _frequency = 0U; void deallocatePins(); }; @@ -335,9 +343,9 @@ class BusManager { void setStatusPixel(uint32_t c); - void IRAM_ATTR setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1); + void setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1); - void setBrightness(uint8_t b); + void setBrightness(uint8_t b, bool immediate=false); // immediate=true is for use in ABL, it applies brightness immediately (warning: inefficient) void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index bbf38d2ea..b19997ce7 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1,7 +1,7 @@ #ifndef BusWrapper_h #define BusWrapper_h -#include "NeoPixelBrightnessBus.h" +#include "NeoPixelBusLg.h" // temporary - these defines should actually be set in platformio.ini // C3: I2S0 and I2S1 methods not supported (has one I2S bus) @@ -53,6 +53,16 @@ #define I_8266_U1_TM2_3 18 #define I_8266_DM_TM2_3 19 #define I_8266_BB_TM2_3 20 +//UCS8903 (RGB) +#define I_8266_U0_UCS_3 49 +#define I_8266_U1_UCS_3 50 +#define I_8266_DM_UCS_3 51 +#define I_8266_BB_UCS_3 52 +//UCS8904 (RGBW) +#define I_8266_U0_UCS_4 53 +#define I_8266_U1_UCS_4 54 +#define I_8266_DM_UCS_4 55 +#define I_8266_BB_UCS_4 56 /*** ESP32 Neopixel methods ***/ //RGB @@ -80,6 +90,16 @@ #define I_32_I0_TM2_3 37 #define I_32_I1_TM2_3 38 //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +//UCS8903 (RGB) +#define I_32_RN_UCS_3 57 +#define I_32_I0_UCS_3 58 +#define I_32_I1_UCS_3 59 +//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +//UCS8904 (RGBW) +#define I_32_RN_UCS_4 60 +#define I_32_I0_UCS_4 61 +#define I_32_I1_UCS_4 62 +//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //APA102 #define I_HS_DOT_3 39 //hardware SPI @@ -102,80 +122,111 @@ #define I_SS_LPO_3 48 +// In the following NeoGammaNullMethod can be replaced with NeoGammaWLEDMethod to perform Gamma correction implicitly +// unfortunately that may apply Gamma correction to pre-calculated palettes which is undesired + /*** ESP8266 Neopixel methods ***/ #ifdef ESP8266 //RGB -#define B_8266_U0_NEO_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio1 -#define B_8266_U1_NEO_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio2 -#define B_8266_DM_NEO_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio3 -#define B_8266_BB_NEO_3 NeoPixelBrightnessBus //3 chan, esp8266, bb (any pin but 16) +#define B_8266_U0_NEO_3 NeoPixelBusLg //3 chan, esp8266, gpio1 +#define B_8266_U1_NEO_3 NeoPixelBusLg //3 chan, esp8266, gpio2 +#define B_8266_DM_NEO_3 NeoPixelBusLg //3 chan, esp8266, gpio3 +#define B_8266_BB_NEO_3 NeoPixelBusLg //3 chan, esp8266, bb (any pin but 16) //RGBW -#define B_8266_U0_NEO_4 NeoPixelBrightnessBus //4 chan, esp8266, gpio1 -#define B_8266_U1_NEO_4 NeoPixelBrightnessBus //4 chan, esp8266, gpio2 -#define B_8266_DM_NEO_4 NeoPixelBrightnessBus //4 chan, esp8266, gpio3 -#define B_8266_BB_NEO_4 NeoPixelBrightnessBus //4 chan, esp8266, bb (any pin) +#define B_8266_U0_NEO_4 NeoPixelBusLg //4 chan, esp8266, gpio1 +#define B_8266_U1_NEO_4 NeoPixelBusLg //4 chan, esp8266, gpio2 +#define B_8266_DM_NEO_4 NeoPixelBusLg //4 chan, esp8266, gpio3 +#define B_8266_BB_NEO_4 NeoPixelBusLg //4 chan, esp8266, bb (any pin) //400Kbps -#define B_8266_U0_400_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio1 -#define B_8266_U1_400_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio2 -#define B_8266_DM_400_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio3 -#define B_8266_BB_400_3 NeoPixelBrightnessBus //3 chan, esp8266, bb (any pin) +#define B_8266_U0_400_3 NeoPixelBusLg //3 chan, esp8266, gpio1 +#define B_8266_U1_400_3 NeoPixelBusLg //3 chan, esp8266, gpio2 +#define B_8266_DM_400_3 NeoPixelBusLg //3 chan, esp8266, gpio3 +#define B_8266_BB_400_3 NeoPixelBusLg //3 chan, esp8266, bb (any pin) //TM1814 (RGBW) -#define B_8266_U0_TM1_4 NeoPixelBrightnessBus -#define B_8266_U1_TM1_4 NeoPixelBrightnessBus -#define B_8266_DM_TM1_4 NeoPixelBrightnessBus -#define B_8266_BB_TM1_4 NeoPixelBrightnessBus +#define B_8266_U0_TM1_4 NeoPixelBusLg +#define B_8266_U1_TM1_4 NeoPixelBusLg +#define B_8266_DM_TM1_4 NeoPixelBusLg +#define B_8266_BB_TM1_4 NeoPixelBusLg //TM1829 (RGB) -#define B_8266_U0_TM2_4 NeoPixelBrightnessBus -#define B_8266_U1_TM2_4 NeoPixelBrightnessBus -#define B_8266_DM_TM2_4 NeoPixelBrightnessBus -#define B_8266_BB_TM2_4 NeoPixelBrightnessBus +#define B_8266_U0_TM2_4 NeoPixelBusLg +#define B_8266_U1_TM2_4 NeoPixelBusLg +#define B_8266_DM_TM2_4 NeoPixelBusLg +#define B_8266_BB_TM2_4 NeoPixelBusLg +//UCS8903 +#define B_8266_U0_UCS_3 NeoPixelBusLg //3 chan, esp8266, gpio1 +#define B_8266_U1_UCS_3 NeoPixelBusLg //3 chan, esp8266, gpio2 +#define B_8266_DM_UCS_3 NeoPixelBusLg //3 chan, esp8266, gpio3 +#define B_8266_BB_UCS_3 NeoPixelBusLg //3 chan, esp8266, bb (any pin but 16) +//UCS8904 RGBW +#define B_8266_U0_UCS_4 NeoPixelBusLg //4 chan, esp8266, gpio1 +#define B_8266_U1_UCS_4 NeoPixelBusLg //4 chan, esp8266, gpio2 +#define B_8266_DM_UCS_4 NeoPixelBusLg //4 chan, esp8266, gpio3 +#define B_8266_BB_UCS_4 NeoPixelBusLg //4 chan, esp8266, bb (any pin) #endif /*** ESP32 Neopixel methods ***/ #ifdef ARDUINO_ARCH_ESP32 //RGB -#define B_32_RN_NEO_3 NeoPixelBrightnessBus +#define B_32_RN_NEO_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS -#define B_32_I0_NEO_3 NeoPixelBrightnessBus +#define B_32_I0_NEO_3 NeoPixelBusLg #endif #ifndef WLED_NO_I2S1_PIXELBUS -#define B_32_I1_NEO_3 NeoPixelBrightnessBus +#define B_32_I1_NEO_3 NeoPixelBusLg #endif -//#define B_32_BB_NEO_3 NeoPixelBrightnessBus // NeoEsp8266BitBang800KbpsMethod +//#define B_32_BB_NEO_3 NeoPixelBusLg // NeoEsp8266BitBang800KbpsMethod //RGBW -#define B_32_RN_NEO_4 NeoPixelBrightnessBus +#define B_32_RN_NEO_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS -#define B_32_I0_NEO_4 NeoPixelBrightnessBus +#define B_32_I0_NEO_4 NeoPixelBusLg #endif #ifndef WLED_NO_I2S1_PIXELBUS -#define B_32_I1_NEO_4 NeoPixelBrightnessBus +#define B_32_I1_NEO_4 NeoPixelBusLg #endif -//#define B_32_BB_NEO_4 NeoPixelBrightnessBus // NeoEsp8266BitBang800KbpsMethod +//#define B_32_BB_NEO_4 NeoPixelBusLg // NeoEsp8266BitBang800KbpsMethod //400Kbps -#define B_32_RN_400_3 NeoPixelBrightnessBus +#define B_32_RN_400_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS -#define B_32_I0_400_3 NeoPixelBrightnessBus +#define B_32_I0_400_3 NeoPixelBusLg #endif #ifndef WLED_NO_I2S1_PIXELBUS -#define B_32_I1_400_3 NeoPixelBrightnessBus +#define B_32_I1_400_3 NeoPixelBusLg #endif -//#define B_32_BB_400_3 NeoPixelBrightnessBus // NeoEsp8266BitBang400KbpsMethod +//#define B_32_BB_400_3 NeoPixelBusLg // NeoEsp8266BitBang400KbpsMethod //TM1814 (RGBW) -#define B_32_RN_TM1_4 NeoPixelBrightnessBus +#define B_32_RN_TM1_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS -#define B_32_I0_TM1_4 NeoPixelBrightnessBus +#define B_32_I0_TM1_4 NeoPixelBusLg #endif #ifndef WLED_NO_I2S1_PIXELBUS -#define B_32_I1_TM1_4 NeoPixelBrightnessBus +#define B_32_I1_TM1_4 NeoPixelBusLg #endif //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //TM1829 (RGB) -#define B_32_RN_TM2_3 NeoPixelBrightnessBus +#define B_32_RN_TM2_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS -#define B_32_I0_TM2_3 NeoPixelBrightnessBus +#define B_32_I0_TM2_3 NeoPixelBusLg #endif #ifndef WLED_NO_I2S1_PIXELBUS -#define B_32_I1_TM2_3 NeoPixelBrightnessBus +#define B_32_I1_TM2_3 NeoPixelBusLg +#endif +//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +//UCS8903 +#define B_32_RN_UCS_3 NeoPixelBusLg +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_UCS_3 NeoPixelBusLg +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_UCS_3 NeoPixelBusLg +#endif +//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +//UCS8904 +#define B_32_RN_UCS_4 NeoPixelBusLg +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_UCS_4 NeoPixelBusLg +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_UCS_4 NeoPixelBusLg #endif //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) @@ -184,45 +235,51 @@ //APA102 #ifdef WLED_USE_ETHERNET // fix for #2542 (by @BlackBird77) -#define B_HS_DOT_3 NeoPixelBrightnessBus //hardware HSPI with DMA (ESP32 only) +#define B_HS_DOT_3 NeoPixelBusLg //hardware HSPI (was DotStarEsp32DmaHspi5MhzMethod in NPB @ 2.6.9) #else -#define B_HS_DOT_3 NeoPixelBrightnessBus //hardware HSPI +#define B_HS_DOT_3 NeoPixelBusLg //hardware VSPI #endif -#define B_SS_DOT_3 NeoPixelBrightnessBus //soft SPI +#define B_SS_DOT_3 NeoPixelBusLg //soft SPI //LPD8806 -#define B_HS_LPD_3 NeoPixelBrightnessBus -#define B_SS_LPD_3 NeoPixelBrightnessBus +#define B_HS_LPD_3 NeoPixelBusLg +#define B_SS_LPD_3 NeoPixelBusLg //LPD6803 -#define B_HS_LPO_3 NeoPixelBrightnessBus -#define B_SS_LPO_3 NeoPixelBrightnessBus +#define B_HS_LPO_3 NeoPixelBusLg +#define B_SS_LPO_3 NeoPixelBusLg //WS2801 -#if defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==40000 -#define B_HS_WS1_3 NeoPixelBrightnessBus // fastest bus speed (not existing method?) -#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==20000 -#define B_HS_WS1_3 NeoPixelBrightnessBus // 20MHz -#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==10000 -#define B_HS_WS1_3 NeoPixelBrightnessBus // 10MHz -#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==2000 -#define B_HS_WS1_3 NeoPixelBrightnessBus //slower, more compatible -#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==1000 -#define B_HS_WS1_3 NeoPixelBrightnessBus //slower, more compatible -#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==500 -#define B_HS_WS1_3 NeoPixelBrightnessBus //slower, more compatible +#ifdef WLED_USE_ETHERNET +#define B_HS_WS1_3 NeoPixelBusLg>, NeoGammaNullMethod> #else -#define B_HS_WS1_3 NeoPixelBrightnessBus // 2MHz; slower, more compatible +#define B_HS_WS1_3 NeoPixelBusLg #endif -#define B_SS_WS1_3 NeoPixelBrightnessBus +#define B_SS_WS1_3 NeoPixelBusLg //P9813 -#define B_HS_P98_3 NeoPixelBrightnessBus -#define B_SS_P98_3 NeoPixelBrightnessBus +#define B_HS_P98_3 NeoPixelBusLg +#define B_SS_P98_3 NeoPixelBusLg + +// 48bit & 64bit to 24bit & 32bit RGB(W) conversion +#define toRGBW32(c) (RGBW32((c>>40)&0xFF, (c>>24)&0xFF, (c>>8)&0xFF, (c>>56)&0xFF)) +#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) //handles pointer type conversion for all possible bus types class PolyBus { public: + // initialize SPI bus speed for DotStar methods + template + static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz = 0U) { + T dotStar_strip = static_cast(busPtr); + #ifdef ESP8266 + dotStar_strip->Begin(); + #else + if (sck == -1 && mosi == -1) dotStar_strip->Begin(); + else dotStar_strip->Begin(sck, miso, mosi, ss); + #endif + if (clock_kHz) dotStar_strip->SetMethodSettings(NeoSpiSettings((uint32_t)clock_kHz*1000)); + } // Begin & initialize the PixelSettings for TM1814 strips. template static void beginTM1814(void* busPtr) { @@ -231,7 +288,7 @@ class PolyBus { // Max current for each LED (22.5 mA). tm1814_strip->SetPixelSettings(NeoTm1814Settings(/*R*/225, /*G*/225, /*B*/225, /*W*/225)); } - static void begin(void* busPtr, uint8_t busType, uint8_t* pins) { + static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) { switch (busType) { case I_NONE: break; #ifdef ESP8266 @@ -255,11 +312,19 @@ class PolyBus { case I_8266_U1_TM2_3: (static_cast(busPtr))->Begin(); break; case I_8266_DM_TM2_3: (static_cast(busPtr))->Begin(); break; case I_8266_BB_TM2_3: (static_cast(busPtr))->Begin(); break; - case I_HS_DOT_3: (static_cast(busPtr))->Begin(); break; - case I_HS_LPD_3: (static_cast(busPtr))->Begin(); break; - case I_HS_LPO_3: (static_cast(busPtr))->Begin(); break; - case I_HS_WS1_3: (static_cast(busPtr))->Begin(); break; - case I_HS_P98_3: (static_cast(busPtr))->Begin(); break; + case I_HS_DOT_3: beginDotStar(busPtr, -1, -1, -1, -1, clock_kHz); break; + case I_HS_LPD_3: beginDotStar(busPtr, -1, -1, -1, -1, clock_kHz); break; + case I_HS_LPO_3: beginDotStar(busPtr, -1, -1, -1, -1, clock_kHz); break; + case I_HS_WS1_3: beginDotStar(busPtr, -1, -1, -1, -1, clock_kHz); break; + case I_HS_P98_3: beginDotStar(busPtr, -1, -1, -1, -1, clock_kHz); break; + case I_8266_U0_UCS_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_UCS_3: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_UCS_3: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_UCS_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_UCS_4: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_UCS_4: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_UCS_4: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_UCS_4: (static_cast(busPtr))->Begin(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Begin(); break; @@ -296,12 +361,28 @@ class PolyBus { case I_32_I1_TM1_4: beginTM1814(busPtr); break; case I_32_I1_TM2_3: (static_cast(busPtr))->Begin(); break; #endif + case I_32_RN_UCS_3: (static_cast(busPtr))->Begin(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_3: (static_cast(busPtr))->Begin(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_3: (static_cast(busPtr))->Begin(); break; + #endif +// case I_32_BB_UCS_3: (static_cast(busPtr))->Begin(); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->Begin(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_4: (static_cast(busPtr))->Begin(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_4: (static_cast(busPtr))->Begin(); break; + #endif +// case I_32_BB_UCS_4: (static_cast(busPtr))->Begin(); break; // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() - case I_HS_DOT_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; - case I_HS_LPD_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; - case I_HS_LPO_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; - case I_HS_WS1_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; - case I_HS_P98_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; + case I_HS_DOT_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; + case I_HS_LPD_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; + case I_HS_LPO_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; + case I_HS_WS1_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; + case I_HS_P98_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; #endif case I_SS_DOT_3: (static_cast(busPtr))->Begin(); break; case I_SS_LPD_3: (static_cast(busPtr))->Begin(); break; @@ -310,7 +391,7 @@ class PolyBus { case I_SS_P98_3: (static_cast(busPtr))->Begin(); break; } }; - static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) { + static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) { void* busPtr = nullptr; switch (busType) { case I_NONE: break; @@ -335,6 +416,14 @@ class PolyBus { case I_8266_U1_TM2_3: busPtr = new B_8266_U1_TM2_4(len, pins[0]); break; case I_8266_DM_TM2_3: busPtr = new B_8266_DM_TM2_4(len, pins[0]); break; case I_8266_BB_TM2_3: busPtr = new B_8266_BB_TM2_4(len, pins[0]); break; + case I_8266_U0_UCS_3: busPtr = new B_8266_U0_UCS_3(len, pins[0]); break; + case I_8266_U1_UCS_3: busPtr = new B_8266_U1_UCS_3(len, pins[0]); break; + case I_8266_DM_UCS_3: busPtr = new B_8266_DM_UCS_3(len, pins[0]); break; + case I_8266_BB_UCS_3: busPtr = new B_8266_BB_UCS_3(len, pins[0]); break; + case I_8266_U0_UCS_4: busPtr = new B_8266_U0_UCS_4(len, pins[0]); break; + case I_8266_U1_UCS_4: busPtr = new B_8266_U1_UCS_4(len, pins[0]); break; + case I_8266_DM_UCS_4: busPtr = new B_8266_DM_UCS_4(len, pins[0]); break; + case I_8266_BB_UCS_4: busPtr = new B_8266_BB_UCS_4(len, pins[0]); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break; @@ -371,6 +460,22 @@ class PolyBus { case I_32_I1_TM1_4: busPtr = new B_32_I1_TM1_4(len, pins[0]); break; case I_32_I1_TM2_3: busPtr = new B_32_I1_TM2_3(len, pins[0]); break; #endif + case I_32_RN_UCS_3: busPtr = new B_32_RN_UCS_3(len, pins[0], (NeoBusChannel)channel); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_3: busPtr = new B_32_I0_UCS_3(len, pins[0]); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_3: busPtr = new B_32_I1_UCS_3(len, pins[0]); break; + #endif +// case I_32_BB_UCS_3: busPtr = new B_32_BB_UCS_3(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_UCS_4: busPtr = new B_32_RN_UCS_4(len, pins[0], (NeoBusChannel)channel); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_4: busPtr = new B_32_I0_UCS_4(len, pins[0]); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_4: busPtr = new B_32_I1_UCS_4(len, pins[0]); break; + #endif +// case I_32_BB_UCS_4: busPtr = new B_32_BB_UCS_4(len, pins[0], (NeoBusChannel)channel); break; #endif // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) case I_HS_DOT_3: busPtr = new B_HS_DOT_3(len, pins[1], pins[0]); break; @@ -384,7 +489,7 @@ class PolyBus { case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break; case I_SS_P98_3: busPtr = new B_SS_P98_3(len, pins[1], pins[0]); break; } - begin(busPtr, busType, pins); + begin(busPtr, busType, pins, clock_kHz); return busPtr; }; static void show(void* busPtr, uint8_t busType) { @@ -411,6 +516,14 @@ class PolyBus { case I_8266_U1_TM2_3: (static_cast(busPtr))->Show(); break; case I_8266_DM_TM2_3: (static_cast(busPtr))->Show(); break; case I_8266_BB_TM2_3: (static_cast(busPtr))->Show(); break; + case I_8266_U0_UCS_3: (static_cast(busPtr))->Show(); break; + case I_8266_U1_UCS_3: (static_cast(busPtr))->Show(); break; + case I_8266_DM_UCS_3: (static_cast(busPtr))->Show(); break; + case I_8266_BB_UCS_3: (static_cast(busPtr))->Show(); break; + case I_8266_U0_UCS_4: (static_cast(busPtr))->Show(); break; + case I_8266_U1_UCS_4: (static_cast(busPtr))->Show(); break; + case I_8266_DM_UCS_4: (static_cast(busPtr))->Show(); break; + case I_8266_BB_UCS_4: (static_cast(busPtr))->Show(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Show(); break; @@ -447,6 +560,22 @@ class PolyBus { case I_32_I1_TM1_4: (static_cast(busPtr))->Show(); break; case I_32_I1_TM2_3: (static_cast(busPtr))->Show(); break; #endif + case I_32_RN_UCS_3: (static_cast(busPtr))->Show(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_3: (static_cast(busPtr))->Show(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_3: (static_cast(busPtr))->Show(); break; + #endif +// case I_32_BB_UCS_3: (static_cast(busPtr))->Show(); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->Show(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_4: (static_cast(busPtr))->Show(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_4: (static_cast(busPtr))->Show(); break; + #endif +// case I_32_BB_UCS_4: (static_cast(busPtr))->Show(); break; #endif case I_HS_DOT_3: (static_cast(busPtr))->Show(); break; case I_SS_DOT_3: (static_cast(busPtr))->Show(); break; @@ -484,6 +613,13 @@ class PolyBus { case I_8266_U1_TM2_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_TM2_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_BB_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_UCS_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_UCS_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_UCS_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_UCS_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_UCS_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_UCS_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_UCS_4: return (static_cast(busPtr))->CanShow(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break; @@ -520,6 +656,22 @@ class PolyBus { case I_32_I1_TM1_4: return (static_cast(busPtr))->CanShow(); break; case I_32_I1_TM2_3: return (static_cast(busPtr))->CanShow(); break; #endif + case I_32_RN_UCS_3: return (static_cast(busPtr))->CanShow(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_3: return (static_cast(busPtr))->CanShow(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_3: return (static_cast(busPtr))->CanShow(); break; + #endif +// case I_32_BB_UCS_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_UCS_4: return (static_cast(busPtr))->CanShow(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_4: return (static_cast(busPtr))->CanShow(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_4: return (static_cast(busPtr))->CanShow(); break; + #endif +// case I_32_BB_UCS_4: return (static_cast(busPtr))->CanShow(); break; #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; case I_SS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -561,36 +713,44 @@ class PolyBus { switch (busType) { case I_NONE: break; #ifdef ESP8266 - case I_8266_U0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_8266_U1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_8266_DM_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_8266_BB_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_U0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_U1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_DM_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_BB_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_8266_U0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_U1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_DM_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_BB_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_8266_U0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_8266_U1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_8266_DM_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_8266_BB_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_U0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_U1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_DM_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_BB_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_8266_U0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_U1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_DM_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_BB_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_8266_U0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_8266_U1_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_8266_DM_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_8266_BB_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_U0_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_8266_U1_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_8266_DM_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_8266_BB_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_8266_U0_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_8266_U1_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_8266_DM_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_8266_BB_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; #endif #ifdef ARDUINO_ARCH_ESP32 - case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; +// case I_32_BB_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_32_RN_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; @@ -599,108 +759,148 @@ class PolyBus { case I_32_I1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; #endif // case I_32_BB_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_RN_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_RN_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; +// case I_32_BB_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(colB)); break; case I_32_RN_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_RN_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_RN_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I1_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I1_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif + case I_32_RN_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + #endif +// case I_32_BB_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + #endif +// case I_32_BB_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; #endif - case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_SS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_HS_LPD_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_SS_LPD_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_HS_LPO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_SS_LPO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_HS_WS1_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_SS_WS1_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_HS_P98_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - case I_SS_P98_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_SS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_HS_LPD_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_SS_LPD_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_HS_LPO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_SS_LPO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_HS_WS1_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_SS_WS1_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_HS_P98_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_SS_P98_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; } }; - static void setBrightness(void* busPtr, uint8_t busType, uint8_t b) { + static void setBrightness(void* busPtr, uint8_t busType, uint8_t b, bool immediate) { // immediate=true is for use in ABL, it applies brightness immediately (warning: inefficient) switch (busType) { case I_NONE: break; #ifdef ESP8266 - case I_8266_U0_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_U1_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_DM_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_BB_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_U0_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_U1_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_DM_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_BB_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_U0_400_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_U1_400_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_DM_400_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_BB_400_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_U0_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_U1_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_DM_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_BB_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_U0_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_U1_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_DM_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_8266_BB_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_U0_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U1_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_DM_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_BB_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U0_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U1_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_DM_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_BB_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U0_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U1_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_DM_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_BB_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U0_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U1_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_DM_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_BB_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U0_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U1_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_DM_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_BB_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U0_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U1_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_DM_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_BB_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif #ifdef ARDUINO_ARCH_ESP32 - case I_32_RN_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_RN_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I0_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I1_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_32_RN_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; +// case I_32_BB_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I1_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif -// case I_32_BB_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_32_RN_400_3: (static_cast(busPtr))->SetBrightness(b); break; +// case I_32_BB_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I0_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I1_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_32_RN_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_32_RN_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; +// case I_32_BB_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I0_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I0_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; - case I_32_I1_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I1_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I1_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif + case I_32_RN_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + #endif +// case I_32_BB_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + #endif +// case I_32_BB_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif - case I_HS_DOT_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_SS_DOT_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_HS_LPD_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_SS_LPD_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_HS_LPO_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_SS_LPO_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_HS_WS1_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_SS_WS1_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_HS_P98_3: (static_cast(busPtr))->SetBrightness(b); break; - case I_SS_P98_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_SS_DOT_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_HS_LPD_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_SS_LPD_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_HS_LPO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_SS_LPO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_HS_WS1_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_SS_WS1_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_HS_P98_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_SS_P98_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; } }; static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { @@ -728,6 +928,14 @@ class PolyBus { case I_8266_U1_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_DM_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_BB_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U0_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; + case I_8266_U1_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; + case I_8266_DM_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; + case I_8266_BB_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; + case I_8266_U0_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; + case I_8266_U1_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; + case I_8266_DM_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; + case I_8266_BB_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -764,6 +972,22 @@ class PolyBus { case I_32_I1_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_I1_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif + case I_32_RN_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; + #endif +// case I_32_BB_UCS_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_RN_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; + #endif +// case I_32_BB_UCS_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_SS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -821,6 +1045,14 @@ class PolyBus { case I_8266_U1_TM2_3: delete (static_cast(busPtr)); break; case I_8266_DM_TM2_3: delete (static_cast(busPtr)); break; case I_8266_BB_TM2_3: delete (static_cast(busPtr)); break; + case I_8266_U0_UCS_3: delete (static_cast(busPtr)); break; + case I_8266_U1_UCS_3: delete (static_cast(busPtr)); break; + case I_8266_DM_UCS_3: delete (static_cast(busPtr)); break; + case I_8266_BB_UCS_3: delete (static_cast(busPtr)); break; + case I_8266_U0_UCS_4: delete (static_cast(busPtr)); break; + case I_8266_U1_UCS_4: delete (static_cast(busPtr)); break; + case I_8266_DM_UCS_4: delete (static_cast(busPtr)); break; + case I_8266_BB_UCS_4: delete (static_cast(busPtr)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: delete (static_cast(busPtr)); break; @@ -857,6 +1089,22 @@ class PolyBus { case I_32_I1_TM1_4: delete (static_cast(busPtr)); break; case I_32_I1_TM2_3: delete (static_cast(busPtr)); break; #endif + case I_32_RN_UCS_3: delete (static_cast(busPtr)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_3: delete (static_cast(busPtr)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_3: delete (static_cast(busPtr)); break; + #endif +// case I_32_BB_UCS_3: delete (static_cast(busPtr)); break; + case I_32_RN_UCS_4: delete (static_cast(busPtr)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_UCS_4: delete (static_cast(busPtr)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_UCS_4: delete (static_cast(busPtr)); break; + #endif +// case I_32_BB_UCS_4: delete (static_cast(busPtr)); break; #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; case I_SS_DOT_3: delete (static_cast(busPtr)); break; @@ -912,6 +1160,10 @@ class PolyBus { return I_8266_U0_TM1_4 + offset; case TYPE_TM1829: return I_8266_U0_TM2_3 + offset; + case TYPE_UCS8903: + return I_8266_U0_UCS_3 + offset; + case TYPE_UCS8904: + return I_8266_U0_UCS_4 + offset; } #else //ESP32 uint8_t offset = 0; //0 = RMT (num 0-7) 8 = I2S0 9 = I2S1 @@ -946,6 +1198,10 @@ class PolyBus { return I_32_RN_TM1_4 + offset; case TYPE_TM1829: return I_32_RN_TM2_3 + offset; + case TYPE_UCS8903: + return I_32_RN_UCS_3 + offset; + case TYPE_UCS8904: + return I_32_RN_UCS_4 + offset; } #endif } diff --git a/wled00/button.cpp b/wled00/button.cpp index fce21424d..d45274a62 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -225,12 +225,10 @@ void handleButton() { static unsigned long lastRead = 0UL; static unsigned long lastRun = 0UL; - bool analog = false; unsigned long now = millis(); - //if (strip.isUpdating()) return; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle) - if (strip.isUpdating() && (millis() - lastRun < 400)) return; // be niced, but avoid button starvation - lastRun = millis(); + if (strip.isUpdating() && (now - lastRun < 400)) return; // don't interfere with strip update (unless strip is updating continuously, e.g. very long strips) + lastRun = now; for (uint8_t b=0; b 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) + // 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 - if (isButtonPressed(b)) { //pressed + // momentary button logic + if (isButtonPressed(b)) { // pressed + + // if all macros are the same, fire action immediately on rising edge + if (macroButton[b] && macroButton[b] == macroLongPress[b] && macroButton[b] == macroDoublePress[b]) { + if (!buttonPressedBefore[b]) + shortPressAction(b); + buttonPressedBefore[b] = true; + buttonPressedTime[b] = now; // continually update (for debouncing to work in release handler) + return; + } if (!buttonPressedBefore[b]) buttonPressedTime[b] = now; buttonPressedBefore[b] = true; @@ -267,9 +278,15 @@ void handleButton() } } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released - long dur = now - buttonPressedTime[b]; - if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce + + // released after rising-edge short press action + if (macroButton[b] && macroButton[b] == macroLongPress[b] && macroButton[b] == macroDoublePress[b]) { + if (dur > WLED_DEBOUNCE_THRESHOLD) buttonPressedBefore[b] = false; // debounce, blocks button for 50 ms once it has been released + return; + } + + if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} // too short "press", debounce bool doublePress = buttonWaitTime[b]; //did we have a short press before? buttonWaitTime[b] = 0; @@ -305,7 +322,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 f1d1e8780..15ef0e1f0 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -156,10 +156,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, freqkHz); mem += BusManager::memUsage(bc); if (mem <= MAX_LED_MEMORY) if (busses.add(bc) == -1) break; // finalization will be done in WLED::beginStrip() } else { @@ -296,9 +297,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } }; if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { #ifdef ESP32 - Wire.setPins(i2c_sda, i2c_scl); // this will fail if Wire is initilised (Wire.begin() called prior) + if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initilised (Wire.begin() called prior) + else Wire.begin(); + #else + Wire.begin(i2c_sda, i2c_scl); #endif - Wire.begin(); } else { i2c_sda = -1; i2c_scl = -1; @@ -335,7 +338,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (light_gc_col > 1.0f) gammaCorrectCol = true; else gammaCorrectCol = false; if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) { - if (gammaCorrectVal != 2.8f) calcGammaTable(gammaCorrectVal); + if (gammaCorrectVal != 2.8f) NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); } else { gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectBri = false; @@ -437,8 +440,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { getStringFromJson(mqttDeviceTopic, if_mqtt[F("topics")][F("device")], 33); // "wled/test" getStringFromJson(mqttGroupTopic, if_mqtt[F("topics")][F("group")], 33); // "" + CJSON(retainMqttMsg, if_mqtt[F("rtn")]); #endif +#ifndef WLED_DISABLE_ESPNOW + JsonObject remote = doc["remote"]; + CJSON(enable_espnow_remote, remote[F("remote_enabled")]); + getStringFromJson(linked_remote, remote[F("linked_remote")], 13); +#endif + + #ifndef WLED_DISABLE_HUESYNC JsonObject if_hue = interfaces["hue"]; CJSON(huePollingEnabled, if_hue["en"]); @@ -735,6 +746,7 @@ void serializeConfig() { ins["type"] = bus->getType() & 0x7F; ins["ref"] = bus->isOffRefreshRequired(); ins[F("rgbwm")] = bus->getAutoWhiteMode(); + ins[F("freq")] = bus->getFrequency(); } JsonArray hw_com = hw.createNestedArray(F("com")); @@ -883,12 +895,20 @@ void serializeConfig() { if_mqtt[F("user")] = mqttUser; if_mqtt[F("pskl")] = strlen(mqttPass); if_mqtt[F("cid")] = mqttClientID; + if_mqtt[F("rtn")] = retainMqttMsg; JsonObject if_mqtt_topics = if_mqtt.createNestedObject(F("topics")); if_mqtt_topics[F("device")] = mqttDeviceTopic; if_mqtt_topics[F("group")] = mqttGroupTopic; #endif +#ifndef WLED_DISABLE_ESPNOW + JsonObject remote = doc.createNestedObject(F("remote")); + remote[F("remote_enabled")] = enable_espnow_remote; + remote[F("linked_remote")] = linked_remote; +#endif + + #ifndef WLED_DISABLE_HUESYNC JsonObject if_hue = interfaces.createNestedObject("hue"); if_hue["en"] = huePollingEnabled; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 9874c3145..8c4baabb5 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -302,7 +302,7 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) { } //gamma 2.8 lookup table used for color correction -static byte gammaT[] = { +uint8_t NeoGammaWLEDMethod::gammaT[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, @@ -320,27 +320,22 @@ static byte gammaT[] = { 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; -uint8_t gamma8_cal(uint8_t b, float gamma) -{ - return (int)(powf((float)b / 255.0f, gamma) * 255.0f + 0.5f); -} - // re-calculates & fills gamma table -void calcGammaTable(float gamma) +void NeoGammaWLEDMethod::calcGammaTable(float gamma) { - for (uint16_t i = 0; i < 256; i++) { - gammaT[i] = gamma8_cal(i, gamma); + for (size_t i = 0; i < 256; i++) { + gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f); } } -// used for individual channel or brightness gamma correction -uint8_t gamma8(uint8_t b) +uint8_t NeoGammaWLEDMethod::Correct(uint8_t value) { - return gammaT[b]; + if (!gammaCorrectCol) return value; + return gammaT[value]; } // used for color gamma correction -uint32_t gamma32(uint32_t color) +uint32_t NeoGammaWLEDMethod::Correct32(uint32_t color) { if (!gammaCorrectCol) return color; uint8_t w = W(color); diff --git a/wled00/const.h b/wled00/const.h index cab89a961..91f3fde53 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -12,6 +12,7 @@ #define DEFAULT_AP_SSID "WLED-AP" #define DEFAULT_AP_PASS "wled1234" #define DEFAULT_OTA_PASS "wledota" +#define DEFAULT_MDNS_NAME "x" //increase if you need more #ifndef WLED_MAX_USERMODS @@ -90,6 +91,21 @@ #endif #endif +#ifndef WLED_MAX_SEGNAME_LEN + #ifdef ESP8266 + #define WLED_MAX_SEGNAME_LEN 32 + #else + #define WLED_MAX_SEGNAME_LEN 64 + #endif +#else + #if WLED_MAX_SEGNAME_LEN<32 + #undef WLED_MAX_SEGNAME_LEN + #define WLED_MAX_SEGNAME_LEN 32 + #else + #warning WLED UI does not support modified maximum segment name length! + #endif +#endif + //Usermod IDs #define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present #define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID @@ -215,6 +231,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) @@ -263,7 +281,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 @@ -272,6 +290,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 @@ -313,7 +335,8 @@ // WLED Error modes #define ERR_NONE 0 // All good :) -#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?) +#define ERR_DENIED 1 // Permission denied +#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?) OBSOLETE #define ERR_NOBUF 3 // JSON buffer was not released in time, request cannot be handled at this time #define ERR_JSON 9 // JSON parsing failed (input too large?) #define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?) @@ -325,12 +348,29 @@ #define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented) #define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented) -//Timer mode types +// Timer mode types #define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness #define NL_MODE_FADE 1 //Fade to target brightness gradually #define NL_MODE_COLORFADE 2 //Fade to target brightness and secondary color gradually #define NL_MODE_SUN 3 //Sunrise/sunset. Target brightness is set immediately, then Sunrise effect is started. Max 60 min. +// Settings sub page IDs +#define SUBPAGE_MENU 0 +#define SUBPAGE_WIFI 1 +#define SUBPAGE_LEDS 2 +#define SUBPAGE_UI 3 +#define SUBPAGE_SYNC 4 +#define SUBPAGE_TIME 5 +#define SUBPAGE_SEC 6 +#define SUBPAGE_DMX 7 +#define SUBPAGE_UM 8 +#define SUBPAGE_UPDATE 9 +#define SUBPAGE_2D 10 +#define SUBPAGE_LOCK 251 +#define SUBPAGE_PINREQ 252 +#define SUBPAGE_CSS 253 +#define SUBPAGE_JS 254 +#define SUBPAGE_WELCOME 255 #define NTP_PACKET_SIZE 48 @@ -363,7 +403,7 @@ #ifdef ESP8266 #define SETTINGS_STACK_BUF_SIZE 2048 #else -#define SETTINGS_STACK_BUF_SIZE 3096 +#define SETTINGS_STACK_BUF_SIZE 3608 // warning: quite a large value for stack #endif #ifdef WLED_USE_ETHERNET @@ -435,7 +475,10 @@ #define DEFAULT_LED_COUNT 30 #endif -#define INTERFACE_UPDATE_COOLDOWN 2000 //time in ms to wait between websockets, alexa, and MQTT updates +#define INTERFACE_UPDATE_COOLDOWN 1000 // time in ms to wait between websockets, alexa, and MQTT updates + +#define PIN_RETRY_COOLDOWN 3000 // time in ms after an incorrect attempt PIN and OTA pass will be rejected even if correct +#define PIN_TIMEOUT 900000 // time in ms after which the PIN will be required again, 15 minutes // HW_PIN_SCL & HW_PIN_SDA are used for information in usermods settings page and usermods themselves // which GPIO pins are actually used in a hardwarea layout (controller board) diff --git a/wled00/data/404.htm b/wled00/data/404.htm index 87244a94f..ff41fa6e0 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 000000000..5a8c801e5 --- /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 e7bd22c2d..049e6f03e 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -134,7 +134,7 @@ button { .off { color: var(--c-6) !important; - cursor: default !important; + /* cursor: default !important; */ } .top .icons, .bot .icons { @@ -151,7 +151,7 @@ button { } .segt TD { - padding: 2px !important; + padding: 2px 0 !important; text-align: center; /*text-transform: uppercase;*/ } @@ -174,34 +174,62 @@ button { } .slider-icon { - transform: translate(3px,3px); -} - -.e-icon { - transform: translateY(3px); + position: absolute; + left: 8px; + bottom: 5px; } .sel-icon { transform: translateX(3px); } -.e-icon, .sel-icon, .slider-icon { +.e-icon, .g-icon, .sel-icon, .slider-icon { cursor: pointer; color: var(--c-d); } +.g-icon { + font-style: normal; + position: absolute; + top: 8px; + right: 8px; +} + +/* pop-up container */ +.pop { + position: absolute; + display: inline-block; + top: 0; + right: 0; +} + +/* pop-up content (segment sets) */ +.pop-c { + position: absolute; + background-color: var(--c-2); + border: 1px solid var(--c-8); + border-radius: 20px; + z-index: 1; + top: 3px; + right: 35px; + padding: 3px 8px 1px; + font-size: 24px; + line-height: 24px; +} +.pop-c span { + padding: 2px 6px; +} + .search-icon { position: absolute; top: 8px; left: 12px; - /*pointer-events: none;*/ width: 24px; height: 24px; } .clear-icon { position: absolute; - display: none; top: 8px; right: 9px; cursor: pointer; @@ -229,14 +257,12 @@ button { #liveview { height: 4px; - display: none; width: 100%; border: 0px; } #liveview2D { height: 90%; - display: none; width: 90%; border: 0px; position: absolute; @@ -314,14 +340,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 +359,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,10 +407,10 @@ button { } #sliders { - width: 300px; - margin: 0 auto; + position: -webkit-sticky; position: sticky; bottom: 0; + max-width: 300px; } #sliders .labels { @@ -392,33 +419,49 @@ button { } .slider { - max-width: 300px; - min-width: 260px; - margin: 0 auto; /* add 5px; if you want some vertical space but looks ugly */ + /*max-width: 300px;*/ + /* 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 { + padding-right: 64px; /* offset for bubble */ +} + #sliders .slider, #info .slider { background-color: var(--c-2); } +#sliders .sliderwrap, .sbs .sliderwrap { + left: 32px; /* 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 */ @@ -467,13 +510,6 @@ button { opacity: 1; } -#pql, .edit-icon { - display: none; -} - -.hide { - display: none !important; -} .fade { visibility: hidden; /* hide it */ opacity: 0; /* make it transparent */ @@ -530,6 +566,7 @@ button { } .close { + position: -webkit-sticky; position: sticky; top: 0; float: right; @@ -551,7 +588,6 @@ button { position: fixed; top: calc(var(--th) + 5px); left: 1px; - display: none; cursor: pointer; } @@ -596,14 +632,9 @@ button { } #info #imgw { - /*display: inline-block;*/ margin: 8px auto; } -/* -#kv, #kn { - display: inline-block; -} -*/ + #lv { max-width: 600px; display: inline-block; @@ -651,23 +682,27 @@ img { #wbal .sliderdisplay { background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #cbdbff); } /* wrapper divs hidden by default */ -#rgbwrap, #swrap, #hwrap, #kwrap, #wwrap, #wbal, #qcs-w, #hexw { +#liveview, #liveview2D, #roverstar, #pql +#rgbwrap, #swrap, #hwrap, #kwrap, #wwrap, #wbal, #qcs-w, #hexw, +.clear-icon, .edit-icon, .ptxt { display: none; } .sliderbubble { width: 24px; - position: relative; + position: absolute; display: inline-block; - border-radius: 10px; + border-radius: 16px; background: var(--c-3); color: var(--c-f); - padding: 2px 4px; + padding: 4px; font-size: 14px; - right: 3px; - transition: visibility 0.25s ease, opacity 0.25s ease; + right: 6px; + transition: visibility .25s ease,opacity .25s ease; opacity: 0; visibility: hidden; + /* left: 8px; */ + top: 4px; } output.sliderbubbleshow { @@ -725,41 +760,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 */ @@ -776,8 +794,8 @@ input[type=range]::-moz-range-thumb { -webkit-transform:translate3d(0,0,0); backface-visibility: hidden; transform:translate3d(0,0,0); - overflow: clip; - text-overflow: clip; + overflow: hidden; + text-overflow: ellipsis; border: 1px solid var(--c-3); background-color: var(--c-3); } @@ -808,6 +826,7 @@ input[type=range]::-moz-range-thumb { .btn-xs, .btn-pl-del, .btn-pl-add { width: 42px !important; height: 42px !important; + text-overflow: clip; } .btn-xs { margin: 2px 0 0 0; @@ -984,26 +1003,26 @@ textarea { } .ptxt { - margin: -1px 4px 8px !important; - display: none; + margin: -1px 4px 8px !important; } .stxt { width: 50px !important; } -.segname, .pname { +.segname, .pname, .bname { white-space: nowrap; - /*cursor: pointer;*/ text-align: center; - overflow: clip; + overflow: hidden; text-overflow: ellipsis; line-height: 24px; padding: 8px 24px; max-width: 170px; - margin: 0 auto; position: relative; } +.bname { + padding: 0 24px; +} .segname .flr, .pname .flr { transform: rotate(0deg); @@ -1012,8 +1031,9 @@ textarea { /* segment power wrapper */ .sbs { - padding: 4px 0 4px 8px; + /*padding: 1px 0 1px 20px;*/ display: var(--sgp); + width: 100%; } .pname { @@ -1055,11 +1075,9 @@ textarea { #csl .xxs { border: 2px solid var(--c-d) !important; - /*box-shadow: 0 0 0 2px var(--c-d);*/ } #csl .xxs-w { border-width: 5px !important; - /*box-shadow: 0 0 0 5px var(--c-d);*/ } .qcs, #namelabel { /* text shadow for name to be legible on grey backround */ @@ -1073,7 +1091,6 @@ textarea { .pwr { color: var(--c-6); - transform: translate(1px, 1px); cursor: pointer; } @@ -1088,18 +1105,13 @@ textarea { } .frz { - left: 32px; + left: 10px; position: absolute; - top: -3px; + top: 8px; cursor: pointer; - padding: 8px; z-index: 1; } -.expanded .frz { - display: none; -} - /* radiobuttons and checkmarks */ .check, .radio { display: block; @@ -1208,10 +1220,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 { @@ -1244,7 +1253,6 @@ TD .checkmark, TD .radiomark { .lbl-s { display: inline-block; - /* margin: 10px 4px 0 0; */ margin-top: 6px; font-size: 13px; width: 48%; @@ -1254,10 +1262,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 +1273,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; @@ -1295,14 +1302,11 @@ TD .checkmark, TD .radiomark { background-color: var(--c-3); } -/*.selected .radiomark { - border: 1px solid var(--c-3); -}*/ - /* selected list item */ .lstI.selected { top: 0; bottom: 0; + border: 1px solid var(--c-4); } .lstI.sticky, @@ -1358,8 +1362,6 @@ TD .checkmark, TD .radiomark { /* find/search element */ .fnd { - width: 280px; - margin: 0 auto; position: relative; } @@ -1391,7 +1393,6 @@ TD .checkmark, TD .radiomark { .presin { padding: 8px; position: relative; - width: 264px; } .btn-s, @@ -1418,7 +1419,7 @@ TD .checkmark, TD .radiomark { .expanded { display: inline-block !important; } -.expanded .segin.hide, .expanded .presin.hide, .expanded .sbs.hide { +.hide, .expanded .segin.hide, .expanded .presin.hide, .expanded .sbs.hide, .expanded .frz, .expanded .g-icon { display: none !important; } @@ -1469,6 +1470,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 +1498,83 @@ 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: 1249px) { - #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; + } + #sliders .sliderwrap, .sbs .sliderwrap { + width: calc(100% - 42px); + } + #sliders .slider { + padding-right: 0; + } + #sliders .sliderwrap { + left: 12px; + } + .segname { + max-width: calc(100% - 110px); + } + .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 050ea5db9..0cf48d6e6 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -67,13 +67,13 @@ - +

Brightness

-
- +
+
@@ -88,7 +88,7 @@
-
+
@@ -198,6 +198,10 @@
+
+ + +
@@ -227,6 +231,10 @@ +
- +
@@ -254,7 +262,7 @@ Effect speed
- +
@@ -355,7 +363,7 @@
- +

@@ -371,8 +379,8 @@
-` - ); - } - } parseInfo(i); + populatePalettes(i); if (isInfo) populateInfo(i); } var s = json.state ? json.state : json; @@ -1610,6 +1645,7 @@ function requestJson(command=null) //load presets and open websocket sequentially if (!pJson || isEmpty(pJson)) setTimeout(()=>{ loadPresets(()=>{ + wsRpt = 0; if (!(ws && ws.readyState === WebSocket.OPEN)) makeWS(); }); },25); @@ -1657,27 +1693,22 @@ function toggleSync() function toggleLiveview() { - //WLEDSR adding liveview2D support if (isInfo && isM) toggleInfo(); if (isNodes && isM) toggleNodes(); isLv = !isLv; + let wsOn = ws && ws.readyState === WebSocket.OPEN; var lvID = "liveview"; - if (isM) { - lvID = "liveview2D" - if (isLv) { - var cn = ''; - d.getElementById('kliveview2D').innerHTML = cn; - } - - gId('mliveview2D').style.transform = (isLv) ? "translateY(0px)":"translateY(100%)"; + if (isM && wsOn) { + lvID += "2D"; + if (isLv) gId('klv2D').innerHTML = ``; + gId('mlv2D').style.transform = (isLv) ? "translateY(0px)":"translateY(100%)"; } gId(lvID).style.display = (isLv) ? "block":"none"; - var url = (loc?`http://${locip}`:'') + "/" + lvID; - gId(lvID).src = (isLv) ? url:"about:blank"; - gId('buttonSr').className = (isLv) ? "active":""; - if (!isLv && ws && ws.readyState === WebSocket.OPEN) ws.send('{"lv":false}'); + gId(lvID).src = (isLv) ? getURL("/" + lvID + ((wsOn) ? "?ws":"")):"about:blank"; + gId('buttonSr').classList.toggle("active"); + if (!isLv && wsOn) ws.send('{"lv":false}'); size(); } @@ -1720,37 +1751,41 @@ 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; } function resetUtil(off=false) { - gId('segutil').innerHTML = `
` + gId('segutil').innerHTML = `
` + '' - + `
Add segment
`; + + `
Add segment
` + + '
' + + `` + + '
' + + '
'; } function makePlSel(el, incPl=false) @@ -2017,14 +2052,14 @@ function tglSegn(s) function selSegAll(o) { var obj = {"seg":[]}; - for (let i=0; i<=lSeg; i++) obj.seg.push({"id":i,"sel":o.checked}); + for (let i=0; i<=lSeg; i++) if (gId(`seg${i}`)) obj.seg.push({"id":i,"sel":o.checked}); requestJson(obj); } function selSegEx(s) { var obj = {"seg":[]}; - for (let i=0; i<=lSeg; i++) obj.seg.push({"id":i,"sel":(i==s)}); + for (let i=0; i<=lSeg; i++) if (gId(`seg${i}`)) obj.seg.push({"id":i,"sel":(i==s)}); obj.mainseg = s; requestJson(obj); } @@ -2036,6 +2071,20 @@ function selSeg(s) requestJson(obj); } +function selGrp(g) +{ + event.preventDefault(); + event.stopPropagation(); + var sel = gId(`segcont`).querySelectorAll(`div[data-set="${g}"]`); + var obj = {"seg":[]}; + for (let i=0; i<=lSeg; i++) if (gId(`seg${i}`)) obj.seg.push({"id":i,"sel":false}); + if (sel) for (let s of sel||[]) { + let i = parseInt(s.id.substring(3)); + obj.seg[i] = {"id":i,"sel":true}; + } + if (obj.seg.length) requestJson(obj); +} + function rptSeg(s) { //TODO: 2D support @@ -2156,6 +2205,14 @@ function setTp(s) requestJson(obj); } +function setGrp(s, g) +{ + event.preventDefault(); + event.stopPropagation(); + var obj = {"seg": {"id": s, "set": g}}; + requestJson(obj); +} + function setSegPwr(s) { var pwr = gId(`seg${s}pwr`).classList.contains('act'); @@ -2187,8 +2244,7 @@ function setFX(ind = null) } else { d.querySelector(`#fxlist input[name="fx"][value="${ind}"]`).checked = true; } - - var obj = {"seg": {"fx": parseInt(ind),"fxdef":1}}; // fxdef sets effect parameters to default values, TODO add client setting + var obj = {"seg": {"fx": parseInt(ind), "fxdef": cfg.comp.fxdef}}; // fxdef sets effect parameters to default values requestJson(obj); } @@ -2522,6 +2578,24 @@ function setBalance(b) requestJson(obj); } +function rmtTgl(ip,i) { + event.preventDefault(); + event.stopPropagation(); + fetch(`http://${ip}/win&T=2`, {method: 'get'}) + .then((r)=>{ + return r.text(); + }) + .then((t)=>{ + let c = (new window.DOMParser()).parseFromString(t, "text/xml"); + // perhaps just i.classList.toggle("off"); would be enough + if (c.getElementsByTagName('ac')[0].textContent === "0") { + i.classList.add("off"); + } else { + i.classList.remove("off"); + } + }); +} + var hc = 0; setInterval(()=>{ if (!isInfo) return; @@ -2544,7 +2618,7 @@ function cnfReset() bt.innerHTML = "Confirm Reboot"; cnfr = true; return; } - window.location.href = "/reset"; + window.location.href = getURL("/reset"); } var cnfrS = false; @@ -2598,9 +2672,7 @@ function loadPalettesData(callback = null) function getPalettesData(page, callback) { - var url = (loc?`http://${locip}`:'') + `/json/palx?page=${page}`; - - fetch(url, { + fetch(getURL(`/json/palx?page=${page}`), { method: 'get', headers: { "Content-type": "application/json; charset=UTF-8" @@ -2619,7 +2691,7 @@ function getPalettesData(page, callback) showToast(error, true); }); } - +/* function hideModes(txt) { for (let e of (gId('fxlist').querySelectorAll('.lstI')||[])) { @@ -2630,7 +2702,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'; @@ -2795,20 +2867,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) h -= 4; sCol('--tp', h + "px"); togglePcMode(); + lastw = wW; } function togglePcMode(fromB = false) @@ -2816,19 +2884,16 @@ function togglePcMode(fromB = false) if (fromB) { pcModeA = !pcModeA; localStorage.setItem('pcm', pcModeA); - pcMode = pcModeA; } - if (wW < 1250 && !pcMode) return; - if (!fromB && ((wW < 1250 && lastw < 1250) || (wW >= 1250 && lastw >= 1250))) 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 < 1250) {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; } function mergeDeep(target, ...sources) @@ -2852,7 +2917,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/liveview.htm b/wled00/data/liveview.htm index 6bff678ed..60e9fb03e 100644 --- a/wled00/data/liveview.htm +++ b/wled00/data/liveview.htm @@ -17,21 +17,17 @@ position: absolute; } - - -
+ + +
\ No newline at end of file diff --git a/wled00/data/liveviewws.htm b/wled00/data/liveviewws.htm deleted file mode 100644 index 0689b201b..000000000 --- a/wled00/data/liveviewws.htm +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - WLED Live Preview - - - -
- - - \ No newline at end of file diff --git a/wled00/data/liveviewws2D.htm b/wled00/data/liveviewws2D.htm index 3b8ac72f3..007ac2467 100644 --- a/wled00/data/liveviewws2D.htm +++ b/wled00/data/liveviewws2D.htm @@ -33,7 +33,14 @@ if (ws && ws.readyState === WebSocket.OPEN) { ws.send("{'lv':true}"); } else { - ws = new WebSocket((window.location.protocol == "https:"?"wss":"ws")+"://"+document.location.host+"/ws"); + let l = window.location; + let pathn = l.pathname; + let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/"); + let url = l.origin.replace("http","ws"); + if (paths.length > 1) { + url += "/" + paths[0]; + } + ws = new WebSocket(url+"/ws"); ws.onopen = ()=>{ ws.send("{'lv':true}"); } diff --git a/wled00/data/msg.htm b/wled00/data/msg.htm index bb5983381..2bb7e8825 100644 --- a/wled00/data/msg.htm +++ b/wled00/data/msg.htm @@ -6,8 +6,8 @@ WLED Message + + +
+
+
+
+ + + +
+
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ +
+ + +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+
+ + +
+ + Images uploaded to + + WLED + + or upload image + +
+
+ +
+
+ +
+
+
+ +
+ +
+
+
+ + + diff --git a/wled00/data/settings.htm b/wled00/data/settings.htm index dc8e3838f..2d0c04012 100644 --- a/wled00/data/settings.htm +++ b/wled00/data/settings.htm @@ -6,7 +6,7 @@ WLED Settings - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/wled00/data/settings_2D.htm b/wled00/data/settings_2D.htm index 5258439b4..5c95d3170 100644 --- a/wled00/data/settings_2D.htm +++ b/wled00/data/settings_2D.htm @@ -7,11 +7,11 @@ 2D Set-up diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6637ff9ca..9f822294a 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -10,11 +10,11 @@ d.um_p = []; d.rsvd = []; d.ro_gpio = []; - d.max_gpio = 39; + d.max_gpio = 50; var customStarts=false,startsDirty=[],maxCOOverrides=5; - var loc = false, locip; + var loc = false, locip, locproto = "http:"; function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");} - function B(){window.open("/settings","_self");} + function B(){window.open(getURL("/settings"),"_self");} function gId(n){return d.getElementById(n);} function off(n){d.getElementsByName(n)[0].value = -1;} // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript @@ -26,8 +26,12 @@ d.body.appendChild(scE); // success event scE.addEventListener("load", () => { - GetV();checkSi();setABL(); + GetV(); + checkSi(); + setABL(); + d.Sf.addEventListener("submit", trySubmit); if (d.um_p[0]==-1) d.um_p.shift(); + pinDropdowns(); }); // error event scE.addEventListener("error", (ev) => { @@ -49,7 +53,7 @@ maxB = b; maxV = v; maxM = m; maxPB = p; maxL = l; } function pinsOK() { - var LCs = d.getElementsByTagName("input"); + var LCs = d.Sf.querySelectorAll("#mLC input[name^=L]"); // input fields for (i=0; i=80) continue; } //check for pin conflicts - if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR") + if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4"/* || nm=="RL" || nm=="BT" || nm=="IR"*/) if (LCs[i].value!="" && LCs[i].value!="-1") { - var p = []; // used pin array - for (k=0;ke==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);LCs[i].value="";LCs[i].focus();return false;} - else if (!(nm == "IR" || nm=="BT") && d.ro_gpio.some((e)=>e==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);LCs[i].value="";LCs[i].focus();return false;} - for (j=i+1; j{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay + if (p.some((e)=>e==parseInt(LCs[i].value))) { + alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`); + LCs[i].value=""; + LCs[i].focus(); + return false; + } + else if (/*!(nm == "IR" || nm=="BT") &&*/ d.ro_gpio.some((e)=>e==parseInt(LCs[i].value))) { + alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`); + LCs[i].value=""; + LCs[i].focus(); + return false; + } + for (j=i+1; j=80) continue; } - if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {alert(`Pin conflict between ${LCs[i].name}/${LCs[j].name}!`);LCs[j].value="";LCs[j].focus();return false;} + if (LCs[j].value!="" && LCs[i].value==LCs[j].value) { + alert(`Pin conflict between ${LCs[i].name}/${LCs[j].name}!`); + LCs[j].value=""; + LCs[j].focus(); + return false; + } } } } @@ -96,6 +113,7 @@ gId('abl').style.display = (en) ? 'inline':'none'; gId('psu2').style.display = (en) ? 'inline':'none'; if (d.Sf.LA.value > 0) setABL(); + UI(); } function enLA() { @@ -117,28 +135,28 @@ default: gId('LAdis').style.display = 'inline'; } gId('m1').innerHTML = maxM; - d.getElementsByName("Sf")[0].addEventListener("submit", trySubmit); - UI(); } //returns mem usage function getMem(t, n) { let len = parseInt(d.getElementsByName("LC"+n)[0].value); len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too + let dbl = 0; + if (d.Sf.LD.checked) dbl = len * 3; // double buffering 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 - return len*15; + if (t > 28) return len*20 + dbl; //RGBW + return len*15 + dbl; } else if (maxM >= 10000) //ESP32 RMT uses double buffer? { - if (t > 29) return len*8; //RGBW - return len*6; + if (t > 28) return len*8 + dbl; //RGBW + return len*6 + dbl; } - if (t > 29) return len*4; //RGBW - return len*3; + if (t > 28) return len*4 + dbl; //RGBW + return len*3 + dbl; } - if (t > 31 && t < 48) return 5; - if (t == 44 || t == 45) return len*4; //RGBW - return len*3; + if (t > 31 && t < 48) return 5; // analog + return len*3 + dbl; } function UI(change=false) @@ -151,52 +169,50 @@ else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value; // enable/disable LED fields - var s = d.getElementsByTagName("select"); - for (i=0; i{ // is the field a LED type? - if (s[i].name.substring(0,2)=="LT") { - var n = s[i].name.substring(2); - var t = parseInt(s[i].value,10); - gId("p0d"+n).innerHTML = (t>=80 && t<96) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:"; - gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : ""; - var LK = d.getElementsByName("L1"+n)[0]; // clock pin + var n = s.name.substring(2); + var t = parseInt(s.value); + gId("p0d"+n).innerHTML = (t>=80 && t<96) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:"; + gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : ""; + //var LK = d.getElementsByName("L1"+n)[0]; // clock pin - memu += getMem(t, n); // calc memory + memu += getMem(t, n); // calc memory - // enumerate pins - for (p=1; p<5; p++) { - var LK = d.getElementsByName("L"+p+n)[0]; // secondary pins - if (!LK) continue; - if (((t>=80 && t<96) && p<4) || (t>49 && p==1) || (t>41 && t < 50 && (p+40 < t))) // TYPE_xxxx values from const.h - { - // display pin field - LK.style.display = "inline"; - LK.required = true; - } else { - // hide pin field - LK.style.display = "none"; - LK.required = false; - LK.value=""; - } + // enumerate pins + for (p=1; p<5; p++) { + var LK = d.getElementsByName("L"+p+n)[0]; // secondary pins + if (!LK) continue; + if (((t>=80 && t<96) && p<4) || (t>49 && p==1) || (t>41 && t < 50 && (p+40 < t))) // TYPE_xxxx values from const.h + { + // display pin field + LK.style.display = "inline"; + LK.required = true; + } else { + // hide pin field + LK.style.display = "none"; + LK.required = false; + LK.value=""; } - if (change) { - gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state - 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 - 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+"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("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 } - } + if (change) { + gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state + 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 > 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 > 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 + }); // display global white channel overrides gId("wc").style.display = (gRGBW) ? 'inline':'none'; if (!gRGBW) { @@ -204,7 +220,7 @@ d.Sf.CR.checked = false; } // check for pin conflicts - var LCs = d.getElementsByTagName("input"); + var LCs = d.Sf.querySelectorAll("#mLC input[name^=L]"); // input fields var sLC = 0, sPC = 0, maxLC = 0; for (i=0; i{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay for (j=0; je==parseInt(LCs[i].value,10))) LCs[i].style.color="red"; else LCs[i].style.color=d.ro_gpio.some((e)=>e==parseInt(LCs[i].value,10))?"orange":"#fff"; + if (p.some((e)=>e==parseInt(LCs[i].value))) LCs[i].style.color="red"; else LCs[i].style.color=d.ro_gpio.some((e)=>e==parseInt(LCs[i].value))?"orange":"#fff"; } // check buttons, IR & relay - if (nm=="IR" || nm=="BT" || nm=="RL") { - LCs[i].max = d.max_gpio; - LCs[i].min = -1; - } + //if (nm=="IR" || nm=="BT" || nm=="RL") { + // LCs[i].max = d.max_gpio; + // LCs[i].min = -1; + //} } // update total led count gId("lc").textContent = sLC; @@ -330,6 +345,8 @@ ${i+1}: \ \ \ +\ +\ \ \ \ @@ -358,15 +375,16 @@ ${i+1}:
+
Start:  
Length:

-GPIO: - - - - +GPIO: + + + +

Reversed:

Skip first LEDs:

Off Refresh:
@@ -542,17 +560,126 @@ Length: {fields.push(e.name);}) // buttons + for (let i of d.Sf.elements) { + if (i.type === "number" && fields.includes(i.name)) { //select all pin select elements + let v = parseInt(i.value); + let sel = addDropdown(i.name,0); + for (var j = -1; j <= d.max_gpio; j++) { + if (d.rsvd.includes(j)) continue; + let foundPin = d.um_p.indexOf(j); + let txt = (j === -1) ? "unused" : `${j}`; + if (foundPin >= 0 && j !== v) txt += ` used`; // already reserved pin + if (d.ro_gpio.includes(j)) txt += " (R/O)"; + let opt = addOption(sel, txt, j); + if (j === v) opt.selected = true; // this is "our" pin + else if (d.um_p.includes(j)) opt.disabled = true; // someone else's pin + } + } + } + // update select options + d.Sf.querySelectorAll("select.pin").forEach((e)=>{pinUpd(e);}); + // add dataset values for LED GPIO pins + d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i)=>{ + if (i.value!=="" && i.value>=0) + i.dataset.val = i.value; + }); + } + function pinUpd(e) { + // update changed select options across all usermods + let oldV = parseInt(e.dataset.val); + e.dataset.val = e.value; + let txt = e.name; + let pins = []; + d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i)=>{ + if (i.value!=="" && i.value>=0 && i.max<255) + pins.push(i.value); + }); + let selects = d.Sf.querySelectorAll("select.pin"); + for (let sel of selects) { + if (sel == e) continue + Array.from(sel.options).forEach((i)=>{ + let led = pins.includes(i.value); + if (!(i.value==oldV || i.value==e.value || led)) return; + if (i.value == -1) { + i.text = "unused"; + return + } + i.text = i.value; + if (i.value==oldV) { + i.disabled = false; + } + if (i.value==e.value || led) { + i.disabled = true; + i.text += ` ${led?'LED':txt}`; + } + if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)"; + }); + } + } + // https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option + function addDropdown(field) { + let sel = d.createElement('select'); + sel.classList.add("pin"); + let inp = d.getElementsByName(field)[0]; + if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName + let v = inp.value; + let n = inp.name; + // copy the existing input element's attributes to the new select element + for (var i = 0; i < inp.attributes.length; ++ i) { + var att = inp.attributes[i]; + // type and value don't apply, so skip them + // ** you might also want to skip style, or others -- modify as needed ** + if (att.name != 'type' && att.name != 'value' && att.name != 'class' && att.name != 'style') { + sel.setAttribute(att.name, att.value); + } + } + sel.setAttribute("data-val", v); + sel.setAttribute("onchange", "pinUpd(this)"); + // finally, replace the old input element with the new select element + inp.parentElement.replaceChild(sel, inp); + return sel; + } + return null; + } + function addOption(sel,txt,val) { + if (sel===null) return; // select object missing + let opt = d.createElement("option"); + opt.value = val; + opt.text = txt; + sel.appendChild(opt); + for (let i=0; i 2) { + locproto = l.protocol; + loc = true; + locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0]; + } } - var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=2'; - loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed + loadJS(getURL('/settings/s.js?p=2'), false); // If we set async false, file is loaded and executed, then next statement is processed + if (loc) d.Sf.action = getURL('/settings/leds'); + } + function getURL(path) { + return (loc ? locproto + "//" + locip : "") + path; } @@ -572,7 +699,7 @@ Length:
Maximum Current: mA
-