From 405a2e8989d16884695d3829c618c5296472005d Mon Sep 17 00:00:00 2001 From: thatdonfc Date: Wed, 12 May 2021 07:19:54 -0700 Subject: [PATCH] Merge wled/master into sr-wled/master Initially had merge issues, working on a fix --- .envrc | 1 + .github/workflows/wled-ci.yml | 67 +- .gitignore | 1 + CHANGELOG.md | 55 + package-lock.json | 4 +- package.json | 2 +- pio-scripts/gzip-firmware.py | 23 - pio-scripts/name-firmware.py | 34 - pio-scripts/output_bins.py | 69 + pio-scripts/set_version.py | 8 + platformio.ini | 75 +- platformio_override.ini.sample | 14 +- requirements.in | 1 + requirements.txt | 54 + tools/WLED_ESP32_8MB.csv | 6 + tools/cdata.js | 20 +- tools/fps_test.htm | 232 +++ .../data/jsontest.htm => tools/json_test.htm | 0 .../Animated_Staircase/Animated_Staircase.h | 397 +++-- .../Animated_Staircase_config.h | 21 - usermods/Animated_Staircase/README.md | 58 +- usermods/BME280_v2/usermod_bme280.h | 86 +- usermods/PIR_sensor_switch/readme.md | 30 +- .../usermod_PIR_sensor_switch.h | 295 ++-- usermods/Temperature/readme.md | 11 +- usermods/Temperature/usermod_temperature.h | 273 ++- usermods/VL53L0X_gestures/readme.md | 35 + .../usermod_vl53l0x_gestures.h | 121 ++ usermods/multi_relay/readme.md | 55 + usermods/multi_relay/usermod_multi_relay.h | 434 +++++ usermods/usermod_v2_auto_save/readme.md | 8 +- .../usermod_v2_auto_save.h | 178 +- .../usermod_v2_four_line_display/readme.md | 10 +- .../usermod_v2_four_line_display.h | 577 ++++--- wled00/.vs/wled00/v15/.suo | Bin 41984 -> 0 bytes wled00/FX.cpp | 139 +- wled00/FX.h | 15 +- wled00/FX_fcn.cpp | 46 +- wled00/__vm/.wled00.vsarduino.h | 124 -- wled00/__vm/Compile.vmps.xml | 12 - wled00/__vm/Configuration.Release.vmps.xml | 9 - wled00/bus_manager.h | 5 + wled00/bus_wrapper.h | 48 +- wled00/button.cpp | 38 +- wled00/cfg.cpp | 198 ++- wled00/const.h | 44 +- wled00/data/index.js | 26 +- wled00/data/settings.htm | 9 +- wled00/data/settings_sec.htm | 2 +- wled00/data/settings_sync.htm | 7 +- wled00/data/settings_um.htm | 141 ++ wled00/data/update.htm | 2 +- wled00/fcn_declare.h | 9 +- wled00/file.cpp | 5 +- wled00/html_other.h | 10 +- wled00/html_settings.h | 37 +- wled00/html_ui.h | 1367 +++++++-------- wled00/ir.cpp | 12 +- wled00/json.cpp | 40 +- wled00/mqtt.cpp | 61 +- wled00/ntp.cpp | 56 +- wled00/overlay.cpp | 5 +- wled00/playlist.cpp | 4 +- wled00/set.cpp | 61 +- wled00/tv_colors.h | 1506 ----------------- wled00/um_manager.cpp | 5 + wled00/usermods_list.cpp | 49 +- wled00/wled.cpp | 9 +- wled00/wled.h | 29 +- wled00/wled_eeprom.cpp | 2 +- wled00/wled_math.h | 83 + wled00/wled_server.cpp | 31 +- wled00/xml.cpp | 12 +- 73 files changed, 3964 insertions(+), 3549 deletions(-) create mode 100644 .envrc delete mode 100644 pio-scripts/gzip-firmware.py delete mode 100644 pio-scripts/name-firmware.py create mode 100644 pio-scripts/output_bins.py create mode 100644 pio-scripts/set_version.py create mode 100644 requirements.in create mode 100644 requirements.txt create mode 100644 tools/WLED_ESP32_8MB.csv create mode 100644 tools/fps_test.htm rename wled00/data/jsontest.htm => tools/json_test.htm (100%) delete mode 100644 usermods/Animated_Staircase/Animated_Staircase_config.h create mode 100644 usermods/VL53L0X_gestures/readme.md create mode 100644 usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h create mode 100644 usermods/multi_relay/readme.md create mode 100644 usermods/multi_relay/usermod_multi_relay.h delete mode 100644 wled00/.vs/wled00/v15/.suo delete mode 100644 wled00/__vm/.wled00.vsarduino.h delete mode 100644 wled00/__vm/Compile.vmps.xml delete mode 100644 wled00/__vm/Configuration.Release.vmps.xml create mode 100644 wled00/data/settings_um.htm delete mode 100644 wled00/tv_colors.h create mode 100644 wled00/wled_math.h diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..83dab5d19d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +layout python-venv python3 diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index 4158dd7ac7..c28446e614 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -3,10 +3,37 @@ name: PlatformIO CI on: [push, pull_request] jobs: - build: + get_default_envs: + name: Gather Environments runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - uses: actions/setup-python@v2 + - 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]')" + outputs: + environments: ${{ steps.envs.outputs.environments }} + + build: + name: Build Enviornments + runs-on: ubuntu-latest + needs: get_default_envs + strategy: + matrix: + environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }} steps: - uses: actions/checkout@v2 - name: Cache pip @@ -24,8 +51,36 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - - name: Run PlatformIO - run: pio run \ No newline at end of file + run: pip install -r requirements.txt + - name: Build firmware + env: + WLED_RELEASE: True + run: pio run -e ${{ matrix.environment }} + - uses: actions/upload-artifact@v2 + with: + name: firmware-${{ matrix.environment }} + path: | + build_output/firmware/*.bin + build_output/firmware/*.gz + - uses: actions/upload-artifact@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + name: firmware-release + path: build_output/release/*.bin + release: + name: Create Release + runs-on: ubuntu-latest + needs: [get_default_envs, build] + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/download-artifact@v2 + with: + name: firmware-release + - name: Create draft release + uses: softprops/action-gh-release@v1 + with: + draft: True + files: | + *.bin + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 61d72b1dc2..47c1f0feab 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ node_modules .cproject .project .idea +.direnv diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c2f51c4ac..0e5c7d021c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,61 @@ ### Builds after release 0.12.0 +#### Build 2105111 + +- Fixed various Codacy code style and logic issues + +#### Build 2105110 + +- Added Usermod settings page and configurable usermods (PR #1951) +- Added experimental `/json/cfg` endpoint for changing settings from JSON (see #1944, not part of official API) + +#### Build 2105070 + +- Fixed not turning on after pressing "Off" on IR remote twice (#1950) +- Fixed OTA update file selection from Android app (TODO: file type verification in JS, since android can't deal with accept='.bin' attribute) + +#### Build 2104220 + +- Version bump to 0.12.1-b1 "Hikari" +- Release and build script improvements (PR #1844) + +#### Build 2104211 + +- Replace default TV simulator effect with the version that saves 18k of flash and appears visually identical + +#### Build 2104210 + +- Added `tb` to JSON state, allowing setting the timebase (set tb=0 to start e.g. wipe effect from the beginning). Receive only. +- Slightly raised Solid mode refresh rate to work with LEDs (TM1814) that require refresh rates of at least 2fps +- Added sunrise and sunset calculation to the backup JSON time source + +#### Build 2104151 + +- `NUM_STRIPS` no longer required with compile-time strip defaults +- Further optimizations in wled_math.h + +#### Build 2104150 + +- Added ability to add multiple busses as compile time defaults using the esp32_multistrip usermod define syntax + +#### Build 2104141 + +- Reduced memory usage by 540b by switching to a different trigonometric approximation + +#### Build 2104140 + +- Added dynamic location-based Sunrise/Sunset macros (PR #1889) +- Improved seasonal background handling (PR #1890) +- Fixed instance discovery not working if MQTT not compiled in +- Fixed Button, IR, Relay pin not assigned by default (resolves #1891) + +#### Build 2104120 + +- Added switch support (button macro is switch closing action, long press macro switch opening) +- Replaced Circus effect with new Running Dual effect (Circus is Tricolor Chase with Red/White/Black) +- Fixed ledmap with multiple segments (PR #1864) + #### Build 2104030 - Fixed ESP32 crash on Drip effect with reversed segment (#1854) diff --git a/package-lock.json b/package-lock.json index f5e8d475d5..87272e4100 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.12.0", + "version": "0.12.1-b1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wled", - "version": "0.12.0", + "version": "0.12.1-b1", "license": "ISC", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index a94d15b763..466800b434 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.12.0", + "version": "0.12.1-b1", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/pio-scripts/gzip-firmware.py b/pio-scripts/gzip-firmware.py deleted file mode 100644 index 2d02830115..0000000000 --- a/pio-scripts/gzip-firmware.py +++ /dev/null @@ -1,23 +0,0 @@ -Import('env') -import os -import shutil -import gzip - -OUTPUT_DIR = "build_output{}".format(os.path.sep) - -def bin_gzip(source, target, env): - variant = str(target[0]).split(os.path.sep)[2] - - # create string with location and file names based on variant - bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) - gzip_file = "{}firmware{}{}.bin.gz".format(OUTPUT_DIR, os.path.sep, variant) - - # check if new target files exist and remove if necessary - if os.path.isfile(gzip_file): os.remove(gzip_file) - - # write gzip firmware file - with open(bin_file,"rb") as fp: - with gzip.open(gzip_file, "wb", compresslevel = 9) as f: - shutil.copyfileobj(fp, f) - -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_gzip]) diff --git a/pio-scripts/name-firmware.py b/pio-scripts/name-firmware.py deleted file mode 100644 index 90ea9867fd..0000000000 --- a/pio-scripts/name-firmware.py +++ /dev/null @@ -1,34 +0,0 @@ -Import('env') -import os -import shutil - -OUTPUT_DIR = "build_output{}".format(os.path.sep) - -def bin_rename_copy(source, target, env): - variant = str(target[0]).split(os.path.sep)[2] - - # check if output directories exist and create if necessary - if not os.path.isdir(OUTPUT_DIR): - os.mkdir(OUTPUT_DIR) - - for d in ['firmware', 'map']: - if not os.path.isdir("{}{}".format(OUTPUT_DIR, d)): - os.mkdir("{}{}".format(OUTPUT_DIR, d)) - - # create string with location and file names based on variant - map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant) - bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) - - # check if new target files exist and remove if necessary - for f in [map_file, bin_file]: - if os.path.isfile(f): - os.remove(f) - - # copy firmware.bin to firmware/.bin - shutil.copy(str(target[0]), bin_file) - - # copy firmware.map to map/.map - if os.path.isfile("firmware.map"): - shutil.move("firmware.map", map_file) - -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_rename_copy]) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py new file mode 100644 index 0000000000..e88e46809e --- /dev/null +++ b/pio-scripts/output_bins.py @@ -0,0 +1,69 @@ +Import('env') +import os +import shutil +import gzip + +OUTPUT_DIR = "build_output{}".format(os.path.sep) + +def _get_cpp_define_value(env, define): + define_list = [item[-1] for item in env["CPPDEFINES"] if item[0] == define] + + if define_list: + return define_list[0] + + return None + +def _create_dirs(dirs=["firmware", "map"]): + # check if output directories exist and create if necessary + if not os.path.isdir(OUTPUT_DIR): + os.mkdir(OUTPUT_DIR) + + for d in dirs: + if not os.path.isdir("{}{}".format(OUTPUT_DIR, d)): + os.mkdir("{}{}".format(OUTPUT_DIR, d)) + +def bin_rename_copy(source, target, env): + _create_dirs() + variant = env["PIOENV"] + + # create string with location and file names based on variant + map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant) + bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) + + release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") + + if release_name and os.getenv("WLED_RELEASE"): + _create_dirs(["release"]) + version = _get_cpp_define_value(env, "WLED_VERSION") + release_file = "{}release{}soundReactive_WLED_{}_{}.bin".format(OUTPUT_DIR, os.path.sep, version, release_name) + shutil.copy(str(target[0]), release_file) + + # check if new target files exist and remove if necessary + for f in [map_file, bin_file]: + if os.path.isfile(f): + os.remove(f) + + # copy firmware.bin to firmware/.bin + shutil.copy(str(target[0]), bin_file) + + # copy firmware.map to map/.map + if os.path.isfile("firmware.map"): + shutil.move("firmware.map", map_file) + +def bin_gzip(source, target, env): + _create_dirs() + variant = env["PIOENV"] + + # create string with location and file names based on variant + bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) + gzip_file = "{}firmware{}{}.bin.gz".format(OUTPUT_DIR, os.path.sep, variant) + + # check if new target files exist and remove if necessary + if os.path.isfile(gzip_file): os.remove(gzip_file) + + # write gzip firmware file + with open(bin_file,"rb") as fp: + with gzip.open(gzip_file, "wb", compresslevel = 9) as f: + shutil.copyfileobj(fp, f) + +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_rename_copy, bin_gzip]) diff --git a/pio-scripts/set_version.py b/pio-scripts/set_version.py new file mode 100644 index 0000000000..1d8e076ea8 --- /dev/null +++ b/pio-scripts/set_version.py @@ -0,0 +1,8 @@ +Import('env') +import json + +PACKAGE_FILE = "package.json" + +with open(PACKAGE_FILE, "r") as package: + version = json.load(package)["version"] + env.Append(BUILD_FLAGS=[f"-DWLED_VERSION={version}"]) diff --git a/platformio.ini b/platformio.ini index d845b4585a..823440fd26 100644 --- a/platformio.ini +++ b/platformio.ini @@ -118,9 +118,6 @@ build_unflags = # enables all features for travis CI build_flags_all_features = - -D WLED_USE_ANALOG_LED - -D WLED_USE_H801 - -D WLED_ENABLE_5CH_LEDS -D WLED_ENABLE_ADALIGHT -D WLED_ENABLE_DMX -D WLED_ENABLE_MQTT @@ -155,10 +152,11 @@ build_flags = -g -DCONFIG_LITTLEFS_FOR_IDF_3_2 [scripts_defaults] -extra_scripts = pio-scripts/name-firmware.py - pio-scripts/gzip-firmware.py - pio-scripts/strip-floats.py - pio-scripts/user_config_copy.py +extra_scripts = + pre:pio-scripts/set_version.py + post:pio-scripts/output_bins.py + post:pio-scripts/strip-floats.py + pre:pio-scripts/user_config_copy.py # ------------------------------------------------------------------------------ # COMMON SETTINGS: @@ -218,7 +216,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 [env:esp01_1m_full] board = esp01_1m @@ -226,7 +224,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA [env:esp07] board = esp07 @@ -266,7 +264,7 @@ build_flags = ${common.build_flags_esp8266} -D LEDPIN=1 -D WLED_DISABLE_INFRARED board = esp32dev platform = espressif32@3.2 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 lib_ignore = ESPAsyncTCP ESPAsyncUDP @@ -285,7 +283,7 @@ board = esp32-poe platform = espressif32@3.2 upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 lib_ignore = ESPAsyncTCP ESPAsyncUDP @@ -296,7 +294,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS +build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA [env:esp8285_4CH_H801] board = esp8285 @@ -304,7 +302,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS -D WLED_USE_H801 +build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA [env:esp8285_5CH_H801] board = esp8285 @@ -312,7 +310,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS -D WLED_USE_H801 -D WLED_ENABLE_5CH_LEDS +build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA [env:d1_mini_5CH_Shojo_PCB] board = d1_mini @@ -320,7 +318,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D WLED_USE_ANALOG_LEDS -D WLED_USE_SHOJO_PCB -D WLED_ENABLE_5CH_LEDS +build_flags = ${common.build_flags_esp8266} -D WLED_USE_SHOJO_PCB # ------------------------------------------------------------------------------ # DEVELOPMENT BOARDS @@ -361,11 +359,11 @@ build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 [env:soundReactive_esp32dev] board = esp32dev platform = espressif32@3.2 -upload_speed = 921600 +upload_speed = 460800 ;upload_port = /dev/cu.SLAB_USBtoUART ;monitor_port = /dev/cu.SLAB_USBtoUART build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_MQTT +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D WLED_DISABLE_MQTT lib_ignore = ESPAsyncTCP ESPAsyncUDP @@ -375,7 +373,7 @@ board = esp32-poe platform = espressif32@3.2 upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 lib_ignore = ESPAsyncTCP ESPAsyncUDP @@ -507,33 +505,10 @@ build_flags = ${common.build_flags_esp32} ${common.debug_flags} ${common.build_f # ------------------------------------------------------------------------------ # codm pixel controller board configurations +# codm-controller-0.6 can also be used for the TYWE3S controller # ------------------------------------------------------------------------------ -[env:codm-controller-0.4] -board = esp_wroom_02 -platform = ${common.platform_wled_default} -platform_packages = ${common.platform_packages} -board_build.ldscript = ${common.ldscript_2m512k} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 - -[env:codm-controller-0.4-WS2801] -board = esp_wroom_02 -platform = ${common.platform_wled_default} -platform_packages = ${common.platform_packages} -board_build.ldscript = ${common.ldscript_2m512k} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D USE_WS2801 -D CLKPIN=13 -D DATAPIN=3 - -[env:codm-controller-0.4-APA102] -board = esp_wroom_02 -platform = ${common.platform_wled_default} -platform_packages = ${common.platform_packages} -board_build.ldscript = ${common.ldscript_2m512k} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D USE_APA102 -D CLKPIN=13 -D DATAPIN=3 - -[env:codm-controller-0.5] +[env:codm-controller-0.6] board = esp_wroom_02 platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} @@ -541,18 +516,10 @@ board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -[env:codm-controller-0.5-WS2801] +[env:codm-controller-0.6-rev2] board = esp_wroom_02 platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} -board_build.ldscript = ${common.ldscript_2m512k} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D USE_WS2801 #-D CLKPIN=0 -D DATAPIN=2 - -[env:codm-controller-0.5-APA102] -board = esp_wroom_02 -platform = ${common.platform_wled_default} -platform_packages = ${common.platform_packages} -board_build.ldscript = ${common.ldscript_2m512k} +board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -D USE_APA102 #-D CLKPIN=0 -D DATAPIN=2 +build_flags = ${common.build_flags_esp8266} diff --git a/platformio_override.ini.sample b/platformio_override.ini.sample index c9dab54872..7df8ce8d59 100644 --- a/platformio_override.ini.sample +++ b/platformio_override.ini.sample @@ -39,12 +39,8 @@ build_flags = ${common.build_flags_esp8266} ; PIN defines for 2 wire LEDs -D CLKPIN=0 -D DATAPIN=2 -; to drive analog LED strips (aka 5050), uncomment the following -; PWM pins 5,12,13,15 are used with Magic Home LED Controller (default) - -D WLED_USE_ANALOG_LEDS -; for the H801 controller (PINs 15,13,12,14 (W2 = 04)) uncomment this -; -D WLED_USE_H801 -; for the BW-LT11 controller (PINs 12,4,14,5 ) uncomment this -; -D WLED_USE_BWLT11 -; and to enable channel 5 for RGBW-CT led strips this -; -D WLED_USE_5CH_LEDS +; to drive analog LED strips (aka 5050) hardware configuration is no longer necessary +; configure the settings in the UI as follows (hard): +; for the Magic Home LED Controller use PWM pins 5,12,13,15 +; for the H801 controller use PINs 15,13,12,14 (W2 = 04) +; for the BW-LT11 controller use PINs 12,4,14,5 diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000000..7c715125fc --- /dev/null +++ b/requirements.in @@ -0,0 +1 @@ +platformio diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..6fc16f0faa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,54 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile +# +aiofiles==0.6.0 + # via platformio +ajsonrpc==1.1.0 + # via platformio +bottle==0.12.19 + # via platformio +certifi==2020.12.5 + # via requests +chardet==4.0.0 + # via requests +click==7.1.2 + # via + # platformio + # uvicorn +colorama==0.4.4 + # via platformio +h11==0.12.0 + # via + # uvicorn + # wsproto +idna==2.10 + # via requests +ifaddr==0.1.7 + # via zeroconf +marshmallow==3.11.1 + # via platformio +platformio==5.1.1 + # via -r requirements.in +pyelftools==0.27 + # via platformio +pyserial==3.5 + # via platformio +requests==2.25.1 + # via platformio +semantic-version==2.8.5 + # via platformio +starlette==0.14.2 + # via platformio +tabulate==0.8.9 + # via platformio +urllib3==1.26.4 + # via requests +uvicorn==0.13.4 + # via platformio +wsproto==1.0.0 + # via platformio +zeroconf==0.28.8 + # via platformio diff --git a/tools/WLED_ESP32_8MB.csv b/tools/WLED_ESP32_8MB.csv new file mode 100644 index 0000000000..5e930b89a2 --- /dev/null +++ b/tools/WLED_ESP32_8MB.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x200000, +app1, app, ota_1, 0x210000,0x200000, +spiffs, data, spiffs, 0x410000,0x3F0000, \ No newline at end of file diff --git a/tools/cdata.js b/tools/cdata.js index 956e446c09..64215e495c 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -121,7 +121,7 @@ function filter(str, type) { collapseWhitespace: true, maxLineLength: 80, minifyCSS: true, - minifyJS: true, + minifyJS: true, continueOnParseError: false, removeComments: true, }); @@ -131,7 +131,7 @@ function filter(str, type) { conservativeCollapse: true, maxLineLength: 80, minifyCSS: true, - minifyJS: true, + minifyJS: true, continueOnParseError: false, removeComments: true, }); @@ -349,6 +349,22 @@ const char PAGE_settings_dmx[] PROGMEM = R"=====()====="; "function GetV() {var d=document;\n" ), }, + { + file: "settings_um.htm", + name: "PAGE_settings_um", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => + str + .replace(/\/gms, "") + .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") + .replace( + /function GetV().*\<\/script\>/gms, + "function GetV() {var d=document;\n" + ), + } ], "wled00/html_settings.h" ); diff --git a/tools/fps_test.htm b/tools/fps_test.htm new file mode 100644 index 0000000000..5858e8ad54 --- /dev/null +++ b/tools/fps_test.htm @@ -0,0 +1,232 @@ + + + + WLED frame rate test tool + + + + +

Starship monitoring dashboard

+ (or rather just a WLED frame rate tester lol)

+ IP:
+ Time per effect: s
+ Effects to test: + + + + +
+ Extra JSON:
+ +
+ LEDs: -, Seg: -, Bri: -
+ FPS min: -, max: -, avg: -

+
+

+ + + + + \ No newline at end of file diff --git a/wled00/data/jsontest.htm b/tools/json_test.htm similarity index 100% rename from wled00/data/jsontest.htm rename to tools/json_test.htm diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 9717589da3..cd43e7ec3d 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -9,25 +9,31 @@ */ #pragma once #include "wled.h" -#include "Animated_Staircase_config.h" -#define USERMOD_ID_ANIMATED_STAIRCASE 1011 - -/* Initial configuration (available in API and stored in flash) */ -bool enabled = true; // Enable this usermod -unsigned long segment_delay_ms = 150; // Time between switching each segment -unsigned long on_time_ms = 5 * 1000; // The time for the light to stay on -#ifndef TOP_PIR_PIN -unsigned int topMaxTimeUs = 1749; // default echo timout, top -#endif -#ifndef BOTTOM_PIR_PIN -unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom -#endif -// Time between checking of the sensors -const int scanDelay = 50; +#define USERMOD_ID_ANIMATED_STAIRCASE 1011 class Animated_Staircase : public Usermod { private: + + /* configuration (available in API and stored in flash) */ + bool enabled = false; // Enable this usermod + unsigned long segment_delay_ms = 150; // Time between switching each segment + unsigned long on_time_ms = 5 * 1000; // The time for the light to stay on + int8_t topPIRorTriggerPin = -1; // disabled + int8_t bottomPIRorTriggerPin = -1; // disabled + int8_t topEchoPin = -1; // disabled + int8_t bottomEchoPin = -1; // disabled + bool useUSSensorTop = false; // using PIR or UltraSound sensor? + bool useUSSensorBottom = false; // using PIR or UltraSound sensor? + unsigned int topMaxTimeUs = 1749; // default echo timout, top + unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom + + /* runtime variables */ + bool initDone = false; + + // Time between checking of the sensors + const unsigned int scanDelay = 50; + // Lights on or off. // Flipping this will start a transition. bool on = false; @@ -63,8 +69,6 @@ class Animated_Staircase : public Usermod { byte maxSegmentId = 1; byte mainSegmentId = 0; - bool saveState = false; - // These values are used by the API to read the // last sensor state, or trigger a sensor // through the API @@ -73,9 +77,24 @@ class Animated_Staircase : public Usermod { bool bottomSensorRead = false; bool bottomSensorWrite = false; + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _segmentDelay[]; + static const char _onTime[]; + static const char _useTopUltrasoundSensor[]; + static const char _topPIRorTrigger_pin[]; + static const char _topEcho_pin[]; + static const char _useBottomUltrasoundSensor[]; + static const char _bottomPIRorTrigger_pin[]; + static const char _bottomEcho_pin[]; + static const char _topEchoTime[]; + static const char _bottomEchoTime[]; + static const char _[]; + void updateSegments() { - mainSegmentId = strip.getMainSegmentId(); - WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId); +// mainSegmentId = strip.getMainSegmentId(); +// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId); WS2812FX::Segment* segments = strip.getSegments(); for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) { if (!segments->isActive()) { @@ -134,17 +153,15 @@ class Animated_Staircase : public Usermod { if ((millis() - lastScanTime) > scanDelay) { lastScanTime = millis(); -#ifdef BOTTOM_PIR_PIN - bottomSensorRead = bottomSensorWrite || (digitalRead(BOTTOM_PIR_PIN) == HIGH); -#else - bottomSensorRead = bottomSensorWrite || ultrasoundRead(BOTTOM_TRIGGER_PIN, BOTTOM_ECHO_PIN, bottomMaxTimeUs); -#endif + if (!useUSSensorBottom) + bottomSensorRead = bottomSensorWrite || (digitalRead(bottomPIRorTriggerPin) == HIGH); + else + bottomSensorRead = bottomSensorWrite || ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxTimeUs); -#ifdef TOP_PIR_PIN - topSensorRead = topSensorWrite || (digitalRead(TOP_PIR_PIN) == HIGH); -#else - topSensorRead = topSensorWrite || ultrasoundRead(TOP_TRIGGER_PIN, TOP_ECHO_PIN, topMaxTimeUs); -#endif + if (!useUSSensorTop) + topSensorRead = topSensorWrite || (digitalRead(topPIRorTriggerPin) == HIGH); + else + topSensorRead = topSensorWrite || ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxTimeUs); // Values read, reset the flags for next API call topSensorWrite = false; @@ -160,9 +177,9 @@ class Animated_Staircase : public Usermod { swipe = bottomSensorRead; if (swipe) { - Serial.println("ON -> Swipe up."); + DEBUG_PRINTLN(F("ON -> Swipe up.")); } else { - Serial.println("ON -> Swipe down."); + DEBUG_PRINTLN(F("ON -> Swipe down.")); } if (onIndex == offIndex) { @@ -181,15 +198,16 @@ class Animated_Staircase : public Usermod { } void autoPowerOff() { + // TODO: add logic to wait until PIR sensor deactivates if (on && ((millis() - lastSwitchTime) > on_time_ms)) { // Swipe OFF in the direction of the last sensor detection swipe = lastSensor; on = false; if (swipe) { - Serial.println("OFF -> Swipe up."); + DEBUG_PRINTLN(F("OFF -> Swipe up.")); } else { - Serial.println("OFF -> Swipe down."); + DEBUG_PRINTLN(F("OFF -> Swipe down.")); } } } @@ -198,8 +216,8 @@ class Animated_Staircase : public Usermod { if ((millis() - lastTime) > segment_delay_ms) { lastTime = millis(); - byte oldOnIndex = onIndex; - byte oldOffIndex = offIndex; +// byte oldOnIndex = onIndex; +// byte oldOffIndex = offIndex; if (on) { // Turn on all segments @@ -217,103 +235,44 @@ class Animated_Staircase : public Usermod { } } - void writeSettingsToJson(JsonObject& root) { - JsonObject staircase = root["staircase"]; - if (staircase.isNull()) { - staircase = root.createNestedObject("staircase"); - } - staircase["enabled"] = enabled; - staircase["segment-delay-ms"] = segment_delay_ms; - staircase["on-time-s"] = on_time_ms / 1000; - -#ifdef TOP_TRIGGER_PIN - staircase["top-echo-us"] = topMaxTimeUs; -#endif -#ifdef BOTTOM_TRIGGER_PIN - staircase["bottom-echo-us"] = bottomMaxTimeUs; -#endif - } - - void writeSensorsToJson(JsonObject& root) { - JsonObject staircase = root["staircase"]; - if (staircase.isNull()) { - staircase = root.createNestedObject("staircase"); - } - staircase["top-sensor"] = topSensorRead; - staircase["bottom-sensor"] = bottomSensorRead; - } - - bool readSettingsFromJson(JsonObject& root) { - JsonObject staircase = root["staircase"]; - bool changed = false; - - bool shouldEnable = staircase["enabled"] | enabled; - if (shouldEnable != enabled) { - enable(shouldEnable); - changed = true; - } - - unsigned long c_segment_delay_ms = staircase["segment-delay-ms"] | segment_delay_ms; - if (c_segment_delay_ms != segment_delay_ms) { - segment_delay_ms = c_segment_delay_ms; - changed = true; - } - - unsigned long c_on_time_ms = (staircase["on-time-s"] | (on_time_ms / 1000)) * 1000; - if (c_on_time_ms != on_time_ms) { - on_time_ms = c_on_time_ms; - changed = true; - } - -#ifdef TOP_TRIGGER_PIN - unsigned int c_topMaxTimeUs = staircase["top-echo-us"] | topMaxTimeUs; - if (c_topMaxTimeUs != topMaxTimeUs) { - topMaxTimeUs = c_topMaxTimeUs; - changed = true; - } -#endif -#ifdef BOTTOM_TRIGGER_PIN - unsigned int c_bottomMaxTimeUs = staircase["bottom-echo-us"] | bottomMaxTimeUs; - if (c_bottomMaxTimeUs != bottomMaxTimeUs) { - bottomMaxTimeUs = c_bottomMaxTimeUs; - changed = true; - } -#endif - - return changed; + // send sesnor values to JSON API + void writeSensorsToJson(JsonObject& staircase) { + staircase[F("top-sensor")] = topSensorRead; + staircase[F("bottom-sensor")] = bottomSensorRead; } - void readSensorsFromJson(JsonObject& root) { - JsonObject staircase = root["staircase"]; - bottomSensorWrite = bottomSensorRead || (staircase["bottom-sensor"].as()); - topSensorWrite = topSensorRead || (staircase["top-sensor"].as()); + // allow overrides from JSON API + void readSensorsFromJson(JsonObject& staircase) { + bottomSensorWrite = bottomSensorRead || (staircase[F("bottom-sensor")].as()); + topSensorWrite = topSensorRead || (staircase[F("top-sensor")].as()); } void enable(bool enable) { if (enable) { - Serial.println("Animated Staircase enabled."); - Serial.print("Delay between steps: "); - Serial.print(segment_delay_ms, DEC); - Serial.print(" milliseconds.\nStairs switch off after: "); - Serial.print(on_time_ms / 1000, DEC); - Serial.println(" seconds."); - -#ifdef BOTTOM_PIR_PIN - pinMode(BOTTOM_PIR_PIN, INPUT); -#else - pinMode(BOTTOM_TRIGGER_PIN, OUTPUT); - pinMode(BOTTOM_ECHO_PIN, INPUT); -#endif - -#ifdef TOP_PIR_PIN - pinMode(TOP_PIR_PIN, INPUT); -#else - pinMode(TOP_TRIGGER_PIN, OUTPUT); - pinMode(TOP_ECHO_PIN, INPUT); -#endif + DEBUG_PRINTLN(F("Animated Staircase enabled.")); + DEBUG_PRINT(F("Delay between steps: ")); + DEBUG_PRINT(segment_delay_ms); + DEBUG_PRINT(F(" milliseconds.\nStairs switch off after: ")); + DEBUG_PRINT(on_time_ms / 1000); + DEBUG_PRINTLN(F(" seconds.")); + + // TODO: attach interrupts + if (!useUSSensorBottom) + pinMode(bottomPIRorTriggerPin, INPUT); + else { + pinMode(bottomPIRorTriggerPin, OUTPUT); + pinMode(bottomEchoPin, INPUT); + } + + if (!useUSSensorTop) + pinMode(topPIRorTriggerPin, INPUT); + else { + pinMode(topPIRorTriggerPin, OUTPUT); + pinMode(topEchoPin, INPUT); + } } else { // Restore segment options - WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId); +// WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId); WS2812FX::Segment* segments = strip.getSegments(); for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) { if (!segments->isActive()) { @@ -323,47 +282,57 @@ class Animated_Staircase : public Usermod { segments->setOption(SEG_OPTION_ON, 1, 1); } colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE); - Serial.println("Animated Staircase disabled."); + DEBUG_PRINTLN(F("Animated Staircase disabled.")); } enabled = enable; } public: - void setup() { enable(enabled); } - - void loop() { - // Write changed settings from to flash (see readFromJsonState()) - if (saveState) { - serializeConfig(); - saveState = false; + void setup() { + // allocate pins + if (topPIRorTriggerPin >= 0) { + if (!pinManager.allocatePin(topPIRorTriggerPin,useUSSensorTop)) + topPIRorTriggerPin = -1; } - - if (!enabled) { - return; + if (topEchoPin >= 0) { + if (!pinManager.allocatePin(topEchoPin,false)) + topEchoPin = -1; + } + if (bottomPIRorTriggerPin >= 0) { + if (!pinManager.allocatePin(bottomPIRorTriggerPin,useUSSensorBottom)) + bottomPIRorTriggerPin = -1; } + if (bottomEchoPin >= 0) { + if (!pinManager.allocatePin(bottomPIRorTriggerPin,false)) + bottomEchoPin = -1; + } + // TODO: attach interrupts in enable() + + // validate pins + if ( topPIRorTriggerPin < 0 || bottomPIRorTriggerPin < 0 || + (useUSSensorTop && topEchoPin < 0) || (useUSSensorBottom && bottomEchoPin < 0) ) + enabled = false; + + enable(enabled); + initDone = true; + } + void loop() { + if (!enabled) return; checkSensors(); autoPowerOff(); updateSwipe(); - } uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; } - /* - * Shows configuration settings to the json API. This object looks like: - * - * "staircase" : { - * "enabled" : true - * "segment-delay-ms" : 150, - * "on-time-s" : 5 - * } - * - */ void addToJsonState(JsonObject& root) { - writeSettingsToJson(root); - writeSensorsToJson(root); - Serial.println("Staircase config exposed in API."); + JsonObject staircase = root[FPSTR(_name)]; + if (staircase.isNull()) { + staircase = root.createNestedObject(FPSTR(_name)); + } + writeSensorsToJson(staircase); + DEBUG_PRINTLN(F("Staircase sensor state exposed in API.")); } /* @@ -371,27 +340,107 @@ class Animated_Staircase : public Usermod { * See void addToJsonState(JsonObject& root) */ void readFromJsonState(JsonObject& root) { - // The call to serializeConfig() must be done in the main loop, - // so we set a flag to signal the main loop to save state. - saveState = readSettingsFromJson(root); - readSensorsFromJson(root); - Serial.println("Staircase config read from API."); + if (!initDone) return; // prevent crash on boot applyPreset() + JsonObject staircase = root[FPSTR(_name)]; + if (!staircase.isNull()) { + if (staircase[FPSTR(_enabled)].is()) { + enabled = staircase[FPSTR(_enabled)].as(); + } else { + String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on + enabled = (bool)(str!="off"); // off is guaranteed to be present + } + readSensorsFromJson(root); + DEBUG_PRINTLN(F("Staircase sensor state read from API.")); + } } /* * Writes the configuration to internal flash memory. */ void addToConfig(JsonObject& root) { - writeSettingsToJson(root); - Serial.println("Staircase config saved."); + JsonObject staircase = root[FPSTR(_name)]; + if (staircase.isNull()) { + staircase = root.createNestedObject(FPSTR(_name)); + } + staircase[FPSTR(_enabled)] = enabled; + staircase[FPSTR(_segmentDelay)] = segment_delay_ms; + staircase[FPSTR(_onTime)] = on_time_ms / 1000; + staircase[FPSTR(_useTopUltrasoundSensor)] = useUSSensorTop; + staircase[FPSTR(_topPIRorTrigger_pin)] = topPIRorTriggerPin; + staircase[FPSTR(_topEcho_pin)] = useUSSensorTop ? topEchoPin : -1; + staircase[FPSTR(_useBottomUltrasoundSensor)] = useUSSensorBottom; + staircase[FPSTR(_bottomPIRorTrigger_pin)] = bottomPIRorTriggerPin; + staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1; + staircase[FPSTR(_topEchoTime)] = topMaxTimeUs; + staircase[FPSTR(_bottomEchoTime)] = bottomMaxTimeUs; + DEBUG_PRINTLN(F("Staircase config saved.")); } /* * Reads the configuration to internal flash memory before setup() is called. */ void readFromConfig(JsonObject& root) { - readSettingsFromJson(root); - Serial.println("Staircase config loaded."); + bool oldUseUSSensorTop = useUSSensorTop; + bool oldUseUSSensorBottom = useUSSensorBottom; + int8_t oldTopAPin = topPIRorTriggerPin; + int8_t oldTopBPin = topEchoPin; + int8_t oldBottomAPin = bottomPIRorTriggerPin; + int8_t oldBottomBPin = bottomEchoPin; + + JsonObject staircase = root[FPSTR(_name)]; + if (!staircase.isNull()) { + if (staircase[FPSTR(_enabled)].is()) { + enabled = staircase[FPSTR(_enabled)].as(); + } else { + String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on + enabled = (bool)(str!="off"); // off is guaranteed to be present + } + segment_delay_ms = min(10000,max(10,staircase[FPSTR(_segmentDelay)].as())); // max delay 10s + on_time_ms = min(900,max(10,staircase[FPSTR(_onTime)].as())) * 1000; // min 10s, max 15min + + if (staircase[FPSTR(_useTopUltrasoundSensor)].is()) { + useUSSensorTop = staircase[FPSTR(_useTopUltrasoundSensor)].as(); + } else { + String str = staircase[FPSTR(_useTopUltrasoundSensor)]; // checkbox -> off or on + useUSSensorTop = (bool)(str!="off"); // off is guaranteed to be present + } + + topPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_topPIRorTrigger_pin)].as())); + topEchoPin = min(39,max(-1,staircase[FPSTR(_topEcho_pin)].as())); + + if (staircase[FPSTR(_useBottomUltrasoundSensor)].is()) { + useUSSensorBottom = staircase[FPSTR(_useBottomUltrasoundSensor)].as(); + } else { + String str = staircase[FPSTR(_useBottomUltrasoundSensor)]; // checkbox -> off or on + useUSSensorBottom = (bool)(str!="off"); // off is guaranteed to be present + } + bottomPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_bottomPIRorTrigger_pin)].as())); + bottomEchoPin = min(39,max(-1,staircase[FPSTR(_bottomEcho_pin)].as())); + topMaxTimeUs = min(18000,max(300,staircase[FPSTR(_topEchoTime)].as())); // max distnace ~3m (a noticable lag of 18ms may be expected) + bottomMaxTimeUs = min(18000,max(300,staircase[FPSTR(_bottomEchoTime)].as())); // max distance ~3m (a noticable lag of 18ms may be expected) + DEBUG_PRINTLN(F("Staircase config (re)loaded.")); + } else { + DEBUG_PRINTLN(F("No config found. (Using defaults.)")); + } + if (!initDone) { + // first run: reading from cfg.json + } else { + // changing paramters from settings page + bool changed = false; + if ((oldUseUSSensorTop != useUSSensorTop) || + (oldUseUSSensorBottom != useUSSensorBottom) || + (oldTopAPin != topPIRorTriggerPin) || + (oldTopBPin != topEchoPin) || + (oldBottomAPin != bottomPIRorTriggerPin) || + (oldBottomBPin != bottomEchoPin)) { + changed = true; + pinManager.deallocatePin(oldTopAPin); + pinManager.deallocatePin(oldTopBPin); + pinManager.deallocatePin(oldBottomAPin); + pinManager.deallocatePin(oldBottomBPin); + } + if (changed) setup(); + } } /* @@ -405,23 +454,33 @@ class Animated_Staircase : public Usermod { } if (enabled) { - JsonArray usermodEnabled = - staircase.createNestedArray("Staircase enabled"); // name + JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name usermodEnabled.add("yes"); // value - JsonArray segmentDelay = - staircase.createNestedArray("Delay between stairs"); // name + JsonArray segmentDelay = staircase.createNestedArray(F("Delay between stairs")); // name segmentDelay.add(segment_delay_ms); // value - segmentDelay.add(" milliseconds"); // unit + segmentDelay.add("ms"); // unit - JsonArray onTime = - staircase.createNestedArray("Power-off stairs after"); // name + JsonArray onTime = staircase.createNestedArray(F("Power-off stairs after")); // name onTime.add(on_time_ms / 1000); // value - onTime.add(" seconds"); // unit + onTime.add("s"); // unit } else { - JsonArray usermodEnabled = - staircase.createNestedArray("Staircase enabled"); // name + JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase enabled")); // name usermodEnabled.add("no"); // value } } -}; \ No newline at end of file +}; + +// strings to reduce flash memory usage (used more than twice) +const char Animated_Staircase::_name[] PROGMEM = "staircase"; +const char Animated_Staircase::_enabled[] PROGMEM = "enabled"; +const char Animated_Staircase::_segmentDelay[] PROGMEM = "segment-delay-ms"; +const char Animated_Staircase::_onTime[] PROGMEM = "on-time-s"; +const char Animated_Staircase::_useTopUltrasoundSensor[] PROGMEM = "useTopUltrasoundSensor"; +const char Animated_Staircase::_topPIRorTrigger_pin[] PROGMEM = "topPIRorTrigger_pin"; +const char Animated_Staircase::_topEcho_pin[] PROGMEM = "topEcho_pin"; +const char Animated_Staircase::_useBottomUltrasoundSensor[] PROGMEM = "useBottomUltrasoundSensor"; +const char Animated_Staircase::_bottomPIRorTrigger_pin[] PROGMEM = "bottomPIRorTrigger_pin"; +const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin"; +const char Animated_Staircase::_topEchoTime[] PROGMEM = "top-echo-us"; +const char Animated_Staircase::_bottomEchoTime[] PROGMEM = "bottom-echo-us"; diff --git a/usermods/Animated_Staircase/Animated_Staircase_config.h b/usermods/Animated_Staircase/Animated_Staircase_config.h deleted file mode 100644 index 9805074771..0000000000 --- a/usermods/Animated_Staircase/Animated_Staircase_config.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Animated_Staircase compiletime confguration. - * - * Please see README.md on how to change this file. - */ - -// Please change the pin numbering below to match your board. -#define TOP_PIR_PIN D5 -#define BOTTOM_PIR_PIN D6 - -// Or uncumment and a pir and use an ultrasound HC-SR04 sensor, -// see README.md for details -#ifndef TOP_PIR_PIN -#define TOP_TRIGGER_PIN D2 -#define TOP_ECHO_PIN D3 -#endif - -#ifndef BOTTOM_PIR_PIN -#define BOTTOM_TRIGGER_PIN D4 -#define BOTTOM_ECHO_PIN D5 -#endif \ No newline at end of file diff --git a/usermods/Animated_Staircase/README.md b/usermods/Animated_Staircase/README.md index 6e84b5444f..fc755e00c9 100644 --- a/usermods/Animated_Staircase/README.md +++ b/usermods/Animated_Staircase/README.md @@ -20,44 +20,10 @@ Edit `usermods_list.cpp`: 2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file 3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function. -Edit `Animated_Staircase_config.h`: -1. Open `usermods/Animated_Staircase/Animated_Staircase_config.h` -2. To use PIR sensors, change these lines to match your setup: - Using D7 and D6 pin notation as used on several boards: - - ```cpp - #define TOP_PIR_PIN D7 - #define BOTTOM_PIR_PIN D6 - ``` - - Or using GPIO numbering for pins 25 and 26: - ```cpp - #define TOP_PIR_PIN 26 - #define BOTTOM_PIR_PIN 25 - ``` - - To use Ultrasonic HC-SR04 sensors instead of (one of the) PIR sensors, - uncomment one of the PIR sensor lines and adjust the pin numbers for the - connected Ultrasonic sensor. In the example below we use an Ultrasonic - sensor at the bottom of the stairs: - - ```cpp - #define TOP_PIR_PIN 32 - //#define BOTTOM_PIR_PIN D6 /* This PIR sensor is disabled */ - - #ifndef TOP_PIR_PIN - #define TOP_SIGNAL_PIN D2 - #define TOP_ECHO_PIN D3 - #endif - - #ifndef BOTTOM_PIR_PIN /* If the bottom PIR is disabled, */ - #define BOTTOM_SIGNAL_PIN 25 /* This Ultrasonic sensor is used */ - #define BOTTOM_ECHO_PIN 26 - #endif - ``` - -After these modifications, compile and upload your WLED binary to your board -and check the WLED info page to see if this usermod is enabled. +You can configure usermod using Usermods settings page. +Please enter GPIO pins for PIR sensors or ultrasonic sensor (trigger and echo). +If you use PIR sensor enter -1 for echo pin. +Maximum distance for ultrasonic sensor can be configured as a time needed for echo (see below). ## Hardware installation 1. Stick the LED strip under each step of the stairs. @@ -93,8 +59,8 @@ or remove them and put everything on one line. | segment-delay-ms | Delay (milliseconds) between switching on/off each step | 150 | | on-time-s | Time (seconds) the stairs stay lit after last detection | 5 | | bottom-echo-us | Detection range of ultrasonic sensor | 1749 | -| bottomsensor | Manually trigger a down to up animation via API | false | -| topsensor | Manually trigger an up to down animation via API | false | +| bottom-sensor | Manually trigger a down to up animation via API | false | +| top-sensor | Manually trigger an up to down animation via API | false | To read the current settings, open a browser to `http://xxx.xxx.xxx.xxx/json/state` (use your WLED @@ -108,8 +74,8 @@ The staircase settings and sensor states are inside the WLED status element: "enabled": true, "segment-delay-ms": 150, "on-time-s": 5, - "bottomsensor": false, - "topsensor": false + "bottom-sensor": false, + "tops-ensor": false }, } ``` @@ -187,7 +153,7 @@ the API. To simulate triggering the bottom sensor, use: ```bash curl -X POST -H "Content-Type: application/json" \ - -d '{"staircase":{"bottomsensor":true}}' \ + -d '{"staircase":{"bottom-sensor":true}}' \ xxx.xxx.xxx.xxx/json/state ``` @@ -195,9 +161,13 @@ Likewise, to trigger the top sensor, use: ```bash curl -X POST -H "Content-Type: application/json" \ - -d '{"staircase":{"topsensor":true}}' \ + -d '{"staircase":{"top-sensor":true}}' \ xxx.xxx.xxx.xxx/json/state ``` Have fun with this usermod.
www.rolfje.com + +## Change log +2021-04 +* Adaptation for runtime configuration. \ No newline at end of file diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index 80a31a4f06..82eb08871e 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -12,10 +12,11 @@ class UsermodBME280 : public Usermod // User-defined configuration #define Celsius // Show temperature mesaurement in Celcius. Comment out for Fahrenheit #define TemperatureDecimals 1 // Number of decimal places in published temperaure values -#define HumidityDecimals 0 // Number of decimal places in published humidity values +#define HumidityDecimals 2 // Number of decimal places in published humidity values #define PressureDecimals 2 // Number of decimal places in published pressure values #define TemperatureInterval 5 // Interval to measure temperature (and humidity, dew point if available) in seconds #define PressureInterval 300 // Interval to measure pressure in seconds +#define PublishAlways 0 // Publish values even when they have not changed // Sanity checks #if !defined(TemperatureDecimals) || TemperatureDecimals < 0 @@ -33,6 +34,9 @@ class UsermodBME280 : public Usermod #if !defined(PressureInterval) || PressureInterval < 0 #define PressureInterval TemperatureInterval #endif +#if !defined(PublishAlways) + #define PublishAlways 0 +#endif #ifdef ARDUINO_ARCH_ESP32 // ESP32 boards uint8_t SCL_PIN = 22; @@ -58,7 +62,7 @@ class UsermodBME280 : public Usermod BME280I2C bme{settings}; - uint8_t SensorType; + uint8_t sensorType; // Measurement timers long timer; @@ -66,11 +70,11 @@ class UsermodBME280 : public Usermod long lastPressureMeasure = 0; // Current sensor values - float SensorTemperature; - float SensorHumidity; - float SensorHeatIndex; - float SensorDewPoint; - float SensorPressure; + float sensorTemperature; + float sensorHumidity; + float sensorHeatIndex; + float sensorDewPoint; + float sensorPressure; // Track previous sensor values float lastTemperature; float lastHumidity; @@ -96,13 +100,13 @@ class UsermodBME280 : public Usermod bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit); - SensorTemperature = _temperature; - SensorHumidity = _humidity; - SensorPressure = _pressure; - if (SensorType == 1) + sensorTemperature = _temperature; + sensorHumidity = _humidity; + sensorPressure = _pressure; + if (sensorType == 1) { - SensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit); - SensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit); + sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit); + sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit); } } @@ -113,7 +117,7 @@ class UsermodBME280 : public Usermod if (!bme.begin()) { - SensorType = 0; + sensorType = 0; Serial.println("Could not find BME280I2C sensor!"); } else @@ -121,15 +125,15 @@ class UsermodBME280 : public Usermod switch (bme.chipModel()) { case BME280::ChipModel_BME280: - SensorType = 1; + sensorType = 1; Serial.println("Found BME280 sensor! Success."); break; case BME280::ChipModel_BMP280: - SensorType = 2; + sensorType = 2; Serial.println("Found BMP280 sensor! No Humidity available."); break; default: - SensorType = 0; + sensorType = 0; Serial.println("Found UNKNOWN sensor! Error!"); } } @@ -139,7 +143,7 @@ class UsermodBME280 : public Usermod { // BME280 sensor MQTT publishing // Check if sensor present and MQTT Connected, otherwise it will crash the MCU - if (SensorType != 0 && mqtt != nullptr) + if (sensorType != 0 && mqtt != nullptr) { // Timer to fetch new temperature, humidity and pressure data at intervals timer = millis(); @@ -148,48 +152,48 @@ class UsermodBME280 : public Usermod { lastTemperatureMeasure = timer; - UpdateBME280Data(SensorType); + UpdateBME280Data(sensorType); - float Temperature = roundf(SensorTemperature * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); - float Humidity, HeatIndex, DewPoint; + float temperature = roundf(sensorTemperature * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); + float humidity, heatIndex, dewPoint; // If temperature has changed since last measure, create string populated with device topic // from the UI and values read from sensor, then publish to broker - if (Temperature != lastTemperature) + if (temperature != lastTemperature || PublishAlways) { String topic = String(mqttDeviceTopic) + "/temperature"; - mqttTemperaturePub = mqtt->publish(topic.c_str(), 0, false, String(Temperature, TemperatureDecimals).c_str()); + mqttTemperaturePub = mqtt->publish(topic.c_str(), 0, false, String(temperature, TemperatureDecimals).c_str()); } - lastTemperature = Temperature; // Update last sensor temperature for next loop + lastTemperature = temperature; // Update last sensor temperature for next loop - if (SensorType == 1) // Only if sensor is a BME280 + if (sensorType == 1) // Only if sensor is a BME280 { - Humidity = roundf(SensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals); - HeatIndex = roundf(SensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); - DewPoint = roundf(SensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); + humidity = roundf(sensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals); + heatIndex = roundf(sensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); + dewPoint = roundf(sensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); - if (Humidity != lastHumidity) + if (humidity != lastHumidity || PublishAlways) { String topic = String(mqttDeviceTopic) + "/humidity"; - mqtt->publish(topic.c_str(), 0, false, String(Humidity, HumidityDecimals).c_str()); + mqtt->publish(topic.c_str(), 0, false, String(humidity, HumidityDecimals).c_str()); } - if (HeatIndex != lastHeatIndex) + if (heatIndex != lastHeatIndex || PublishAlways) { String topic = String(mqttDeviceTopic) + "/heat_index"; - mqtt->publish(topic.c_str(), 0, false, String(HeatIndex, TemperatureDecimals).c_str()); + mqtt->publish(topic.c_str(), 0, false, String(heatIndex, TemperatureDecimals).c_str()); } - if (DewPoint != lastDewPoint) + if (dewPoint != lastDewPoint || PublishAlways) { String topic = String(mqttDeviceTopic) + "/dew_point"; - mqtt->publish(topic.c_str(), 0, false, String(DewPoint, TemperatureDecimals).c_str()); + mqtt->publish(topic.c_str(), 0, false, String(dewPoint, TemperatureDecimals).c_str()); } - lastHumidity = Humidity; - lastHeatIndex = HeatIndex; - lastDewPoint = DewPoint; + lastHumidity = humidity; + lastHeatIndex = heatIndex; + lastDewPoint = dewPoint; } } @@ -197,15 +201,15 @@ class UsermodBME280 : public Usermod { lastPressureMeasure = timer; - float Pressure = roundf(SensorPressure * pow(10, PressureDecimals)) / pow(10, PressureDecimals); + float pressure = roundf(sensorPressure * pow(10, PressureDecimals)) / pow(10, PressureDecimals); - if (Pressure != lastPressure) + if (pressure != lastPressure || PublishAlways) { String topic = String(mqttDeviceTopic) + "/pressure"; - mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(Pressure, PressureDecimals).c_str()); + mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(pressure, PressureDecimals).c_str()); } - lastPressure = Pressure; + lastPressure = pressure; } } } diff --git a/usermods/PIR_sensor_switch/readme.md b/usermods/PIR_sensor_switch/readme.md index 3d00b50542..d6ea0fb0b8 100644 --- a/usermods/PIR_sensor_switch/readme.md +++ b/usermods/PIR_sensor_switch/readme.md @@ -10,27 +10,14 @@ The LED strip is switched [using a relay](https://github.com/Aircoookie/WLED/wik ## Webinterface The info page in the web interface shows the items below - -- the state of the sensor. By clicking on the state the sensor can be deactivated/activated. Changes persist after a reboot. -**I recommend to deactivate the sensor before an OTA update and activate it again afterwards**. - the remaining time of the off timer. - -## JSON API - -The usermod supports the following state changes: - -| JSON key | Value range | Description | -|------------|-------------|---------------------------------| -| PIRenabled | bool | Deactivdate/activate the sensor | -| PIRoffSec | 60 to 43200 | Off timer seconds | - - Changes also persist after a reboot. +**I recommend to deactivate the sensor before an OTA update and activate it again afterwards**. ## Sensor connection My setup uses an HC-SR501 sensor, a HC-SR505 should also work. -The usermod uses GPIO13 (D1 mini pin D7) for the sensor signal. +The usermod uses GPIO13 (D1 mini pin D7) by default for the sensor signal but can be changed in the Usermod settings page. [This example page](http://www.esp8266learning.com/wemos-mini-pir-sensor-example.php) describes how to connect the sensor. Use the potentiometers on the sensor to set the time-delay to the minimum and the sensitivity to about half, or slightly above. @@ -76,8 +63,6 @@ void registerUsermods() ## API to enable/disable the PIR sensor from outside. For example from another usermod. -The class provides the static method `PIRsensorSwitch* PIRsensorSwitch::GetInstance()` to get a pointer to the usermod object. - To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available. ### There are two options to get access to the usermod instance: @@ -98,12 +83,19 @@ class MyUsermod : public Usermod { //... void togglePIRSensor() { - if (PIRsensorSwitch::GetInstance() != nullptr) { - PIRsensorSwitch::GetInstance()->EnablePIRsensor(!PIRsensorSwitch::GetInstance()->PIRsensorEnabled()); + #ifdef USERMOD_PIR_SENSOR_SWITCH + PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) usermods.lookup(USERMOD_ID_PIRSWITCH); + if (PIRsensor != nullptr) { + PIRsensor->EnablePIRsensor(!PIRsensor->PIRsensorEnabled()); } + #endif } //... }; ``` Have fun - @gegu + +## Change log +2021-04 +* Adaptation for runtime configuration. \ No newline at end of file diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 421528bfe6..19fcc2a93e 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -2,6 +2,15 @@ #include "wled.h" +#ifndef PIR_SENSOR_PIN + // compatible with QuinLED-Dig-Uno + #ifdef ARDUINO_ARCH_ESP32 + #define PIR_SENSOR_PIN 23 // Q4 + #else //ESP8266 boards + #define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini) + #endif +#endif + /* * This usermod handles PIR sensor states. * The strip will be switched on and the off timer will be resetted when the sensor goes HIGH. @@ -30,24 +39,11 @@ class PIRsensorSwitch : public Usermod /** * constructor */ - PIRsensorSwitch() - { - // set static instance pointer - PIRsensorSwitchInstance(this); - } + PIRsensorSwitch() {} /** * desctructor */ - ~PIRsensorSwitch() - { - PIRsensorSwitchInstance(nullptr, true); - ; - } - - /** - * return the instance pointer of the class - */ - static PIRsensorSwitch *GetInstance() { return PIRsensorSwitchInstance(); } + ~PIRsensorSwitch() {} /** * Enable/Disable the PIR sensor @@ -60,19 +56,24 @@ class PIRsensorSwitch : public Usermod private: // PIR sensor pin - const uint8_t PIRsensorPin = 13; // D7 on D1 mini + int8_t PIRsensorPin = PIR_SENSOR_PIN; // notification mode for colorUpdated() const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE // delay before switch off after the sensor state goes LOW - uint32_t m_switchOffDelay = 600000; + uint32_t m_switchOffDelay = 600000; // 10min // off timer start time uint32_t m_offTimerStart = 0; // current PIR sensor pin state byte m_PIRsensorPinState = LOW; // PIR sensor enabled - ISR attached bool m_PIRenabled = true; - // state if serializeConfig() should be called - bool m_updateConfig = false; + // status of initialisation + bool initDone = false; + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _switchOffDelay[]; + static const char _enabled[]; /** * return or change if new PIR sensor state is available @@ -84,11 +85,6 @@ class PIRsensorSwitch : public Usermod */ static void IRAM_ATTR ISR_PIRstateChange(); - /** - * Set/get instance pointer - */ - static PIRsensorSwitch *PIRsensorSwitchInstance(PIRsensorSwitch *pInstance = nullptr, bool bRemoveInstance = false); - /** * switch strip on/off */ @@ -107,6 +103,17 @@ class PIRsensorSwitch : public Usermod } } + void publishMqtt(const char* state) + { + //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, true, state); + } + } + /** * Read and update PIR sensor state. * Initilize/reset switch off timer @@ -121,6 +128,7 @@ class PIRsensorSwitch : public Usermod { m_offTimerStart = 0; switchStrip(true); + publishMqtt("on"); } else if (bri != 0) { @@ -143,6 +151,7 @@ class PIRsensorSwitch : public Usermod if (m_PIRenabled == true) { switchStrip(false); + publishMqtt("off"); } m_offTimerStart = 0; return true; @@ -159,13 +168,20 @@ class PIRsensorSwitch : public Usermod */ void setup() { - // PIR Sensor mode INPUT_PULLUP - pinMode(PIRsensorPin, INPUT_PULLUP); - if (m_PIRenabled) - { - // assign interrupt function and set CHANGE mode - attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); + // pin retrieved from cfg.json (readFromConfig()) prior to running setup() + if (!pinManager.allocatePin(PIRsensorPin,false)) { + PIRsensorPin = -1; // allocation failed + m_PIRenabled = false; + DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed.")); + } else { + // PIR Sensor mode INPUT_PULLUP + pinMode(PIRsensorPin, INPUT_PULLUP); + if (m_PIRenabled) { + // assign interrupt function and set CHANGE mode + attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); + } } + initDone = true; } /** @@ -181,14 +197,8 @@ class PIRsensorSwitch : public Usermod */ void loop() { - if (!updatePIRsensorState()) - { + if (!updatePIRsensorState()) { handleOffTimer(); - if (m_updateConfig) - { - serializeConfig(); - m_updateConfig = false; - } } } @@ -199,69 +209,70 @@ class PIRsensorSwitch : public Usermod */ void addToJsonInfo(JsonObject &root) { - //this code adds "u":{"⏲ PIR sensor state":uiDomString} to the info object - // the value contains a button to toggle the sensor enabled/disabled JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); - - JsonArray infoArr = user.createNestedArray("⏲ PIR sensor state"); //name - String uiDomString = ""; + uiDomString += F(""); infoArr.add(uiDomString); //value - - //this code adds "u":{"⏲ switch off timer":uiDomString} to the info object - uiDomString = "⏲ switch off timer\ -after min"; - infoArr = user.createNestedArray(uiDomString); //name - - // off timer - if (m_offTimerStart > 0) +*/ + if (m_PIRenabled) { - uiDomString = ""; - unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000; - if (offSeconds >= 3600) - { - uiDomString += (offSeconds / 3600); - uiDomString += " hours "; - offSeconds %= 3600; - } - if (offSeconds >= 60) - { - uiDomString += (offSeconds / 60); - offSeconds %= 60; - } - else if (uiDomString.length() > 0) - { - uiDomString += 0; - } - if (uiDomString.length() > 0) +/* + JsonArray infoArr = user.createNestedArray(F("PIR switch-off timer after")); //name + String uiDomString = F("min"); + infoArr.add(uiDomString); +*/ + // off timer + String uiDomString = F("PIR "); + JsonArray infoArr = user.createNestedArray(uiDomString); // timer value + if (m_offTimerStart > 0) { - uiDomString += " min "; + uiDomString = ""; + unsigned int offSeconds = (m_switchOffDelay - (millis() - m_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(F("inactive")); } - uiDomString += (offSeconds); - infoArr.add(uiDomString + " sec"); - } - else - { - infoArr.add("inactive"); } } @@ -273,8 +284,8 @@ after ()))); - m_updateConfig = true; + if (root[FPSTR(_switchOffDelay)] != nullptr) { + m_switchOffDelay = (1000 * max(60UL, min(43200UL, root[FPSTR(_switchOffDelay)].as()))); + } +/* + if (root["pin"] != nullptr) { + int8_t pin = (int)root["pin"]; + // check if pin is OK + if (pin != PIRsensorPin && pin>=0 && pinManager.allocatePin(pin,false)) { + // deallocate old pin + pinManager.deallocatePin(PIRsensorPin); + // PIR Sensor mode INPUT_PULLUP + pinMode(pin, INPUT_PULLUP); + if (m_PIRenabled) + { + // remove old ISR + detachInterrupt(PIRsensorPin); + // assign interrupt function and set CHANGE mode + attachInterrupt(digitalPinToInterrupt(pin), ISR_PIRstateChange, CHANGE); + newPIRsensorState(true, true); + } + PIRsensorPin = pin; + } } - if (root["PIRenabled"] != nullptr) - { - if (root["PIRenabled"] && !m_PIRenabled) - { + if (root[FPSTR(_enabled)] != nullptr) { + if (root[FPSTR(_enabled)] && !m_PIRenabled && PIRsensorPin >= 0) { attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); newPIRsensorState(true, true); - } - else if (m_PIRenabled) - { + } else if (m_PIRenabled && PIRsensorPin >= 0) { detachInterrupt(PIRsensorPin); } - m_PIRenabled = root["PIRenabled"]; - m_updateConfig = true; + m_PIRenabled = root[FPSTR(_enabled)]; } +*/ } /** @@ -312,19 +337,72 @@ after ())); // check bounds + } + + if (top[FPSTR(_enabled)] != nullptr) { + if (top[FPSTR(_enabled)].is()) { + m_PIRenabled = top[FPSTR(_enabled)].as(); // reading from cfg.json + } else { + // change from settings page + String str = top[FPSTR(_enabled)]; // checkbox -> off or on + m_PIRenabled = (bool)(str!="off"); // off is guaranteed to be present + } + } + + if (top[FPSTR(_switchOffDelay)] != nullptr) { + m_switchOffDelay = (top[FPSTR(_switchOffDelay)].as() * 1000); + } + + if (!initDone) { + // reading config prior to setup() + DEBUG_PRINTLN(F("PIR config loaded.")); + } else { + if (oldPin != PIRsensorPin || oldEnabled != m_PIRenabled) { + if (oldEnabled) { + // remove old ISR if disabling usermod + detachInterrupt(oldPin); + } + // check if pin is OK + if (oldPin != PIRsensorPin && oldPin >= 0) { + // if we are changing pin in settings page + // deallocate old pin + pinManager.deallocatePin(oldPin); + if (pinManager.allocatePin(PIRsensorPin,false)) { + pinMode(PIRsensorPin, INPUT_PULLUP); + } else { + // allocation failed + PIRsensorPin = -1; + m_PIRenabled = false; + } + } + if (m_PIRenabled) { + attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); + newPIRsensorState(true, true); + } + DEBUG_PRINTLN(F("PIR config (re)loaded.")); + } + } } /** @@ -355,12 +433,7 @@ void IRAM_ATTR PIRsensorSwitch::ISR_PIRstateChange() newPIRsensorState(true, true); } -PIRsensorSwitch *PIRsensorSwitch::PIRsensorSwitchInstance(PIRsensorSwitch *pInstance, bool bRemoveInstance) -{ - static PIRsensorSwitch *s_pPIRsensorSwitch = nullptr; - if (pInstance != nullptr || bRemoveInstance) - { - s_pPIRsensorSwitch = pInstance; - } - return s_pPIRsensorSwitch; -} +// strings to reduce flash memory usage (used more than twice) +const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch"; +const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled"; +const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec"; diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md index 5b7f5b9585..3cfc3a4c67 100644 --- a/usermods/Temperature/readme.md +++ b/usermods/Temperature/readme.md @@ -14,10 +14,10 @@ Copy the example `platformio_override.ini` to the root directory. This file sho ### Define Your 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 celsious, 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 +All parameters can be configured at runtime using Usermods settings page. + ## Project link * [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link @@ -41,10 +41,7 @@ default_envs = d1_mini ... lib_deps = ... - #For use SSD1306 OLED display uncomment following - U8g2@~2.27.3 - #For Dallas sensor uncomment following 2 lines - DallasTemperature@~3.8.0 + #For Dallas sensor uncomment following line OneWire@~2.3.5 ... ``` @@ -56,3 +53,5 @@ lib_deps = * Do not report low temperatures that indicate an error to mqtt * Disable plugin if temperature sensor not detected * Report the number of seconds until the first read in the info screen instead of sensor error +2021-04 +* Adaptation for runtime configuration. \ No newline at end of file diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 1ce0322e8c..8c639ae569 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -2,15 +2,16 @@ #include "wled.h" -#include //DS18B20 +//#include //DS18B20 +#include "OneWire.h" -//Pin defaults for QuinLed Dig-Uno +//Pin defaults for QuinLed Dig-Uno if not overriden #ifndef TEMPERATURE_PIN -#ifdef ARDUINO_ARCH_ESP32 -#define TEMPERATURE_PIN 18 -#else //ESP8266 boards -#define TEMPERATURE_PIN 14 -#endif + #ifdef ARDUINO_ARCH_ESP32 + #define TEMPERATURE_PIN 18 + #else //ESP8266 boards + #define TEMPERATURE_PIN 14 + #endif #endif // the frequency to check temperature, 1 minute @@ -20,19 +21,20 @@ // how many seconds after boot to take first measurement, 20 seconds #ifndef USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT -#define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000 +#define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000 #endif -OneWire oneWire(TEMPERATURE_PIN); -DallasTemperature sensor(&oneWire); - class UsermodTemperature : public Usermod { + private: - // The device's unique 64-bit serial code stored in on-board ROM. - // Reading directly from the sensor device address is faster than - // reading from index. When reading by index, DallasTemperature - // must first look up the device address at the specified index. - DeviceAddress sensorDeviceAddress; + + bool initDone = false; + OneWire *oneWire; + // GPIO pin used for sensor (with a default compile-time fallback) + int8_t temperaturePin = TEMPERATURE_PIN; + // measurement unit (true==°C, false==°F) + bool degC = true; + unsigned long readingInterval = USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL; // set last reading as "40 sec before boot", so first reading is taken after 20 sec unsigned long lastMeasurement = UINT32_MAX - (USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT); // last time requestTemperatures was called @@ -42,85 +44,120 @@ class UsermodTemperature : public Usermod { float temperature = -100; // default to -100, DS18B20 only goes down to -50C // indicates requestTemperatures has been called but the sensor measurement is not complete bool waitingForConversion = false; - // flag to indicate we have finished the first getTemperature call + // flag to indicate we have finished the first readTemperature call // allows this library to report to the user how long until the first // measurement - bool getTemperatureComplete = false; + bool readTemperatureComplete = false; // flag set at startup if DS18B20 sensor not found, avoids trying to keep getting // temperature if flashed to a board without a sensor attached bool disabled = false; - void requestTemperatures() { - // there is requestTemperaturesByAddress however it - // appears to do more work, - // TODO: measure exection time difference - sensor.requestTemperatures(); - lastTemperaturesRequest = millis(); - waitingForConversion = true; + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _readInterval[]; + + //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 + int16_t readDallas() { + byte i; + byte data[2]; + int16_t result; // raw data from sensor + oneWire->reset(); + oneWire->write(0xCC); // skip ROM + oneWire->write(0xBE); // read (temperature) from EEPROM + for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature + for (i=2; i < 8; i++) oneWire->read(); // read unused bytes + result = (data[1]<<8) | data[0]; + result >>= 4; // 9-bit precision accurate to 1°C (/16) + if (data[1]&0x80) result |= 0xF000; // fix negative value + //if (data[0]&0x08) ++result; + oneWire->reset(); + oneWire->write(0xCC); // skip ROM + oneWire->write(0x44,0); // request new temperature reading (without parasite power) + return result; } - void getTemperature() { - if (strip.isUpdating()) return; - #ifdef USERMOD_DALLASTEMPERATURE_CELSIUS - temperature = sensor.getTempC(sensorDeviceAddress); - #else - temperature = sensor.getTempF(sensorDeviceAddress); - #endif + void requestTemperatures() { + readDallas(); + lastTemperaturesRequest = millis(); + waitingForConversion = true; + DEBUG_PRINTLN(F("Requested temperature.")); + } + void readTemperature() { + temperature = readDallas(); lastMeasurement = millis(); waitingForConversion = false; - getTemperatureComplete = true; + readTemperatureComplete = true; + DEBUG_PRINTF("Read temperature %2.1f.\n", temperature); } - public: + 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(); + while (oneWire->search(deviceAddress)) { + 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.")); + return true; + } + } + } + return false; + } + public: void setup() { - sensor.begin(); - - // get the unique 64-bit serial code stored in on-board ROM - // if getAddress returns false, the sensor was not found - disabled = !sensor.getAddress(sensorDeviceAddress, 0); - - if (!disabled) { - DEBUG_PRINTLN(F("Dallas Temperature found")); - // set the resolution for this specific device - sensor.setResolution(sensorDeviceAddress, 9, true); - // do not block waiting for reading - sensor.setWaitForConversion(false); - // allocate pin & prevent other use - if (!pinManager.allocatePin(TEMPERATURE_PIN,false)) - disabled = true; + int retries = 10; + // pin retrieved from cfg.json (readFromConfig()) prior to running setup() + if (!pinManager.allocatePin(temperaturePin,false)) { + temperaturePin = -1; // allocation failed + disabled = true; + DEBUG_PRINTLN(F("Temperature pin allocation failed.")); } else { - DEBUG_PRINTLN(F("Dallas Temperature not found")); + if (!disabled) { + // config says we are enabled + oneWire = new OneWire(temperaturePin); + if (!oneWire->reset()) + disabled = true; // resetting 1-Wire bus yielded an error + else + while ((disabled=!findSensor()) && retries--) delay(25); // try to find sensor + } } + initDone = true; } void loop() { if (disabled || strip.isUpdating()) return; - + 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 < USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL) return; + if (now - lastMeasurement < readingInterval) return; - // we are due for a measurement, if we are not already waiting + // 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) - { + if (!waitingForConversion) { requestTemperatures(); return; } // we were waiting for a conversion to complete, have we waited log enough? - if (now - lastTemperaturesRequest >= 94 /* 93.75ms per the datasheet */) - { - getTemperature(); - + if (now - lastTemperaturesRequest >= 100 /* 93.75ms per the datasheet but can be up to 750ms */) { + readTemperature(); + if (WLED_MQTT_CONNECTED) { - char subuf[38]; + char subuf[64]; strcpy(subuf, mqttDeviceTopic); if (-100 <= temperature) { // dont publish super low temperature as the graph will get messed up @@ -128,6 +165,8 @@ class UsermodTemperature : public Usermod { // reading the sensor strcat_P(subuf, PSTR("/temperature")); mqtt->publish(subuf, 0, true, String(temperature).c_str()); + strcat_P(subuf, PSTR("_f")); + mqtt->publish(subuf, 0, true, String((float)temperature * 1.8f + 32).c_str()); } else { // publish something else to indicate status? } @@ -135,16 +174,32 @@ class UsermodTemperature : public Usermod { } } + /* + * API calls te enable data exchange between WLED modules + */ + inline float getTemperatureC() { + return (float)temperature; + } + inline float getTemperatureF() { + return (float)temperature * 1.8f + 32; + } + + /* + * 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 (disabled) return; - JsonObject user = root[F("u")]; - if (user.isNull()) user = root.createNestedObject(F("u")); + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); - JsonArray temp = user.createNestedArray(F("Temperature")); + JsonArray temp = user.createNestedArray(FPSTR(_name)); + //temp.add(F("Loaded.")); - if (!getTemperatureComplete) { + if (!readTemperatureComplete) { // if we haven't read the sensor yet, let the user know // that we are still waiting for the first measurement temp.add((USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - millis()) / 1000); @@ -158,12 +213,85 @@ class UsermodTemperature : public Usermod { return; } - temp.add(temperature); - #ifdef USERMOD_DALLASTEMPERATURE_CELSIUS - temp.add(F("°C")); - #else - temp.add(F("°F")); - #endif + temp.add(degC ? temperature : (float)temperature * 1.8f + 32); + if (degC) temp.add(F("°C")); + else temp.add(F("°F")); + } + + /** + * 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) + //{ + //} + + /** + * 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() + //} + + /** + * 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)] = !disabled; + top["pin"] = temperaturePin; // usermodparam + top["degC"] = degC; // usermodparam + top[FPSTR(_readInterval)] = readingInterval / 1000; + DEBUG_PRINTLN(F("Temperature config saved.")); + } + + /** + * readFromConfig() is called before setup() to populate properties from values stored in cfg.json + */ + void readFromConfig(JsonObject &root) { + // we look for JSON object: {"Temperature": {"pin": 0, "degC": true}} + JsonObject top = root[FPSTR(_name)]; + int8_t newTemperaturePin = temperaturePin; + + if (!top.isNull() && top["pin"] != nullptr) { + if (top[FPSTR(_enabled)].is()) { + disabled = !top[FPSTR(_enabled)].as(); + } else { + String str = top[FPSTR(_enabled)]; // checkbox -> off or on + disabled = (bool)(str=="off"); // off is guaranteed to be present + } + newTemperaturePin = min(39,max(-1,top["pin"].as())); + if (top["degC"].is()) { + // reading from cfg.json + degC = top["degC"].as(); + } else { + // new configuration from set.cpp + String str = top["degC"]; // checkbox -> off or on + degC = (bool)(str!="off"); // off is guaranteed to be present + } + readingInterval = min(120,max(10,top[FPSTR(_readInterval)].as())) * 1000; // convert to ms + DEBUG_PRINTLN(F("Temperature config (re)loaded.")); + } else { + DEBUG_PRINTLN(F("No config found. (Using defaults.)")); + } + + if (!initDone) { + // first run: reading from cfg.json + temperaturePin = newTemperaturePin; + } else { + // changing paramters from settings page + if (newTemperaturePin != temperaturePin) { + // deallocate pin and release memory + delete oneWire; + pinManager.deallocatePin(temperaturePin); + temperaturePin = newTemperaturePin; + // initialise + setup(); + } + } } uint16_t getId() @@ -171,3 +299,8 @@ class UsermodTemperature : public Usermod { return USERMOD_ID_TEMPERATURE; } }; + +// strings to reduce flash memory usage (used more than twice) +const char UsermodTemperature::_name[] PROGMEM = "Temperature"; +const char UsermodTemperature::_enabled[] PROGMEM = "enabled"; +const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s"; \ No newline at end of file diff --git a/usermods/VL53L0X_gestures/readme.md b/usermods/VL53L0X_gestures/readme.md new file mode 100644 index 0000000000..dec8413008 --- /dev/null +++ b/usermods/VL53L0X_gestures/readme.md @@ -0,0 +1,35 @@ +# Description + +That usermod implements support of simple hand gestures with VL53L0X sensor: on/off and brightness correction. +It can be useful for kitchen strips to avoid any touches. + - on/off - just swipe a hand below your sensor ("shortPressAction" is called and can be customized through WLED macros) + - brightness correction - keep your hand below sensor for 1 second to switch to "brightness" mode. + Configure brightness by changing distance to the sensor (see parameters below for customization). + "macroLongPress" is also called here. + +## Installation + +1. Attach VL53L0X sensor to i2c pins according to default pins for your board. +2. Add `-D USERMOD_VL53L0X_GESTURES` to your build flags at platformio.ini (plaformio_override.ini) for needed environment. +In my case, for example: `build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D USERMOD_VL53L0X_GESTURES` +3. Add "pololu/VL53L0X" dependency below to `lib_deps` like this: +```ini +lib_deps = ${env.lib_deps} + pololu/VL53L0X @ ^1.3.0 +``` + +My entire `platformio_override.ini` for example (for nodemcu board): +```ini +[platformio] +default_envs = nodemcu + +[env:nodemcu] +board = nodemcu +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_4m1m} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D USERMOD_VL53L0X_GESTURES +lib_deps = ${env.lib_deps} + pololu/VL53L0X @ ^1.3.0 +``` \ No newline at end of file diff --git a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h new file mode 100644 index 0000000000..83f26e084d --- /dev/null +++ b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h @@ -0,0 +1,121 @@ +/* + * That usermod implements support of simple hand gestures with VL53L0X sensor: on/off and brightness correction. + * It can be useful for kitchen strips to avoid any touches. + * - on/off - just swipe a hand below your sensor ("shortPressAction" is called and can be customized through WLED macros) + * - brightness correction - keep your hand below sensor for 1 second to switch to "brightness" mode. + * Configure brightness by changing distance to the sensor (see parameters below for customization). + * "macroLongPress" is also called here. + * + * Enabling this mod usermod: + * 1. Attach VL53L0X sensor to i2c pins according to default pins for your board. + * 2. Add "-D USERMOD_VL53L0X_GESTURES" to your build flags at platformio.ini (plaformio_override.ini) for needed environment. + * In my case, for example: build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D USERMOD_VL53L0X_GESTURES + * 3. Add "pololu/VL53L0X" dependency to lib_deps like this: + * lib_deps = ${env.lib_deps} + * pololu/VL53L0X @ ^1.3.0 + */ +#pragma once + +#include "wled.h" + +#include +#include + +#ifndef VL53L0X_MAX_RANGE_MM +#define VL53L0X_MAX_RANGE_MM 230 // max height in millimiters to react for motions +#endif + +#ifndef VL53L0X_MIN_RANGE_OFFSET +#define VL53L0X_MIN_RANGE_OFFSET 60 // minimal range in millimiters that sensor can detect. Used in long motions to correct brightnes calculation. +#endif + +#ifndef VL53L0X_DELAY_MS +#define VL53L0X_DELAY_MS 100 // how often to get data from sensor +#endif + +#ifndef VL53L0X_LONG_MOTION_DELAY_MS +#define VL53L0X_LONG_MOTION_DELAY_MS 1000 // how often to get data from sensor +#endif + +class UsermodVL53L0XGestures : public Usermod { + private: + //Private class members. You can declare variables and functions only accessible to your usermod here + unsigned long lastTime = 0; + VL53L0X sensor; + + bool wasMotionBefore = false; + bool isLongMotion = false; + unsigned long motionStartTime = 0; + + public: + + void setup() { + Wire.begin(); + + sensor.setTimeout(150); + if (!sensor.init()) + { + DEBUG_PRINTLN(F("Failed to detect and initialize VL53L0X sensor!")); + } else { + sensor.setMeasurementTimingBudget(20000); // set high speed mode + } + } + + + void loop() { + if (millis() - lastTime > VL53L0X_DELAY_MS) + { + lastTime = millis(); + + int range = sensor.readRangeSingleMillimeters(); + DEBUG_PRINTF(F("range: %d, brightness: %d"), range, bri); + + if (range < VL53L0X_MAX_RANGE_MM) + { + if (!wasMotionBefore) + { + motionStartTime = millis(); + DEBUG_PRINTF(F("motionStartTime: %d"), motionStartTime); + } + wasMotionBefore = true; + + if (millis() - motionStartTime > VL53L0X_LONG_MOTION_DELAY_MS) //long motion + { + DEBUG_PRINTF(F("long motion: %d"), motionStartTime); + if (!isLongMotion) + { + if (macroLongPress) + { + applyMacro(macroLongPress); + } + isLongMotion = true; + } + + // set brightness according to range + bri = (VL53L0X_MAX_RANGE_MM - max(range, VL53L0X_MIN_RANGE_OFFSET)) * 255 / (VL53L0X_MAX_RANGE_MM - VL53L0X_MIN_RANGE_OFFSET); + DEBUG_PRINTF(F("new brightness: %d"), bri); + colorUpdated(1); + } + } else if (wasMotionBefore) { //released + long dur = millis() - motionStartTime; + + if (!isLongMotion) + { //short press + DEBUG_PRINTF(F("shortPressAction...")); + shortPressAction(); + } + wasMotionBefore = false; + isLongMotion = false; + } + } + } + + /* + * 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_VL53L0X; + } +}; \ No newline at end of file diff --git a/usermods/multi_relay/readme.md b/usermods/multi_relay/readme.md new file mode 100644 index 0000000000..eab5a00c6e --- /dev/null +++ b/usermods/multi_relay/readme.md @@ -0,0 +1,55 @@ +# Multi Relay + +This usermod-v2 modification allows the connection of multiple relays each with individual delay and on/off mode. + +## Usermod installation + +1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `usermods.add(new MultiRelay());` at the bottom of `usermods_list.cpp`. +or +2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY`in your platformio.ini + +You can override the default maximum number (4) of relays by defining MULTI_RELAY_MAX_RELAYS. + +Example **usermods_list.cpp**: + +```cpp +#include "wled.h" +/* + * Register your v2 usermods here! + * (for v1 usermods using just usermod.cpp, you can ignore this file) + */ + +/* + * Add/uncomment your usermod filename here (and once more below) + * || || || + * \/ \/ \/ + */ +//#include "usermod_v2_example.h" +//#include "usermod_temperature.h" +#include "../usermods/usermod_multi_relay.h" + +void registerUsermods() +{ + /* + * Add your usermod class name here + * || || || + * \/ \/ \/ + */ + //usermods.add(new MyExampleUsermod()); + //usermods.add(new UsermodTemperature()); + usermods.add(new MultiRelay()); + +} +``` + +## Configuration + +Usermod can be configured in Usermods settings page. + +If there is no MultiRelay section, just save current configuration and re-open Usermods settings page. + +Have fun - @blazoncek + +## Change log +2021-04 +* First implementation. \ No newline at end of file diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h new file mode 100644 index 0000000000..8d2861babc --- /dev/null +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -0,0 +1,434 @@ +#pragma once + +#include "wled.h" + +#ifndef MULTI_RELAY_MAX_RELAYS + #define MULTI_RELAY_MAX_RELAYS 4 +#endif + +#define ON true +#define OFF false + +/* + * 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. + */ + + +typedef struct relay_t { + int8_t pin; + bool active; + bool mode; + bool state; + bool external; + uint16_t delay; +} Relay; + + +class MultiRelay : public Usermod { + + private: + // array of relays + Relay _relay[MULTI_RELAY_MAX_RELAYS]; + + // switch timer start time + uint32_t _switchTimerStart = 0; + // old brightness + uint8_t _oldBrightness = 0; + + // usermod enabled + bool enabled = false; // needs to be configured (no default config) + // status of initialisation + bool initDone = false; + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _relay_str[]; + static const char _delay_str[]; + static const char _activeHigh[]; + static const char _external[]; + + + void publishMqtt(const char* state, int relay) { + //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, true, state); + } + } + + /** + * switch off the strip if the delay has elapsed + */ + void handleOffTimer() { + bool activeRelays = false; + for (uint8_t i=0; i 0 && millis() - _switchTimerStart > (_relay[i].delay*1000)) { + if (!_relay[i].external) toggleRelay(i); + _relay[i].active = false; + } + activeRelays = activeRelays || _relay[i].active; + } + if (!activeRelays) _switchTimerStart = 0; + } + + /** + * 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 much arugments 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 mutch arugments 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() { + for (uint8_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(mode ? "on" : "off", relay); + } + + /** + * toggle relay + */ + inline void toggleRelay(uint8_t relay) { + switchRelay(relay, !_relay[relay].state); + } + + uint8_t getActiveRelayCount() { + uint8_t count = 0; + for (uint8_t i=0; i=0) count++; + return count; + } + + //Functions called by WLED + + /** + * 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); + } + } + + /** + * 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 (uint8_t i=0; i=0) _relay[i].active = true; + } + } + + handleOffTimer(); + } + + /** + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + */ + void addToJsonInfo(JsonObject &root) { + if (enabled) { + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name + infoArr.add(String(getActiveRelayCount())); + } + } + + /** + * 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) { + } + + /** + * 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) { + } + + /** + * provide the changeable values + */ + void addToConfig(JsonObject &root) { + JsonObject top = root.createNestedObject(FPSTR(_name)); + + top[FPSTR(_enabled)] = enabled; + for (uint8_t i=0; i()) { + enabled = top[FPSTR(_enabled)].as(); // reading from cfg.json + } else { + // change from settings page + String str = top[FPSTR(_enabled)]; // checkbox -> off or on + enabled = (bool)(str!="off"); // off is guaranteed to be present + } + } + + for (uint8_t i=0; i())); + + if (top[parName+FPSTR(_activeHigh)] != nullptr) { + if (top[parName+FPSTR(_activeHigh)].is()) { + _relay[i].mode = top[parName+FPSTR(_activeHigh)].as(); // reading from cfg.json + } else { + // change from settings page + String str = top[parName+FPSTR(_activeHigh)]; // checkbox -> off or on + _relay[i].mode = (bool)(str!="off"); // off is guaranteed to be present + } + } + + if (top[parName+FPSTR(_external)] != nullptr) { + if (top[parName+FPSTR(_external)].is()) { + _relay[i].external = top[parName+FPSTR(_external)].as(); // reading from cfg.json + } else { + // change from settings page + String str = top[parName+FPSTR(_external)]; // checkbox -> off or on + _relay[i].external = (bool)(str!="off"); // off is guaranteed to be present + } + } + + _relay[i].delay = min(600,max(0,abs(top[parName+FPSTR(_delay_str)].as()))); + } + + if (!initDone) { + // reading config prior to setup() + DEBUG_PRINTLN(F("MultiRelay config loaded.")); + } else { + // deallocate all pins 1st + for (uint8_t i=0; i=0) { + pinManager.deallocatePin(oldPin[i]); + } + // allocate new pins + for (uint8_t i=0; i=0 && pinManager.allocatePin(_relay[i].pin,true)) { + if (!_relay[i].external) switchRelay(i, _relay[i].state = (bool)bri); + } else { + _relay[i].pin = -1; + } + _relay[i].active = false; + } + DEBUG_PRINTLN(F("MultiRelay config (re)loaded.")); + } + } + + /** + * 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; + } +}; + +// 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"; diff --git a/usermods/usermod_v2_auto_save/readme.md b/usermods/usermod_v2_auto_save/readme.md index 5c835c60ef..7cf81085e7 100644 --- a/usermods/usermod_v2_auto_save/readme.md +++ b/usermods/usermod_v2_auto_save/readme.md @@ -29,9 +29,9 @@ This file should be placed in the same directory as `platformio.ini`. ### Define Your Options * `USERMOD_AUTO_SAVE` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp -* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) -* `AUTOSAVE_SETTLE_MS` - Minimum time to wave before auto saving, defaults to 10000 (10s) -* `AUTOSAVE_PRESET_NUM` - Preset number to auto-save to, auto-load at startup from, defaults to 99 +* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) + +You can configure auto-save parameters using Usermods settings page. ### PlatformIO requirements @@ -43,3 +43,5 @@ Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. 2021-02 * First public release +2021-04 +* Adaptation for runtime configuration. \ No newline at end of file diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index bd7ea6d817..caf542e587 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -2,9 +2,8 @@ #include "wled.h" -// // v2 Usermod to automatically save settings -// to preset number AUTOSAVE_PRESET_NUM after a change to any of +// to configurable preset after a change to any of // // * brightness // * effect speed @@ -12,45 +11,34 @@ // * mode (effect) // * palette // -// but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle" +// but it will wait for configurable number of seconds, a "settle" // period in case there are other changes (any change will // extend the "settle" window). // -// It will additionally load preset AUTOSAVE_PRESET_NUM at startup. -// during the first `loop()`. Reasoning below. +// It can be configured to load auto saved preset at startup, +// during the first `loop()`. // // AutoSaveUsermod is standalone, but if FourLineDisplayUsermod // is installed, it will notify the user of the saved changes. -// -// Note: I don't love that WLED doesn't respect the brightness -// of the preset being auto loaded, so the AutoSaveUsermod -// will set the AUTOSAVE_PRESET_NUM preset in the first loop, -// so brightness IS honored. This means WLED will effectively -// ignore Default brightness and Apply N preset at boot when -// the AutoSaveUsermod is installed. - -//How long to wait after settings change to auto-save -#ifndef AUTOSAVE_SETTLE_MS -#define AUTOSAVE_SETTLE_MS 10*1000 -#endif - -//Preset number to save to -#ifndef AUTOSAVE_PRESET_NUM -#define AUTOSAVE_PRESET_NUM 99 -#endif - -// "Auto save MM-DD HH:MM:SS" + +// format: "~ MM-DD HH:MM:SS ~" #define PRESET_NAME_BUFFER_SIZE 25 class AutoSaveUsermod : public Usermod { - private: - // If we've detected the need to auto save, this will - // be non zero. - unsigned long autoSaveAfter = 0; - char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; + private: bool firstLoop = true; + bool initDone = false; + bool enabled = true; + + // configurable parameters + unsigned long autoSaveAfterSec = 15; // 15s by default + uint8_t autoSavePreset = 250; // last possible preset + bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot? + + // If we've detected the need to auto save, this will be non zero. + unsigned long autoSaveAfter = 0; uint8_t knownBrightness = 0; uint8_t knownEffectSpeed = 0; @@ -58,35 +46,65 @@ class AutoSaveUsermod : public Usermod { uint8_t knownMode = 0; uint8_t knownPalette = 0; -#ifdef USERMOD_FOUR_LINE_DISLAY + #ifdef USERMOD_FOUR_LINE_DISPLAY FourLineDisplayUsermod* display; -#endif + #endif + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _autoSaveEnabled[]; + static const char _autoSaveAfterSec[]; + static const char _autoSavePreset[]; + static const char _autoSaveApplyOnBoot[]; + + void inline saveSettings() { + char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; + updateLocalTime(); + sprintf_P(presetNameBuffer, + PSTR("~ %02d-%02d %02d:%02d:%02d ~"), + month(localTime), day(localTime), + hour(localTime), minute(localTime), second(localTime)); + savePreset(autoSavePreset, true, presetNameBuffer); + } + + void inline displayOverlay() { + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display != nullptr) { + display->wakeDisplay(); + display->overlay("Settings", "Auto Saved", 1500); + } + #endif + } public: + // gets called once at boot. Do all initialization that doesn't depend on // network here void setup() { -#ifdef USERMOD_FOUR_LINE_DISLAY - // This Usermod has enhanced funcionality if - // FourLineDisplayUsermod is available. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); -#endif + #ifdef USERMOD_FOUR_LINE_DISPLAY + // This Usermod has enhanced funcionality if + // FourLineDisplayUsermod is available. + display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + #endif + initDone = true; } // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here void connected() {} - /** + /* * Da loop. */ void loop() { + if (!autoSaveAfterSec || !enabled) return; // setting 0 as autosave seconds disables autosave + unsigned long now = millis(); uint8_t currentMode = strip.getMode(); uint8_t currentPalette = strip.getSegment(0).palette; if (firstLoop) { firstLoop = false; - applyPreset(AUTOSAVE_PRESET_NUM); + if (applyAutoSaveOnBoot) applyPreset(autoSavePreset); knownBrightness = bri; knownEffectSpeed = effectSpeed; knownEffectIntensity = effectIntensity; @@ -95,7 +113,7 @@ class AutoSaveUsermod : public Usermod { return; } - unsigned long wouldAutoSaveAfter = now + AUTOSAVE_SETTLE_MS; + unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000; if (knownBrightness != bri) { knownBrightness = bri; autoSaveAfter = wouldAutoSaveAfter; @@ -121,37 +139,32 @@ class AutoSaveUsermod : public Usermod { } } - void saveSettings() { - updateLocalTime(); - sprintf(presetNameBuffer, - "Auto save %02d-%02d %02d:%02d:%02d", - month(localTime), day(localTime), - hour(localTime), minute(localTime), second(localTime)); - savePreset(AUTOSAVE_PRESET_NUM, true, presetNameBuffer); - } - - void displayOverlay() { -#ifdef USERMOD_FOUR_LINE_DISLAY - if (display != nullptr) { - display->wakeDisplay(); - display->overlay("Settings", "Auto Saved", 1500); - } -#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) { + //JsonObject user = root["u"]; + //if (user.isNull()) user = root.createNestedObject("u"); + //JsonArray data = user.createNestedArray(F("Autosave")); + //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 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) { - } + //void readFromJsonState(JsonObject& root) { + // if (!initDone) return; // prevent crash on boot applyPreset() + //} /* * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. @@ -168,6 +181,13 @@ class AutoSaveUsermod : public Usermod { * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ void addToConfig(JsonObject& root) { + // we add JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + top[FPSTR(_autoSaveEnabled)] = enabled; + top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam + top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam + top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot; + DEBUG_PRINTLN(F("Autosave config saved.")); } /* @@ -179,7 +199,33 @@ class AutoSaveUsermod : public Usermod { * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) */ void readFromConfig(JsonObject& root) { - } + // we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINTLN(F("No config found. (Using defaults.)")); + return; + } + + if (top[FPSTR(_autoSaveEnabled)].is()) { + // reading from cfg.json + enabled = top[FPSTR(_autoSaveEnabled)].as(); + } else { + // reading from POST message + String str = top[FPSTR(_autoSaveEnabled)]; // checkbox -> off or on + enabled = (bool)(str!="off"); // off is guaranteed to be present + } + autoSaveAfterSec = min(3600,max(10,top[FPSTR(_autoSaveAfterSec)].as())); + autoSavePreset = min(250,max(100,top[FPSTR(_autoSavePreset)].as())); + if (top[FPSTR(_autoSaveApplyOnBoot)].is()) { + // reading from cfg.json + applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as(); + } else { + // reading from POST message + String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on + applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present + } + DEBUG_PRINTLN(F("Autosave config (re)loaded.")); + } /* * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). @@ -188,5 +234,11 @@ class AutoSaveUsermod : public Usermod { uint16_t getId() { return USERMOD_ID_AUTO_SAVE; } - -}; \ No newline at end of file +}; + +// strings to reduce flash memory usage (used more than twice) +const char AutoSaveUsermod::_name[] PROGMEM = "Autosave"; +const char AutoSaveUsermod::_autoSaveEnabled[] PROGMEM = "enabled"; +const char AutoSaveUsermod::_autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec"; +const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset"; +const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot"; diff --git a/usermods/usermod_v2_four_line_display/readme.md b/usermods/usermod_v2_four_line_display/readme.md index 3198b2be50..367f3d7ac5 100644 --- a/usermods/usermod_v2_four_line_display/readme.md +++ b/usermods/usermod_v2_four_line_display/readme.md @@ -1,4 +1,4 @@ -# Rotary Encoder UI Usermod +# I2C 4 Line Display Usermod First, thanks to the authors of the ssd11306_i2c_oled_u8g2 mod. @@ -19,13 +19,11 @@ This file should be placed in the same directory as `platformio.ini`. ### Define Your Options -* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells Rotary Encoder usermod, if installed, that the display is available +* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells Rotary Encoder usermod, if installed, that the display is available * `FLD_PIN_SCL` - The display SCL pin, defaults to 5 * `FLD_PIN_SDA` - The display SDA pin, defaults to 4 -* `FLIP_MODE` - Set to 0 or 1 -* `LINE_HEIGHT` - Set to 1 or 2 -There are other `#define` values in the Usermod that might be of interest. +All of the parameters can be configured using Usermods settings page, inluding GPIO pins. ### PlatformIO requirements @@ -37,3 +35,5 @@ UI usermod folder for how to include these using `platformio_override.ini`. 2021-02 * First public release +2021-04 +* Adaptation for runtime configuration. \ No newline at end of file diff --git a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h index 0b59af5517..ea1a6f90b7 100644 --- a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h +++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h @@ -24,50 +24,25 @@ // //The SCL and SDA pins are defined here. -#ifndef FLD_PIN_SCL -#define FLD_PIN_SCL 5 -#endif - -#ifndef FLD_PIN_SDA -#define FLD_PIN_SDA 4 -#endif - -// U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8( -// U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA); -U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8( - U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA); - -// Screen upside down? Change to 0 or 1 -#ifndef FLIP_MODE -#define FLIP_MODE 0 -#endif - -// LINE_HEIGHT 1 is single height, for 128x32 displays. -// LINE_HEIGHT 2 makes the 128x64 screen display at double height. -#ifndef LINE_HEIGHT -#define LINE_HEIGHT 2 -#endif - -// If you aren't also including RotaryEncoderUIUsermod -// you probably want to set both -// SLEEP_MODE_ENABLED false -// CLOCK_MODE_ENABLED false -// as you will never be able wake the display / disable the clock. -#ifdef USERMOD_ROTARY_ENCODER_UI -#ifndef SLEEP_MODE_ENABLED -#define SLEEP_MODE_ENABLED true -#endif -#ifndef CLOCK_MODE_ENABLED -#define CLOCK_MODE_ENABLED true -#endif +#ifdef ARDUINO_ARCH_ESP32 + #ifndef FLD_PIN_SCL + #define FLD_PIN_SCL 22 + #endif + #ifndef FLD_PIN_SDA + #define FLD_PIN_SDA 21 + #endif #else -#define SLEEP_MODE_ENABLED false -#define CLOCK_MODE_ENABLED false + #ifndef FLD_PIN_SCL + #define FLD_PIN_SCL 5 + #endif + #ifndef FLD_PIN_SDA + #define FLD_PIN_SDA 4 + #endif #endif // When to time out to the clock or blank the screen // if SLEEP_MODE_ENABLED. -#define SCREEN_TIMEOUT_MS 15*1000 +#define SCREEN_TIMEOUT_MS 60*1000 // 1 min #define TIME_INDENT 0 #define DATE_INDENT 2 @@ -75,33 +50,43 @@ U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8( // Minimum time between redrawing screen in ms #define USER_LOOP_REFRESH_RATE_MS 1000 -#if LINE_HEIGHT == 2 -#define DRAW_STRING draw1x2String -#define DRAW_GLYPH draw1x2Glyph -#define DRAW_BIG_STRING draw2x2String -#else -#define DRAW_STRING drawString -#define DRAW_GLYPH drawGlyph -#define DRAW_BIG_STRING draw2x2String -#endif - // Extra char (+1) for null #define LINE_BUFFER_SIZE 16+1 -#define FLD_LINE_3_BRIGHTNESS 0 -#define FLD_LINE_3_EFFECT_SPEED 1 -#define FLD_LINE_3_EFFECT_INTENSITY 2 -#define FLD_LINE_3_PALETTE 3 -#if LINE_HEIGHT == 2 -#define TIME_LINE 1 -#else -#define TIME_LINE 0 -#endif +typedef enum { + FLD_LINE_4_BRIGHTNESS = 0, + FLD_LINE_4_EFFECT_SPEED, + FLD_LINE_4_EFFECT_INTENSITY, + FLD_LINE_4_MODE, + FLD_LINE_4_PALETTE +} Line4Type; + +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 +} DisplayType; class FourLineDisplayUsermod : public Usermod { + private: + + bool initDone = false; unsigned long lastTime = 0; + // HW interface & configuration + U8X8 *u8x8 = nullptr; // pointer to U8X8 display object + int8_t sclPin=FLD_PIN_SCL, sdaPin=FLD_PIN_SDA; // I2C pins for interfacing, get initialised in readFromConfig() + DisplayType type = SSD1306; // display type + bool flip = false; // flip display 180° + uint8_t contrast = 10; // screen contrast + uint8_t lineHeight = 1; // 1 row or 2 rows + uint32_t refreshRate = USER_LOOP_REFRESH_RATE_MS; // in ms + uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms + bool sleepMode = true; // allow screen sleep? + bool clockMode = false; // display clock + // needRedraw marks if redraw is required to prevent often redrawing. bool needRedraw = true; @@ -118,38 +103,72 @@ class FourLineDisplayUsermod : public Usermod { uint8_t knownHour = 99; bool displayTurnedOff = false; - long lastUpdate = 0; - long lastRedraw = 0; - long overlayUntil = 0; - byte lineThreeType = FLD_LINE_3_BRIGHTNESS; + unsigned long lastUpdate = 0; + unsigned long lastRedraw = 0; + unsigned long overlayUntil = 0; + Line4Type lineFourType = FLD_LINE_4_BRIGHTNESS; // Set to 2 or 3 to mark lines 2 or 3. Other values ignored. byte markLineNum = 0; - char lineBuffer[LINE_BUFFER_SIZE]; - - char **modes_qstrings = nullptr; - char **palettes_qstrings = nullptr; + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _contrast[]; + static const char _refreshRate[]; + static const char _screenTimeOut[]; + static const char _flip[]; + static const char _sleepMode[]; + static const char _clockMode[]; // 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 + public: // gets called once at boot. Do all initialization that doesn't depend on // network here void setup() { - u8x8.begin(); - u8x8.setFlipMode(FLIP_MODE); - u8x8.setPowerSave(0); - u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 - u8x8.setFont(u8x8_font_chroma48medium8_r); - u8x8.DRAW_STRING(0, 0*LINE_HEIGHT, "Loading..."); - - ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT); - modes_qstrings = modeSortUsermod->getModesQStrings(); - palettes_qstrings = modeSortUsermod->getPalettesQStrings(); + if (type==NONE) return; + if (!pinManager.allocatePin(sclPin)) { sclPin = -1; type = NONE; return;} + if (!pinManager.allocatePin(sdaPin)) { pinManager.deallocatePin(sclPin); sclPin = sdaPin = -1; type = NONE; return; } + switch (type) { + case SSD1306: + #ifdef ESP8266 + if (!(sclPin==5 && sdaPin==4)) + u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset + else + #endif + u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA + break; + case SH1106: + #ifdef ESP8266 + if (!(sclPin==5 && sdaPin==4)) + u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset + else + #endif + u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA + break; + case SSD1306_64: + #ifdef ESP8266 + if (!(sclPin==5 && sdaPin==4)) + u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset + else + #endif + u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA + break; + default: + u8x8 = nullptr; + type = NONE; + return; + } + (static_cast(u8x8))->begin(); + setFlipMode(flip); + 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..."); + initDone = true; } // gets called every time WiFi is (re-)connected. Initialize own network @@ -160,7 +179,7 @@ class FourLineDisplayUsermod : public Usermod { * Da loop. */ void loop() { - if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + if (millis() - lastUpdate < (clockMode?1000:refreshRate)) { return; } lastUpdate = millis(); @@ -168,18 +187,59 @@ class FourLineDisplayUsermod : public Usermod { redraw(false); } + /** + * Wrappers for screen drawing + */ + void setFlipMode(uint8_t mode) { + if (type==NONE) return; + (static_cast(u8x8))->setFlipMode(mode); + } + void setContrast(uint8_t contrast) { + if (type==NONE) return; + (static_cast(u8x8))->setContrast(contrast); + } + void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { + if (type==NONE) return; + (static_cast(u8x8))->setFont(u8x8_font_chroma48medium8_r); + if (!ignoreLH && lineHeight==2) (static_cast(u8x8))->draw1x2String(col, row, string); + else (static_cast(u8x8))->drawString(col, row, string); + } + void draw2x2String(uint8_t col, uint8_t row, const char *string) { + if (type==NONE) return; + (static_cast(u8x8))->setFont(u8x8_font_chroma48medium8_r); + (static_cast(u8x8))->draw2x2String(col, row, string); + } + void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) { + if (type==NONE) return; + (static_cast(u8x8))->setFont(font); + if (!ignoreLH && lineHeight==2) (static_cast(u8x8))->draw1x2Glyph(col, row, glyph); + else (static_cast(u8x8))->drawGlyph(col, row, glyph); + } + uint8_t getCols() { + if (type==NONE) return 0; + return (static_cast(u8x8))->getCols(); + } + void clear() { + if (type==NONE) return; + (static_cast(u8x8))->clear(); + } + void setPowerSave(uint8_t save) { + if (type==NONE) return; + (static_cast(u8x8))->setPowerSave(save); + } + /** * Redraw the screen (but only if things have changed * or if forceRedraw). */ void redraw(bool forceRedraw) { + if (type==NONE) return; if (overlayUntil > 0) { if (millis() >= overlayUntil) { // Time to display the overlay has elapsed. overlayUntil = 0; forceRedraw = true; - } - else { + } else { // We are still displaying the overlay // Don't redraw. return; @@ -208,22 +268,46 @@ class FourLineDisplayUsermod : public Usermod { if (!needRedraw) { // Nothing to change. // Turn off display after 3 minutes with no change. - if(SLEEP_MODE_ENABLED && !displayTurnedOff && - (millis() - lastRedraw > SCREEN_TIMEOUT_MS)) { + 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. + knownHour = 99; // force screen clear sleepOrClock(true); - } - else if (displayTurnedOff && CLOCK_MODE_ENABLED) { + } else if (displayTurnedOff && clockMode) { showTime(); + } else if ((millis() - lastRedraw)/1000%3 == 0) { + // change 4th line every 3s + switch (lineFourType) { + case FLD_LINE_4_BRIGHTNESS: + setLineFourType(FLD_LINE_4_EFFECT_SPEED); + break; + case FLD_LINE_4_MODE: + setLineFourType(FLD_LINE_4_BRIGHTNESS); + break; + case FLD_LINE_4_PALETTE: + setLineFourType(clockMode ? FLD_LINE_4_MODE : FLD_LINE_4_BRIGHTNESS); + break; + case FLD_LINE_4_EFFECT_SPEED: + setLineFourType(FLD_LINE_4_EFFECT_INTENSITY); + break; + case FLD_LINE_4_EFFECT_INTENSITY: + setLineFourType(FLD_LINE_4_PALETTE); + break; + default: + break; + } + drawLineFour(); } return; + } else { + knownHour = 99; // force time display + clear(); } + needRedraw = false; lastRedraw = millis(); - if (displayTurnedOff) - { + if (displayTurnedOff) { // Turn the display back on sleepOrClock(false); } @@ -242,79 +326,94 @@ class FourLineDisplayUsermod : public Usermod { knownEffectIntensity = effectIntensity; // Do the actual drawing - u8x8.clear(); - u8x8.setFont(u8x8_font_chroma48medium8_r); // First row with Wifi name - String ssidString = knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0); - u8x8.DRAW_STRING(1, 0*LINE_HEIGHT, ssidString.c_str()); - // Print `~` char to indicate that SSID is longer, than owr dicplay - if (knownSsid.length() > u8x8.getCols()) { - u8x8.DRAW_STRING(u8x8.getCols() - 1, 0*LINE_HEIGHT, "~"); + drawGlyph(0, 0, 80, u8x8_font_open_iconic_embedded_1x1); // wifi icon + String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); + drawString(1, 0, ssidString.c_str()); + // Print `~` char to indicate that SSID is longer, than our display + if (knownSsid.length() > getCols()) { + drawString(getCols() - 1, 0, "~"); } // Second row with IP or Psssword + drawGlyph(0, lineHeight, 68, u8x8_font_open_iconic_embedded_1x1); // home icon // Print password in AP mode and if led is OFF. if (apActive && bri == 0) { - u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, apPass); - } - else { - String ipString = knownIp.toString(); - u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, ipString.c_str()); + drawString(1, lineHeight, apPass); + } else { + drawString(1, lineHeight, (knownIp.toString()).c_str()); } - // Third row with mode name - showCurrentEffectOrPalette(modes_qstrings[knownMode], 2); + // Third row with mode name or current time + if (clockMode) showTime(false); + else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 2); + + // Fourth row + drawLineFour(); + + drawGlyph(0, 2*lineHeight, 66 + (bri > 0 ? 3 : 0), u8x8_font_open_iconic_weather_2x2); // sun/moon icon + //if (markLineNum>1) drawGlyph(2, markLineNum*lineHeight, 66, u8x8_font_open_iconic_arrow_1x1); // arrow icon + } - switch(lineThreeType) { - case FLD_LINE_3_BRIGHTNESS: - sprintf(lineBuffer, "Brightness %d", bri); - u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); + void drawLineFour() { + char lineBuffer[LINE_BUFFER_SIZE]; + switch(lineFourType) { + case FLD_LINE_4_BRIGHTNESS: + sprintf_P(lineBuffer, PSTR("Brightness %3d"), bri); + drawString(2, 3*lineHeight, lineBuffer); + break; + case FLD_LINE_4_EFFECT_SPEED: + sprintf_P(lineBuffer, PSTR("FX Speed %3d"), effectSpeed); + drawString(2, 3*lineHeight, lineBuffer); break; - case FLD_LINE_3_EFFECT_SPEED: - sprintf(lineBuffer, "FX Speed %d", effectSpeed); - u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); + case FLD_LINE_4_EFFECT_INTENSITY: + sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity); + drawString(2, 3*lineHeight, lineBuffer); break; - case FLD_LINE_3_EFFECT_INTENSITY: - sprintf(lineBuffer, "FX Intense %d", effectIntensity); - u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); + case FLD_LINE_4_MODE: + showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); break; - case FLD_LINE_3_PALETTE: - showCurrentEffectOrPalette(palettes_qstrings[knownPalette], 3); + case FLD_LINE_4_PALETTE: + default: + showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 3); break; } - - u8x8.setFont(u8x8_font_open_iconic_arrow_1x1); - u8x8.DRAW_GLYPH(0, markLineNum*LINE_HEIGHT, 66); // arrow icon - - u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); - u8x8.DRAW_GLYPH(0, 0*LINE_HEIGHT, 80); // wifi icon - u8x8.DRAW_GLYPH(0, 1*LINE_HEIGHT, 68); // home icon } /** * Display the current effect or palette (desiredEntry) * on the appropriate line (row). - * - * TODO: Should we cache the current effect and - * TODO: palette name? This seems expensive. */ - void showCurrentEffectOrPalette(char *qstring, uint8_t row) { - uint8_t printedChars = 1; + void showCurrentEffectOrPalette(int knownMode, const char *qstring, uint8_t row) { + char lineBuffer[LINE_BUFFER_SIZE]; + uint8_t qComma = 0; + bool insideQuotes = false; + uint8_t printedChars = 0; char singleJsonSymbol; - int i = 0; - while (true) { + + // Find the mode name in JSON + for (size_t i = 0; i < strlen_P(qstring); i++) { singleJsonSymbol = pgm_read_byte_near(qstring + i); - if (singleJsonSymbol == '"' || singleJsonSymbol == '\0' ) { - break; - } - u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol); - printedChars++; - if ( (printedChars > u8x8.getCols() - 2)) { - break; + if (singleJsonSymbol == '\0') break; + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownMode)) break; + lineBuffer[printedChars++] = singleJsonSymbol; } - i++; + if ((qComma > knownMode) || (printedChars > getCols()-2) || printedChars > sizeof(lineBuffer)-2) break; } + for (;printedChars < getCols()-2 || printedChars > sizeof(lineBuffer)-2; printedChars++) lineBuffer[printedChars]=' '; + lineBuffer[printedChars] = 0; + drawString(2, row*lineHeight, lineBuffer); } /** @@ -324,6 +423,7 @@ class FourLineDisplayUsermod : public Usermod { * to wake up the screen. */ bool wakeDisplay() { + knownHour = 99; if (displayTurnedOff) { // Turn the display back on sleepOrClock(false); @@ -345,36 +445,31 @@ class FourLineDisplayUsermod : public Usermod { } // Print the overlay - u8x8.clear(); - u8x8.setFont(u8x8_font_chroma48medium8_r); - if (line1) { - u8x8.DRAW_STRING(0, 1*LINE_HEIGHT, line1); - } - if (line2) { - u8x8.DRAW_STRING(0, 2*LINE_HEIGHT, line2); - } + clear(); + if (line1) drawString(0, 1*lineHeight, line1); + if (line2) drawString(0, 2*lineHeight, line2); overlayUntil = millis() + showHowLong; } /** - * Specify what data should be defined on line 3 + * Specify what data should be defined on line 4 * (the last line). */ - void setLineThreeType(byte newLineThreeType) { - if (newLineThreeType == FLD_LINE_3_BRIGHTNESS || - newLineThreeType == FLD_LINE_3_EFFECT_SPEED || - newLineThreeType == FLD_LINE_3_EFFECT_INTENSITY || - newLineThreeType == FLD_LINE_3_PALETTE) { - lineThreeType = newLineThreeType; - } - else { - // Unknown value. - lineThreeType = FLD_LINE_3_BRIGHTNESS; + void setLineFourType(Line4Type newLineFourType) { + if (newLineFourType == FLD_LINE_4_BRIGHTNESS || + newLineFourType == FLD_LINE_4_EFFECT_SPEED || + newLineFourType == FLD_LINE_4_EFFECT_INTENSITY || + newLineFourType == FLD_LINE_4_MODE || + newLineFourType == FLD_LINE_4_PALETTE) { + lineFourType = newLineFourType; + } else { + // Unknown value + lineFourType = FLD_LINE_4_BRIGHTNESS; } } /** - * Line 2 or 3 (last two lines) can be marked with an + * Line 3 or 4 (last two lines) can be marked with an * arrow in the first column. Pass 2 or 3 to this to * specify which line to mark with an arrow. * Any other values are ignored. @@ -388,42 +483,17 @@ class FourLineDisplayUsermod : public Usermod { } } - /* - * 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 - } - */ - /** * Enable sleep (turn the display off) or clock mode. */ void sleepOrClock(bool enabled) { if (enabled) { - if (CLOCK_MODE_ENABLED) { - showTime(); - } - else { - u8x8.setPowerSave(1); - } + if (clockMode) showTime(); + else setPowerSave(1); displayTurnedOff = true; } else { - if (!CLOCK_MODE_ENABLED) { - u8x8.setPowerSave(0); - } + setPowerSave(0); displayTurnedOff = false; } } @@ -433,23 +503,28 @@ class FourLineDisplayUsermod : public Usermod { * on the middle rows. Based 24 or 12 hour depending on * the useAMPM configuration. */ - void showTime() { + void showTime(bool fullScreen = true) { + char lineBuffer[LINE_BUFFER_SIZE]; + updateLocalTime(); byte minuteCurrent = minute(localTime); byte hourCurrent = hour(localTime); + byte secondCurrent = second(localTime); if (knownMinute == minuteCurrent && knownHour == hourCurrent) { // Time hasn't changed. - return; + if (!fullScreen) return; + } else { + if (fullScreen) clear(); } knownMinute = minuteCurrent; knownHour = hourCurrent; - u8x8.clear(); - u8x8.setFont(u8x8_font_chroma48medium8_r); - - int currentMonth = month(localTime); - sprintf(lineBuffer, "%s %d", monthShortStr(currentMonth), day(localTime)); - u8x8.DRAW_BIG_STRING(DATE_INDENT, TIME_LINE*LINE_HEIGHT, lineBuffer); + byte currentMonth = month(localTime); + sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(currentMonth), day(localTime)); + if (fullScreen) + draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays + else + drawString(2, lineHeight*2, lineBuffer); byte showHour = hourCurrent; boolean isAM = false; @@ -467,25 +542,45 @@ class FourLineDisplayUsermod : public Usermod { } } - sprintf(lineBuffer, "%02d:%02d %s", showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : ""); + sprintf_P(lineBuffer, (secondCurrent%2 || !fullScreen) ? PSTR("%2d:%02d") : PSTR("%2d %02d"), (useAMPM ? showHour : hourCurrent), minuteCurrent); // For time, we always use LINE_HEIGHT of 2 since // we are printing it big. - u8x8.DRAW_BIG_STRING(TIME_INDENT + (useAMPM ? 0 : 2), (TIME_LINE + 1) * 2, lineBuffer); + if (fullScreen) { + draw2x2String(TIME_INDENT+2, lineHeight*2, lineBuffer); + sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); + if (!useAMPM) drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line + } else { + drawString(9+(useAMPM?0:2), lineHeight*2, lineBuffer); + } + if (useAMPM) drawString(12+(fullScreen?0:2), lineHeight*2, (isAM ? "AM" : "PM"), true); } + /* + * 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.")); + //} + /* * 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) { - } + //void readFromJsonState(JsonObject& root) { + // if (!initDone) return; // prevent crash on boot applyPreset() + //} /* * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. @@ -502,6 +597,18 @@ 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) { + JsonObject top = root.createNestedObject(FPSTR(_name)); + JsonArray i2c_pin = top.createNestedArray("pin"); + i2c_pin.add(sclPin); + i2c_pin.add(sdaPin); + top["type"] = type; + top[FPSTR(_flip)] = (bool) flip; + top[FPSTR(_contrast)] = contrast; + top[FPSTR(_refreshRate)] = refreshRate/1000; + top[FPSTR(_screenTimeOut)] = screenTimeout/1000; + top[FPSTR(_sleepMode)] = (bool) sleepMode; + top[FPSTR(_clockMode)] = (bool) clockMode; + DEBUG_PRINTLN(F("4 Line Display config saved.")); } /* @@ -513,6 +620,74 @@ class FourLineDisplayUsermod : public Usermod { * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) */ void readFromConfig(JsonObject& root) { + bool needsRedraw = false; + DisplayType newType = type; + int8_t newScl = sclPin; + int8_t newSda = sdaPin; + + JsonObject top = root[FPSTR(_name)]; + if (!top.isNull() && top["pin"] != nullptr) { + newScl = top["pin"][0]; + newSda = top["pin"][1]; + newType = top["type"]; + if (top[FPSTR(_flip)].is()) { + flip = top[FPSTR(_flip)].as(); + } else { + String str = top[FPSTR(_flip)]; // checkbox -> off or on + flip = (bool)(str!="off"); // off is guaranteed to be present + needRedraw |= true; + } + contrast = top[FPSTR(_contrast)].as(); + refreshRate = top[FPSTR(_refreshRate)].as() * 1000; + screenTimeout = top[FPSTR(_screenTimeOut)].as() * 1000; + if (top[FPSTR(_sleepMode)].is()) { + sleepMode = top[FPSTR(_sleepMode)].as(); + } else { + String str = top[FPSTR(_sleepMode)]; // checkbox -> off or on + sleepMode = (bool)(str!="off"); // off is guaranteed to be present + needRedraw |= true; + } + if (top[FPSTR(_clockMode)].is()) { + clockMode = top[FPSTR(_clockMode)].as(); + } else { + String str = top[FPSTR(_clockMode)]; // checkbox -> off or on + clockMode = (bool)(str!="off"); // off is guaranteed to be present + needRedraw |= true; + } + DEBUG_PRINTLN(F("4 Line Display config (re)loaded.")); + } else { + DEBUG_PRINTLN(F("No config found. (Using defaults.)")); + } + + if (!initDone) { + // first run: reading from cfg.json + sclPin = newScl; + sdaPin = newSda; + type = newType; + lineHeight = type==SSD1306 ? 1 : 2; + } else { + // changing paramters from settings page + if (sclPin!=newScl || sdaPin!=newSda || type!=newType) { + if (type==SSD1306) delete (static_cast(u8x8)); + if (type==SH1106) delete (static_cast(u8x8)); + if (type==SSD1306_64) delete (static_cast(u8x8)); + pinManager.deallocatePin(sclPin); + pinManager.deallocatePin(sdaPin); + sclPin = newScl; + sdaPin = newSda; + if (newScl<0 || newSda<0) { + type = NONE; + return; + } else + type = newType; + lineHeight = type==SSD1306 ? 1 : 2; + setup(); + needRedraw |= true; + } + setContrast(contrast); + setFlipMode(flip); + if (needsRedraw && !wakeDisplay()) redraw(true); + } } /* @@ -522,5 +697,13 @@ class FourLineDisplayUsermod : public Usermod { uint16_t getId() { return USERMOD_ID_FOUR_LINE_DISP; } - -}; \ No newline at end of file +}; + +// strings to reduce flash memory usage (used more than twice) +const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay"; +const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast"; +const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRateSec"; +const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec"; +const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip"; +const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode"; +const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode"; diff --git a/wled00/.vs/wled00/v15/.suo b/wled00/.vs/wled00/v15/.suo deleted file mode 100644 index 5bbdedd721cd1323926d95284350e94ada9b9459..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41984 zcmeHQYm{5nb-rV4{Gyl?LI_D=z!)4nlCLzLX$KJw56p@+ax5=ltO9Rl+vUnZqq>9wIH;m4b2buk(FOfRv$mo)x_@i-FwgI z>W&^)8b4-+duDxeU+12Ep8M>x&)!#Wy#Bs-zVPDp?@ESnmvotQ_S|Oaa^3wl-MMs? zByGj@I^gWNbLaSo&v9ynRa65Qo9tEvN-0YSvIBVaILgqvCTI&c&q)nZTm9* ze`)&w;yvV4Blm)0PC6mo1L`9<3rjN%QmK4=Eqb87x+|Ynzj_U~(S4P0SSq2^87U3= z#HbX-bsR`3dC{_|#a?>2MOxRk3%9Qan2)6~s^3}AZa1ReS^f3JyQJT2`8j4ksina2 z{WDzx9Lo$>fDYhVfN2?n9ry15b^})e4nXAD7}t0mL0sd#TG#J45w{m{ zWE1gf(O3!hFXs5a>PuU!@sIw(uu{gq=!2V(C&35Ac;dJhe1PMhachnL4S3G+&v!Zg z8MoE(zZ37@25>(42yi>Vv3?D}bUFTcY&Kko@lW30Y(7>}{Es95e+RJLh+o3_F9VMP zp8$RX_!WT9K853_we!zt$IofUCvf~V;McY5Cvp5u;J1LMfZqmw2lzbjyTI=OzYlx? z_#*Hn;A!B?z*m50fMU?00|Ft*%XSMNf|m4fFtq0O~mO<*s%-F;3Pm9l|7{h%m7osG%yd4bIk##fJcA@ z;A6lq0uKREP)=yQZVJ@c3n_aU`GOs#S9>W(X-OHOcpY`A>Yv$8$nrVfYZOi+0{a$2 zGxRx5i)bxMf?4F6^|P;q{{JxEBfns{A1$6lxr-=OUU~q1vRmN?>W+x|g!giwM|n_{ z&eV7L-`fKH7x3PVS`zivA6yf$f7c7G@Sn$!Ap0GIv7H4E=6WDwVkKyAq5S=zOI;p= zk>814#{D2;-ncI1+@KGT|J%q<$eYaezdpoWuO(7RzYl$&r1UbeAC*^DW=q>$dmTJz zUE7toAYU{~pJQ9RA3}+SL331DdFQbnVlQLgZY24uDYUmx|53au>OVPka{m#(-yhf= z?LQhA2#xhRj`?RoIsbGpJea@F>B|j;X5;DI`}Ys^4ebt0pNPaWW4>T~f27zw>)d_s zoguK-7}yP$DoLCTVz7k4O3U|TR8r-Y9m6P#4t9h4?eD$(*uuW!4?i~d=1k%Z7KxRp z56a5Z37+l&pYK9K3A9lW#{!NC>7lluk^!|HDMfIVs9!y`V;-5oIPJn*5(B@_Bby~H z12JSfjr!+Xd7QD%8K(fgNeCWZabS+JYpZFsO#95%-ywAVVbm_OI;jiwe|PgqH@$e-%x(G{(|9 z`u?x#eW>%H45o5dv$UD=PK-aUr71J8+wfkaun`f)*Qv1@@n6ai^+GHB_d7_C^ZEG- z6~m>;MA|FQnb}Ji18T~8eN-==u}=!_Urib(|KFz3He3Et z#D8`x{vp((-w*1n8KH?%->CeL`L~U7_;r9;`uh5B)c;+Tt42B0Jy9>P{H%AbOU%~)XAxJ(UybN<9aArmKG#KD1DU1& zIO6_POSBC~_6N2hWg?ynP)6SjSnI#FyZ-A#Pb`4{cZ1K7_eUX8a3w?;l$z0MJP%!B zF^K-pzQ?fmpw7vk;+a_g4S<5szz>7+401r8$F*o;ag^3hjQXFe@%ZNfv-H1)xI+Hd z>;L;u;xt67%6$O)BLA(n6W{`UvT~*HCd>ak;{y!(`PYvUbFP^}xhWSFK{d#Ud>hjcY8fbGY8& zoX2_R?(e?vsd(|hXEyz?`-zv{|Fx2(|0(lN*5XRn|M*9|yM1F=0Q&tyhoVR2d;Ix= zE78Rzq5o+1NWUZC_XlTAOiy|5JJQda_a?$)#}djqB1 z9AP{%T)~COOiwzI&lPebMf*UyP>N@w#Zoezvme1}x;WdH%Vi4ozVu|lej{RYEE3b1 zRMO!)exi`e+SA$G7jdg$FzO#De`m2ajA8{mfdwbEK=lIl9~JrMHbiQL{}}1_fa6Ul zGtev+yJ}vzH(YBT;{F458H-G+US_ptjNhl78|hQKn8KVm4!)aH=yML#=cJMV`2Qkp zv*qV}CFVaPeU%btV$~ADk9&%@Ip{3O4fch^{cC++3SBm*>$gKY1I2M0XXqp&A zeWC54X2*`Z1yq<`twO!TXU^Uo-uk~MuX@?{-M+WJ^IC|R)@M}9?1)+VDP=;Wm@*7{ zOI0yKA++>~StX6WT1}rPQzP+Grg*$KF&PX7!uDdSP%Na1#dLPGR+p_vn><1c{fY-> zFO2+SJ#YMHTXeQiOikE>xlAUND5i7Sf_;A~o64sX_E1{65zo&)=)C`aEn>8oN0vL< zCklyNK9e5qXdg)B3wUp@%P#Y;qdkanmh!2+*;J{Rk7qjCdrQNabmCwN{AfIt-8<~@ z#N7#Zm%|5UD|>wpbcn?Dxi}Cnj1}X|MlH{qg1L#wcs`ZiL~7CWTnc=6 z)8q9jMXQ|fNtMHj1Riu;&>DI`VFR1V@)_3iWF=;kncG^SH;iG$DMK41Z~V!(5C6|h z=$p@vxPE-*?>`}Qc+};@G_9wQ?>TIHkcwUx%9QPjW>@HU$@3FSUO zC6qOkxGPC-u9=dhVg1DYZMN6jO_r*W{TcO;^#a%bwDqE$irMw2UjNvLKG(-nNZnN< z6I}n3uG}L11CR!jilj+MAzxs%6S(kM{>{2`5YE9DPh=}gZHS}x=u^HTRB zY+&l``-yz9{<)T?9>^^H0mKz{MU==J$;cN+h7==Hwd+tVGqR|e9P}U5!K|%WgBl*L zkeVN{E4Rg_(38n!^C%hBG%Vfsfz`PVF!o25j@DtSZ3OKFSiaReKhnA*gIRtAz~PsdSvxolJAzTeC_(oPd9Hr z{MmDl{^a#DPt&9tcYk=zjyGS}zJ34KfAH?YZNn4VgiiepZHLUNftePp!zOSSN|S7g86xD3Okcqfd#&J_U~G zhMeI=Ekn<=Wr-3^BqWTK%SFQ(E%JhjO`aFl+`Djxc~GdgNmuAq^M*^B!82vz8ht!F zW>-o+H6PGmmk$q=dx&~gzju_M8s9@&kmw??GK@;MZ2g&k*QdXA;KjGU`uf#h9^BSk z|A|W8k!#MJJ#$0%)1Oa1|Ktrn_BHoDt^HkEUU>ANRcx>^521#`IP1b+C+>$4ud?Q8 z7m!l)c0K4d8T5{{p&yxPe-BcZL8tXxB(WV+**8`HU45@(52=hJ@}_!xGVUhDhEm@* zmz%dclwHyDnW7!uk}1?Rcj#yvH?Qr?bz)4|F_Tf6R;{N4h?PXC%VU@ux92x6<-NOR z@gsfbBSB}-@AWRstBKgB5;K$e+==Zja?Y4x2~yla;3f3pD91MIIWwsot=s=Rv?m%^^oNT2!!+I^Y*0)enU z;M$j=l2Y}nBe-2_|6R4aqAhR|xkxH@SoQKl|8tJ$?SNVOy>;l9?JuYepofF7RK-hG z0?My^Q>z`%%+kLWaR+F`gnMsm+Vl-Tu#{PSe zc1p3yc(Tx*j2E#LpGp$pC{$uktWiL_8Do=aVJOVGcJ6m>7?brV6oCVY1U18;j?Y ztS{}-L3@iBj4<-?d>WC$(cX#Bp=hj-Pl$8OL8mzBGAOgJ;h*|MmKWyb4tX9vTZPm~ zWU$b_b23+m(PjnhfCf@(z;HQ|%TG8^4`%CsK7tnlwrHO!jU*EZ zS1Rs9BP8Sm8o@bi8+OWWTgvUio#cqmU? z*`+4hS0k&7qNT7y6ZXR~%y+bWHf!iL5r>^qzHEPN!r~HHDhH$Rx@|FMsuAV(Y8Bl= zoJTGbL((u(l}qu=uAa=N2LgwWPRj@PkLGBJdMMgI7VaO#8LV*;js*Sp@pv9=cblti zy;mg7>eSjkU|M@4v3_TR2H*xqUT{;`j4d+yyccg?QBoT%wEYwh(070RdP-L z@3-32-67%Y$bCNUjq#h*^hBaLFD;vf9#D3(x7(eyM#QEcwrp|1O~XM^l4ZlceIRV#$9(_T$l#|19|r z-N3L!5ItmJ-r?%%aH_*qlK_`1|7oJP<^M@W1#q)E0_+D40QUg&=Q{{=13kbY;4mQk ze=~uIV zrm-xR|KIB9bC&;KtKUetIK13AuINK#`G2Xu0CV9+7_11E{q3vMkXv6-meC)0<#C*I zKRvWEEO4*T+r+e2+lgo@!b-K`X#ERp>Q{Hoe5qyWS3is+*7E<$DSo`GIUOggXRBc(*Mx%|Klh8EdM|HB3k}`=hs8d^8XuyKb6rB(enRm z$q~`=|2uCEntJ~~%l>zH_P;l3_P^`l0c83A(ErNv|Is;J)pDv?)BZo5T^$b1RVhUu z5$c1+YIt?1U)X@>hxYpcmj4f|{Vt;aPwSc^>-XP!6_2CU{rhifD_&|Zg!7?=<}VeA zz%OEK1VSs}TNGQ$9+V9n#u_|cd)nu<)B7&f?|*z4y`27M{2c-PRtA6X*i5kezuw#a zU+07KTKj*h$A<9JxcFW?%a!7+`M;@;Vl}6WvgZE=zahOAdf#fD|NBbm$zJtqH07WC zakx$Y=8xPt;vVsgI2=j09Pf;8;P({v*7)@@YT9%^U8W;#kCt-$APqm3!xV11lX0Wk z?Ba=ymGjTMxHD!=J<7IK0B?Q9iEN$_^+_ z@2WSsqi*6{wfviA^}W`7;MFyl`R{p%PgL-0q2+Iauso<&E4$Jl&RV&e(%%V z&)C=c8FqZ>1Nl7Nr2T&7_Y10jbD+6jqgQ{~t(LZJX?!>?*qv*f+>{Qv&}uzviH diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 6868914584..643da763f1 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -25,7 +25,6 @@ */ #include "FX.h" -#include "tv_colors.h" #define IBN 5100 #define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3) @@ -35,7 +34,7 @@ */ uint16_t WS2812FX::mode_static(void) { fill(SEGCOLOR(0)); - return (SEGMENT.getOption(SEG_OPTION_TRANSITIONAL)) ? FRAMETIME : 500; //update faster if in transition + return (SEGMENT.getOption(SEG_OPTION_TRANSITIONAL)) ? FRAMETIME : 350; //update faster if in transition } @@ -445,28 +444,45 @@ uint16_t WS2812FX::mode_theater_chase_rainbow(void) { /* * Running lights effect with smooth sine transition base. */ -uint16_t WS2812FX::running_base(bool saw) { +uint16_t WS2812FX::running_base(bool saw, bool dual=false) { uint8_t x_scale = SEGMENT.intensity >> 2; uint32_t counter = (now * SEGMENT.speed) >> 9; for(uint16_t i = 0; i < SEGLEN; i++) { - uint8_t s = 0; - uint8_t a = i*x_scale - counter; + uint16_t a = i*x_scale - counter; if (saw) { + a &= 0xFF; if (a < 16) { a = 192 + a*8; } else { a = map(a,16,255,64,192); } + a = 255 - a; } - s = sin8(a); - setPixelColor(i, color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), s)); + uint8_t s = dual ? sin_gap(a) : sin8(a); + uint32_t ca = color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s); + if (dual) { + uint16_t b = (SEGLEN-1-i)*x_scale - counter; + uint8_t t = sin_gap(b); + uint32_t cb = color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), t); + ca = color_blend(ca, cb, 127); + } + setPixelColor(i, ca); } return FRAMETIME; } +/* + * Running lights in opposite directions. + * Idea: Make the gap width controllable with a third slider in the future + */ +uint16_t WS2812FX::mode_running_dual(void) { + return running_base(false, true); +} + + /* * Running lights effect with smooth sine transition. */ @@ -1335,14 +1351,6 @@ uint16_t WS2812FX::tricolor_chase(uint32_t color1, uint32_t color2) { } -/* - * Alternating white/red/black pixels running. PLACEHOLDER - */ -uint16_t WS2812FX::mode_circus_combustus(void) { - return tricolor_chase(RED, WHITE); -} - - /* * Tricolor chase mode */ @@ -1459,8 +1467,8 @@ uint16_t WS2812FX::mode_tricolor_fade(void) } byte stp = prog; // % 256 - uint32_t color = 0; for(uint16_t i = 0; i < SEGLEN; i++) { + uint32_t color; if (stage == 2) { color = color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), color2, stp); } else if (stage == 1) { @@ -3108,7 +3116,7 @@ uint16_t WS2812FX::mode_drip(void) drops[j].vel += gravity; for (uint16_t i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets - uint16_t pos = uint16_t(drops[j].pos) +i; //this is BAD, returns a pos >= SEGLEN occasionally + uint16_t pos = constrain(uint16_t(drops[j].pos) +i, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally setPixelColor(pos,color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling } @@ -3818,65 +3826,99 @@ uint16_t WS2812FX::mode_blends(void) { return FRAMETIME; } -#ifndef WLED_DISABLE_FX_HIGH_FLASH_USE typedef struct TvSim { uint32_t totalTime = 0; uint32_t fadeTime = 0; uint32_t startTime = 0; uint32_t elapsed = 0; uint32_t pixelNum = 0; + uint16_t sliderValues = 0; + uint32_t sceeneStart = 0; + uint32_t sceeneDuration = 0; + uint16_t sceeneColorHue = 0; + uint8_t sceeneColorSat = 0; + uint8_t sceeneColorBri = 0; + uint8_t actualColorR = 0; + uint8_t actualColorG = 0; + uint8_t actualColorB = 0; uint16_t pr = 0; // Prev R, G, B uint16_t pg = 0; uint16_t pb = 0; } tvSim; -#define numTVPixels (sizeof(tv_colors) / 2) // 2 bytes per Pixel (5/6/5) -#endif /* TV Simulator Modified and adapted to WLED by Def3nder, based on "Fake TV Light for Engineers" by Phillip Burgess https://learn.adafruit.com/fake-tv-light-for-engineers/arduino-sketch */ uint16_t WS2812FX::mode_tv_simulator(void) { - #ifdef WLED_DISABLE_FX_HIGH_FLASH_USE - return mode_static(); - #else - uint16_t nr, ng, nb, r, g, b, i; - uint8_t hi, lo, r8, g8, b8; + uint16_t nr, ng, nb, r, g, b, i, hue; + uint8_t sat, bri, j; if (!SEGENV.allocateData(sizeof(tvSim))) return mode_static(); //allocation failed TvSim* tvSimulator = reinterpret_cast(SEGENV.data); - // initialize start of the TV-Colors - if (SEGENV.call == 0) { - tvSimulator->pixelNum = ((uint8_t)random(18)) * numTVPixels / 18; // Begin at random movie (18 in total) - } + uint8_t colorSpeed = map(SEGMENT.speed, 0, UINT8_MAX, 1, 20); + uint8_t colorIntensity = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 30); - // Read next 16-bit (5/6/5) color - hi = pgm_read_byte(&tv_colors[tvSimulator->pixelNum * 2 ]); - lo = pgm_read_byte(&tv_colors[tvSimulator->pixelNum * 2 + 1]); + i = SEGMENT.speed << 8 | SEGMENT.intensity; + if (i != tvSimulator->sliderValues) { + tvSimulator->sliderValues = i; + SEGENV.aux1 = 0; + } - // Expand to 24-bit (8/8/8) - r8 = (hi & 0xF8) | (hi >> 5); - g8 = ((hi << 5) & 0xff) | ((lo & 0xE0) >> 3) | ((hi & 0x06) >> 1); - b8 = ((lo << 3) & 0xff) | ((lo & 0x1F) >> 2); + // create a new sceene + if (((millis() - tvSimulator->sceeneStart) >= tvSimulator->sceeneDuration) || SEGENV.aux1 == 0) { + tvSimulator->sceeneStart = millis(); // remember the start of the new sceene + tvSimulator->sceeneDuration = random16(60* 250* colorSpeed, 60* 750 * colorSpeed); // duration of a "movie sceene" which has similar colors (5 to 15 minutes with max speed slider) + tvSimulator->sceeneColorHue = random16( 0, 768); // random start color-tone for the sceene + tvSimulator->sceeneColorSat = random8 ( 100, 130 + colorIntensity); // random start color-saturation for the sceene + tvSimulator->sceeneColorBri = random8 ( 200, 240); // random start color-brightness for the sceene + SEGENV.aux1 = 1; + SEGENV.aux0 = 0; + } - // Apply gamma correction, further expand to 16/16/16 - nr = (uint8_t)gamma8(r8) * 257; // New R/G/B - ng = (uint8_t)gamma8(g8) * 257; - nb = (uint8_t)gamma8(b8) * 257; + // slightly change the color-tone in this sceene + if ( SEGENV.aux0 == 0) { + // hue change in both directions + j = random8(4 * colorIntensity); + hue = (random8() < 128) ? ((j < tvSimulator->sceeneColorHue) ? tvSimulator->sceeneColorHue - j : 767 - tvSimulator->sceeneColorHue - j) : // negative + ((j + tvSimulator->sceeneColorHue) < 767 ? tvSimulator->sceeneColorHue + j : tvSimulator->sceeneColorHue + j - 767) ; // positive + + // saturation + j = random8(2 * colorIntensity); + sat = (tvSimulator->sceeneColorSat - j) < 0 ? 0 : tvSimulator->sceeneColorSat - j; + + // brightness + j = random8(100); + bri = (tvSimulator->sceeneColorBri - j) < 0 ? 0 : tvSimulator->sceeneColorBri - j; + + // calculate R,G,B from HSV + // Source: https://blog.adafruit.com/2012/03/14/constant-brightness-hsb-to-rgb-algorithm/ + { // just to create a local scope for the variables + uint8_t temp[5], n = (hue >> 8) % 3; + uint8_t x = ((((hue & 255) * sat) >> 8) * bri) >> 8; + uint8_t s = ( (256 - sat) * bri) >> 8; + temp[0] = temp[3] = s; + temp[1] = temp[4] = x + s; + temp[2] = bri - x; + tvSimulator->actualColorR = temp[n + 2]; + tvSimulator->actualColorG = temp[n + 1]; + tvSimulator->actualColorB = temp[n ]; + } + } + // Apply gamma correction, further expand to 16/16/16 + nr = (uint8_t)gamma8(tvSimulator->actualColorR) * 257; // New R/G/B + ng = (uint8_t)gamma8(tvSimulator->actualColorG) * 257; + nb = (uint8_t)gamma8(tvSimulator->actualColorB) * 257; if (SEGENV.aux0 == 0) { // initialize next iteration SEGENV.aux0 = 1; - // increase color-index for next loop - tvSimulator->pixelNum++; - if (tvSimulator->pixelNum >= numTVPixels) tvSimulator->pixelNum = 0; - // randomize total duration and fade duration for the actual color - tvSimulator->totalTime = random(250, 2500); // Semi-random pixel-to-pixel time - tvSimulator->fadeTime = random(0, tvSimulator->totalTime); // Pixel-to-pixel transition time - if (random(10) < 3) tvSimulator->fadeTime = 0; // Force scene cut 30% of time + tvSimulator->totalTime = random16(250, 2500); // Semi-random pixel-to-pixel time + tvSimulator->fadeTime = random16(0, tvSimulator->totalTime); // Pixel-to-pixel transition time + if (random8(10) < 3) tvSimulator->fadeTime = 0; // Force scene cut 30% of time tvSimulator->startTime = millis(); } // end of initialization @@ -3909,7 +3951,6 @@ uint16_t WS2812FX::mode_tv_simulator(void) { } return FRAMETIME; - #endif } /* @@ -4777,7 +4818,7 @@ uint16_t WS2812FX::mode_binmap(void) { // Binmap. Scale raw f ////////////////////// uint16_t WS2812FX::mode_blurz(void) { // Blurz. By Andrew Tuline. - + CRGB *leds = (CRGB*) ledData; if (SEGENV.call == 0) {fill_solid(leds,SEGLEN, 0); SEGENV.aux0 = 0; } diff --git a/wled00/FX.h b/wled00/FX.h index d25655c132..6877b52566 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -51,9 +51,6 @@ #define MAX(a,b) ((a)>(b)?(a):(b)) #endif -/* Disable effects with high flash memory usage (currently TV simulator) - saves 18.5kB */ -//#define WLED_DISABLE_FX_HIGH_FLASH_USE - /* Not used in all effects yet */ #define WLED_FPS 42 #define FRAMETIME (1000/WLED_FPS) @@ -171,7 +168,7 @@ #define FX_MODE_POLICE_ALL 49 #define FX_MODE_TWO_DOTS 50 #define FX_MODE_TWO_AREAS 51 -#define FX_MODE_CIRCUS_COMBUSTUS 52 +#define FX_MODE_RUNNING_DUAL 52 #define FX_MODE_HALLOWEEN 53 #define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_WIPE 55 @@ -547,7 +544,7 @@ class WS2812FX { _mode[FX_MODE_POLICE_ALL] = &WS2812FX::mode_police_all; _mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots; _mode[FX_MODE_TWO_AREAS] = &WS2812FX::mode_two_areas; - _mode[FX_MODE_CIRCUS_COMBUSTUS] = &WS2812FX::mode_circus_combustus; + _mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual; _mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween; _mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase; _mode[FX_MODE_TRICOLOR_WIPE] = &WS2812FX::mode_tricolor_wipe; @@ -695,6 +692,7 @@ class WS2812FX { bool isRgbw = false, + isOffRefreshRequred = false, //periodic refresh is required for the strip to remain off. gammaCorrectBri = false, gammaCorrectCol = true, applyToAllSelected = true, @@ -721,6 +719,7 @@ class WS2812FX { getColorOrder(void), gamma8(uint8_t), gamma8_cal(uint8_t, float), + sin_gap(uint16_t), get_random_wheel_index(uint8_t); int8_t @@ -822,7 +821,7 @@ class WS2812FX { mode_police_all(void), mode_two_dots(void), mode_two_areas(void), - mode_circus_combustus(void), + mode_running_dual(void), mode_bicolor_chase(void), mode_tricolor_chase(void), mode_tricolor_wipe(void), @@ -958,7 +957,7 @@ class WS2812FX { dynamic(bool), scan(bool), theater_chase(uint32_t, uint32_t, bool), - running_base(bool), + running_base(bool,bool), larson_scanner(bool), sinelon_base(bool,bool), dissolve(uint32_t), @@ -1012,7 +1011,7 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Sparkle","Sparkle Dark","Sparkle+","Strobe","Strobe Rainbow","Strobe Mega","Blink Rainbow","Android","Chase","Chase Random", "Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Aurora","Stream", "Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Police All", -"Two Dots","Two Areas","Circus","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet", +"Two Dots","Two Areas","Running Dual","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet", "Scanner Dual","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise", "Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple", "Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst", diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 8aaebcf2f6..2c38d0ea2e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -43,6 +43,23 @@ 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25] */ +//factory defaults LED setup +//#define PIXEL_COUNTS 30, 30, 30, 30 +//#define DATA_PINS 16, 1, 3, 4 +//#define DEFAULT_LED_TYPE TYPE_WS2812_RGB + +#ifndef PIXEL_COUNTS + #define PIXEL_COUNTS 30 +#endif + +#ifndef DATA_PINS + #define DATA_PINS LEDPIN +#endif + +#ifndef DEFAULT_LED_TYPE + #define DEFAULT_LED_TYPE TYPE_WS2812_RGB +#endif + //do not call this method from system context (network callback) void WS2812FX::finalizeInit(uint16_t countPixels, bool skipFirst) { @@ -57,9 +74,22 @@ void WS2812FX::finalizeInit(uint16_t countPixels, bool skipFirst) //if busses failed to load, add default (FS issue...) if (busses.getNumBusses() == 0) { - uint8_t defPin[] = {LEDPIN}; - BusConfig defCfg = BusConfig(TYPE_WS2812_RGB, defPin, 0, _lengthRaw, COL_ORDER_GRB); - busses.add(defCfg); + const uint8_t defDataPins[] = {DATA_PINS}; + const uint16_t defCounts[] = {PIXEL_COUNTS}; + const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0])); + const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); + uint16_t prevLen = 0; + for (uint8_t i = 0; i < defNumBusses; i++) { + uint8_t defPin[] = {defDataPins[i]}; + uint16_t start = prevLen; + uint16_t count = _lengthRaw; + if (defNumBusses > 1 && defNumCounts) { + count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; + } + prevLen += count; + BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, COL_ORDER_GRB); + busses.add(defCfg); + } } deserializeMap(); @@ -192,14 +222,14 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) for (uint16_t j = 0; j < SEGMENT.grouping; j++) { int indexSet = realIndex + (reversed ? -j : j); - if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) { - busses.setPixelColor(indexSet + skip, col); if (IS_MIRROR) { //set the corresponding mirrored pixel uint16_t indexMir = SEGMENT.stop - indexSet + SEGMENT.start - 1; if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir]; busses.setPixelColor(indexMir + skip, col); } + if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; + busses.setPixelColor(indexSet + skip, col); } } } else { //live data, etc. @@ -771,6 +801,12 @@ uint16_t WS2812FX::triwave16(uint16_t in) return 0xFFFF - (in - 0x8000)*2; } +uint8_t WS2812FX::sin_gap(uint16_t in) { + if (in & 0x100) return 0; + //if (in > 255) return 0; + return sin8(in + 192); //correct phase shift of sine so that it starts and stops at 0 +} + /* * Generates a tristate square wave w/ attac & decay * @param x input value 0-255 diff --git a/wled00/__vm/.wled00.vsarduino.h b/wled00/__vm/.wled00.vsarduino.h deleted file mode 100644 index 2a9ef40a43..0000000000 --- a/wled00/__vm/.wled00.vsarduino.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - Editor: https://www.visualmicro.com/ - This file is for intellisense purpose only. - Visual micro (and the arduino ide) ignore this code during compilation. This code is automatically maintained by visualmicro, manual changes to this file will be overwritten - The contents of the _vm sub folder can be deleted prior to publishing a project - All non-arduino files created by visual micro and all visual studio project or solution files can be freely deleted and are not required to compile a sketch (do not delete your own code!). - Note: debugger breakpoints are stored in '.sln' or '.asln' files, knowledge of last uploaded breakpoints is stored in the upload.vmps.xml file. Both files are required to continue a previous debug session without needing to compile and upload again - - Hardware: ESP32 Dev Module, Platform=esp32, Package=esp32 -*/ - -#if defined(_VMICRO_INTELLISENSE) - -#ifndef _VSARDUINO_H_ -#define _VSARDUINO_H_ -#define __ESP32_esp32__ -#define __ESP32_ESP32__ -#define ESP_PLATFORM -#define HAVE_CONFIG_H -#define GCC_NOT_5_2_0 0 -#define WITH_POSIX -#define F_CPU 240000000L -#define ARDUINO 108011 -#define ARDUINO_ESP32_DEV -#define ARDUINO_ARCH_ESP32 -#define ESP32 -#define CORE_DEBUG_LEVEL 0 -#define __cplusplus 201103L - -#define _Pragma(x) -#undef __cplusplus -#define __cplusplus 201103L - -#define __STDC__ -#define __ARM__ -#define __arm__ -#define __inline__ -#define __asm__(...) -#define __extension__ -#define __ATTR_PURE__ -#define __ATTR_CONST__ -#define __volatile__ - -#define __ASM -#define __INLINE -#define __attribute__(noinline) - -//#define _STD_BEGIN -//#define EMIT -#define WARNING -#define _Lockit -#define __CLR_OR_THIS_CALL -#define C4005 -#define _NEW - -typedef bool _Bool; -typedef int _read; -typedef int _seek; -typedef int _write; -typedef int _close; -typedef int __cleanup; - -//#define inline - -#define __builtin_clz -#define __builtin_clzl -#define __builtin_clzll -#define __builtin_labs -#define __builtin_va_list -typedef int __gnuc_va_list; - -#define __ATOMIC_ACQ_REL - -#define __CHAR_BIT__ -#define _EXFUN() - -typedef unsigned char byte; -extern "C" void __cxa_pure_virtual() {;} - -typedef long __INTPTR_TYPE__ ; -typedef long __UINTPTR_TYPE__ ; -typedef long __SIZE_TYPE__ ; -typedef long __PTRDIFF_TYPE__; - -typedef long pthread_t; -typedef long pthread_key_t; -typedef long pthread_once_t; -typedef long pthread_mutex_t; -typedef long pthread_mutex_t; -typedef long pthread_cond_t; - - - -#include "arduino.h" -#include - -#define interrupts() sei() -#define noInterrupts() cli() - -#define ESP_LOGI(tag, ...) - -#include "wled00.ino" -#include "wled01_eeprom.ino" -#include "wled02_xml.ino" -#include "wled03_set.ino" -#include "wled04_file.ino" -#include "wled05_init.ino" -#include "wled06_usermod.ino" -#include "wled07_notify.ino" -#include "wled08_led.ino" -#include "wled09_button.ino" -#include "wled10_ntp.ino" -#include "wled11_ol.ino" -#include "wled12_alexa.ino" -#include "wled13_cronixie.ino" -#include "wled14_colors.ino" -#include "wled15_hue.ino" -#include "wled16_blynk.ino" -#include "wled17_mqtt.ino" -#include "wled18_server.ino" -#include "wled19_json.ino" -#include "wled20_ir.ino" -#endif -#endif diff --git a/wled00/__vm/Compile.vmps.xml b/wled00/__vm/Compile.vmps.xml deleted file mode 100644 index 6390398ede..0000000000 --- a/wled00/__vm/Compile.vmps.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/wled00/__vm/Configuration.Release.vmps.xml b/wled00/__vm/Configuration.Release.vmps.xml deleted file mode 100644 index bcbca636a5..0000000000 --- a/wled00/__vm/Configuration.Release.vmps.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 7d74d055b1..e2c35c8215 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -399,6 +399,11 @@ class BusManager { return false; } + //Return true if the strip requires a refresh to stay off. + static bool isOffRefreshRequred(uint8_t type) { + return type == TYPE_TM1814; + } + private: uint8_t numBusses = 0; Bus* busses[WLED_MAX_BUSSES]; diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 445d2a439c..e60ef56d0b 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -28,7 +28,7 @@ #define I_8266_U1_400_3 10 #define I_8266_DM_400_3 11 #define I_8266_BB_400_3 12 -//TM1418 (RGBW) +//TM1814 (RGBW) #define I_8266_U0_TM1_4 13 #define I_8266_U1_TM1_4 14 #define I_8266_DM_TM1_4 15 @@ -68,7 +68,7 @@ #define I_32_R7_400_3 44 #define I_32_I0_400_3 45 #define I_32_I1_400_3 46 -//TM1418 (RGBW) +//TM1814 (RGBW) #define I_32_R0_TM1_4 47 #define I_32_R1_TM1_4 48 #define I_32_R2_TM1_4 49 @@ -115,7 +115,7 @@ #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) -//TM1418 (RGBW) +//TM1814 (RGBW) #define B_8266_U0_TM1_4 NeoPixelBrightnessBus #define B_8266_U1_TM1_4 NeoPixelBrightnessBus #define B_8266_DM_TM1_4 NeoPixelBrightnessBus @@ -157,7 +157,7 @@ #define B_32_R7_400_3 NeoPixelBrightnessBus #define B_32_I0_400_3 NeoPixelBrightnessBus #define B_32_I1_400_3 NeoPixelBrightnessBus -//TM1418 (RGBW) +//TM1814 (RGBW) #define B_32_R0_TM1_4 NeoPixelBrightnessBus #define B_32_R1_TM1_4 NeoPixelBrightnessBus #define B_32_R2_TM1_4 NeoPixelBrightnessBus @@ -191,6 +191,14 @@ //handles pointer type conversion for all possible bus types class PolyBus { public: + // Begin & initialize the PixelSettings for TM1814 strips. + template + static void beginTM1814(void* busPtr) { + T tm1814_strip = static_cast(busPtr); + tm1814_strip->Begin(); + // 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) { switch (busType) { case I_NONE: break; @@ -207,10 +215,10 @@ class PolyBus { case I_8266_U1_400_3: (static_cast(busPtr))->Begin(); break; case I_8266_DM_400_3: (static_cast(busPtr))->Begin(); break; case I_8266_BB_400_3: (static_cast(busPtr))->Begin(); break; - case I_8266_U0_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_8266_U1_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_8266_DM_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_8266_BB_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_TM1_4: beginTM1814(busPtr); break; + case I_8266_U1_TM1_4: beginTM1814(busPtr); break; + case I_8266_DM_TM1_4: beginTM1814(busPtr); break; + case I_8266_BB_TM1_4: beginTM1814(busPtr); break; case I_HS_DOT_3: (static_cast(busPtr))->Begin(); break; case I_HS_LPD_3: (static_cast(busPtr))->Begin(); break; case I_HS_WS1_3: (static_cast(busPtr))->Begin(); break; @@ -247,16 +255,16 @@ class PolyBus { case I_32_R7_400_3: (static_cast(busPtr))->Begin(); break; case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break; case I_32_I1_400_3: (static_cast(busPtr))->Begin(); break; - case I_32_R0_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_32_R1_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_32_R2_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_32_R3_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_32_R4_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_32_R5_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_32_R6_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_32_R7_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_32_I0_TM1_4: (static_cast(busPtr))->Begin(); break; - case I_32_I1_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_R0_TM1_4: beginTM1814(busPtr); break; + case I_32_R1_TM1_4: beginTM1814(busPtr); break; + case I_32_R2_TM1_4: beginTM1814(busPtr); break; + case I_32_R3_TM1_4: beginTM1814(busPtr); break; + case I_32_R4_TM1_4: beginTM1814(busPtr); break; + case I_32_R5_TM1_4: beginTM1814(busPtr); break; + case I_32_R6_TM1_4: beginTM1814(busPtr); break; + case I_32_R7_TM1_4: beginTM1814(busPtr); break; + case I_32_I0_TM1_4: beginTM1814(busPtr); break; + case I_32_I1_TM1_4: beginTM1814(busPtr); 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; @@ -860,6 +868,8 @@ class PolyBus { return I_8266_U0_NEO_4 + offset; case TYPE_WS2811_400KHZ: return I_8266_U0_400_3 + offset; + case TYPE_TM1814: + return I_8266_U0_TM1_4 + offset; } #else //ESP32 uint8_t offset = num; //RMT bus # == bus index in BusManager @@ -872,6 +882,8 @@ class PolyBus { return I_32_R0_NEO_4 + offset; case TYPE_WS2811_400KHZ: return I_32_R0_400_3 + offset; + case TYPE_TM1814: + return I_32_R0_TM1_4 + offset; } #endif } diff --git a/wled00/button.cpp b/wled00/button.cpp index 22e71a69bc..c263a66df6 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -4,6 +4,8 @@ * Physical IO */ +#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing) + void shortPressAction() { if (!macroButton) @@ -25,10 +27,42 @@ bool isButtonPressed() } +void handleSwitch() +{ + if (buttonPressedBefore != isButtonPressed()) { + buttonPressedTime = millis(); + buttonPressedBefore = !buttonPressedBefore; + } + + if (buttonLongPressed == buttonPressedBefore) return; + + if (millis() - buttonPressedTime > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) + if (buttonPressedBefore) { //LOW, falling edge, switch closed + if (macroButton) applyPreset(macroButton); + else { //turn on + if (!bri) {toggleOnOff(); colorUpdated(NOTIFIER_CALL_MODE_BUTTON);} + } + } else { //HIGH, rising edge, switch opened + if (macroLongPress) applyPreset(macroLongPress); + else { //turn off + if (bri) {toggleOnOff(); colorUpdated(NOTIFIER_CALL_MODE_BUTTON);} + } + } + buttonLongPressed = buttonPressedBefore; //save the last "long term" switch state + } +} + + void handleButton() { - if (btnPin<0 || !buttonEnabled) return; + if (btnPin<0 || buttonType < BTN_TYPE_PUSH) return; + + + if (buttonType == BTN_TYPE_SWITCH) { //button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NO gpio0) + handleSwitch(); return; + } + //momentary button logic if (isButtonPressed()) //pressed { if (!buttonPressedBefore) buttonPressedTime = millis(); @@ -48,7 +82,7 @@ void handleButton() else if (!isButtonPressed() && buttonPressedBefore) //released { long dur = millis() - buttonPressedTime; - if (dur < 50) {buttonPressedBefore = false; return;} //too short "press", debounce + if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore = false; return;} //too short "press", debounce bool doublePress = buttonWaitTime; buttonWaitTime = 0; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 391d8cac35..21eea22977 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -12,24 +12,7 @@ void getStringFromJson(char* dest, const char* src, size_t len) { if (src != nullptr) strlcpy(dest, src, len); } -void deserializeConfig() { - bool fromeep = false; - bool success = deserializeConfigSec(); - if (!success) { //if file does not exist, try reading from EEPROM - deEEPSettings(); - fromeep = true; - } - - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - - DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); - - success = readObjectFromFile("/cfg.json", nullptr, &doc); - if (!success) { //if file does not exist, try reading from EEPROM - if (!fromeep) deEEPSettings(); - return; - } - +bool deserializeConfig(JsonObject doc, bool fromFS) { //int rev_major = doc["rev"][0]; // 1 //int rev_minor = doc["rev"][1]; // 0 @@ -107,52 +90,58 @@ void deserializeConfig() { CJSON(strip.matrixSerpentine, hw_led[F("mxs")]); JsonArray ins = hw_led["ins"]; - uint8_t s = 0; //bus iterator - strip.isRgbw = false; - busses.removeAll(); - uint32_t mem = 0; - for (JsonObject elm : ins) { - if (s >= WLED_MAX_BUSSES) break; - uint8_t pins[5] = {255, 255, 255, 255, 255}; - JsonArray pinArr = elm[F("pin")]; - if (pinArr.size() == 0) continue; - pins[0] = pinArr[0]; - uint8_t i = 0; - for (int p : pinArr) { - pins[i] = p; - i++; - if (i>4) break; + if (fromFS || !ins.isNull()) { + uint8_t s = 0; //bus iterator + strip.isRgbw = false; + busses.removeAll(); + uint32_t mem = 0; + for (JsonObject elm : ins) { + if (s >= WLED_MAX_BUSSES) break; + uint8_t pins[5] = {255, 255, 255, 255, 255}; + JsonArray pinArr = elm[F("pin")]; + if (pinArr.size() == 0) continue; + pins[0] = pinArr[0]; + uint8_t i = 0; + for (int p : pinArr) { + pins[i] = p; + i++; + if (i>4) break; + } + + uint16_t length = elm[F("len")]; + if (length==0) continue; + uint8_t colorOrder = (int)elm[F("order")]; + //only use skip from the first strip (this shouldn't have been in ins obj. but remains here for compatibility) + if (s==0) skipFirstLed = elm[F("skip")]; + uint16_t start = elm[F("start")] | 0; + if (start >= ledCount) continue; + //limit length of strip if it would exceed total configured LEDs + if (start + length > ledCount) length = ledCount - start; + uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; + bool reversed = elm["rev"]; + //RGBW mode is enabled if at least one of the strips is RGBW + strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(ledType)); + //refresh is required to remain off if at least one of the strips requires the refresh. + strip.isOffRefreshRequred |= BusManager::isOffRefreshRequred(ledType); + s++; + BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed); + mem += busses.memUsage(bc); + if (mem <= MAX_LED_MEMORY) busses.add(bc); } - - uint16_t length = elm[F("len")]; - if (length==0) continue; - uint8_t colorOrder = (int)elm[F("order")]; - //only use skip from the first strip (this shouldn't have been in ins obj. but remains here for compatibility) - if (s==0) skipFirstLed = elm[F("skip")]; - uint16_t start = elm[F("start")] | 0; - if (start >= ledCount) continue; - //limit length of strip if it would exceed total configured LEDs - if (start + length > ledCount) length = ledCount - start; - uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; - bool reversed = elm["rev"]; - //RGBW mode is enabled if at least one of the strips is RGBW - strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(ledType)); - s++; - BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed); - mem += busses.memUsage(bc); - if (mem <= MAX_LED_MEMORY) busses.add(bc); + strip.finalizeInit(ledCount, skipFirstLed); } - strip.finalizeInit(ledCount, skipFirstLed); if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus JsonObject hw_btn_ins_0 = hw[F("btn")][F("ins")][0]; - CJSON(buttonEnabled, hw_btn_ins_0["type"]); - int hw_btn_pin = hw_btn_ins_0[F("pin")][0]; - if (pinManager.allocatePin(hw_btn_pin,false)) { - btnPin = hw_btn_pin; - pinMode(btnPin, INPUT_PULLUP); - } else { - btnPin = -1; + CJSON(buttonType, hw_btn_ins_0["type"]); + int hw_btn_pin = hw_btn_ins_0[F("pin")][0] | -2; //-2 = not present in doc, keep current. -1 = disable + if (hw_btn_pin > -2) { + if (pinManager.allocatePin(hw_btn_pin,false)) { + btnPin = hw_btn_pin; + pinMode(btnPin, INPUT_PULLUP); + } else { + btnPin = -1; + } } JsonArray hw_btn_ins_0_macros = hw_btn_ins_0[F("macros")]; @@ -160,26 +149,27 @@ void deserializeConfig() { CJSON(macroLongPress,hw_btn_ins_0_macros[1]); CJSON(macroDoublePress, hw_btn_ins_0_macros[2]); - //int hw_btn_ins_0_type = hw_btn_ins_0["type"]; // 0 - #ifndef WLED_DISABLE_INFRARED - int hw_ir_pin = hw["ir"]["pin"] | -1; // 4 - if (pinManager.allocatePin(hw_ir_pin,false)) { - irPin = hw_ir_pin; - } else { - irPin = -1; + int hw_ir_pin = hw["ir"]["pin"] | -2; // 4 + if (hw_ir_pin > -2) { + if (pinManager.allocatePin(hw_ir_pin,false)) { + irPin = hw_ir_pin; + } else { + irPin = -1; + } } #endif CJSON(irEnabled, hw["ir"]["type"]); JsonObject relay = hw[F("relay")]; - - int hw_relay_pin = relay["pin"]; - if (pinManager.allocatePin(hw_relay_pin,true)) { - rlyPin = hw_relay_pin; - pinMode(rlyPin, OUTPUT); - } else { - rlyPin = -1; + int hw_relay_pin = relay["pin"] | -2; + if (hw_relay_pin > -2) { + if (pinManager.allocatePin(hw_relay_pin,true)) { + rlyPin = hw_relay_pin; + pinMode(rlyPin, OUTPUT); + } else { + rlyPin = -1; + } } if (relay.containsKey("rev")) { rlyMde = !relay["rev"]; @@ -238,12 +228,13 @@ void deserializeConfig() { CJSON(fadeTransition, light_tr[F("mode")]); int tdd = light_tr[F("dur")] | -1; if (tdd >= 0) transitionDelayDefault = tdd * 100; - CJSON(strip.paletteFade, light_tr[F("pal")]); + CJSON(strip.paletteFade, light_tr["pal"]); JsonObject light_nl = light["nl"]; CJSON(nightlightMode, light_nl[F("mode")]); + byte prev = nightlightDelayMinsDefault; CJSON(nightlightDelayMinsDefault, light_nl[F("dur")]); - nightlightDelayMins = nightlightDelayMinsDefault; + if (nightlightDelayMinsDefault != prev) nightlightDelayMins = nightlightDelayMinsDefault; CJSON(nightlightTargetBri, light_nl[F("tbri")]); CJSON(macroNl, light_nl[F("macro")]); @@ -273,11 +264,13 @@ void deserializeConfig() { CJSON(receiveNotificationBrightness, if_sync_recv["bri"]); CJSON(receiveNotificationColor, if_sync_recv["col"]); CJSON(receiveNotificationEffects, if_sync_recv[F("fx")]); + //! following line might be a problem if called after boot receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); JsonObject if_sync_send = if_sync["send"]; + prev = notifyDirectDefault; CJSON(notifyDirectDefault, if_sync_send[F("dir")]); - notifyDirect = notifyDirectDefault; + if (notifyDirectDefault != prev) notifyDirect = notifyDirectDefault; CJSON(notifyButton, if_sync_send[F("btn")]); CJSON(notifyAlexa, if_sync_send[F("va")]); CJSON(notifyHue, if_sync_send[F("hue")]); @@ -357,9 +350,10 @@ void deserializeConfig() { // ol == overlay JsonObject ol = doc[F("ol")]; + prev = overlayDefault; CJSON(overlayDefault ,ol[F("clock")]); // 0 CJSON(countdownMode, ol[F("cntdwn")]); - overlayCurrent = overlayDefault; + if (prev != overlayDefault) overlayCurrent = overlayDefault; CJSON(overlayMin, ol[F("min")]); CJSON(overlayMax, ol[F("max")]); @@ -435,21 +429,46 @@ void deserializeConfig() { // Begin Sound Reactive specific settings - 1st attempt JsonObject sound = doc["snd"]; - JsonObject snd_cfg = sound[F("cfg")]; // Sound Reactive Configuration - CJSON(soundSquelch, snd_cfg[F("sq")]); - CJSON(sampleGain, snd_cfg[F("gn")]); + JsonObject snd_cfg = sound[F("cfg")]; // Sound Reactive Configuration + CJSON(soundSquelch, snd_cfg[F("sq")]); // sound squelch + CJSON(sampleGain, snd_cfg[F("gn")]); // gain - JsonObject snd_fft = sound[F("fft")]; // FFT Settings + JsonObject snd_fft = sound[F("fft")]; // FFT Settings CJSON(effectFFT1, snd_fft[F("f1")]); CJSON(effectFFT2, snd_fft[F("f2")]); CJSON(effectFFT3, snd_fft[F("f3")]); - JsonObject snd_sync = sound[F("sync")]; // Sound Reactive audio sync - CJSON(audioSyncPort, snd_sync[F("port")]); // 11988 + JsonObject snd_sync = sound[F("sync")]; // Sound Reactive audio sync + CJSON(audioSyncPort, snd_sync[F("port")]); // 11988 CJSON(audioSyncEnabled, snd_sync[F("en")]); JsonObject usermods_settings = doc["um"]; - usermods.readFromConfig(usermods_settings); + if (!usermods_settings.isNull()) usermods.readFromConfig(usermods_settings); + + if (fromFS) return false; + doReboot = doc[F("rb")] | doReboot; + return (doc["sv"] | true); +} + +void deserializeConfigFromFS() { + bool fromeep = false; + bool success = deserializeConfigSec(); + if (!success) { //if file does not exist, try reading from EEPROM + deEEPSettings(); + fromeep = true; + } + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); + + success = readObjectFromFile("/cfg.json", nullptr, &doc); + if (!success) { //if file does not exist, try reading from EEPROM + if (!fromeep) deEEPSettings(); + return; + } + + deserializeConfig(doc.as(), true); } void serializeConfig() { @@ -529,7 +548,6 @@ void serializeConfig() { Bus *bus = busses.getBus(s); if (!bus || bus->getLength()==0) break; JsonObject ins = hw_led_ins.createNestedObject(); - ins["en"] = true; ins[F("start")] = bus->getStart(); ins[F("len")] = bus->getLength(); JsonArray ins_pin = ins.createNestedArray("pin"); @@ -548,7 +566,7 @@ void serializeConfig() { // button BTNPIN JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject(); - hw_btn_ins_0["type"] = (buttonEnabled) ? BTN_TYPE_PUSH : BTN_TYPE_NONE; + hw_btn_ins_0["type"] = buttonType; JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin"); hw_btn_ins_0_pin.add(btnPin); @@ -594,7 +612,7 @@ void serializeConfig() { JsonObject light_tr = light.createNestedObject("tr"); light_tr[F("mode")] = fadeTransition; light_tr[F("dur")] = transitionDelayDefault / 100; - light_tr[F("pal")] = strip.paletteFade; + light_tr["pal"] = strip.paletteFade; JsonObject light_nl = light.createNestedObject("nl"); light_nl[F("mode")] = nightlightMode; @@ -754,17 +772,17 @@ void serializeConfig() { // Begin Sound Reactive specific settings - 1st attempt JsonObject sound = doc.createNestedObject("snd"); - JsonObject snd_cfg = sound.createNestedObject("cfg"); // Sound Reactive Configuration + JsonObject snd_cfg = sound.createNestedObject("cfg"); // Sound Reactive Configuration snd_cfg[F("sq")] = soundSquelch; snd_cfg[F("gn")] = sampleGain; - JsonObject snd_fft = sound.createNestedObject("fft"); // FFT Settings + JsonObject snd_fft = sound.createNestedObject("fft"); // FFT Settings snd_fft[F("f1")] = effectFFT1; snd_fft[F("f2")] = effectFFT2; snd_fft[F("f3")] = effectFFT3; JsonObject snd_sync = sound.createNestedObject("sync"); // Sound Reactive audio sync - snd_sync[F("port")] = audioSyncPort; // 11988 + snd_sync[F("port")] = audioSyncPort; // 11988 snd_sync[F("en")] = audioSyncEnabled; JsonObject usermods_settings = doc.createNestedObject("um"); diff --git a/wled00/const.h b/wled00/const.h index c404e3a391..ed1ceed598 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -11,12 +11,24 @@ #define DEFAULT_OTA_PASS "wledota" //increase if you need more -#define WLED_MAX_USERMODS 4 +#ifndef WLED_MAX_USERMODS + #ifdef ESP8266 + #define WLED_MAX_USERMODS 4 + #else + #define WLED_MAX_USERMODS 6 + #endif +#endif -#ifdef ESP8266 -#define WLED_MAX_BUSSES 3 -#else -#define WLED_MAX_BUSSES 10 +#ifndef WLED_MAX_BUSSES + #ifdef ESP8266 + #define WLED_MAX_BUSSES 3 + #else + #ifdef CONFIG_IDF_TARGET_ESP32S2 + #define WLED_MAX_BUSSES 5 + #else + #define WLED_MAX_BUSSES 10 + #endif + #endif #endif //Usermod IDs @@ -32,6 +44,8 @@ #define USERMOD_ID_AUTO_SAVE 9 //Usermod "usermod_v2_auto_save.h" #define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h" #define USERMOD_ID_MODE_SORT 11 //Usermod "usermod_v2_mode_sort.h" +#define USERMOD_ID_VL53L0X 12 //Usermod "usermod_vl53l0x_gestures.h" +#define USERMOD_ID_MULTI_RELAY 101 //Usermod "usermod_multi_relay.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -115,10 +129,10 @@ #define TYPE_LPD8806 52 #define TYPE_P9813 53 -#define IS_DIGITAL(t) (t & 0x10) //digital are 16-31 and 48-63 -#define IS_PWM(t) (t > 40 && t < 46) -#define NUM_PWM_PINS(t) (t - 40) //for analog PWM 41-45 only -#define IS_2PIN(t) (t > 47) +#define IS_DIGITAL(t) ((t) & 0x10) //digital are 16-31 and 48-63 +#define IS_PWM(t) ((t) > 40 && (t) < 46) +#define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only +#define IS_2PIN(t) ((t) > 47) //Color orders #define COL_ORDER_GRB 0 //GRB(w),defaut @@ -134,7 +148,7 @@ #define BTN_TYPE_RESERVED 1 #define BTN_TYPE_PUSH 2 #define BTN_TYPE_PUSH_ACT_HIGH 3 //not implemented -#define BTN_TYPE_SWITCH 4 //not implemented +#define BTN_TYPE_SWITCH 4 #define BTN_TYPE_SWITCH_ACT_HIGH 5 //not implemented //Ethernet board types @@ -247,7 +261,11 @@ //this is merely a default now and can be changed at runtime #ifndef LEDPIN -#define LEDPIN 2 +#ifdef ESP8266 + #define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards +#else + #define LEDPIN 16 // alligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards +#endif #endif #ifdef WLED_ENABLE_DMX @@ -258,4 +276,8 @@ #endif #endif +#ifndef DEFAULT_LED_COUNT + #define DEFAULT_LED_COUNT 30 +#endif + #endif diff --git a/wled00/data/index.js b/wled00/data/index.js index 4e0ce4dd87..9fb0fc3a04 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -151,13 +151,25 @@ function cTheme(light) { function loadBg(iUrl) { let bg = d.getElementById('bg'); - let img = d.createElement("img"); - img.src = iUrl; - if (iUrl == "") { - var today = new Date(); - if (today.getMonth() == 11 && (today.getDate() > 23 && today.getDate() < 28)) img.src = "https://aircoookie.github.io/xmas.png"; - else if (today.getMonth() == 3 && (today.getDate() > 3 && today.getDate() < 6)) img.src = "https://aircoookie.github.io/easter.png"; - } + let img = d.createElement("img"); + img.src = iUrl; + if (iUrl == "") { + var today = new Date(); + var hol = [ + [0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas + [0,2,17,1,"https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day + [2022,3,17,2,"https://aircoookie.github.io/easter.png"], + [2023,3,9,2,"https://aircoookie.github.io/easter.png"], + [2024,2,31,2,"https://aircoookie.github.io/easter.png"] + ]; + for (var i=0; i=hs && today<=he) img.src = hol[i][4]; + } + } img.addEventListener('load', (event) => { var a = parseFloat(cfg.theme.alpha.bg); if (isNaN(a)) a = 0.6; diff --git a/wled00/data/settings.htm b/wled00/data/settings.htm index 3a1a29e522..191c2835b9 100644 --- a/wled00/data/settings.htm +++ b/wled00/data/settings.htm @@ -10,17 +10,15 @@ margin: 0; } html { - --h: 10.45vh; + --h: 8.9vh; } button { background: #333; color: #fff; font-family: Verdana, Helvetica, sans-serif; - border: 0.3ch solid #333; + border: 1px solid #333; border-radius: 0.5em; - display: inline-block; - font-size: 7.75vmin; - line-height: 1em; + font-size: 6.5vmin; height: var(--h); width: 95%; margin-top: 2vh; @@ -44,6 +42,7 @@
+
\ No newline at end of file diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index f9d1a3a53c..22c4386e5b 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -39,7 +39,7 @@

Security & Update setup

Settings on this page are only changable if OTA lock is disabled!
Deny access to WiFi settings if locked:

Factory reset:
- All EEPROM content (settings) will be erased.

+ All settings and presets will be erased.

HTTP traffic is unencrypted. An attacker in the same network can intercept form data!

Software Update


diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 4289feb8e0..6a401fb544 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -16,7 +16,12 @@

Sync setup

Button setup

-On/Off button enabled:
+Button type: +
Infrared remote:
+

Updating...
Please do not close or refresh the page :)
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 7511503033..baedceb0d1 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -31,7 +31,8 @@ void handleButton(); void handleIO(); //cfg.cpp -void deserializeConfig(); +bool deserializeConfig(JsonObject doc, bool fromFS = false); +void deserializeConfigFromFS(); bool deserializeConfigSec(); void serializeConfig(); void serializeConfigSec(); @@ -155,6 +156,7 @@ void _overlayCronixie(); void _drawOverlayCronixie(); //playlist.cpp +void shufflePlaylist(); void unloadPlaylist(); void loadPlaylist(JsonObject playlistObject); void handlePlaylist(); @@ -191,6 +193,8 @@ class Usermod { virtual void readFromJsonState(JsonObject& obj) {} virtual void addToConfig(JsonObject& obj) {} virtual void readFromConfig(JsonObject& obj) {} + virtual void onMqttConnect(bool sessionPresent) {} + virtual bool onMqttMessage(char* topic, char* payload) { return false; } virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} }; @@ -211,7 +215,8 @@ class UsermodManager { void addToConfig(JsonObject& obj); void readFromConfig(JsonObject& obj); - + void onMqttConnect(bool sessionPresent); + bool onMqttMessage(char* topic, char* payload); bool add(Usermod* um); Usermod* lookup(uint16_t mod_id); byte getModCount(); diff --git a/wled00/file.cpp b/wled00/file.cpp index e3aac3ae00..90c0d629fa 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -55,13 +55,12 @@ bool bufferedFind(const char *target, bool fromStart = true) { size_t targetLen = strlen(target); size_t index = 0; - uint16_t bufsize = 0, count = 0; byte buf[FS_BUFSIZE]; if (fromStart) f.seek(0); while (f.position() < f.size() -1) { - bufsize = f.read(buf, FS_BUFSIZE); - count = 0; + uint16_t bufsize = f.read(buf, FS_BUFSIZE); + uint16_t count = 0; while (count < bufsize) { if(buf[count] != target[index]) index = 0; // reset index if any char does not match diff --git a/wled00/html_other.h b/wled00/html_other.h index f5d443440e..5d694be2ef 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -42,13 +42,13 @@ function B(){window.history.back()}function U(){document.getElementById("uf").st .bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none}

WLED Software Update

-Installed version: 0.12.0
Download the latest binary: Download the latest binary: -

-
Updating... -
Please do not close or refresh the page :)
)====="; +


Updating...
+Please do not close or refresh the page :)
)====="; // Autogenerated from wled00/data/welcome.htm, do not edit!! diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 0e63b14948..3ab205194f 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -12,7 +12,7 @@ const char PAGE_settingsCss[] PROGMEM = R"=====(
%DMXMENU%
+ action="/settings/um">
)====="; @@ -247,12 +248,13 @@ var d=document;function H(){window.open("https://github.com/atuline/WLED/wiki/Se id="form_s" name="Sf" method="post">

Sync setup

Button setup

-On/Off button enabled:
Infrared remote: -

+Infrared remote:
IR info

WLED Broadcast

UDP Port:
2nd Port:
Settings on this page are only changable if OTA lock is disabled!
Deny access to WiFi settings if locked:

Factory reset:
-All EEPROM content (settings) will be erased.

+All settings and presets will be erased.

HTTP traffic is unencrypted. An attacker in the same network can intercept form data!

Software Update


Enable ArduinoOTA:

About

WLED - version 0.12.0


Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

@@ -418,3 +420,18 @@ MIT license

Server message: Response error!
)====="; + +// Autogenerated from wled00/data/settings_um.htm, do not edit!! +const char PAGE_settings_um[] PROGMEM = R"=====(UI Settings")); }