diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index 7d27717dd..f9ffb6481 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -1,4 +1,4 @@ -name: PlatformIO CI +name: WLED CI on: [push, pull_request] @@ -8,17 +8,11 @@ jobs: name: Gather Environments runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Cache pip - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - uses: actions/setup-python@v4 - with: - python-version: '3.9' + python-version: '3.12' + cache: 'pip' - name: Install PlatformIO run: pip install -r requirements.txt - name: Get default environments @@ -38,59 +32,63 @@ jobs: matrix: environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: cache: 'npm' - run: npm install - - name: Cache pip - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - name: Cache PlatformIO - uses: actions/cache@v3 + uses: actions/cache@v4 with: - path: ~/.platformio - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + path: | + ~/.platformio/.cache + ~/.buildcache + build_output + key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }} + restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}- - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: '3.12' + cache: 'pip' - name: Install PlatformIO run: pip install -r requirements.txt - name: Build firmware - env: - WLED_RELEASE: True run: pio run -e ${{ matrix.environment }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 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 + build_output/release/*.bin + build_output/release/*_ESP02.bin.gz release: name: Create Release runs-on: ubuntu-latest - needs: [get_default_envs, build] + needs: build if: startsWith(github.ref, 'refs/tags/') steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: - name: firmware-release + merge-multiple: true - name: Create draft release uses: softprops/action-gh-release@v1 with: draft: True files: | *.bin - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + *.bin.gz + + + testCdata: + name: Test cdata.js + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + - run: npm ci + - run: npm test diff --git a/CHANGELOG.md b/CHANGELOG.md index 17c37c530..5d432e357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,70 @@ ## WLED changelog -#### Build 2309120 till build 2201060 +#### Build 2403070 +- Add additional segment options when controlling over e1.31 (#3616 by @demophoon) +- LockedJsonResponse: Release early if possible (#3760 by @willmmiles) +- Update setup-node and cache usermods in wled-ci.yml (#3737 by @WoodyLetsCode) +- Fix preset sorting (#3790 by @WoodyLetsCode) +- compile time button configuration #3792 +- remove IR config if not compiled +- additional string optimisations +- Better low brightness level PWM handling (fixes #2767, #2868) + +#### Build 2402290 +- Multiple analog button fix for #3549 +- Preset caching on chips with PSRAM (credit @akaricchi) +- Fixing stairway usermod and adding buildflags (by @lost-hope) +- ESP-NOW packet modification +- JSON buffer lock error messages / Reduce wait time for lock to 100ms +- Reduce string RAM usage for ESP8266 +- Fixing a potential array bounds violation in ESPDMX +- Move timezone table to PROGMEM (#3766 by @willmmiles) +- Reposition upload warning message. (fixes #3778) +- ABL display fix & optimisation +- Add virtual Art-Net RGBW option (#3783 by @shammy642) + +#### Build 2402090 +- Added new Ethernet controller RGB2Go Tetra (duplicate of ESP3DEUXQuattro) +- Usermod: httpPullLightControl (#3560 by @roelbroersma) +- DMX: S2 & C3 support via modified ESPDMX +- Bugfix: prevent cleaning of JSON buffer after a failed lock attempt (BufferGuard) +- Product/Brand override (API & AP SSID) (#3750 by @moustachauve) + +#### Build 2402060 +- WLED version 0.15.0-b1 +- Harmonic Random Cycle palette (#3729 by @dedehai) +- Multi PIR sensor usermod (added support for attaching multiple PIR sensors) +- Removed obsolete (and nonfunctional) usermods + +#### Build 2309120 till build 2402010 - WLED version 0.15.0-a0 +- Multi-WiFi support. Add up to 3 (or more via cusom compile) WiFis to connect to +- Temporary AP. Use your WLED in public with temporary AP. +- Github CI build system enhancements (#3718 by @WoodyLetsCode) +- Accessibility: Node list ( #3715 by @WoodyLetsCode) +- Analog clock overlay enhancement (#3489 by @WoodyLetsCode) +- ESP32-POE-WROVER from Olimex ethernet support (#3625 by @m-wachter) +- APA106 support (#3580 by @itstefanjanos) +- BREAKING: Effect: updated Palette effect to support 2D (#3683 by @TripleWhy) +- "SuperSync" from WLED MM (by @MoonModules) +- Effect: DNA Spiral Effect Speed Fix (#3723 by @Derek4aty1) +- Fix for #3693 +- Orange flash fix (#3196) for transitions +- Add own background image upload (#3596 by @WoodyLetsCode) +- WLED time overrides (`WLED_NTP_ENABLED`, `WLED_TIMEZONE`, `WLED_UTC_OFFSET`, `WLED_LAT` and `WLED_LON`) +- Better sorting and naming of static palettes (by @WoodyLetsCode) +- ANIMartRIX usermod and effects (#3673 by @netmindz) +- Use canvas instead of CSS gradient for liveview (#3621 by @zanhecht) +- Fix for #3672 +- ColoOrderMap W channel swap (color order overrides now have W swap) +- En-/disable LED maps when receiving realtime data (#3554 by @ezcGman) +- Added PWM frequency selection to UI (Settings) +- Automatically build UI before compiling (#3598, #3666 by @WoodyLetsCode) +- Internal: Added *suspend* API to `strip` (`WS2812FX class`) +- Possible fix for #3589 & partial fix for #3605 +- MPU6050 upgrade (#3654 by @willmmiles) +- UI internals (#3656 by @WoodyLetsCode) +- ColorPicker fix (#3658 by @WoodyLetsCode) - Global JSON buffer guarding (#3648 by @willmmiles, resolves #3641, #3312, #3367, #3637, #3646, #3447) - Effect: Fireworks 1D (fix for matrix trailing strip) - BREAKING: Reduced number of segments (12) on ESP8266 due to less available RAM @@ -57,6 +120,33 @@ - send UDP/WS on segment change - pop_back() when removing last segment +#### Build 2401141 +- Official release of WLED 0.14.1 +- Fix for #3566, #3665, #3672 +- Sorting of palettes in custom palette editor (#3674 by @WoodyLetsCode) + +#### Build 2401060 +- Version bump: 0.14.1-b3 +- Global JSON buffer guarding (#3648 by @willmmiles, resolves #3641, #3312, #3367, #3637, #3646, #3447) +- Fix for #3632 +- Custom palette editor mobile UI enhancement (#3617 by @imeszaros) +- changelog update + +#### Build 2312290 +- Fix for #3622, #3613, #3609 +- Various tweaks and fixes +- changelog update + +#### Build 2312230 +- Version bump: 0.14.1-b2 +- Fix for Pixel Magic button +- Fix for #2922 (option to force WiFi PHY mode to G on ESP8266) +- Fix for #3601, #3400 (incorrect sunrise/sunset, #3612 by @softhack007) + +#### Build 2312180 +- Bugfixes (#3593, #3490, #3573, #3517, #3561, #3555, #3541, #3536, #3515, #3522, #3533, #3508) +- Various other internal cleanups and optimisations + #### Build 2311160 - Version bump: 0.14.1-b1 - Bugfixes (#3526, #3502, #3496, #3484, #3487, #3445, #3466, #3296, #3382, #3312) @@ -489,7 +579,7 @@ - Added application level pong websockets reply (#2139) - Use AsyncTCP 1.0.3 as it mitigates the flickering issue from 0.13.0-b2 -- Fixed transition manually updated in preset overriden by field value +- Fixed transition manually updated in preset overridden by field value #### Build 2108050 @@ -1018,7 +1108,7 @@ #### Build 2011040 -- Inversed Rain direction (fixes #1147) +- Inverted Rain direction (fixes #1147) #### Build 2011010 @@ -1229,7 +1319,7 @@ - Added module info page to web UI - Added realtime override functionality to web UI -- Added individial segment power and brightness to web UI +- Added individual segment power and brightness to web UI - Added feature to one-click select single segment only by tapping segment name - Removed palette jumping to default if color is changed diff --git a/package-lock.json b/package-lock.json index 55fc0b17b..99e3efc3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,277 +1,434 @@ { "name": "wled", - "version": "0.15.0-a0", - "lockfileVersion": 1, + "version": "0.15.0-b1", + "lockfileVersion": 3, "requires": true, - "dependencies": { - "abbrev": { + "packages": { + "": { + "name": "wled", + "version": "0.15.0-b1", + "license": "ISC", + "dependencies": { + "clean-css": "^5.3.3", + "html-minifier-terser": "^7.2.0", + "inliner": "^1.13.1", + "nodemon": "^3.0.2", + "zlib": "^1.0.5" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz", + "integrity": "sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "ajv": { + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { + "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "align-text": { + "node_modules/align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "requires": { + "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", + "dependencies": { "kind-of": "^3.0.2", "longest": "^1.0.1", "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "ansi-escapes": { + "node_modules/ansi-escapes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==", + "engines": { + "node": ">=0.10.0" + } }, - "ansi-regex": { + "node_modules/ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "engines": { + "node": ">=0.10.0" + } }, - "ansi-styles": { + "node_modules/ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "engines": { + "node": ">=0.10.0" + } }, - "anymatch": { + "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "requires": { + "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "argparse": { + "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { + "dependencies": { "sprintf-js": "~1.0.2" } }, - "asap": { + "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { "safer-buffer": "~2.1.0" } }, - "assert-plus": { + "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } }, - "asynckit": { + "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "aws-sign2": { + "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "bcrypt-pbkdf": { + "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { "tweetnacl": "^0.14.3" } }, - "binary-extensions": { + "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } }, - "boolbase": { + "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "braces": { + "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { + "dependencies": { "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" } }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "camelcase": { + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" + "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", + "engines": { + "node": ">=0.10.0" } }, - "chalk": { + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "charset": { + "node_modules/charset": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", - "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==" + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", + "engines": { + "node": ">=4.0.0" + } }, - "cheerio": { + "node_modules/cheerio": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", - "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", - "requires": { + "integrity": "sha512-Fwcm3zkR37STnPC8FepSHeSYJM5Rd596TZOcfDUdojR4Q735aK1Xn+M+ISagNneuCwMjK28w4kX+ETILGNT/UQ==", + "dependencies": { "css-select": "~1.0.0", "dom-serializer": "~0.1.0", "entities": "~1.1.1", "htmlparser2": "~3.8.1", "lodash": "^3.2.0" + }, + "engines": { + "node": ">= 0.6" } }, - "chokidar": { + "node_modules/cheerio/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "clap": { + "node_modules/clap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "requires": { + "dependencies": { "chalk": "^1.1.3" + }, + "engines": { + "node": ">=0.10.0" } }, - "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", - "requires": { + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dependencies": { "source-map": "~0.6.0" }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } + "engines": { + "node": ">= 10.0" } }, - "cliui": { + "node_modules/cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "requires": { + "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", + "dependencies": { "center-align": "^0.1.1", "right-align": "^0.1.1", "wordwrap": "0.0.2" } }, - "coa": { + "node_modules/coa": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", - "requires": { + "integrity": "sha512-KAGck/eNAmCL0dcT3BiuYwLbExK6lduR8DxM3C1TyDzaXhZHyZ8ooX5I5+na2e3dPFuibfxrGdorr0/Lr7RYCQ==", + "dependencies": { "q": "^1.1.2" + }, + "engines": { + "node": ">= 0.8.0" } }, - "colors": { + "node_modules/colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", + "engines": { + "node": ">=0.1.90" + } }, - "combined-stream": { + "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { + "dependencies": { "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "configstore": { + "node_modules/configstore": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz", - "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=", - "requires": { + "integrity": "sha512-Zcx2SVdZC06IuRHd2MhkVYFNJBkZBj166LGdsJXRcqNC8Gs5Bwh8mosStNeCBBmtIm4wNii2uarD50qztjKOjw==", + "dependencies": { "graceful-fs": "^4.1.2", "mkdirp": "^0.5.0", "object-assign": "^4.0.1", @@ -281,285 +438,338 @@ "write-file-atomic": "^1.1.2", "xdg-basedir": "^2.0.0" }, - "dependencies": { - "uuid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" - } + "engines": { + "node": ">=0.10.0" } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "node_modules/configstore/node_modules/uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha512-FULf7fayPdpASncVy4DLh3xydlXEJJpvIELjYjNeQWYUZ9pclcpvCZSr2gkmN2FrrGcI7G/cJsIEwk5/8vfXpg==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details." }, - "css-select": { + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/css-select": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", - "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", - "requires": { + "integrity": "sha512-/xPlD7betkfd7ChGkLGGWx5HWyiHDOSn7aACLzdH0nwucPvB0EAm8hMBm7Xn7vGfAeRRN7KZ8wumGm8NoNcMRw==", + "dependencies": { "boolbase": "~1.0.0", "css-what": "1.0", "domutils": "1.4", "nth-check": "~1.0.0" } }, - "css-what": { + "node_modules/css-what": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", - "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=" + "integrity": "sha512-60SUMPBreXrLXgvpM8kYpO0AOyMRhdRlXFX5BMQbZq1SIJCyNE56nqFQhmvREQdUJpedbGRYZ5wOyq3/F6q5Zw==", + "engines": { + "node": "*" + } }, - "csso": { + "node_modules/csso": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/csso/-/csso-2.0.0.tgz", - "integrity": "sha1-F4tDpEYhIhwndWCG9THgL0KQDug=", - "requires": { + "integrity": "sha512-tckZA0LhyEnToPoQDmncCA+TUS3aoIVl/MsSaoipR52Sfa+H83fJvIHRVOHMFn9zW6kIV1L0D7tUDFFjvN28lg==", + "dependencies": { "clap": "^1.0.9", "source-map": "^0.5.3" + }, + "bin": { + "csso": "bin/csso" + }, + "engines": { + "node": ">=0.10.0" } }, - "dashdash": { + "node_modules/csso/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" } }, - "debug": { + "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { + "dependencies": { "ms": "2.0.0" } }, - "decamelize": { + "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } }, - "deep-extend": { + "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } }, - "delayed-stream": { + "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } }, - "dom-serializer": { + "node_modules/dom-serializer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "requires": { + "dependencies": { "domelementtype": "^1.3.0", "entities": "^1.1.1" } }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", - "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", - "requires": { - "domelementtype": "1" - } - }, - "dot-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", - "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", - "requires": { - "no-case": "^3.0.3", - "tslib": "^1.10.0" - }, - "dependencies": { - "lower-case": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", - "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", - "requires": { - "tslib": "^1.10.0" - } - }, - "no-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", - "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", - "requires": { - "lower-case": "^2.0.1", - "tslib": "^1.10.0" - } - } - } - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "entities": { + "node_modules/dom-serializer/node_modules/entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, - "es6-promise": { + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", + "integrity": "sha512-ZkVgS/PpxjyJMb+S2iVHHEZjVnOUtjGp0/zstqKGTE9lrZtNHlNQmLwP/lhLMEApYbzc08BKMx9IFpKhaSbW1w==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/duplexify/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexify/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es6-promise": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", - "integrity": "sha1-lu258v2wGZWCKyY92KratnSBgbw=" + "integrity": "sha512-oyOjMhyKMLEjOOtvkwg0G4pAzLQ9WdbbeX7WdqKzvYXu+UFgD0Zo/Brq5Q49zNmnGPPzV5rmYvrr0jz1zWx8Iw==" }, - "escape-string-regexp": { + "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } }, - "esprima": { + "node_modules/esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } }, - "extend": { + "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, - "extsprintf": { + "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "fast-json-stable-stringify": { + "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, - "fill-range": { + "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { + "dependencies": { "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "forever-agent": { + "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } }, - "form-data": { + "node_modules/form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { + "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" } }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } }, - "getpass": { + "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { "assert-plus": "^1.0.0" } }, - "glob-parent": { + "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "requires": { + "dependencies": { "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "got": { + "node_modules/got": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", - "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=", - "requires": { + "integrity": "sha512-7chPlc0pWHjvq7B6dEEXz4GphoDupOvBSSl6AwRsAJX7GPTZ+bturaZiIigX4Dp6KrAP67nvzuKkNc0SLA0DKg==", + "dependencies": { "duplexify": "^3.2.0", "infinity-agent": "^2.0.0", "is-redirect": "^1.0.0", @@ -571,166 +781,167 @@ "read-all-stream": "^3.0.0", "timed-out": "^2.0.0" }, - "dependencies": { - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" - } + "engines": { + "node": ">=0.10.0" } }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "node_modules/got/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", + "engines": { + "node": ">=0.10.0" + } }, - "har-schema": { + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" } }, - "has-ansi": { + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dependencies": { "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "has-flag": { + "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - }, - "html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", - "requires": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", - "he": "^1.2.0", - "param-case": "^3.0.3", - "relateurl": "^0.2.7", - "terser": "^4.6.3" - }, - "dependencies": { - "camel-case": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", - "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==", - "requires": { - "pascal-case": "^3.1.1", - "tslib": "^1.10.0" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - }, - "param-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", - "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", - "requires": { - "dot-case": "^3.0.3", - "tslib": "^1.10.0" - } - } + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" } }, - "htmlparser2": { + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/htmlparser2": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", - "requires": { + "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==", + "dependencies": { "domelementtype": "1", "domhandler": "2.3", "domutils": "1.5", "entities": "1.0", "readable-stream": "1.1" - }, - "dependencies": { - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" - } } }, - "http-signature": { + "node_modules/htmlparser2/node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==" + }, + "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" } }, - "iconv-lite": { + "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { + "dependencies": { "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" } }, - "ignore-by-default": { + "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" }, - "imurmurhash": { + "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } }, - "infinity-agent": { + "node_modules/infinity-agent": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz", - "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=" + "integrity": "sha512-CnfUJe5o2S9aAQWXGMhDZI4UL39MAJV3guOTfHHIdos4tuVHkl1j/J+1XLQn+CLIvqcpgQR/p+xXYXzcrhCe5w==" }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ini": { + "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, - "inliner": { + "node_modules/inliner": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/inliner/-/inliner-1.13.1.tgz", - "integrity": "sha1-5QApgev1Dp2fMTcRSBz/Ei1PP8s=", - "requires": { + "integrity": "sha512-yoS+56puOu+Ug8FBRtxtTFnEn2NHqFs8BNQgSOvzh3J0ommbwNw8VKiaVNYjWK6fgPuByq95KyV0LC+qV9IwLw==", + "dependencies": { "ansi-escapes": "^1.4.0", "ansi-styles": "^2.2.1", "chalk": "^1.1.3", @@ -750,619 +961,806 @@ "then-fs": "^2.0.0", "uglify-js": "^2.8.0", "update-notifier": "^0.5.0" + }, + "bin": { + "inliner": "cli/index.js" } }, - "is-binary-path": { + "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "requires": { + "dependencies": { "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "is-buffer": { + "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, - "is-extglob": { + "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } }, - "is-finite": { + "node_modules/is-finite": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "is-glob": { + "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "requires": { + "dependencies": { "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-npm": { + "node_modules/is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "js-yaml": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", - "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", - "requires": { - "argparse": "^1.0.7", - "esprima": "^2.6.0" + "integrity": "sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg==", + "engines": { + "node": ">=0.10.0" } }, - "jsbn": { + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/js-yaml": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", + "integrity": "sha512-BLv3oxhfET+w5fjPwq3PsAsxzi9i3qzU//HMpWVz0A6KplF86HdR9x2TGnv9DXhSUrO7LO8czUiTd3yb3mLSvg==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^2.6.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, - "jschardet": { + "node_modules/jschardet": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.6.0.tgz", - "integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==" + "integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==", + "engines": { + "node": ">=0.1.90" + } }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, - "json-schema-traverse": { + "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "json-stringify-safe": { + "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" } }, - "kind-of": { + "node_modules/kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" } }, - "latest-version": { + "node_modules/latest-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-1.0.1.tgz", - "integrity": "sha1-cs/Ebj6NG+ZR4eu1Tqn26pbzdLs=", - "requires": { + "integrity": "sha512-HERbxp4SBlmI380+eM0B0u4nxjfTaPeydIMzl9+9UQ4nSu3xMWKlX9WoT34e4wy7VWe67c53Nv9qPVjS8fHKgg==", + "dependencies": { "package-json": "^1.0.0" + }, + "bin": { + "latest-version": "cli.js" + }, + "engines": { + "node": ">=0.10.0" } }, - "lazy-cache": { + "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "engines": { + "node": ">=0.10.0" + } }, - "lodash": { + "node_modules/lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==" }, - "lodash._arrayeach": { + "node_modules/lodash._arrayeach": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" + "integrity": "sha512-Mn7HidOVcl3mkQtbPsuKR0Fj0N6Q6DQB77CtYncZcJc0bx5qv2q4Gl6a0LC1AN+GSxpnBDNnK3CKEm9XNA4zqQ==" }, - "lodash._baseassign": { + "node_modules/lodash._baseassign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "requires": { + "integrity": "sha512-t3N26QR2IdSN+gqSy9Ds9pBu/J1EAFEshKlUHpJG3rvyJOYgcELIxcIeKKfZk7sjOz11cFfzJRsyFry/JyabJQ==", + "dependencies": { "lodash._basecopy": "^3.0.0", "lodash.keys": "^3.0.0" } }, - "lodash._basecopy": { + "node_modules/lodash._basecopy": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==" }, - "lodash._baseeach": { + "node_modules/lodash._baseeach": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", - "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", - "requires": { + "integrity": "sha512-IqUZ9MQo2UT1XPGuBntInqTOlc+oV+bCo0kMp+yuKGsfvRSNgUW0YjWVZUrG/gs+8z/Eyuc0jkJjOBESt9BXxg==", + "dependencies": { "lodash.keys": "^3.0.0" } }, - "lodash._bindcallback": { + "node_modules/lodash._bindcallback": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + "integrity": "sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ==" }, - "lodash._createassigner": { + "node_modules/lodash._createassigner": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", - "requires": { + "integrity": "sha512-LziVL7IDnJjQeeV95Wvhw6G28Z8Q6da87LWKOPWmzBLv4u6FAT/x5v00pyGW0u38UoogNF2JnD3bGgZZDaNEBw==", + "dependencies": { "lodash._bindcallback": "^3.0.0", "lodash._isiterateecall": "^3.0.0", "lodash.restparam": "^3.0.0" } }, - "lodash._getnative": { + "node_modules/lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==" }, - "lodash._isiterateecall": { + "node_modules/lodash._isiterateecall": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==" }, - "lodash.assign": { + "node_modules/lodash.assign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", - "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", - "requires": { + "integrity": "sha512-/VVxzgGBmbphasTg51FrztxQJ/VgAUpol6zmJuSVSGcNg4g7FA4z7rQV8Ovr9V3vFBNWZhvKWHfpAytjTVUfFA==", + "dependencies": { "lodash._baseassign": "^3.0.0", "lodash._createassigner": "^3.0.0", "lodash.keys": "^3.0.0" } }, - "lodash.defaults": { + "node_modules/lodash.defaults": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", - "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", - "requires": { + "integrity": "sha512-X7135IXFQt5JDFnYxOVAzVz+kFvwDn3N8DJYf+nrz/mMWEuSu7+OL6rWqsk3+VR1T4TejFCSu5isBJOLSID2bg==", + "dependencies": { "lodash.assign": "^3.0.0", "lodash.restparam": "^3.0.0" } }, - "lodash.foreach": { + "node_modules/lodash.foreach": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.3.tgz", - "integrity": "sha1-b9fvt5aRrs1n/erCdhyY5wHWw5o=", - "requires": { + "integrity": "sha512-PA7Lp7pe2HMJBoB1vELegEIF3waUFnM0fWDKJVYolwZ4zHh6WTmnq0xmzfQksD66gx2quhDNyBdyaE2T8/DP3Q==", + "dependencies": { "lodash._arrayeach": "^3.0.0", "lodash._baseeach": "^3.0.0", "lodash._bindcallback": "^3.0.0", "lodash.isarray": "^3.0.0" } }, - "lodash.isarguments": { + "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" }, - "lodash.isarray": { + "node_modules/lodash.isarray": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==" }, - "lodash.keys": { + "node_modules/lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", + "dependencies": { "lodash._getnative": "^3.0.0", "lodash.isarguments": "^3.0.0", "lodash.isarray": "^3.0.0" } }, - "lodash.restparam": { + "node_modules/lodash.restparam": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==" }, - "longest": { + "node_modules/longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" + "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", + "engines": { + "node": ">=0.10.0" } }, - "minimatch": { + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "ms": { + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "nested-error-stacks": { + "node_modules/nested-error-stacks": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz", - "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=", - "requires": { + "integrity": "sha512-o32anp9JA7oezPOFSfG2BBXSdHepOm5FpJvwxHWDtfJ3Bg3xdi68S6ijPlEOfUg6quxZWyvJM+8fHk1yMDKspA==", + "dependencies": { "inherits": "~2.0.1" } }, - "nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", - "requires": { + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/nodemon": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz", + "integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==", + "dependencies": { "chokidar": "^3.5.2", - "debug": "^3.2.7", + "debug": "^4", "ignore-by-default": "^1.0.1", "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } + "optional": true } } }, - "nopt": { + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "requires": { + "dependencies": { "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" } }, - "normalize-path": { + "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } }, - "nth-check": { + "node_modules/nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { + "dependencies": { "boolbase": "~1.0.0" } }, - "oauth-sign": { + "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } }, - "object-assign": { + "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { "wrappy": "1" } }, - "os-homedir": { + "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "engines": { + "node": ">=0.10.0" + } }, - "os-tmpdir": { + "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } }, - "osenv": { + "node_modules/osenv": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { + "dependencies": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" } }, - "package-json": { + "node_modules/package-json": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz", - "integrity": "sha1-yOysCUInzfdqMWh07QXifMk5oOA=", - "requires": { + "integrity": "sha512-knDtirWWqKVJrLY3gEBLflVvueTMpyjbAwX/9j/EKi2DsjNemp5voS8cyKyGh57SNaMJNhNRZbIaWdneOcLU1g==", + "dependencies": { "got": "^3.2.0", "registry-url": "^3.0.0" - } - }, - "pascal-case": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz", - "integrity": "sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==", - "requires": { - "no-case": "^3.0.3", - "tslib": "^1.10.0" }, - "dependencies": { - "lower-case": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", - "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", - "requires": { - "tslib": "^1.10.0" - } - }, - "no-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", - "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", - "requires": { - "lower-case": "^2.0.1", - "tslib": "^1.10.0" - } - } + "engines": { + "node": ">=0.10.0" } }, - "performance-now": { + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, - "picomatch": { + "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "prepend-http": { + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "engines": { + "node": ">=0.10.0" + } }, - "process-nextick-args": { + "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "promise": { + "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { + "dependencies": { "asap": "~2.0.3" } }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, - "pstree.remy": { + "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } }, - "q": { + "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } }, - "qs": { + "node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } }, - "rc": { + "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { + "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" } }, - "read-all-stream": { + "node_modules/read-all-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", - "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", - "requires": { + "integrity": "sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==", + "dependencies": { "pinkie-promise": "^2.0.0", "readable-stream": "^2.0.0" }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } + "engines": { + "node": ">=0.10.0" } }, - "readable-stream": { + "node_modules/read-all-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/read-all-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/read-all-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/read-all-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, - "readdirp": { + "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "requires": { + "dependencies": { "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "registry-url": { + "node_modules/registry-url": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "requires": { + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dependencies": { "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "relateurl": { + "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "repeating": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", - "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", - "requires": { - "is-finite": "^1.0.0" + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "engines": { + "node": ">= 0.10" } }, - "request": { + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/repeating": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", + "integrity": "sha512-Nh30JLeMHdoI+AsQ5eblhZ7YlTsM9wiJQe/AHIunlK3KWzvXhXb36IJ7K1IOeRjIOtzMjdUHjwXUFxKJoPTSOg==", + "dependencies": { + "is-finite": "^1.0.0" + }, + "bin": { + "repeating": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", @@ -1383,95 +1781,130 @@ "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" } }, - "right-align": { + "node_modules/right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "requires": { + "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", + "dependencies": { "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "safer-buffer": { + "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "sax": { + "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "semver-diff": { + "node_modules/semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "requires": { - "semver": "^5.0.3" - } - }, - "simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "requires": { - "semver": "~7.0.0" - }, + "integrity": "sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==", "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } + "semver": "^5.0.3" + }, + "engines": { + "node": ">=0.10.0" } }, - "slide": { + "node_modules/semver-diff/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "engines": { + "node": "*" } }, - "sprintf-js": { + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", @@ -1481,49 +1914,70 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" } }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + "node_modules/stream-shift": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.2.tgz", + "integrity": "sha512-rV4Bovi9xx0BFzOb/X0B2GqoIjvqPCttZdu0Wgtx2Dxkj7ETyWl9gmqJ4EutWRLvtZWm8dxE+InQZX1IryZn/w==" }, - "string-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", - "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=", - "requires": { - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { + "node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" + "node_modules/string-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", + "integrity": "sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw==", + "dependencies": { + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "strip-json-comments": { + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } }, - "supports-color": { + "node_modules/supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "engines": { + "node": ">=0.8.0" + } }, - "svgo": { + "node_modules/svgo": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.6.6.tgz", - "integrity": "sha1-s0CIkDbyD5tEdUMHfQ9Vc+0ETAg=", - "requires": { + "integrity": "sha512-C5A1r5SjFesNoKsmc+kWBxmB04iBGH2D/nFy8HJaME9+SyZKcmqcN8QG+GwxIc7D2+JWhaaW7uaM9+XwfplTEQ==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "dependencies": { "coa": "~1.0.1", "colors": "~1.1.2", "csso": "~2.0.0", @@ -1531,107 +1985,149 @@ "mkdirp": "~0.5.1", "sax": "~1.2.1", "whet.extend": "~0.9.9" - } - }, - "terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=0.10.0" } }, - "then-fs": { + "node_modules/terser": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/then-fs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz", - "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=", - "requires": { + "integrity": "sha512-5ffcBcU+vFUCYDNi/o507IqjqrTkuGsLVZ1Fp50hwgZRY7ufVFa9jFfTy5uZ2QnSKacKigWKeaXkOqLa4DsjLw==", + "dependencies": { "promise": ">=3.2 <8" } }, - "timed-out": { + "node_modules/timed-out": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", - "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=" + "integrity": "sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==", + "engines": { + "node": ">=0.10.0" + } }, - "to-regex-range": { + "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { + "dependencies": { "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "touch": { + "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "requires": { + "dependencies": { "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" } }, - "tough-cookie": { + "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { + "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" } }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "tunnel-agent": { + "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" } }, - "tweetnacl": { + "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" }, - "uglify-js": { + "node_modules/uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "requires": { + "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", + "dependencies": { "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" } }, - "uglify-to-browserify": { + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglify-to-browserify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", "optional": true }, - "undefsafe": { + "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" }, - "update-notifier": { + "node_modules/update-notifier": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz", - "integrity": "sha1-B7XcIGazYnqztPUwEw9+3doHpMw=", - "requires": { + "integrity": "sha512-zOGOlUKDAgDlLHLv7Oiszz3pSj8fKlSJ3i0u49sEakjXUEVJ6DMjo/Mh/B6mg2eOALvRTJkd0kbChcipQoYCng==", + "dependencies": { "chalk": "^1.0.0", "configstore": "^1.0.0", "is-npm": "^1.0.0", @@ -1639,89 +2135,125 @@ "repeating": "^1.1.2", "semver-diff": "^2.0.0", "string-length": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { "punycode": "^2.1.0" } }, - "util-deprecate": { + "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "uuid": { + "node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } }, - "verror": { + "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, - "whet.extend": { + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/whet.extend": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=" + "integrity": "sha512-mmIPAft2vTgEILgPeZFqE/wWh24SEsR/k+N9fJ3Jxrz44iDFy9aemCxdksfURSHYFCLmvs/d/7Iso5XjPpNfrA==", + "engines": { + "node": ">=0.6.0" + } }, - "window-size": { + "node_modules/window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", + "engines": { + "node": ">= 0.8.0" + } }, - "wordwrap": { + "node_modules/wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", + "engines": { + "node": ">=0.4.0" + } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "write-file-atomic": { + "node_modules/write-file-atomic": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", - "requires": { + "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", + "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", "slide": "^1.1.5" } }, - "xdg-basedir": { + "node_modules/xdg-basedir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", - "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", - "requires": { + "integrity": "sha512-NF1pPn594TaRSUO/HARoB4jK8I+rWgcpVlpQCK6/6o5PHyLUt2CSiDrpUZbQ6rROck+W2EwF8mBJcTs+W98J9w==", + "dependencies": { "os-homedir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "yargs": { + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "requires": { + "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", + "dependencies": { "camelcase": "^1.0.2", "cliui": "^2.1.0", "decamelize": "^1.0.0", "window-size": "0.1.0" } }, - "zlib": { + "node_modules/zlib": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", - "integrity": "sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=" + "integrity": "sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w==", + "hasInstallScript": true, + "engines": { + "node": ">=0.2.0" + } } } } diff --git a/package.json b/package.json index 68bd16b82..f2c0e3d65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-a0", + "version": "0.15.0-b1", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { @@ -9,6 +9,7 @@ }, "scripts": { "build": "node tools/cdata.js", + "test": "node --test", "dev": "nodemon -e js,html,htm,css,png,jpg,gif,ico,js -w tools/ -w wled00/data/ -x node tools/cdata.js" }, "repository": { @@ -22,10 +23,10 @@ }, "homepage": "https://github.com/Aircoookie/WLED#readme", "dependencies": { - "clean-css": "^4.2.3", - "html-minifier-terser": "^5.1.1", + "clean-css": "^5.3.3", + "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", - "nodemon": "^2.0.20", + "nodemon": "^3.0.2", "zlib": "^1.0.5" } } diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 01223e93d..e12b11c2c 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -22,6 +22,16 @@ def _create_dirs(dirs=["firmware", "map"]): if not os.path.isdir("{}{}".format(OUTPUT_DIR, d)): os.mkdir("{}{}".format(OUTPUT_DIR, d)) +def create_release(source): + release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") + if release_name: + _create_dirs(["release"]) + version = _get_cpp_define_value(env, "WLED_VERSION") + # get file extension of source file (.bin or .bin.gz) + ext = source.split(".", 1)[1] + release_file = "{}release{}WLED_{}_{}.{}".format(OUTPUT_DIR, os.path.sep, version, release_name, ext) + shutil.copy(source, release_file) + def bin_rename_copy(source, target, env): _create_dirs() variant = env["PIOENV"] @@ -30,14 +40,6 @@ def bin_rename_copy(source, target, env): 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: - _create_dirs(["release"]) - version = _get_cpp_define_value(env, "WLED_VERSION") - release_file = "{}release{}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): @@ -46,6 +48,8 @@ def bin_rename_copy(source, target, env): # copy firmware.bin to firmware/.bin shutil.copy(str(target[0]), bin_file) + create_release(bin_file) + # copy firmware.map to map/.map if os.path.isfile("firmware.map"): shutil.move("firmware.map", map_file) @@ -66,4 +70,6 @@ def bin_gzip(source, target, env): with gzip.open(gzip_file, "wb", compresslevel = 9) as f: shutil.copyfileobj(fp, f) + create_release(gzip_file) + env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_rename_copy, bin_gzip]) diff --git a/platformio.ini b/platformio.ini index d242cbc22..66bd09957 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,39 +9,8 @@ # (use `platformio_override.ini` when building for your own board; see `platformio_override.ini.sample` for an example) # ------------------------------------------------------------------------------ -# CI binaries -; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # ESP32 variant builds are temporarily excluded from CI due to toolchain issues on the GitHub Actions Linux environment -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi - -# Release binaries -; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB - -# Build everything -; default_envs = esp32dev, esp8285_4CH_MagicHome, codm-controller-0_6-rev2, codm-controller-0_6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips - -# Single binaries (uncomment your board) -; default_envs = elekstube_ips -; default_envs = nodemcuv2 -; default_envs = esp8266_2m -; default_envs = esp01_1m_full -; default_envs = esp07 -; default_envs = d1_mini -; default_envs = heltec_wifi_kit_8 -; default_envs = h803wf -; default_envs = d1_mini_debug -; default_envs = d1_mini_ota -; default_envs = esp32dev -; default_envs = esp8285_4CH_MagicHome -; default_envs = esp8285_H801 -; default_envs = d1_mini_5CH_Shojo_PCB -; default_envs = wemos_shield_esp32 -; default_envs = m5atom -; default_envs = esp32_eth -; default_envs = esp32dev_qio80 -; default_envs = esp32_eth_ota1mapp -; default_envs = esp32s2_saola -; default_envs = esp32c3dev -; default_envs = lolin_s2_mini +# CI/release binaries +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -60,9 +29,9 @@ extra_configs = arduino_core_2_6_3 = espressif8266@2.3.3 arduino_core_2_7_4 = espressif8266@2.6.2 arduino_core_3_0_0 = espressif8266@3.0.0 -arduino_core_3_2_0 = espressif8266@3.2.0 -arduino_core_4_1_0 = espressif8266@4.1.0 -arduino_core_3_1_2 = espressif8266@4.2.0 +arduino_core_3_0_2 = espressif8266@3.2.0 +arduino_core_3_1_0 = espressif8266@4.1.0 +arduino_core_3_1_2 = espressif8266@4.2.1 # Development platforms arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop @@ -132,12 +101,7 @@ build_flags = -D DECODE_SONY=true -D DECODE_SAMSUNG=true -D DECODE_LG=true - ;-Dregister= # remove warnings in C++17 due to use of deprecated register keyword by the FastLED library ;; warning: this breaks framework code on ESP32-C3 and ESP32-S2 -DWLED_USE_MY_CONFIG - ; -D USERMOD_SENSORSTOMQTT - #For ADS1115 sensor uncomment following - ; -D USERMOD_ADS1115 - ; -D USERMOD_ANIMARTRIX build_unflags = @@ -165,10 +129,8 @@ extra_scripts = framework = arduino board_build.flash_mode = dout monitor_speed = 115200 -# slow upload speed (comment this out with a ';' when building for development use) +# slow upload speed but most compatible (use platformio_override.ini to use faster speed) upload_speed = 115200 -# fast upload speed (remove ';' when building for development use) -; upload_speed = 921600 # ------------------------------------------------------------------------------ # LIBRARIES: required dependencies @@ -183,28 +145,34 @@ lib_deps = IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.5 https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 - # ESP-NOW library (includes mandatory QuickDebug library) - ; gmag11/QuickESPNow @ 0.6.2 + # for I2C interface + ;Wire + # ESP-NOW library + ;gmag11/QuickESPNow @ ~0.7.0 https://github.com/blazoncek/QuickESPNow.git#optional-debug #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For compatible OLED display uncomment following - #U8g2 #@ ~2.33.15 + #olikraus/U8g2 #@ ~2.33.15 #For Dallas sensor uncomment following - #OneWire @ ~2.3.7 + #paulstoffregen/OneWire @ ~2.3.8 #For BME280 sensor uncomment following #BME280 @ ~3.0.0 - ; adafruit/Adafruit BMP280 Library @ 2.1.0 - ; adafruit/Adafruit CCS811 Library @ 1.0.4 - ; adafruit/Adafruit Si7021 Library @ 1.4.0 + ;adafruit/Adafruit BMP280 Library @ 2.1.0 + ;adafruit/Adafruit CCS811 Library @ 1.0.4 + ;adafruit/Adafruit Si7021 Library @ 1.4.0 #For ADS1115 sensor uncomment following - ; adafruit/Adafruit BusIO @ 1.13.2 - ; adafruit/Adafruit ADS1X15 @ 2.4.0 + ;adafruit/Adafruit BusIO @ 1.13.2 + ;adafruit/Adafruit ADS1X15 @ 2.4.0 #For MPU6050 IMU uncomment follwoing - ; electroniccats/MPU6050 @1.0.1 - # For -D USERMOD_ANIMARTRIX - # CC BY-NC 3.0 licensed effects by Stefan Petrick, include this usermod only if you accept the terms! - ; https://github.com/netmindz/animartrix.git#18bf17389e57c69f11bc8d04ebe1d215422c7fb7 + ;electroniccats/MPU6050 @1.0.1 + # For -D USERMOD_ANIMARTRIX + # CC BY-NC 3.0 licensed effects by Stefan Petrick, include this usermod only if you accept the terms! + ;https://github.com/netmindz/animartrix.git#18bf17389e57c69f11bc8d04ebe1d215422c7fb7 + # SHT85 + ;robtillaart/SHT85@~0.3.3 + # Audioreactive usermod + ;kosme/arduinoFFT @ 2.0.0 extra_scripts = ${scripts_defaults.extra_scripts} @@ -256,8 +224,8 @@ lib_deps = pixelmatix/GifDecoder@^1.1.0 ${env.lib_deps} # additional build flags for audioreactive -AR_build_flags = -D USERMOD_AUDIOREACTIVE -D UM_AUDIOREACTIVE_USE_NEW_FFT -AR_lib_deps = https://github.com/kosme/arduinoFFT#develop @ ^1.9.2 +AR_build_flags = -D USERMOD_AUDIOREACTIVE +AR_lib_deps = kosme/arduinoFFT @ 2.0.0 [esp32_idf_V4] ;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5 @@ -282,6 +250,7 @@ lib_deps = ;; generic definitions for all ESP32-S2 boards platform = espressif32@5.3.0 platform_packages = +default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32S2 @@ -292,7 +261,6 @@ build_flags = -g -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT - lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} @@ -310,7 +278,6 @@ build_flags = -g -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT - lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} @@ -368,44 +335,6 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_D ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} -[env:esp07] -board = esp07 -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} -lib_deps = ${esp8266.lib_deps} - -[env:d1_mini] -board = d1_mini -platform = ${common.platform_wled_default} -platform_packages = ${common.platform_packages} -upload_speed = 921600 -board_build.ldscript = ${common.ldscript_4m1m} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -lib_deps = ${esp8266.lib_deps} -monitor_filters = esp8266_exception_decoder - -[env:heltec_wifi_kit_8] -board = d1_mini -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} -lib_deps = ${esp8266.lib_deps} - -[env:h803wf] -board = d1_mini -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 LEDPIN=1 -D WLED_DISABLE_INFRARED -lib_deps = ${esp8266.lib_deps} - [env:esp32dev] board = esp32dev platform = ${esp32.platform} @@ -430,33 +359,6 @@ board_build.partitions = ${esp32.default_partitions} ; board_build.f_flash = 80000000L ; board_build.flash_mode = dio -[env:esp32dev_qio80] -board = esp32dev -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_qio80 #-D WLED_DISABLE_BROWNOUT_DET -lib_deps = ${esp32.lib_deps} -monitor_filters = esp32_exception_decoder -board_build.partitions = ${esp32.default_partitions} -board_build.f_flash = 80000000L -board_build.flash_mode = qio - -[env:esp32dev_V4_dio80] -;; experimental ESP32 env using ESP-IDF V4.4.x -;; Warning: this build environment is not stable!! -;; please erase your device before installing. -board = esp32dev -platform = ${esp32_idf_V4.platform} -platform_packages = ${esp32_idf_V4.platform_packages} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_V4_qio80 #-D WLED_DISABLE_BROWNOUT_DET -lib_deps = ${esp32_idf_V4.lib_deps} -monitor_filters = esp32_exception_decoder -board_build.partitions = ${esp32_idf_V4.default_partitions} -board_build.f_flash = 80000000L -board_build.flash_mode = dio - [env:esp32_eth] board = esp32-poe platform = ${esp32.platform} @@ -468,19 +370,18 @@ build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D lib_deps = ${esp32.lib_deps} board_build.partitions = ${esp32.default_partitions} -[env:esp32s2_saola] -board = esp32-s2-saola-1 -platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip -platform_packages = -framework = arduino -board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +[env:esp32_wrover] +platform = ${esp32.platform} +board = ttgo-t7-v14-mini32 +board_build.f_flash = 80000000L board_build.flash_mode = qio -upload_speed = 460800 +board_build.partitions = ${esp32.default_partitions} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=S2_saola - ;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work - -DARDUINO_USB_CDC_ON_BOOT=1 -lib_deps = ${esp32s2.lib_deps} +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_WROVER + -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue + -D WLED_USE_PSRAM + -D LEDPIN=25 +lib_deps = ${esp32.lib_deps} [env:esp32c3dev] extends = esp32c3 @@ -525,7 +426,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_PSRAM_opi -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -537,95 +438,24 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder -[env:esp32s3dev_8MB_PSRAM_qspi] -;; ESP32-TinyS3 development board, with 8MB FLASH and PSRAM (memory_type: qio_qspi) -extends = env:esp32s3dev_8MB_PSRAM_opi -;board = um_tinys3 ; -> needs workaround from https://github.com/Aircoookie/WLED/pull/2905#issuecomment-1328049860 -board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support -board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB - -[env:esp8285_4CH_MagicHome] -board = esp8285 -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 -lib_deps = ${esp8266.lib_deps} - -[env:esp8285_H801] -board = esp8285 -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 -lib_deps = ${esp8266.lib_deps} - -[env:d1_mini_5CH_Shojo_PCB] -board = d1_mini -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_SHOJO_PCB -lib_deps = ${esp8266.lib_deps} - -# ------------------------------------------------------------------------------ -# DEVELOPMENT BOARDS -# ------------------------------------------------------------------------------ - -[env:d1_mini_debug] -board = d1_mini -build_type = debug -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} ${common.debug_flags} -lib_deps = ${esp8266.lib_deps} - -[env:d1_mini_ota] -board = d1_mini -upload_protocol = espota -# exchange for your WLED IP -upload_port = "10.10.1.27" -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} -lib_deps = ${esp8266.lib_deps} - -[env:anavi_miracle_controller] -board = d1_mini -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 LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 -lib_deps = ${esp8266.lib_deps} - [env:lolin_s2_mini] platform = ${esp32s2.platform} platform_packages = ${esp32s2.platform_packages} board = lolin_s2_mini board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv -build_unflags = ${common.build_unflags} #-DARDUINO_USB_CDC_ON_BOOT=1 +;board_build.flash_mode = qio +;board_build.f_flash = 80000000L +build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2 -DBOARD_HAS_PSRAM - -DARDUINO_USB_CDC_ON_BOOT=1 # try disabling and enabling unflag above in case of board-specific issues, will disable Serial + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_USE_PSRAM - ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 6792 bytes FLASH -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D LEDPIN=16 - -D BTNPIN=18 - -D RLYPIN=9 - -D IRPIN=7 -D HW_PIN_SCL=35 -D HW_PIN_SDA=33 -D HW_PIN_CLOCKSPI=7 @@ -633,188 +463,3 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -D HW_PIN_MISOSPI=9 ; -D STATUSLED=15 lib_deps = ${esp32s2.lib_deps} - -# ------------------------------------------------------------------------------ -# custom board configurations -# ------------------------------------------------------------------------------ - -[env:esp32c3dev_2MB] -;; for ESP32-C3 boards with 2MB flash (instead of 4MB). -;; this board need a specific partition file. OTA not possible. -extends = esp32c3 -platform = ${esp32c3.platform} -platform_packages = ${esp32c3.platform_packages} -board = esp32-c3-devkitm-1 -build_flags = ${common.build_flags} ${esp32c3.build_flags} #-D WLED_RELEASE_NAME=ESP32-C3 - -D WLED_WATCHDOG_TIMEOUT=0 - -D WLED_DISABLE_OTA - ; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB - -DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip -build_unflags = ${common.build_unflags} -upload_speed = 115200 -lib_deps = ${esp32c3.lib_deps} -board_build.partitions = tools/WLED_ESP32_2MB_noOTA.csv -board_build.flash_mode = dio - -[env:wemos_shield_esp32] -board = esp32dev -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} -upload_speed = 460800 -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} - -D LEDPIN=16 - -D RLYPIN=19 - -D BTNPIN=17 - -D IRPIN=18 - -D UWLED_USE_MY_CONFIG - -D USERMOD_DALLASTEMPERATURE - -D USERMOD_FOUR_LINE_DISPLAY - -D TEMPERATURE_PIN=23 - -D USE_ALT_DISPlAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI - -D USERMOD_AUDIOREACTIVE -lib_deps = ${esp32.lib_deps} - OneWire@~2.3.5 - olikraus/U8g2 @ ^2.28.8 - https://github.com/blazoncek/arduinoFFT.git -board_build.partitions = ${esp32.default_partitions} - -[env:m5atom] -board = esp32dev -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39 -lib_deps = ${esp32.lib_deps} -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} -board_build.partitions = ${esp32.default_partitions} - -[env:sp501e] -board = esp_wroom_02 -platform = ${common.platform_wled_default} -board_build.ldscript = ${common.ldscript_2m512k} -build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=1 -lib_deps = ${esp8266.lib_deps} - -[env:sp511e] -board = esp_wroom_02 -platform = ${common.platform_wled_default} -board_build.ldscript = ${common.ldscript_2m512k} -build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 -lib_deps = ${esp8266.lib_deps} - -[env:Athom_RGBCW] ;7w and 5w(GU10) bulbs -board = esp8285 -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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5 - -D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -lib_deps = ${esp8266.lib_deps} - - -[env:Athom_15w_RGBCW] ;15w bulb -board = esp8285 -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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13 - -D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT -lib_deps = ${esp8266.lib_deps} - - -[env:Athom_3Pin_Controller] ;small controller with only data -board = esp8285 -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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=-1 -D LEDPIN=1 -D WLED_DISABLE_INFRARED -lib_deps = ${esp8266.lib_deps} - - -[env:Athom_4Pin_Controller] ; With clock and data interface -board = esp8285 -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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=12 -D LEDPIN=1 -D WLED_DISABLE_INFRARED -lib_deps = ${esp8266.lib_deps} - - -[env:Athom_5Pin_Controller] ;Analog light strip controller -board = esp8285 -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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=-1 DATA_PINS=4,12,14,13 -D WLED_DISABLE_INFRARED -lib_deps = ${esp8266.lib_deps} - - -[env:MY9291] -board = esp01_1m -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_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D USERMOD_MY9291 -lib_deps = ${esp8266.lib_deps} - -# ------------------------------------------------------------------------------ -# codm pixel controller board configurations -# codm-controller-0_6 can also be used for the TYWE3S controller -# ------------------------------------------------------------------------------ - -[env:codm-controller-0_6] -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} -lib_deps = ${esp8266.lib_deps} - -[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_4m1m} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -lib_deps = ${esp8266.lib_deps} - -# ------------------------------------------------------------------------------ -# EleksTube-IPS -# ------------------------------------------------------------------------------ -[env:elekstube_ips] -board = esp32dev -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} -upload_speed = 921600 -build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED - -D USERMOD_RTC - -D USERMOD_ELEKSTUBE_IPS - -D LEDPIN=12 - -D RLYPIN=27 - -D BTNPIN=34 - -D DEFAULT_LED_COUNT=6 - # Display config - -D ST7789_DRIVER - -D TFT_WIDTH=135 - -D TFT_HEIGHT=240 - -D CGRAM_OFFSET - -D TFT_SDA_READ - -D TFT_MOSI=23 - -D TFT_SCLK=18 - -D TFT_DC=25 - -D TFT_RST=26 - -D SPI_FREQUENCY=40000000 - -D USER_SETUP_LOADED -monitor_filters = esp32_exception_decoder -lib_deps = - ${esp32.lib_deps} - TFT_eSPI @ ^2.3.70 -board_build.partitions = ${esp32.default_partitions} diff --git a/platformio_override.ini.sample b/platformio_override.ini.sample deleted file mode 100644 index d6ea5d964..000000000 --- a/platformio_override.ini.sample +++ /dev/null @@ -1,65 +0,0 @@ -# Example PlatformIO Project Configuration Override -# ------------------------------------------------------------------------------ -# Copy to platformio_override.ini to activate overrides -# ------------------------------------------------------------------------------ -# Please visit documentation: https://docs.platformio.org/page/projectconf.html - -[platformio] -default_envs = WLED_tasmota_1M - -[env:WLED_tasmota_1M] -board = esp01_1m -platform = ${common.platform_wled_default} -platform_packages = ${common.platform_packages} -board_build.ldscript = ${common.ldscript_1m128k} -lib_deps = ${esp8266.lib_deps} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} -; ********************************************************************* -; *** Use custom settings from file my_config.h - -DWLED_USE_MY_CONFIG -; ********************************************************************* -; -; -; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above. -; -; disable specific features -; -D WLED_DISABLE_OTA -; -D WLED_DISABLE_ALEXA -; -D WLED_DISABLE_HUESYNC -; -D WLED_DISABLE_INFRARED -; -D WLED_DISABLE_WEBSOCKETS -; PIN defines - uncomment and change, if needed: -; -D LEDPIN=2 -; -D BTNPIN=0 -; -D TOUCHPIN=T0 -; -D IRPIN=4 -; -D RLYPIN=12 -; -D RLYMDE=1 -; digital LED strip types - uncomment only one ! - this will disable WS281x / SK681x support -; -D USE_APA102 -; -D USE_WS2801 -; -D USE_LPD8806 -; PIN defines for 2 wire LEDs - -D CLKPIN=0 - -D DATAPIN=2 -; 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 -; -; set the name of the module - make sure there is a quote-backslash-quote before the name and a backslash-quote-quote after the name -; -D SERVERNAME="\"WLED\"" -; -; set the number of LEDs -; -D DEFAULT_LED_COUNT=30 -; -; set milliampere limit when using ESP pin to power leds -; -D ABL_MILLIAMPS_DEFAULT=850 -; -; enable IR by setting remote type -; -D IRTYPE=0 ;0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote -; -; set default color order of your led strip -; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini new file mode 100644 index 000000000..29f5c6b57 --- /dev/null +++ b/platformio_override.sample.ini @@ -0,0 +1,495 @@ +# Example PlatformIO Project Configuration Override +# ------------------------------------------------------------------------------ +# Copy to platformio_override.ini to activate overrides +# ------------------------------------------------------------------------------ +# Please visit documentation: https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = WLED_tasmota_1M # define as many as you need + +#---------- +# SAMPLE +#---------- +[env:WLED_tasmota_1M] +extends = env:esp01_1m_full # when you want to extend the existing environment (define only updated options) +; board = esp01_1m # uncomment when ou need different board +; platform = ${common.platform_wled_default} # uncomment and change when you want particular platform +; platform_packages = ${common.platform_packages} +; board_build.ldscript = ${common.ldscript_1m128k} +; upload_speed = 921600 # fast upload speed (remove ';' if your board supports fast upload speed) +# Sample libraries used for various usermods. Uncomment when using particular usermod. +lib_deps = ${esp8266.lib_deps} +; olikraus/U8g2 # @~2.33.15 +; paulstoffregen/OneWire@~2.3.8 +; adafruit/Adafruit Unified Sensor@^1.1.4 +; adafruit/DHT sensor library@^1.4.1 +; adafruit/Adafruit BME280 Library@^2.2.2 +; Wire +; robtillaart/SHT85@~0.3.3 +; gmag11/QuickESPNow ;@ 0.6.2 +; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library +; https://github.com/kosme/arduinoFFT#develop @ 1.9.2+sha.419d7b0 ;; used for USERMOD_AUDIOREACTIVE - using "known working" hash +; build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} +; +; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above. +; +; disable specific features +; -D WLED_DISABLE_OTA +; -D WLED_DISABLE_ALEXA +; -D WLED_DISABLE_HUESYNC +; -D WLED_DISABLE_LOXONE +; -D WLED_DISABLE_INFRARED +; -D WLED_DISABLE_WEBSOCKETS +; -D WLED_DISABLE_MQTT +; -D WLED_DISABLE_ADALIGHT +; -D WLED_DISABLE_2D +; -D WLED_DISABLE_PXMAGIC +; -D WLED_DISABLE_ESPNOW +; -D WLED_DISABLE_BROWNOUT_DET +; +; PIN defines - uncomment and change, if needed: +; -D LEDPIN=2 +; or use this for multiple outputs +; -D DATA_PINS=1,3 +; -D BTNPIN=0 +; -D IRPIN=4 +; -D RLYPIN=12 +; -D RLYMDE=1 +; -D LED_BUILTIN=2 # GPIO of built-in LED +; +; Limit max buses +; -D WLED_MAX_BUSSES=2 +; +; Configure default WiFi +; -D CLIENT_SSID='"MyNetwork"' +; -D CLIENT_PASS='"Netw0rkPassw0rd"' +; +; Configure and use Ethernet +; -D WLED_USE_ETHERNET +; -D WLED_ETH_DEFAULT=5 +; do not use pins 5, (16,) 17, 18, 19, 21, 22, 23, 25, 26, 27 for anything but ethernet +; -D PHY_ADDR=0 -D ETH_PHY_POWER=5 -D ETH_PHY_MDC=23 -D ETH_PHY_MDIO=18 +; -D ETH_CLK_MODE=ETH_CLOCK_GPIO17_OUT +; +; NTP time configuration +; -D WLED_NTP_ENABLED=true +; -D WLED_TIMEZONE=2 +; -D WLED_LAT=48.86 +; -D WLED_LON=2.33 +; +; Use Watchdog timer with 10s guard +; -D WLED_WATCHDOG_TIMEOUT=10 +; +; Create debug build (with remote debug) +; -D WLED_DEBUG +; -D WLED_DEBUG_HOST='"192.168.0.100"' +; -D WLED_DEBUG_PORT=7868 +; +; Use Autosave usermod and set it to do save after 90s +; -D USERMOD_AUTO_SAVE +; -D AUTOSAVE_AFTER_SEC=90 +; +; Use 4 Line Display usermod with SPI display +; -D USERMOD_FOUR_LINE_DISPLAY +; -D USE_ALT_DISPlAY # mandatory +; -DFLD_SPI_DEFAULT +; -D FLD_TYPE=SSD1306_SPI64 +; -D FLD_PIN_CLOCKSPI=14 +; -D FLD_PIN_DATASPI=13 +; -D FLD_PIN_DC=26 +; -D FLD_PIN_CS=15 +; -D FLD_PIN_RESET=27 +; +; Use Rotary encoder usermod (in conjunction with 4LD) +; -D USERMOD_ROTARY_ENCODER_UI +; -D ENCODER_DT_PIN=5 +; -D ENCODER_CLK_PIN=18 +; -D ENCODER_SW_PIN=19 +; +; Use Dallas DS18B20 temperature sensor usermod and configure it to use GPIO13 +; -D USERMOD_DALLASTEMPERATURE +; -D TEMPERATURE_PIN=13 +; +; Use Multi Relay usermod and configure it to use 6 relays and appropriate GPIO +; -D USERMOD_MULTI_RELAY +; -D MULTI_RELAY_MAX_RELAYS=6 +; -D MULTI_RELAY_PINS=12,23,22,21,24,25 +; +; Use PIR sensor usermod and configure it to use GPIO4 and timer of 60s +; -D USERMOD_PIRSWITCH +; -D PIR_SENSOR_PIN=4 +; -D PIR_SENSOR_OFF_SEC=60 +; +; Use Audioreactive usermod and configure I2S microphone +; -D USERMOD_AUDIOREACTIVE +; -D UM_AUDIOREACTIVE_USE_NEW_FFT +; -D AUDIOPIN=-1 +; -D DMTYPE=1 # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM +; -D I2S_SDPIN=36 +; -D I2S_WSPIN=23 +; -D I2S_CKPIN=19 +; +; Use PWM fan usermod +; -D USERMOD_PWM_FAN +; -D TACHO_PIN=33 +; -D PWM_PIN=32 +; +; Use built-in or custom LED as a status indicator (assumes LED is connected to GPIO16) +; -D STATUSLED=16 +; +; set the name of the module - make sure there is a quote-backslash-quote before the name and a backslash-quote-quote after the name +; -D SERVERNAME="\"WLED\"" +; +; set the number of LEDs +; -D DEFAULT_LED_COUNT=30 +; or this for multiple outputs +; -D PIXEL_COUNTS=30,30 +; +; set milliampere limit when using ESP pin to power leds +; -D ABL_MILLIAMPS_DEFAULT=850 +; +; enable IR by setting remote type +; -D IRTYPE=0 ;0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote +; +; set default color order of your led strip +; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB +; +; use PSRAM if a device (ESP) has one +; -DBOARD_HAS_PSRAM +; -D WLED_USE_PSRAM +; +; configure I2C and SPI interface (for various hardware) +; -D I2CSDAPIN=33 # initialise interface +; -D I2CSCLPIN=35 # initialise interface +; -D HW_PIN_SCL=35 +; -D HW_PIN_SDA=33 +; -D HW_PIN_CLOCKSPI=7 +; -D HW_PIN_DATASPI=11 +; -D HW_PIN_MISOSPI=9 + + + +# ------------------------------------------------------------------------------ +# PRE-CONFIGURED DEVELOPMENT BOARDS AND CONTROLLERS +# ------------------------------------------------------------------------------ + +[env:esp07] +board = esp07 +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} +lib_deps = ${esp8266.lib_deps} + +[env:d1_mini] +board = d1_mini +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +upload_speed = 921600 +board_build.ldscript = ${common.ldscript_4m1m} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} +lib_deps = ${esp8266.lib_deps} +monitor_filters = esp8266_exception_decoder + +[env:heltec_wifi_kit_8] +board = d1_mini +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} +lib_deps = ${esp8266.lib_deps} + +[env:h803wf] +board = d1_mini +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 LEDPIN=1 -D WLED_DISABLE_INFRARED +lib_deps = ${esp8266.lib_deps} + +[env:esp32dev_qio80] +board = esp32dev +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_qio80 #-D WLED_DISABLE_BROWNOUT_DET +lib_deps = ${esp32.lib_deps} +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = qio + +[env:esp32dev_V4_dio80] +;; experimental ESP32 env using ESP-IDF V4.4.x +;; Warning: this build environment is not stable!! +;; please erase your device before installing. +board = esp32dev +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_V4_qio80 #-D WLED_DISABLE_BROWNOUT_DET +lib_deps = ${esp32_idf_V4.lib_deps} +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32_idf_V4.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = dio + +[env:esp32s2_saola] +board = esp32-s2-saola-1 +platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip +platform_packages = +framework = arduino +board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +board_build.flash_mode = qio +upload_speed = 460800 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=S2_saola + ;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work + -DARDUINO_USB_CDC_ON_BOOT=1 +lib_deps = ${esp32s2.lib_deps} + +[env:esp32s3dev_8MB_PSRAM_qspi] +;; ESP32-TinyS3 development board, with 8MB FLASH and PSRAM (memory_type: qio_qspi) +extends = env:esp32s3dev_8MB_PSRAM_opi +;board = um_tinys3 ; -> needs workaround from https://github.com/Aircoookie/WLED/pull/2905#issuecomment-1328049860 +board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support +board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB + +[env:esp8285_4CH_MagicHome] +board = esp8285 +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 +lib_deps = ${esp8266.lib_deps} + +[env:esp8285_H801] +board = esp8285 +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 +lib_deps = ${esp8266.lib_deps} + +[env:d1_mini_5CH_Shojo_PCB] +board = d1_mini +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_SHOJO_PCB +lib_deps = ${esp8266.lib_deps} + +[env:d1_mini_debug] +board = d1_mini +build_type = debug +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} ${common.debug_flags} +lib_deps = ${esp8266.lib_deps} + +[env:d1_mini_ota] +board = d1_mini +upload_protocol = espota +# exchange for your WLED IP +upload_port = "10.10.1.27" +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} +lib_deps = ${esp8266.lib_deps} + +[env:anavi_miracle_controller] +board = d1_mini +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 LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 +lib_deps = ${esp8266.lib_deps} + +[env:esp32c3dev_2MB] +;; for ESP32-C3 boards with 2MB flash (instead of 4MB). +;; this board need a specific partition file. OTA not possible. +extends = esp32c3 +platform = ${esp32c3.platform} +platform_packages = ${esp32c3.platform_packages} +board = esp32-c3-devkitm-1 +build_flags = ${common.build_flags} ${esp32c3.build_flags} #-D WLED_RELEASE_NAME=ESP32-C3 + -D WLED_WATCHDOG_TIMEOUT=0 + -D WLED_DISABLE_OTA + ; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB + -DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip +build_unflags = ${common.build_unflags} +upload_speed = 115200 +lib_deps = ${esp32c3.lib_deps} +board_build.partitions = tools/WLED_ESP32_2MB_noOTA.csv +board_build.flash_mode = dio + +[env:wemos_shield_esp32] +board = esp32dev +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} +upload_speed = 460800 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} + -D LEDPIN=16 + -D RLYPIN=19 + -D BTNPIN=17 + -D IRPIN=18 + -D UWLED_USE_MY_CONFIG + -D USERMOD_DALLASTEMPERATURE + -D USERMOD_FOUR_LINE_DISPLAY + -D TEMPERATURE_PIN=23 + -D USE_ALT_DISPlAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI + -D USERMOD_AUDIOREACTIVE +lib_deps = ${esp32.lib_deps} + OneWire@~2.3.5 + olikraus/U8g2 @ ^2.28.8 + https://github.com/blazoncek/arduinoFFT.git +board_build.partitions = ${esp32.default_partitions} + +[env:m5atom] +board = esp32dev +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39 +lib_deps = ${esp32.lib_deps} +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} +board_build.partitions = ${esp32.default_partitions} + +[env:sp501e] +board = esp_wroom_02 +platform = ${common.platform_wled_default} +board_build.ldscript = ${common.ldscript_2m512k} +build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=1 +lib_deps = ${esp8266.lib_deps} + +[env:sp511e] +board = esp_wroom_02 +platform = ${common.platform_wled_default} +board_build.ldscript = ${common.ldscript_2m512k} +build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 +lib_deps = ${esp8266.lib_deps} + +[env:Athom_RGBCW] ;7w and 5w(GU10) bulbs +board = esp8285 +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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5 + -D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 +lib_deps = ${esp8266.lib_deps} + +[env:Athom_15w_RGBCW] ;15w bulb +board = esp8285 +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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13 + -D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT +lib_deps = ${esp8266.lib_deps} + +[env:Athom_3Pin_Controller] ;small controller with only data +board = esp8285 +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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=-1 -D LEDPIN=1 -D WLED_DISABLE_INFRARED +lib_deps = ${esp8266.lib_deps} + +[env:Athom_4Pin_Controller] ; With clock and data interface +board = esp8285 +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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=12 -D LEDPIN=1 -D WLED_DISABLE_INFRARED +lib_deps = ${esp8266.lib_deps} + +[env:Athom_5Pin_Controller] ;Analog light strip controller +board = esp8285 +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 WLED_RELEASE_NAME=ESP8266 -D BTNPIN=0 -D RLYPIN=-1 DATA_PINS=4,12,14,13 -D WLED_DISABLE_INFRARED +lib_deps = ${esp8266.lib_deps} + +[env:MY9291] +board = esp01_1m +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_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D USERMOD_MY9291 +lib_deps = ${esp8266.lib_deps} + +# ------------------------------------------------------------------------------ +# codm pixel controller board configurations +# codm-controller-0_6 can also be used for the TYWE3S controller +# ------------------------------------------------------------------------------ + +[env:codm-controller-0_6] +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} +lib_deps = ${esp8266.lib_deps} + +[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_4m1m} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} +lib_deps = ${esp8266.lib_deps} + +# ------------------------------------------------------------------------------ +# EleksTube-IPS +# ------------------------------------------------------------------------------ +[env:elekstube_ips] +board = esp32dev +platform = ${esp32.platform} +platform_packages = ${esp32.platform_packages} +upload_speed = 921600 +build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED + -D USERMOD_RTC + -D USERMOD_ELEKSTUBE_IPS + -D LEDPIN=12 + -D RLYPIN=27 + -D BTNPIN=34 + -D DEFAULT_LED_COUNT=6 + # Display config + -D ST7789_DRIVER + -D TFT_WIDTH=135 + -D TFT_HEIGHT=240 + -D CGRAM_OFFSET + -D TFT_SDA_READ + -D TFT_MOSI=23 + -D TFT_SCLK=18 + -D TFT_DC=25 + -D TFT_RST=26 + -D SPI_FREQUENCY=40000000 + -D USER_SETUP_LOADED +monitor_filters = esp32_exception_decoder +lib_deps = + ${esp32.lib_deps} + TFT_eSPI @ ^2.3.70 +board_build.partitions = ${esp32.default_partitions} diff --git a/readme.md b/readme.md index b5100e248..11c1733f8 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control - Up to 250 user presets to save and load colors/effects easily, supports cycling through them. - Presets can be used to automatically execute API calls - Nightlight function (gradually dims down) -- Full OTA software updatability (HTTP + ArduinoOTA), password protectable +- Full OTA software updateability (HTTP + ArduinoOTA), password protectable - Configurable analog clock (Cronixie, 7-segment and EleksTube IPS clock support via usermods) - Configurable Auto Brightness limit for safe operation - Filesystem-based config for easier backup of presets and settings diff --git a/requirements.txt b/requirements.txt index 17eca159a..1c0644f98 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: # # pip-compile # @@ -21,7 +21,9 @@ click==8.1.3 # platformio # uvicorn colorama==0.4.6 - # via platformio + # via + # click + # platformio h11==0.14.0 # via # uvicorn @@ -50,7 +52,7 @@ starlette==0.23.1 # via platformio tabulate==0.9.0 # via platformio -urllib3==1.26.15 +urllib3==1.26.18 # via requests uvicorn==0.20.0 # via platformio diff --git a/tools/cdata-test.js b/tools/cdata-test.js new file mode 100644 index 000000000..55f068073 --- /dev/null +++ b/tools/cdata-test.js @@ -0,0 +1,205 @@ +'use strict'; + +const assert = require('node:assert'); +const { describe, it, before, after } = require('node:test'); +const fs = require('fs'); +const path = require('path'); +const child_process = require('child_process'); +const util = require('util'); +const execPromise = util.promisify(child_process.exec); + +process.env.NODE_ENV = 'test'; // Set the environment to testing +const cdata = require('./cdata.js'); + +describe('Function', () => { + const testFolderPath = path.join(__dirname, 'testFolder'); + const oldFilePath = path.join(testFolderPath, 'oldFile.txt'); + const newFilePath = path.join(testFolderPath, 'newFile.txt'); + + // Create a temporary file before the test + before(() => { + // Create test folder + if (!fs.existsSync(testFolderPath)) { + fs.mkdirSync(testFolderPath); + } + + // Create an old file + fs.writeFileSync(oldFilePath, 'This is an old file.'); + // Modify the 'mtime' to simulate an old file + const oldTime = new Date(); + oldTime.setFullYear(oldTime.getFullYear() - 1); + fs.utimesSync(oldFilePath, oldTime, oldTime); + + // Create a new file + fs.writeFileSync(newFilePath, 'This is a new file.'); + }); + + // delete the temporary files after the test + after(() => { + fs.rmSync(testFolderPath, { recursive: true }); + }); + + describe('isFileNewerThan', async () => { + it('should return true if the file is newer than the provided time', async () => { + const pastTime = Date.now() - 10000; // 10 seconds ago + assert.strictEqual(cdata.isFileNewerThan(newFilePath, pastTime), true); + }); + + it('should return false if the file is older than the provided time', async () => { + assert.strictEqual(cdata.isFileNewerThan(oldFilePath, Date.now()), false); + }); + + it('should throw an exception if the file does not exist', async () => { + assert.throws(() => { + cdata.isFileNewerThan('nonexistent.txt', Date.now()); + }); + }); + }); + + describe('isAnyFileInFolderNewerThan', async () => { + it('should return true if a file in the folder is newer than the given time', async () => { + const time = fs.statSync(path.join(testFolderPath, 'oldFile.txt')).mtime; + assert.strictEqual(cdata.isAnyFileInFolderNewerThan(testFolderPath, time), true); + }); + + it('should return false if no files in the folder are newer than the given time', async () => { + assert.strictEqual(cdata.isAnyFileInFolderNewerThan(testFolderPath, new Date()), false); + }); + + it('should throw an exception if the folder does not exist', async () => { + assert.throws(() => { + cdata.isAnyFileInFolderNewerThan('nonexistent', new Date()); + }); + }); + }); +}); + +describe('Script', () => { + const folderPath = 'wled00'; + const dataPath = path.join(folderPath, 'data'); + + before(() => { + process.env.NODE_ENV = 'production'; + // Backup files + fs.cpSync("wled00/data", "wled00Backup", { recursive: true }); + fs.cpSync("tools/cdata.js", "cdata.bak.js"); + }); + after(() => { + // Restore backup + fs.rmSync("wled00/data", { recursive: true }); + fs.renameSync("wled00Backup", "wled00/data"); + fs.rmSync("tools/cdata.js"); + fs.renameSync("cdata.bak.js", "tools/cdata.js"); + }); + + // delete all html_*.h files + async function deleteBuiltFiles() { + const files = await fs.promises.readdir(folderPath); + await Promise.all(files.map(file => { + if (file.startsWith('html_') && path.extname(file) === '.h') { + return fs.promises.unlink(path.join(folderPath, file)); + } + })); + } + + // check if html_*.h files were created + async function checkIfBuiltFilesExist() { + const files = await fs.promises.readdir(folderPath); + const htmlFiles = files.filter(file => file.startsWith('html_') && path.extname(file) === '.h'); + assert(htmlFiles.length > 0, 'html_*.h files were not created'); + } + + async function runAndCheckIfBuiltFilesExist() { + await execPromise('node tools/cdata.js'); + await checkIfBuiltFilesExist(); + } + + async function checkIfFileWasNewlyCreated(file) { + const modifiedTime = fs.statSync(file).mtimeMs; + assert(Date.now() - modifiedTime < 500, file + ' was not modified'); + } + + async function testFileModification(sourceFilePath, resultFile) { + // run cdata.js to ensure html_*.h files are created + await execPromise('node tools/cdata.js'); + + // modify file + fs.appendFileSync(sourceFilePath, ' '); + // delay for 1 second to ensure the modified time is different + await new Promise(resolve => setTimeout(resolve, 1000)); + + // run script cdata.js again and wait for it to finish + await execPromise('node tools/cdata.js'); + + checkIfFileWasNewlyCreated(path.join(folderPath, resultFile)); + } + + describe('should build if', () => { + it('html_*.h files are missing', async () => { + await deleteBuiltFiles(); + await runAndCheckIfBuiltFilesExist(); + }); + + it('only one html_*.h file is missing', async () => { + // run script cdata.js and wait for it to finish + await execPromise('node tools/cdata.js'); + + // delete a random html_*.h file + let files = await fs.promises.readdir(folderPath); + let htmlFiles = files.filter(file => file.startsWith('html_') && path.extname(file) === '.h'); + const randomFile = htmlFiles[Math.floor(Math.random() * htmlFiles.length)]; + await fs.promises.unlink(path.join(folderPath, randomFile)); + + await runAndCheckIfBuiltFilesExist(); + }); + + it('script was executed with -f or --force', async () => { + await execPromise('node tools/cdata.js'); + await new Promise(resolve => setTimeout(resolve, 1000)); + await execPromise('node tools/cdata.js --force'); + await checkIfFileWasNewlyCreated(path.join(folderPath, 'html_ui.h')); + await new Promise(resolve => setTimeout(resolve, 1000)); + await execPromise('node tools/cdata.js -f'); + await checkIfFileWasNewlyCreated(path.join(folderPath, 'html_ui.h')); + }); + + it('a file changes', async () => { + await testFileModification(path.join(dataPath, 'index.htm'), 'html_ui.h'); + }); + + it('a inlined file changes', async () => { + await testFileModification(path.join(dataPath, 'index.js'), 'html_ui.h'); + }); + + it('a settings file changes', async () => { + await testFileModification(path.join(dataPath, 'settings_leds.htm'), 'html_ui.h'); + }); + + it('the favicon changes', async () => { + await testFileModification(path.join(dataPath, 'favicon.ico'), 'html_ui.h'); + }); + + it('cdata.js changes', async () => { + await testFileModification('tools/cdata.js', 'html_ui.h'); + }); + }); + + describe('should not build if', () => { + it('the files are already built', async () => { + await deleteBuiltFiles(); + + // run script cdata.js and wait for it to finish + let startTime = Date.now(); + await execPromise('node tools/cdata.js'); + const firstRunTime = Date.now() - startTime; + + // run script cdata.js and wait for it to finish + startTime = Date.now(); + await execPromise('node tools/cdata.js'); + const secondRunTime = Date.now() - startTime; + + // check if second run was faster than the first (must be at least 2x faster) + assert(secondRunTime < firstRunTime / 2, 'html_*.h files were rebuilt'); + }); + }); +}); \ No newline at end of file diff --git a/tools/cdata.js b/tools/cdata.js index 4b0d15d4f..ef7e06f41 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -16,28 +16,59 @@ */ const fs = require("fs"); -const path = require('path'); +const path = require("path"); const inliner = require("inliner"); const zlib = require("zlib"); const CleanCSS = require("clean-css"); -const MinifyHTML = require("html-minifier-terser").minify; +const minifyHtml = require("html-minifier-terser").minify; const packageJson = require("../package.json"); +// Export functions for testing +module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan }; + const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_pxmagic.h", "wled00/html_settings.h", "wled00/html_other.h"] -/** - * +// \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset +const wledBanner = ` +\t\x1b[34m## ## ## ######## ######## +\t\x1b[34m## ## ## ## ## ## ## +\t\x1b[34m## ## ## ## ## ## ## +\t\x1b[34m## ## ## ## ###### ## ## +\t\x1b[34m## ## ## ## ## ## ## +\t\x1b[34m## ## ## ## ## ## ## +\t\x1b[34m ### ### ######## ######## ######## +\t\t\x1b[36mbuild script for web UI +\x1b[0m`; + +const singleHeader = `/* + * Binary array for the Web UI. + * gzip is used for smaller size and improved speeds. + * + * Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui + * to find out how to easily modify the web UI source! */ -function hexdump(buffer,isHex=false) { + +`; + +const multiHeader = `/* + * More web UI HTML source arrays. + * This file is auto generated, please don't make any changes manually. + * + * Instead, see https://kno.wled.ge/advanced/custom-features/#changing-web-ui + * to find out how to easily modify the web UI source! + */ +`; + +function hexdump(buffer, isHex = false) { let lines = []; - for (let i = 0; i < buffer.length; i +=(isHex?32:16)) { + for (let i = 0; i < buffer.length; i += (isHex ? 32 : 16)) { var block; let hexArray = []; if (isHex) { block = buffer.slice(i, i + 32) - for (let j = 0; j < block.length; j +=2 ) { - hexArray.push("0x" + block.slice(j,j+2)) + for (let j = 0; j < block.length; j += 2) { + hexArray.push("0x" + block.slice(j, j + 2)) } } else { block = buffer.slice(i, i + 16); // cut buffer into blocks of 16 @@ -54,183 +85,112 @@ function hexdump(buffer,isHex=false) { return lines.join(",\n"); } -function strReplace(str, search, replacement) { - return str.split(search).join(replacement); -} - function adoptVersionAndRepo(html) { let repoUrl = packageJson.repository ? packageJson.repository.url : undefined; if (repoUrl) { repoUrl = repoUrl.replace(/^git\+/, ""); repoUrl = repoUrl.replace(/\.git$/, ""); - // Replace we - html = strReplace(html, "https://github.com/atuline/WLED", repoUrl); - html = strReplace(html, "https://github.com/Aircoookie/WLED", repoUrl); + html = html.replaceAll("https://github.com/atuline/WLED", repoUrl); + html = html.replaceAll("https://github.com/Aircoookie/WLED", repoUrl); } let version = packageJson.version; if (version) { - html = strReplace(html, "##VERSION##", version); + html = html.replaceAll("##VERSION##", version); } return html; } -function filter(str, type) { - str = adoptVersionAndRepo(str); - if (type === undefined) { +async function minify(str, type = "plain") { + const options = { + collapseWhitespace: true, + collapseBooleanAttributes: true, + collapseInlineTagWhitespace: true, + minifyCSS: true, + minifyJS: true, + removeAttributeQuotes: true, + removeComments: true, + sortAttributes: true, + sortClassName: true, + }; + + if (type == "plain") { return str; } else if (type == "css-minify") { return new CleanCSS({}).minify(str).styles; } else if (type == "js-minify") { - return MinifyHTML('', { - collapseWhitespace: true, - minifyJS: true, - continueOnParseError: false, - removeComments: true, - }).replace(/<[\/]*script>/g,''); + return await minifyHtml('', options).replace(/<[\/]*script>/g, ''); } else if (type == "html-minify") { - return MinifyHTML(str, { - collapseWhitespace: true, - maxLineLength: 80, - minifyCSS: true, - minifyJS: true, - continueOnParseError: false, - removeComments: true, - }); - } else if (type == "html-minify-ui") { - return MinifyHTML(str, { - collapseWhitespace: true, - conservativeCollapse: true, - maxLineLength: 80, - minifyCSS: true, - minifyJS: true, - continueOnParseError: false, - removeComments: true, - }); - } else { - console.warn("Unknown filter: " + type); - return str; + return await minifyHtml(str, options); } + + throw new Error("Unknown filter: " + type); } -function writeHtmlGzipped(sourceFile, resultFile, page) { +async function writeHtmlGzipped(sourceFile, resultFile, page) { console.info("Reading " + sourceFile); - new inliner(sourceFile, function (error, html) { - console.info("Inlined " + html.length + " characters"); - html = filter(html, "html-minify-ui"); - console.info("Minified to " + html.length + " characters"); - - if (error) { - console.warn(error); - throw error; - } + new inliner(sourceFile, async function (error, html) { + if (error) throw error; html = adoptVersionAndRepo(html); - zlib.gzip(html, { level: zlib.constants.Z_BEST_COMPRESSION }, function (error, result) { - if (error) { - console.warn(error); - throw error; - } - - console.info("Compressed " + result.length + " bytes"); - const array = hexdump(result); - const src = `/* - * Binary array for the Web UI. - * gzip is used for smaller size and improved speeds. - * - * Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui - * to find out how to easily modify the web UI source! - */ - -// Autogenerated from ${sourceFile}, do not edit!! -const uint16_t PAGE_${page}_L = ${result.length}; -const uint8_t PAGE_${page}[] PROGMEM = { -${array} -}; -`; - console.info("Writing " + resultFile); - fs.writeFileSync(resultFile, src); - }); + const originalLength = html.length; + html = await minify(html, "html-minify"); + const result = zlib.gzipSync(html, { level: zlib.constants.Z_BEST_COMPRESSION }); + console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes"); + const array = hexdump(result); + let src = singleHeader; + src += `const uint16_t PAGE_${page}_L = ${result.length};\n`; + src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`; + console.info("Writing " + resultFile); + fs.writeFileSync(resultFile, src); }); } -function specToChunk(srcDir, s) { - if (s.method == "plaintext") { - const buf = fs.readFileSync(srcDir + "/" + s.file); - const str = buf.toString("utf-8"); - const chunk = ` -// Autogenerated from ${srcDir}/${s.file}, do not edit!! -const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter(str, s.filter)}${ - s.append || "" - }"; +async function specToChunk(srcDir, s) { + const buf = fs.readFileSync(srcDir + "/" + s.file); + let chunk = `\n// Autogenerated from ${srcDir}/${s.file}, do not edit!!\n` -`; - return s.mangle ? s.mangle(chunk) : chunk; - } else if (s.method == "gzip") { - const buf = fs.readFileSync(srcDir + "/" + s.file); - var str = buf.toString('utf-8'); - if (s.mangle) str = s.mangle(str); - const zip = zlib.gzipSync(filter(str, s.filter), { level: zlib.constants.Z_BEST_COMPRESSION }); - const result = hexdump(zip.toString('hex'), true); - const chunk = ` -// Autogenerated from ${srcDir}/${s.file}, do not edit!! -const uint16_t ${s.name}_length = ${zip.length}; -const uint8_t ${s.name}[] PROGMEM = { -${result} -}; - -`; - return chunk; - } else if (s.method == "binary") { - const buf = fs.readFileSync(srcDir + "/" + s.file); - const result = hexdump(buf); - const chunk = ` -// Autogenerated from ${srcDir}/${s.file}, do not edit!! -const uint16_t ${s.name}_length = ${buf.length}; -const uint8_t ${s.name}[] PROGMEM = { -${result} -}; - -`; - return chunk; - } else { - console.warn("Unknown method: " + s.method); - return undefined; - } -} - -function writeChunks(srcDir, specs, resultFile) { - let src = `/* - * More web UI HTML source arrays. - * This file is auto generated, please don't make any changes manually. - * Instead, see https://kno.wled.ge/advanced/custom-features/#changing-web-ui - * to find out how to easily modify the web UI source! - */ -`; - specs.forEach((s) => { - const file = srcDir + "/" + s.file; - try { - console.info("Reading " + file + " as " + s.name); - src += specToChunk(srcDir, s); - } catch (e) { - console.warn( - "Failed " + s.name + " from " + file, - e.message.length > 60 ? e.message.substring(0, 60) : e.message - ); + if (s.method == "plaintext" || s.method == "gzip") { + let str = buf.toString("utf-8"); + str = adoptVersionAndRepo(str); + const originalLength = str.length; + if (s.method == "gzip") { + if (s.mangle) str = s.mangle(str); + const zip = zlib.gzipSync(await minify(str, s.filter), { level: zlib.constants.Z_BEST_COMPRESSION }); + console.info("Minified and compressed " + s.file + " from " + originalLength + " to " + zip.length + " bytes"); + const result = hexdump(zip); + chunk += `const uint16_t ${s.name}_length = ${zip.length};\n`; + chunk += `const uint8_t ${s.name}[] PROGMEM = {\n${result}\n};\n\n`; + return chunk; + } else { + const minified = await minify(str, s.filter); + console.info("Minified " + s.file + " from " + originalLength + " to " + minified.length + " bytes"); + chunk += `const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${minified}${s.append || ""}";\n\n`; + return s.mangle ? s.mangle(chunk) : chunk; } - }); + } else if (s.method == "binary") { + const result = hexdump(buf); + chunk += `const uint16_t ${s.name}_length = ${buf.length};\n`; + chunk += `const uint8_t ${s.name}[] PROGMEM = {\n${result}\n};\n\n`; + return chunk; + } + + throw new Error("Unknown method: " + s.method); +} + +async function writeChunks(srcDir, specs, resultFile) { + let src = multiHeader; + for (const s of specs) { + console.info("Reading " + srcDir + "/" + s.file + " as " + s.name); + src += await specToChunk(srcDir, s); + } console.info("Writing " + src.length + " characters into " + resultFile); fs.writeFileSync(resultFile, src); } // Check if a file is newer than a given time function isFileNewerThan(filePath, time) { - try { - const stats = fs.statSync(filePath); - return stats.mtimeMs > time; - } catch (e) { - console.error(`Failed to get stats for file ${filePath}:`, e); - return false; - } + const stats = fs.statSync(filePath); + return stats.mtimeMs > time; } // Check if any file in a folder (or its subfolders) is newer than a given time @@ -248,21 +208,30 @@ function isAnyFileInFolderNewerThan(folderPath, time) { return false; } +// Check if the web UI is already built function isAlreadyBuilt(folderPath) { let lastBuildTime = Infinity; for (const file of output) { try { lastBuildTime = Math.min(lastBuildTime, fs.statSync(file).mtimeMs); - } - catch (e) { + } catch (e) { + if (e.code !== 'ENOENT') throw e; + console.info("File " + file + " does not exist. Rebuilding..."); return false; } } - return !isAnyFileInFolderNewerThan(folderPath, lastBuildTime); + return !isAnyFileInFolderNewerThan(folderPath, lastBuildTime) && !isFileNewerThan("tools/cdata.js", lastBuildTime); } +// Don't run this script if we're in a test environment +if (process.env.NODE_ENV === 'test') { + return; +} + +console.info(wledBanner); + if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.argv[2] !== '-f') { console.info("Web UI is already built"); return; @@ -283,7 +252,7 @@ writeChunks( filter: "css-minify", mangle: (str) => str - .replace("%%","%") + .replace("%%", "%") }, { file: "settings.htm", diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 147497211..8953756d3 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -133,7 +133,7 @@ class Animated_Staircase : public Usermod { * received within this time, an object is detected * and the function will return true. * - * The speed of sound is 343 meters per second at 20 degress Celcius. + * The speed of sound is 343 meters per second at 20 degrees Celsius. * Since the sound has to travel back and forth, the detection * distance for the sensor in cm is (0.0343 * maxTimeUs) / 2. * @@ -259,7 +259,7 @@ class Animated_Staircase : public Usermod { } } - // send sesnor values to JSON API + // send sensor values to JSON API void writeSensorsToJson(JsonObject& staircase) { staircase[F("top-sensor")] = topSensorRead; staircase[F("bottom-sensor")] = bottomSensorRead; @@ -309,7 +309,7 @@ class Animated_Staircase : public Usermod { seg.setOption(SEG_OPTION_ON, true); } strip.trigger(); // force strip update - stateChanged = true; // inform external dvices/UI of change + stateChanged = true; // inform external devices/UI of change colorUpdated(CALL_MODE_DIRECT_CHANGE); DEBUG_PRINTLN(F("Animated Staircase disabled.")); } @@ -492,7 +492,7 @@ class Animated_Staircase : public Usermod { bottomEchoPin = top[FPSTR(_bottomEcho_pin)] | bottomEchoPin; topMaxDist = top[FPSTR(_topEchoCm)] | topMaxDist; - topMaxDist = min(150,max(30,(int)topMaxDist)); // max distnace ~1.5m (a lag of 9ms may be expected) + topMaxDist = min(150,max(30,(int)topMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected) bottomMaxDist = top[FPSTR(_bottomEchoCm)] | bottomMaxDist; bottomMaxDist = min(150,max(30,(int)bottomMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected) diff --git a/usermods/Animated_Staircase/README.md b/usermods/Animated_Staircase/README.md index 61c1cb2d6..320b744a5 100644 --- a/usermods/Animated_Staircase/README.md +++ b/usermods/Animated_Staircase/README.md @@ -11,7 +11,7 @@ The Animated Staircase can be controlled by the WLED API. Change settings such a speed, on/off time and distance by sending an HTTP request, see below. ## WLED integration -To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://github.com/Aircoookie/WLED/wiki/Compiling-WLED). +To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/). Before compiling, you have to make the following modifications: @@ -38,7 +38,7 @@ Maximum distance for ultrasonic sensor can be configured as the time needed for You _may_ need to use 10k pull-down resistors on the selected PIR pins, depending on the sensor. ## WLED configuration -1. In the WLED UI, confgure a segment for each step. The lowest step of the stairs is the +1. In the WLED UI, configure a segment for each step. The lowest step of the stairs is the lowest segment id. 2. Save your segments into a preset. 3. Ideally, add the preset in the config > LED setup menu to the "apply @@ -91,7 +91,7 @@ To enable the usermod again, use `"enabled":true`. Alternatively you can use _Usermod_ Settings page where you can change other parameters as well. ### Changing animation parameters and detection range of the ultrasonic HC-SR04 sensor -Using _Usermod_ Settings page you can define different usermod parameters, includng sensor pins, delay between segment activation etc. +Using _Usermod_ Settings page you can define different usermod parameters, including sensor pins, delay between segment activation etc. When an ultrasonic sensor is enabled you can enter maximum detection distance in centimeters separately for top and bottom sensors. diff --git a/usermods/BH1750_v2/readme.md b/usermods/BH1750_v2/readme.md index 05a033cf0..6e6c693d4 100644 --- a/usermods/BH1750_v2/readme.md +++ b/usermods/BH1750_v2/readme.md @@ -9,7 +9,7 @@ The luminance is displayed in both the Info section of the web UI, as well as pu - This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`). - Data is published over MQTT - make sure you've enabled the MQTT sync interface. -## Compiliation +## Compilation To enable, compile with `USERMOD_BH1750` defined (e.g. in `platformio_override.ini`) ```ini diff --git a/usermods/BH1750_v2/usermod_bh1750.h b/usermods/BH1750_v2/usermod_bh1750.h index 5e597d015..ede4aabc4 100644 --- a/usermods/BH1750_v2/usermod_bh1750.h +++ b/usermods/BH1750_v2/usermod_bh1750.h @@ -25,7 +25,7 @@ #define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000 #endif -// only report if differance grater than offset value +// only report if difference grater than offset value #ifndef USERMOD_BH1750_OFFSET_VALUE #define USERMOD_BH1750_OFFSET_VALUE 1 #endif @@ -86,7 +86,7 @@ private: StaticJsonDocument<600> doc; - doc[F("name")] = String(serverDescription) + F(" ") + name; + doc[F("name")] = String(serverDescription) + " " + name; doc[F("state_topic")] = topic; doc[F("unique_id")] = String(mqttClientID) + name; if (unitOfMeasurement != "") @@ -98,8 +98,8 @@ private: JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device device[F("name")] = serverDescription; device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); - device[F("manufacturer")] = F("WLED"); - device[F("model")] = F("FOSS"); + device[F("manufacturer")] = F(WLED_BRAND); + device[F("model")] = F(WLED_PRODUCT_NAME); device[F("sw_version")] = versionString; String temp; diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index c7d25ec15..38930da5a 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -31,7 +31,7 @@ private: // set the default pins based on the architecture, these get overridden by Usermod menu settings #ifdef ESP8266 - //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 + //uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8 #endif bool initDone = false; @@ -78,7 +78,7 @@ private: static const char _name[]; static const char _enabled[]; - // Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Farenheit being set in Usermod Menu) + // Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Fahrenheit being set in Usermod Menu) void UpdateBME280Data(int SensorType) { float _temperature, _humidity, _pressure; @@ -160,8 +160,8 @@ private: JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device device[F("name")] = serverDescription; device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); - device[F("manufacturer")] = F("WLED"); - device[F("model")] = F("FOSS"); + device[F("manufacturer")] = F(WLED_BRAND); + device[F("model")] = F(WLED_PRODUCT_NAME); device[F("sw_version")] = versionString; String temp; diff --git a/usermods/Battery/readme.md b/usermods/Battery/readme.md index d55573abe..999c0a541 100644 --- a/usermods/Battery/readme.md +++ b/usermods/Battery/readme.md @@ -19,7 +19,7 @@ If you have an ESP32 board, connect the positive side of the battery to ADC1 (GP - 💯 Displays current battery voltage - 🚥 Displays battery level - 🚫 Auto-off with configurable Threshold -- 🚨 Low power indicator with many configuration posibilities +- 🚨 Low power indicator with many configuration possibilities ## 🎈 Installation @@ -41,7 +41,7 @@ define `USERMOD_BATTERY` in `wled00/my_config.h` | `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | battery check interval. defaults to 30 seconds | | `USERMOD_BATTERY_MIN_VOLTAGE` | v | minimum battery voltage. default is 2.6 (18650 battery standard) | | `USERMOD_BATTERY_MAX_VOLTAGE` | v | maximum battery voltage. default is 4.2 (18650 battery standard) | -| `USERMOD_BATTERY_TOTAL_CAPACITY` | mAh | the capacity of all cells in parralel sumed up | +| `USERMOD_BATTERY_TOTAL_CAPACITY` | mAh | the capacity of all cells in parallel summed up | | `USERMOD_BATTERY_CALIBRATION` | | offset / calibration number, fine tune the measured voltage by the microcontroller | | Auto-Off | --- | --- | | `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | enables auto-off | diff --git a/usermods/Cronixie/usermod_cronixie.h b/usermods/Cronixie/usermod_cronixie.h index 534fd3a7c..671c5d134 100644 --- a/usermods/Cronixie/usermod_cronixie.h +++ b/usermods/Cronixie/usermod_cronixie.h @@ -114,7 +114,7 @@ class UsermodCronixie : public Usermod { //W Week of Month | WW Week of Year //D Day of Week | DD Day Of Month | DDD Day Of Year - DEBUG_PRINT("cset "); + DEBUG_PRINT(F("cset ")); DEBUG_PRINTLN(cronixieDisplay); for (int i = 0; i < 6; i++) @@ -160,7 +160,7 @@ class UsermodCronixie : public Usermod { //case 'v': break; //user var1 } } - DEBUG_PRINT("result "); + DEBUG_PRINT(F("result ")); for (int i = 0; i < 5; i++) { DEBUG_PRINT((int)dP[i]); diff --git a/usermods/DHT/usermod_dht.h b/usermods/DHT/usermod_dht.h index b6142f432..05a7267b5 100644 --- a/usermods/DHT/usermod_dht.h +++ b/usermods/DHT/usermod_dht.h @@ -49,7 +49,7 @@ #endif // how many seconds after boot to take first measurement, 90 seconds -// 90 gives enough time to OTA update firmware if this crashses +// 90 gives enough time to OTA update firmware if this crashes #ifndef USERMOD_DHT_FIRST_MEASUREMENT_AT #define USERMOD_DHT_FIRST_MEASUREMENT_AT 90000 #endif diff --git a/usermods/EXAMPLE_v2/usermod_v2_example.h b/usermods/EXAMPLE_v2/usermod_v2_example.h index 910e0cae2..32374fde2 100644 --- a/usermods/EXAMPLE_v2/usermod_v2_example.h +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -46,7 +46,7 @@ class MyExampleUsermod : public Usermod { static const char _enabled[]; - // any private methods should go here (non-inline methosd should be defined out of class) + // any private methods should go here (non-inline method should be defined out of class) void publishMqtt(const char* state, bool retain = false); // example for publishing MQTT message diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp b/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp index 1ca160501..823ad7472 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp +++ b/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp @@ -15,23 +15,23 @@ OneWire oneWire(13); DallasTemperature sensor(&oneWire); long temptimer = millis(); long lastMeasure = 0; -#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit +#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit // 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 -// --> First choise of cheap I2C OLED 128X32 0.91" +// --> First choice of cheap I2C OLED 128X32 0.91" U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" +// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA // gets called once at boot. Do all initialization that doesn't depend on // network here void userSetup() { sensor.begin(); //Start Dallas temperature sensor u8x8.begin(); - //u8x8.setFlipMode(1); //Uncoment if using WLED Wemos shield + //u8x8.setFlipMode(1); //Un-comment if using WLED Wemos shield 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); @@ -71,7 +71,7 @@ void userLoop() { if (mqtt != nullptr) { sensor.requestTemperatures(); -//Gets prefered temperature scale based on selection in definitions section +//Gets preferred temperature scale based on selection in definitions section #ifdef Celsius float board_temperature = sensor.getTempCByIndex(0); #else @@ -138,11 +138,11 @@ void userLoop() { // First row with Wifi name u8x8.setCursor(1, 0); u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0)); - // Print `~` char to indicate that SSID is longer, than owr dicplay + // Print `~` char to indicate that SSID is longer than our display if (knownSsid.length() > u8x8.getCols()) u8x8.print("~"); - // Second row with IP or Psssword + // Second row with IP or Password u8x8.setCursor(1, 1); // Print password in AP mode and if led is OFF. if (apActive && bri == 0) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp b/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp index d5fd4a0c2..29a4332db 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp +++ b/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp @@ -10,7 +10,7 @@ void UpdateBME280Data(); -#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit +#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit BME280I2C bme; // Default : forced mode, standby time = 1000 ms // Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off, @@ -20,14 +20,14 @@ uint8_t SDA_PIN = 21; #else //ESP8266 boards uint8_t SCL_PIN = 5; uint8_t SDA_PIN = 4; -// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 +// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8 #endif //The SCL and SDA pins are defined here. //ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21 #define U8X8_PIN_SCL SCL_PIN #define U8X8_PIN_SDA SDA_PIN -//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8 +//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8 // If display does not work or looks corrupted check the // constructor reference: @@ -36,9 +36,9 @@ uint8_t SDA_PIN = 4; // https://github.com/olikraus/u8g2/wiki/gallery // --> First choise of cheap I2C OLED 128X32 0.91" U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" +// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91" +// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91" //U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8 // gets called once at boot. Do all initialization that doesn't depend on network here @@ -181,11 +181,11 @@ void userLoop() { // First row with Wifi name u8x8.setCursor(1, 0); u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0)); - // Print `~` char to indicate that SSID is longer, than owr dicplay + // Print `~` char to indicate that SSID is longer than our display if (knownSsid.length() > u8x8.getCols()) u8x8.print("~"); - // Second row with IP or Psssword + // Second row with IP or Password u8x8.setCursor(1, 1); // Print password in AP mode and if led is OFF. if (apActive && bri == 0) diff --git a/usermods/Fix_unreachable_netservices_v2/readme.md b/usermods/Fix_unreachable_netservices_v2/readme.md index 24d5ff5aa..006eaf9f9 100644 --- a/usermods/Fix_unreachable_netservices_v2/readme.md +++ b/usermods/Fix_unreachable_netservices_v2/readme.md @@ -2,7 +2,7 @@ **Attention: This usermod compiles only for ESP8266** -This usermod-v2 modification performs a ping request to a local IP address every 60 seconds. This ensures WLED net services remain accessible in some problematic WLAN environments. +This usermod-v2 modification performs a ping request to a local IP address every 60 seconds. This ensures WLED net services remain accessible in some problematic WiFi environments. The modification works with static or DHCP IP address configuration. @@ -24,7 +24,7 @@ The usermod supports the following state changes: | JSON key | Value range | Description | |-------------|------------------|---------------------------------| -| PingDelayMs | 5000 to 18000000 | Deactivdate/activate the sensor | +| PingDelayMs | 5000 to 18000000 | Deactivate/activate the sensor | Changes also persist after a reboot. diff --git a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h index 180176a2f..3989e7668 100644 --- a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h +++ b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h @@ -15,7 +15,7 @@ private: static const char _enabled[]; static const char _loopInterval[]; - // any private methods should go here (non-inline methosd should be defined out of class) + // any private methods should go here (non-inline method should be defined out of class) void publishMqtt(const char *state, bool retain = false); // example for publishing MQTT message public: diff --git a/usermods/LDR_Dusk_Dawn_v2/README.md b/usermods/LDR_Dusk_Dawn_v2/README.md new file mode 100644 index 000000000..5e33518a9 --- /dev/null +++ b/usermods/LDR_Dusk_Dawn_v2/README.md @@ -0,0 +1,26 @@ +# LDR_Dusk_Dawn_v2 +This usermod will obtain readings from a Light Dependent Resistor (LDR) and will turn on/off specific presets based on those readings. This is useful for exterior lighting situations where you want the lights to only be on when it is dark out. + +# Installation +Add "-D USERMOD_LDR_DUSK_DAWN" to your platformio.ini [common] build_flags and build. + +Example: +``` +[common] +build_flags = + -D USERMOD_LDR_DUSK_DAWN # Enable LDR Dusk Dawn Usermod +``` + +# Usermod Settings +Setting | Description | Default +--- | --- | --- +Enabled | Enable/Disable the LDR functionality. | Disabled +LDR Pin | The analog capable pin your LDR is connected to. | 34 +Threshold Minutes | The number of minutes of consistent readings above/below the on/off threshold before the LED state will change. | 5 +Threshold | The analog read value threshold from the LDR. Readings lower than this number will count towards changing the LED state to off. You can see the current LDR reading by going into the info section when LDR functionality is enabled. | 1000 +On Preset | The WLED preset to be used for the LED on state. | 1 +Off Preset | The WLED preset to be used for the LED off state. | 2 + +## Author +[@jeffwdh](https://github.com/jeffwdh) +jeffwdh@tarball.ca diff --git a/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h b/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h new file mode 100644 index 000000000..393fc2232 --- /dev/null +++ b/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h @@ -0,0 +1,153 @@ +#pragma once +#include "wled.h" + +#ifndef ARDUINO_ARCH_ESP32 + // 8266 does not support analogRead on user selectable pins + #error only ESP32 is supported by usermod LDR_DUSK_DAWN +#endif + +class LDR_Dusk_Dawn_v2 : public Usermod { + private: + // Defaults + bool ldrEnabled = false; + int ldrPin = 34; //A2 on Adafruit Huzzah32 + int ldrThresholdMinutes = 5; // How many minutes of readings above/below threshold until it switches LED state + int ldrThreshold = 1000; // Readings higher than this number will turn off LED. + int ldrOnPreset = 1; // Default "On" Preset + int ldrOffPreset = 2; // Default "Off" Preset + + // Variables + bool initDone = false; + bool ldrEnabledPreviously = false; // Was LDR enabled for the previous check? First check is always no. + int ldrOffCount; // Number of readings above the threshold + int ldrOnCount; // Number of readings below the threshold + int ldrReading = 0; // Last LDR reading + int ldrLEDState; // Current LED on/off state + unsigned long lastMillis = 0; + static const char _name[]; + + public: + void setup() { + // register ldrPin + if ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)) { + if(!pinManager.allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod + else pinMode(ldrPin, INPUT); // alloc success -> configure pin for input + } else ldrEnabled = false; // invalid pin -> disable usermod + initDone = true; + } + + void loop() { + // Only update every 10 seconds + if (millis() - lastMillis > 10000) { + if ( (ldrEnabled == true) + && (ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0) ) { // make sure that pin is valid for analogread() + // Default state is off + if (ldrEnabledPreviously == false) { + applyPreset(ldrOffPreset); + ldrEnabledPreviously = true; + ldrLEDState = 0; + } + + // Get LDR reading and increment counter by number of seconds since last read + ldrReading = analogRead(ldrPin); + if (ldrReading <= ldrThreshold) { + ldrOnCount = ldrOnCount + 10; + ldrOffCount = 0; + } else { + ldrOffCount = ldrOffCount + 10; + ldrOnCount = 0; + } + + if (ldrOnCount >= (ldrThresholdMinutes * 60)) { + ldrOnCount = 0; + // If LEDs were previously off, turn on + if (ldrLEDState == 0) { + applyPreset(ldrOnPreset); + ldrLEDState = 1; + } + } + + if (ldrOffCount >= (ldrThresholdMinutes * 60)) { + ldrOffCount = 0; + // If LEDs were previously on, turn off + if (ldrLEDState == 1) { + applyPreset(ldrOffPreset); + ldrLEDState = 0; + } + } + } else { + // LDR is disabled, reset variables to default + ldrReading = 0; + ldrOnCount = 0; + ldrOffCount = 0; + ldrLEDState = 0; + ldrEnabledPreviously = false; + } + lastMillis = millis(); + } + } + + void addToConfig(JsonObject& root) { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top["Enabled"] = ldrEnabled; + top["LDR Pin"] = ldrPin; + top["Threshold Minutes"] = ldrThresholdMinutes; + top["Threshold"] = ldrThreshold; + top["On Preset"] = ldrOnPreset; + top["Off Preset"] = ldrOffPreset; + } + + bool readFromConfig(JsonObject& root) { + int8_t oldLdrPin = ldrPin; + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); + configComplete &= getJsonValue(top["Enabled"], ldrEnabled); + configComplete &= getJsonValue(top["LDR Pin"], ldrPin); + configComplete &= getJsonValue(top["Threshold Minutes"], ldrThresholdMinutes); + configComplete &= getJsonValue(top["Threshold"], ldrThreshold); + configComplete &= getJsonValue(top["On Preset"], ldrOnPreset); + configComplete &= getJsonValue(top["Off Preset"], ldrOffPreset); + + if (initDone && (ldrPin != oldLdrPin)) { + // pin changed - un-register previous pin, register new pin + if (oldLdrPin >= 0) pinManager.deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN); + setup(); // setup new pin + } + return configComplete; + } + + void addToJsonInfo(JsonObject& root) { + // If "u" object does not exist yet we need to create it + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray LDR_Enabled = user.createNestedArray("LDR dusk/dawn enabled"); + LDR_Enabled.add(ldrEnabled); + if (!ldrEnabled) return; // do not add more if usermod is disabled + + JsonArray LDR_Reading = user.createNestedArray("LDR reading"); + LDR_Reading.add(ldrReading); + + JsonArray LDR_State = user.createNestedArray("LDR turned LEDs on"); + LDR_State.add(bool(ldrLEDState)); + + // Optional debug information: + //JsonArray LDR_On_Count = user.createNestedArray("LDR on count"); + //LDR_On_Count.add(ldrOnCount); + + //JsonArray LDR_Off_Count = user.createNestedArray("LDR off count"); + //LDR_Off_Count.add(ldrOffCount); + + //bool pinValid = ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)); + //if (pinManager.getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false; + //JsonArray LDR_valid = user.createNestedArray(F("LDR pin")); + //LDR_valid.add(ldrPin); + //LDR_valid.add(pinValid ? F(" OK"): F(" invalid")); + } + + uint16_t getId() { + return USERMOD_ID_LDR_DUSK_DAWN; + } +}; + +const char LDR_Dusk_Dawn_v2::_name[] PROGMEM = "LDR_Dusk_Dawn_v2"; diff --git a/usermods/PIR_sensor_switch/readme.md b/usermods/PIR_sensor_switch/readme.md index 574bd06d8..4dfdb07bd 100644 --- a/usermods/PIR_sensor_switch/readme.md +++ b/usermods/PIR_sensor_switch/readme.md @@ -23,7 +23,7 @@ You can also use usermod's off timer instead of sensor's. In such case rotate th ## Usermod installation -**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionaly `-D PIR_SENSOR_PIN=16` to override default pin. You can also change the default off time by adding `-D PIR_SENSOR_OFF_SEC=30`. +**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionally `-D PIR_SENSOR_PIN=16` to override default pin. You can also change the default off time by adding `-D PIR_SENSOR_OFF_SEC=30`. ## API to enable/disable the PIR sensor from outside. For example from another usermod: @@ -31,7 +31,7 @@ To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` an When the PIR sensor state changes an MQTT message is broadcasted with topic `wled/deviceMAC/motion` and message `on` or `off`. Usermod can also be configured to send just the MQTT message but not change WLED state using settings page as well as responding to motion only at night -(assuming NTP and lattitude/longitude are set to determine sunrise/sunset times). +(assuming NTP and latitude/longitude are set to determine sunrise/sunset times). ### There are two options to get access to the usermod instance: @@ -75,6 +75,9 @@ Usermod can be configured via the Usermods settings page. * `mqtt-only` - send only MQTT messages, do not interact with WLED * `off-only` - only trigger presets or turn WLED on/off if WLED is not already on (displaying effect) * `notifications` - enable or disable sending notifications to other WLED instances using Sync button +* `HA-discovery` - enable automatic discovery in Home Assistant +* `override` - override PIR input when WLED state is changed using UI +* `domoticz-idx` - Domoticz virtual switch ID (used with MQTT `domoticz/in`) Have fun - @gegu & @blazoncek @@ -85,9 +88,16 @@ Have fun - @gegu & @blazoncek 2021-11 * Added information about dynamic configuration options -* Added option to temporary enable/disble usermod from WLED UI (Info dialog) +* Added option to temporary enable/disable usermod from WLED UI (Info dialog) 2022-11 * Added compile time option for off timer. * Added Home Assistant autodiscovery MQTT broadcast. * Updated info on compiling. + +2023-?? +* Override option +* Domoticz virtual switch ID (used with MQTT `domoticz/in`) + +2024-02 +* Added compile time option to expand number of PIR sensors (they are logically ORed) `-D PIR_SENSOR_MAX_SENSORS=3` \ 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 2e909ae0e..d66b1b333 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -15,6 +15,9 @@ #define PIR_SENSOR_OFF_SEC 600 #endif +#ifndef PIR_SENSOR_MAX_SENSORS + #define PIR_SENSOR_MAX_SENSORS 1 +#endif /* * This usermod handles PIR sensor states. @@ -50,13 +53,13 @@ private: volatile unsigned long offTimerStart = 0; // off timer start time volatile bool PIRtriggered = false; // did PIR trigger? - bool sensorPinState = LOW; // current PIR sensor pin state - bool initDone = false; // status of initialization - unsigned long lastLoop = 0; + bool initDone = false; // status of initialization + unsigned long lastLoop = 0; + bool sensorPinState[PIR_SENSOR_MAX_SENSORS] = {LOW}; // current PIR sensor pin state // configurable parameters bool enabled = true; // PIR sensor enabled - int8_t PIRsensorPin = PIR_SENSOR_PIN; // PIR sensor pin + int8_t PIRsensorPin[PIR_SENSOR_MAX_SENSORS] = {PIR_SENSOR_PIN}; // PIR sensor pin uint32_t m_switchOffDelay = PIR_SENSOR_OFF_SEC*1000; // delay before switch off after the sensor state goes LOW (10min) uint8_t m_onPreset = 0; // on preset uint8_t m_offPreset = 0; // off preset @@ -101,7 +104,7 @@ private: /** * Read and update PIR sensor state. - * Initilize/reset switch off timer + * Initialize/reset switch off timer */ bool updatePIRsensorState(); @@ -309,8 +312,8 @@ void PIRsensorSwitch::publishHomeAssistantAutodiscovery() JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device device[F("name")] = serverDescription; device[F("ids")] = String(F("wled-sensor-")) + mqttClientID; - device[F("mf")] = "WLED"; - device[F("mdl")] = F("FOSS"); + device[F("mf")] = F(WLED_BRAND); + device[F("mdl")] = F(WLED_PRODUCT_NAME); device[F("sw")] = versionString; sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid); @@ -325,21 +328,29 @@ void PIRsensorSwitch::publishHomeAssistantAutodiscovery() bool PIRsensorSwitch::updatePIRsensorState() { - bool pinState = digitalRead(PIRsensorPin); - if (pinState != sensorPinState) { - sensorPinState = pinState; // change previous state + bool stateChanged = false; + bool allOff = true; + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { + if (PIRsensorPin[i] < 0) continue; - if (sensorPinState == HIGH) { - offTimerStart = 0; - if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); - } else { - // start switch off timer - offTimerStart = millis(); + bool pinState = digitalRead(PIRsensorPin[i]); + if (pinState != sensorPinState[i]) { + sensorPinState[i] = pinState; // change previous state + stateChanged = true; + + if (sensorPinState[i] == HIGH) { + offTimerStart = 0; + allOff = false; + if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); + } } - publishMqtt(sensorPinState == HIGH); - return true; } - return false; + if (stateChanged) { + publishMqtt(!allOff); + // start switch off timer + if (allOff) offTimerStart = millis(); + } + return stateChanged; } bool PIRsensorSwitch::handleOffTimer() @@ -356,18 +367,21 @@ bool PIRsensorSwitch::handleOffTimer() void PIRsensorSwitch::setup() { - if (enabled) { + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { + sensorPinState[i] = LOW; + if (PIRsensorPin[i] < 0) continue; // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { - // PIR Sensor mode INPUT_PULLUP - pinMode(PIRsensorPin, INPUT_PULLUP); - sensorPinState = digitalRead(PIRsensorPin); + if (pinManager.allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) { + // PIR Sensor mode INPUT_PULLDOWN + #ifdef ESP8266 + pinMode(PIRsensorPin[i], PIRsensorPin[i]==16 ? INPUT_PULLDOWN_16 : INPUT_PULLUP); // ESP8266 has INPUT_PULLDOWN on GPIO16 only + #else + pinMode(PIRsensorPin[i], INPUT_PULLDOWN); + #endif + sensorPinState[i] = digitalRead(PIRsensorPin[i]); } else { - if (PIRsensorPin >= 0) { - DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed.")); - } - PIRsensorPin = -1; // allocation failed - enabled = false; + DEBUG_PRINT(F("PIRSensorSwitch pin ")); DEBUG_PRINTLN(i); DEBUG_PRINTLN(F(" allocation failed.")); + PIRsensorPin[i] = -1; // allocation failed } } initDone = true; @@ -382,8 +396,8 @@ void PIRsensorSwitch::onMqttConnect(bool sessionPresent) void PIRsensorSwitch::loop() { - // only check sensors 4x/s - if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; + // only check sensors 5x/s + if (!enabled || millis() - lastLoop < 200) return; lastLoop = millis(); if (!updatePIRsensorState()) { @@ -396,37 +410,35 @@ void PIRsensorSwitch::addToJsonInfo(JsonObject &root) JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); + bool state = LOW; + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) + if (PIRsensorPin[i] >= 0) state |= sensorPinState[i]; + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); String uiDomString; if (enabled) { - if (offTimerStart > 0) - { + if (offTimerStart > 0) { uiDomString = ""; unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000; - if (offSeconds >= 3600) - { + if (offSeconds >= 3600) { uiDomString += (offSeconds / 3600); uiDomString += F("h "); offSeconds %= 3600; } - if (offSeconds >= 60) - { + if (offSeconds >= 60) { uiDomString += (offSeconds / 60); offSeconds %= 60; - } - else if (uiDomString.length() > 0) - { + } else if (uiDomString.length() > 0) { uiDomString += 0; } - if (uiDomString.length() > 0) - { + if (uiDomString.length() > 0) { uiDomString += F("min "); } uiDomString += (offSeconds); infoArr.add(uiDomString + F("s")); } else { - infoArr.add(sensorPinState ? F("sensor on") : F("inactive")); + infoArr.add(state ? F("sensor on") : F("inactive")); } } else { infoArr.add(F("disabled")); @@ -446,9 +458,11 @@ void PIRsensorSwitch::addToJsonInfo(JsonObject &root) uiDomString += F(""); infoArr.add(uiDomString); - JsonObject sensor = root[F("sensor")]; - if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); - sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; + if (enabled) { + JsonObject sensor = root[F("sensor")]; + if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); + sensor[F("motion")] = state || offTimerStart>0 ? true : false; + } } void PIRsensorSwitch::onStateChange(uint8_t mode) { @@ -478,7 +492,8 @@ void PIRsensorSwitch::addToConfig(JsonObject &root) JsonObject top = root.createNestedObject(FPSTR(_name)); top[FPSTR(_enabled)] = enabled; top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000; - top["pin"] = PIRsensorPin; + JsonArray pinArray = top.createNestedArray("pin"); + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) pinArray.add(PIRsensorPin[i]); top[FPSTR(_onPreset)] = m_onPreset; top[FPSTR(_offPreset)] = m_offPreset; top[FPSTR(_nightTime)] = m_nightTimeOnly; @@ -494,12 +509,20 @@ void PIRsensorSwitch::appendConfigData() { oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { + char str[128]; + sprintf_P(str, PSTR("addInfo('PIRsensorSwitch:pin[]',%d,'','#%d');"), i, i); + oappend(str); + } } bool PIRsensorSwitch::readFromConfig(JsonObject &root) { - bool oldEnabled = enabled; - int8_t oldPin = PIRsensorPin; + int8_t oldPin[PIR_SENSOR_MAX_SENSORS]; + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { + oldPin[i] = PIRsensorPin[i]; + PIRsensorPin[i] = -1; + } DEBUG_PRINT(FPSTR(_name)); JsonObject top = root[FPSTR(_name)]; @@ -508,7 +531,13 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) return false; } - PIRsensorPin = top["pin"] | PIRsensorPin; + JsonArray pins = top["pin"]; + if (!pins.isNull()) { + for (size_t i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) + if (i < pins.size()) PIRsensorPin[i] = pins[i] | PIRsensorPin[i]; + } else { + PIRsensorPin[0] = top["pin"] | oldPin[0]; + } enabled = top[FPSTR(_enabled)] | enabled; @@ -530,26 +559,11 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) // reading config prior to setup() DEBUG_PRINTLN(F(" config loaded.")); } else { - if (oldPin != PIRsensorPin || oldEnabled != enabled) { - // check if pin is OK - if (oldPin != PIRsensorPin && oldPin >= 0) { - // if we are changing pin in settings page - // deallocate old pin - pinManager.deallocatePin(oldPin, PinOwner::UM_PIR); - if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { - pinMode(PIRsensorPin, INPUT_PULLUP); - } else { - // allocation failed - PIRsensorPin = -1; - enabled = false; - } - } - if (enabled) { - sensorPinState = digitalRead(PIRsensorPin); - } - } + for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) + if (oldPin[i] >= 0) pinManager.deallocatePin(oldPin[i], PinOwner::UM_PIR); + setup(); DEBUG_PRINTLN(F(" config (re)loaded.")); } // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_domoticzIDX)].isNull(); + return !(pins.isNull() || pins.size() != PIR_SENSOR_MAX_SENSORS); } diff --git a/usermods/PWM_fan/readme.md b/usermods/PWM_fan/readme.md index 1fbfe0e6c..6a44acf3b 100644 --- a/usermods/PWM_fan/readme.md +++ b/usermods/PWM_fan/readme.md @@ -5,7 +5,7 @@ v2 Usermod to to control PWM fan with RPM feedback and temperature control This usermod requires the Dallas Temperature usermod to obtain temperature information. If it's not available, the fan will run at 100% speed. If the fan does not have _tachometer_ (RPM) output you can set the _tachometer-pin_ to -1 to disable that feature. -You can also set the thershold temperature at which fan runs at lowest speed. If the measured temperature is 3°C greater than the threshold temperature, the fan will run at 100%. +You can also set the threshold temperature at which fan runs at lowest speed. If the measured temperature is 3°C greater than the threshold temperature, the fan will run at 100%. If the _tachometer_ is supported, the current speed (in RPM) will be displayed on the WLED Info page. @@ -22,7 +22,7 @@ This includes: * PWM output pin (can be configured at compile time `-D PWM_PIN=xx`) * tachometer input pin (can be configured at compile time `-D TACHO_PIN=xx`) * sampling frequency in seconds -* threshold temperature in degees C +* threshold temperature in degrees Celsius _NOTE:_ You may also need to tweak Dallas Temperature usermod sampling frequency to match PWM fan sampling frequency. diff --git a/usermods/RelayBlinds/usermod.cpp b/usermods/RelayBlinds/usermod.cpp index ee61b0cce..911053099 100644 --- a/usermods/RelayBlinds/usermod.cpp +++ b/usermods/RelayBlinds/usermod.cpp @@ -43,12 +43,12 @@ void handleRelay() digitalWrite(PIN_UP_RELAY, LOW); upActiveBefore = true; upStartTime = millis(); - DEBUG_PRINTLN("UPA"); + DEBUG_PRINTLN(F("UPA")); } if (millis()- upStartTime > PIN_ON_TIME) { upActive = false; - DEBUG_PRINTLN("UPN"); + DEBUG_PRINTLN(F("UPN")); } } else if (upActiveBefore) { diff --git a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h index 60861e4c5..c09b5d907 100644 --- a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h +++ b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h @@ -30,7 +30,7 @@ #define USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE 10000.0f #endif -// only report if differance grater than offset value +// only report if difference grater than offset value #ifndef USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE #define USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE 5 #endif @@ -119,7 +119,7 @@ public: } else { - DEBUG_PRINTLN("Missing MQTT connection. Not publishing data"); + DEBUG_PRINTLN(F("Missing MQTT connection. Not publishing data")); } } #endif diff --git a/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h b/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h index bdf784844..9f027382d 100644 --- a/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h +++ b/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h @@ -93,8 +93,8 @@ class Si7021_MQTT_HA : public Usermod JsonObject device = doc.createNestedObject("device"); // attach the sensor to the same device device["name"] = String(serverDescription); - device["model"] = "WLED"; - device["manufacturer"] = "Aircoookie"; + device["model"] = F(WLED_PRODUCT_NAME); + device["manufacturer"] = F(WLED_BRAND); device["identifiers"] = String("wled-") + String(serverDescription); device["sw_version"] = VERSION; diff --git a/usermods/TTGO-T-Display/usermod.cpp b/usermods/TTGO-T-Display/usermod.cpp index b126d40a0..cbba07771 100644 --- a/usermods/TTGO-T-Display/usermod.cpp +++ b/usermods/TTGO-T-Display/usermod.cpp @@ -3,7 +3,7 @@ * This file allows you to add own functionality to WLED more easily * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h) - * bytes 2400+ are currently ununsed, but might be used for future wled features + * bytes 2400+ are currently unused, but might be used for future wled features */ /* @@ -144,7 +144,7 @@ void userLoop() { // First row with Wifi name tft.setCursor(1, 1); tft.print(knownSsid.substring(0, tftcharwidth > 1 ? tftcharwidth - 1 : 0)); - // Print `~` char to indicate that SSID is longer, than our dicplay + // Print `~` char to indicate that SSID is longer than our display if (knownSsid.length() > tftcharwidth) tft.print("~"); diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md index 2657c6c8f..b41e3e119 100644 --- a/usermods/Temperature/readme.md +++ b/usermods/Temperature/readme.md @@ -18,7 +18,7 @@ Copy the example `platformio_override.ini` to the root directory. This file sho * `USERMOD_DALLASTEMPERATURE` - enables this user mod wled00/usermods_list.cpp * `USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL` - number of milliseconds between measurements, defaults to 60000 ms (60s) -All parameters can be configured at runtime via the Usermods settings page, including pin, temperature in degrees Celsius or Farenheit and measurement interval. +All parameters can be configured at runtime via the Usermods settings page, including pin, temperature in degrees Celsius or Fahrenheit and measurement interval. ## Project link diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 4d4f04368..5b6b21d8c 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -57,7 +57,10 @@ class UsermodTemperature : public Usermod { static const char _parasite[]; static const char _parasitePin[]; static const char _domoticzIDX[]; - + static const char _sensor[]; + static const char _temperature[]; + static const char _Temperature[]; + //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 float readDallas(); void requestTemperatures(); @@ -110,9 +113,9 @@ float UsermodTemperature::readDallas() { #ifdef WLED_DEBUG if (OneWire::crc8(data,8) != data[8]) { DEBUG_PRINTLN(F("CRC error reading temperature.")); - for (byte i=0; i < 9; i++) DEBUG_PRINTF("0x%02X ", data[i]); + for (byte i=0; i < 9; i++) DEBUG_PRINTF_P(PSTR("0x%02X "), data[i]); DEBUG_PRINT(F(" => ")); - DEBUG_PRINTF("0x%02X\n", OneWire::crc8(data,8)); + DEBUG_PRINTF_P(PSTR("0x%02X\n"), OneWire::crc8(data,8)); } #endif switch(sensorFound) { @@ -149,7 +152,7 @@ void UsermodTemperature::readTemperature() { temperature = readDallas(); lastMeasurement = millis(); waitingForConversion = false; - //DEBUG_PRINTF("Read temperature %2.1f.\n", temperature); // does not work properly on 8266 + //DEBUG_PRINTF_P(PSTR("Read temperature %2.1f.\n"), temperature); // does not work properly on 8266 DEBUG_PRINT(F("Read temperature ")); DEBUG_PRINTLN(temperature); } @@ -171,7 +174,7 @@ bool UsermodTemperature::findSensor() { case 0x42: // DS28EA00 DEBUG_PRINTLN(F("Sensor found.")); sensorFound = deviceAddress[0]; - DEBUG_PRINTF("0x%02X\n", sensorFound); + DEBUG_PRINTF_P(PSTR("0x%02X\n"), sensorFound); return true; } } @@ -191,9 +194,9 @@ void UsermodTemperature::publishHomeAssistantAutodiscovery() { sprintf_P(buf, PSTR("%s Temperature"), serverDescription); json[F("name")] = buf; strcpy(buf, mqttDeviceTopic); - strcat_P(buf, PSTR("/temperature")); + strcat_P(buf, _Temperature); json[F("state_topic")] = buf; - json[F("device_class")] = F("temperature"); + json[F("device_class")] = FPSTR(_temperature); json[F("unique_id")] = escapedMac.c_str(); json[F("unit_of_measurement")] = F("°C"); payload_size = serializeJson(json, json_str); @@ -272,7 +275,7 @@ void UsermodTemperature::loop() { // dont publish super low temperature as the graph will get messed up // the DallasTemperature library returns -127C or -196.6F when problem // reading the sensor - strcat_P(subuf, PSTR("/temperature")); + strcat_P(subuf, _Temperature); mqtt->publish(subuf, 0, false, String(getTemperatureC()).c_str()); strcat_P(subuf, PSTR("_f")); mqtt->publish(subuf, 0, false, String(getTemperatureF()).c_str()); @@ -335,9 +338,9 @@ void UsermodTemperature::addToJsonInfo(JsonObject& root) { temp.add(getTemperature()); temp.add(getTemperatureUnit()); - JsonObject sensor = root[F("sensor")]; - if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); - temp = sensor.createNestedArray(F("temperature")); + JsonObject sensor = root[FPSTR(_sensor)]; + if (sensor.isNull()) sensor = root.createNestedObject(FPSTR(_sensor)); + temp = sensor.createNestedArray(FPSTR(_temperature)); temp.add(getTemperature()); temp.add(getTemperatureUnit()); } @@ -367,7 +370,7 @@ void UsermodTemperature::addToConfig(JsonObject &root) { JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname top[FPSTR(_enabled)] = enabled; top["pin"] = temperaturePin; // usermodparam - top["degC"] = degC; // usermodparam + top[F("degC")] = degC; // usermodparam top[FPSTR(_readInterval)] = readingInterval / 1000; top[FPSTR(_parasite)] = parasite; top[FPSTR(_parasitePin)] = parasitePin; @@ -393,7 +396,7 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { enabled = top[FPSTR(_enabled)] | enabled; newTemperaturePin = top["pin"] | newTemperaturePin; - degC = top["degC"] | degC; + degC = top[F("degC")] | degC; readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000; readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms parasite = top[FPSTR(_parasite)] | parasite; @@ -444,3 +447,6 @@ const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s"; const char UsermodTemperature::_parasite[] PROGMEM = "parasite-pwr"; const char UsermodTemperature::_parasitePin[] PROGMEM = "parasite-pwr-pin"; const char UsermodTemperature::_domoticzIDX[] PROGMEM = "domoticz-idx"; +const char UsermodTemperature::_sensor[] PROGMEM = "sensor"; +const char UsermodTemperature::_temperature[] PROGMEM = "temperature"; +const char UsermodTemperature::_Temperature[] PROGMEM = "/temperature"; \ No newline at end of file diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp index 78cc32a81..e7d1212a1 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp @@ -34,30 +34,30 @@ uint8_t DALLAS_PIN =23; uint8_t SCL_PIN = 5; uint8_t SDA_PIN = 4; uint8_t DALLAS_PIN =13; -// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 +// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8 #endif //The SCL and SDA pins are defined here. //ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21 #define U8X8_PIN_SCL SCL_PIN #define U8X8_PIN_SDA SDA_PIN -//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8 +//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8 // Dallas sensor reading timer long temptimer = millis(); long lastMeasure = 0; -#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit +#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit // 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 -// --> First choise of cheap I2C OLED 128X32 0.91" +// --> First choice of cheap I2C OLED 128X32 0.91" U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" +// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91" +// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91" //U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8 // gets called once at boot. Do all initialization that doesn't depend on network here void userSetup() { @@ -97,7 +97,7 @@ void userLoop() { //----> Dallas temperature sensor MQTT publishing temptimer = millis(); -// Timer to publishe new temperature every 60 seconds +// Timer to publish new temperature every 60 seconds if (temptimer - lastMeasure > 60000) { lastMeasure = temptimer; @@ -106,7 +106,7 @@ void userLoop() { if (mqtt != nullptr) { // Serial.println(Dallas(DALLAS_PIN,0)); -//Gets prefered temperature scale based on selection in definitions section +//Gets preferred temperature scale based on selection in definitions section #ifdef Celsius int16_t board_temperature = Dallas(DALLAS_PIN,0); #else @@ -173,11 +173,11 @@ void userLoop() { // First row with Wifi name u8x8.setCursor(1, 0); u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0)); - // Print `~` char to indicate that SSID is longer, than owr dicplay + // Print `~` char to indicate that SSID is longer than our display if (knownSsid.length() > u8x8.getCols()) u8x8.print("~"); - // Second row with IP or Psssword + // Second row with IP or Password u8x8.setCursor(1, 1); // Print password in AP mode and if led is OFF. if (apActive && bri == 0) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp index c9d9a527e..ff1cf7e53 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp @@ -6,7 +6,7 @@ void UpdateBME280Data(); -#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit +#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit BME280I2C bme; // Default : forced mode, standby time = 1000 ms // Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off, @@ -16,25 +16,25 @@ uint8_t SDA_PIN = 21; #else //ESP8266 boards uint8_t SCL_PIN = 5; uint8_t SDA_PIN = 4; -// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 +// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8 #endif //The SCL and SDA pins are defined here. //ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21 #define U8X8_PIN_SCL SCL_PIN #define U8X8_PIN_SDA SDA_PIN -//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8 +//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8 // 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 -// --> First choise of cheap I2C OLED 128X32 0.91" +// --> First choice of cheap I2C OLED 128X32 0.91" U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" +// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91" +// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91" //U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8 // gets called once at boot. Do all initialization that doesn't depend on network here @@ -179,11 +179,11 @@ void userLoop() { // First row with Wifi name u8x8.setCursor(1, 0); u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0)); - // Print `~` char to indicate that SSID is longer, than owr dicplay + // Print `~` char to indicate that SSID is longer, than our display if (knownSsid.length() > u8x8.getCols()) u8x8.print("~"); - // Second row with IP or Psssword + // Second row with IP or Password u8x8.setCursor(1, 1); // Print password in AP mode and if led is OFF. if (apActive && bri == 0) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 50555ca54..61915170c 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -4,6 +4,10 @@ #include #include +#ifdef WLED_ENABLE_DMX + #error This audio reactive usermod is not compatible with DMX Out. +#endif + #ifndef ARDUINO_ARCH_ESP32 #error This audio reactive usermod does not support the ESP8266. #endif @@ -75,11 +79,15 @@ static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group // user settable parameters for limitSoundDynamics() -static bool limiterOn = true; // bool: enable / disable dynamics limiter +#ifdef UM_AUDIOREACTIVE_DYNAMICS_LIMITER_OFF +static bool limiterOn = false; // bool: enable / disable dynamics limiter +#else +static bool limiterOn = true; +#endif static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec // user settable options for FFTResult scaling -static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized sqare root +static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root // // AGC presets @@ -114,9 +122,9 @@ static float sampleAgc = 0.0f; // Smoothed AGC sample static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() static uint8_t maxVol = 31; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) -static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData +static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData static unsigned long timeOfPeak = 0; // time of last sample peak detection. -static void detectSamplePeak(void); // peak detection function (needs scaled FFT reasults in vReal[]) +static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) static void autoResetPeak(void); // peak auto-reset function @@ -175,31 +183,20 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT resul // These are the input and output vectors. Input vectors receive computed results from FFT. static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins static float vImag[samplesFFT] = {0.0f}; // imaginary parts -#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT static float windowWeighingFactors[samplesFFT] = {0.0f}; -#endif // Create FFT object -#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT - // lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 - // these options actually cause slow-downs on all esp32 processors, don't use them. - // #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 - // #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 - // Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt() - #define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 - #define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 -#else - // around 40% slower on -S2 - // lib_deps += https://github.com/blazoncek/arduinoFFT.git -#endif +// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 +// these options actually cause slow-downs on all esp32 processors, don't use them. +// #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 +// #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 +// Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt() +#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 +#define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 #include -#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); -#else -static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE); -#endif // Helper functions @@ -208,7 +205,7 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_ return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } -// compute average of several FFT resut bins +// compute average of several FFT result bins static float fftAddAvg(int from, int to) { float result = 0.0f; for (int i = from; i <= to; i++) { @@ -280,28 +277,13 @@ void FFTcode(void * parameter) #endif // run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2) -#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT FFT.dcRemoval(); // remove DC offset FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection FFT.compute( FFTDirection::Forward ); // Compute FFT FFT.complexToMagnitude(); // Compute magnitudes -#else - FFT.DCRemoval(); // let FFT lib remove DC component, so we don't need to care about this in getSamples() - //FFT.Windowing( FFT_WIN_TYP_HAMMING, FFT_FORWARD ); // Weigh data - standard Hamming window - //FFT.Windowing( FFT_WIN_TYP_BLACKMAN, FFT_FORWARD ); // Blackman window - better side freq rejection - //FFT.Windowing( FFT_WIN_TYP_BLACKMAN_HARRIS, FFT_FORWARD );// Blackman-Harris - excellent sideband rejection - FFT.Windowing( FFT_WIN_TYP_FLT_TOP, FFT_FORWARD ); // Flat Top Window - better amplitude accuracy - FFT.Compute( FFT_FORWARD ); // Compute FFT - FFT.ComplexToMagnitude(); // Compute magnitudes -#endif - -#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT - FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant -#else - FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant -#endif + FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects #if defined(WLED_DEBUG) || defined(SR_DEBUG) @@ -326,7 +308,7 @@ void FFTcode(void * parameter) * * Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap. * Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255. - * Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then detetermine the bins. + * Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then determine the bins. * End frequency = Start frequency * multiplier ^ 16 * Multiplier = (End frequency/ Start frequency) ^ 1/16 * Multiplier = 1.320367784 @@ -385,7 +367,7 @@ void FFTcode(void * parameter) } } - // post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling) + // post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling) postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS); #if defined(WLED_DEBUG) || defined(SR_DEBUG) @@ -432,7 +414,7 @@ static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // p // FIR lowpass, to remove high frequency noise float highFilteredSample; if (i < (numSamples-1)) highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*sampleBuffer[i+1]; // smooth out spikes - else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // spcial handling for last sample in array + else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // special handling for last sample in array last_vals[1] = last_vals[0]; last_vals[0] = sampleBuffer[i]; sampleBuffer[i] = highFilteredSample; @@ -614,7 +596,12 @@ class AudioReactive : public Usermod { }; // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) + #ifdef UM_AUDIOREACTIVE_ENABLE + bool enabled = true; + #else bool enabled = false; + #endif + bool initDone = false; bool addPalettes = false; int8_t palettes = 0; @@ -631,7 +618,7 @@ class AudioReactive : public Usermod { // variables used by getSample() and agcAvg() int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed - double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controler. + double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controller. double micLev = 0.0; // Used to convert returned value to have '0' as minimum. A leveller float expAdjF = 0.0f; // Used for exponential filter. float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. @@ -653,6 +640,9 @@ class AudioReactive : public Usermod { // strings to reduce flash memory usage (used more than twice) static const char _name[]; static const char _enabled[]; + static const char _config[]; + static const char _dynamics[]; + static const char _frequency[]; static const char _inputLvl[]; static const char _analogmic[]; static const char _digitalmic[]; @@ -754,13 +744,13 @@ class AudioReactive : public Usermod { * 2. we use two setpoints, one at ~60%, and one at ~80% of the maximum signal * 3. the amplification depends on signal level: * a) normal zone - very slow adjustment - * b) emergency zome (<10% or >90%) - very fast adjustment + * b) emergency zone (<10% or >90%) - very fast adjustment */ void agcAvg(unsigned long the_time) { const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function - float lastMultAgc = multAgc; // last muliplier used + float lastMultAgc = multAgc; // last multiplier used float multAgcTemp = multAgc; // new multiplier float tmpAgc = sampleReal * multAgc; // what-if amplified signal @@ -800,13 +790,13 @@ class AudioReactive : public Usermod { if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) - control_integrated += control_error * 0.002 * 0.25; // 2ms = intgration time; 0.25 for damping + control_integrated += control_error * 0.002 * 0.25; // 2ms = integration time; 0.25 for damping else control_integrated *= 0.9; // spin down that beasty integrator // apply PI Control tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain - if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergy zone + if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower energy zone multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error; multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; } else { // "normal zone" @@ -814,7 +804,7 @@ class AudioReactive : public Usermod { multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; } - // limit amplification again - PI controler sometimes "overshoots" + // limit amplification again - PI controller sometimes "overshoots" //multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32 if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; @@ -844,7 +834,7 @@ class AudioReactive : public Usermod { void getSample() { float sampleAdj; // Gain adjusted sample value - float tmpSample; // An interim sample variable used for calculatioins. + float tmpSample; // An interim sample variable used for calculations. const float weighting = 0.2f; // Exponential filter weighting. Will be adjustable in a future release. const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function @@ -1266,16 +1256,16 @@ class AudioReactive : public Usermod { { #ifdef WLED_DEBUG if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled" - DEBUG_PRINTLN("[AR userLoop] realtime mode active - audio processing suspended."); - DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride)); + DEBUG_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended.")); + DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); } #endif disableSoundProcessing = true; } else { #ifdef WLED_DEBUG if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled" - DEBUG_PRINTLN("[AR userLoop] realtime mode ended - audio processing resumed."); - DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride)); + DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed.")); + DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); } #endif if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping @@ -1299,7 +1289,7 @@ class AudioReactive : public Usermod { // complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second. // softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS //if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) { - //DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay); + // DEBUG_PRINTF_P(PSTR("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n"), userloopDelay); //} #endif @@ -1516,7 +1506,7 @@ class AudioReactive : public Usermod { } else { // Analog or I2S digital input if (audioSource && (audioSource->isInitialized())) { - // audio source sucessfully configured + // audio source successfully configured if (audioSource->getType() == AudioSource::Type_I2SAdc) { infoArr.add(F("ADC analog")); } else { @@ -1694,29 +1684,29 @@ class AudioReactive : public Usermod { #endif JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic)); - dmic[F("type")] = dmType; + dmic["type"] = dmType; JsonArray pinArray = dmic.createNestedArray("pin"); pinArray.add(i2ssdPin); pinArray.add(i2swsPin); pinArray.add(i2sckPin); pinArray.add(mclkPin); - JsonObject cfg = top.createNestedObject("config"); + JsonObject cfg = top.createNestedObject(FPSTR(_config)); cfg[F("squelch")] = soundSquelch; cfg[F("gain")] = sampleGain; cfg[F("AGC")] = soundAgc; - JsonObject dynLim = top.createNestedObject("dynamics"); + JsonObject dynLim = top.createNestedObject(FPSTR(_dynamics)); dynLim[F("limiter")] = limiterOn; dynLim[F("rise")] = attackTime; dynLim[F("fall")] = decayTime; - JsonObject freqScale = top.createNestedObject("frequency"); + JsonObject freqScale = top.createNestedObject(FPSTR(_frequency)); freqScale[F("scale")] = FFTScalingMode; JsonObject sync = top.createNestedObject("sync"); - sync[F("port")] = audioSyncPort; - sync[F("mode")] = audioSyncEnabled; + sync["port"] = audioSyncPort; + sync["mode"] = audioSyncEnabled; } @@ -1764,18 +1754,18 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); - configComplete &= getJsonValue(top["config"][F("squelch")], soundSquelch); - configComplete &= getJsonValue(top["config"][F("gain")], sampleGain); - configComplete &= getJsonValue(top["config"][F("AGC")], soundAgc); + configComplete &= getJsonValue(top[FPSTR(_config)][F("squelch")], soundSquelch); + configComplete &= getJsonValue(top[FPSTR(_config)][F("gain")], sampleGain); + configComplete &= getJsonValue(top[FPSTR(_config)][F("AGC")], soundAgc); - configComplete &= getJsonValue(top["dynamics"][F("limiter")], limiterOn); - configComplete &= getJsonValue(top["dynamics"][F("rise")], attackTime); - configComplete &= getJsonValue(top["dynamics"][F("fall")], decayTime); + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("limiter")], limiterOn); + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("rise")], attackTime); + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("fall")], decayTime); - configComplete &= getJsonValue(top["frequency"][F("scale")], FFTScalingMode); + configComplete &= getJsonValue(top[FPSTR(_frequency)][F("scale")], FFTScalingMode); - configComplete &= getJsonValue(top["sync"][F("port")], audioSyncPort); - configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled); + configComplete &= getJsonValue(top["sync"]["port"], audioSyncPort); + configComplete &= getJsonValue(top["sync"]["mode"], audioSyncEnabled); if (initDone) { // add/remove custom/audioreactive palettes @@ -1946,6 +1936,9 @@ void AudioReactive::fillAudioPalettes() { // strings to reduce flash memory usage (used more than twice) const char AudioReactive::_name[] PROGMEM = "AudioReactive"; const char AudioReactive::_enabled[] PROGMEM = "enabled"; +const char AudioReactive::_config[] PROGMEM = "config"; +const char AudioReactive::_dynamics[] PROGMEM = "dynamics"; +const char AudioReactive::_frequency[] PROGMEM = "frequency"; const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel"; #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 4d4eb7de5..18d00da3c 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -44,7 +44,7 @@ // benefit: analog mic inputs will be sampled contiously -> better response times and less "glitches" // WARNING: this option WILL lock-up your device in case that any other analogRead() operation is performed; // for example if you want to read "analog buttons" -//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continously sample analog ADC microphone. WARNING will cause analogRead() lock-up +//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continuously sample analog ADC microphone. WARNING will cause analogRead() lock-up // data type requested from the I2S driver - currently we always use 32bit //#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible @@ -192,7 +192,7 @@ class I2SSource : public AudioSource { } virtual void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE, int8_t mclkPin = I2S_PIN_NO_CHANGE) { - DEBUGSR_PRINTLN("I2SSource:: initialize()."); + DEBUGSR_PRINTLN(F("I2SSource:: initialize().")); if (i2swsPin != I2S_PIN_NO_CHANGE && i2ssdPin != I2S_PIN_NO_CHANGE) { if (!pinManager.allocatePin(i2swsPin, true, PinOwner::UM_Audioreactive) || !pinManager.allocatePin(i2ssdPin, false, PinOwner::UM_Audioreactive)) { // #206 @@ -378,7 +378,7 @@ class I2SSource : public AudioSource { }; /* ES7243 Microphone - This is an I2S microphone that requires ininitialization over + This is an I2S microphone that requires initialization over I2C before I2S data can be received */ class ES7243 : public I2SSource { @@ -413,7 +413,7 @@ public: }; void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { - DEBUGSR_PRINTLN("ES7243:: initialize();"); + DEBUGSR_PRINTLN(F("ES7243:: initialize();")); if ((i2sckPin < 0) || (mclkPin < 0)) { DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); return; @@ -429,8 +429,8 @@ public: } }; -/* ES8388 Sound Modude - This is an I2S sound processing unit that requires ininitialization over +/* ES8388 Sound Module + This is an I2S sound processing unit that requires initialization over I2C before I2S data can be received. */ class ES8388Source : public I2SSource { @@ -475,7 +475,7 @@ class ES8388Source : public I2SSource { // The mics *and* line-in are BOTH connected to LIN2/RIN2 on the AudioKit // so there's no way to completely eliminate the mics. It's also hella noisy. // Line-in works OK on the AudioKit, generally speaking, as the mics really need - // amplification to be noticable in a quiet room. If you're in a very loud room, + // amplification to be noticeable in a quiet room. If you're in a very loud room, // the mics on the AudioKit WILL pick up sound even in line-in mode. // TL;DR: Don't use the AudioKit for anything, use the LyraT. // @@ -529,7 +529,7 @@ class ES8388Source : public I2SSource { }; void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { - DEBUGSR_PRINTLN("ES8388Source:: initialize();"); + DEBUGSR_PRINTLN(F("ES8388Source:: initialize();")); if ((i2sckPin < 0) || (mclkPin < 0)) { DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); return; @@ -587,7 +587,7 @@ class I2SAdcSource : public I2SSource { AudioSourceType getType(void) {return(Type_I2SAdc);} void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { - DEBUGSR_PRINTLN("I2SAdcSource:: initialize()."); + DEBUGSR_PRINTLN(F("I2SAdcSource:: initialize().")); _myADCchannel = 0x0F; if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { DEBUGSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin); @@ -759,7 +759,7 @@ class SPH0654 : public I2SSource { {} void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE) { - DEBUGSR_PRINTLN("SPH0654:: initialize();"); + DEBUGSR_PRINTLN(F("SPH0654:: initialize();")); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // these registers are only existing in "classic" ESP32 diff --git a/usermods/audioreactive/readme.md b/usermods/audioreactive/readme.md index d9f9ea783..47804b611 100644 --- a/usermods/audioreactive/readme.md +++ b/usermods/audioreactive/readme.md @@ -1,6 +1,6 @@ # Audioreactive usermod -Enabless controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter. +Enables controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter. Supported microphones range from analog (MAX4466, MAX9814, ...) to digital (INMP441, ICS-43434, ...). Does audio processing and provides data structure that specially written effects can use. @@ -19,7 +19,7 @@ This usermod is an evolution of [SR-WLED](https://github.com/atuline/WLED), and ## Supported MCUs This audioreactive usermod works best on "classic ESP32" (dual core), and on ESP32-S3 which also has dual core and hardware floating point support. -It will compile succesfully for ESP32-S2 and ESP32-C3, however might not work well, as other WLED functions will become slow. Audio processing requires a lot of computing power, which can be problematic on smaller MCUs like -S2 and -C3. +It will compile successfully for ESP32-S2 and ESP32-C3, however might not work well, as other WLED functions will become slow. Audio processing requires a lot of computing power, which can be problematic on smaller MCUs like -S2 and -C3. Analog audio is only possible on "classic" ESP32, but not on other MCUs like ESP32-S3. @@ -35,7 +35,7 @@ Customised _arduinoFFT_ library for use with this usermod can be found at https: ### using latest (develop) _arduinoFFT_ library Alternatively, you can use the latest arduinoFFT development version. -ArduinoFFT `develop` library is slightly more accurate, and slighly faster than our customised library, however also needs additional 2kB RAM. +ArduinoFFT `develop` library is slightly more accurate, and slightly faster than our customised library, however also needs additional 2kB RAM. * `build_flags` = `-D USERMOD_AUDIOREACTIVE` `-D UM_AUDIOREACTIVE_USE_NEW_FFT` * `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2` @@ -55,6 +55,11 @@ If you want to define default GPIOs during compile time, use the following (defa - `-D ES7243_SDAPIN` : GPIO for I2C SDA pin on ES7243 microphone (-1) - `-D ES7243_SCLPIN` : GPIO for I2C SCL pin on ES7243 microphone (-1) +Other options: + +- `-D UM_AUDIOREACTIVE_ENABLE` : makes usermod default enabled (not the same as include into build option!) +- `-D UM_AUDIOREACTIVE_DYNAMICS_LIMITER_OFF` : disables rise/fall limiter default + **NOTE** I2S is used for analog audio sampling. Hence, the analog *buttons* (i.e. potentiometers) are disabled when running this usermod with an analog microphone. ### Advanced Compile-Time Options @@ -63,7 +68,7 @@ You can use the following additional flags in your `build_flags` * `-D SR_GAIN=x` : Default "gain" setting (60) * `-D I2S_USE_RIGHT_CHANNEL`: Use RIGHT instead of LEFT channel (not recommended unless you strictly need this). * `-D I2S_USE_16BIT_SAMPLES`: Use 16bit instead of 32bit for internal sample buffers. Reduces sampling quality, but frees some RAM ressources (not recommended unless you absolutely need this). -* `-D I2S_GRAB_ADC1_COMPLETELY`: Experimental: continously sample analog ADC microphone. Only effective on ESP32. WARNING this _will_ cause conflicts(lock-up) with any analogRead() call. +* `-D I2S_GRAB_ADC1_COMPLETELY`: Experimental: continuously sample analog ADC microphone. Only effective on ESP32. WARNING this _will_ cause conflicts(lock-up) with any analogRead() call. * `-D MIC_LOGGER` : (debugging) Logs samples from the microphone to serial USB. Use with serial plotter (Arduino IDE) * `-D SR_DEBUG` : (debugging) Additional error diagnostics and debug info on serial USB. diff --git a/usermods/boblight/boblight.h b/usermods/boblight/boblight.h index b11a22a83..32208a4fa 100644 --- a/usermods/boblight/boblight.h +++ b/usermods/boblight/boblight.h @@ -174,9 +174,9 @@ class BobLightUsermod : public Usermod { #if WLED_DEBUG DEBUG_PRINTLN(F("Fill light data: ")); - DEBUG_PRINTF(" lights %d\n", numLights); + DEBUG_PRINTF_P(PSTR(" lights %d\n"), numLights); for (int i=0; i strip.getLengthTotal() ) { DEBUG_PRINTLN(F("BobLight: Too many lights.")); - DEBUG_PRINTF("%d+%d+%d+%d>%d\n", bottom, left, top, right, strip.getLengthTotal()); + DEBUG_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal()); totalLights = strip.getLengthTotal(); top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f); left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f); @@ -318,7 +318,7 @@ class BobLightUsermod : public Usermod { void addToConfig(JsonObject& root) override { JsonObject umData = root.createNestedObject(FPSTR(_name)); umData[FPSTR(_enabled)] = enabled; - umData[F("port")] = bobPort; + umData[ "port" ] = bobPort; umData[F("top")] = top; umData[F("bottom")] = bottom; umData[F("left")] = left; @@ -334,7 +334,7 @@ class BobLightUsermod : public Usermod { configComplete &= getJsonValue(umData[FPSTR(_enabled)], en); enable(en); - configComplete &= getJsonValue(umData[F("port")], bobPort); + configComplete &= getJsonValue(umData[ "port" ], bobPort); configComplete &= getJsonValue(umData[F("bottom")], bottom, 16); configComplete &= getJsonValue(umData[F("top")], top, 16); configComplete &= getJsonValue(umData[F("left")], left, 9); @@ -392,7 +392,7 @@ void BobLightUsermod::pollBob() { //get data from the client while (bobClient.available()) { String input = bobClient.readStringUntil('\n'); - // DEBUG_PRINT("Client: "); DEBUG_PRINTLN(input); // may be to stressful on Serial + // DEBUG_PRINT(F("Client: ")); DEBUG_PRINTLN(input); // may be to stressful on Serial if (input.startsWith(F("hello"))) { DEBUG_PRINTLN(F("hello")); bobClient.print(F("hello\n")); @@ -445,7 +445,7 @@ void BobLightUsermod::pollBob() { //strip.setPixelColor(light_id, RGBW32(red, green, blue, 0)); setRealtimePixel(light_id, red, green, blue, 0); } // currently no support for interpolation or speed, we just ignore this - } else if (input.startsWith(F("sync"))) { + } else if (input.startsWith("sync")) { BobSync(); } else { // Client sent gibberish diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h index a3301bfff..51dd646c7 100644 --- a/usermods/mpu6050_imu/usermod_mpu6050_imu.h +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -233,7 +233,7 @@ class MPU6050Driver : public Usermod { * Use it to initialize network interfaces */ void connected() { - //DEBUG_PRINTLN("Connected to WiFi!"); + //DEBUG_PRINTLN(F("Connected to WiFi!")); } diff --git a/usermods/mqtt_switch_v2/README.md b/usermods/mqtt_switch_v2/README.md index 148e4a564..744d7fe3c 100644 --- a/usermods/mqtt_switch_v2/README.md +++ b/usermods/mqtt_switch_v2/README.md @@ -50,5 +50,5 @@ This usermod listens on `[mqttDeviceTopic]/switch/0/set` (where 0 is replaced wi Feedback about the current state is provided at `[mqttDeviceTopic]/switch/0/state`. ### Home Assistant auto-discovery -Auto-discovery information is automatically published and you shoudn't have to do anything to register the switches in Home Assistant. +Auto-discovery information is automatically published and you shouldn't have to do anything to register the switches in Home Assistant. diff --git a/usermods/multi_relay/readme.md b/usermods/multi_relay/readme.md index 00addb7e9..24dd394b8 100644 --- a/usermods/multi_relay/readme.md +++ b/usermods/multi_relay/readme.md @@ -2,7 +2,7 @@ This usermod-v2 modification allows the connection of multiple relays, each with individual delay and on/off mode. Usermod supports PCF8574 I2C port expander to reduce GPIO use. -PCF8574 supports 8 outputs and each output corresponds to a relay in WLED (relay 0 = port 0, etc). I you are using more than 8 relays with multiple PCF8574 make sure their addresses are set conscutively (e.g. 0x20 and 0x21). You can set address of first expander in settings. +PCF8574 supports 8 outputs and each output corresponds to a relay in WLED (relay 0 = port 0, etc). I you are using more than 8 relays with multiple PCF8574 make sure their addresses are set in sequence (e.g. 0x20 and 0x21). You can set address of first expander in settings. (**NOTE:** Will require Wire library and global I2C pins defined.) ## HTTP API diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index 22fa6496d..cb1eec8e1 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -104,6 +104,9 @@ class MultiRelay : public Usermod { static const char _HAautodiscovery[]; static const char _pcf8574[]; static const char _pcfAddress[]; + static const char _switch[]; + static const char _toggle[]; + static const char _Command[]; void handleOffTimer(); void InitHtmlAPIHandle(); @@ -220,7 +223,7 @@ class MultiRelay : public Usermod { }; -// class implementetion +// class implementation void MultiRelay::publishMqtt(int relay) { #ifndef WLED_DISABLE_MQTT @@ -261,8 +264,8 @@ void MultiRelay::handleOffTimer() { void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer DEBUG_PRINTLN(F("Relays: Initialize HTML API")); - server.on("/relays", HTTP_GET, [this](AsyncWebServerRequest *request) { - DEBUG_PRINTLN("Relays: HTML API"); + server.on(SET_F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { + DEBUG_PRINTLN(F("Relays: HTML API")); String janswer; String error = ""; //int params = request->params(); @@ -271,9 +274,9 @@ void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsync if (getActiveRelayCount()) { // Commands - if(request->hasParam("switch")) { + if (request->hasParam(FPSTR(_switch))) { /**** Switch ****/ - AsyncWebParameter* p = request->getParam("switch"); + AsyncWebParameter* p = request->getParam(FPSTR(_switch)); // Get Values for (int i=0; ivalue(), ',', i); @@ -284,9 +287,9 @@ void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsync if (_relay[i].external) switchRelay(i, (bool)value); } } - } else if(request->hasParam("toggle")) { + } else if (request->hasParam(FPSTR(_toggle))) { /**** Toggle ****/ - AsyncWebParameter* p = request->getParam("toggle"); + AsyncWebParameter* p = request->getParam(FPSTR(_toggle)); // Get Values for (int i=0;ivalue(), ',', i); @@ -314,7 +317,7 @@ void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsync janswer += error; janswer += F("\","); janswer += F("\"SW Version\":\""); - janswer += String(GEOGABVERSION); + janswer += String(F(GEOGABVERSION)); janswer += F("\"}"); request->send(200, "application/json", janswer); }); @@ -388,7 +391,7 @@ void MultiRelay::switchRelay(uint8_t relay, bool mode) { if (relay>=MULTI_RELAY_MAX_RELAYS || _relay[relay].pin<0) return; _relay[relay].state = mode; if (usePcf8574 && _relay[relay].pin >= 100) { - // we need to send all ouputs at the same time + // we need to send all outputs at the same time uint8_t state = 0; for (int i=0; i 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, PSTR("/command"), 8) == 0) { + if (strlen(topic) > 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, _Command, 8) == 0) { uint8_t relay = strtoul(topic+7, NULL, 10); if (relaysubscribe(buf, 0); json[F("stat_t")] = "~"; @@ -675,8 +678,8 @@ void MultiRelay::addToJsonInfo(JsonObject &root) { uiDomString += F(",on:"); uiDomString += _relay[i].state ? "false" : "true"; uiDomString += F("}});\">"); - uiDomString += F(""); infoArr.add(uiDomString); } @@ -699,11 +702,11 @@ void MultiRelay::addToJsonState(JsonObject &root) { if (_relay[i].pin < 0) continue; JsonObject relay = rel_arr.createNestedObject(); relay[FPSTR(_relay_str)] = i; - relay[F("state")] = _relay[i].state; + relay["state"] = _relay[i].state; } #else multiRelay[FPSTR(_relay_str)] = 0; - multiRelay[F("state")] = _relay[0].state; + multiRelay["state"] = _relay[0].state; #endif } @@ -836,3 +839,6 @@ const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec"; const char MultiRelay::_HAautodiscovery[] PROGMEM = "HA-autodiscovery"; const char MultiRelay::_pcf8574[] PROGMEM = "use-PCF8574"; const char MultiRelay::_pcfAddress[] PROGMEM = "PCF8574-address"; +const char MultiRelay::_switch[] PROGMEM = "switch"; +const char MultiRelay::_toggle[] PROGMEM = "toggle"; +const char MultiRelay::_Command[] PROGMEM = "/command"; diff --git a/usermods/quinled-an-penta/readme.md b/usermods/quinled-an-penta/readme.md index 2338747d6..c1260d913 100644 --- a/usermods/quinled-an-penta/readme.md +++ b/usermods/quinled-an-penta/readme.md @@ -2,7 +2,7 @@ The (un)official usermod to get the best out of the QuinLED-An-Penta (https://quinled.info/quinled-an-penta/), e.g. using the OLED and the SHT30 temperature/humidity sensor. ## Requirements -* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2 +* "u8g2" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2 * "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85 ## Usermod installation diff --git a/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h index 4f51750ac..9b5bd8c88 100644 --- a/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h +++ b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h @@ -85,8 +85,8 @@ private: JsonObject device = doc.createNestedObject("device"); // attach the sensor to the same device device["identifiers"] = String("wled-sensor-") + mqttClientID; - device["manufacturer"] = "Aircoookie"; - device["model"] = "WLED"; + device["manufacturer"] = F(WLED_BRAND); + device["model"] = F(WLED_PRODUCT_NAME); device["sw_version"] = VERSION; device["name"] = mqttClientID; diff --git a/usermods/seven_segment_display/readme.md b/usermods/seven_segment_display/readme.md index a5294701c..792393a83 100644 --- a/usermods/seven_segment_display/readme.md +++ b/usermods/seven_segment_display/readme.md @@ -17,7 +17,7 @@ The number of individual LEDs per segment. 7 segments per digit. #### perPeriod -- ssLEDPerPeriod The number of individual LEDs per period. A ':' (colon) has two periods. #### startIdx -- ssStartLED -Index of the LED the display starts at. Enabless a seven segment display to be in the middle of a string. +Index of the LED the display starts at. Enables a seven segment display to be in the middle of a string. #### timeEnable -- ssTimeEnabled When true, when displayMask is configured for a time output and no message is set, the time will be displayed. #### scrollSpd -- ssScrollSpeed diff --git a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h index e5b726e52..20fef15df 100644 --- a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h +++ b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h @@ -409,7 +409,7 @@ public: if (mqttGroupTopic[0] != 0) { - //subcribe for sevenseg messages on the group topic + //subscribe for sevenseg messages on the group topic sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_sevenSeg); mqtt->subscribe(subBuffer, 2); } @@ -417,7 +417,7 @@ public: bool onMqttMessage(char *topic, char *payload) { - //If topic beings iwth sevenSeg cut it off, otherwise not our message. + //If topic beings with sevenSeg cut it off, otherwise not our message. size_t topicPrefixLen = strlen_P(PSTR("/sevenSeg/")); if (strncmp_P(topic, PSTR("/sevenSeg/"), topicPrefixLen) == 0) topic += topicPrefixLen; diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h index bf0b4703b..5c2fac0d4 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -480,14 +480,14 @@ public: if (mqttGroupTopic[0] != 0) { - //subcribe for sevenseg messages on the group topic + //subscribe for sevenseg messages on the group topic sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_name); mqtt->subscribe(subBuffer, 2); } } bool onMqttMessage(char *topic, char *payload) { - //If topic beings iwth sevenSeg cut it off, otherwise not our message. + //If topic begins with sevenSeg cut it off, otherwise not our message. size_t topicPrefixLen = strlen_P(PSTR("/wledSS/")); if (strncmp_P(topic, PSTR("/wledSS/"), topicPrefixLen) == 0) { topic += topicPrefixLen; diff --git a/usermods/sht/usermod_sht.h b/usermods/sht/usermod_sht.h index 56cea2195..c6e17221b 100644 --- a/usermods/sht/usermod_sht.h +++ b/usermods/sht/usermod_sht.h @@ -290,7 +290,7 @@ void ShtUsermod::loop() /** * Whenever MQTT is connected, publish HA autodiscovery topics. * - * Is only donce once. + * Is only done once. * * @see Usermod::onMqttConnect() * @see UsermodManager::onMqttConnect() diff --git a/usermods/stairway_wipe_basic/readme.md b/usermods/stairway_wipe_basic/readme.md index 35bc0d416..353856b3c 100644 --- a/usermods/stairway_wipe_basic/readme.md +++ b/usermods/stairway_wipe_basic/readme.md @@ -1,5 +1,13 @@ -### Stairway lighting +# Stairway lighting +## Install +Add the buildflag `-D USERMOD_STAIRCASE_WIPE` to your enviroment to activate it. + +### Configuration +`-D STAIRCASE_WIPE_OFF` +
Have the LEDs wipe off instead of fading out + +## Description Quick usermod to accomplish something similar to [this video](https://www.youtube.com/watch?v=NHkju5ncC4A). This usermod enables you to add a lightstrip alongside or on the steps of a staircase. diff --git a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h index 9cc0bea4c..f712316b8 100644 --- a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h +++ b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h @@ -19,10 +19,12 @@ class StairwayWipeUsermod : public Usermod { unsigned long timeStaticStart = 0; uint16_t previousUserVar0 = 0; +//moved to buildflag //comment this out if you want the turn off effect to be just fading out instead of reverse wipe -#define STAIRCASE_WIPE_OFF +//#define STAIRCASE_WIPE_OFF public: - +void setup() { + } void loop() { //userVar0 (U0 in HTTP API): //has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266 @@ -84,7 +86,7 @@ class StairwayWipeUsermod : public Usermod { uint16_t getId() { - return USERMOD_ID_EXAMPLE; + return USERMOD_ID_STAIRWAY_WIPE; } diff --git a/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h b/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h index 61b76ba19..85a9a1605 100644 --- a/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h +++ b/usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h @@ -23,7 +23,7 @@ private: unsigned char Enc_B; unsigned char Enc_A_prev = 0; - // private class memebers configurable by Usermod Settings (defaults set inside readFromConfig()) + // private class members configurable by Usermod Settings (defaults set inside readFromConfig()) int8_t pins[3]; // pins[0] = DT from encoder, pins[1] = CLK from encoder, pins[2] = CLK from encoder (optional) int fadeAmount; // how many points to fade the Neopixel with each step @@ -162,7 +162,7 @@ public: * - configComplete is used to return false if any value is missing, not just if the main object is missing * - The defaults are loaded every time readFromConfig() is run, not just once after boot * - * This ensures that missing values are added to the config, with their default values, in the rare but plauible cases of: + * This ensures that missing values are added to the config, with their default values, in the rare but plausible cases of: * - a single value being missing at boot, e.g. if the Usermod was upgraded and a new setting was added * - a single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) * diff --git a/usermods/usermod_v2_HttpPullLightControl/readme.md b/usermods/usermod_v2_HttpPullLightControl/readme.md new file mode 100644 index 000000000..cf7f971f7 --- /dev/null +++ b/usermods/usermod_v2_HttpPullLightControl/readme.md @@ -0,0 +1,110 @@ +# usermod_v2_HttpPullLightControl + +The `usermod_v2_HttpPullLightControl` is a custom user module for WLED that enables remote control over the lighting state and color through HTTP requests. It periodically polls a specified URL to obtain a JSON response containing instructions for controlling individual lights. + +## Features + +* Configure the URL endpoint (only support HTTP for now, no HTTPS) and polling interval via the WLED user interface. +* All options from the JSON API are supported (since v0.0.3). See: https://kno.wled.ge/interfaces/json-api/ +* The ability to control the brightness of all lights and the state (on/off) and color of individual lights remotely. +* Start or stop an effect and when you run the same effect when its's already running, it won't restart. +* The ability to control all these settings per segment. +* Remotely turn on/off relays, change segments or presets. +* Unique ID generation based on the device's MAC address and a configurable salt value, appended to the request URL for identification. + +## Configuration +* Enable the `usermod_v2_HttpPullLightControl` via the WLED user interface. +* Specify the URL endpoint and polling interval. + +## JSON Format and examples +* The module sends a GET request to the configured URL, appending a unique identifier as a query parameter: `https://www.example.com/mycustompage.php?id=xxxxxxxx` where xxxxxxx is a 40 character long SHA1 hash of the MAC address combined with a given salt. + +* Response Format (since v0.0.3) it is eactly the same as the WLED JSON API, see: https://kno.wled.ge/interfaces/json-api/ +After getting the URL (it can be a static file like static.json or a mylogic.php which gives a dynamic response), the response is read and parsed to WLED. + +* An example of a response to set the individual lights: 0 to RED, 12 to Green and 14 to BLUE. Remember that is will SET lights, you might want to set all the others to black. +`{ + "seg": + { + "i": [ + 0, "FF0000", + 12, "00FF00", + 14, "0000FF" + ] + } +}` + +* Another example setting the first 10 LEDs to RED, LED 40 to a PURPLE (using RGB values) and all LEDs in between OFF (black color) +`{ + "seg": + { + "i": [ + 0,10, "FF0000", + 10,40, "00FF00", + 40, [0,100,100] + ] + } +}` + +* Or first set all lights to black (off), then the LED5 to color RED: +`{ + "seg": + { + "i": [ + 0,40, "000000", + 5, "FF0000" + ] + } +}` + +* Or use the following example to start an effect, but first we UNFREEZE (frz=false) the segment because it was frozen by individual light control in the previous examples (28=Chase effect, Speed=180m Intensity=128). The three color slots are the slots you see under the color wheel and used by the effect. RED, Black, White in this case. +`{ + "seg": + { + "frz": false, + "fx": 28, + "sx": 200, + "ix": 128, + "col": [ + "FF0000", + "000000", + "FFFFFF" + ] + } +}` + + +## Installation + +1. Add `usermod_v2_HttpPullLightControl` to your WLED project following the instructions provided in the WLED documentation. +2. Compile by setting the build_flag: -D USERMOD_HTTP_PULL_LIGHT_CONTROL and upload to your ESP32/ESP8266! +3. There are several compile options which you can put in your platformio.ini or platformio_override.ini: +- -DUSERMOD_HTTP_PULL_LIGHT_CONTROL ;To Enable the usermod +- -DHTTP_PULL_LIGHT_CONTROL_URL="\"http://mydomain.com/json-response.php\"" ; The URL which will be requested all the time to set the lights/effects +- -DHTTP_PULL_LIGHT_CONTROL_SALT="\"my_very-S3cret_C0de\"" ; A secret SALT which will help by making the ID more safe +- -DHTTP_PULL_LIGHT_CONTROL_INTERVAL=30 ; The interval at which the URL is requested in seconds +- -DHTTP_PULL_LIGHT_CONTROL_HIDE_SALT ; Do you want to Hide the SALT in the User Interface? If yes, Set this flag. Note that the salt can now only be set via the above -DHTTP_PULL_LIGHT_CONTROL_SALT= setting + +- -DWLED_AP_SSID="\"Christmas Card\"" ; These flags are not just for my Usermod but you probably want to set them +- -DWLED_AP_PASS="\"christmas\"" +- -DWLED_OTA_PASS="\"otapw-secret\"" +- -DMDNS_NAME="\"christmascard\"" +- -DSERVERNAME="\"CHRISTMASCARD\"" +- -D ABL_MILLIAMPS_DEFAULT=450 +- -D DEFAULT_LED_COUNT=60 ; For a LED Ring of 60 LEDs +- -D BTNPIN=41 ; The M5Stack Atom S3 Lite has a button on GPIO41 +- -D LEDPIN=2 ; The M5Stack Atom S3 Lite has a Grove connector on the front, we use this GPIO2 +- -D STATUSLED=35 ; The M5Stack Atom S3 Lite has a Multi-Color LED on GPIO35, although I didnt managed to control it +- -D IRPIN=4 ; The M5Stack Atom S3 Lite has a IR LED on GPIO4 + +- -D DEBUG=1 ; Set these DEBUG flags ONLY if you want to debug and read out Serial (using Visual Studio Code - Serial Monitor) +- -DDEBUG_LEVEL=5 +- -DWLED_DEBUG + +## Use Case: Interactive Christmas Cards + +Imagine distributing interactive Christmas cards embedded with a tiny ESP32 and a string of 20 LEDs to 20 friends. When a friend powers on their card, it connects to their Wi-Fi network and starts polling your server via the `usermod_v2_HttpPullLightControl`. (Tip: Let them scan a QR code to connect to the WLED WiFi, from there they configure their own WiFi). + +Your server keeps track of how many cards are active at any given time. If all 20 cards are active, your server instructs each card to light up all of its LEDs. However, if only 4 cards are active, your server instructs each card to light up only 4 LEDs. This creates a real-time interactive experience, symbolizing the collective spirit of the holiday season. Each lit LED represents a friend who's thinking about the others, and the visual feedback creates a sense of connection among the group, despite the physical distance. + +This setup demonstrates a unique way to blend traditional holiday sentiments with modern technology, offering an engaging and memorable experience. \ No newline at end of file diff --git a/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.cpp b/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.cpp new file mode 100644 index 000000000..854cc2067 --- /dev/null +++ b/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.cpp @@ -0,0 +1,319 @@ +#include "usermod_v2_HttpPullLightControl.h" + +// add more strings here to reduce flash memory usage +const char HttpPullLightControl::_name[] PROGMEM = "HttpPullLightControl"; +const char HttpPullLightControl::_enabled[] PROGMEM = "Enable"; + +void HttpPullLightControl::setup() { + //Serial.begin(115200); + + // Print version number + DEBUG_PRINT(F("HttpPullLightControl version: ")); + DEBUG_PRINTLN(HTTP_PULL_LIGHT_CONTROL_VERSION); + + // Start a nice chase so we know its booting and searching for its first http pull. + DEBUG_PRINTLN(F("Starting a nice chase so we now it is booting.")); + Segment& seg = strip.getMainSegment(); + seg.setMode(28); // Set to chase + seg.speed = 200; + seg.intensity = 255; + seg.setPalette(128); + seg.setColor(0, 5263440); + seg.setColor(1, 0); + seg.setColor(2, 4605510); + + // Go on with generating a unique ID and splitting the URL into parts + uniqueId = generateUniqueId(); // Cache the unique ID + DEBUG_PRINT(F("UniqueId calculated: ")); + DEBUG_PRINTLN(uniqueId); + parseUrl(); + DEBUG_PRINTLN(F("HttpPullLightControl successfully setup")); +} + +// This is the main loop function, from here we check the URL and handle the response. +// Effects or individual lights are set as a result from this. +void HttpPullLightControl::loop() { + if (!enabled || offMode) return; // Do nothing when not enabled or powered off + if (millis() - lastCheck >= checkInterval * 1000) { + DEBUG_PRINTLN(F("Calling checkUrl function")); + checkUrl(); + lastCheck = millis(); + } + +} + +// Generate a unique ID based on the MAC address and a SALT +String HttpPullLightControl::generateUniqueId() { + uint8_t mac[6]; + WiFi.macAddress(mac); + char macStr[18]; + sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + // Set the MAC Address to a string and make it UPPERcase + String macString = String(macStr); + macString.toUpperCase(); + DEBUG_PRINT(F("WiFi MAC address is: ")); + DEBUG_PRINTLN(macString); + DEBUG_PRINT(F("Salt is: ")); + DEBUG_PRINTLN(salt); + String input = macString + salt; + + #ifdef ESP8266 + // For ESP8266 we use the Hash.h library which is built into the ESP8266 Core + return sha1(input); + #endif + + #ifdef ESP32 + // For ESP32 we use the mbedtls library which is built into the ESP32 core + int status = 0; + unsigned char shaResult[20]; // SHA1 produces a hash of 20 bytes (which is 40 HEX characters) + mbedtls_sha1_context ctx; + mbedtls_sha1_init(&ctx); + status = mbedtls_sha1_starts_ret(&ctx); + if (status != 0) { + DEBUG_PRINTLN(F("Error starting SHA1 checksum calculation")); + } + status = mbedtls_sha1_update_ret(&ctx, reinterpret_cast(input.c_str()), input.length()); + if (status != 0) { + DEBUG_PRINTLN(F("Error feeding update buffer into ongoing SHA1 checksum calculation")); + } + status = mbedtls_sha1_finish_ret(&ctx, shaResult); + if (status != 0) { + DEBUG_PRINTLN(F("Error finishing SHA1 checksum calculation")); + } + mbedtls_sha1_free(&ctx); + + // Convert the Hash to a hexadecimal string + char buf[41]; + for (int i = 0; i < 20; i++) { + sprintf(&buf[i*2], "%02x", shaResult[i]); + } + return String(buf); + #endif +} + +// This function is called when the user updates the Sald and so we need to re-calculate the unique ID +void HttpPullLightControl::updateSalt(String newSalt) { + DEBUG_PRINTLN(F("Salt updated")); + this->salt = newSalt; + uniqueId = generateUniqueId(); + DEBUG_PRINT(F("New UniqueId is: ")); + DEBUG_PRINTLN(uniqueId); +} + +// The function is used to separate the URL in a host part and a path part +void HttpPullLightControl::parseUrl() { + int firstSlash = url.indexOf('/', 7); // Skip http(s):// + host = url.substring(7, firstSlash); + path = url.substring(firstSlash); +} + +// This function is called by WLED when the USERMOD config is read +bool HttpPullLightControl::readFromConfig(JsonObject& root) { + // Attempt to retrieve the nested object for this usermod + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); // check if the object exists + + // Retrieve the values using the getJsonValue function for better error handling + configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled, enabled); // default value=enabled + configComplete &= getJsonValue(top["checkInterval"], checkInterval, checkInterval); // default value=60 + #ifndef HTTP_PULL_LIGHT_CONTROL_HIDE_URL + configComplete &= getJsonValue(top["url"], url, url); // default value="http://example.com" + #endif + #ifndef HTTP_PULL_LIGHT_CONTROL_HIDE_SALT + configComplete &= getJsonValue(top["salt"], salt, salt); // default value=your_salt_here + #endif + + return configComplete; +} + +// This function is called by WLED when the USERMOD config is saved in the frontend +void HttpPullLightControl::addToConfig(JsonObject& root) { + // Create a nested object for this usermod + JsonObject top = root.createNestedObject(FPSTR(_name)); + + // Write the configuration parameters to the nested object + top[FPSTR(_enabled)] = enabled; + if (enabled==false) + // To make it a bit more user-friendly, we unfreeze the main segment after disabling the module. Because individual light control (like for a christmas card) might have been done. + strip.getMainSegment().freeze=false; + top["checkInterval"] = checkInterval; + #ifndef HTTP_PULL_LIGHT_CONTROL_HIDE_URL + top["url"] = url; + #endif + #ifndef HTTP_PULL_LIGHT_CONTROL_HIDE_SALT + top["salt"] = salt; + updateSalt(salt); // Update the UniqueID + #endif + parseUrl(); // Re-parse the URL, maybe path and host is changed +} + +// Do the http request here. Note that we can not do https requests with the AsyncTCP library +// We do everything Asynchronous, so all callbacks are defined here +void HttpPullLightControl::checkUrl() { + // Extra Inactivity check to see if AsyncCLient hangs + if (client != nullptr && ( millis() - lastActivityTime > inactivityTimeout ) ) { + DEBUG_PRINTLN(F("Inactivity detected, deleting client.")); + delete client; + client = nullptr; + } + if (client != nullptr && client->connected()) { + DEBUG_PRINTLN(F("We are still connected, do nothing")); + // Do nothing, Client is still connected + return; + } + + if (client != nullptr) { + // Delete previous client instance if exists, just to prevent any memory leaks + DEBUG_PRINTLN(F("Delete previous instances")); + delete client; + client = nullptr; + } + + DEBUG_PRINTLN(F("Creating new AsyncClient instance.")); + client = new AsyncClient(); + if(client) { + client->onData([](void *arg, AsyncClient *c, void *data, size_t len) { + DEBUG_PRINTLN(F("Data received.")); + // Cast arg back to the usermod class instance + HttpPullLightControl *instance = (HttpPullLightControl *)arg; + instance->lastActivityTime = millis(); // Update lastactivity time when data is received + // Convertert to Safe-String + char *strData = new char[len + 1]; + strncpy(strData, (char*)data, len); + strData[len] = '\0'; + String responseData = String(strData); + //String responseData = String((char *)data); + // Make sure its zero-terminated String + //responseData[len] = '\0'; + delete[] strData; // Do not forget to remove this one + instance->handleResponse(responseData); + }, this); + client->onDisconnect([](void *arg, AsyncClient *c) { + DEBUG_PRINTLN(F("Disconnected.")); + //Set the class-own client pointer to nullptr if its the current client + HttpPullLightControl *instance = static_cast(arg); + if (instance->client == c) { + delete instance->client; // Delete the client instance + instance->client = nullptr; + } + }, this); + client->onTimeout([](void *arg, AsyncClient *c, uint32_t time) { + DEBUG_PRINTLN(F("Timeout")); + //Set the class-own client pointer to nullptr if its the current client + HttpPullLightControl *instance = static_cast(arg); + if (instance->client == c) { + delete instance->client; // Delete the client instance + instance->client = nullptr; + } + }, this); + client->onError([](void *arg, AsyncClient *c, int8_t error) { + DEBUG_PRINTLN("Connection error occurred!"); + DEBUG_PRINT("Error code: "); + DEBUG_PRINTLN(error); + //Set the class-own client pointer to nullptr if its the current client + HttpPullLightControl *instance = static_cast(arg); + if (instance->client == c) { + delete instance->client; + instance->client = nullptr; + } + // Do not remove client here, it is maintained by AsyncClient + }, this); + client->onConnect([](void *arg, AsyncClient *c) { + // Cast arg back to the usermod class instance + HttpPullLightControl *instance = (HttpPullLightControl *)arg; + instance->onClientConnect(c); // Call a method on the instance when the client connects + }, this); + client->setAckTimeout(ackTimeout); // Just some safety measures because we do not want any memory fillup + client->setRxTimeout(rxTimeout); + DEBUG_PRINT(F("Connecting to: ")); + DEBUG_PRINT(host); + DEBUG_PRINT(F(" via port ")); + DEBUG_PRINTLN((url.startsWith("https")) ? 443 : 80); + // Update lastActivityTime just before sending the request + lastActivityTime = millis(); + //Try to connect + if (!client->connect(host.c_str(), (url.startsWith("https")) ? 443 : 80)) { + DEBUG_PRINTLN(F("Failed to initiate connection.")); + // Connection failed, so cleanup + delete client; + client = nullptr; + } else { + // Connection successfull, wait for callbacks to go on. + DEBUG_PRINTLN(F("Connection initiated, awaiting response...")); + } + } else { + DEBUG_PRINTLN(F("Failed to create AsyncClient instance.")); + } +} + +// This function is called from the checkUrl function when the connection is establised +// We request the data here +void HttpPullLightControl::onClientConnect(AsyncClient *c) { + DEBUG_PRINT(F("Client connected: ")); + DEBUG_PRINTLN(c->connected() ? F("Yes") : F("No")); + + if (c->connected()) { + String request = "GET " + path + (path.indexOf('?') > 0 ? "&id=" : "?id=") + uniqueId + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n" + "Accept: application/json\r\n" + "Accept-Encoding: identity\r\n" // No compression + "User-Agent: ESP32 HTTP Client\r\n\r\n"; // Optional: User-Agent and end with a double rnrn ! + DEBUG_PRINT(request.c_str()); + auto bytesSent = c->write(request.c_str()); + if (bytesSent == 0) { + // Connection could not be made + DEBUG_PRINT(F("Failed to send HTTP request.")); + } else { + DEBUG_PRINT(F("Request sent successfully, bytes sent: ")); + DEBUG_PRINTLN(bytesSent ); + } + } +} + + +// This function is called when we receive data after connecting and doing our request +// It parses the JSON data to WLED +void HttpPullLightControl::handleResponse(String& responseStr) { + DEBUG_PRINTLN(F("Received response for handleResponse.")); + + // Get a Bufferlock, we can not use doc + if (!requestJSONBufferLock(myLockId)) { + DEBUG_PRINT(F("ERROR: Can not request JSON Buffer Lock, number: ")); + DEBUG_PRINTLN(myLockId); + releaseJSONBufferLock(); // Just release in any case, maybe there was already a buffer lock + return; + } + + // Search for two linebreaks between headers and content + int bodyPos = responseStr.indexOf("\r\n\r\n"); + if (bodyPos > 0) { + String jsonStr = responseStr.substring(bodyPos + 4); // +4 Skip the two CRLFs + jsonStr.trim(); + + DEBUG_PRINTLN("Response: "); + DEBUG_PRINTLN(jsonStr); + + // Check for valid JSON, otherwise we brick the program runtime + if (jsonStr[0] == '{' || jsonStr[0] == '[') { + // Attempt to deserialize the JSON response + DeserializationError error = deserializeJson(doc, jsonStr); + if (error == DeserializationError::Ok) { + // Get JSON object from th doc + JsonObject obj = doc.as(); + // Parse the object throuhg deserializeState (use CALL_MODE_NO_NOTIFY or OR CALL_MODE_DIRECT_CHANGE) + deserializeState(obj, CALL_MODE_NO_NOTIFY); + } else { + // If there is an error in deserialization, exit the function + DEBUG_PRINT(F("DeserializationError: ")); + DEBUG_PRINTLN(error.c_str()); + } + } else { + DEBUG_PRINTLN(F("Invalid JSON response")); + } + } else { + DEBUG_PRINTLN(F("No body found in the response")); + } + // Release the BufferLock again + releaseJSONBufferLock(); +} \ No newline at end of file diff --git a/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.h b/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.h new file mode 100644 index 000000000..187b2b091 --- /dev/null +++ b/usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.h @@ -0,0 +1,104 @@ +#pragma once +/* + * Usermod: HttpPullLightControl + * Versie: 0.0.4 + * Repository: https://github.com/roelbroersma/WLED-usermodv2_HttpPullLightControl + * Author: Roel Broersma + * Website: https://www.roelbroersma.nl + * Github author: github.com/roelbroersma + * Description: This usermod for WLED will request a given URL to know which effects + * or individual lights it should turn on/off. So you can remote control a WLED + * installation without having access to it (if no port forward, vpn or public IP is available). + * Use Case: Create a WLED 'Ring of Thought' christmas card. Sent a LED ring with 60 LEDs to 60 friends. + * When they turn it on and put it at their WiFi, it will contact your server. Now you can reply with a given + * number of lights that should turn on. Each light is a friend who did contact your server in the past 5 minutes. + * So on each of your friends LED rings, the number of lights will be the number of friends who have it turned on. + * Features: It sends a unique ID (has of MAC and salt) to the URL, so you can define each client without a need to map their IP address. + * Tested: Tested on WLED v0.14 with ESP32-S3 (M5Stack Atom S3 Lite), but should also workd for other ESPs and ESP8266. + */ + +#include "wled.h" + +// Use the following for SHA1 computation of our HASH, unfortunatelly PlatformIO doesnt recognize Hash.h while its already in the Core. +// We use Hash.h for ESP8266 (in the core) and mbedtls/sha256.h for ESP32 (in the core). +#ifdef ESP8266 + #include +#endif +#ifdef ESP32 + #include "mbedtls/sha1.h" +#endif + +#define HTTP_PULL_LIGHT_CONTROL_VERSION "0.0.4" + +class HttpPullLightControl : public Usermod { +private: + static const char _name[]; + static const char _enabled[]; + static const char _salt[]; + static const char _url[]; + + bool enabled = true; + + #ifdef HTTP_PULL_LIGHT_CONTROL_INTERVAL + uint16_t checkInterval = HTTP_PULL_LIGHT_CONTROL_INTERVAL; + #else + uint16_t checkInterval = 60; // Default interval of 1 minute + #endif + + #ifdef HTTP_PULL_LIGHT_CONTROL_URL + String url = HTTP_PULL_LIGHT_CONTROL_URL; + #else + String url = "http://example.org/example.php"; // Default-URL (http only!), can also be url with IP address in it. HttpS urls are not supported (yet) because of AsyncTCP library + #endif + + #ifdef HTTP_PULL_LIGHT_CONTROL_SALT + String salt = HTTP_PULL_LIGHT_CONTROL_SALT; + #else + String salt = "1just_a_very-secret_salt2"; // Salt for generating a unique ID when requesting the URL (in this way you can give different answers based on the WLED device who does the request) + #endif + // NOTE THAT THERE IS ALSO A #ifdef HTTP_PULL_LIGHT_CONTROL_HIDE_URL and a HTTP_PULL_LIGHT_CONTROL_HIDE_SALT IF YOU DO NOT WANT TO SHOW THE OPTIONS IN THE USERMOD SETTINGS + + // Define constants + static const uint8_t myLockId = USERMOD_ID_HTTP_PULL_LIGHT_CONTROL ; // Used for the requestJSONBufferLock(id) function + static const int16_t ackTimeout = 9000; // ACK timeout in milliseconds when doing the URL request + static const uint16_t rxTimeout = 9000; // RX timeout in milliseconds when doing the URL request + static const unsigned long FNV_offset_basis = 2166136261; + static const unsigned long FNV_prime = 16777619; + static const unsigned long inactivityTimeout = 30000; // When the AsyncClient is inactive (hanging) for this many milliseconds, we kill it + + unsigned long lastCheck = 0; // Timestamp of last check + unsigned long lastActivityTime = 0; // Time of last activity of AsyncClient + String host; // Host extracted from the URL + String path; // Path extracted from the URL + String uniqueId; // Cached unique ID + AsyncClient *client = nullptr; // Used very often, beware of closing and freeing + String generateUniqueId(); + + void parseUrl(); + void updateSalt(String newSalt); // Update the salt value and recalculate the unique ID + void checkUrl(); // Check the specified URL for light control instructions + void handleResponse(String& response); + void onClientConnect(AsyncClient *c); + +public: + void setup(); + void loop(); + bool readFromConfig(JsonObject& root); + void addToConfig(JsonObject& root); + uint16_t getId() { return USERMOD_ID_HTTP_PULL_LIGHT_CONTROL; } + inline void enable(bool enable) { enabled = enable; } // Enable or Disable the usermod + inline bool isEnabled() { return enabled; } // Get usermod enabled or disabled state + virtual ~HttpPullLightControl() { + // Remove the cached client if needed + if (client) { + client->onDisconnect(nullptr); + client->onError(nullptr); + client->onTimeout(nullptr); + client->onData(nullptr); + client->onConnect(nullptr); + // Now it is safe to delete the client. + delete client; // This is safe even if client is nullptr. + client = nullptr; + } + } +}; \ 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 8283aeed1..2a63dd4d8 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -101,7 +101,7 @@ class AutoSaveUsermod : public Usermod { // network here void setup() { #ifdef USERMOD_FOUR_LINE_DISPLAY - // This Usermod has enhanced funcionality if + // This Usermod has enhanced functionality if // FourLineDisplayUsermod is available. display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); #endif @@ -148,7 +148,7 @@ class AutoSaveUsermod : public Usermod { if (autoSaveAfter && now > autoSaveAfter) { autoSaveAfter = 0; - // Time to auto save. You may have some flickry? + // Time to auto save. You may have some flickery? saveSettings(); displayOverlay(); } diff --git a/usermods/usermod_v2_four_line_display/readme.md b/usermods/usermod_v2_four_line_display/readme.md deleted file mode 100644 index 26250cb5c..000000000 --- a/usermods/usermod_v2_four_line_display/readme.md +++ /dev/null @@ -1,63 +0,0 @@ -# I2C 4 Line Display Usermod - -First, thanks to the authors of the ssd11306_i2c_oled_u8g2 mod. - -Provides a four line display using either -128x32 or 128x64 OLED displays. -It can operate independently, but starts to provide -a relatively complete on-device UI when paired with the -Rotary Encoder UI usermod. I strongly encourage you to use -them together. - -[See the pair of usermods in action](https://www.youtube.com/watch?v=tITQY80rIOA) - -## Installation - -Copy and update the example `platformio_override.ini.sample` -from the Rotary Encoder UI usermode folder to the root directory of your particular build. -This file should be placed in the same directory as `platformio.ini`. - -### Define Your Options - -* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this mod included wled00\usermods_list.cpp - also tells Rotary Encoder usermod, if installed, the display is available -* `FLD_PIN_SCL` - The display SCL pin, defaults to 5 -* `FLD_PIN_SDA` - The display SDA pin, defaults to 4 - -All of the parameters can be configured via the Usermods settings page, inluding GPIO pins. - -### PlatformIO requirements - -This usermod requires the `U8g2` and `Wire` libraries. See the -`platformio_override.ini.sample` found in the Rotary Encoder -UI usermod folder for how to include these using `platformio_override.ini`. - -## Configuration - -* `enabled` - enable/disable usermod -* `pin` - GPIO pins used for display; I2C displays use Clk & Data; SPI displays can use SCK, MOSI, CS, DC & RST -* `type` - display type in numeric format - * 1 = I2C SSD1306 128x32 - * 2 = I2C SH1106 128x32 - * 3 = I2C SSD1306 128x64 (4 double-height lines) - * 4 = I2C SSD1305 128x32 - * 5 = I2C SSD1305 128x64 (4 double-height lines) - * 6 = SPI SSD1306 128x32 - * 7 = SPI SSD1306 128x64 (4 double-height lines) -* `contrast` - set display contrast (higher contrast may reduce display lifetime) -* `refreshRateSec` - display refresh time in seconds -* `screenTimeOutSec` - screen saver time-out in seconds -* `flip` - flip/rotate display 180° -* `sleepMode` - enable/disable screen saver -* `clockMode` - enable/disable clock display in screen saver mode -* `i2c-freq-kHz` - I2C clock frequency in kHz (may help reduce dropped frames, range: 400-3400) - -## Change Log - -2021-02 -* First public release - -2021-04 -* Adaptation for runtime configuration. - -2021-11 -* Added configuration option description. 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 deleted file mode 100644 index 3fcf66128..000000000 --- a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h +++ /dev/null @@ -1,742 +0,0 @@ -#pragma once - -#include "wled.h" -#include // from https://github.com/olikraus/u8g2/ - -// -// Insired by the v1 usermod: ssd1306_i2c_oled_u8g2 -// -// v2 usermod for using 128x32 or 128x64 i2c -// OLED displays to provide a four line display -// for WLED. -// -// Dependencies -// * This usermod REQURES the ModeSortUsermod -// * This Usermod works best, by far, when coupled -// with RotaryEncoderUIUsermod. -// -// Make sure to enable NTP and set your time zone in WLED Config | Time. -// -// REQUIREMENT: You must add the following requirements to -// REQUIREMENT: "lib_deps" within platformio.ini / platformio_override.ini -// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine) -// REQUIREMENT: * Wire -// - -//The SCL and SDA pins are defined here. -#ifndef FLD_PIN_SCL - #define FLD_PIN_SCL i2c_scl -#endif -#ifndef FLD_PIN_SDA - #define FLD_PIN_SDA i2c_sda -#endif -#ifndef FLD_PIN_CLOCKSPI - #define FLD_PIN_CLOCKSPI spi_sclk -#endif - #ifndef FLD_PIN_DATASPI - #define FLD_PIN_DATASPI spi_mosi -#endif -#ifndef FLD_PIN_CS - #define FLD_PIN_CS spi_cs -#endif -#ifdef ARDUINO_ARCH_ESP32 - #ifndef FLD_PIN_DC - #define FLD_PIN_DC 19 - #endif - #ifndef FLD_PIN_RESET - #define FLD_PIN_RESET 26 - #endif -#else - #ifndef FLD_PIN_DC - #define FLD_PIN_DC 12 - #endif - #ifndef FLD_PIN_RESET - #define FLD_PIN_RESET 16 - #endif -#endif - -#ifndef FLD_TYPE - #ifndef FLD_SPI_DEFAULT - #define FLD_TYPE SSD1306 - #else - #define FLD_TYPE SSD1306_SPI - #endif -#endif - -// When to time out to the clock or blank the screen -// if SLEEP_MODE_ENABLED. -#define SCREEN_TIMEOUT_MS 60*1000 // 1 min - -#define TIME_INDENT 0 -#define DATE_INDENT 2 - -// Minimum time between redrawing screen in ms -#define USER_LOOP_REFRESH_RATE_MS 1000 - -// Extra char (+1) for null -#define LINE_BUFFER_SIZE 16+1 - -typedef enum { - FLD_LINE_BRIGHTNESS = 0, - FLD_LINE_EFFECT_SPEED, - FLD_LINE_EFFECT_INTENSITY, - FLD_LINE_MODE, - FLD_LINE_PALETTE, - FLD_LINE_TIME -} 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 - SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C - SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C - SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI - SSD1306_SPI64 // U8X8_SSD1306_128X64_NONAME_HW_SPI -} DisplayType; - -class FourLineDisplayUsermod : public Usermod { - - private: - - bool initDone = false; - unsigned long lastTime = 0; - - // HW interface & configuration - U8X8 *u8x8 = nullptr; // pointer to U8X8 display object - #ifndef FLD_SPI_DEFAULT - int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA - uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000) - #else - int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST - uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz) - #endif - DisplayType type = FLD_TYPE; // 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 - bool enabled = true; - - // Next variables hold the previous known values to determine if redraw is - // required. - String knownSsid = ""; - IPAddress knownIp; - uint8_t knownBrightness = 0; - uint8_t knownEffectSpeed = 0; - uint8_t knownEffectIntensity = 0; - uint8_t knownMode = 0; - uint8_t knownPalette = 0; - uint8_t knownMinute = 99; - uint8_t knownHour = 99; - - bool displayTurnedOff = false; - unsigned long lastUpdate = 0; - unsigned long lastRedraw = 0; - unsigned long overlayUntil = 0; - Line4Type lineType = FLD_LINE_BRIGHTNESS; - // Set to 2 or 3 to mark lines 2 or 3. Other values ignored. - byte markLineNum = 0; - - // strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _enabled[]; - static const char _contrast[]; - static const char _refreshRate[]; - static const char _screenTimeOut[]; - static const char _flip[]; - static const char _sleepMode[]; - static const char _clockMode[]; - static const char _busClkFrequency[]; - - // 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() { - if (type == NONE || !enabled) return; - - bool isHW; - PinOwner po = PinOwner::UM_FourLineDisplay; - if (type == SSD1306_SPI || type == SSD1306_SPI64) { - isHW = (ioPin[0]==spi_sclk && ioPin[1]==spi_mosi); - if (isHW) po = PinOwner::HW_SPI; // allow multiple allocations of HW I2C bus pins - PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true }}; - if (!pinManager.allocateMultiplePins(pins, 5, po)) { type=NONE; return; } - } else { - isHW = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda); - if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; - if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; } - } - - DEBUG_PRINTLN(F("Allocating display.")); - switch (type) { - case SSD1306: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - lineHeight = 1; - break; - case SH1106: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - lineHeight = 2; - break; - case SSD1306_64: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - lineHeight = 2; - break; - case SSD1305: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - lineHeight = 1; - break; - case SSD1305_64: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - lineHeight = 2; - break; - case SSD1306_SPI: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset - lineHeight = 1; - break; - case SSD1306_SPI64: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset - lineHeight = 2; - break; - default: - u8x8 = nullptr; - } - - if (nullptr == u8x8) { - DEBUG_PRINTLN(F("Display init failed.")); - pinManager.deallocateMultiplePins((const uint8_t*)ioPin, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po); - type = NONE; - return; - } - - initDone = true; - DEBUG_PRINTLN(F("Starting display.")); - /*if (!(type == SSD1306_SPI || type == SSD1306_SPI64))*/ u8x8->setBusClock(ioFrequency); // can be used for SPI too - 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..."); - } - - // gets called every time WiFi is (re-)connected. Initialize own network - // interfaces here - void connected() {} - - /** - * Da loop. - */ - void loop() { - if (!enabled || millis() - lastUpdate < (clockMode?1000:refreshRate) || strip.isUpdating()) return; - lastUpdate = millis(); - - redraw(false); - } - - /** - * Wrappers for screen drawing - */ - void setFlipMode(uint8_t mode) { - if (type == NONE || !enabled) return; - u8x8->setFlipMode(mode); - } - void setContrast(uint8_t contrast) { - if (type == NONE || !enabled) return; - u8x8->setContrast(contrast); - } - void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { - if (type == NONE || !enabled) return; - u8x8->setFont(u8x8_font_chroma48medium8_r); - if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); - else u8x8->drawString(col, row, string); - } - void draw2x2String(uint8_t col, uint8_t row, const char *string) { - if (type == NONE || !enabled) return; - u8x8->setFont(u8x8_font_chroma48medium8_r); - 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 || !enabled) return; - u8x8->setFont(font); - if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); - else u8x8->drawGlyph(col, row, glyph); - } - uint8_t getCols() { - if (type==NONE || !enabled) return 0; - return u8x8->getCols(); - } - void clear() { - if (type == NONE || !enabled) return; - u8x8->clear(); - } - void setPowerSave(uint8_t save) { - if (type == NONE || !enabled) return; - u8x8->setPowerSave(save); - } - - void center(String &line, uint8_t width) { - int len = line.length(); - if (len0; i--) line = ' ' + line; - for (byte i=line.length(); i 0) { - if (now >= overlayUntil) { - // Time to display the overlay has elapsed. - overlayUntil = 0; - forceRedraw = true; - } else { - // We are still displaying the overlay - // Don't redraw. - return; - } - } - - // Check if values which are shown on display changed from the last time. - if (forceRedraw || - (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) || - (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : Network.localIP())) || - (knownBrightness != bri) || - (knownEffectSpeed != effectSpeed) || - (knownEffectIntensity != effectIntensity) || - (knownMode != strip.getMainSegment().mode) || - (knownPalette != strip.getMainSegment().palette)) { - knownHour = 99; // force time update - lastRedraw = now; // update lastRedraw marker - } else if (sleepMode && !displayTurnedOff && ((now - lastRedraw)/1000)%5 == 0) { - // change line every 5s - showName = !showName; - switch (lineType) { - case FLD_LINE_BRIGHTNESS: - lineType = FLD_LINE_EFFECT_SPEED; - break; - case FLD_LINE_MODE: - lineType = FLD_LINE_BRIGHTNESS; - break; - case FLD_LINE_PALETTE: - lineType = clockMode ? FLD_LINE_MODE : FLD_LINE_BRIGHTNESS; - break; - case FLD_LINE_EFFECT_SPEED: - lineType = FLD_LINE_EFFECT_INTENSITY; - break; - case FLD_LINE_EFFECT_INTENSITY: - lineType = FLD_LINE_PALETTE; - break; - default: - lineType = FLD_LINE_MODE; - break; - } - knownHour = 99; // force time update - // do not update lastRedraw marker if just switching row contenet - } else { - // Nothing to change. - // Turn off display after 3 minutes with no change. - if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) { - // We will still check if there is a change in redraw() - // and turn it back on if it changed. - sleepOrClock(true); - } else if (displayTurnedOff && clockMode) { - showTime(); - } - return; - } - - // Turn the display back on - if (displayTurnedOff) sleepOrClock(false); - - // Update last known values. - knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); - knownIp = apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); - knownBrightness = bri; - knownMode = strip.getMainSegment().mode; - knownPalette = strip.getMainSegment().palette; - knownEffectSpeed = effectSpeed; - knownEffectIntensity = effectIntensity; - - // Do the actual drawing - String line; - // First row with Wifi name - drawGlyph(0, 0, 80, u8x8_font_open_iconic_embedded_1x1); // home icon - line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); - center(line, getCols()-2); - drawString(1, 0, line.c_str()); - // Print `~` char to indicate that SSID is longer, than our display - if (knownSsid.length() > (int)getCols()-1) { - drawString(getCols() - 1, 0, "~"); - } - - // Second row with IP or Psssword - drawGlyph(0, lineHeight, 68, u8x8_font_open_iconic_embedded_1x1); // wifi icon - // Print password in AP mode and if led is OFF. - if (apActive && bri == 0) { - drawString(1, lineHeight, apPass); - } else { - // alternate IP address and server name - line = knownIp.toString(); - if (showName && strcmp(serverDescription, "WLED") != 0) { - line = serverDescription; - } - center(line, getCols()-1); - drawString(1, lineHeight, line.c_str()); - } - - // draw third and fourth row - drawLine(2, clockMode ? lineType : FLD_LINE_MODE); - drawLine(3, clockMode ? FLD_LINE_TIME : lineType); - - 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 - } - - void drawLine(uint8_t line, Line4Type lineType) { - char lineBuffer[LINE_BUFFER_SIZE]; - uint8_t printedChars; - switch(lineType) { - case FLD_LINE_BRIGHTNESS: - sprintf_P(lineBuffer, PSTR("Brightness %3d"), bri); - drawString(2, line*lineHeight, lineBuffer); - break; - case FLD_LINE_EFFECT_SPEED: - sprintf_P(lineBuffer, PSTR("FX Speed %3d"), effectSpeed); - drawString(2, line*lineHeight, lineBuffer); - break; - case FLD_LINE_EFFECT_INTENSITY: - sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity); - drawString(2, line*lineHeight, lineBuffer); - break; - case FLD_LINE_MODE: - printedChars = extractModeName(knownMode, JSON_mode_names, lineBuffer, LINE_BUFFER_SIZE-1); - for (;printedChars < getCols()-2 && printedChars < LINE_BUFFER_SIZE-3; printedChars++) lineBuffer[printedChars]=' '; - lineBuffer[printedChars] = 0; - drawString(2, line*lineHeight, lineBuffer); - break; - case FLD_LINE_PALETTE: - printedChars = extractModeName(knownPalette, JSON_palette_names, lineBuffer, LINE_BUFFER_SIZE-1); - for (;printedChars < getCols()-2 && printedChars < LINE_BUFFER_SIZE-3; printedChars++) lineBuffer[printedChars]=' '; - lineBuffer[printedChars] = 0; - drawString(2, line*lineHeight, lineBuffer); - break; - case FLD_LINE_TIME: - default: - showTime(false); - break; - } - } - - /** - * If there screen is off or in clock is displayed, - * this will return true. This allows us to throw away - * the first input from the rotary encoder but - * to wake up the screen. - */ - bool wakeDisplay() { - if (type == NONE || !enabled) return false; - knownHour = 99; - if (displayTurnedOff) { - // Turn the display back on - sleepOrClock(false); - redraw(true); - return true; - } - return false; - } - - /** - * Allows you to show up to two lines as overlay for a - * period of time. - * Clears the screen and prints on the middle two lines. - */ - void overlay(const char* line1, const char *line2, long showHowLong) { - if (type == NONE || !enabled) return; - - if (displayTurnedOff) { - // Turn the display back on (includes clear()) - sleepOrClock(false); - } else { - clear(); - } - - // Print the overlay - if (line1) { - String buf = line1; - center(buf, getCols()); - drawString(0, 1*lineHeight, buf.c_str()); - } - if (line2) { - String buf = line2; - center(buf, getCols()); - drawString(0, 2*lineHeight, buf.c_str()); - } - overlayUntil = millis() + showHowLong; - } - - void setLineType(byte lT) { - lineType = (Line4Type) lT; - } - - /** - * 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. - */ - void setMarkLine(byte newMarkLineNum) { - if (newMarkLineNum == 2 || newMarkLineNum == 3) { - markLineNum = newMarkLineNum; - } - else { - markLineNum = 0; - } - } - - /** - * Enable sleep (turn the display off) or clock mode. - */ - void sleepOrClock(bool enabled) { - clear(); - if (enabled) { - if (clockMode) showTime(); - else setPowerSave(1); - displayTurnedOff = true; - } else { - setPowerSave(0); - displayTurnedOff = false; - } - } - - /** - * Display the current date and time in large characters - * on the middle rows. Based 24 or 12 hour depending on - * the useAMPM configuration. - */ - void showTime(bool fullScreen = true) { - if (type == NONE || !enabled) return; - 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. - if (!fullScreen) return; - } - knownMinute = minuteCurrent; - knownHour = hourCurrent; - - 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*3, lineBuffer); - - byte showHour = hourCurrent; - boolean isAM = false; - if (useAMPM) { - if (showHour == 0) { - showHour = 12; - isAM = true; - } - else if (showHour > 12) { - showHour -= 12; - isAM = false; - } - else { - isAM = true; - } - } - - 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. - if (fullScreen) { - draw2x2String(TIME_INDENT+2, lineHeight*2, lineBuffer); - sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); - if (useAMPM) drawString(12+(fullScreen?0:2), lineHeight*2, (isAM ? "AM" : "PM"), true); - else drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line - } else { - drawString(9+(useAMPM?0:2), lineHeight*3, lineBuffer); - if (useAMPM) drawString(12+(fullScreen?0:2), lineHeight*3, (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) { - //} - - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - //void readFromJsonState(JsonObject& root) { - // if (!initDone) return; // prevent crash on boot applyPreset() - //} - - /* - * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. - * It will be called by WLED when settings are actually saved (for example, LED settings are saved) - * If you want to force saving the current state, use serializeConfig() in your loop(). - * - * CAUTION: serializeConfig() will initiate a filesystem write operation. - * It might cause the LEDs to stutter and will cause flash wear if called too often. - * Use it sparingly and always in the loop, never in network callbacks! - * - * addToConfig() will also not yet add your setting to one of the settings pages automatically. - * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. - * - * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! - */ - void addToConfig(JsonObject& root) { - JsonObject top = root.createNestedObject(FPSTR(_name)); - top[FPSTR(_enabled)] = enabled; - JsonArray io_pin = top.createNestedArray("pin"); - for (byte i=0; i<5; i++) io_pin.add(ioPin[i]); - top["help4Pins"] = F("Clk,Data,CS,DC,RST"); // help for Settings page - top["type"] = type; - top["help4Type"] = F("1=SSD1306,2=SH1106,3=SSD1306_128x64,4=SSD1305,5=SSD1305_128x64,6=SSD1306_SPI,7=SSD1306_SPI_128x64"); // help for Settings page - 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; - top[FPSTR(_busClkFrequency)] = ioFrequency/1000; - DEBUG_PRINTLN(F("4 Line Display config saved.")); - } - - /* - * readFromConfig() can be used to read back the custom settings you added with addToConfig(). - * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) - * - * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), - * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. - * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) - */ - bool readFromConfig(JsonObject& root) { - bool needsRedraw = false; - DisplayType newType = type; - int8_t newPin[5]; for (byte i=0; i<5; i++) newPin[i] = ioPin[i]; - - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - - enabled = top[FPSTR(_enabled)] | enabled; - newType = top["type"] | newType; - for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i]; - flip = top[FPSTR(_flip)] | flip; - contrast = top[FPSTR(_contrast)] | contrast; - refreshRate = (top[FPSTR(_refreshRate)] | refreshRate/1000) * 1000; - screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; - sleepMode = top[FPSTR(_sleepMode)] | sleepMode; - clockMode = top[FPSTR(_clockMode)] | clockMode; - if (newType == SSD1306_SPI || newType == SSD1306_SPI64) - ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency - else - ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency - - DEBUG_PRINT(FPSTR(_name)); - if (!initDone) { - // first run: reading from cfg.json - for (byte i=0; i<5; i++) ioPin[i] = newPin[i]; - type = newType; - DEBUG_PRINTLN(F(" config loaded.")); - } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); - // changing parameters from settings page - bool pinsChanged = false; - for (byte i=0; i<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } - if (pinsChanged || type!=newType) { - if (type != NONE) delete u8x8; - PinOwner po = PinOwner::UM_FourLineDisplay; - bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); - if (isSPI) { - if (ioPin[0]==spi_sclk && ioPin[1]==spi_mosi) po = PinOwner::HW_SPI; // allow multiple allocations of HW SPI bus pins - pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 5, po); - } else { - if (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); - } - for (byte i=0; i<5; i++) ioPin[i] = newPin[i]; - if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1 - type = NONE; - return true; - } else type = newType; - setup(); - needsRedraw |= true; - } - if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too - setContrast(contrast); - setFlipMode(flip); - if (needsRedraw && !wakeDisplay()) redraw(true); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_enabled)].isNull(); - } - - /* - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - * This could be used in the future for the system to determine whether your usermod is installed. - */ - uint16_t getId() { - return USERMOD_ID_FOUR_LINE_DISP; - } -}; - -// strings to reduce flash memory usage (used more than twice) -const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay"; -const char FourLineDisplayUsermod::_enabled[] PROGMEM = "enabled"; -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"; -const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz"; diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 40136a92d..82a5e1a81 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -6,7 +6,7 @@ #include "4LD_wled_fonts.c" #ifndef FLD_ESP32_NO_THREADS - #define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!! + #define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behaviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!! #endif // @@ -243,7 +243,7 @@ class FourLineDisplayUsermod : public Usermod { */ void setMarkLine(byte newMarkLineNum, byte newMarkColNum); - //Draw the arrow for the current setting beiong changed + //Draw the arrow for the current setting being changed void drawArrow(); //Display the current effect or palette (desiredEntry) @@ -793,7 +793,7 @@ void FourLineDisplayUsermod::setMarkLine(byte newMarkLineNum, byte newMarkColNum markColNum = newMarkColNum; } -//Draw the arrow for the current setting beiong changed +//Draw the arrow for the current setting being changed void FourLineDisplayUsermod::drawArrow() { #if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) unsigned long now = millis(); @@ -1066,7 +1066,7 @@ void FourLineDisplayUsermod::networkOverlay(const char* line1, long showHowLong) bool FourLineDisplayUsermod::handleButton(uint8_t b) { yield(); if (!enabled - || b // butto 0 only + || b // button 0 only || buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_NONE || buttonType[b] == BTN_TYPE_RESERVED diff --git a/usermods/usermod_v2_klipper_percentage/readme.md b/usermods/usermod_v2_klipper_percentage/readme.md index 0619bf857..e967d6b21 100644 --- a/usermods/usermod_v2_klipper_percentage/readme.md +++ b/usermods/usermod_v2_klipper_percentage/readme.md @@ -10,7 +10,7 @@ curl --location --request GET 'http://[]/printer/objects/query?virtual_sdcard=pr ## Usage Compile the source with the buildflag `-D USERMOD_KLIPPER_PERCENTAGE` added. -You can also use the WLBD bot in the Discord by simply extending an exsisting build enviroment: +You can also use the WLBD bot in the Discord by simply extending an existing build environment: ``` [env:esp32klipper] extends = env:esp32dev @@ -23,7 +23,7 @@ build_flags = ${common.build_flags_esp32} -D USERMOD_KLIPPER_PERCENTAGE Checkbox to enable or disable the overlay ### Klipper IP: -IP adress of your Klipper instance you want to poll. ESP has to be restarted after change +IP address of your Klipper instance you want to poll. ESP has to be restarted after change ### Direction : 0 = normal diff --git a/usermods/usermod_v2_klipper_percentage/usermod_v2_klipper_percentage.h b/usermods/usermod_v2_klipper_percentage/usermod_v2_klipper_percentage.h index 0e19cc80f..bd4170dd2 100644 --- a/usermods/usermod_v2_klipper_percentage/usermod_v2_klipper_percentage.h +++ b/usermods/usermod_v2_klipper_percentage/usermod_v2_klipper_percentage.h @@ -6,7 +6,7 @@ class klipper_percentage : public Usermod { private: unsigned long lastTime = 0; - String ip = "192.168.25.207"; + String ip = F("0.0.0.0"); WiFiClient wifiClient; char errorMessage[100] = ""; int printPercent = 0; @@ -30,7 +30,7 @@ private: { // Send HTTP request client.println(F("GET /printer/objects/query?virtual_sdcard=progress HTTP/1.0")); - client.println("Host: " + ip); + client.print(F("Host: ")); client.println(ip); client.println(F("Connection: close")); if (client.println() == 0) { @@ -41,7 +41,7 @@ private: // Check HTTP status char status[32] = {0}; client.readBytesUntil('\r', status, sizeof(status)); - if (strcmp(status, "HTTP/1.1 200 OK") != 0) + if (strcmp_P(status, PSTR("HTTP/1.1 200 OK")) != 0) { strcat(errorMessage, PSTR("Unexpected response: ")); strcat(errorMessage, status); @@ -79,18 +79,18 @@ public: httpGet(wifiClient, errorMessage); if (strcmp(errorMessage, "") == 0) { - PSRAMDynamicJsonDocument klipperDoc(4096); // in practive about 2673 + PSRAMDynamicJsonDocument klipperDoc(4096); // in practice about 2673 DeserializationError error = deserializeJson(klipperDoc, wifiClient); if (error) { strcat(errorMessage, PSTR("deserializeJson() failed: ")); strcat(errorMessage, error.c_str()); } - printPercent = (int)(klipperDoc["result"]["status"]["virtual_sdcard"]["progress"].as() * 100); + printPercent = (int)(klipperDoc[F("result")][F("status")][F("virtual_sdcard")][F("progress")].as() * 100); - DEBUG_PRINT("Percent: "); - DEBUG_PRINTLN((int)(klipperDoc["result"]["status"]["virtual_sdcard"]["progress"].as() * 100)); - DEBUG_PRINT("LEDs: "); + DEBUG_PRINT(F("Percent: ")); + DEBUG_PRINTLN((int)(klipperDoc[F("result")][F("status")][F("virtual_sdcard")][F("progress")].as() * 100)); + DEBUG_PRINT(F("LEDs: ")); DEBUG_PRINTLN(direction == 2 ? (strip.getLengthTotal() / 2) * printPercent / 100 : strip.getLengthTotal() * printPercent / 100); } else @@ -106,10 +106,10 @@ public: void addToConfig(JsonObject &root) { - JsonObject top = root.createNestedObject("Klipper Printing Percentage"); - top["Enabled"] = enabled; - top["Klipper IP"] = ip; - top["Direction"] = direction; + JsonObject top = root.createNestedObject(F("Klipper Printing Percentage")); + top[F("Enabled")] = enabled; + top[F("Klipper IP")] = ip; + top[F("Direction")] = direction; } bool readFromConfig(JsonObject &root) @@ -117,12 +117,12 @@ public: // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) - JsonObject top = root["Klipper Printing Percentage"]; + JsonObject top = root[F("Klipper Printing Percentage")]; bool configComplete = !top.isNull(); - configComplete &= getJsonValue(top["Klipper IP"], ip); - configComplete &= getJsonValue(top["Enabled"], enabled); - configComplete &= getJsonValue(top["Direction"], direction); + configComplete &= getJsonValue(top[F("Klipper IP")], ip); + configComplete &= getJsonValue(top[F("Enabled")], enabled); + configComplete &= getJsonValue(top[F("Direction")], direction); return configComplete; } diff --git a/usermods/usermod_v2_mode_sort/readme.md b/usermods/usermod_v2_mode_sort/readme.md deleted file mode 100644 index c24322f32..000000000 --- a/usermods/usermod_v2_mode_sort/readme.md +++ /dev/null @@ -1,33 +0,0 @@ -# Mode Sort - -v2 usermod that provides data about modes and -palettes to other usermods. Notably it provides: -* A direct method for a mode or palette name -* Ability to retrieve mode and palette names in - alphabetical order - -```char **getModesQStrings()``` - -Provides a char* array (pointers) to the names of the -palettes contained in JSON_mode_names, in the same order as -JSON_mode_names. These strings end in double quote (") -(or \0 if there is a problem). - -```byte *getModesAlphaIndexes()``` - -A byte array designating the indexes of names of the -modes in alphabetical order. "Solid" will always remain -at the top of the list. - -```char **getPalettesQStrings()``` - -Provides a char* array (pointers) to the names of the -palettes contained in JSON_palette_names, in the same order as -JSON_palette_names. These strings end in double quote (") -(or \0 if there is a problem). - -```byte *getPalettesAlphaIndexes()``` - -A byte array designating the indexes of names of the -palettes in alphabetical order. "Default" and those -starting with "(" will always remain at the top of the list. diff --git a/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h deleted file mode 100644 index 092206bb6..000000000 --- a/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h +++ /dev/null @@ -1,244 +0,0 @@ -#pragma once - -#include "wled.h" - -// -// v2 usermod that provides data about modes and -// palettes to other usermods. Notably it provides: -// * A direct method for a mode or palette name -// * Ability to retrieve mode and palette names in -// alphabetical order -// -// char **getModesQStrings() -// Provides an array of char* (pointers) to the names of the -// palettes within JSON_mode_names, in the same order as -// JSON_mode_names. These strings end in double quote (") -// (or \0 if there is a problem). -// -// byte *getModesAlphaIndexes() -// An array of byte designating the indexes of names of the -// modes in alphabetical order. "Solid" will always remain -// at the front of the list. -// -// char **getPalettesQStrings() -// Provides an array of char* (pointers) to the names of the -// palettes within JSON_palette_names, in the same order as -// JSON_palette_names. These strings end in double quote (") -// (or \0 if there is a problem). -// -// byte *getPalettesAlphaIndexes() -// An array of byte designating the indexes of names of the -// palettes in alphabetical order. "Default" and those -// starting with "(" will always remain at the front of the list. -// - -// Number of modes at the start of the list to not sort -#define MODE_SORT_SKIP_COUNT 1 - -// Which list is being sorted -char **listBeingSorted = nullptr; - -/** - * Modes and palettes are stored as strings that - * end in a quote character. Compare two of them. - * We are comparing directly within either - * JSON_mode_names or JSON_palette_names. - */ -int re_qstringCmp(const void *ap, const void *bp) { - char *a = listBeingSorted[*((byte *)ap)]; - char *b = listBeingSorted[*((byte *)bp)]; - int i = 0; - do { - char aVal = pgm_read_byte_near(a + i); - if (aVal >= 97 && aVal <= 122) { - // Lowercase - aVal -= 32; - } - char bVal = pgm_read_byte_near(b + i); - if (bVal >= 97 && bVal <= 122) { - // Lowercase - bVal -= 32; - } - // Relly we shouldn't ever get to '\0' - if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') { - // We're done. one is a substring of the other - // or something happenend and the quote didn't stop us. - if (aVal == bVal) { - // Same value, probably shouldn't happen - // with this dataset - return 0; - } - else if (aVal == '"' || aVal == '\0') { - return -1; - } - else { - return 1; - } - } - if (aVal == bVal) { - // Same characters. Move to the next. - i++; - continue; - } - // We're done - if (aVal < bVal) { - return -1; - } - else { - return 1; - } - } while (true); - // We shouldn't get here. - return 0; -} - -class ModeSortUsermod : public Usermod { -private: - - // Pointers the start of the mode names within JSON_mode_names - char **modes_qstrings = nullptr; - - // Array of mode indexes in alphabetical order. - byte *modes_alpha_indexes = nullptr; - - // Pointers the start of the palette names within JSON_palette_names - char **palettes_qstrings = nullptr; - - // Array of palette indexes in alphabetical order. - byte *palettes_alpha_indexes = nullptr; - -public: - /** - * setup() is called once at boot. WiFi is not yet connected at this point. - * You can use it to initialize variables, sensors or similar. - */ - void setup() { - // Sort the modes and palettes on startup - // as they are guarantted to change. - sortModesAndPalettes(); - } - - char **getModesQStrings() { - return modes_qstrings; - } - - byte *getModesAlphaIndexes() { - return modes_alpha_indexes; - } - - char **getPalettesQStrings() { - return palettes_qstrings; - } - - byte *getPalettesAlphaIndexes() { - return palettes_alpha_indexes; - } - - /** - * This Usermod doesn't have anything for loop. - */ - void loop() {} - - /** - * Sort the modes and palettes to the index arrays - * modes_alpha_indexes and palettes_alpha_indexes. - */ - void sortModesAndPalettes() { - modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); - modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); - re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); - - palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()); - palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); - - int skipPaletteCount = 1; - while (true) { - // How many palette names start with '*' and should not be sorted? - // (Also skipping the first one, 'Default'). - if (pgm_read_byte_near(palettes_qstrings[skipPaletteCount]) == '*') { - skipPaletteCount++; - } - else { - break; - } - } - re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); - } - - byte *re_initIndexArray(int numModes) { - byte *indexes = (byte *)malloc(sizeof(byte) * numModes); - for (byte i = 0; i < numModes; i++) { - indexes[i] = i; - } - return indexes; - } - - /** - * Return an array of mode or palette names from the JSON string. - * They don't end in '\0', they end in '"'. - */ - char **re_findModeStrings(const char json[], int numModes) { - char **modeStrings = (char **)malloc(sizeof(char *) * numModes); - uint8_t modeIndex = 0; - bool insideQuotes = false; - // advance past the mark for markLineNum that may exist. - char singleJsonSymbol; - - // Find the mode name in JSON - bool complete = false; - for (size_t i = 0; i < strlen_P(json); i++) { - singleJsonSymbol = pgm_read_byte_near(json + i); - if (singleJsonSymbol == '\0') break; - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - if (insideQuotes) { - // We have a new mode or palette - modeStrings[modeIndex] = (char *)(json + i + 1); - } - break; - case '[': - break; - case ']': - if (!insideQuotes) complete = true; - break; - case ',': - if (!insideQuotes) modeIndex++; - default: - if (!insideQuotes) break; - } - if (complete) break; - } - return modeStrings; - } - - /** - * Sort either the modes or the palettes using quicksort. - */ - void re_sortModes(char **modeNames, byte *indexes, int count, int numSkip) { - listBeingSorted = modeNames; - qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp); - listBeingSorted = nullptr; - } - - /* - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void addToJsonState(JsonObject &root) {} - - /* - * 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) {} - - /* - * 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_MODE_SORT; - } -}; diff --git a/usermods/usermod_v2_ping_pong_clock/readme.md b/usermods/usermod_v2_ping_pong_clock/readme.md index 9f01b3ebf..f8219489d 100644 --- a/usermods/usermod_v2_ping_pong_clock/readme.md +++ b/usermods/usermod_v2_ping_pong_clock/readme.md @@ -7,4 +7,4 @@ Contains a modification to use WLED in combination with the Ping Pong Ball LED C To install this Usermod, you instruct PlatformIO to compile the Project with the USERMOD_PING_PONG_CLOCK flag. WLED then automatically provides you with various settings on the Usermod Page. -Note: Depending on the size of your clock, you may have to update the led indices for the indivdual numbers and the base indices. +Note: Depending on the size of your clock, you may have to update the led indices for the individual numbers and the base indices. diff --git a/usermods/usermod_v2_ping_pong_clock/usermod_v2_ping_pong_clock.h b/usermods/usermod_v2_ping_pong_clock/usermod_v2_ping_pong_clock.h index a690c1b1e..40ff675c0 100644 --- a/usermods/usermod_v2_ping_pong_clock/usermod_v2_ping_pong_clock.h +++ b/usermods/usermod_v2_ping_pong_clock/usermod_v2_ping_pong_clock.h @@ -18,15 +18,15 @@ private: // ---- Variables for correct LED numbering below, edit only if your clock is built different ---- - int baseH = 43; // Adress for the one place of the hours - int baseHH = 7; // Adress for the tens place of the hours - int baseM = 133; // Adress for the one place of the minutes - int baseMM = 97; // Adress for the tens place of the minutes - int colon1 = 79; // Adress for the first colon led - int colon2 = 80; // Adress for the second colon led + int baseH = 43; // Address for the one place of the hours + int baseHH = 7; // Address for the tens place of the hours + int baseM = 133; // Address for the one place of the minutes + int baseMM = 97; // Address for the tens place of the minutes + int colon1 = 79; // Address for the first colon led + int colon2 = 80; // Address for the second colon led // Matrix for the illumination of the numbers - // Note: These only define the increments of the base adress. e.g. to define the second Minute you have to add the baseMM to every led position + // Note: These only define the increments of the base address. e.g. to define the second Minute you have to add the baseMM to every led position const int numbers[10][10] = { { 0, 1, 4, 6, 13, 15, 18, 19, -1, -1 }, // 0: null diff --git a/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample b/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample deleted file mode 100644 index 4b537a8f7..000000000 --- a/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample +++ /dev/null @@ -1,48 +0,0 @@ -[platformio] -default_envs = d1_mini -; default_envs = esp32dev - -[env:esp32dev] -board = esp32dev -platform = espressif32@3.2 -build_unflags = ${common.build_unflags} -build_flags = - ${common.build_flags_esp32} - -D USERMOD_MODE_SORT - -D USERMOD_FOUR_LINE_DISPLAY -D FLD_PIN_SCL=22 -D FLD_PIN_SDA=21 - -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19 - -D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1 - -D LEDPIN=16 -D BTNPIN=13 -upload_speed = 460800 -lib_ignore = - ESPAsyncTCP - ESPAsyncUDP - -[env:d1_mini] -board = d1_mini -platform = ${common.platform_wled_default} -platform_packages = ${common.platform_packages} -upload_speed = 460800 -board_build.ldscript = ${common.ldscript_4m1m} -build_unflags = ${common.build_unflags} -build_flags = - ${common.build_flags_esp8266} - -D USERMOD_MODE_SORT - -D USERMOD_FOUR_LINE_DISPLAY -D FLD_PIN_SCL=5 -D FLD_PIN_SDA=4 - -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=12 -D ENCODER_CLK_PIN=14 -D ENCODER_SW_PIN=13 - -D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1 - -D LEDPIN=3 -D BTNPIN=0 -monitor_filters = esp8266_exception_decoder - -[env] -lib_deps = - fastled/FastLED @ 3.3.2 - NeoPixelBus @ 2.6.0 - ESPAsyncTCP @ 1.2.0 - ESPAsyncUDP - AsyncTCP @ 1.0.3 - IRremoteESP8266 @ 2.7.3 - https://github.com/lorol/LITTLEFS.git - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.0 - U8g2@~2.27.2 - Wire diff --git a/usermods/usermod_v2_rotary_encoder_ui/readme.md b/usermods/usermod_v2_rotary_encoder_ui/readme.md deleted file mode 100644 index 5e4f3cff6..000000000 --- a/usermods/usermod_v2_rotary_encoder_ui/readme.md +++ /dev/null @@ -1,39 +0,0 @@ -# Rotary Encoder UI Usermod - -First, thanks to the authors of other Rotary Encoder usermods. - -This usermod starts to provide a relatively complete on-device -UI when paired with the Four Line Display usermod. I strongly -encourage you to try them together. - -[See the pair of usermods in action](https://www.youtube.com/watch?v=tITQY80rIOA) - -## Installation - -Copy and update the example `platformio_override.ini.sample` to the root directory of your particular build. -This file should be placed in the same directory as `platformio.ini`. - -### Define Your Options - -* `USERMOD_ROTARY_ENCODER_UI` - define this to have this user mod included wled00\usermods_list.cpp -* `USERMOD_ROTARY_ENCODER_GPIO` - define the GPIO function (INPUT, INPUT_PULLUP, etc...) -* `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) -* `ENCODER_DT_PIN`   - defaults to 12 -* `ENCODER_CLK_PIN` - defaults to 14 -* `ENCODER_SW_PIN`   - defaults to 13 -* `USERMOD_ROTARY_ENCODER_GPIO` - GPIO functionality: - `INPUT_PULLUP` to use internal pull-up - `INPUT` to use pull-up on the PCB - -### PlatformIO requirements - -No special requirements. - -Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. - -## Change Log - -2021-02 -* First public release diff --git a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h deleted file mode 100644 index 02bc0ccda..000000000 --- a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h +++ /dev/null @@ -1,496 +0,0 @@ -#pragma once - -#include "wled.h" - -// -// Inspired by the v1 usermods -// * rotary_encoder_change_brightness -// * rotary_encoder_change_effect -// -// v2 usermod that provides a rotary encoder-based UI. -// -// This usermod allows you to control: -// -// * Brightness -// * Selected Effect -// * Effect Speed -// * Effect Intensity -// * Palette -// -// Change between modes by pressing a button. -// -// Dependencies -// * This usermod REQURES the ModeSortUsermod -// * This Usermod works best coupled with -// FourLineDisplayUsermod. -// - -#ifndef ENCODER_DT_PIN -#define ENCODER_DT_PIN 12 -#endif - -#ifndef ENCODER_CLK_PIN -#define ENCODER_CLK_PIN 14 -#endif - -#ifndef ENCODER_SW_PIN -#define ENCODER_SW_PIN 13 -#endif - -#ifndef USERMOD_FOUR_LINE_DISPLAY -// These constants won't be defined if we aren't using FourLineDisplay. -#define FLD_LINE_BRIGHTNESS 0 -#define FLD_LINE_MODE 0 -#define FLD_LINE_EFFECT_SPEED 0 -#define FLD_LINE_EFFECT_INTENSITY 0 -#define FLD_LINE_PALETTE 0 -#endif - - -// The last UI state -#define LAST_UI_STATE 4 - - -class RotaryEncoderUIUsermod : public Usermod { -private: - int fadeAmount = 10; // Amount to change every step (brightness) - unsigned long currentTime; - unsigned long loopTime; - int8_t pinA = ENCODER_DT_PIN; // DT from encoder - int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder - int8_t pinC = ENCODER_SW_PIN; // SW from encoder - unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed - unsigned char button_state = HIGH; - unsigned char prev_button_state = HIGH; - -#ifdef USERMOD_FOUR_LINE_DISPLAY - FourLineDisplayUsermod *display; -#else - void* display = nullptr; -#endif - - byte *modes_alpha_indexes = nullptr; - byte *palettes_alpha_indexes = nullptr; - - unsigned char Enc_A; - unsigned char Enc_B; - unsigned char Enc_A_prev = 0; - - bool currentEffectAndPaletteInitialized = false; - uint8_t effectCurrentIndex = 0; - uint8_t effectPaletteIndex = 0; - - bool initDone = false; - bool enabled = true; - - // strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _enabled[]; - static const char _DT_pin[]; - static const char _CLK_pin[]; - static const char _SW_pin[]; - -public: - /* - * setup() is called once at boot. WiFi is not yet connected at this point. - * You can use it to initialize variables, sensors or similar. - */ - void setup() - { - DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); - PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; - if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { - // BUG: configuring this usermod with conflicting pins - // will cause it to de-allocate pins it does not own - // (at second config) - // This is the exact type of bug solved by pinManager - // tracking the owner tags.... - pinA = pinB = pinC = -1; - enabled = false; - return; - } - - #ifndef USERMOD_ROTARY_ENCODER_GPIO - #define USERMOD_ROTARY_ENCODER_GPIO INPUT_PULLUP - #endif - pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO); - pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO); - pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO); - - currentTime = millis(); - loopTime = currentTime; - - ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT); - modes_alpha_indexes = modeSortUsermod->getModesAlphaIndexes(); - palettes_alpha_indexes = modeSortUsermod->getPalettesAlphaIndexes(); - -#ifdef USERMOD_FOUR_LINE_DISPLAY - // This Usermod uses FourLineDisplayUsermod for the best experience. - // But it's optional. But you want it. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); - if (display != nullptr) { - display->setLineType(FLD_LINE_BRIGHTNESS); - display->setMarkLine(3); - } -#endif - - initDone = true; - } - - /* - * connected() is called every time the WiFi is (re)connected - * Use it to initialize network interfaces - */ - void connected() - { - //Serial.println("Connected to WiFi!"); - } - - /* - * loop() is called continuously. Here you can check for events, read sensors, etc. - * - * Tips: - * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. - * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. - * - * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. - * Instead, use a timer check as shown here. - */ - void loop() - { - if (!enabled) return; - - currentTime = millis(); // get the current elapsed time - - // Initialize effectCurrentIndex and effectPaletteIndex to - // current state. We do it here as (at least) effectCurrent - // is not yet initialized when setup is called. - if (!currentEffectAndPaletteInitialized) { - findCurrentEffectAndPalette(); - } - - if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz - { - button_state = digitalRead(pinC); - if (prev_button_state != button_state) - { - if (button_state == LOW) - { - prev_button_state = button_state; - - char newState = select_state + 1; - if (newState > LAST_UI_STATE) newState = 0; - - bool changedState = true; - if (display != nullptr) { - switch(newState) { - case 0: - changedState = changeState("Brightness", FLD_LINE_BRIGHTNESS, 3); - break; - case 1: - changedState = changeState("Select FX", FLD_LINE_MODE, 2); - break; - case 2: - changedState = changeState("FX Speed", FLD_LINE_EFFECT_SPEED, 3); - break; - case 3: - changedState = changeState("FX Intensity", FLD_LINE_EFFECT_INTENSITY, 3); - break; - case 4: - changedState = changeState("Palette", FLD_LINE_PALETTE, 3); - break; - } - } - if (changedState) { - select_state = newState; - } - } - else - { - prev_button_state = button_state; - } - } - int Enc_A = digitalRead(pinA); // Read encoder pins - int Enc_B = digitalRead(pinB); - if ((!Enc_A) && (Enc_A_prev)) - { // A has gone from high to low - if (Enc_B == HIGH) - { // B is high so clockwise - switch(select_state) { - case 0: - changeBrightness(true); - break; - case 1: - changeEffect(true); - break; - case 2: - changeEffectSpeed(true); - break; - case 3: - changeEffectIntensity(true); - break; - case 4: - changePalette(true); - break; - } - } - else if (Enc_B == LOW) - { // B is low so counter-clockwise - switch(select_state) { - case 0: - changeBrightness(false); - break; - case 1: - changeEffect(false); - break; - case 2: - changeEffectSpeed(false); - break; - case 3: - changeEffectIntensity(false); - break; - case 4: - changePalette(false); - break; - } - } - } - Enc_A_prev = Enc_A; // Store value of A for next time - loopTime = currentTime; // Updates loopTime - } - } - - void findCurrentEffectAndPalette() { - currentEffectAndPaletteInitialized = true; - for (uint8_t i = 0; i < strip.getModeCount(); i++) { - //byte value = modes_alpha_indexes[i]; - if (modes_alpha_indexes[i] == effectCurrent) { - effectCurrentIndex = i; - break; - } - } - - for (uint8_t i = 0; i < strip.getPaletteCount(); i++) { - //byte value = palettes_alpha_indexes[i]; - if (palettes_alpha_indexes[i] == strip.getSegment(0).palette) { - effectPaletteIndex = i; - break; - } - } - } - - boolean changeState(const char *stateName, byte lineThreeMode, byte markedLine) { -#ifdef USERMOD_FOUR_LINE_DISPLAY - if (display != nullptr) { - if (display->wakeDisplay()) { - // Throw away wake up input - return false; - } - display->overlay("Mode change", stateName, 1500); - display->setLineType(lineThreeMode); - display->setMarkLine(markedLine); - } - #endif - return true; - } - - void lampUdated() { - colorUpdated(CALL_MODE_BUTTON); - updateInterfaces(CALL_MODE_BUTTON); - } - - void changeBrightness(bool increase) { -#ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } -#endif - if (increase) { - bri = (bri + fadeAmount <= 255) ? (bri + fadeAmount) : 255; - } - else { - bri = (bri - fadeAmount >= 0) ? (bri - fadeAmount) : 0; - } - lampUdated(); - } - - void changeEffect(bool increase) { -#ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } -#endif - if (increase) { - effectCurrentIndex = (effectCurrentIndex + 1 >= strip.getModeCount()) ? 0 : (effectCurrentIndex + 1); - } - else { - effectCurrentIndex = (effectCurrentIndex - 1 < 0) ? (strip.getModeCount() - 1) : (effectCurrentIndex - 1); - } - effectCurrent = modes_alpha_indexes[effectCurrentIndex]; - lampUdated(); - } - - void changeEffectSpeed(bool increase) { -#ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } -#endif - if (increase) { - effectSpeed = (effectSpeed + fadeAmount <= 255) ? (effectSpeed + fadeAmount) : 255; - } - else { - effectSpeed = (effectSpeed - fadeAmount >= 0) ? (effectSpeed - fadeAmount) : 0; - } - lampUdated(); - } - - void changeEffectIntensity(bool increase) { -#ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } -#endif - if (increase) { - effectIntensity = (effectIntensity + fadeAmount <= 255) ? (effectIntensity + fadeAmount) : 255; - } - else { - effectIntensity = (effectIntensity - fadeAmount >= 0) ? (effectIntensity - fadeAmount) : 0; - } - lampUdated(); - } - - void changePalette(bool increase) { -#ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - // Throw away wake up input - return; - } -#endif - if (increase) { - effectPaletteIndex = (effectPaletteIndex + 1 >= strip.getPaletteCount()) ? 0 : (effectPaletteIndex + 1); - } - else { - effectPaletteIndex = (effectPaletteIndex - 1 < 0) ? (strip.getPaletteCount() - 1) : (effectPaletteIndex - 1); - } - effectPalette = palettes_alpha_indexes[effectPaletteIndex]; - lampUdated(); - } - - /* - * 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 - } - */ - - /* - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void addToJsonState(JsonObject &root) - { - //root["user0"] = userVar0; - } - - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void readFromJsonState(JsonObject &root) - { - //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value - //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); - } - - /** - * addToConfig() (called from set.cpp) stores persistent properties to cfg.json - */ - void addToConfig(JsonObject &root) { - // we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} - JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname - top[FPSTR(_enabled)] = enabled; - top[FPSTR(_DT_pin)] = pinA; - top[FPSTR(_CLK_pin)] = pinB; - top[FPSTR(_SW_pin)] = pinC; - DEBUG_PRINTLN(F("Rotary Encoder config saved.")); - } - - /** - * readFromConfig() is called before setup() to populate properties from values stored in cfg.json - * - * The function should return true if configuration was successfully loaded or false if there was no configuration. - */ - bool readFromConfig(JsonObject &root) { - // we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; - int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; - int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; - - enabled = top[FPSTR(_enabled)] | enabled; - - DEBUG_PRINT(FPSTR(_name)); - if (!initDone) { - // first run: reading from cfg.json - pinA = newDTpin; - pinB = newCLKpin; - pinC = newSWpin; - DEBUG_PRINTLN(F(" config loaded.")); - } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); - // changing parameters from settings page - if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) { - pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); - pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); - pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); - pinA = newDTpin; - pinB = newCLKpin; - pinC = newSWpin; - if (pinA<0 || pinB<0 || pinC<0) { - enabled = false; - return true; - } - setup(); - } - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_enabled)].isNull(); - } - - /* - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - * This could be used in the future for the system to determine whether your usermod is installed. - */ - uint16_t getId() - { - return USERMOD_ID_ROTARY_ENC_UI; - } -}; - -// strings to reduce flash memory usage (used more than twice) -const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder"; -const char RotaryEncoderUIUsermod::_enabled[] PROGMEM = "enabled"; -const char RotaryEncoderUIUsermod::_DT_pin[] PROGMEM = "DT-pin"; -const char RotaryEncoderUIUsermod::_CLK_pin[] PROGMEM = "CLK-pin"; -const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin"; diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index adea86774..6a15b520b 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -4,7 +4,7 @@ // // Inspired by the original v2 usermods -// * usermod_v2_rotaty_encoder_ui +// * usermod_v2_rotary_encoder_ui // // v2 usermod that provides a rotary encoder-based UI. // @@ -99,7 +99,7 @@ static int re_qstringCmp(const void *ap, const void *bp) { // Lowercase bVal -= 32; } - // Relly we shouldn't ever get to '\0' + // Really we shouldn't ever get to '\0' if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') { // We're done. one is a substring of the other // or something happenend and the quote didn't stop us. @@ -596,7 +596,7 @@ void RotaryEncoderUIUsermod::loop() bool changedState = false; char lineBuffer[64]; do { - // finde new state + // find new state switch (newState) { case 0: strcpy_P(lineBuffer, PSTR("Brightness")); changedState = true; break; case 1: if (!extractModeSlider(effectCurrent, 0, lineBuffer, 63)) newState++; else changedState = true; break; // speed diff --git a/usermods/usermod_v2_word_clock/readme.md b/usermods/usermod_v2_word_clock/readme.md index 1dde2223c..c42ee0ee4 100644 --- a/usermods/usermod_v2_word_clock/readme.md +++ b/usermods/usermod_v2_word_clock/readme.md @@ -8,7 +8,7 @@ active: enable/disable usermod diplayItIs: enable/disable display of "Es ist" on the clock ledOffset: number of LEDs before the wordclock LEDs -### Update for alternatative wiring pattern +### Update for alternative wiring pattern Based on this fantastic work I added an alternative wiring pattern. The original used a long wire to connect DO to DI, from one line to the next line. diff --git a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h index 058b8318b..b66be290a 100644 --- a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h +++ b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h @@ -7,8 +7,8 @@ * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality * * This usermod can be used to drive a wordclock with a 11x10 pixel matrix with WLED. There are also 4 additional dots for the minutes. - * The visualisation is desribed in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr"). - * There are 2 parameters to chnage the behaviour: + * The visualisation is described in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr"). + * There are 2 parameters to change the behaviour: * * active: enable/disable usermod * diplayItIs: enable/disable display of "Es ist" on the clock. diff --git a/usermods/wireguard/wireguard.h b/usermods/wireguard/wireguard.h index a83b9fe78..8c88d0001 100644 --- a/usermods/wireguard/wireguard.h +++ b/usermods/wireguard/wireguard.h @@ -66,9 +66,9 @@ class WireguardUsermod : public Usermod { void addToConfig(JsonObject& root) { JsonObject top = root.createNestedObject(F("WireGuard")); top[F("host")] = endpoint_address; - top[F("port")] = endpoint_port; - top[F("ip")] = local_ip.toString(); - top[F("psk")] = preshared_key; + top["port"] = endpoint_port; + top["ip"] = local_ip.toString(); + top["psk"] = preshared_key; top[F("pem")] = private_key; top[F("pub")] = public_key; top[F("tz")] = posix_tz; @@ -77,11 +77,11 @@ class WireguardUsermod : public Usermod { bool readFromConfig(JsonObject& root) { JsonObject top = root[F("WireGuard")]; - if (top["host"].isNull() || top["port"].isNull() || top["ip"].isNull() || top["pem"].isNull() || top["pub"].isNull() || top["tz"].isNull()) { + if (top[F("host")].isNull() || top["port"].isNull() || top["ip"].isNull() || top[F("pem")].isNull() || top[F("pub")].isNull() || top[F("tz")].isNull()) { is_enabled = false; return false; } else { - const char* host = top["host"]; + const char* host = top[F("host")]; strncpy(endpoint_address, host, 100); const char* ip_s = top["ip"]; @@ -89,16 +89,16 @@ class WireguardUsermod : public Usermod { sscanf(ip_s, "%u.%u.%u.%u", &ip[0], &ip[1], &ip[2], &ip[3]); local_ip = IPAddress(ip[0], ip[1], ip[2], ip[3]); - const char* pem = top["pem"]; + const char* pem = top[F("pem")]; strncpy(private_key, pem, 45); - const char* pub = top["pub"]; + const char* pub = top[F("pub")]; strncpy(public_key, pub, 45); - const char* tz = top["tz"]; + const char* tz = top[F("tz")]; strncpy(posix_tz, tz, 150); - endpoint_port = top["port"]; + endpoint_port = top[F("port")]; if (!top["psk"].isNull()) { const char* psk = top["psk"]; diff --git a/usermods/wizlights/readme.md b/usermods/wizlights/readme.md index a0e0a8b8f..9e633043b 100644 --- a/usermods/wizlights/readme.md +++ b/usermods/wizlights/readme.md @@ -1,6 +1,6 @@ # Controlling Wiz lights -Enabless controlling [WiZ](https://www.wizconnected.com/en/consumer/) lights that are part of the same network as the WLED controller. +Enables controlling [WiZ](https://www.wizconnected.com/en/consumer/) lights that are part of the same network as the WLED controller. The mod takes the colors from the first few pixels and sends them to the lights. @@ -8,7 +8,7 @@ The mod takes the colors from the first few pixels and sends them to the lights. - Interval (ms) - How frequently to update the WiZ lights, in milliseconds. - - Setting it too low may causse the ESP to become unresponsive. + - Setting it too low may cause the ESP to become unresponsive. - Send Delay (ms) - An optional millisecond delay after updating each WiZ light. - Can help smooth out effects when using a large number of WiZ lights diff --git a/usermods/word-clock-matrix/usermod_word_clock_matrix.h b/usermods/word-clock-matrix/usermod_word_clock_matrix.h index 582563004..506c1275e 100644 --- a/usermods/word-clock-matrix/usermod_word_clock_matrix.h +++ b/usermods/word-clock-matrix/usermod_word_clock_matrix.h @@ -325,8 +325,8 @@ public: void addToConfig(JsonObject& root) { JsonObject modName = root.createNestedObject("id"); - modName["mdns"] = "wled-word-clock"; - modName["name"] = "WLED WORD CLOCK"; + modName[F("mdns")] = "wled-word-clock"; + modName[F("name")] = "WLED WORD CLOCK"; } uint16_t getId() diff --git a/wled00/FX.cpp b/wled00/FX.cpp index be21ebe0e..681f3d107 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -51,7 +51,7 @@ uint16_t triwave16(uint16_t in) { * Generates a tristate square wave w/ attac & decay * @param x input value 0-255 * @param pulsewidth 0-127 - * @param attdec attac & decay, max. pulsewidth / 2 + * @param attdec attack & decay, max. pulsewidth / 2 * @returns signed waveform value */ int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { @@ -1239,7 +1239,7 @@ uint16_t mode_fireworks() { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, col); else SEGMENT.setPixelColor(index, col); SEGENV.aux1 = SEGENV.aux0; // old spark - SEGENV.aux0 = index; // remember where spark occured + SEGENV.aux0 = index; // remember where spark occurred } } return FRAMETIME; @@ -1272,8 +1272,8 @@ uint16_t mode_rain() { SEGENV.aux0++; // increase spark index SEGENV.aux1++; } - if (SEGENV.aux0 == 0) SEGENV.aux0 = UINT16_MAX; // reset previous spark positiom - if (SEGENV.aux1 == 0) SEGENV.aux0 = UINT16_MAX; // reset previous spark positiom + if (SEGENV.aux0 == 0) SEGENV.aux0 = UINT16_MAX; // reset previous spark position + if (SEGENV.aux1 == 0) SEGENV.aux0 = UINT16_MAX; // reset previous spark position if (SEGENV.aux0 >= width*height) SEGENV.aux0 = 0; // ignore if (SEGENV.aux1 >= width*height) SEGENV.aux1 = 0; } @@ -1848,10 +1848,10 @@ uint16_t mode_lightning(void) { } SEGENV.aux1--; - SEGENV.step = millis(); + SEGENV.step = strip.now; //return random8(4, 10); // each flash only lasts one frame/every 24ms... originally 4-10 milliseconds } else { - if (millis() - SEGENV.step > SEGENV.aux0) { + if (strip.now - SEGENV.step > SEGENV.aux0) { SEGENV.aux1--; if (SEGENV.aux1 < 2) SEGENV.aux1 = 0; @@ -1859,7 +1859,7 @@ uint16_t mode_lightning(void) { if (SEGENV.aux1 == 2) { SEGENV.aux0 = (random8(255 - SEGMENT.speed) * 100); // delay between strikes } - SEGENV.step = millis(); + SEGENV.step = strip.now; } } return FRAMETIME; @@ -1929,22 +1929,102 @@ static const char _data_FX_MODE_JUGGLE[] PROGMEM = "Juggle@!,Trail;;!;;sx=64,ix= uint16_t mode_palette() { - uint16_t counter = 0; - if (SEGMENT.speed != 0) - { - counter = (strip.now * ((SEGMENT.speed >> 3) +1)) & 0xFFFF; - counter = counter >> 8; - } + // Set up some compile time constants so that we can handle integer and float based modes using the same code base. +#ifdef ESP8266 + using mathType = int32_t; + using wideMathType = int64_t; + using angleType = uint16_t; + constexpr mathType sInt16Scale = 0x7FFF; + constexpr mathType maxAngle = 0x8000; + constexpr mathType staticRotationScale = 256; + constexpr mathType animatedRotationScale = 1; + constexpr int16_t (*sinFunction)(uint16_t) = &sin16; + constexpr int16_t (*cosFunction)(uint16_t) = &cos16; +#else + using mathType = float; + using wideMathType = float; + using angleType = float; + constexpr mathType sInt16Scale = 1.0f; + constexpr mathType maxAngle = M_PI / 256.0; + constexpr mathType staticRotationScale = 1.0f; + constexpr mathType animatedRotationScale = M_TWOPI / double(0xFFFF); + constexpr float (*sinFunction)(float) = &sin_t; + constexpr float (*cosFunction)(float) = &cos_t; +#endif + const bool isMatrix = strip.isMatrix; + const int cols = SEGMENT.virtualWidth(); + const int rows = isMatrix ? SEGMENT.virtualHeight() : strip.getActiveSegmentsNum(); - for (int i = 0; i < SEGLEN; i++) - { - uint8_t colorIndex = (i * 255 / SEGLEN) - counter; - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, PALETTE_MOVING_WRAP, 255)); - } + const int inputShift = SEGMENT.speed; + const int inputSize = SEGMENT.intensity; + const int inputRotation = SEGMENT.custom1; + const bool inputAnimateShift = SEGMENT.check1; + const bool inputAnimateRotation = SEGMENT.check2; + const bool inputAssumeSquare = SEGMENT.check3; + const angleType theta = (!inputAnimateRotation) ? (inputRotation * maxAngle / staticRotationScale) : (((strip.now * ((inputRotation >> 4) +1)) & 0xFFFF) * animatedRotationScale); + const mathType sinTheta = sinFunction(theta); + const mathType cosTheta = cosFunction(theta); + + const mathType maxX = std::max(1, cols-1); + const mathType maxY = std::max(1, rows-1); + // Set up some parameters according to inputAssumeSquare, so that we can handle anamorphic mode using the same code base. + const mathType maxXIn = inputAssumeSquare ? maxX : mathType(1); + const mathType maxYIn = inputAssumeSquare ? maxY : mathType(1); + const mathType maxXOut = !inputAssumeSquare ? maxX : mathType(1); + const mathType maxYOut = !inputAssumeSquare ? maxY : mathType(1); + const mathType centerX = sInt16Scale * maxXOut / mathType(2); + const mathType centerY = sInt16Scale * maxYOut / mathType(2); + // The basic idea for this effect is to rotate a rectangle that is filled with the palette along one axis, then map our + // display to it, to find what color a pixel should have. + // However, we want a) no areas of solid color (in front of or behind the palette), and b) we want to make use of the full palette. + // So the rectangle needs to have exactly the right size. That size depends on the rotation. + // This scale computation here only considers one dimension. You can think of it like the rectangle is always scaled so that + // the left and right most points always match the left and right side of the display. + const mathType scale = std::abs(sinTheta) + (std::abs(cosTheta) * maxYOut / maxXOut); + // 2D simulation: + // If we are dealing with a 1D setup, we assume that each segment represents one line on a 2-dimensional display. + // The function is called once per segments, so we need to handle one line at a time. + const int yFrom = isMatrix ? 0 : strip.getCurrSegmentId(); + const int yTo = isMatrix ? maxY : yFrom; + for (int y = yFrom; y <= yTo; ++y) { + // translate, scale, rotate + const mathType ytCosTheta = mathType((wideMathType(cosTheta) * wideMathType(y * sInt16Scale - centerY * maxYIn))/wideMathType(maxYIn * scale)); + for (int x = 0; x < cols; ++x) { + // translate, scale, rotate + const mathType xtSinTheta = mathType((wideMathType(sinTheta) * wideMathType(x * sInt16Scale - centerX * maxXIn))/wideMathType(maxXIn * scale)); + // Map the pixel coordinate to an imaginary-rectangle-coordinate. + // The y coordinate doesn't actually matter, as our imaginary rectangle is filled with the palette from left to right, + // so all points at a given x-coordinate have the same color. + const mathType sourceX = xtSinTheta + ytCosTheta + centerX; + // The computation was scaled just right so that the result should always be in range [0, maxXOut], but enforce this anyway + // to account for imprecision. Then scale it so that the range is [0, 255], which we can use with the palette. + int colorIndex = (std::min(std::max(sourceX, mathType(0)), maxXOut * sInt16Scale) * 255) / (sInt16Scale * maxXOut); + // inputSize determines by how much we want to scale the palette: + // values < 128 display a fraction of a palette, + // values > 128 display multiple palettes. + if (inputSize <= 128) { + colorIndex = (colorIndex * inputSize) / 128; + } else { + // Linear function that maps colorIndex 128=>1, 256=>9. + // With this function every full palette repetition is exactly 16 configuration steps wide. + // That allows displaying exactly 2 repetitions for example. + colorIndex = ((inputSize - 112) * colorIndex) / 16; + } + // Finally, shift the palette a bit. + const int paletteOffset = (!inputAnimateShift) ? (inputShift-128) : (((strip.now * ((inputShift >> 3) +1)) & 0xFFFF) >> 8); + colorIndex += paletteOffset; + const uint32_t color = SEGMENT.color_wheel((uint8_t)colorIndex); + if (isMatrix) { + SEGMENT.setPixelColorXY(x, y, color); + } else { + SEGMENT.setPixelColor(x, color); + } + } + } return FRAMETIME; } -static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Cycle speed;;!;;c3=0,o2=0"; +static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;c1=128,c2=128,c3=128,o1=1,o2=1,o3=0"; // WLED limitation: Analog Clock overlay will NOT work when Fire2012 is active @@ -2899,7 +2979,7 @@ uint16_t mode_bouncing_balls(void) { uint16_t numBalls = (SEGMENT.intensity * (maxNumBalls - 1)) / 255 + 1; // minimum 1 ball const float gravity = -9.81f; // standard value of gravity const bool hasCol2 = SEGCOLOR(2); - const unsigned long time = millis(); + const unsigned long time = strip.now; if (SEGENV.call == 0) { for (size_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; @@ -3336,7 +3416,7 @@ uint16_t mode_starburst(void) { if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - uint32_t it = millis(); + uint32_t it = strip.now; star* stars = reinterpret_cast(SEGENV.data); @@ -3692,12 +3772,12 @@ uint16_t mode_tetrix(void) { // initialize dropping on first call or segment full if (SEGENV.call == 0) { drop->stack = 0; // reset brick stack size - drop->step = millis() + 2000; // start by fading out strip + drop->step = strip.now + 2000; // start by fading out strip if (SEGMENT.check1) drop->col = 0;// use only one color from palette } if (drop->step == 0) { // init brick - // speed calcualtion: a single brick should reach bottom of strip in X seconds + // speed calculation: a single brick should reach bottom of strip in X seconds // if the speed is set to 1 this should take 5s and at 255 it should take 0.25s // as this is dependant on SEGLEN it should be taken into account and the fact that effect runs every FRAMETIME s int speed = SEGMENT.speed ? SEGMENT.speed : random8(1,255); @@ -3726,13 +3806,13 @@ uint16_t mode_tetrix(void) { } else { // we hit bottom drop->step = 0; // proceed with next brick, go back to init drop->stack += drop->brick; // increase the stack size - if (drop->stack >= SEGLEN) drop->step = millis() + 2000; // fade out stack + if (drop->stack >= SEGLEN) drop->step = strip.now + 2000; // fade out stack } } if (drop->step > 2) { // fade strip drop->brick = 0; // reset brick size (no more growing) - if (drop->step > millis()) { + if (drop->step > strip.now) { // allow fading of virtual strip for (int i = 0; i < SEGLEN; i++) SEGMENT.blendPixelColor(indexToVStrip(i, stripNr), SEGCOLOR(1), 25); // 10% blend } else { @@ -3780,7 +3860,7 @@ static const char _data_FX_MODE_PLASMA[] PROGMEM = "Plasma@Phase,!;!;!"; /* * Percentage display - * Intesity values from 0-100 turn on the leds. + * Intensity values from 0-100 turn on the leds. */ uint16_t mode_percent(void) { @@ -3833,7 +3913,7 @@ static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@,% of fill,,,,One c /* * Modulates the brightness similar to a heartbeat - * (unimplemented?) tries to draw an ECG aproximation on a 2D matrix + * (unimplemented?) tries to draw an ECG approximation on a 2D matrix */ uint16_t mode_heartbeat(void) { uint8_t bpm = 40 + (SEGMENT.speed >> 3); @@ -3991,7 +4071,7 @@ uint16_t mode_sunrise() { //speed 60 - 120 : sunset time in minutes - 60; //speed above: "breathing" rise and set if (SEGENV.call == 0 || SEGMENT.speed != SEGENV.aux0) { - SEGENV.step = millis(); //save starting time, millis() because now can change from sync + SEGENV.step = millis(); //save starting time, millis() because strip.now can change from sync SEGENV.aux0 = SEGMENT.speed; } @@ -4104,9 +4184,9 @@ uint16_t mode_noisepal(void) { // Slow noise CRGBPalette16* palettes = reinterpret_cast(SEGENV.data); uint16_t changePaletteMs = 4000 + SEGMENT.speed *10; //between 4 - 6.5sec - if (millis() - SEGENV.step > changePaletteMs) + if (strip.now - SEGENV.step > changePaletteMs) { - SEGENV.step = millis(); + SEGENV.step = strip.now; uint8_t baseI = random8(); palettes[1] = CRGBPalette16(CHSV(baseI+random8(64), 255, random8(128,255)), CHSV(baseI+128, 255, random8(128,255)), CHSV(baseI+random8(92), 192, random8(128,255)), CHSV(baseI+random8(92), 255, random8(128,255))); @@ -4261,7 +4341,7 @@ uint16_t mode_dancing_shadows(void) SEGMENT.fill(BLACK); - unsigned long time = millis(); + unsigned long time = strip.now; bool respawn = false; for (size_t i = 0; i < numSpotlights; i++) { @@ -4465,8 +4545,8 @@ uint16_t mode_tv_simulator(void) { } // create a new sceene - if (((millis() - tvSimulator->sceeneStart) >= tvSimulator->sceeneDuration) || SEGENV.aux1 == 0) { - tvSimulator->sceeneStart = millis(); // remember the start of the new sceene + if (((strip.now - tvSimulator->sceeneStart) >= tvSimulator->sceeneDuration) || SEGENV.aux1 == 0) { + tvSimulator->sceeneStart = strip.now; // 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 @@ -4517,13 +4597,13 @@ uint16_t mode_tv_simulator(void) { 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(); + tvSimulator->startTime = strip.now; } // end of initialization // how much time is elapsed ? - tvSimulator->elapsed = millis() - tvSimulator->startTime; + tvSimulator->elapsed = strip.now - tvSimulator->startTime; - // fade from prev volor to next color + // fade from prev color to next color if (tvSimulator->elapsed < tvSimulator->fadeTime) { r = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pr, nr); g = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pg, ng); @@ -4725,7 +4805,7 @@ uint16_t mode_perlinmove(void) { if (SEGLEN == 1) return mode_static(); SEGMENT.fade_out(255-SEGMENT.custom1); for (int i = 0; i < SEGMENT.intensity/16 + 1; i++) { - uint16_t locn = inoise16(millis()*128/(260-SEGMENT.speed)+i*15000, millis()*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. + uint16_t locn = inoise16(strip.now*128/(260-SEGMENT.speed)+i*15000, strip.now*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. uint16_t pixloc = map(locn, 50*256, 192*256, 0, SEGLEN-1); // Map that to the length of the strand, and ensure we don't go over. SEGMENT.setPixelColor(pixloc, SEGMENT.color_from_palette(pixloc%255, false, PALETTE_SOLID_WRAP, 0)); } @@ -4742,7 +4822,7 @@ static const char _data_FX_MODE_PERLINMOVE[] PROGMEM = "Perlin Move@!,# of pixel uint16_t mode_wavesins(void) { for (int i = 0; i < SEGLEN; i++) { - uint8_t bri = sin8(millis()/4 + i * SEGMENT.intensity); + uint8_t bri = sin8(strip.now/4 + i * SEGMENT.intensity); uint8_t index = beatsin8(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); // custom3 is reduced resolution slider //SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, index, bri, LINEARBLEND)); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, bri)); @@ -4760,8 +4840,8 @@ static const char _data_FX_MODE_WAVESINS[] PROGMEM = "Wavesins@!,Brightness vari uint16_t mode_FlowStripe(void) { const uint16_t hl = SEGLEN * 10 / 13; - uint8_t hue = millis() / (SEGMENT.speed+1); - uint32_t t = millis() / (SEGMENT.intensity/8+1); + uint8_t hue = strip.now / (SEGMENT.speed+1); + uint32_t t = strip.now / (SEGMENT.intensity/8+1); for (int i = 0; i < SEGLEN; i++) { int c = (abs(i - hl) / hl) * 127; @@ -4791,7 +4871,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma uint16_t x, y; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails - unsigned long t = millis()/128; // timebase + unsigned long t = strip.now/128; // timebase // outer stars for (size_t i = 0; i < 8; i++) { x = beatsin8(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + t * i); @@ -4877,8 +4957,8 @@ uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pa SEGMENT.fadeToBlackBy(64); for (int i = 0; i < cols; i++) { - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+millis()/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+millis()/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); + SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); + SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); } SEGMENT.blur(SEGMENT.intensity>>3); @@ -4890,7 +4970,7 @@ static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur;;!;2"; ///////////////////////// // 2D DNA Spiral // ///////////////////////// -uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulmatelights.com/gallery/810 , modified by: Andrew Tuline +uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulmatelights.com/gallery/512-dna-spiral-variation , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up const uint16_t cols = SEGMENT.virtualWidth(); @@ -4900,10 +4980,10 @@ uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulma SEGMENT.fill(BLACK); } - uint8_t speeds = SEGMENT.speed/2 + 1; + uint8_t speeds = SEGMENT.speed/2 + 7; uint8_t freq = SEGMENT.intensity/8; - uint32_t ms = millis() / 20; + uint32_t ms = strip.now / 20; SEGMENT.fadeToBlackBy(135); for (int i = 0; i < rows; i++) { @@ -4943,7 +5023,7 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli SEGMENT.fadeToBlackBy(128); const uint16_t maxDim = MAX(cols, rows)/2; - unsigned long t = millis() / (32 - (SEGMENT.speed>>3)); + unsigned long t = strip.now / (32 - (SEGMENT.speed>>3)); unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup for (float i = 1; i < maxDim; i += 0.25) { float angle = radians(t * (maxDim - i)); @@ -4982,7 +5062,7 @@ uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline for (int j=0; j < cols; j++) { for (int i=0; i < rows; i++) { - indexx = inoise8(j*yscale*rows/255, i*xscale+millis()/4); // We're moving along our Perlin map. + indexx = inoise8(j*yscale*rows/255, i*xscale+strip.now/4); // We're moving along our Perlin map. SEGMENT.setPixelColorXY(j, i, ColorFromPalette(SEGPALETTE, min(i*(indexx)>>4, 255), i*255/cols, LINEARBLEND)); // With that value, look up the 8 bit colour palette value and assign it to the current LED. } // for i } // for j @@ -5213,8 +5293,8 @@ uint16_t mode_2DJulia(void) { // An animated Julia set reAl = -0.94299f; // PixelBlaze example imAg = 0.3162f; - reAl += sin_t((float)millis()/305.f)/20.f; - imAg += sin_t((float)millis()/405.f)/20.f; + reAl += sin_t((float)strip.now/305.f)/20.f; + imAg += sin_t((float)strip.now/405.f)/20.f; dx = (xmax - xmin) / (cols); // Scale the delta x and y values to our matrix size. dy = (ymax - ymin) / (rows); @@ -5273,7 +5353,7 @@ uint16_t mode_2DLissajous(void) { // By: Andrew Tuline const uint16_t rows = SEGMENT.virtualHeight(); SEGMENT.fadeToBlackBy(SEGMENT.intensity); - uint_fast16_t phase = (millis() * (1 + SEGENV.custom3)) /32; // allow user to control rotation speed + uint_fast16_t phase = (strip.now * (1 + SEGENV.custom3)) /32; // allow user to control rotation speed //for (int i=0; i < 4*(cols+rows); i ++) { for (int i=0; i < 256; i ++) { @@ -5283,7 +5363,7 @@ uint16_t mode_2DLissajous(void) { // By: Andrew Tuline uint_fast8_t ylocn = cos8(phase/2 + i*2); xlocn = (cols < 2) ? 1 : (map(2*xlocn, 0,511, 0,2*(cols-1)) +1) /2; // softhack007: "(2* ..... +1) /2" for proper rounding ylocn = (rows < 2) ? 1 : (map(2*ylocn, 0,511, 0,2*(rows-1)) +1) /2; // "rows > 1" is needed to avoid div/0 in map() - SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(millis()/100+i, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); } return FRAMETIME; @@ -5433,7 +5513,7 @@ uint16_t mode_2Dnoise(void) { // By Andrew Tuline for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { - uint8_t pixelHue8 = inoise8(x * scale, y * scale, millis() / (16 - SEGMENT.speed/16)); + uint8_t pixelHue8 = inoise8(x * scale, y * scale, strip.now / (16 - SEGMENT.speed/16)); SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, pixelHue8)); } } @@ -5453,7 +5533,7 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito const uint16_t rows = SEGMENT.virtualHeight(); SEGMENT.fadeToBlackBy(SEGMENT.custom1>>2); - uint_fast32_t t = (millis() * 8) / (256 - SEGMENT.speed); // optimized to avoid float + uint_fast32_t t = (strip.now * 8) / (256 - SEGMENT.speed); // optimized to avoid float for (int i = 0; i < cols; i++) { uint16_t thisVal = inoise8(i * 30, t, t); uint16_t thisMax = map(thisVal, 0, 255, 0, cols-1); @@ -5571,7 +5651,7 @@ uint16_t mode_2DSindots(void) { // By: ldirko http SEGMENT.fadeToBlackBy(SEGMENT.custom1>>3); - byte t1 = millis() / (257 - SEGMENT.speed); // 20; + byte t1 = strip.now / (257 - SEGMENT.speed); // 20; byte t2 = sin8(t1) / 4 * 2; for (int i = 0; i < 13; i++) { byte x = sin8(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! @@ -5610,11 +5690,9 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g uint8_t n = beatsin8(15, kBorderWidth, rows-kBorderWidth); uint8_t p = beatsin8(20, kBorderWidth, rows-kBorderWidth); - uint16_t ms = millis(); - - SEGMENT.addPixelColorXY(i, m, ColorFromPalette(SEGPALETTE, ms/29, 255, LINEARBLEND)); - SEGMENT.addPixelColorXY(j, n, ColorFromPalette(SEGPALETTE, ms/41, 255, LINEARBLEND)); - SEGMENT.addPixelColorXY(k, p, ColorFromPalette(SEGPALETTE, ms/73, 255, LINEARBLEND)); + SEGMENT.addPixelColorXY(i, m, ColorFromPalette(SEGPALETTE, strip.now/29, 255, LINEARBLEND)); + SEGMENT.addPixelColorXY(j, n, ColorFromPalette(SEGPALETTE, strip.now/41, 255, LINEARBLEND)); + SEGMENT.addPixelColorXY(k, p, ColorFromPalette(SEGPALETTE, strip.now/73, 255, LINEARBLEND)); return FRAMETIME; } // mode_2Dsquaredswirl() @@ -5637,7 +5715,7 @@ uint16_t mode_2DSunradiation(void) { // By: ldirko https://edi SEGMENT.fill(BLACK); } - unsigned long t = millis() / 4; + unsigned long t = strip.now / 4; int index = 0; uint8_t someVal = SEGMENT.speed/4; // Was 25. for (int j = 0; j < (rows + 2); j++) { @@ -5769,14 +5847,14 @@ uint16_t mode_2Dcrazybees(void) { int8_t deltaX, deltaY, signX, signY, error; void aimed(uint16_t w, uint16_t h) { //random16_set_seed(millis()); - aimX = random8(0, w); - aimY = random8(0, h); - hue = random8(); + aimX = random8(0, w); + aimY = random8(0, h); + hue = random8(); deltaX = abs(aimX - posX); deltaY = abs(aimY - posY); - signX = posX < aimX ? 1 : -1; - signY = posY < aimY ? 1 : -1; - error = deltaX - deltaY; + signX = posX < aimX ? 1 : -1; + signY = posY < aimY ? 1 : -1; + error = deltaX - deltaY; }; } bee_t; @@ -5784,6 +5862,7 @@ uint16_t mode_2Dcrazybees(void) { bee_t *bee = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { + random16_set_seed(strip.now); for (size_t i = 0; i < n; i++) { bee[i].posX = random8(0, cols); bee[i].posY = random8(0, rows); @@ -5791,8 +5870,8 @@ uint16_t mode_2Dcrazybees(void) { } } - if (millis() > SEGENV.step) { - SEGENV.step = millis() + (FRAMETIME * 16 / ((SEGMENT.speed>>4)+1)); + if (strip.now > SEGENV.step) { + SEGENV.step = strip.now + (FRAMETIME * 16 / ((SEGMENT.speed>>4)+1)); SEGMENT.fadeToBlackBy(32); @@ -5869,8 +5948,8 @@ uint16_t mode_2Dghostrider(void) { } } - if (millis() > SEGENV.step) { - SEGENV.step = millis() + 1024 / (cols+rows); + if (strip.now > SEGENV.step) { + SEGENV.step = strip.now + 1024 / (cols+rows); SEGMENT.fadeToBlackBy((SEGMENT.speed>>2)+64); @@ -5958,7 +6037,7 @@ uint16_t mode_2Dfloatingblobs(void) { // Bounce balls around for (size_t i = 0; i < Amount; i++) { - if (SEGENV.step < millis()) blob->color[i] = add8(blob->color[i], 4); // slowly change color + if (SEGENV.step < strip.now) blob->color[i] = add8(blob->color[i], 4); // slowly change color // change radius if needed if (blob->grow[i]) { // enlarge radius until it is >= 4 @@ -6005,7 +6084,7 @@ uint16_t mode_2Dfloatingblobs(void) { } SEGMENT.blur(SEGMENT.custom1>>2); - if (SEGENV.step < millis()) SEGENV.step = millis() + 2000; // change colors every 2 seconds + if (SEGENV.step < strip.now) SEGENV.step = strip.now + 2000; // change colors every 2 seconds return FRAMETIME; } @@ -6069,13 +6148,12 @@ uint16_t mode_2Dscrollingtext(void) { } const int numberOfLetters = strlen(text); - const unsigned long now = millis(); // reduce millis() calls int width = (numberOfLetters * rotLW); int yoffset = map(SEGMENT.intensity, 0, 255, -rows/2, rows/2) + (rows-rotLH)/2; if (width <= cols) { // scroll vertically (e.g. ^^ Way out ^^) if it fits int speed = map(SEGMENT.speed, 0, 255, 5000, 1000); - int frac = now % speed + 1; + int frac = strip.now % speed + 1; if (SEGMENT.intensity == 255) { yoffset = (2 * frac * rows)/speed - rows; } else if (SEGMENT.intensity == 0) { @@ -6083,7 +6161,7 @@ uint16_t mode_2Dscrollingtext(void) { } } - if (SEGENV.step < now) { + if (SEGENV.step < strip.now) { // calculate start offset if (width > cols) { if (SEGMENT.check3) { @@ -6092,7 +6170,7 @@ uint16_t mode_2Dscrollingtext(void) { } else ++SEGENV.aux0 %= width + cols; } else SEGENV.aux0 = (cols + width)/2; ++SEGENV.aux1 &= 0xFF; // color shift - SEGENV.step = now + map(SEGMENT.speed, 0, 255, 250, 50); // shift letters every ~250ms to ~50ms + SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 250, 50); // shift letters every ~250ms to ~50ms } if (!SEGMENT.check2) SEGMENT.fade_out(255 - (SEGMENT.custom1>>4)); // trail @@ -6140,6 +6218,51 @@ uint16_t mode_2Ddriftrose(void) { } static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;;2"; +///////////////////////////// +// 2D PLASMA ROTOZOOMER // +///////////////////////////// +// Plasma Rotozoomer by ldirko (c)2020 [https://editor.soulmatelights.com/gallery/457-plasma-rotozoomer], adapted for WLED by Blaz Kristan (AKA blazoncek) +uint16_t mode_2Dplasmarotozoom() { + if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + uint16_t dataSize = SEGMENT.length() + sizeof(float); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + float *a = reinterpret_cast(SEGENV.data); + byte *plasma = reinterpret_cast(SEGENV.data+sizeof(float)); + + uint16_t ms = strip.now/15; + + // plasma + for (int j = 0; j < rows; j++) { + int index = j*cols; + for (int i = 0; i < cols; i++) { + if (SEGMENT.check1) plasma[index+i] = (i * 4 ^ j * 4) + ms / 6; + else plasma[index+i] = inoise8(i * 40, j * 40, ms); + } + } + + // rotozoom + float f = (sin_t(*a/2)+((128-SEGMENT.intensity)/128.0f)+1.1f)/1.5f; // scale factor + float kosinus = cos_t(*a) * f; + float sinus = sin_t(*a) * f; + for (int i = 0; i < cols; i++) { + float u1 = i * kosinus; + float v1 = i * sinus; + for (int j = 0; j < rows; j++) { + byte u = abs8(u1 - j * sinus) % cols; + byte v = abs8(v1 + j * kosinus) % rows; + SEGMENT.setPixelColorXY(i, j, SEGMENT.color_from_palette(plasma[v*cols+u], false, PALETTE_SOLID_WRAP, 0)); + } + } + *a -= 0.03f + float(SEGENV.speed-128)*0.0002f; // rotation speed + + return FRAMETIME; +} +static const char _data_FX_MODE_2DPLASMAROTOZOOM[] PROGMEM = "Rotozoomer@!,Scale,,,,Alt;;!;2;pal=54"; + #endif // WLED_DISABLE_2D @@ -6290,7 +6413,6 @@ uint16_t mode_2DSwirl(void) { uint8_t j = beatsin8( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); uint8_t ni = (cols - 1) - i; uint8_t nj = (cols - 1) - j; - uint16_t ms = millis(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6300,12 +6422,12 @@ uint16_t mode_2DSwirl(void) { float volumeSmth = *(float*) um_data->u_data[0]; //ewowi: use instead of sampleAvg??? int16_t volumeRaw = *(int16_t*) um_data->u_data[1]; - SEGMENT.addPixelColorXY( i, j, ColorFromPalette(SEGPALETTE, (ms / 11 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 11, 200, 255); - SEGMENT.addPixelColorXY( j, i, ColorFromPalette(SEGPALETTE, (ms / 13 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 13, 200, 255); - SEGMENT.addPixelColorXY(ni,nj, ColorFromPalette(SEGPALETTE, (ms / 17 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 17, 200, 255); - SEGMENT.addPixelColorXY(nj,ni, ColorFromPalette(SEGPALETTE, (ms / 29 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 29, 200, 255); - SEGMENT.addPixelColorXY( i,nj, ColorFromPalette(SEGPALETTE, (ms / 37 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 37, 200, 255); - SEGMENT.addPixelColorXY(ni, j, ColorFromPalette(SEGPALETTE, (ms / 41 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 41, 200, 255); + SEGMENT.addPixelColorXY( i, j, ColorFromPalette(SEGPALETTE, (strip.now / 11 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 11, 200, 255); + SEGMENT.addPixelColorXY( j, i, ColorFromPalette(SEGPALETTE, (strip.now / 13 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 13, 200, 255); + SEGMENT.addPixelColorXY(ni,nj, ColorFromPalette(SEGPALETTE, (strip.now / 17 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 17, 200, 255); + SEGMENT.addPixelColorXY(nj,ni, ColorFromPalette(SEGPALETTE, (strip.now / 29 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 29, 200, 255); + SEGMENT.addPixelColorXY( i,nj, ColorFromPalette(SEGPALETTE, (strip.now / 37 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 37, 200, 255); + SEGMENT.addPixelColorXY(ni, j, ColorFromPalette(SEGPALETTE, (strip.now / 41 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 41, 200, 255); return FRAMETIME; } // mode_2DSwirl() @@ -6331,7 +6453,7 @@ uint16_t mode_2DWaverly(void) { SEGMENT.fadeToBlackBy(SEGMENT.speed); - long t = millis() / 2; + long t = strip.now / 2; for (int i = 0; i < cols; i++) { uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; // use audio if available @@ -6386,14 +6508,14 @@ uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. SEGMENT.fade_out(251); // 30% float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; - segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling + segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivity" upscaling float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 32, 0, (float)SEGLEN/2.0f); // map to pixels available in current segment uint16_t tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; for (int i=0; itopLED--; if (gravcen->topLED >= 0) { - SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0)); - SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0)); } gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; @@ -6438,14 +6560,14 @@ uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew SEGMENT.fade_out(253); // 50% float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; - segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivty" upscaling + segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling float mySampleAvg = mapf(segmentSampleAvg*2.0, 0.0f, 32.0f, 0.0f, (float)SEGLEN/2.0f); // map to pixels availeable in current segment int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; for (int i=0; itopLED--; if (gravcen->topLED > 0) { - SEGMENT.setPixelColor(gravcen->topLED, SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(gravcen->topLED, SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0)); } gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; @@ -6529,7 +6651,7 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. uint16_t my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); for (size_t i=0; iSEGLEN/2) maxLen = SEGLEN/2; @@ -6625,7 +6747,7 @@ uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. if (SEGENV.call == 0) SEGMENT.fill(BLACK); for (int i = 0; i < SEGLEN; i++) { - uint16_t index = inoise8(i*SEGMENT.speed/64,millis()*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of millis(). By Andrew Tuline. + uint16_t index = inoise8(i*SEGMENT.speed/64,strip.now*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of millis(). By Andrew Tuline. index = (255 - i*256/SEGLEN) * index/(256-SEGMENT.intensity); // Now we need to scale index so that it gets blacker as we get close to one of the ends. // This is a simple y=mx+b equation that's been scaled. index/128 is another scaling. @@ -6697,7 +6819,7 @@ uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. int pixBri = volumeRaw * SEGMENT.intensity / 64; - SEGMENT.setPixelColor(SEGLEN/2, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0), pixBri)); + SEGMENT.setPixelColor(SEGLEN/2, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri)); for (int i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } @@ -6785,7 +6907,7 @@ uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. } for (int i=0; iu_data[0]; - myVals[millis()%32] = volumeSmth; // filling values semi randomly + myVals[strip.now%32] = volumeSmth; // filling values semi randomly SEGMENT.fade_out(64+(SEGMENT.speed>>1)); @@ -6998,7 +7120,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1; // MajorPeak holds the freq. value which is most abundant in the last sample. - // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz + // With our sampling rate of 10240Hz we have a usable freq range from roughly 80Hz to 10240/2 Hz // we will treat everything with less than 65Hz as 0 if (FFT_MajorPeak < 80) { @@ -7019,7 +7141,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch return FRAMETIME; } // mode_freqmatrix() -static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensivity;;;1f;m12=3,si=0"; // Corner, Beatsin +static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensitivity;;;1f;m12=3,si=0"; // Corner, Beatsin ////////////////////// @@ -7103,7 +7225,7 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1.0f; // MajorPeak holds the freq. value which is most abundant in the last sample. - // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz + // With our sampling rate of 10240Hz we have a usable freq range from roughly 80Hz to 10240/2 Hz // we will treat everything with less than 65Hz as 0 if (FFT_MajorPeak < 80) { @@ -7150,7 +7272,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. SEGMENT.fade_out(250); float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; - segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivty" upscaling + segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0,32, 0, (float)SEGLEN/2.0f); // map to pixels availeable in current segment int tempsamp = constrain(mySampleAvg,0,SEGLEN/2); // Keep the sample from overflowing. @@ -7178,7 +7300,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. return FRAMETIME; } // mode_gravfreq() -static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sensivity;!,!;!;1f;ix=128,m12=0,si=0"; // Pixels, Beatsin +static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sensitivity;!,!;!;1f;ix=128,m12=0,si=0"; // Pixels, Beatsin ////////////////////// @@ -7198,7 +7320,7 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. for (int i=0; i= (256U - SEGMENT.intensity)) { - SEGENV.step = millis(); + if (strip.now - SEGENV.step >= (256U - SEGMENT.intensity)) { + SEGENV.step = strip.now; rippleTime = true; } @@ -7532,7 +7654,7 @@ uint16_t mode_2Ddistortionwaves() { uint8_t w = 2; - uint16_t a = millis()/32; + uint16_t a = strip.now/32; uint16_t a2 = a/2; uint16_t a3 = a/3; @@ -7750,7 +7872,7 @@ uint16_t mode_2Dwavingcell() { const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - uint32_t t = millis()/(257-SEGMENT.speed); + uint32_t t = strip.now/(257-SEGMENT.speed); uint8_t aX = SEGMENT.custom1/16 + 9; uint8_t aY = SEGMENT.custom2/16 + 1; uint8_t aZ = SEGMENT.custom3 + 1; @@ -7770,7 +7892,7 @@ static const char _data_FX_MODE_2DWAVINGCELL[] PROGMEM = "Waving Cell@!,,Amplitu static const char _data_RESERVED[] PROGMEM = "RSVD"; // add (or replace reserved) effect mode and data into vector -// use id==255 to find unallocatd gaps (with "Reserved" data string) +// use id==255 to find unallocated gaps (with "Reserved" data string) // if vector size() is smaller than id (single) data is appended at the end (regardless of id) void WS2812FX::addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name) { if (id == 255) { // find empty slot @@ -7957,6 +8079,7 @@ void WS2812FX::setupEffectData() { // --- 2D effects --- #ifndef WLED_DISABLE_2D + addEffect(FX_MODE_2DPLASMAROTOZOOM, &mode_2Dplasmarotozoom, _data_FX_MODE_2DPLASMAROTOZOOM); addEffect(FX_MODE_2DSPACESHIPS, &mode_2Dspaceships, _data_FX_MODE_2DSPACESHIPS); addEffect(FX_MODE_2DCRAZYBEES, &mode_2Dcrazybees, _data_FX_MODE_2DCRAZYBEES); addEffect(FX_MODE_2DGHOSTRIDER, &mode_2Dghostrider, _data_FX_MODE_2DGHOSTRIDER); diff --git a/wled00/FX.h b/wled00/FX.h index b51c9dfb5..d72580935 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -180,7 +180,7 @@ #define FX_MODE_TWO_DOTS 50 #define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity) #define FX_MODE_RUNNING_DUAL 52 -// #define FX_MODE_HALLOWEEN 53 // removed in 0.14! +#define FX_MODE_IMAGE 53 // was Halloween before 0.14 #define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_WIPE 55 #define FX_MODE_TRICOLOR_FADE 56 @@ -241,7 +241,7 @@ #define FX_MODE_CHUNCHUN 111 #define FX_MODE_DANCING_SHADOWS 112 #define FX_MODE_WASHING_MACHINE 113 -#define FX_MODE_IMAGE 114 // was Candy Cane +#define FX_MODE_2DPLASMAROTOZOOM 114 // was Candy Cane prior to 0.14 (use Chase 2) #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 // candidate for removal (check3 in dynamic) @@ -420,7 +420,8 @@ typedef struct Segment { // perhaps this should be per segment, not static static CRGBPalette16 _randomPalette; // actual random palette static CRGBPalette16 _newRandomPalette; // target random palette - static unsigned long _lastPaletteChange; // last random palette change time in millis() + static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000 + static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF #ifndef WLED_DISABLE_MODE_BLEND static bool _modeBlend; // mode/effect blending semaphore #endif @@ -436,7 +437,7 @@ typedef struct Segment { uint8_t _briT; // temporary brightness uint8_t _cctT; // temporary CCT CRGBPalette16 _palT; // temporary palette - uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 belnds possible) + uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 blends possible) unsigned long _start; // must accommodate millis() uint16_t _dur; Transition(uint16_t dur=750) @@ -802,8 +803,7 @@ class WS2812FX { // 96 bytes getActiveSegmentsNum(void), getFirstSelectedSegId(void), getLastActiveSegmentId(void), - getActiveSegsLightCapabilities(bool selectedOnly = false), - setPixelSegment(uint8_t n); + getActiveSegsLightCapabilities(bool selectedOnly = false); inline uint8_t getBrightness(void) { return _brightness; } // returns current strip brightness inline uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 6f5075d08..7aecd2271 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -133,7 +133,7 @@ void WS2812FX::setUpMatrix() { DEBUG_PRINT(F("Matrix ledmap:")); for (unsigned i=0; i %p\n", &orig, this); + //DEBUG_PRINTF_P(PSTR("-- Copy segment constructor: %p -> %p\n"), &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); _t = nullptr; // copied segment cannot be in transition name = nullptr; @@ -99,7 +100,7 @@ Segment::Segment(const Segment &orig) { // move constructor Segment::Segment(Segment &&orig) noexcept { - //DEBUG_PRINTF("-- Move segment constructor: %p -> %p\n", &orig, this); + //DEBUG_PRINTF_P(PSTR("-- Move segment constructor: %p -> %p\n"), &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig._t = nullptr; // old segment cannot be in transition any more orig.name = nullptr; @@ -109,7 +110,7 @@ Segment::Segment(Segment &&orig) noexcept { // copy assignment Segment& Segment::operator= (const Segment &orig) { - //DEBUG_PRINTF("-- Copying segment: %p -> %p\n", &orig, this); + //DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this); if (this != &orig) { // clean destination if (name) { delete[] name; name = nullptr; } @@ -129,7 +130,7 @@ Segment& Segment::operator= (const Segment &orig) { // move assignment Segment& Segment::operator= (Segment &&orig) noexcept { - //DEBUG_PRINTF("-- Moving segment: %p -> %p\n", &orig, this); + //DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this); if (this != &orig) { if (name) { delete[] name; name = nullptr; } // free old name stopTransition(); @@ -150,12 +151,12 @@ bool IRAM_ATTR Segment::allocateData(size_t len) { if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation return true; } - //DEBUG_PRINTF("-- Allocating data (%d): %p\n", len, this); + //DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this); deallocateData(); // if the old buffer was smaller release it first if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) { // not enough memory DEBUG_PRINT(F("!!! Effect RAM depleted: ")); - DEBUG_PRINTF("%d/%d !!!\n", len, Segment::getUsedSegmentData()); + DEBUG_PRINTF_P(PSTR("%d/%d !!!\n"), len, Segment::getUsedSegmentData()); errorFlag = ERR_NORAM; return false; } @@ -163,21 +164,21 @@ bool IRAM_ATTR Segment::allocateData(size_t len) { data = (byte*)calloc(len, sizeof(byte)); if (!data) { DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); return false; } // allocation failed Segment::addUsedSegmentData(len); - //DEBUG_PRINTF("--- Allocated data (%p): %d/%d -> %p\n", this, len, Segment::getUsedSegmentData(), data); + //DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data); _dataLen = len; return true; } void IRAM_ATTR Segment::deallocateData() { if (!data) { _dataLen = 0; return; } - //DEBUG_PRINTF("--- Released data (%p): %d/%d -> %p\n", this, _dataLen, Segment::getUsedSegmentData(), data); + //DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data); if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer free(data); } else { DEBUG_PRINT(F("---- Released data ")); - DEBUG_PRINTF("(%p): ", this); + DEBUG_PRINTF_P(PSTR("(%p): "), this); DEBUG_PRINT(F("inconsistent UsedSegmentData ")); - DEBUG_PRINTF("(%d/%d)", _dataLen, Segment::getUsedSegmentData()); + DEBUG_PRINTF_P(PSTR("(%d/%d)"), _dataLen, Segment::getUsedSegmentData()); DEBUG_PRINTLN(F(", cowardly refusing to free nothing.")); } data = nullptr; @@ -194,7 +195,7 @@ void IRAM_ATTR Segment::deallocateData() { */ void Segment::resetIfRequired() { if (!reset) return; - //DEBUG_PRINTF("-- Segment reset: %p\n", this); + //DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this); if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; @@ -220,20 +221,9 @@ CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint switch (pal) { case 0: //default palette. Exceptions for specific effects above targetPalette = PartyColors_p; break; - case 1: {//periodically replace palette with a random one - unsigned long timeSinceLastChange = millis() - _lastPaletteChange; - if (timeSinceLastChange > randomPaletteChangeTime * 1000U) { - _randomPalette = _newRandomPalette; - _newRandomPalette = CRGBPalette16( - CHSV(random8(), random8(160, 255), random8(128, 255)), - CHSV(random8(), random8(160, 255), random8(128, 255)), - CHSV(random8(), random8(160, 255), random8(128, 255)), - CHSV(random8(), random8(160, 255), random8(128, 255))); - _lastPaletteChange = millis(); - handleRandomPalette(); // do a 1st pass of blend - } - targetPalette = _randomPalette; - break;} + case 1: //randomly generated palette + targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette() + break; case 2: {//primary color only CRGB prim = gamma32(colors[0]); targetPalette = CRGBPalette16(prim); break;} @@ -246,7 +236,7 @@ CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint CRGB sec = gamma32(colors[1]); CRGB ter = gamma32(colors[2]); targetPalette = CRGBPalette16(ter,sec,prim); break;} - case 5: {//primary + secondary (+tert if not off), more distinct + case 5: {//primary + secondary (+tertiary if not off), more distinct CRGB prim = gamma32(colors[0]); CRGB sec = gamma32(colors[1]); if (colors[2]) { @@ -285,7 +275,7 @@ CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint void Segment::startTransition(uint16_t dur) { if (dur == 0) { - if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransisiton() + if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransition() return; } if (isInTransition()) return; // already in transition no need to store anything @@ -294,7 +284,7 @@ void Segment::startTransition(uint16_t dur) { _t = new Transition(dur); // no previous transition running if (!_t) return; // failed to allocate data - //DEBUG_PRINTF("-- Started transition: %p (%p)\n", this, _t); + //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t); loadPalette(_t->_palT, palette); _t->_briT = on ? opacity : 0; _t->_cctT = cct; @@ -307,7 +297,7 @@ void Segment::startTransition(uint16_t dur) { if (_dataLen > 0 && data) { _t->_segT._dataT = (byte *)malloc(_dataLen); if (_t->_segT._dataT) { - //DEBUG_PRINTF("-- Allocated duplicate data (%d) for %p: %p\n", _dataLen, this, _t->_segT._dataT); + //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); memcpy(_t->_segT._dataT, data, _dataLen); _t->_segT._dataLenT = _dataLen; } @@ -322,10 +312,10 @@ void Segment::startTransition(uint16_t dur) { void Segment::stopTransition() { if (isInTransition()) { - //DEBUG_PRINTF("-- Stopping transition: %p\n", this); + //DEBUG_PRINTF_P(PSTR("-- Stopping transition: %p\n"), this); #ifndef WLED_DISABLE_MODE_BLEND if (_t->_segT._dataT && _t->_segT._dataLenT > 0) { - //DEBUG_PRINTF("-- Released duplicate data (%d) for %p: %p\n", _t->_segT._dataLenT, this, _t->_segT._dataT); + //DEBUG_PRINTF_P(PSTR("-- Released duplicate data (%d) for %p: %p\n"), _t->_segT._dataLenT, this, _t->_segT._dataT); free(_t->_segT._dataT); _t->_segT._dataT = nullptr; _t->_segT._dataLenT = 0; @@ -352,7 +342,7 @@ uint16_t IRAM_ATTR Segment::progress() { #ifndef WLED_DISABLE_MODE_BLEND void Segment::swapSegenv(tmpsegd_t &tmpSeg) { - //DEBUG_PRINTF("-- Saving temp seg: %p->(%p) [%d->%p]\n", this, &tmpSeg, _dataLen, data); + //DEBUG_PRINTF_P(PSTR("-- Saving temp seg: %p->(%p) [%d->%p]\n"), this, &tmpSeg, _dataLen, data); tmpSeg._optionsT = options; for (size_t i=0; i(%p) [%d->%p]\n", &tmpSeg, this, _dataLen, data); + //DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data); if (_t && &(_t->_segT) != &tmpSeg) { // update possibly changed variables to keep old effect running correctly _t->_segT._aux0T = aux0; _t->_segT._aux1T = aux1; _t->_segT._stepT = step; _t->_segT._callT = call; - //if (_t->_segT._dataT != data) DEBUG_PRINTF("--- data re-allocated: (%p) %p -> %p\n", this, _t->_segT._dataT, data); + //if (_t->_segT._dataT != data) DEBUG_PRINTF_P(PSTR("--- data re-allocated: (%p) %p -> %p\n"), this, _t->_segT._dataT, data); _t->_segT._dataT = data; _t->_segT._dataLenT = _dataLen; } @@ -440,6 +430,7 @@ uint8_t IRAM_ATTR Segment::currentMode() { } uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { + if (slot >= NUM_COLORS) slot = 0; #ifndef WLED_DISABLE_MODE_BLEND return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; #else @@ -461,10 +452,22 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u return targetPalette; } -// relies on WS2812FX::service() to call it max every 8ms or more (MIN_SHOW_DELAY) +// relies on WS2812FX::service() to call it for each frame void Segment::handleRandomPalette() { - // just do a blend; if the palettes are identical it will just compare 48 bytes (same as _randomPalette == _newRandomPalette) - // this will slowly blend _newRandomPalette into _randomPalette every 15ms or 8ms (depending on MIN_SHOW_DELAY) + // is it time to generate a new palette? + if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = millis()/1000U; + _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately + } + + // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) + if (strip.paletteFade) { + // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) + // in reality there need to be 255 blends to fully blend two entirely different palettes + if ((millis() & 0xFFFF) - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update + _lastPaletteBlend = millis(); + } nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } @@ -1025,7 +1028,7 @@ void Segment::blur(uint8_t blur_amount) { * Inspired by the Adafruit examples. */ uint32_t Segment::color_wheel(uint8_t pos) { - if (palette) return color_from_palette(pos, false, true, 0); + if (palette) return color_from_palette(pos, false, true, 0); // perhaps "strip.paletteBlend < 2" should be better instead of "true" uint8_t w = W(currentColor(0)); pos = 255 - pos; if (pos < 85) { @@ -1056,6 +1059,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ uint8_t paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); + // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" CRGBPalette16 curPal; curPal = currentPalette(curPal, palette); @@ -1140,7 +1144,7 @@ void WS2812FX::service() { _isServicing = true; _segment_index = 0; - Segment::handleRandomPalette(); // move it into for loop when each segment has individual random palette + for (segment &seg : _segments) { if (_suspend) return; // immediately stop processing segments if suspend requested during service() @@ -1203,6 +1207,7 @@ void WS2812FX::service() { #endif if (doShow) { yield(); + Segment::handleRandomPalette(); // slowly transtion random palette; move it into for loop when each segment has individual random palette show(); } #ifdef WLED_DEBUG @@ -1223,7 +1228,7 @@ uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) { } void WS2812FX::show(void) { - // avoid race condition, caputre _callback value + // avoid race condition, capture _callback value show_callback callback = _callback; if (callback) callback(); @@ -1250,7 +1255,7 @@ bool WS2812FX::isUpdating() { /** * Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough. - * Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accurary varies + * Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies */ uint16_t WS2812FX::getFps() { if (millis() - _lastShow > 2000) return 0; @@ -1453,6 +1458,7 @@ void WS2812FX::resetSegments() { segment seg = Segment(0, _length); #endif _segments.push_back(seg); + _segments.shrink_to_fit(); // just in case ... _mainSegment = 0; } @@ -1571,18 +1577,7 @@ bool WS2812FX::checkSegmentAlignment() { return true; } -//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply) -//Note: If called in an interrupt (e.g. JSON API), original segment must be restored, -//otherwise it can lead to a crash on ESP32 because _segment_index is modified while in use by the main thread -uint8_t WS2812FX::setPixelSegment(uint8_t n) { - uint8_t prevSegId = _segment_index; - if (n < _segments.size()) { - _segment_index = n; - _virtualSegmentLength = _segments[_segment_index].virtualLength(); - } - return prevSegId; -} - +// used by analog clock overlay void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { if (i2 < i) std::swap(i,i2); for (unsigned x = i; x <= i2; x++) setPixelColor(x, col); @@ -1592,12 +1587,12 @@ void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { void WS2812FX::printSize() { size_t size = 0; for (const Segment &seg : _segments) size += seg.getSize(); - DEBUG_PRINTF("Segments: %d -> %uB\n", _segments.size(), size); - DEBUG_PRINTF("Modes: %d*%d=%uB\n", sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr))); - DEBUG_PRINTF("Data: %d*%d=%uB\n", sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *))); - DEBUG_PRINTF("Map: %d*%d=%uB\n", sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t)); + DEBUG_PRINTF_P(PSTR("Segments: %d -> %uB\n"), _segments.size(), size); + DEBUG_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr))); + DEBUG_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *))); + DEBUG_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t)); size = getLengthTotal(); - if (useGlobalLedBuffer) DEBUG_PRINTF("Buffer: %d*%u=%uB\n", sizeof(CRGB), size, size*sizeof(CRGB)); + if (useGlobalLedBuffer) DEBUG_PRINTF_P(PSTR("Buffer: %d*%u=%uB\n"), sizeof(CRGB), size, size*sizeof(CRGB)); } #endif @@ -1626,7 +1621,7 @@ void WS2812FX::loadCustomPalettes() { tcp[ j ] = (uint8_t) pal[ i ].as(); // index colorFromHexString(rgbw, pal[i+1].as()); // will catch non-string entires for (size_t c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component - DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); + DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); } } else { size_t palSize = MIN(pal.size(), 72); @@ -1636,7 +1631,7 @@ void WS2812FX::loadCustomPalettes() { tcp[i+1] = gamma8((uint8_t) pal[i+1].as()); // R tcp[i+2] = gamma8((uint8_t) pal[i+2].as()); // G tcp[i+3] = gamma8((uint8_t) pal[i+3].as()); // B - DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); + DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); } } customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp)); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp old mode 100755 new mode 100644 index b0ea62e87..3ac12c04e --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -12,7 +12,6 @@ //colors.cpp uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint16_t approximateKelvinFromRGB(uint32_t rgb); -void colorRGBtoRGBW(byte* rgb); //udp.cpp uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false); @@ -32,10 +31,12 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte #define DEBUG_PRINT(x) DEBUGOUT.print(x) #define DEBUG_PRINTLN(x) DEBUGOUT.println(x) #define DEBUG_PRINTF(x...) DEBUGOUT.printf(x) + #define DEBUG_PRINTF_P(x...) DEBUGOUT.printf_P(x) #else #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) #define DEBUG_PRINTF(x...) + #define DEBUG_PRINTF_P(x...) #endif //color mangling macros @@ -375,13 +376,22 @@ BusPwm::BusPwm(BusConfig &bc) _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; #ifdef ESP8266 - analogWriteRange(255); //same range as one RGB channel + // duty cycle resolution (_depth) can be extracted from this formula: 1MHz > _frequency * 2^_depth + if (_frequency > 1760) _depth = 8; + else if (_frequency > 880) _depth = 9; + else _depth = 10; // WLED_PWM_FREQ <= 880Hz + analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); #else _ledcStart = pinManager.allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels deallocatePins(); return; } + // duty cycle resolution (_depth) can be extracted from this formula: 80MHz > _frequency * 2^_depth + if (_frequency > 78124) _depth = 9; + else if (_frequency > 39062) _depth = 10; + else if (_frequency > 19531) _depth = 11; + else _depth = 12; // WLED_PWM_FREQ <= 19531Hz #endif for (unsigned i = 0; i < numPins; i++) { @@ -393,7 +403,7 @@ BusPwm::BusPwm(BusConfig &bc) #ifdef ESP8266 pinMode(_pins[i], OUTPUT); #else - ledcSetup(_ledcStart + i, _frequency, 8); + ledcSetup(_ledcStart + i, _frequency, _depth); ledcAttachPin(_pins[i], _ledcStart + i); #endif } @@ -460,12 +470,49 @@ uint32_t BusPwm::getPixelColor(uint16_t pix) { return RGBW32(_data[0], _data[1], _data[2], _data[3]); } +#ifndef ESP8266 +static const uint16_t cieLUT[256] = { + 0, 2, 4, 5, 7, 9, 11, 13, 15, 16, + 18, 20, 22, 24, 26, 27, 29, 31, 33, 35, + 34, 36, 37, 39, 41, 43, 45, 47, 49, 52, + 54, 56, 59, 61, 64, 67, 69, 72, 75, 78, + 81, 84, 87, 90, 94, 97, 100, 104, 108, 111, + 115, 119, 123, 127, 131, 136, 140, 144, 149, 154, + 158, 163, 168, 173, 178, 183, 189, 194, 200, 205, + 211, 217, 223, 229, 235, 241, 247, 254, 261, 267, + 274, 281, 288, 295, 302, 310, 317, 325, 333, 341, + 349, 357, 365, 373, 382, 391, 399, 408, 417, 426, + 436, 445, 455, 464, 474, 484, 494, 505, 515, 526, + 536, 547, 558, 569, 580, 592, 603, 615, 627, 639, + 651, 663, 676, 689, 701, 714, 727, 741, 754, 768, + 781, 795, 809, 824, 838, 853, 867, 882, 897, 913, + 928, 943, 959, 975, 991, 1008, 1024, 1041, 1058, 1075, + 1092, 1109, 1127, 1144, 1162, 1180, 1199, 1217, 1236, 1255, + 1274, 1293, 1312, 1332, 1352, 1372, 1392, 1412, 1433, 1454, + 1475, 1496, 1517, 1539, 1561, 1583, 1605, 1628, 1650, 1673, + 1696, 1719, 1743, 1767, 1791, 1815, 1839, 1864, 1888, 1913, + 1939, 1964, 1990, 2016, 2042, 2068, 2095, 2121, 2148, 2176, + 2203, 2231, 2259, 2287, 2315, 2344, 2373, 2402, 2431, 2461, + 2491, 2521, 2551, 2581, 2612, 2643, 2675, 2706, 2738, 2770, + 2802, 2835, 2867, 2900, 2934, 2967, 3001, 3035, 3069, 3104, + 3138, 3174, 3209, 3244, 3280, 3316, 3353, 3389, 3426, 3463, + 3501, 3539, 3576, 3615, 3653, 3692, 3731, 3770, 3810, 3850, + 3890, 3930, 3971, 4012, 4053, 4095 +}; +#endif + void BusPwm::show() { if (!_valid) return; uint8_t numPins = NUM_PWM_PINS(_type); + unsigned maxBri = (1<<_depth) - 1; + #ifdef ESP8266 + unsigned pwmBri = (unsigned)(roundf(powf((float)_bri / 255.0f, 1.7f) * (float)maxBri + 0.5f)); // using gamma 1.7 to extrapolate PWM duty cycle + #else + unsigned pwmBri = cieLUT[_bri] >> (12 - _depth); // use CIE LUT + #endif for (unsigned i = 0; i < numPins; i++) { - uint8_t scaled = (_data[i] * _bri) / 255; - if (_reversed) scaled = 255 - scaled; + unsigned scaled = (_data[i] * pwmBri) / 255; + if (_reversed) scaled = maxBri - scaled; #ifdef ESP8266 analogWrite(_pins[i], scaled); #else @@ -552,6 +599,10 @@ BusNetwork::BusNetwork(BusConfig &bc) _rgbw = false; _UDPtype = 2; break; + case TYPE_NET_ARTNET_RGBW: + _rgbw = true; + _UDPtype = 2; + break; case TYPE_NET_E131_RGB: _rgbw = false; _UDPtype = 1; @@ -619,7 +670,7 @@ uint32_t BusManager::memUsage(BusConfig &bc) { multiplier = 5; } #else //ESP32 RMT uses double buffer, I2S uses 5x buffer - if (type > 28) channels = 4; //RGBW + if (bc.type > 28) channels = 4; //RGBW multiplier = 2; #endif } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index b2dbd50fa..0b791adf3 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -242,7 +242,7 @@ class BusDigital : public Bus { uint8_t* chan = (uint8_t*) &c; for (uint_fast8_t i=0; i<4; i++) { uint_fast16_t val = chan[i]; - chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); //adding _bri slighly improves recovery / stops degradation on re-scale + chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); //adding _bri slightly improves recovery / stops degradation on re-scale } } return c; @@ -268,6 +268,7 @@ class BusPwm : public Bus { #ifdef ARDUINO_ARCH_ESP32 uint8_t _ledcStart; #endif + uint8_t _depth; uint16_t _frequency; void deallocatePins(); diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 72b4435e5..c63e055a8 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -63,23 +63,28 @@ #define I_8266_U1_UCS_4 54 #define I_8266_DM_UCS_4 55 #define I_8266_BB_UCS_4 56 +//ESP8266 APA106 +#define I_8266_U0_APA106_3 81 +#define I_8266_U1_APA106_3 82 +#define I_8266_DM_APA106_3 83 +#define I_8266_BB_APA106_3 84 /*** ESP32 Neopixel methods ***/ //RGB #define I_32_RN_NEO_3 21 #define I_32_I0_NEO_3 22 #define I_32_I1_NEO_3 23 -#define I_32_BB_NEO_3 24 // bitbangging on ESP32 not recommended +#define I_32_BB_NEO_3 24 // bitbanging on ESP32 not recommended //RGBW #define I_32_RN_NEO_4 25 #define I_32_I0_NEO_4 26 #define I_32_I1_NEO_4 27 -#define I_32_BB_NEO_4 28 // bitbangging on ESP32 not recommended +#define I_32_BB_NEO_4 28 // bitbanging on ESP32 not recommended //400Kbps #define I_32_RN_400_3 29 #define I_32_I0_400_3 30 #define I_32_I1_400_3 31 -#define I_32_BB_400_3 32 // bitbangging on ESP32 not recommended +#define I_32_BB_400_3 32 // bitbanging on ESP32 not recommended //TM1814 (RGBW) #define I_32_RN_TM1_4 33 #define I_32_I0_TM1_4 34 @@ -100,6 +105,10 @@ #define I_32_I0_UCS_4 61 #define I_32_I1_UCS_4 62 //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +#define I_32_RN_APA106_3 85 +#define I_32_I0_APA106_3 86 +#define I_32_I1_APA106_3 87 +#define I_32_BB_APA106_3 88 // bitbangging on ESP32 not recommended //APA102 #define I_HS_DOT_3 39 //hardware SPI @@ -162,6 +171,11 @@ #define B_8266_U1_UCS_4 NeoPixelBusLg //4 chan, esp8266, gpio2 #define B_8266_DM_UCS_4 NeoPixelBusLg //4 chan, esp8266, gpio3 #define B_8266_BB_UCS_4 NeoPixelBusLg //4 chan, esp8266, bb (any pin) +//APA106 +#define B_8266_U0_APA106_3 NeoPixelBusLg //3 chan, esp8266, gpio1 +#define B_8266_U1_APA106_3 NeoPixelBusLg //3 chan, esp8266, gpio2 +#define B_8266_DM_APA106_3 NeoPixelBusLg //3 chan, esp8266, gpio3 +#define B_8266_BB_APA106_3 NeoPixelBusLg //3 chan, esp8266, bb (any pin but 16) #endif /*** ESP32 Neopixel methods ***/ @@ -229,6 +243,14 @@ #define B_32_I1_UCS_4 NeoPixelBusLg #endif //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +#define B_32_RN_APA106_3 NeoPixelBusLg +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_APA106_3 NeoPixelBusLg +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_APA106_3 NeoPixelBusLg +#endif +//#define B_32_BB_APA106_3 NeoPixelBusLg // NeoEsp8266BitBang800KbpsMethod #endif @@ -327,6 +349,10 @@ class PolyBus { case I_8266_U1_UCS_4: (static_cast(busPtr))->Begin(); break; case I_8266_DM_UCS_4: (static_cast(busPtr))->Begin(); break; case I_8266_BB_UCS_4: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_APA106_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_APA106_3: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_APA106_3: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_APA106_3: (static_cast(busPtr))->Begin(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Begin(); break; @@ -379,6 +405,14 @@ class PolyBus { case I_32_I1_UCS_4: (static_cast(busPtr))->Begin(); break; #endif // case I_32_BB_UCS_4: (static_cast(busPtr))->Begin(); break; + case I_32_RN_APA106_3: (static_cast(busPtr))->Begin(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_APA106_3: (static_cast(busPtr))->Begin(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_APA106_3: (static_cast(busPtr))->Begin(); break; + #endif +// case I_32_BB_APA106_3: (static_cast(busPtr))->Begin(); break; // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() case I_HS_DOT_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; case I_HS_LPD_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; @@ -427,6 +461,10 @@ class PolyBus { case I_8266_U1_UCS_4: busPtr = new B_8266_U1_UCS_4(len, pins[0]); break; case I_8266_DM_UCS_4: busPtr = new B_8266_DM_UCS_4(len, pins[0]); break; case I_8266_BB_UCS_4: busPtr = new B_8266_BB_UCS_4(len, pins[0]); break; + case I_8266_U0_APA106_3: busPtr = new B_8266_U0_APA106_3(len, pins[0]); break; + case I_8266_U1_APA106_3: busPtr = new B_8266_U1_APA106_3(len, pins[0]); break; + case I_8266_DM_APA106_3: busPtr = new B_8266_DM_APA106_3(len, pins[0]); break; + case I_8266_BB_APA106_3: busPtr = new B_8266_BB_APA106_3(len, pins[0]); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break; @@ -479,6 +517,14 @@ class PolyBus { case I_32_I1_UCS_4: busPtr = new B_32_I1_UCS_4(len, pins[0]); break; #endif // case I_32_BB_UCS_4: busPtr = new B_32_BB_UCS_4(len, pins[0], (NeoBusChannel)channel); break; + case I_32_RN_APA106_3: busPtr = new B_32_RN_APA106_3(len, pins[0], (NeoBusChannel)channel); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_APA106_3: busPtr = new B_32_I0_APA106_3(len, pins[0]); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_APA106_3: busPtr = new B_32_I1_APA106_3(len, pins[0]); break; + #endif +// case I_32_BB_APA106_3: busPtr = new B_32_BB_APA106_3(len, pins[0], (NeoBusChannel)channel); break; #endif // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) case I_HS_DOT_3: busPtr = new B_HS_DOT_3(len, pins[1], pins[0]); break; @@ -528,6 +574,10 @@ class PolyBus { case I_8266_U1_UCS_4: (static_cast(busPtr))->Show(consistent); break; case I_8266_DM_UCS_4: (static_cast(busPtr))->Show(consistent); break; case I_8266_BB_UCS_4: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_APA106_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_APA106_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_APA106_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_APA106_3: (static_cast(busPtr))->Show(consistent); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Show(consistent); break; @@ -580,6 +630,14 @@ class PolyBus { case I_32_I1_UCS_4: (static_cast(busPtr))->Show(consistent); break; #endif // case I_32_BB_UCS_4: (static_cast(busPtr))->Show(consistent); break; + case I_32_RN_APA106_3: (static_cast(busPtr))->Show(consistent); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_APA106_3: (static_cast(busPtr))->Show(consistent); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_APA106_3: (static_cast(busPtr))->Show(consistent); break; + #endif +// case I_32_BB_APA106_3: (static_cast(busPtr))->Show(consistent); break; #endif case I_HS_DOT_3: (static_cast(busPtr))->Show(consistent); break; case I_SS_DOT_3: (static_cast(busPtr))->Show(consistent); break; @@ -625,6 +683,10 @@ class PolyBus { case I_8266_U0_UCS_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_U1_UCS_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_UCS_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_APA106_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_APA106_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_APA106_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_APA106_3: return (static_cast(busPtr))->CanShow(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break; @@ -677,6 +739,14 @@ class PolyBus { case I_32_I1_UCS_4: return (static_cast(busPtr))->CanShow(); break; #endif // case I_32_BB_UCS_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_APA106_3: return (static_cast(busPtr))->CanShow(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_APA106_3: return (static_cast(busPtr))->CanShow(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_APA106_3: return (static_cast(busPtr))->CanShow(); break; + #endif +// case I_32_BB_APA106_3: return (static_cast(busPtr))->CanShow(); break; #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; case I_SS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -747,6 +817,10 @@ class PolyBus { case I_8266_U1_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; case I_8266_DM_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; case I_8266_BB_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_8266_U0_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_U1_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_DM_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_8266_BB_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -799,6 +873,14 @@ class PolyBus { case I_32_I1_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; #endif // case I_32_BB_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_32_RN_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + #endif +// case I_32_BB_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_SS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -845,6 +927,10 @@ class PolyBus { case I_8266_U1_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_DM_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; case I_8266_BB_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -897,6 +983,14 @@ class PolyBus { case I_32_I1_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; #endif // case I_32_BB_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_RN_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; + #endif +// case I_32_BB_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; #endif case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; case I_SS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -944,6 +1038,10 @@ class PolyBus { case I_8266_U1_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; case I_8266_DM_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; case I_8266_BB_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; + case I_8266_U0_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U1_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_DM_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_BB_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -996,6 +1094,14 @@ class PolyBus { case I_32_I1_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; #endif // case I_32_BB_UCS_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_RN_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + #endif +// case I_32_BB_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_SS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1061,6 +1167,10 @@ class PolyBus { case I_8266_U1_UCS_4: delete (static_cast(busPtr)); break; case I_8266_DM_UCS_4: delete (static_cast(busPtr)); break; case I_8266_BB_UCS_4: delete (static_cast(busPtr)); break; + case I_8266_U0_APA106_3: delete (static_cast(busPtr)); break; + case I_8266_U1_APA106_3: delete (static_cast(busPtr)); break; + case I_8266_DM_APA106_3: delete (static_cast(busPtr)); break; + case I_8266_BB_APA106_3: delete (static_cast(busPtr)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: delete (static_cast(busPtr)); break; @@ -1113,6 +1223,14 @@ class PolyBus { case I_32_I1_UCS_4: delete (static_cast(busPtr)); break; #endif // case I_32_BB_UCS_4: delete (static_cast(busPtr)); break; + case I_32_RN_APA106_3: delete (static_cast(busPtr)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_APA106_3: delete (static_cast(busPtr)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_APA106_3: delete (static_cast(busPtr)); break; + #endif +// case I_32_BB_APA106_3: delete (static_cast(busPtr)); break; #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; case I_SS_DOT_3: delete (static_cast(busPtr)); break; @@ -1172,6 +1290,8 @@ class PolyBus { return I_8266_U0_UCS_3 + offset; case TYPE_UCS8904: return I_8266_U0_UCS_4 + offset; + case TYPE_APA106: + return I_8266_U0_APA106_3 + offset; } #else //ESP32 uint8_t offset = 0; //0 = RMT (num 0-7) 8 = I2S0 9 = I2S1 @@ -1210,6 +1330,8 @@ class PolyBus { return I_32_RN_UCS_3 + offset; case TYPE_UCS8904: return I_32_RN_UCS_4 + offset; + case TYPE_APA106: + return I_32_RN_APA106_3 + offset; } #endif } diff --git a/wled00/button.cpp b/wled00/button.cpp index b1e7f2a79..29cb0abeb 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -168,8 +168,6 @@ void handleAnalog(uint8_t b) #endif yield(); // keep WiFi task running - analog read may take several millis on ESP8266 - DEBUG_PRINT(F("Analog: Raw read = ")); DEBUG_PRINTLN(rawReading); - filteredReading[b] += POT_SMOOTHING * ((float(rawReading) / 16.0f) - filteredReading[b]); // filter raw input, and scale to [0..255] uint16_t aRead = max(min(int(filteredReading[b]), 255), 0); // squash into 8bit if(aRead <= POT_SENSITIVITY) aRead = 0; // make sure that 0 and 255 are used @@ -180,7 +178,8 @@ void handleAnalog(uint8_t b) // remove noise & reduce frequency of UI updates if (abs(int(aRead) - int(oldRead[b])) <= POT_SENSITIVITY) return; // no significant change in reading - DEBUG_PRINT(F("Analog: Filtered read = ")); DEBUG_PRINTLN(aRead); + DEBUG_PRINT(F("Analog: Raw = ")); DEBUG_PRINT(rawReading); + DEBUG_PRINT(F(" Filtered = ")); DEBUG_PRINTLN(aRead); // Unomment the next lines if you still see flickering related to potentiometer // This waits until strip finishes updating (why: strip was not updating at the start of handleButton() but may have started during analogRead()?) @@ -188,7 +187,6 @@ void handleAnalog(uint8_t b) //while(strip.isUpdating() && (millis() - wait_started < STRIP_WAIT_TIME)) { // delay(1); //} - //if (strip.isUpdating()) return; // give up oldRead[b] = aRead; @@ -240,11 +238,11 @@ void handleAnalog(uint8_t b) void handleButton() { - static unsigned long lastRead = 0UL; + static unsigned long lastAnalogRead = 0UL; static unsigned long lastRun = 0UL; unsigned long now = millis(); - if (strip.isUpdating() && (now - lastRun < 400)) return; // don't interfere with strip update (unless strip is updating continuously, e.g. very long strips) + if (strip.isUpdating() && (now - lastRun < ANALOG_BTN_READ_CYCLE+1)) return; // don't interfere with strip update (unless strip is updating continuously, e.g. very long strips) lastRun = now; for (uint8_t b=0; b ANALOG_BTN_READ_CYCLE) { + if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) { handleAnalog(b); - lastRead = now; } continue; } @@ -339,6 +336,9 @@ void handleButton() shortPressAction(b); } } + if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) { + lastAnalogRead = now; + } } // If enabled, RMT idle level is set to HIGH when off @@ -388,7 +388,7 @@ void handleIO() if (!offMode) { #ifdef ESP8266 // turn off built-in LED if strip is turned off - // this will break digital bus so will need to be reinitialised on On + // this will break digital bus so will need to be re-initialised on On PinOwner ledPinOwner = pinManager.getPinOwner(LED_BUILTIN); if (!strip.isOffRefreshRequired() && (ledPinOwner == PinOwner::None || ledPinOwner == PinOwner::BusDigital)) { pinMode(LED_BUILTIN, OUTPUT); @@ -405,4 +405,4 @@ void handleIO() } offMode = true; } -} \ No newline at end of file +} diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp old mode 100755 new mode 100644 index 1234a1c5f..e51b666e4 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -40,21 +40,39 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { linked_remote[12] = '\0'; #endif - JsonObject nw_ins_0 = nw["ins"][0]; - getStringFromJson(clientSSID, nw_ins_0[F("ssid")], 33); - //int nw_ins_0_pskl = nw_ins_0[F("pskl")]; - //The WiFi PSK is normally not contained in the regular file for security reasons. - //If it is present however, we will use it - getStringFromJson(clientPass, nw_ins_0["psk"], 65); + size_t n = 0; + JsonArray nw_ins = nw["ins"]; + if (!nw_ins.isNull()) { + // as password are stored separately in wsec.json when reading configuration vector resize happens there, but for dynamic config we need to resize if necessary + if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing + for (JsonObject wifi : nw_ins) { + JsonArray ip = wifi["ip"]; + JsonArray gw = wifi["gw"]; + JsonArray sn = wifi["sn"]; + char ssid[33] = ""; + char pass[65] = ""; + IPAddress nIP = (uint32_t)0U, nGW = (uint32_t)0U, nSN = (uint32_t)0x00FFFFFF; // little endian + getStringFromJson(ssid, wifi[F("ssid")], 33); + getStringFromJson(pass, wifi["psk"], 65); // password is not normally present but if it is, use it + for (size_t i = 0; i < 4; i++) { + CJSON(nIP[i], ip[i]); + CJSON(nGW[i], gw[i]); + CJSON(nSN[i], sn[i]); + } + if (strlen(ssid) > 0) strlcpy(multiWiFi[n].clientSSID, ssid, 33); // this will keep old SSID intact if not present in JSON + if (strlen(pass) > 0) strlcpy(multiWiFi[n].clientPass, pass, 65); // this will keep old password intact if not present in JSON + multiWiFi[n].staticIP = nIP; + multiWiFi[n].staticGW = nGW; + multiWiFi[n].staticSN = nSN; + if (++n >= WLED_MAX_WIFI_COUNT) break; + } + } - JsonArray nw_ins_0_ip = nw_ins_0["ip"]; - JsonArray nw_ins_0_gw = nw_ins_0["gw"]; - JsonArray nw_ins_0_sn = nw_ins_0["sn"]; - - for (byte i = 0; i < 4; i++) { - CJSON(staticIP[i], nw_ins_0_ip[i]); - CJSON(staticGateway[i], nw_ins_0_gw[i]); - CJSON(staticSubnet[i], nw_ins_0_sn[i]); + JsonArray dns = nw[F("dns")]; + if (!dns.isNull()) { + for (size_t i = 0; i < 4; i++) { + CJSON(dnsAddress[i], dns[i]); + } } JsonObject ap = doc["ap"]; @@ -162,7 +180,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; bool reversed = elm["rev"]; bool refresh = elm["ref"] | false; - uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully) + uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY; uint8_t maPerLed = elm[F("ledma")] | 55; uint16_t maMax = elm[F("maxpwr")] | (ablMilliampsMax * length) / total; // rough (incorrect?) per strip ABL calculation when no config exists @@ -212,7 +230,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject btn_obj = hw["btn"]; bool pull = btn_obj[F("pull")] | (!disablePullUp); // if true, pullup is enabled disablePullUp = !pull; - JsonArray hw_btn_ins = btn_obj[F("ins")]; + JsonArray hw_btn_ins = btn_obj["ins"]; if (!hw_btn_ins.isNull()) { for (uint8_t b = 0; b < WLED_MAX_BUTTONS; b++) { // deallocate existing button pins pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button @@ -268,13 +286,22 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // new install/missing configuration (button 0 has defaults) if (fromFS) { // relies upon only being called once with fromFS == true, which is currently true. - uint8_t s = 0; - if (pinManager.allocatePin(btnPin[0], false, PinOwner::Button)) { // initialized to #define value BTNPIN, or zero if not defined(!) - ++s; // do not clear default button if allocated successfully - } - for (; s= 0) { + if (disablePullUp) { + pinMode(btnPin[s], INPUT); + } else { + #ifdef ESP32 + pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP); + #else + pinMode(btnPin[s], INPUT_PULLUP); + #endif + } + } macroButton[s] = 0; macroLongPress[s] = 0; macroDoublePress[s] = 0; @@ -284,6 +311,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(touchThreshold,btn_obj[F("tt")]); CJSON(buttonPublishMqtt,btn_obj["mqtt"]); + #ifndef WLED_DISABLE_INFRARED int hw_ir_pin = hw["ir"]["pin"] | -2; // 4 if (hw_ir_pin > -2) { pinManager.deallocatePin(irPin, PinOwner::IR); @@ -294,6 +322,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } } CJSON(irEnabled, hw["ir"]["type"]); + #endif CJSON(irApplyToAllSelected, hw["ir"]["sel"]); JsonObject relay = hw[F("relay")]; @@ -321,7 +350,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } }; if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { #ifdef ESP32 - if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initilised (Wire.begin() called prior) + if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initialised (Wire.begin() called prior) else Wire.begin(); #else Wire.begin(i2c_sda, i2c_scl); @@ -377,6 +406,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { strip.setTransition(fadeTransition ? transitionDelayDefault : 0); CJSON(strip.paletteFade, light_tr["pal"]); CJSON(randomPaletteChangeTime, light_tr[F("rpc")]); + CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]); JsonObject light_nl = light["nl"]; CJSON(nightlightMode, light_nl["mode"]); @@ -402,7 +432,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(useESPNowSync, if_sync[F("espnow")]); #endif - JsonObject if_sync_recv = if_sync["recv"]; + JsonObject if_sync_recv = if_sync[F("recv")]; CJSON(receiveNotificationBrightness, if_sync_recv["bri"]); CJSON(receiveNotificationColor, if_sync_recv["col"]); CJSON(receiveNotificationEffects, if_sync_recv["fx"]); @@ -410,7 +440,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(receiveSegmentOptions, if_sync_recv["seg"]); CJSON(receiveSegmentBounds, if_sync_recv["sb"]); - JsonObject if_sync_send = if_sync["send"]; + JsonObject if_sync_send = if_sync[F("send")]; CJSON(sendNotifications, if_sync_send["en"]); sendNotificationsRT = sendNotifications; CJSON(notifyDirect, if_sync_send[F("dir")]); @@ -433,7 +463,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (e131Port == DDP_DEFAULT_PORT) e131Port = E131_DEFAULT_PORT; // prevent double DDP port allocation CJSON(e131Multicast, if_live[F("mc")]); - JsonObject if_live_dmx = if_live[F("dmx")]; + JsonObject if_live_dmx = if_live["dmx"]; CJSON(e131Universe, if_live_dmx[F("uni")]); CJSON(e131SkipOutOfSequence, if_live_dmx[F("seqskip")]); CJSON(DMXAddress, if_live_dmx[F("addr")]); @@ -507,6 +537,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(analogClock12pixel, ol[F("o12pix")]); CJSON(analogClock5MinuteMarks, ol[F("o5m")]); CJSON(analogClockSecondsTrail, ol[F("osec")]); + CJSON(analogClockSolidBlack, ol[F("osb")]); //timed macro rules JsonObject tm = doc[F("timers")]; @@ -596,6 +627,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { return (doc["sv"] | true); } + +static const char s_cfg_json[] PROGMEM = "/cfg.json"; + void deserializeConfigFromFS() { bool success = deserializeConfigSec(); if (!success) { //if file does not exist, try reading from EEPROM @@ -609,7 +643,7 @@ void deserializeConfigFromFS() { DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); - success = readObjectFromFile("/cfg.json", nullptr, pDoc); + success = readObjectFromFile(s_cfg_json, nullptr, pDoc); if (!success) { // if file does not exist, optionally try reading from EEPROM and then save defaults to FS releaseJSONBufferLock(); #ifdef WLED_ADD_EEPROM_SUPPORT @@ -665,19 +699,23 @@ void serializeConfig() { #endif JsonArray nw_ins = nw.createNestedArray("ins"); + for (size_t n = 0; n < multiWiFi.size(); n++) { + JsonObject wifi = nw_ins.createNestedObject(); + wifi[F("ssid")] = multiWiFi[n].clientSSID; + wifi[F("pskl")] = strlen(multiWiFi[n].clientPass); + JsonArray wifi_ip = wifi.createNestedArray("ip"); + JsonArray wifi_gw = wifi.createNestedArray("gw"); + JsonArray wifi_sn = wifi.createNestedArray("sn"); + for (size_t i = 0; i < 4; i++) { + wifi_ip.add(multiWiFi[n].staticIP[i]); + wifi_gw.add(multiWiFi[n].staticGW[i]); + wifi_sn.add(multiWiFi[n].staticSN[i]); + } + } - JsonObject nw_ins_0 = nw_ins.createNestedObject(); - nw_ins_0[F("ssid")] = clientSSID; - nw_ins_0[F("pskl")] = strlen(clientPass); - - JsonArray nw_ins_0_ip = nw_ins_0.createNestedArray("ip"); - JsonArray nw_ins_0_gw = nw_ins_0.createNestedArray("gw"); - JsonArray nw_ins_0_sn = nw_ins_0.createNestedArray("sn"); - - for (byte i = 0; i < 4; i++) { - nw_ins_0_ip.add(staticIP[i]); - nw_ins_0_gw.add(staticGateway[i]); - nw_ins_0_sn.add(staticSubnet[i]); + JsonArray dns = nw.createNestedArray(F("dns")); + for (size_t i = 0; i < 4; i++) { + dns.add(dnsAddress[i]); } JsonObject ap = root.createNestedObject("ap"); @@ -693,7 +731,7 @@ void serializeConfig() { ap_ip.add(2); ap_ip.add(1); - JsonObject wifi = root.createNestedObject("wifi"); + JsonObject wifi = root.createNestedObject(F("wifi")); wifi[F("sleep")] = !noWifiSleep; wifi[F("phy")] = force802_3g; @@ -721,7 +759,7 @@ void serializeConfig() { } #endif - JsonObject hw = root.createNestedObject("hw"); + JsonObject hw = root.createNestedObject(F("hw")); JsonObject hw_led = hw.createNestedObject("led"); hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL @@ -811,8 +849,10 @@ void serializeConfig() { hw_btn["mqtt"] = buttonPublishMqtt; JsonObject hw_ir = hw.createNestedObject("ir"); + #ifndef WLED_DISABLE_INFRARED hw_ir["pin"] = irPin; hw_ir["type"] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled ) + #endif hw_ir["sel"] = irApplyToAllSelected; JsonObject hw_relay = hw.createNestedObject(F("relay")); @@ -849,6 +889,7 @@ void serializeConfig() { light_tr["dur"] = transitionDelayDefault / 100; light_tr["pal"] = strip.paletteFade; light_tr[F("rpc")] = randomPaletteChangeTime; + light_tr[F("hrp")] = useHarmonicRandomPalette; JsonObject light_nl = light.createNestedObject("nl"); light_nl["mode"] = nightlightMode; @@ -871,7 +912,7 @@ void serializeConfig() { if_sync[F("espnow")] = useESPNowSync; #endif - JsonObject if_sync_recv = if_sync.createNestedObject("recv"); + JsonObject if_sync_recv = if_sync.createNestedObject(F("recv")); if_sync_recv["bri"] = receiveNotificationBrightness; if_sync_recv["col"] = receiveNotificationColor; if_sync_recv["fx"] = receiveNotificationEffects; @@ -879,7 +920,7 @@ void serializeConfig() { if_sync_recv["seg"] = receiveSegmentOptions; if_sync_recv["sb"] = receiveSegmentBounds; - JsonObject if_sync_send = if_sync.createNestedObject("send"); + JsonObject if_sync_send = if_sync.createNestedObject(F("send")); if_sync_send["en"] = sendNotifications; if_sync_send[F("dir")] = notifyDirect; if_sync_send["btn"] = notifyButton; @@ -944,7 +985,7 @@ void serializeConfig() { if_hue["id"] = huePollLightId; if_hue[F("iv")] = huePollIntervalMs / 100; - JsonObject if_hue_recv = if_hue.createNestedObject("recv"); + JsonObject if_hue_recv = if_hue.createNestedObject(F("recv")); if_hue_recv["on"] = hueApplyOnOff; if_hue_recv["bri"] = hueApplyBri; if_hue_recv["col"] = hueApplyColor; @@ -973,6 +1014,7 @@ void serializeConfig() { ol[F("o12pix")] = analogClock12pixel; ol[F("o5m")] = analogClock5MinuteMarks; ol[F("osec")] = analogClockSecondsTrail; + ol[F("osb")] = analogClockSolidBlack; JsonObject timers = root.createNestedObject(F("timers")); @@ -1026,7 +1068,7 @@ void serializeConfig() { JsonObject usermods_settings = root.createNestedObject("um"); usermods.addToConfig(usermods_settings); - File f = WLED_FS.open("/cfg.json", "w"); + File f = WLED_FS.open(FPSTR(s_cfg_json), "w"); if (f) serializeJson(root, f); f.close(); releaseJSONBufferLock(); @@ -1034,13 +1076,16 @@ void serializeConfig() { doSerializeConfig = false; } + +static const char s_wsec_json[] PROGMEM = "/wsec.json"; + //settings in /wsec.json, not accessible via webserver, for passwords and tokens bool deserializeConfigSec() { DEBUG_PRINTLN(F("Reading settings from /wsec.json...")); if (!requestJSONBufferLock(3)) return false; - bool success = readObjectFromFile("/wsec.json", nullptr, pDoc); + bool success = readObjectFromFile(s_wsec_json, nullptr, pDoc); if (!success) { releaseJSONBufferLock(); return false; @@ -1048,8 +1093,17 @@ bool deserializeConfigSec() { JsonObject root = pDoc->as(); - JsonObject nw_ins_0 = root["nw"]["ins"][0]; - getStringFromJson(clientPass, nw_ins_0["psk"], 65); + size_t n = 0; + JsonArray nw_ins = root["nw"]["ins"]; + if (!nw_ins.isNull()) { + if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing + for (JsonObject wifi : nw_ins) { + char pw[65] = ""; + getStringFromJson(pw, wifi["psk"], 65); + strlcpy(multiWiFi[n].clientPass, pw, 65); + if (++n >= WLED_MAX_WIFI_COUNT) break; + } + } JsonObject ap = root["ap"]; getStringFromJson(apPass, ap["psk"] , 65); @@ -1088,9 +1142,10 @@ void serializeConfigSec() { JsonObject nw = root.createNestedObject("nw"); JsonArray nw_ins = nw.createNestedArray("ins"); - - JsonObject nw_ins_0 = nw_ins.createNestedObject(); - nw_ins_0["psk"] = clientPass; + for (size_t i = 0; i < multiWiFi.size(); i++) { + JsonObject wifi = nw_ins.createNestedObject(); + wifi[F("psk")] = multiWiFi[i].clientPass; + } JsonObject ap = root.createNestedObject("ap"); ap["psk"] = apPass; @@ -1113,7 +1168,7 @@ void serializeConfigSec() { ota[F("lock-wifi")] = wifiLock; ota[F("aota")] = aOtaEnabled; - File f = WLED_FS.open("/wsec.json", "w"); + File f = WLED_FS.open(FPSTR(s_wsec_json), "w"); if (f) serializeJson(root, f); f.close(); releaseJSONBufferLock(); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 21c27d651..3ed54d959 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -91,6 +91,115 @@ void setRandomColor(byte* rgb) colorHStoRGB(lastRandomIndex*256,255,rgb); } +/* + * generates a random palette based on harmonic color theory + * takes a base palette as the input, it will choose one color of the base palette and keep it + */ +CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) +{ + CHSV palettecolors[4]; //array of colors for the new palette + uint8_t keepcolorposition = random8(4); //color position of current random palette to keep + palettecolors[keepcolorposition] = rgb2hsv_approximate(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette + palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color + //generate 4 saturation and brightness value numbers + //only one saturation is allowed to be below 200 creating mostly vibrant colors + //only one brightness value number is allowed below 200, creating mostly bright palettes + + for (int i = 0; i < 3; i++) { //generate three high values + palettecolors[i].saturation = random8(200,255); + palettecolors[i].value = random8(220,255); + } + //allow one to be lower + palettecolors[3].saturation = random8(20,255); + palettecolors[3].value = random8(80,255); + + //shuffle the arrays + for (int i = 3; i > 0; i--) { + std::swap(palettecolors[i].saturation, palettecolors[random8(i + 1)].saturation); + std::swap(palettecolors[i].value, palettecolors[random8(i + 1)].value); + } + + //now generate three new hues based off of the hue of the chosen current color + uint8_t basehue = palettecolors[keepcolorposition].hue; + uint8_t harmonics[3]; //hues that are harmonic but still a little random + uint8_t type = random8(5); //choose a harmony type + + switch (type) { + case 0: // analogous + harmonics[0] = basehue + random8(30, 50); + harmonics[1] = basehue + random8(10, 30); + harmonics[2] = basehue - random8(10, 30); + break; + + case 1: // triadic + harmonics[0] = basehue + 113 + random8(15); + harmonics[1] = basehue + 233 + random8(15); + harmonics[2] = basehue -7 + random8(15); + break; + + case 2: // split-complementary + harmonics[0] = basehue + 145 + random8(10); + harmonics[1] = basehue + 205 + random8(10); + harmonics[2] = basehue - 5 + random8(10); + break; + + case 3: // square + harmonics[0] = basehue + 85 + random8(10); + harmonics[1] = basehue + 175 + random8(10); + harmonics[2] = basehue + 265 + random8(10); + break; + + case 4: // tetradic + harmonics[0] = basehue + 80 + random8(20); + harmonics[1] = basehue + 170 + random8(20); + harmonics[2] = basehue + random8(30)-15; + break; + } + + if (random8() < 128) { + //50:50 chance of shuffling hues or keep the color order + for (int i = 2; i > 0; i--) { + std::swap(harmonics[i], harmonics[random8(i + 1)]); + } + } + + //now set the hues + int j = 0; + for (int i = 0; i < 4; i++) { + if (i==keepcolorposition) continue; //skip the base color + palettecolors[i].hue = harmonics[j]; + j++; + } + + bool makepastelpalette = false; + if (random8() < 25) { //~10% chance of desaturated 'pastel' colors + makepastelpalette = true; + } + + //apply saturation & gamma correction + CRGB RGBpalettecolors[4]; + for (int i = 0; i < 4; i++) { + if (makepastelpalette && palettecolors[i].saturation > 180) { + palettecolors[i].saturation -= 160; //desaturate all four colors + } + RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB + RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB + } + + return CRGBPalette16(RGBpalettecolors[0], + RGBpalettecolors[1], + RGBpalettecolors[2], + RGBpalettecolors[3]); +} + +CRGBPalette16 generateRandomPalette(void) //generate fully random palette +{ + return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)), + CHSV(random8(), random8(160, 255), random8(128, 255)), + CHSV(random8(), random8(160, 255), random8(128, 255)), + CHSV(random8(), random8(160, 255), random8(128, 255))); +} + void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb { float h = ((float)hue)/65535.0f; diff --git a/wled00/const.h b/wled00/const.h index 4ef81b907..dd965bc40 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -7,14 +7,35 @@ #define GRADIENT_PALETTE_COUNT 58 +// You can define custom product info from build flags. +// This is useful to allow API consumer to identify what type of WLED version +// they are interacting with. Be aware that changing this might cause some third +// party API consumers to consider this as a non-WLED device since the values +// returned by the API and by MQTT will no longer be default. However, most +// third party only uses mDNS to validate, so this is generally fine to change. +// For example, Home Assistant will still work fine even with this value changed. +// Use like this: +// -D WLED_BRAND="\"Custom Brand\"" +// -D WLED_PRODUCT_NAME="\"Custom Product\"" +#ifndef WLED_BRAND + #define WLED_BRAND "WLED" +#endif +#ifndef WLED_PRODUCT_NAME + #define WLED_PRODUCT_NAME "FOSS" +#endif + //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" -#define DEFAULT_AP_SSID "WLED-AP" +#define DEFAULT_AP_SSID WLED_BRAND "-AP" #define DEFAULT_AP_PASS "wled1234" #define DEFAULT_OTA_PASS "wledota" #define DEFAULT_MDNS_NAME "x" //increase if you need more +#ifndef WLED_MAX_WIFI_COUNT + #define WLED_MAX_WIFI_COUNT 3 +#endif + #ifndef WLED_MAX_USERMODS #ifdef ESP8266 #define WLED_MAX_USERMODS 4 @@ -72,6 +93,11 @@ #else #define WLED_MAX_BUTTONS 4 #endif +#else + #if WLED_MAX_BUTTONS < 2 + #undef WLED_MAX_BUTTONS + #define WLED_MAX_BUTTONS 2 + #endif #endif #ifdef ESP8266 @@ -150,13 +176,20 @@ #define USERMOD_ID_KLIPPER 40 //Usermod Klipper percentage #define USERMOD_ID_WIREGUARD 41 //Usermod "wireguard.h" #define USERMOD_ID_INTERNAL_TEMPERATURE 42 //Usermod "usermod_internal_temperature.h" -#define USERMOD_ID_ANIMARTRIX 44 //Usermod "usermod_v2_animartrix.h" +#define USERMOD_ID_LDR_DUSK_DAWN 43 //Usermod "usermod_LDR_Dusk_Dawn_v2.h" +#define USERMOD_ID_STAIRWAY_WIPE 44 //Usermod "stairway-wipe-usermod-v2.h" +#define USERMOD_ID_ANIMARTRIX 45 //Usermod "usermod_v2_animartrix.h" +#define USERMOD_ID_HTTP_PULL_LIGHT_CONTROL 46 //usermod "usermod_v2_HttpPullLightControl.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot #define AP_BEHAVIOR_NO_CONN 1 //Open when no connection (either after boot or if connection is lost) #define AP_BEHAVIOR_ALWAYS 2 //Always open #define AP_BEHAVIOR_BUTTON_ONLY 3 //Only when button pressed for 6 sec +#define AP_BEHAVIOR_TEMPORARY 4 //Open AP when no connection after boot but only temporary +#ifndef WLED_AP_TIMEOUT + #define WLED_AP_TIMEOUT 300000 //Temporary AP timeout +#endif //Notifier callMode #define CALL_MODE_INIT 0 //no updates on init, can be used to disable updates @@ -207,8 +240,8 @@ #define DMX_MODE_MULTIPLE_RGB 4 //every LED is addressed with its own RGB (ledCount * 3 channels) #define DMX_MODE_MULTIPLE_DRGB 5 //every LED is addressed with its own RGB and share a master dimmer (ledCount * 3 + 1 channels) #define DMX_MODE_MULTIPLE_RGBW 6 //every LED is addressed with its own RGBW (ledCount * 4 channels) -#define DMX_MODE_EFFECT_SEGMENT 8 //trigger standalone effects of WLED (15 channels per segement) -#define DMX_MODE_EFFECT_SEGMENT_W 9 //trigger standalone effects of WLED (18 channels per segement) +#define DMX_MODE_EFFECT_SEGMENT 8 //trigger standalone effects of WLED (15 channels per segment) +#define DMX_MODE_EFFECT_SEGMENT_W 9 //trigger standalone effects of WLED (18 channels per segment) #define DMX_MODE_PRESET 10 //apply presets (1 channel) //Light capability byte (unused) 0bRCCCTTTT @@ -235,6 +268,7 @@ #define TYPE_WS2811_400KHZ 24 //half-speed WS2812 protocol, used by very old WS2811 units #define TYPE_TM1829 25 #define TYPE_UCS8903 26 +#define TYPE_APA106 27 #define TYPE_UCS8904 29 //first RGBW digital type (hardcoded in busmanager.cpp, memUsage()) #define TYPE_SK6812_RGBW 30 #define TYPE_TM1814 31 @@ -256,6 +290,7 @@ #define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus, unused) #define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus, unused) #define TYPE_NET_DDP_RGBW 88 //network DDP RGBW bus (master broadcast bus) +#define TYPE_NET_ARTNET_RGBW 89 //network ArtNet RGB bus (master broadcast bus, unused) #define IS_TYPE_VALID(t) ((t) > 15 && (t) < 128) #define IS_DIGITAL(t) (((t) > 15 && (t) < 40) || ((t) > 47 && (t) < 64)) //digital are 16-39 and 48-63 @@ -292,19 +327,20 @@ #define BTN_TYPE_TOUCH_SWITCH 9 //Ethernet board types -#define WLED_NUM_ETH_TYPES 11 +#define WLED_NUM_ETH_TYPES 12 -#define WLED_ETH_NONE 0 -#define WLED_ETH_WT32_ETH01 1 -#define WLED_ETH_ESP32_POE 2 -#define WLED_ETH_WESP32 3 -#define WLED_ETH_QUINLED 4 -#define WLED_ETH_TWILIGHTLORD 5 -#define WLED_ETH_ESP32DEUX 6 -#define WLED_ETH_ESP32ETHKITVE 7 -#define WLED_ETH_QUINLED_OCTA 8 -#define WLED_ETH_ABCWLEDV43ETH 9 -#define WLED_ETH_SERG74 10 +#define WLED_ETH_NONE 0 +#define WLED_ETH_WT32_ETH01 1 +#define WLED_ETH_ESP32_POE 2 +#define WLED_ETH_WESP32 3 +#define WLED_ETH_QUINLED 4 +#define WLED_ETH_TWILIGHTLORD 5 +#define WLED_ETH_ESP32DEUX 6 +#define WLED_ETH_ESP32ETHKITVE 7 +#define WLED_ETH_QUINLED_OCTA 8 +#define WLED_ETH_ABCWLEDV43ETH 9 +#define WLED_ETH_SERG74 10 +#define WLED_ETH_ESP32_POE_WROVER 11 //Hue error codes #define HUE_ERROR_INACTIVE 0 @@ -331,7 +367,7 @@ #define SEG_DIFFERS_OPT 0x02 // all segment options except: selected, reset & transitional #define SEG_DIFFERS_COL 0x04 // colors #define SEG_DIFFERS_FX 0x08 // effect/mode parameters -#define SEG_DIFFERS_BOUNDS 0x10 // segment start/stop ounds +#define SEG_DIFFERS_BOUNDS 0x10 // segment start/stop bounds #define SEG_DIFFERS_GSO 0x20 // grouping, spacing & offset #define SEG_DIFFERS_SEL 0x80 // selected @@ -356,7 +392,7 @@ #define ERR_FS_PLOAD 12 // It was attempted to load a preset that does not exist #define ERR_FS_IRLOAD 13 // It was attempted to load an IR JSON cmd, but the "ir.json" file does not exist #define ERR_FS_RMLOAD 14 // It was attempted to load an remote JSON cmd, but the "remote.json" file does not exist -#define ERR_FS_GENERAL 19 // A general unspecified filesystem error occured +#define ERR_FS_GENERAL 19 // A general unspecified filesystem error occurred #define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented) #define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented) #define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented) @@ -385,7 +421,8 @@ #define SUBPAGE_JS 254 #define SUBPAGE_WELCOME 255 -#define NTP_PACKET_SIZE 48 +#define NTP_PACKET_SIZE 48 // size of NTP receive buffer +#define NTP_MIN_PACKET_SIZE 48 // min expected size - NTP v4 allows for "extended information" appended to the standard fields //maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses #ifndef MAX_LEDS @@ -416,7 +453,7 @@ #ifdef ESP8266 #define SETTINGS_STACK_BUF_SIZE 2048 #else -#define SETTINGS_STACK_BUF_SIZE 3608 // warning: quite a large value for stack +#define SETTINGS_STACK_BUF_SIZE 3840 // warning: quite a large value for stack (640 * WLED_MAX_USERMODS) #endif #ifdef WLED_USE_ETHERNET @@ -473,8 +510,8 @@ //this is merely a default now and can be changed at runtime #ifndef LEDPIN -#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)) || defined(CONFIG_IDF_TARGET_ESP32C3) - #define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards +#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ARDUINO_ESP32_PICO) + #define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, and on boards where GPIO16 is not available #else #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards #endif @@ -498,7 +535,7 @@ #define PIN_TIMEOUT 900000 // time in ms after which the PIN will be required again, 15 minutes // HW_PIN_SCL & HW_PIN_SDA are used for information in usermods settings page and usermods themselves -// which GPIO pins are actually used in a hardwarea layout (controller board) +// which GPIO pins are actually used in a hardware layout (controller board) #if defined(I2CSCLPIN) && !defined(HW_PIN_SCL) #define HW_PIN_SCL I2CSCLPIN #endif @@ -521,7 +558,7 @@ #endif // HW_PIN_SCLKSPI & HW_PIN_MOSISPI & HW_PIN_MISOSPI are used for information in usermods settings page and usermods themselves -// which GPIO pins are actually used in a hardwarea layout (controller board) +// which GPIO pins are actually used in a hardware layout (controller board) #if defined(SPISCLKPIN) && !defined(HW_PIN_CLOCKSPI) #define HW_PIN_CLOCKSPI SPISCLKPIN #endif diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index 852641ec3..a4b913592 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -506,7 +506,7 @@ console.log('Error: ', e); console.log(' Status: ', this.status); //Show some error notification for some time setTimeout(()=>{ - //Remove it when time has pased + //Remove it when time has passed }, 1000); }); req.open("POST", "/upload"); @@ -554,7 +554,7 @@ paletteArray.push({"palette":[0,70,70,70,255,70,70,70]}); } - //Get static palettes from localStorage and do some magic to reformat them into the same format as the pallete JSONs + //Get static palettes from localStorage and do some magic to reformat them into the same format as the palette JSONs //This code excludes any objects with "non valid integer colors", i.e. r, c1, c2, c3 and such //This code also fixes potentially broken palettes which doesn't end on 255 //The code finally also removes any representations of the custom palettes, since we read them from file diff --git a/wled00/data/index.css b/wled00/data/index.css index 095de572a..37eb6a596 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -346,10 +346,14 @@ button { -webkit-overflow-scrolling: touch; } +#Segments, #Presets, #Effects, #Colors { + font-size: 19px; + padding: 4px 0 0; +} + #segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw, .fnd { max-width: 280px; - font-size: 19px; } #putil, #segutil, #segutil2 { @@ -361,7 +365,7 @@ button { padding-top: 12px; } -#fx, #pql, #segcont, #pcont, #sliders, #picker, #qcs-w, #hexw, #pall, #ledmap, +#fx, #pql, #segcont, #pcont, #sliders, #qcs-w, #hexw, #pall, #ledmap, .slider, .filter, .option, .segname, .pname, .fnd { margin: 0 auto; } @@ -371,15 +375,10 @@ button { } /* Quick load magin for simplified UI */ -.simplified #pql { +.simplified #pql, .simplified #palw, .simplified #fx { margin-bottom: 8px; } -/* Button margin for simplified UI */ -.simplified #fx .btn, .simplified #palw .btn { - margin-top: 0; -} - .smooth { transition: transform calc(var(--f, 1)*.5s) ease-out } .tab-label { @@ -624,12 +623,10 @@ button { padding-bottom: 8px; } -#info .btn { +.infobtn { margin: 5px; } -#info table .btn, #nodes table .btn { - margin: 0; -} + #info div, #nodes div { max-width: 490px; margin: 0 auto; @@ -784,14 +781,14 @@ input[type=range]::-moz-range-thumb { } #picker { - margin-top: 8px !important; + margin: 4px auto 0 !important; max-width: max-content; } /* buttons */ .btn { padding: 8px; - margin: 10px 4px; + /*margin: 10px 4px;*/ width: 230px; font-size: 19px; color: var(--c-d); @@ -837,14 +834,14 @@ input[type=range]::-moz-range-thumb { text-overflow: clip; } .btn-xs { - margin: 2px 0 0 0; -} -#putil .btn-xs { margin: 0; } #info .btn-xs { border: 1px solid var(--c-4); } +#btns .btn-xs { + margin: 0 4px; +} #putil .btn-s { width: 135px; @@ -863,6 +860,15 @@ input[type=range]::-moz-range-thumb { margin: 0; white-space: nowrap; } +a.btn { + display: block; + white-space: nowrap; + text-align: center; + padding: 9px 32px 7px 24px; + position: relative; + box-sizing: border-box; + line-height: 24px; +} /* Quick color select wrapper div */ #qcs-w { @@ -913,9 +919,6 @@ select { #tt { text-align: center; } -.cl { - background-color: #000; -} select.sel-p, select.sel-pl, select.sel-ple { margin: 5px 0; width: 100%; @@ -1018,7 +1021,7 @@ textarea { width: 50px !important; } -.segname, .pname, .bname { +.segname, .pname { white-space: nowrap; text-align: center; overflow: hidden; @@ -1028,9 +1031,6 @@ textarea { max-width: 170px; position: relative; } -.bname { - padding: 0 24px; -} .segname .flr, .pname .flr { transform: rotate(0deg); @@ -1065,27 +1065,24 @@ textarea { .newseg { cursor: default; } - +/* .ic { padding: 6px 0 0 0; } - -.xxs { +*/ +/* color selector */ +#csl button { width: 44px; height: 44px; margin: 5px; + border: 2px solid var(--c-d) !important; + background-color: #000; } - -.xxs-w { +/* selected color selector */ +#csl .sl { margin: 2px; width: 50px; height: 50px; -} - -#csl .xxs { - border: 2px solid var(--c-d) !important; -} -#csl .xxs-w { border-width: 5px !important; } @@ -1290,15 +1287,11 @@ TD .checkmark, TD .radiomark { position: -webkit-sticky; position: sticky; border-radius: 21px; - margin: 13px auto 0; + margin: 0 auto 12px; min-height: 40px; border: 1px solid var(--c-2); } -#segutil .lstI { - margin-top: 0; -} - /* Simplify segments */ .simplified #segcont .lstI { margin-top: 4px; @@ -1397,7 +1390,7 @@ dialog { width: 100%; box-sizing: border-box; padding: 8px 40px 8px 44px; - margin: 5px auto 0; + margin: 4px auto 12px; text-align: left; border-radius: 21px; background: var(--c-2); @@ -1415,6 +1408,13 @@ dialog { background-color: var(--c-3); } +#fxFind.fnd input[type="text"] { + margin-bottom: 0; +} +#fxFind { + margin-bottom: 12px; +} + /* segment & preset inner/expanded content */ .segin, .presin { @@ -1520,7 +1520,7 @@ dialog { #info .infobtn, #nodes .infobtn { width: 145px; } - #info div, #nodes div { + #info div, #nodes div, #nodes a.btn { max-width: 320px; } } diff --git a/wled00/data/index.htm b/wled00/data/index.htm index ae685bdb9..a58c76da6 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -46,91 +46,91 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-

-
-
-
-
-
R
+
+
+
+
+
+

+
+
+
+
+
R
- - - + + +

- +
- - - + + +
-

Color palette

+

Color palette

@@ -159,27 +159,27 @@
-
- -
+ +
- -
+ +
@@ -216,7 +216,7 @@
-
+
@@ -224,7 +224,7 @@
-
+
@@ -232,22 +232,22 @@
-
+
-
+

Segments

Loading...
diff --git a/wled00/data/index.js b/wled00/data/index.js index ec8fcb23a..7889e3b34 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -88,7 +88,6 @@ function setCSL(cs) function applyCfg() { cTheme(cfg.theme.base === "light"); - gId("Colors").style.paddingTop = cfg.comp.colors.picker ? "0" : "28px"; var bg = cfg.theme.color.bg; if (bg) sCol('--c-1', bg); var l = cfg.comp.labels; @@ -278,25 +277,17 @@ function onLoad() pmtLS = localStorage.getItem('wledPmt'); // Load initial data - // Once we figure out why ESP8266 sometimes corrupts JSON responses if they are made in quick succession - // we can remove all setTimeout() throttling loadPalettes(()=>{ - setTimeout(()=>{ // ESP8266 can't handle quick requests - // fill effect extra data array - loadFXData(()=>{ - setTimeout(()=>{ // ESP8266 can't handle quick requests - // load and populate effects - loadFX(()=>{ - setTimeout(()=>{ // ESP8266 can't handle quick requests - loadPalettesData(()=>{ - requestJson();// will load presets and create WS - if (cfg.comp.css) setTimeout(()=>{loadSkinCSS('skinCss')},100); - }); - },50); - }); - },50); + // fill effect extra data array + loadFXData(()=>{ + // load and populate effects + loadFX(()=>{ + loadPalettesData(()=>{ + requestJson();// will load presets and create WS + if (cfg.comp.css) setTimeout(()=>{loadSkinCSS('skinCss')},50); + }); }); - },50); + }); }); resetUtil(); @@ -809,13 +800,13 @@ function populateSegments(s) ``+ ``+ `
`+ - `&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};`+ + `&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};`+ (inst.n ? inst.n : "Segment "+i) + `
`+ - `ɸ${String.fromCharCode(inst.set+"A".charCodeAt(0))};`+ + `ɸ${String.fromCharCode(inst.set+"A".charCodeAt(0))};`+ `
`+ `
`+ - ``+ + ``+ `
`+ ``+ (cfg.comp.segpwr ? segp : '') + @@ -846,7 +837,7 @@ function populateSegments(s) ``+ ``+ ``+ - ``+ + ``+ ``+ ``+ `
`+ @@ -878,10 +869,11 @@ function populateSegments(s) updateLen(i); updateTrail(gId(`seg${i}bri`)); gId(`segr${i}`).classList.add("hide"); + //if (i`; - var url = ``; + var url = `${bname(o)}${o.vid<2307130?'':onoff}`; urows += inforow(url,`${btype(o.type&0x7F)}
${o.vid==0?"N/A":o.vid}`); nnodes++; } @@ -1448,7 +1441,7 @@ function readState(s,command=false) if (s.seg[i].sel) { if (sellvl < 2) selc = i; // get first selected segment sellvl = 2; - var lc = lastinfo.leds.seglc[s.seg[i].id]; + var lc = lastinfo.leds.seglc[i]; hasRGB |= !!(lc & 0x01); hasWhite |= !!(lc & 0x02); hasCCT |= !!(lc & 0x04); @@ -1458,7 +1451,7 @@ function readState(s,command=false) } var i=s.seg[selc]; if (sellvl == 1) { - var lc = lastinfo.leds.seglc[i.id]; + var lc = lastinfo.leds.seglc[selc]; hasRGB = !!(lc & 0x01); hasWhite = !!(lc & 0x02); hasCCT = !!(lc & 0x04); @@ -1534,9 +1527,9 @@ function readState(s,command=false) // - For AC effects (id<128) 2 sliders and 3 colors and the palette will be shown // - For SR effects (id>128) 5 sliders and 3 colors and the palette will be shown // If effective (@) -// - a ; seperates slider controls (left) from color controls (middle) and palette control (right) +// - a ; separates slider controls (left) from color controls (middle) and palette control (right) // - if left, middle or right is empty no controls are shown -// - a , seperates slider controls (max 5) or color controls (max 3). Palette has only one value +// - a , separates slider controls (max 5) or color controls (max 3). Palette has only one value // - a ! means that the default is used. // - For sliders: Effect speeds, Effect intensity, Custom 1, Custom 2, Custom 3 // - For colors: Fx color, Background color, Custom @@ -1560,12 +1553,12 @@ function setEffectParameters(idx) // set html slider items on/off let sliders = d.querySelectorAll("#sliders .sliderwrap"); sliders.forEach((slider, i)=>{ - let text = slider.getAttribute("tooltip"); + let text = slider.getAttribute("title"); if ((!controlDefined && i<((idx<128)?2:nSliders)) || (slOnOff.length>i && slOnOff[i]!="")) { if (slOnOff.length>i && slOnOff[i]!="!") text = slOnOff[i]; // restore overwritten default tooltips if (i<2 && slOnOff[i]==="!") text = i==0 ? "Effect speed" : "Effect intensity"; - slider.setAttribute("tooltip", text); + slider.setAttribute("title", text); slider.parentElement.classList.remove('hide'); } else slider.parentElement.classList.add('hide'); @@ -1575,10 +1568,10 @@ function setEffectParameters(idx) gId('fxopt').classList.remove('fade'); let checks = d.querySelectorAll("#sliders .ochkl"); checks.forEach((check, i)=>{ - let text = check.getAttribute("tooltip"); + let text = check.getAttribute("title"); if (5+i5+i && slOnOff[5+i]!="!") text = slOnOff[5+i]; - check.setAttribute("tooltip", text); + check.setAttribute("title", text); check.classList.remove('hide'); } else check.classList.add('hide'); @@ -1827,17 +1820,16 @@ function toggleNodes() function makeSeg() { - var ns = 0, ct = 0; + var ns = 0, ct = isM ? mw : ledCount; var lu = lowestUnused; let li = lastinfo; if (lu > 0) { let xend = parseInt(gId(`seg${lu -1}e`).value,10) + (cfg.comp.seglen?parseInt(gId(`seg${lu -1}s`).value,10):0); if (isM) { ns = 0; - ct = mw; } else { if (xend < ledCount) ns = xend; - ct = ledCount-(cfg.comp.seglen?ns:0) + ct -= cfg.comp.seglen?ns:0; } } gId('segutil').scrollIntoView({ @@ -1876,7 +1868,7 @@ function resetUtil(off=false) + '' + `
Add segment
` + '
' - + `` + + `` + '
' + '
'; } @@ -2161,7 +2153,7 @@ function selGrp(g) var sel = gId(`segcont`).querySelectorAll(`div[data-set="${g}"]`); var obj = {"seg":[]}; for (let i=0; i<=lSeg; i++) if (gId(`seg${i}`)) obj.seg.push({"id":i,"sel":false}); - if (sel) for (let s of sel||[]) { + for (let s of (sel||[])) { let i = parseInt(s.id.substring(3)); obj.seg[i] = {"id":i,"sel":true}; } @@ -2514,8 +2506,8 @@ function selectSlot(b) { csel = b; var cd = gId('csl').children; - for (let i of cd) i.classList.remove('xxs-w'); - cd[b].classList.add('xxs-w'); + for (let i of cd) i.classList.remove('sl'); + cd[b].classList.add('sl'); setPicker(rgbStr(cd[b].dataset)); // force slider update on initial load (picker "color:change" not fired if black) if (cpick.color.value == 0) updatePSliders(); @@ -2803,6 +2795,12 @@ function search(field, listId = null) { const search = field.value !== ''; + // restore default preset sorting if no search term is entered + if (listId === 'pcont' && !search) { + populatePresets(); + return; + } + // clear filter if searching in fxlist if (listId === 'fxlist' && search) { gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { e.checked = false; }); @@ -3071,14 +3069,19 @@ function mergeDeep(target, ...sources) return mergeDeep(target, ...sources); } -function tooltip() +function tooltip(cont=null) { - const elements = d.querySelectorAll("[tooltip]"); + const elements = d.querySelectorAll((cont?cont+" ":"")+"[title]"); elements.forEach((element)=>{ element.addEventListener("mouseover", ()=>{ + // save title + element.setAttribute("data-title", element.getAttribute("title")); const tooltip = d.createElement("span"); tooltip.className = "tooltip"; - tooltip.textContent = element.getAttribute("tooltip"); + tooltip.textContent = element.getAttribute("title"); + + // prevent default title popup + element.removeAttribute("title"); let { top, left, width } = element.getBoundingClientRect(); @@ -3101,6 +3104,8 @@ function tooltip() tooltip.classList.remove("visible"); d.body.removeChild(tooltip); }); + // restore title + element.setAttribute("title", element.getAttribute("data-title")); }); }); }; diff --git a/wled00/data/liveviewws2D.htm b/wled00/data/liveviewws2D.htm index 007ac2467..c50f40fbc 100644 --- a/wled00/data/liveviewws2D.htm +++ b/wled00/data/liveviewws2D.htm @@ -54,7 +54,7 @@ let mW = leds[2]; // matrix width let mH = leds[3]; // matrix height let pPL = Math.min(c.width / mW, c.height / mH); // pixels per LED (width of circle) - let lOf = Math.floor((c.width - pPL*mW)/2); //left offeset (to center matrix) + let lOf = Math.floor((c.width - pPL*mW)/2); //left offset (to center matrix) var i = 4; for (y=0.5;yBack - + diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 10605e0f9..611653a64 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -123,10 +123,10 @@ gId('abl').style.display = (en) ? 'inline':'none'; gId('psu2').style.display = (en) ? 'inline':'none'; if (!en) d.Sf.PPL.checked = false; - enPPL(); UI(); } - function enPPL() + // enable per port limiter and calculate current + function enPPL(sDI=0) { const abl = d.Sf.ABL.checked; const ppl = d.Sf.PPL.checked; @@ -139,9 +139,11 @@ d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,n)=>{ gId("PSU"+n).style.display = ppl ? "inline" : "none"; const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT + const c = parseInt(d.Sf["LC"+n].value); //get LED count i.min = ppl && !(isVir(t) || isAna(t)) ? 250 : 0; - if (!abl) i.value = 0; + if (!abl || isVir(t) || isAna(t)) i.value = 0; else if (ppl) sumMA += parseInt(i.value,10); + else if (sDI) i.value = Math.round(parseInt(d.Sf.MA.value,10)*c/sDI); }); if (ppl) d.Sf.MA.value = sumMA; // populate UI ABL value if PPL used } @@ -201,10 +203,9 @@ function UI(change=false) { let isRGBW = false, gRGBW = false, memu = 0; - let sumMA = 0, busMA = 0; + let busMA = 0; let sLC = 0, sPC = 0, sDI = 0, maxLC = 0; const ablEN = d.Sf.ABL.checked; - const pplEN = d.Sf.PPL.checked; // enable/disable LED fields d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{ @@ -253,9 +254,6 @@ gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off) gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog //gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description - if (ablEN && pplEN && !(isVir(t) || isAna(t))) { - sumMA += parseInt(d.Sf["MA"+n].value); // summarize PPL ABL limit (fields) - } }); // display global white channel overrides gId("wc").style.display = (gRGBW) ? 'inline':'none'; @@ -279,7 +277,6 @@ if (s+c > sLC) sLC = s+c; //update total count if (c > maxLC) maxLC = c; //max per output if (!isVir(t)) sPC += c; //virtual out busses do not count towards physical LEDs - //if (!(isVir(t) || isPWM(t))) sDI += c; if (!(isVir(t) || isAna(t))) { sDI += c; // summarize digital LED count let maPL = parseInt(d.Sf["LA"+n].value); @@ -327,18 +324,9 @@ else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff"; } }); - // distribute ABL current if not using PPL, otherwise sumMA contains summarized ABL limit - d.Sf.querySelectorAll("#mLC input[name^=LC]").forEach((s,n)=>{ - let c = parseInt(s.value,10); //get LED count - let t = parseInt(d.Sf["LT"+n].value); //get LED type - if (!ablEN || isVir(t) || isAna(t)) { - // virtual and analog LEDs have no limiter - d.Sf["MA"+n].value = 0; - return; - } - if (!pplEN) d.Sf["MA"+n].value = Math.round(parseInt(d.Sf.MA.value,10)*c/sDI); - }); - if (pplEN) d.Sf.MA.value = sumMA; // update global ABL if using PPL + // distribute ABL current if not using PPL + enPPL(sDI); + // update total led count gId("lc").textContent = sLC; gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)"; @@ -389,11 +377,12 @@ ${i+1}:
mA/LED: gId("-").style.display = (i>0) ? "inline":"none"; if (!init) { - enPPL(); UI(); } } @@ -775,7 +764,7 @@ Swap: mA
- Use per-output limiter:
+ Use per-output limiter:
Countdown Mode:
Countdown Goal:
diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index f9b7b28ee..a50fc8269 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -227,10 +227,10 @@ } else if (typeof(fld) === "number") sel.classList.add("pin"); // a hack to add a class let arr = d.getElementsByName(um); let idx = arr[0].type==="hidden"?1:0; // ignore hidden field - if (arr.length > 2) { + if (arr.length > 1+idx) { // we have array of values (usually pins) for (let i of arr) { - if (i.type === "number") break; + if (i.nodeName === "INPUT" && i.type === "number") break; idx++; } } diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 24e6000cc..76e733671 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -8,7 +8,7 @@ var d = document; var loc = false, locip, locproto = "http:"; var scanLoops = 0, preScanSSID = ""; - + var maxNetworks = 3; function gId(e) { return d.getElementById(e); } function cE(e) { return d.createElement(e); } function toggle(el){gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide");} @@ -33,7 +33,7 @@ ).reduce( // Filter out duplicate SSIDs. Since it is sorted by signal // strength, the strongest signal will be kept in the - // order it orginally appeared in the array. + // order it as originally appeared in the array. (unique, other) => { if(!unique.some(obj => obj.ssid === other.ssid)) { unique.push(other); @@ -52,13 +52,14 @@ } scanLoops = 0; - let cs = gId("CS"); - if (cs) { + let cs = d.querySelectorAll("#wifi_entries input[type=text]"); + for (let input of (cs||[])) { + let found = false; let select = cE("select"); - select.setAttribute("id", "CS"); - select.setAttribute("name", "CS"); - select.setAttribute("onchange", "T()"); - preScanSSID = cs.value; + select.id = input.id; + select.name = input.name; + select.setAttribute("onchange", "T(this)"); + preScanSSID = input.value; for (let i = 0; i < select.children.length; i++) { select.removeChild(select.children[i]); @@ -70,8 +71,9 @@ option.setAttribute("value", networks[i].ssid); option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`; - if (networks[i].ssid === cs.value) { + if (networks[i].ssid === input.value) { option.setAttribute("selected", "selected"); + found = true; } select.appendChild(option); @@ -79,10 +81,11 @@ const option = cE("option"); option.setAttribute("value", "!Cs"); - option.textContent = `Other network...`; + option.textContent = "Other network..."; select.appendChild(option); - cs.replaceWith(select); + if (input.value === "" || found) input.replaceWith(select); + else select.remove(); } button.disabled = false; @@ -90,17 +93,48 @@ }); } // replace WiFi select with custom SSID input field again - function T() { - let cs = gId("CS"); + function T(cs) { if (!cs || cs.value != "!Cs") return; let input = cE("input"); input.type = "text"; - input.id = "CS"; - input.name ="CS"; + input.id = cs.id; + input.name = cs.name; input.setAttribute("maxlength",32); input.value = preScanSSID; cs.replaceWith(input); } + function resetWiFi(maxN = undefined) { + if (maxN) maxNetworks = maxN; + let entries = gId("wifi_entries").children + for (let i = entries.length; i > 0; i--) entries[i-1].remove(); + btnWiFi(0); + } + function btnWiFi(i) { + gId("wifi_add").style.display = (i1) ? "inline":"none"; + } + function addWiFi(ssid="",pass="",ip=0,gw=0,sn=0x00ffffff) { // little endian + var i = gId("wifi_entries").childNodes.length; + if (i >= maxNetworks) return; + var b = `

+Network name (SSID${i==0?", empty to not connect":""}):
0?"required":""}>
+Network password:

+Static IP (leave at 0.0.0.0 for DHCP)${i==0?"
Also used by Ethernet":""}:
+...
+Static gateway:
+...
+Static subnet mask:
+...
`; + gId("wifi_entries").insertAdjacentHTML("beforeend", b); + btnWiFi(i+1); + } + function remWiFi() { + const entries = gId("wifi_entries").children; + const i = entries.length; + if (i < 2) return; + entries[i-1].remove(); + btnWiFi(i-1); + } // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript function loadJS(FILE_URL, async = true) { let scE = cE("script"); @@ -158,24 +192,16 @@

WiFi setup

Connect to existing network


- Network name (SSID, empty to not connect):
-
- Network password:

- Static IP (leave at 0.0.0.0 for DHCP):
- . - . - . -
- Static gateway:
- . - . - . -
- Static subnet mask:
- . - . - . -
+
+ Wireless networks +
+
+ +
+
+ DNS server address:
+ ...
+
mDNS address (leave empty for no mDNS):
http:// .local
Client IP: Not connected
@@ -186,10 +212,12 @@ Access Point WiFi channel:
AP opens:
+ + + + + +
AP IP: Not active

Experimental

Force 802.11g mode (ESP8266 only):
@@ -215,7 +243,8 @@ - + + diff --git a/wled00/data/style.css b/wled00/data/style.css index 52b26a825..b6cb0f9e6 100644 --- a/wled00/data/style.css +++ b/wled00/data/style.css @@ -81,6 +81,7 @@ input:disabled { } input[type="text"], input[type="number"], +input[type="password"], select { font-size: medium; margin: 2px; diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp index bcd7e7f9e..6bdf80a79 100644 --- a/wled00/dmx.cpp +++ b/wled00/dmx.cpp @@ -69,14 +69,10 @@ void handleDMX() } void initDMX() { - #ifdef ESP8266 + #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) dmx.init(512); // initialize with bus length #else dmx.initWrite(512); // initialize with bus length #endif } - -#else -void handleDMX() {} -void initDMX() {} #endif diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 68c7ca5a5..e54f60bf3 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -225,11 +225,16 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3]; if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]); - uint8_t segOption = (uint8_t)floor(e131_data[dataOffset+5]/64.0); - if (segOption == 0 && (seg.mirror || seg.reverse )) {seg.setOption(SEG_OPTION_MIRROR, false); seg.setOption(SEG_OPTION_REVERSED, false);} - if (segOption == 1 && (seg.mirror || !seg.reverse)) {seg.setOption(SEG_OPTION_MIRROR, false); seg.setOption(SEG_OPTION_REVERSED, true);} - if (segOption == 2 && (!seg.mirror || seg.reverse )) {seg.setOption(SEG_OPTION_MIRROR, true); seg.setOption(SEG_OPTION_REVERSED, false);} - if (segOption == 3 && (!seg.mirror || !seg.reverse)) {seg.setOption(SEG_OPTION_MIRROR, true); seg.setOption(SEG_OPTION_REVERSED, true);} + if ((e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.setOption(SEG_OPTION_REVERSED_Y, e131_data[dataOffset+5] & 0b00000010); } + if ((e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.setOption(SEG_OPTION_MIRROR_Y, e131_data[dataOffset+5] & 0b00000100); } + if ((e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.setOption(SEG_OPTION_TRANSPOSED, e131_data[dataOffset+5] & 0b00001000); } + if ((e131_data[dataOffset+5] & 0b00110000) / 8 != seg.map1D2D) { + seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) / 8; + } + // To maintain backwards compatibility with prior e1.31 values, reverse is fixed to mask 0x01000000 + if ((e131_data[dataOffset+5] & 0b01000000) != seg.reverse) { seg.setOption(SEG_OPTION_REVERSED, e131_data[dataOffset+5] & 0b01000000); } + // To maintain backwards compatibility with prior e1.31 values, mirror is fixed to mask 0x10000000 + if ((e131_data[dataOffset+5] & 0b10000000) != seg.mirror) { seg.setOption(SEG_OPTION_MIRROR, e131_data[dataOffset+5] & 0b10000000); } uint32_t colors[3]; byte whites[3] = {0,0,0}; @@ -490,7 +495,7 @@ void prepareArtnetPollReply(ArtPollReply *reply) { // Node is DHCP capable // Node supports 15 bit Port-Address (Art-Net 3 or 4) // Node is able to switch between ArtNet and sACN - reply->reply_status_2 = (staticIP[0] == 0) ? 0x1F : 0x1D; + reply->reply_status_2 = (multiWiFi[0].staticIP[0] == 0) ? 0x1F : 0x1D; // RDM is disabled // Output style is continuous diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 586f533e4..59be67624 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -50,6 +50,21 @@ bool getJsonValue(const JsonVariant& element, DestType& destination, const Defau return true; } +typedef struct WiFiConfig { + char clientSSID[33]; + char clientPass[65]; + IPAddress staticIP; + IPAddress staticGW; + IPAddress staticSN; + WiFiConfig(const char *ssid="", const char *pass="", uint32_t ip=0, uint32_t gw=0, uint32_t subnet=0x00FFFFFF) // little endian + : staticIP(ip) + , staticGW(gw) + , staticSN(subnet) + { + strncpy(clientSSID, ssid, 32); clientSSID[32] = 0; + strncpy(clientPass, pass, 64); clientPass[64] = 0; + } +} wifi_config; //colors.cpp // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) @@ -67,6 +82,8 @@ class NeoGammaWLEDMethod { uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); uint32_t color_add(uint32_t,uint32_t, bool fast=false); uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); +CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); +CRGBPalette16 generateRandomPalette(void); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb void colorKtoRGB(uint16_t kelvin, byte* rgb); @@ -97,6 +114,10 @@ bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest); void updateFSInfo(); void closeFile(); +inline bool writeObjectToFileUsingId(const String &file, uint16_t id, JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); }; +inline bool writeObjectToFile(const String &file, const char* key, JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); }; +inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest) { return readObjectFromFileUsingId(file.c_str(), id, dest); }; +inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest) { return readObjectFromFile(file.c_str(), key, dest); }; //hue.cpp void handleHue(); @@ -219,6 +240,7 @@ void handlePlaylist(); void serializePlaylist(JsonObject obj); //presets.cpp +const char *getPresetsFileName(bool persistent = true); void initPresetsFile(); void handlePresets(); bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE); @@ -247,6 +269,7 @@ void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); void refreshNodeList(); void sendSysInfoUDP(); #ifndef WLED_DISABLE_ESPNOW +void espNowSentCB(uint8_t* address, uint8_t status); void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); #endif @@ -422,15 +445,11 @@ void handleSerial(); void updateBaudRate(uint32_t rate); //wled_server.cpp -bool isIp(String str); +String getFileContentType(String &filename); void createEditHandler(bool enable); -bool captivePortal(AsyncWebServerRequest *request); void initServer(); -void serveIndex(AsyncWebServerRequest* request); -String msgProcessor(const String& var); void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255); void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t error); -String dmxProcessor(const String& var); void serveSettings(AsyncWebServerRequest* request, bool post = false); void serveSettingsJS(AsyncWebServerRequest* request); @@ -441,7 +460,6 @@ void sendDataWs(AsyncWebSocketClient * client = nullptr); //xml.cpp void XML_response(AsyncWebServerRequest *request, char* dest = nullptr); -void URL_response(AsyncWebServerRequest *request); void getSettingsJS(byte subPage, char* dest); #endif diff --git a/wled00/file.cpp b/wled00/file.cpp index 4edbdd6fb..37f794424 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -226,7 +226,7 @@ bool appendObjectToFile(const char* key, JsonDocument* content, uint32_t s, uint if (pos == 0) //not found { - DEBUGFS_PRINTLN("not }"); + DEBUGFS_PRINTLN(F("not }")); f.seek(0); while (bufferedFind("}",false)) //find last closing bracket in JSON if not last char { @@ -272,8 +272,8 @@ bool writeObjectToFile(const char* file, const char* key, JsonDocument* content) #endif size_t pos = 0; - f = WLED_FS.open(file, "r+"); - if (!f && !WLED_FS.exists(file)) f = WLED_FS.open(file, "w+"); + char fileName[129]; strncpy_P(fileName, file, 128); fileName[128] = 0; //use PROGMEM safe copy as FS.open() does not + f = WLED_FS.open(fileName, WLED_FS.exists(fileName) ? "r+" : "w+"); if (!f) { DEBUGFS_PRINTLN(F("Failed to open!")); return false; @@ -340,7 +340,8 @@ bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest) DEBUGFS_PRINTF("Read from %s with key %s >>>\n", file, (key==nullptr)?"nullptr":key); uint32_t s = millis(); #endif - f = WLED_FS.open(file, "r"); + char fileName[129]; strncpy_P(fileName, file, 128); fileName[128] = 0; //use PROGMEM safe copy as FS.open() does not + f = WLED_FS.open(fileName, "r"); if (!f) return false; if (key != nullptr && !bufferedFind(key)) //key does not exist in file @@ -374,36 +375,69 @@ void updateFSInfo() { #endif } +#if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) +// caching presets in PSRAM may prevent occasional flashes seen when HomeAssitant polls WLED +// original idea by @akaricchi (https://github.com/Akaricchi) +// returns a pointer to the PSRAM buffer updates size parameter +static const uint8_t *getPresetCache(size_t &size) { + static unsigned long presetsCachedTime; + static uint8_t *presetsCached; + static size_t presetsCachedSize; -//Un-comment any file types you need -static String getContentType(AsyncWebServerRequest* request, String filename){ - if(request->hasArg("download")) return "application/octet-stream"; - else if(filename.endsWith(".htm")) return "text/html"; - else if(filename.endsWith(".html")) return "text/html"; - else if(filename.endsWith(".css")) return "text/css"; - else if(filename.endsWith(".js")) return "application/javascript"; - else if(filename.endsWith(".json")) return "application/json"; - else if(filename.endsWith(".png")) return "image/png"; - else if(filename.endsWith(".gif")) return "image/gif"; - else if(filename.endsWith(".jpg")) return "image/jpeg"; - else if(filename.endsWith(".ico")) return "image/x-icon"; -// else if(filename.endsWith(".xml")) return "text/xml"; -// else if(filename.endsWith(".pdf")) return "application/x-pdf"; -// else if(filename.endsWith(".zip")) return "application/x-zip"; -// else if(filename.endsWith(".gz")) return "application/x-gzip"; - return "text/plain"; + if (!psramFound()) { + size = 0; + return nullptr; + } + + if (presetsModifiedTime != presetsCachedTime) { + if (presetsCached) { + free(presetsCached); + presetsCached = nullptr; + } + } + + if (!presetsCached) { + File file = WLED_FS.open(FPSTR(getPresetsFileName()), "r"); + if (file) { + presetsCachedTime = presetsModifiedTime; + presetsCachedSize = 0; + presetsCached = (uint8_t*)ps_malloc(file.size() + 1); + if (presetsCached) { + presetsCachedSize = file.size(); + file.read(presetsCached, presetsCachedSize); + presetsCached[presetsCachedSize] = 0; + file.close(); + } + } + } + + size = presetsCachedSize; + return presetsCached; } +#endif bool handleFileRead(AsyncWebServerRequest* request, String path){ - DEBUG_PRINTLN("WS FileRead: " + path); + DEBUG_PRINT(F("WS FileRead: ")); DEBUG_PRINTLN(path); if(path.endsWith("/")) path += "index.htm"; - if(path.indexOf("sec") > -1) return false; - String contentType = getContentType(request, path); + if(path.indexOf(F("sec")) > -1) return false; + String contentType = getFileContentType(path); + if(request->hasArg(F("download"))) contentType = F("application/octet-stream"); /*String pathWithGz = path + ".gz"; if(WLED_FS.exists(pathWithGz)){ request->send(WLED_FS, pathWithGz, contentType); return true; }*/ + #if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) + if (path.endsWith(FPSTR(getPresetsFileName()))) { + size_t psize; + const uint8_t *presets = getPresetCache(psize); + if (presets) { + AsyncWebServerResponse *response = request->beginResponse_P(200, contentType, presets, psize); + request->send(response); + return true; + } + } + #endif if(WLED_FS.exists(path)) { request->send(WLED_FS, path, contentType); return true; diff --git a/wled00/improv.cpp b/wled00/improv.cpp index 695d07ff7..9c2fb20e7 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -210,7 +210,7 @@ void sendImprovInfoResponse() { //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); char vString[20]; - sprintf_P(vString, PSTR("0.15.0-a0/%i"), VERSION); + sprintf_P(vString, PSTR("0.15.0-b1/%i"), VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); @@ -259,14 +259,14 @@ void parseWiFiCommand(char* rpcData) { uint8_t ssidLen = rpcData[1]; if (ssidLen > len -1 || ssidLen > 32) return; - memset(clientSSID, 0, 32); - memcpy(clientSSID, rpcData+2, ssidLen); + memset(multiWiFi[0].clientSSID, 0, 32); + memcpy(multiWiFi[0].clientSSID, rpcData+2, ssidLen); - memset(clientPass, 0, 64); + memset(multiWiFi[0].clientPass, 0, 64); if (len > ssidLen +1) { uint8_t passLen = rpcData[2+ssidLen]; - memset(clientPass, 0, 64); - memcpy(clientPass, rpcData+3+ssidLen, passLen); + memset(multiWiFi[0].clientPass, 0, 64); + memcpy(multiWiFi[0].clientPass, rpcData+3+ssidLen, passLen); } sendImprovStateResponse(0x03); //provisioning diff --git a/wled00/ir.cpp b/wled00/ir.cpp index 3da4d68be..ba34aa526 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -631,6 +631,7 @@ Sample: void decodeIRJson(uint32_t code) { char objKey[10]; + char fileName[16]; String cmdStr; JsonObject fdo; JsonObject jsonCmdObj; @@ -638,17 +639,18 @@ void decodeIRJson(uint32_t code) if (!requestJSONBufferLock(13)) return; sprintf_P(objKey, PSTR("\"0x%lX\":"), (unsigned long)code); + strcpy_P(fileName, PSTR("/ir.json")); // for FS.exists() // attempt to read command from ir.json // this may fail for two reasons: ir.json does not exist or IR code not found // if the IR code is not found readObjectFromFile() will clean() doc JSON document // so we can differentiate between the two - readObjectFromFile("/ir.json", objKey, pDoc); + readObjectFromFile(fileName, objKey, pDoc); fdo = pDoc->as(); lastValidCode = 0; if (fdo.isNull()) { //the received code does not exist - if (!WLED_FS.exists("/ir.json")) errorFlag = ERR_FS_IRLOAD; //warn if IR file itself doesn't exist + if (!WLED_FS.exists(fileName)) errorFlag = ERR_FS_IRLOAD; //warn if IR file itself doesn't exist releaseJSONBufferLock(); return; } diff --git a/wled00/json.cpp b/wled00/json.cpp index 395ee7cbc..b79da8d90 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -31,11 +31,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) newSeg = true; } - //DEBUG_PRINTLN("-- JSON deserialize segment."); + //DEBUG_PRINTLN(F("-- JSON deserialize segment.")); Segment& seg = strip.getSegment(id); - //DEBUG_PRINTF("-- Original segment: %p (%p)\n", &seg, seg.data); + //DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data); Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor) - //DEBUG_PRINTF("-- Duplicate segment: %p (%p)\n", &prev, prev.data); + //DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data); uint16_t start = elem["start"] | seg.start; if (stop < 0) { @@ -233,7 +233,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) getVal(elem["c1"], &seg.custom1); getVal(elem["c2"], &seg.custom2); uint8_t cust3 = seg.custom3; - getVal(elem["c3"], &cust3); // we can't pass reference to bifield + getVal(elem["c3"], &cust3); // we can't pass reference to bitfield seg.custom3 = constrain(cust3, 0, 31); seg.check1 = getBoolVal(elem["o1"], seg.check1); @@ -353,9 +353,9 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) nightlightTargetBri = nl[F("tbri")] | nightlightTargetBri; JsonObject udpn = root["udpn"]; - sendNotificationsRT = getBoolVal(udpn["send"], sendNotificationsRT); - syncGroups = udpn["sgrp"] | syncGroups; - receiveGroups = udpn["rgrp"] | receiveGroups; + sendNotificationsRT = getBoolVal(udpn[F("send")], sendNotificationsRT); + syncGroups = udpn[F("sgrp")] | syncGroups; + receiveGroups = udpn[F("rgrp")] | receiveGroups; if ((bool)udpn[F("nn")]) callMode = CALL_MODE_NO_NOTIFY; //send no notification just for this request unsigned long timein = root["time"] | UINT32_MAX; //backup time source if NTP not synced @@ -438,7 +438,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) // applying preset (2 cases: a) API call includes all preset values ("pd"), b) API only specifies preset ID ("ps")) byte presetToRestore = 0; // a) already applied preset content (requires "seg" or "win" but will ignore the rest) - if (!root["pd"].isNull() && stateChanged) { + if (!root[F("pd")].isNull() && stateChanged) { currentPreset = root[F("pd")] | currentPreset; if (root["win"].isNull()) presetCycCurr = currentPreset; // otherwise it was set in handleSet() [set.cpp] presetToRestore = currentPreset; // stateUpdated() will clear the preset, so we need to restore it after @@ -470,6 +470,19 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } + JsonObject wifi = root[F("wifi")]; + if (!wifi.isNull()) { + bool apMode = getBoolVal(wifi[F("ap")], apActive); + if (!apActive && apMode) WLED::instance().initAP(); // start AP mode immediately + else if (apActive && !apMode) { // stop AP mode immediately + dnsServer.stop(); + WiFi.softAPdisconnect(true); + apActive = false; + } + //bool restart = wifi[F("restart")] | false; + //if (restart) forceReconnect = true; + } + stateUpdated(callMode); if (presetToRestore) currentPreset = presetToRestore; @@ -566,17 +579,13 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme nl["dur"] = nightlightDelayMins; nl["mode"] = nightlightMode; nl[F("tbri")] = nightlightTargetBri; - if (nightlightActive) { - nl[F("rem")] = (nightlightDelayMs - (millis() - nightlightStartTime)) / 1000; // seconds remaining - } else { - nl[F("rem")] = -1; - } + nl[F("rem")] = nightlightActive ? (nightlightDelayMs - (millis() - nightlightStartTime)) / 1000 : -1; // seconds remaining JsonObject udpn = root.createNestedObject("udpn"); - udpn["send"] = sendNotificationsRT; - udpn["recv"] = receiveGroups != 0; - udpn["sgrp"] = syncGroups; - udpn["rgrp"] = receiveGroups; + udpn[F("send")] = sendNotificationsRT; + udpn[F("recv")] = receiveGroups != 0; + udpn[F("sgrp")] = syncGroups; + udpn[F("rgrp")] = receiveGroups; root[F("lor")] = realtimeOverride; } @@ -611,7 +620,7 @@ void serializeInfo(JsonObject root) root[F("vid")] = VERSION; root[F("cn")] = F(WLED_CODENAME); - JsonObject leds = root.createNestedObject("leds"); + JsonObject leds = root.createNestedObject(F("leds")); leds[F("count")] = strip.getLengthTotal(); leds[F("pwr")] = BusManager::currentMilliamps(); leds["fps"] = strip.getFps(); @@ -622,7 +631,7 @@ void serializeInfo(JsonObject root) #ifndef WLED_DISABLE_2D if (strip.isMatrix) { - JsonObject matrix = leds.createNestedObject("matrix"); + JsonObject matrix = leds.createNestedObject(F("matrix")); matrix["w"] = Segment::maxWidth; matrix["h"] = Segment::maxHeight; } @@ -674,12 +683,7 @@ void serializeInfo(JsonObject root) case REALTIME_MODE_DDP: root["lm"] = F("DDP"); break; } - if (realtimeIP[0] == 0) - { - root[F("lip")] = ""; - } else { - root[F("lip")] = realtimeIP.toString(); - } + root[F("lip")] = realtimeIP[0] == 0 ? "" : realtimeIP.toString(); #ifdef WLED_ENABLE_WEBSOCKETS root[F("ws")] = ws.count(); @@ -702,7 +706,7 @@ void serializeInfo(JsonObject root) } } - JsonObject wifi_info = root.createNestedObject("wifi"); + JsonObject wifi_info = root.createNestedObject(F("wifi")); wifi_info[F("bssid")] = WiFi.BSSIDstr(); int qrssi = WiFi.RSSI(); wifi_info[F("rssi")] = qrssi; @@ -716,7 +720,7 @@ void serializeInfo(JsonObject root) root[F("ndc")] = nodeListEnabled ? (int)Nodes.size() : -1; - #ifdef ARDUINO_ARCH_ESP32 +#ifdef ARDUINO_ARCH_ESP32 #ifdef WLED_DEBUG wifi_info[F("txPower")] = (int) WiFi.getTxPower(); wifi_info[F("sleep")] = (bool) WiFi.getSleep(); @@ -727,21 +731,21 @@ void serializeInfo(JsonObject root) root[F("arch")] = ESP.getChipModel(); #endif root[F("core")] = ESP.getSdkVersion(); - //root[F("maxalloc")] = ESP.getMaxAllocHeap(); #ifdef WLED_DEBUG - root[F("resetReason0")] = (int)rtc_get_reset_reason(0); - root[F("resetReason1")] = (int)rtc_get_reset_reason(1); + root[F("maxalloc")] = ESP.getMaxAllocHeap(); + root[F("resetReason0")] = (int)rtc_get_reset_reason(0); + root[F("resetReason1")] = (int)rtc_get_reset_reason(1); #endif root[F("lwip")] = 0; //deprecated - #else +#else root[F("arch")] = "esp8266"; root[F("core")] = ESP.getCoreVersion(); - //root[F("maxalloc")] = ESP.getMaxFreeBlockSize(); #ifdef WLED_DEBUG - root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason; + root[F("maxalloc")] = ESP.getMaxFreeBlockSize(); + root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason; #endif root[F("lwip")] = LWIP_VERSION_MAJOR; - #endif +#endif root[F("freeheap")] = ESP.getFreeHeap(); #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) @@ -786,8 +790,8 @@ void serializeInfo(JsonObject root) #endif root[F("opt")] = os; - root[F("brand")] = "WLED"; - root[F("product")] = F("FOSS"); + root[F("brand")] = F(WLED_BRAND); + root[F("product")] = F(WLED_PRODUCT_NAME); root["mac"] = escapedMac; char s[16] = ""; if (Network.isConnected()) @@ -886,7 +890,7 @@ void serializePalettes(JsonObject root, int page) curPalette.add("c2"); curPalette.add("c1"); break; - case 5: //primary + secondary (+tert if not off), more distinct + case 5: //primary + secondary (+tertiary if not off), more distinct curPalette.add("c1"); curPalette.add("c1"); curPalette.add("c1"); @@ -1017,40 +1021,53 @@ void serializeModeNames(JsonArray arr) } } - -// Global buffer locking response helper class -class GlobalBufferAsyncJsonResponse: public JSONBufferGuard, public AsyncJsonResponse { +// Global buffer locking response helper class (to make sure lock is released when AsyncJsonResponse is destroyed) +class LockedJsonResponse: public AsyncJsonResponse { + bool _holding_lock; public: - inline GlobalBufferAsyncJsonResponse(bool isArray) : JSONBufferGuard(17), AsyncJsonResponse(pDoc, isArray) {}; - virtual ~GlobalBufferAsyncJsonResponse() {}; + // WARNING: constructor assumes requestJSONBufferLock() was successfully acquired externally/prior to constructing the instance + // Not a good practice with C++. Unfortunately AsyncJsonResponse only has 2 constructors - for dynamic buffer or existing buffer, + // with existing buffer it clears its content during construction + // if the lock was not acquired (using JSONBufferGuard class) previous implementation still cleared existing buffer + inline LockedJsonResponse(JsonDocument* doc, bool isArray) : AsyncJsonResponse(doc, isArray), _holding_lock(true) {}; - // Other members are inherited + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) { + size_t result = AsyncJsonResponse::_fillBuffer(buf, maxLen); + // Release lock as soon as we're done filling content + if (((result + _sentLength) >= (_contentLength)) && _holding_lock) { + releaseJSONBufferLock(); + _holding_lock = false; + } + return result; + } + + // destructor will remove JSON buffer lock when response is destroyed in AsyncWebServer + virtual ~LockedJsonResponse() { if (_holding_lock) releaseJSONBufferLock(); }; }; - void serveJson(AsyncWebServerRequest* request) { byte subJson = 0; const String& url = request->url(); - if (url.indexOf("state") > 0) subJson = JSON_PATH_STATE; - else if (url.indexOf("info") > 0) subJson = JSON_PATH_INFO; - else if (url.indexOf("si") > 0) subJson = JSON_PATH_STATE_INFO; - else if (url.indexOf("nodes") > 0) subJson = JSON_PATH_NODES; - else if (url.indexOf("eff") > 0) subJson = JSON_PATH_EFFECTS; - else if (url.indexOf("palx") > 0) subJson = JSON_PATH_PALETTES; - else if (url.indexOf("fxda") > 0) subJson = JSON_PATH_FXDATA; - else if (url.indexOf("net") > 0) subJson = JSON_PATH_NETWORKS; + if (url.indexOf("state") > 0) subJson = JSON_PATH_STATE; + else if (url.indexOf("info") > 0) subJson = JSON_PATH_INFO; + else if (url.indexOf("si") > 0) subJson = JSON_PATH_STATE_INFO; + else if (url.indexOf(F("nodes")) > 0) subJson = JSON_PATH_NODES; + else if (url.indexOf(F("eff")) > 0) subJson = JSON_PATH_EFFECTS; + else if (url.indexOf(F("palx")) > 0) subJson = JSON_PATH_PALETTES; + else if (url.indexOf(F("fxda")) > 0) subJson = JSON_PATH_FXDATA; + else if (url.indexOf(F("net")) > 0) subJson = JSON_PATH_NETWORKS; #ifdef WLED_ENABLE_JSONLIVE - else if (url.indexOf("live") > 0) { + else if (url.indexOf("live") > 0) { serveLiveLeds(request); return; } #endif else if (url.indexOf("pal") > 0) { - request->send_P(200, "application/json", JSON_palette_names); + request->send_P(200, "application/json", JSON_palette_names); // contentType defined in AsyncJson-v6.h return; } - else if (url.indexOf("cfg") > 0 && handleFileRead(request, "/cfg.json")) { + else if (url.indexOf(F("cfg")) > 0 && handleFileRead(request, F("/cfg.json"))) { return; } else if (url.length() > 6) { //not just /json @@ -1058,12 +1075,13 @@ void serveJson(AsyncWebServerRequest* request) return; } - GlobalBufferAsyncJsonResponse *response = new GlobalBufferAsyncJsonResponse(subJson==JSON_PATH_FXDATA || subJson==JSON_PATH_EFFECTS); // will clear and convert JsonDocument into JsonArray if necessary - if (!response->owns_lock()) { + if (!requestJSONBufferLock(17)) { serveJsonError(request, 503, ERR_NOBUF); - delete response; return; } + // releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer) + // make sure you delete "response" if no "request->send(response);" is made + LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==JSON_PATH_FXDATA || subJson==JSON_PATH_EFFECTS); // will clear and convert JsonDocument into JsonArray if necessary JsonVariant lDoc = response->getRoot(); @@ -1076,7 +1094,7 @@ void serveJson(AsyncWebServerRequest* request) case JSON_PATH_NODES: serializeNodes(lDoc); break; case JSON_PATH_PALETTES: - serializePalettes(lDoc, request->hasParam("page") ? request->getParam("page")->value().toInt() : 0); break; + serializePalettes(lDoc, request->hasParam(F("page")) ? request->getParam(F("page"))->value().toInt() : 0); break; case JSON_PATH_EFFECTS: serializeModeNames(lDoc); break; case JSON_PATH_FXDATA: @@ -1097,7 +1115,7 @@ void serveJson(AsyncWebServerRequest* request) //lDoc["m"] = lDoc.memoryUsage(); // JSON buffer usage, for remote debugging } - DEBUG_PRINTF("JSON buffer size: %u for request: %d\n", lDoc.memoryUsage(), subJson); + DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for request: %d\n"), lDoc.memoryUsage(), subJson); #ifdef WLED_DEBUG size_t len = @@ -1166,7 +1184,7 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) #endif oappend("}"); if (request) { - request->send(200, "application/json", buffer); + request->send(200, "application/json", buffer); // contentType defined in AsyncJson-v6.h } #ifdef WLED_ENABLE_WEBSOCKETS else { diff --git a/wled00/led.cpp b/wled00/led.cpp index b8b51873c..ba772df9b 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -214,7 +214,6 @@ void colorUpdated(byte callMode) { void handleNightlight() { - static unsigned long lastNlUpdate; unsigned long now = millis(); if (now < 100 && lastNlUpdate > 0) lastNlUpdate = 0; // take care of millis() rollover if (now - lastNlUpdate < 100) return; // allow only 10 NL updates per second diff --git a/wled00/network.cpp b/wled00/network.cpp index 1b02d0c5d..2ae38f799 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -123,6 +123,16 @@ const ethernet_settings ethernetBoards[] = { 18, // eth_mdio, ETH_PHY_LAN8720, // eth_type, ETH_CLOCK_GPIO17_OUT // eth_clk_mode + }, + + // ESP32-POE-WROVER + { + 0, // eth_address, + 12, // eth_power, + 23, // eth_mdc, + 18, // eth_mdio, + ETH_PHY_LAN8720, // eth_type, + ETH_CLOCK_GPIO0_OUT // eth_clk_mode } }; #endif @@ -163,8 +173,8 @@ void WiFiEvent(WiFiEvent_t event) if (!apActive) { WiFi.disconnect(true); } - if (staticIP != (uint32_t)0x00000000 && staticGateway != (uint32_t)0x00000000) { - ETH.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); + if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) { + ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress); } else { ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); } diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index c9c4874b9..0b2cf3665 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -48,129 +48,118 @@ Timezone* tz; #define TZ_ANCHORAGE 20 #define TZ_MX_CENTRAL 21 #define TZ_PAKISTAN 22 + +#define TZ_COUNT 23 #define TZ_INIT 255 byte tzCurrent = TZ_INIT; //uninitialized +/* C++11 form -- static std::array, TZ_COUNT> TZ_TABLE PROGMEM = {{ */ +static const std::pair TZ_TABLE[] PROGMEM = { + /* TZ_UTC */ { + {Last, Sun, Mar, 1, 0}, // UTC + {Last, Sun, Mar, 1, 0} // Same + }, + /* TZ_UK */ { + {Last, Sun, Mar, 1, 60}, //British Summer Time + {Last, Sun, Oct, 2, 0} //Standard Time + }, + /* TZ_EUROPE_CENTRAL */ { + {Last, Sun, Mar, 2, 120}, //Central European Summer Time + {Last, Sun, Oct, 3, 60} //Central European Standard Time + }, + /* TZ_EUROPE_EASTERN */ { + {Last, Sun, Mar, 3, 180}, //East European Summer Time + {Last, Sun, Oct, 4, 120} //East European Standard Time + }, + /* TZ_US_EASTERN */ { + {Second, Sun, Mar, 2, -240}, //EDT = UTC - 4 hours + {First, Sun, Nov, 2, -300} //EST = UTC - 5 hours + }, + /* TZ_US_CENTRAL */ { + {Second, Sun, Mar, 2, -300}, //CDT = UTC - 5 hours + {First, Sun, Nov, 2, -360} //CST = UTC - 6 hours + }, + /* TZ_US_MOUNTAIN */ { + {Second, Sun, Mar, 2, -360}, //MDT = UTC - 6 hours + {First, Sun, Nov, 2, -420} //MST = UTC - 7 hours + }, + /* TZ_US_ARIZONA */ { + {First, Sun, Nov, 2, -420}, //MST = UTC - 7 hours + {First, Sun, Nov, 2, -420} //MST = UTC - 7 hours + }, + /* TZ_US_PACIFIC */ { + {Second, Sun, Mar, 2, -420}, //PDT = UTC - 7 hours + {First, Sun, Nov, 2, -480} //PST = UTC - 8 hours + }, + /* TZ_CHINA */ { + {Last, Sun, Mar, 1, 480}, //CST = UTC + 8 hours + {Last, Sun, Mar, 1, 480} + }, + /* TZ_JAPAN */ { + {Last, Sun, Mar, 1, 540}, //JST = UTC + 9 hours + {Last, Sun, Mar, 1, 540} + }, + /* TZ_AUSTRALIA_EASTERN */ { + {First, Sun, Oct, 2, 660}, //AEDT = UTC + 11 hours + {First, Sun, Apr, 3, 600} //AEST = UTC + 10 hours + }, + /* TZ_NEW_ZEALAND */ { + {Last, Sun, Sep, 2, 780}, //NZDT = UTC + 13 hours + {First, Sun, Apr, 3, 720} //NZST = UTC + 12 hours + }, + /* TZ_NORTH_KOREA */ { + {Last, Sun, Mar, 1, 510}, //Pyongyang Time = UTC + 8.5 hours + {Last, Sun, Mar, 1, 510} + }, + /* TZ_INDIA */ { + {Last, Sun, Mar, 1, 330}, //India Standard Time = UTC + 5.5 hours + {Last, Sun, Mar, 1, 330} + }, + /* TZ_SASKACHEWAN */ { + {First, Sun, Nov, 2, -360}, //CST = UTC - 6 hours + {First, Sun, Nov, 2, -360} + }, + /* TZ_AUSTRALIA_NORTHERN */ { + {First, Sun, Apr, 3, 570}, //ACST = UTC + 9.5 hours + {First, Sun, Apr, 3, 570} + }, + /* TZ_AUSTRALIA_SOUTHERN */ { + {First, Sun, Oct, 2, 630}, //ACDT = UTC + 10.5 hours + {First, Sun, Apr, 3, 570} //ACST = UTC + 9.5 hours + }, + /* TZ_HAWAII */ { + {Last, Sun, Mar, 1, -600}, //HST = UTC - 10 hours + {Last, Sun, Mar, 1, -600} + }, + /* TZ_NOVOSIBIRSK */ { + {Last, Sun, Mar, 1, 420}, //CST = UTC + 7 hours + {Last, Sun, Mar, 1, 420} + }, + /* TZ_ANCHORAGE */ { + {Second, Sun, Mar, 2, -480}, //AKDT = UTC - 8 hours + {First, Sun, Nov, 2, -540} //AKST = UTC - 9 hours + }, + /* TZ_MX_CENTRAL */ { + {First, Sun, Apr, 2, -360}, //CST = UTC - 6 hours + {First, Sun, Apr, 2, -360} + }, + /* TZ_PAKISTAN */ { + {Last, Sun, Mar, 1, 300}, //Pakistan Standard Time = UTC + 5 hours + {Last, Sun, Mar, 1, 300} + } +}; + void updateTimezone() { delete tz; - TimeChangeRule tcrDaylight = {Last, Sun, Mar, 1, 0}; //UTC - TimeChangeRule tcrStandard = tcrDaylight; //UTC - - switch (currentTimezone) { - case TZ_UK : { - tcrDaylight = {Last, Sun, Mar, 1, 60}; //British Summer Time - tcrStandard = {Last, Sun, Oct, 2, 0}; //Standard Time - break; - } - case TZ_EUROPE_CENTRAL : { - tcrDaylight = {Last, Sun, Mar, 2, 120}; //Central European Summer Time - tcrStandard = {Last, Sun, Oct, 3, 60}; //Central European Standard Time - break; - } - case TZ_EUROPE_EASTERN : { - tcrDaylight = {Last, Sun, Mar, 3, 180}; //East European Summer Time - tcrStandard = {Last, Sun, Oct, 4, 120}; //East European Standard Time - break; - } - case TZ_US_EASTERN : { - tcrDaylight = {Second, Sun, Mar, 2, -240}; //EDT = UTC - 4 hours - tcrStandard = {First, Sun, Nov, 2, -300}; //EST = UTC - 5 hours - break; - } - case TZ_US_CENTRAL : { - tcrDaylight = {Second, Sun, Mar, 2, -300}; //CDT = UTC - 5 hours - tcrStandard = {First, Sun, Nov, 2, -360}; //CST = UTC - 6 hours - break; - } - case TZ_US_MOUNTAIN : { - tcrDaylight = {Second, Sun, Mar, 2, -360}; //MDT = UTC - 6 hours - tcrStandard = {First, Sun, Nov, 2, -420}; //MST = UTC - 7 hours - break; - } - case TZ_US_ARIZONA : { - tcrDaylight = {First, Sun, Nov, 2, -420}; //MST = UTC - 7 hours - tcrStandard = {First, Sun, Nov, 2, -420}; //MST = UTC - 7 hours - break; - } - case TZ_US_PACIFIC : { - tcrDaylight = {Second, Sun, Mar, 2, -420}; //PDT = UTC - 7 hours - tcrStandard = {First, Sun, Nov, 2, -480}; //PST = UTC - 8 hours - break; - } - case TZ_CHINA : { - tcrDaylight = {Last, Sun, Mar, 1, 480}; //CST = UTC + 8 hours - tcrStandard = tcrDaylight; - break; - } - case TZ_JAPAN : { - tcrDaylight = {Last, Sun, Mar, 1, 540}; //JST = UTC + 9 hours - tcrStandard = tcrDaylight; - break; - } - case TZ_AUSTRALIA_EASTERN : { - tcrDaylight = {First, Sun, Oct, 2, 660}; //AEDT = UTC + 11 hours - tcrStandard = {First, Sun, Apr, 3, 600}; //AEST = UTC + 10 hours - break; - } - case TZ_NEW_ZEALAND : { - tcrDaylight = {Last, Sun, Sep, 2, 780}; //NZDT = UTC + 13 hours - tcrStandard = {First, Sun, Apr, 3, 720}; //NZST = UTC + 12 hours - break; - } - case TZ_NORTH_KOREA : { - tcrDaylight = {Last, Sun, Mar, 1, 510}; //Pyongyang Time = UTC + 8.5 hours - tcrStandard = tcrDaylight; - break; - } - case TZ_INDIA : { - tcrDaylight = {Last, Sun, Mar, 1, 330}; //India Standard Time = UTC + 5.5 hours - tcrStandard = tcrDaylight; - break; - } - case TZ_SASKACHEWAN : { - tcrDaylight = {First, Sun, Nov, 2, -360}; //CST = UTC - 6 hours - tcrStandard = tcrDaylight; - break; - } - case TZ_AUSTRALIA_NORTHERN : { - tcrDaylight = {First, Sun, Apr, 3, 570}; //ACST = UTC + 9.5 hours - tcrStandard = tcrDaylight; - break; - } - case TZ_AUSTRALIA_SOUTHERN : { - tcrDaylight = {First, Sun, Oct, 2, 630}; //ACDT = UTC + 10.5 hours - tcrStandard = {First, Sun, Apr, 3, 570}; //ACST = UTC + 9.5 hours - break; - } - case TZ_HAWAII : { - tcrDaylight = {Last, Sun, Mar, 1, -600}; //HST = UTC - 10 hours - tcrStandard = tcrDaylight; - break; - } - case TZ_NOVOSIBIRSK : { - tcrDaylight = {Last, Sun, Mar, 1, 420}; //CST = UTC + 7 hours - tcrStandard = tcrDaylight; - break; - } - case TZ_ANCHORAGE : { - tcrDaylight = {Second, Sun, Mar, 2, -480}; //AKDT = UTC - 8 hours - tcrStandard = {First, Sun, Nov, 2, -540}; //AKST = UTC - 9 hours - break; - } - case TZ_MX_CENTRAL : { - tcrDaylight = {First, Sun, Apr, 2, -360}; //CST = UTC - 6 hours - tcrStandard = tcrDaylight; - break; - } - case TZ_PAKISTAN : { - tcrDaylight = {Last, Sun, Mar, 1, 300}; //Pakistan Standard Time = UTC + 5 hours - tcrStandard = tcrDaylight; - break; - } + TimeChangeRule tcrDaylight, tcrStandard; + auto tz_table_entry = currentTimezone; + if (tz_table_entry >= TZ_COUNT) { + tz_table_entry = 0; } - tzCurrent = currentTimezone; + memcpy_P(&tcrDaylight, &TZ_TABLE[tz_table_entry].first, sizeof(tcrDaylight)); + memcpy_P(&tcrStandard, &TZ_TABLE[tz_table_entry].second, sizeof(tcrStandard)); tz = new Timezone(tcrDaylight, tcrStandard); } @@ -199,6 +188,9 @@ void handleNetworkTime() { if (millis() - ntpPacketSentTime > 10000) { + #ifdef ARDUINO_ARCH_ESP32 // I had problems using udp.flush() on 8266 + while (ntpUdp.parsePacket() > 0) ntpUdp.flush(); // flush any existing packets + #endif sendNTPPacket(); ntpPacketSentTime = millis(); } @@ -239,16 +231,38 @@ void sendNTPPacket() ntpUdp.endPacket(); } +static bool isValidNtpResponse(byte * ntpPacket) { + // Perform a few validity checks on the packet + // based on https://github.com/taranais/NTPClient/blob/master/NTPClient.cpp + if((ntpPacket[0] & 0b11000000) == 0b11000000) return false; //reject LI=UNSYNC + // if((ntpPacket[0] & 0b00111000) >> 3 < 0b100) return false; //reject Version < 4 + if((ntpPacket[0] & 0b00000111) != 0b100) return false; //reject Mode != Server + if((ntpPacket[1] < 1) || (ntpPacket[1] > 15)) return false; //reject invalid Stratum + if( ntpPacket[16] == 0 && ntpPacket[17] == 0 && + ntpPacket[18] == 0 && ntpPacket[19] == 0 && + ntpPacket[20] == 0 && ntpPacket[21] == 0 && + ntpPacket[22] == 0 && ntpPacket[23] == 0) //reject ReferenceTimestamp == 0 + return false; + + return true; +} + bool checkNTPResponse() { int cb = ntpUdp.parsePacket(); - if (!cb) return false; + if (cb < NTP_MIN_PACKET_SIZE) { + #ifdef ARDUINO_ARCH_ESP32 // I had problems using udp.flush() on 8266 + if (cb > 0) ntpUdp.flush(); // this avoids memory leaks on esp32 + #endif + return false; + } uint32_t ntpPacketReceivedTime = millis(); DEBUG_PRINT(F("NTP recv, l=")); DEBUG_PRINTLN(cb); byte pbuf[NTP_PACKET_SIZE]; ntpUdp.read(pbuf, NTP_PACKET_SIZE); // read the packet into the buffer + if (!isValidNtpResponse(pbuf)) return false; // verify we have a valid response to client Toki::Time arrived = toki.fromNTP(pbuf + 32); Toki::Time departed = toki.fromNTP(pbuf + 40); @@ -303,7 +317,8 @@ void getTimeString(char* out) sprintf_P(out,PSTR("%i-%i-%i, %02d:%02d:%02d"),year(localTime), month(localTime), day(localTime), hr, minute(localTime), second(localTime)); if (useAMPM) { - strcat(out,(hour(localTime) > 11)? " PM":" AM"); + strcat_P(out,PSTR(" ")); + strcat(out,(hour(localTime) > 11)? "PM":"AM"); } } @@ -373,7 +388,7 @@ void checkTimers() // re-calculate sunrise and sunset just after midnight if (!hour(localTime) && minute(localTime)==1) calculateSunriseAndSunset(); - DEBUG_PRINTF("Local time: %02d:%02d\n", hour(localTime), minute(localTime)); + DEBUG_PRINTF_P(PSTR("Local time: %02d:%02d\n"), hour(localTime), minute(localTime)); for (uint8_t i = 0; i < 8; i++) { if (timerMacro[i] != 0 @@ -391,7 +406,7 @@ void checkTimers() // sunrise macro if (sunrise) { time_t tmp = sunrise + timerMinutes[8]*60; // NOTE: may not be ok - DEBUG_PRINTF("Trigger time: %02d:%02d\n", hour(tmp), minute(tmp)); + DEBUG_PRINTF_P(PSTR("Trigger time: %02d:%02d\n"), hour(tmp), minute(tmp)); if (timerMacro[8] != 0 && hour(tmp) == hour(localTime) && minute(tmp) == minute(localTime) @@ -400,13 +415,13 @@ void checkTimers() { unloadPlaylist(); applyPreset(timerMacro[8]); - DEBUG_PRINTF("Sunrise macro %d triggered.",timerMacro[8]); + DEBUG_PRINTF_P(PSTR("Sunrise macro %d triggered."),timerMacro[8]); } } // sunset macro if (sunset) { time_t tmp = sunset + timerMinutes[9]*60; // NOTE: may not be ok - DEBUG_PRINTF("Trigger time: %02d:%02d\n", hour(tmp), minute(tmp)); + DEBUG_PRINTF_P(PSTR("Trigger time: %02d:%02d\n"), hour(tmp), minute(tmp)); if (timerMacro[9] != 0 && hour(tmp) == hour(localTime) && minute(tmp) == minute(localTime) @@ -415,15 +430,15 @@ void checkTimers() { unloadPlaylist(); applyPreset(timerMacro[9]); - DEBUG_PRINTF("Sunset macro %d triggered.",timerMacro[9]); + DEBUG_PRINTF_P(PSTR("Sunset macro %d triggered."),timerMacro[9]); } } } } #define ZENITH -0.83 -// get sunrise (or sunset) time (in minutes) for a given day at a given geo location -int getSunriseUTC(int year, int month, int day, float lat, float lon, bool sunset=false) { +// get sunrise (or sunset) time (in minutes) for a given day at a given geo location. Returns >= INT16_MAX in case of "no sunset" +static int getSunriseUTC(int year, int month, int day, float lat, float lon, bool sunset=false) { //1. first calculate the day of the year float N1 = 275 * month / 9; float N2 = (month + 9) / 12; @@ -457,8 +472,8 @@ int getSunriseUTC(int year, int month, int day, float lat, float lon, bool sunse //7a. calculate the Sun's local hour angle float cosH = (sinf(DEG_TO_RAD*ZENITH) - (sinDec * sinf(DEG_TO_RAD*lat))) / (cosDec * cosf(DEG_TO_RAD*lat)); - if ((cosH > 1.0f) && !sunset) return 0; // the sun never rises on this location (on the specified date) - if ((cosH < -1.0f) && sunset) return 0; // the sun never sets on this location (on the specified date) + if ((cosH > 1.0f) && !sunset) return INT16_MAX; // the sun never rises on this location (on the specified date) + if ((cosH < -1.0f) && sunset) return INT16_MAX; // the sun never sets on this location (on the specified date) //7b. finish calculating H and convert into hours float H = sunset ? RAD_TO_DEG*acosf(cosH) : 360 - RAD_TO_DEG*acosf(cosH); @@ -474,6 +489,7 @@ int getSunriseUTC(int year, int month, int day, float lat, float lon, bool sunse return UT*60; } +#define SUNSET_MAX (24*60) // 1day = max expected absolute value for sun offset in minutes // calculate sunrise and sunset (if longitude and latitude are set) void calculateSunriseAndSunset() { if ((int)(longitude*10.) || (int)(latitude*10.)) { @@ -484,26 +500,44 @@ void calculateSunriseAndSunset() { tim_0.tm_sec = 0; tim_0.tm_isdst = 0; - int minUTC = getSunriseUTC(year(localTime), month(localTime), day(localTime), latitude, longitude); - if (minUTC) { + // Due to limited accuracy, its possible to get a bad sunrise/sunset displayed as "00:00" (see issue #3601) + // So in case of invalid result, we try to use the sunset/sunrise of previous day. Max 3 days back, this worked well in all cases I tried. + // When latitude = 66,6 (N or S), the functions sometimes returns 2147483647, so this "unexpected large" is another condition for retry + int minUTC = 0; + int retryCount = 0; + do { + time_t theDay = localTime - retryCount * 86400; // one day back = 86400 seconds + minUTC = getSunriseUTC(year(theDay), month(theDay), day(theDay), latitude, longitude, false); + DEBUG_PRINT(F("* sunrise (minutes from UTC) = ")); DEBUG_PRINTLN(minUTC); + retryCount ++; + } while ((abs(minUTC) > SUNSET_MAX) && (retryCount <= 3)); + + if (abs(minUTC) <= SUNSET_MAX) { // there is a sunrise if (minUTC < 0) minUTC += 24*60; // add a day if negative tim_0.tm_hour = minUTC / 60; tim_0.tm_min = minUTC % 60; sunrise = tz->toLocal(mktime(&tim_0) + utcOffsetSecs); - DEBUG_PRINTF("Sunrise: %02d:%02d\n", hour(sunrise), minute(sunrise)); + DEBUG_PRINTF_P(PSTR("Sunrise: %02d:%02d\n"), hour(sunrise), minute(sunrise)); } else { sunrise = 0; } - minUTC = getSunriseUTC(year(localTime), month(localTime), day(localTime), latitude, longitude, true); - if (minUTC) { + retryCount = 0; + do { + time_t theDay = localTime - retryCount * 86400; // one day back = 86400 seconds + minUTC = getSunriseUTC(year(theDay), month(theDay), day(theDay), latitude, longitude, true); + DEBUG_PRINT(F("* sunset (minutes from UTC) = ")); DEBUG_PRINTLN(minUTC); + retryCount ++; + } while ((abs(minUTC) > SUNSET_MAX) && (retryCount <= 3)); + + if (abs(minUTC) <= SUNSET_MAX) { // there is a sunset if (minUTC < 0) minUTC += 24*60; // add a day if negative tim_0.tm_hour = minUTC / 60; tim_0.tm_min = minUTC % 60; sunset = tz->toLocal(mktime(&tim_0) + utcOffsetSecs); - DEBUG_PRINTF("Sunset: %02d:%02d\n", hour(sunset), minute(sunset)); + DEBUG_PRINTF_P(PSTR("Sunset: %02d:%02d\n"), hour(sunset), minute(sunset)); } else { sunset = 0; } diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index f065cd36a..19b26c224 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -89,6 +89,16 @@ void _overlayAnalogCountdown() void handleOverlayDraw() { usermods.handleOverlayDraw(); + if (analogClockSolidBlack) { + const Segment* segments = strip.getSegments(); + for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) { + const Segment& segment = segments[i]; + if (!segment.isActive()) continue; + if (segment.mode > 0 || segment.colors[0] > 0) { + return; + } + } + } if (overlayCurrent == 1) _overlayAnalogClock(); } diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 102acd0ea..044dc6c92 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -6,7 +6,7 @@ static void DebugPrintOwnerTag(PinOwner tag) { uint32_t q = static_cast(tag); if (q) { - DEBUG_PRINTF("0x%02x (%d)", q, q); + DEBUG_PRINTF_P(PSTR("0x%02x (%d)"), q, q); } else { DEBUG_PRINT(F("(no owner)")); } @@ -109,7 +109,7 @@ bool PinManagerClass::allocateMultiplePins(const managed_pin_type * mptArray, by #ifdef WLED_DEBUG DEBUG_PRINT(F("PIN ALLOC: Invalid pin attempted to be allocated: GPIO ")); DEBUG_PRINT(gpio); - DEBUG_PRINT(" as "); DEBUG_PRINT(mptArray[i].isOutput ? "output": "input"); + DEBUG_PRINT(F(" as ")); DEBUG_PRINT(mptArray[i].isOutput ? F("output"): F("input")); DEBUG_PRINTLN(F("")); #endif shouldFail = true; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 1402de2bd..6a50df588 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -60,7 +60,8 @@ enum struct PinOwner : uint8_t { UM_BME280 = USERMOD_ID_BME280, // 0x1E // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE, // 0x20 // Usermod "audio_reactive.h" UM_SdCard = USERMOD_ID_SD_CARD, // 0x25 // Usermod "usermod_sd_card.h" - UM_PWM_OUTPUTS = USERMOD_ID_PWM_OUTPUTS // 0x26 // Usermod "usermod_pwm_outputs.h" + UM_PWM_OUTPUTS = USERMOD_ID_PWM_OUTPUTS, // 0x26 // Usermod "usermod_pwm_outputs.h" + UM_LDR_DUSK_DAWN = USERMOD_ID_LDR_DUSK_DAWN // 0x2B // Usermod "usermod_LDR_Dusk_Dawn_v2.h" }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); diff --git a/wled00/presets.cpp b/wled00/presets.cpp index a1a7b08fc..72833a97c 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -12,17 +12,18 @@ static volatile byte presetToApply = 0; static volatile byte callModeToApply = 0; static volatile byte presetToSave = 0; static volatile int8_t saveLedmap = -1; -static char quickLoad[9]; -static char saveName[33]; +static char *quickLoad = nullptr; +static char *saveName = nullptr; static bool includeBri = true, segBounds = true, selectedOnly = false, playlistSave = false;; -static const char *getFileName(bool persist = true) { - return persist ? "/presets.json" : "/tmp.json"; +static const char presets_json[] PROGMEM = "/presets.json"; +static const char tmp_json[] PROGMEM = "/tmp.json"; +const char *getPresetsFileName(bool persistent) { + return persistent ? presets_json : tmp_json; } static void doSaveState() { bool persist = (presetToSave < 251); - const char *filename = getFileName(persist); unsigned long start = millis(); while (strip.isUpdating() && millis()-start < (2*FRAMETIME_FIXED)+1) yield(); // wait 2 frames @@ -38,8 +39,9 @@ static void doSaveState() { } else { serializeState(sObj, true, includeBri, segBounds, selectedOnly); } - sObj["n"] = saveName; - if (quickLoad[0]) sObj[F("ql")] = quickLoad; + if (saveName) sObj["n"] = saveName; + else sObj["n"] = F("Unkonwn preset"); // should not happen, but just in case... + if (quickLoad && quickLoad[0]) sObj[F("ql")] = quickLoad; if (saveLedmap >= 0) sObj[F("ledmap")] = saveLedmap; /* #ifdef WLED_DEBUG @@ -63,11 +65,11 @@ static void doSaveState() { if (tmpRAMbuffer!=nullptr) { serializeJson(*fileDoc, tmpRAMbuffer, len); } else { - writeObjectToFileUsingId(filename, presetToSave, fileDoc); + writeObjectToFileUsingId(getPresetsFileName(persist), presetToSave, fileDoc); } } else #endif - writeObjectToFileUsingId(filename, presetToSave, fileDoc); + writeObjectToFileUsingId(getPresetsFileName(persist), presetToSave, fileDoc); if (persist) presetsModifiedTime = toki.second(); //unix time releaseJSONBufferLock(); @@ -76,17 +78,18 @@ static void doSaveState() { // clean up saveLedmap = -1; presetToSave = 0; - saveName[0] = '\0'; - quickLoad[0] = '\0'; + delete[] saveName; + delete[] quickLoad; + saveName = nullptr; + quickLoad = nullptr; playlistSave = false; } bool getPresetName(byte index, String& name) { - if (!requestJSONBufferLock(9)) return false; + if (!requestJSONBufferLock(19)) return false; bool presetExists = false; - if (readObjectFromFileUsingId(getFileName(), index, pDoc)) - { + if (readObjectFromFileUsingId(getPresetsFileName(), index, pDoc)) { JsonObject fdo = pDoc->as(); if (fdo["n"]) { name = (const char*)(fdo["n"]); @@ -99,12 +102,13 @@ bool getPresetName(byte index, String& name) void initPresetsFile() { - if (WLED_FS.exists(getFileName())) return; + char fileName[33]; strncpy_P(fileName, getPresetsFileName(), 32); fileName[32] = 0; //use PROGMEM safe copy as FS.open() does not + if (WLED_FS.exists(fileName)) return; StaticJsonDocument<64> doc; JsonObject sObj = doc.to(); sObj.createNestedObject("0"); - File f = WLED_FS.open(getFileName(), "w"); + File f = WLED_FS.open(fileName, "w"); if (!f) { errorFlag = ERR_FS_GENERAL; return; @@ -147,7 +151,6 @@ void handlePresets() uint8_t tmpMode = callModeToApply; JsonObject fdo; - const char *filename = getFileName(tmpPreset < 255); // allocate buffer if (!requestJSONBufferLock(9)) return; // will also assign fileDoc @@ -165,7 +168,7 @@ void handlePresets() } else #endif { - errorFlag = readObjectFromFileUsingId(filename, tmpPreset, fileDoc) ? ERR_NONE : ERR_FS_PLOAD; + errorFlag = readObjectFromFileUsingId(getPresetsFileName(tmpPreset < 255), tmpPreset, fileDoc) ? ERR_NONE : ERR_FS_PLOAD; } fdo = fileDoc->as(); @@ -203,6 +206,10 @@ void handlePresets() //called from handleSet(PS=) [network callback (fileDoc==nullptr), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)] void savePreset(byte index, const char* pname, JsonObject sObj) { + if (!saveName) saveName = new char[33]; + if (!quickLoad) quickLoad = new char[9]; + if (!saveName || !quickLoad) return; + if (index == 0 || (index > 250 && index < 255)) return; if (pname) strlcpy(saveName, pname, 33); else { @@ -215,6 +222,7 @@ void savePreset(byte index, const char* pname, JsonObject sObj) presetToSave = index; playlistSave = false; if (sObj[F("ql")].is()) strlcpy(quickLoad, sObj[F("ql")].as(), 9); // client limits QL to 2 chars, buffer for 8 bytes to allow unicode + else quickLoad[0] = 0; if (sObj.size()==0 || sObj["o"].isNull()) { // no "o" means not a playlist or custom API call, saving of state is async (not immediately) includeBri = sObj["ib"].as() || sObj.size()==0 || index==255; // temporary preset needs brightness @@ -226,17 +234,22 @@ void savePreset(byte index, const char* pname, JsonObject sObj) if (sObj[F("playlist")].isNull()) { // we will save API call immediately (often causes presets.json corruption) presetToSave = 0; - if (index > 250 || !fileDoc) return; // cannot save API calls to temporary preset (255) - sObj.remove("o"); - sObj.remove("v"); - sObj.remove("time"); - sObj.remove(F("error")); - sObj.remove(F("psave")); - if (sObj["n"].isNull()) sObj["n"] = saveName; - initPresetsFile(); // just in case if someone deleted presets.json using /edit - writeObjectToFileUsingId(getFileName(index<255), index, fileDoc); - presetsModifiedTime = toki.second(); //unix time - updateFSInfo(); + if (index <= 250 && fileDoc) { // cannot save API calls to temporary preset (255) + sObj.remove("o"); + sObj.remove("v"); + sObj.remove("time"); + sObj.remove(F("error")); + sObj.remove(F("psave")); + if (sObj["n"].isNull()) sObj["n"] = saveName; + initPresetsFile(); // just in case if someone deleted presets.json using /edit + writeObjectToFileUsingId(getPresetsFileName(), index, fileDoc); + presetsModifiedTime = toki.second(); //unix time + updateFSInfo(); + } + delete[] saveName; + delete[] quickLoad; + saveName = nullptr; + quickLoad = nullptr; } else { // store playlist // WARNING: playlist will be loaded in json.cpp after this call and will have repeat counter increased by 1 @@ -248,7 +261,7 @@ void savePreset(byte index, const char* pname, JsonObject sObj) void deletePreset(byte index) { StaticJsonDocument<24> empty; - writeObjectToFileUsingId(getFileName(), index, &empty); + writeObjectToFileUsingId(getPresetsFileName(), index, &empty); presetsModifiedTime = toki.second(); //unix time updateFSInfo(); } \ No newline at end of file diff --git a/wled00/remote.cpp b/wled00/remote.cpp index c41d88421..49fbc4b02 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -123,11 +123,11 @@ static bool remoteJson(int button) sprintf_P(objKey, PSTR("\"%d\":"), button); // attempt to read command from remote.json - readObjectFromFile("/remote.json", objKey, pDoc); + readObjectFromFile(PSTR("/remote.json"), objKey, pDoc); JsonObject fdo = pDoc->as(); if (fdo.isNull()) { // the received button does not exist - if (!WLED_FS.exists("/remote.json")) errorFlag = ERR_FS_RMLOAD; //warn if file itself doesn't exist + //if (!WLED_FS.exists(F("/remote.json"))) errorFlag = ERR_FS_RMLOAD; //warn if file itself doesn't exist releaseJSONBufferLock(); return parsed; } diff --git a/wled00/set.cpp b/wled00/set.cpp index e83911783..4e2e60b3d 100755 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -19,21 +19,52 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) //WIFI SETTINGS if (subPage == SUBPAGE_WIFI) { - char oldSSID[sizeof(clientSSID)]; + unsigned cnt = 0; + for (size_t n = 0; n < WLED_MAX_WIFI_COUNT; n++) { + char cs[4] = "CS"; cs[2] = 48+n; cs[3] = 0; //client SSID + char pw[4] = "PW"; pw[2] = 48+n; pw[3] = 0; //client password + char ip[5] = "IP"; ip[2] = 48+n; ip[4] = 0; //IP address + char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address + char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask + if (request->hasArg(cs)) { + if (n >= multiWiFi.size()) multiWiFi.push_back(WiFiConfig()); // expand vector by one + char oldSSID[33]; strcpy(oldSSID, multiWiFi[n].clientSSID); + char oldPass[65]; strcpy(oldPass, multiWiFi[n].clientPass); - strcpy(oldSSID, clientSSID); - strlcpy(clientSSID,request->arg(F("CS")).c_str(), 33); - if (!strcmp(oldSSID, clientSSID)) forceReconnect = true; + strlcpy(multiWiFi[n].clientSSID, request->arg(cs).c_str(), 33); + if (strlen(oldSSID) == 0 || !strncmp(multiWiFi[n].clientSSID, oldSSID, 32)) { + forceReconnect = true; + } + if (!isAsterisksOnly(request->arg(pw).c_str(), 65)) { + strlcpy(multiWiFi[n].clientPass, request->arg(pw).c_str(), 65); + forceReconnect = true; + } + for (size_t i = 0; i < 4; i++) { + ip[3] = 48+i; + gw[3] = 48+i; + sn[3] = 48+i; + multiWiFi[n].staticIP[i] = request->arg(ip).toInt(); + multiWiFi[n].staticGW[i] = request->arg(gw).toInt(); + multiWiFi[n].staticSN[i] = request->arg(sn).toInt(); + } + cnt++; + } + } + // remove unused + if (cnt < multiWiFi.size()) { + cnt = multiWiFi.size() - cnt; + while (cnt--) multiWiFi.pop_back(); + multiWiFi.shrink_to_fit(); // release memory + } - if (!isAsterisksOnly(request->arg(F("CP")).c_str(), 65)) { - strlcpy(clientPass, request->arg(F("CP")).c_str(), 65); - forceReconnect = true; + if (request->hasArg(F("D0"))) { + dnsAddress = IPAddress(request->arg(F("D0")).toInt(),request->arg(F("D1")).toInt(),request->arg(F("D2")).toInt(),request->arg(F("D3")).toInt()); } strlcpy(cmDNS, request->arg(F("CM")).c_str(), 33); apBehavior = request->arg(F("AB")).toInt(); - strcpy(oldSSID, apSSID); + char oldSSID[33]; strcpy(oldSSID, apSSID); strlcpy(apSSID, request->arg(F("AS")).c_str(), 33); if (!strcmp(oldSSID, apSSID) && apActive) forceReconnect = true; apHide = request->hasArg(F("AH")); @@ -61,21 +92,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) ethernetType = request->arg(F("ETH")).toInt(); WLED::instance().initEthernet(); #endif - - char k[3]; k[2] = 0; - for (int i = 0; i<4; i++) - { - k[1] = i+48;//ascii 0,1,2,3 - - k[0] = 'I'; //static IP - staticIP[i] = request->arg(k).toInt(); - - k[0] = 'G'; //gateway - staticGateway[i] = request->arg(k).toInt(); - - k[0] = 'S'; //subnet - staticSubnet[i] = request->arg(k).toInt(); - } } //LED SETTINGS @@ -86,9 +102,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) if (rlyPin>=0 && pinManager.isPinAllocated(rlyPin, PinOwner::Relay)) { pinManager.deallocatePin(rlyPin, PinOwner::Relay); } + #ifndef WLED_DISABLE_INFRARED if (irPin>=0 && pinManager.isPinAllocated(irPin, PinOwner::IR)) { pinManager.deallocatePin(irPin, PinOwner::IR); } + #endif for (uint8_t s=0; s=0 && pinManager.isPinAllocated(btnPin[s], PinOwner::Button)) { pinManager.deallocatePin(btnPin[s], PinOwner::Button); @@ -149,12 +167,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) uint16_t freq = request->arg(sp).toInt(); if (IS_PWM(type)) { switch (freq) { - case 0 : freq = WLED_PWM_FREQ/3; break; - case 1 : freq = WLED_PWM_FREQ/2; break; + case 0 : freq = WLED_PWM_FREQ/2; break; + case 1 : freq = WLED_PWM_FREQ*2/3; break; default: - case 2 : freq = WLED_PWM_FREQ; break; - case 3 : freq = WLED_PWM_FREQ*4/3; break; - case 4 : freq = WLED_PWM_FREQ*2; break; + case 2 : freq = WLED_PWM_FREQ; break; + case 3 : freq = WLED_PWM_FREQ*2; break; + case 4 : freq = WLED_PWM_FREQ*10/3; break; // uint16_t max (19531 * 3.333) } } else if (IS_DIGITAL(type) && IS_2PIN(type)) { switch (freq) { @@ -201,7 +219,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } BusManager::updateColorOrderMap(com); - // upate other pins + // update other pins + #ifndef WLED_DISABLE_INFRARED int hw_ir_pin = request->arg(F("IR")).toInt(); if (pinManager.allocatePin(hw_ir_pin,false, PinOwner::IR)) { irPin = hw_ir_pin; @@ -209,6 +228,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) irPin = -1; } irEnabled = request->arg(F("IT")).toInt(); + #endif irApplyToAllSelected = !request->hasArg(F("MSO")); int hw_rly_pin = request->arg(F("RL")).toInt(); @@ -232,14 +252,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) if (((buttonType[i] == BTN_TYPE_ANALOG) || (buttonType[i] == BTN_TYPE_ANALOG_INVERTED)) && (digitalPinToAnalogChannel(btnPin[i]) < 0)) { // not an ADC analog pin - DEBUG_PRINTF("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n", btnPin[i], i); + DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[i], i); btnPin[i] = -1; pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); } else if ((buttonType[i] == BTN_TYPE_TOUCH || buttonType[i] == BTN_TYPE_TOUCH_SWITCH) && digitalPinToTouchChannel(btnPin[i]) < 0) { // not a touch pin - DEBUG_PRINTF("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n", btnPin[i], i); + DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), btnPin[i], i); btnPin[i] = -1; pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); } @@ -286,6 +306,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) strip.paletteFade = request->hasArg(F("PF")); t = request->arg(F("TP")).toInt(); randomPaletteChangeTime = MIN(255,MAX(1,t)); + useHarmonicRandomPalette = request->hasArg(F("TH")); nightlightTargetBri = request->arg(F("TB")).toInt(); t = request->arg(F("TL")).toInt(); @@ -426,7 +447,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) //start ntp if not already connected if (ntpEnabled && WLED_CONNECTED && !ntpConnected) ntpConnected = ntpUdp.begin(ntpLocalPort); - ntpLastSyncTime = 0; // force new NTP query + ntpLastSyncTime = NTP_NEVER; // force new NTP query longitude = request->arg(F("LN")).toFloat(); latitude = request->arg(F("LT")).toFloat(); @@ -440,6 +461,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) analogClock12pixel = request->arg(F("OM")).toInt(); analogClock5MinuteMarks = request->hasArg(F("O5")); analogClockSecondsTrail = request->hasArg(F("OS")); + analogClockSolidBlack = request->hasArg(F("OB")); countdownMode = request->hasArg(F("CE")); countdownYear = request->arg(F("CY")).toInt(); @@ -651,7 +673,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) // if the resulting name still contains ":" this means nested object JsonObject subObj; int umSubObj = name.indexOf(":"); - DEBUG_PRINTF("(%d):",umSubObj); + DEBUG_PRINTF_P(PSTR("(%d):"),umSubObj); if (umSubObj>0) { subObj = mod[name.substring(0,umSubObj)]; if (subObj.isNull()) @@ -676,16 +698,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) else subObj[name].add(value.toInt()); // we may have an int j++; } - DEBUG_PRINT("["); - DEBUG_PRINT(j); - DEBUG_PRINT("] = "); - DEBUG_PRINTLN(value); + DEBUG_PRINT(F("[")); DEBUG_PRINT(j); DEBUG_PRINT(F("] = ")); DEBUG_PRINTLN(value); } else { // we are using a hidden field with the same name as our parameter (!before the actual parameter!) - // to describe the type of parameter (text,float,int), for boolean patameters the first field contains "off" + // to describe the type of parameter (text,float,int), for boolean parameters the first field contains "off" // so checkboxes have one or two fields (first is always "false", existence of second depends on checkmark and may be "true") if (subObj[name].isNull()) { - // the first occurence of the field describes the parameter type (used in next loop) + // the first occurrence of the field describes the parameter type (used in next loop) if (value == "false") subObj[name] = false; // checkboxes may have only one field else subObj[name] = value; } else { @@ -698,8 +717,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } else if (type == "int") subObj[name] = value.toInt(); else subObj[name] = value; // text fields } - DEBUG_PRINT(" = "); - DEBUG_PRINTLN(value); + DEBUG_PRINT(F(" = ")); DEBUG_PRINTLN(value); } } usermods.readFromConfig(um); // force change of usermod parameters diff --git a/wled00/src/dependencies/dmx/ESPDMX.cpp b/wled00/src/dependencies/dmx/ESPDMX.cpp index 9f7c6e56c..a80cad71c 100644 --- a/wled00/src/dependencies/dmx/ESPDMX.cpp +++ b/wled00/src/dependencies/dmx/ESPDMX.cpp @@ -11,7 +11,7 @@ // - - - - - /* ----- LIBRARIES ----- */ -#ifdef ESP8266 +#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) #include @@ -28,10 +28,10 @@ #define BREAKFORMAT SERIAL_8N1 bool dmxStarted = false; -int sendPin = 2; //dafault on ESP8266 +int sendPin = 2; //default on ESP8266 -//DMX value array and size. Entry 0 will hold startbyte -uint8_t dmxDataStore[dmxMaxChannel] = {}; +//DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements +uint8_t dmxDataStore[dmxMaxChannel+1] = {}; int channelSize; @@ -106,4 +106,4 @@ void DMXESPSerial::update() { // Function to update the DMX bus -#endif \ No newline at end of file +#endif diff --git a/wled00/src/dependencies/time/DS1307RTC.cpp b/wled00/src/dependencies/time/DS1307RTC.cpp index 95518ad01..223556b4e 100644 --- a/wled00/src/dependencies/time/DS1307RTC.cpp +++ b/wled00/src/dependencies/time/DS1307RTC.cpp @@ -34,7 +34,7 @@ #define DS1307_CTRL_ID 0x68 // PUBLIC FUNCTIONS -time_t DS1307RTC::get() // Aquire data from buffer and convert to time_t +time_t DS1307RTC::get() // Acquire data from buffer and convert to time_t { tmElements_t tm; if (read(tm) == false) return 0; @@ -48,7 +48,7 @@ bool DS1307RTC::set(time_t t) return write(tm); } -// Aquire data from the RTC chip in BCD format +// Acquire data from the RTC chip in BCD format bool DS1307RTC::read(tmElements_t &tm) { uint8_t sec; diff --git a/wled00/src/dependencies/time/Time.cpp b/wled00/src/dependencies/time/Time.cpp index 21f2e989e..2dadb90c2 100644 --- a/wled00/src/dependencies/time/Time.cpp +++ b/wled00/src/dependencies/time/Time.cpp @@ -101,9 +101,9 @@ int year(time_t t) { // the year for the given time /*============================================================================*/ /* functions to convert to and from system time */ -/* These are for interfacing with time serivces and are not normally needed in a sketch */ +/* These are for interfacing with time services and are not normally needed in a sketch */ -// leap year calulator expects year argument as years offset from 1970 +// leap year calculator expects year argument as years offset from 1970 #define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 @@ -193,7 +193,7 @@ time_t makeTime(tmElements_t &tm){ } time_t getUnixTime(int hr,int min,int sec,int dy, int mnth, int yr){ - // year can be given as full four digit year or two digts (2010 or 10 for 2010); + // year can be given as full four digit year or two digits (2010 or 10 for 2010); //it is converted to years since 1970 if( yr > 99) yr = yr - 1970; diff --git a/wled00/src/dependencies/timezone/Timezone.cpp b/wled00/src/dependencies/timezone/Timezone.cpp index 1606fd8fc..b114e3917 100644 --- a/wled00/src/dependencies/timezone/Timezone.cpp +++ b/wled00/src/dependencies/timezone/Timezone.cpp @@ -11,7 +11,7 @@ #include "Timezone.h" -//THIS LINE WAS ADDED FOR COMPATIBILY WITH THE WLED DEPENDENCY STRUCTURE. REMOVE IF YOU USE IT OUTSIDE OF WLED! +//THIS LINE WAS ADDED FOR COMPATIBILITY WITH THE WLED DEPENDENCY STRUCTURE. REMOVE IF YOU USE IT OUTSIDE OF WLED! #include "../time/TimeLib.h" #ifdef __AVR__ diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 65c35882b..100ace166 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -13,7 +13,7 @@ typedef struct PartialEspNowPacket { uint8_t magic; uint8_t packet; - uint8_t segs; + uint8_t noOfPackets; uint8_t data[247]; } partial_packet_t; @@ -56,7 +56,7 @@ void notify(byte callMode, bool followUp) //3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette //6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet //9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet 11: per segment options, variable packet length (40+MAX_NUM_SEGMENTS*3) - //12: enhanced effct sliders, 2D & mapping options + //12: enhanced effect sliders, 2D & mapping options udpOut[11] = 12; col = mainseg.colors[1]; udpOut[12] = R(col); @@ -151,33 +151,45 @@ void notify(byte callMode, bool followUp) #ifndef WLED_DISABLE_ESPNOW if (enableESPNow && useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) { - partial_packet_t buffer = {'W', 0, (uint8_t)s, {0}}; + partial_packet_t buffer = {'W', 0, 1, {0}}; // send global data - DEBUG_PRINTLN(F("ESP-NOW sending first packet.")); - memcpy(buffer.data, udpOut, 41); - auto err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), 41+3); - if (!err) { - // send segment data + DEBUG_PRINTLN(F("ESP-NOW sending first packet.")); + const size_t bufferSize = sizeof(buffer.data)/sizeof(uint8_t); + size_t packetSize = 41; + size_t s0 = 0; + memcpy(buffer.data, udpOut, packetSize); + // stuff as many segments in first packet as possible (normally up to 5) + for (size_t i = 0; packetSize < bufferSize && i < s; i++) { + memcpy(buffer.data + packetSize, &udpOut[41+i*UDP_SEG_SIZE], UDP_SEG_SIZE); + packetSize += UDP_SEG_SIZE; + s0++; + } + if (s > s0) buffer.noOfPackets += 1 + ((s - s0) * UDP_SEG_SIZE) / bufferSize; // set number of packets + auto err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize+3); + if (!err && s0 < s) { + // send rest of the segments buffer.packet++; - size_t packetSize = 0; - int32_t err = 0; - for (size_t i = 0; i < s; i++) { + packetSize = 0; + // WARNING: this will only work for up to 3 messages (~17 segments) as QuickESPNOW only has a ring buffer capable of holding 3 queued messages + // to work around that limitation it is mandatory to utilize onDataSent() callback which should reduce number queued messages + // and wait until at least one space is available in the buffer + for (size_t i = s0; i < s; i++) { memcpy(buffer.data + packetSize, &udpOut[41+i*UDP_SEG_SIZE], UDP_SEG_SIZE); packetSize += UDP_SEG_SIZE; - if (packetSize + UDP_SEG_SIZE < sizeof(buffer.data)/sizeof(uint8_t)) continue; - DEBUG_PRINTF("ESP-NOW sending packet: %d (%d)\n", (int)buffer.packet, packetSize+3); + if (packetSize + UDP_SEG_SIZE < bufferSize) continue; + DEBUG_PRINTF_P(PSTR("ESP-NOW sending packet: %d (%d)\n"), (int)buffer.packet, packetSize+3); err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize+3); buffer.packet++; packetSize = 0; if (err) break; } if (!err && packetSize > 0) { - DEBUG_PRINTF("ESP-NOW sending last packet: %d (%d)\n", (int)buffer.packet, packetSize+3); + DEBUG_PRINTF_P(PSTR("ESP-NOW sending last packet: %d (%d)\n"), (int)buffer.packet, packetSize+3); err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize+3); } - if (err) { - DEBUG_PRINTLN(F("ESP-NOW sending packet failed.")); - } + } + if (err) { + DEBUG_PRINTLN(F("ESP-NOW sending packet failed.")); } } if (udpConnected) @@ -286,7 +298,7 @@ void parseNotifyPacket(uint8_t *udpIn) { uint16_t stopY = version > 11 ? (udpIn[34+ofs] << 8 | udpIn[35+ofs]) : 1; uint16_t offset = (udpIn[7+ofs] << 8 | udpIn[8+ofs]); if (!receiveSegmentOptions) { - DEBUG_PRINTF("Set segment w/o options: %d [%d,%d;%d,%d]\n", id, (int)start, (int)stop, (int)startY, (int)stopY); + DEBUG_PRINTF_P(PSTR("Set segment w/o options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" selseg.setUp(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY); strip.resume(); @@ -325,12 +337,12 @@ void parseNotifyPacket(uint8_t *udpIn) { } } if (receiveSegmentBounds) { - DEBUG_PRINTF("Set segment w/ options: %d [%d,%d;%d,%d]\n", id, (int)start, (int)stop, (int)startY, (int)stopY); + DEBUG_PRINTF_P(PSTR("Set segment w/ options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" selseg.setUp(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY); strip.resume(); } else { - DEBUG_PRINTF("Set segment grouping: %d [%d,%d]\n", id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]); + DEBUG_PRINTF_P(PSTR("Set segment grouping: %d [%d,%d]\n"), id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]); strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" selseg.setUp(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY); strip.resume(); @@ -942,13 +954,18 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8 } #ifndef WLED_DISABLE_ESPNOW +// ESP-NOW message sent callback function +void espNowSentCB(uint8_t* address, uint8_t status) { + DEBUG_PRINTF_P(PSTR("Message sent to " MACSTR ", status: %d\n"), MAC2STR(address), status); +} + // ESP-NOW message receive callback function void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { sprintf_P(last_signal_src, PSTR("%02x%02x%02x%02x%02x%02x"), address[0], address[1], address[2], address[3], address[4], address[5]); #ifdef WLED_DEBUG DEBUG_PRINT(F("ESP-NOW: ")); DEBUG_PRINT(last_signal_src); DEBUG_PRINT(F(" -> ")); DEBUG_PRINTLN(len); - for (int i=0; ipacket == 0) { - if (udpIn == nullptr) udpIn = (uint8_t *)malloc(WLEDPACKETSIZE); // we cannot use stack as we are in callback - DEBUG_PRINTLN(F("ESP-NOW inited UDP buffer.")); - memcpy(udpIn, buffer->data, len-3); // global data (41 bytes) - packetsReceived |= 0x01 << buffer->packet; - segsReceived = 0; - return; - } else if (((len-3)/UDP_SEG_SIZE)*UDP_SEG_SIZE != (len-3)) { - DEBUG_PRINTF("ESP-NOW incorrect packet size: %d (%d) [%d]\n", (int)buffer->packet, (int)len-3, (int)UDP_SEG_SIZE); + packetsReceived = 0; // it will increment later (this is to make sure we start counting packets correctly) + if (udpIn == nullptr) { + udpIn = (uint8_t *)malloc(WLEDPACKETSIZE); // we cannot use stack as we are in callback + if (!udpIn) return; // memory alocation failed + DEBUG_PRINTLN(F("ESP-NOW inited UDP buffer.")); + } + memcpy(udpIn, buffer->data, len-3); // global data (41 bytes + up to 5 segments) + segsReceived = (len - 3 - 41) / UDP_SEG_SIZE; + } else if (buffer->packet == packetsReceived && udpIn && ((len - 3) / UDP_SEG_SIZE) * UDP_SEG_SIZE == (len-3)) { + // we received a packet full of segments + if (segsReceived >= MAX_NUM_SEGMENTS) { + // we are already past max segments, just ignore + DEBUG_PRINTLN(F("ESP-NOW received segments past maximum.")); + len = 3; + } else if ((segsReceived + ((len - 3) / UDP_SEG_SIZE)) >= MAX_NUM_SEGMENTS) { + len = ((MAX_NUM_SEGMENTS - segsReceived) * UDP_SEG_SIZE) + 3; // we have reached max number of segments + } + if (len > 3) { + memcpy(udpIn + 41 + (segsReceived * UDP_SEG_SIZE), buffer->data, len-3); + segsReceived += (len - 3) / UDP_SEG_SIZE; + } + } else { + // any out of order packet or incorrectly sized packet or if we have no UDP buffer will abort + DEBUG_PRINTF_P(PSTR("ESP-NOW incorrect packet: %d (%d) [%d]\n"), (int)buffer->packet, (int)len-3, (int)UDP_SEG_SIZE); if (udpIn) free(udpIn); udpIn = nullptr; packetsReceived = 0; @@ -991,15 +1024,9 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs } if (!udpIn) return; - // TODO add verification if segsReceived > MAX_NUM_SEGMENTS or WLEDPACKETSIZE - - memcpy(udpIn+41+segsReceived, buffer->data, len-3); - packetsReceived |= 0x01 << buffer->packet; - segsReceived += (len-3)/UDP_SEG_SIZE; - - DEBUG_PRINTF("ESP-NOW packet received: %d (%d) [%d]\n", (int)buffer->packet, (int)len-3, (int)segsReceived); - - if (segsReceived == buffer->segs) { + packetsReceived++; + DEBUG_PRINTF_P(PSTR("ESP-NOW packet received: %d (%d/%d) s:[%d/%d]\n"), (int)buffer->packet, (int)packetsReceived, (int)buffer->noOfPackets, (int)segsReceived, MAX_NUM_SEGMENTS); + if (packetsReceived >= buffer->noOfPackets) { // last packet received if (millis() - lastProcessed > 250) { DEBUG_PRINTLN(F("ESP-NOW processing complete message.")); diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 2e176bc30..e744651f6 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -19,7 +19,7 @@ bool UsermodManager::handleButton(uint8_t b) { bool UsermodManager::getUMData(um_data_t **data, uint8_t mod_id) { for (byte i = 0; i < numMods; i++) { if (mod_id > 0 && ums[i]->getId() != mod_id) continue; // only get data form requested usermod if provided - if (ums[i]->getUMData(data)) return true; // if usermod does provide data return immediately (only one usermod can povide data at one time) + if (ums[i]->getUMData(data)) return true; // if usermod does provide data return immediately (only one usermod can provide data at one time) } return false; } diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 6268aa983..bcfb41fa3 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -44,10 +44,6 @@ #include "../usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h" #endif -#ifdef USERMOD_MODE_SORT - #include "../usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h" -#endif - #ifdef USERMOD_BH1750 #include "../usermods/BH1750_v2/usermod_BH1750.h" #endif @@ -58,19 +54,11 @@ #endif #ifdef USERMOD_FOUR_LINE_DISPLAY - #ifdef USE_ALT_DISPlAY - #include "../usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h" - #else - #include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h" - #endif + #include "../usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h" #endif #ifdef USERMOD_ROTARY_ENCODER_UI - #ifdef USE_ALT_DISPlAY - #include "../usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h" - #else - #include "../usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h" - #endif + #include "../usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h" #endif #ifdef USERMOD_AUTO_SAVE @@ -170,7 +158,7 @@ #endif #ifdef USERMOD_KLIPPER_PERCENTAGE - #include "..\usermods\usermod_v2_klipper_percentage\usermod_v2_klipper_percentage.h" + #include "../usermods/usermod_v2_klipper_percentage/usermod_v2_klipper_percentage.h" #endif #ifdef USERMOD_BOBLIGHT @@ -178,7 +166,7 @@ #endif #ifdef USERMOD_ANIMARTRIX -#include "../usermods/usermod_v2_animartrix/usermod_v2_animartrix.h" + #include "../usermods/usermod_v2_animartrix/usermod_v2_animartrix.h" #endif #ifdef USERMOD_INTERNAL_TEMPERATURE @@ -198,15 +186,27 @@ #endif #ifdef USERMOD_PWM_OUTPUTS -#include "../usermods/pwm_outputs/usermod_pwm_outputs.h" + #include "../usermods/pwm_outputs/usermod_pwm_outputs.h" +#endif + +#ifdef USERMOD_HTTP_PULL_LIGHT_CONTROL + #include "../usermods/usermod_v2_HttpPullLightControl/usermod_v2_HttpPullLightControl.h" #endif #ifdef USERMOD_MPU6050_IMU -#include "../usermods/mpu6050_imu/usermod_mpu6050_imu.h" + #include "../usermods/mpu6050_imu/usermod_mpu6050_imu.h" #endif #ifdef USERMOD_MPU6050_IMU -#include "../usermods/mpu6050_imu/usermod_gyro_surge.h" + #include "../usermods/mpu6050_imu/usermod_gyro_surge.h" +#endif + +#ifdef USERMOD_LDR_DUSK_DAWN + #include "../usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h" +#endif + +#ifdef USERMOD_STAIRCASE_WIPE + #include "../usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h" #endif void registerUsermods() @@ -254,10 +254,6 @@ void registerUsermods() usermods.add(new PIRsensorSwitch()); #endif - #ifdef USERMOD_MODE_SORT - usermods.add(new ModeSortUsermod()); - #endif - #ifdef USERMOD_FOUR_LINE_DISPLAY usermods.add(new FourLineDisplayUsermod()); #endif @@ -390,6 +386,10 @@ void registerUsermods() usermods.add(new InternalTemperatureUsermod()); #endif + #ifdef USERMOD_HTTP_PULL_LIGHT_CONTROL + usermods.add(new HttpPullLightControl()); + #endif + #ifdef USERMOD_MPU6050_IMU static MPU6050Driver mpu6050; usermods.add(&mpu6050); #endif @@ -397,4 +397,12 @@ void registerUsermods() #ifdef USERMOD_GYRO_SURGE static GyroSurge gyro_surge; usermods.add(&gyro_surge); #endif + + #ifdef USERMOD_LDR_DUSK_DAWN + usermods.add(new LDR_Dusk_Dawn_v2()); + #endif + + #ifdef USERMOD_STAIRCASE_WIPE + usermods.add(new StairwayWipeUsermod()); + #endif } diff --git a/wled00/util.cpp b/wled00/util.cpp index d6fa9fb18..fa6c8faff 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -157,9 +157,9 @@ bool oappendi(int i) bool oappend(const char* txt) { uint16_t len = strlen(txt); - if (olen + len >= SETTINGS_STACK_BUF_SIZE) { + if ((obuf == nullptr) || (olen + len >= SETTINGS_STACK_BUF_SIZE)) { // sanity checks #ifdef WLED_DEBUG - DEBUG_PRINT(F("oappend() buffer overflow. Cannnot append ")); + DEBUG_PRINT(F("oappend() buffer overflow. Cannot append ")); DEBUG_PRINT(len); DEBUG_PRINT(F(" bytes \t\"")); DEBUG_PRINT(txt); DEBUG_PRINTLN(F("\"")); #endif @@ -173,7 +173,7 @@ bool oappend(const char* txt) void prepareHostname(char* hostname) { - sprintf_P(hostname, "wled-%*s", 6, escapedMac.c_str() + 6); + sprintf_P(hostname, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); const char *pC = serverDescription; uint8_t pos = 5; // keep "wled-" while (*pC && pos < 24) { // while !null and not over length @@ -215,10 +215,10 @@ bool requestJSONBufferLock(uint8_t module) } unsigned long now = millis(); - while (jsonBufferLock && millis()-now < 1000) delay(1); // wait for a second for buffer lock + while (jsonBufferLock && millis()-now < 100) delay(1); // wait for fraction for buffer lock - if (millis()-now >= 1000) { - DEBUG_PRINT(F("ERROR: Locking JSON buffer failed! (")); + if (jsonBufferLock) { + DEBUG_PRINT(F("ERROR: Locking JSON buffer failed! (still locked by ")); DEBUG_PRINT(jsonBufferLock); DEBUG_PRINTLN(")"); return false; // waiting time-outed @@ -245,7 +245,7 @@ void releaseJSONBufferLock() // extracts effect mode (or palette) name from names serialized string -// caller must provide large enough buffer for name (incluing SR extensions)! +// caller must provide large enough buffer for name (including SR extensions)! uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen) { if (src == JSON_mode_names || src == nullptr) { @@ -539,13 +539,13 @@ um_data_t* simulateSound(uint8_t simulationId) return um_data; } - +static const char s_ledmap_tmpl[] PROGMEM = "ledmap%d.json"; // enumerate all ledmapX.json files on FS and extract ledmap names if existing void enumerateLedmaps() { ledMaps = 1; for (size_t i=1; i 2) DEBUG_PRINTF("Loop delayed more than %ums.\n", loopDelay); + if (loopDelay > 2) DEBUG_PRINTF_P(PSTR("Loop delayed more than %ums.\n"), loopDelay); static unsigned long maxLoopMillis = 0; static size_t avgLoopMillis = 0; static unsigned long maxUsermodMillis = 0; @@ -47,12 +49,12 @@ void WLED::loop() static unsigned long maxStripMillis = 0; static size_t avgStripMillis = 0; unsigned long stripMillis; - #endif +#endif handleTime(); - #ifndef WLED_DISABLE_INFRARED +#ifndef WLED_DISABLE_INFRARED handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too - #endif +#endif handleConnection(); handleSerial(); handleImprovWifiScan(); @@ -130,7 +132,7 @@ void WLED::loop() if (lastMqttReconnectAttempt > millis()) { rolloverMillis++; lastMqttReconnectAttempt = 0; - ntpLastSyncTime = 0; + ntpLastSyncTime = NTP_NEVER; // force new NTP query strip.restartRuntime(); } if (millis() - lastMqttReconnectAttempt > 30000 || lastMqttReconnectAttempt == 0) { // lastMqttReconnectAttempt==0 forces immediate broadcast @@ -151,6 +153,21 @@ void WLED::loop() createEditHandler(false); } + // reconnect WiFi to clear stale allocations if heap gets too low + if (millis() - heapTime > 15000) { + uint32_t heap = ESP.getFreeHeap(); + if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) { + DEBUG_PRINT(F("Heap too low! ")); DEBUG_PRINTLN(heap); + forceReconnect = true; + strip.resetSegments(); // remove all but one segments from memory + } else if (heap < MIN_HEAP_SIZE) { + DEBUG_PRINTLN(F("Heap low, purging segments.")); + strip.purgeSegments(); + } + lastHeap = heap; + heapTime = millis(); + } + //LED settings have been saved, re-init busses //This code block causes severe FPS drop on ESP32 with the original "if (busConfigs[0] != nullptr)" conditional. Investigate! if (doInitBusses) { @@ -212,9 +229,9 @@ void WLED::loop() #ifdef WLED_DEBUG loopMillis = millis() - loopMillis; if (loopMillis > 30) { - DEBUG_PRINTF("Loop took %lums.\n", loopMillis); - DEBUG_PRINTF("Usermods took %lums.\n", usermodMillis); - DEBUG_PRINTF("Strip took %lums.\n", stripMillis); + DEBUG_PRINTF_P(PSTR("Loop took %lums.\n"), loopMillis); + DEBUG_PRINTF_P(PSTR("Usermods took %lums.\n"), usermodMillis); + DEBUG_PRINTF_P(PSTR("Strip took %lums.\n"), stripMillis); } avgLoopMillis += loopMillis; if (loopMillis > maxLoopMillis) maxLoopMillis = loopMillis; @@ -230,6 +247,9 @@ void WLED::loop() } #endif DEBUG_PRINT(F("Wifi state: ")); DEBUG_PRINTLN(WiFi.status()); + #ifndef WLED_DISABLE_ESPNOW + DEBUG_PRINT(F("ESP-NOW state: ")); DEBUG_PRINTLN(statusESPNow); + #endif if (WiFi.status() != lastWifiState) { wifiStateChangedTime = millis(); @@ -308,15 +328,15 @@ void WLED::setup() DEBUG_PRINTLN(); DEBUG_PRINT(F("---WLED ")); DEBUG_PRINT(versionString); - DEBUG_PRINT(" "); + DEBUG_PRINT(F(" ")); DEBUG_PRINT(VERSION); DEBUG_PRINTLN(F(" INIT---")); #ifdef ARDUINO_ARCH_ESP32 DEBUG_PRINT(F("esp32 ")); DEBUG_PRINTLN(ESP.getSdkVersion()); #if defined(ESP_ARDUINO_VERSION) - //DEBUG_PRINTF(F("arduino-esp32 0x%06x\n"), ESP_ARDUINO_VERSION); - DEBUG_PRINTF("arduino-esp32 v%d.%d.%d\n", int(ESP_ARDUINO_VERSION_MAJOR), int(ESP_ARDUINO_VERSION_MINOR), int(ESP_ARDUINO_VERSION_PATCH)); // availeable since v2.0.0 + //DEBUG_PRINTF_P(PSTR("arduino-esp32 0x%06x\n"), ESP_ARDUINO_VERSION); + DEBUG_PRINTF_P(PSTR("arduino-esp32 v%d.%d.%d\n"), int(ESP_ARDUINO_VERSION_MAJOR), int(ESP_ARDUINO_VERSION_MINOR), int(ESP_ARDUINO_VERSION_PATCH)); // availeable since v2.0.0 #else DEBUG_PRINTLN(F("arduino-esp32 v1.0.x\n")); // we can't say in more detail. #endif @@ -369,7 +389,7 @@ void WLED::setup() pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); #endif */ - #if defined(WLED_USE_PSRAM) + #if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) pDoc = new PSRAMDynamicJsonDocument(2*JSON_BUFFER_SIZE); if (!pDoc) pDoc = new PSRAMDynamicJsonDocument(JSON_BUFFER_SIZE); // falback if double sized buffer could not be allocated // if the above still fails requestJsonBufferLock() will always return false preventing crashes @@ -382,11 +402,16 @@ void WLED::setup() DEBUG_PRINTLN(F("PSRAM not used.")); #endif #endif +#if defined(ARDUINO_ESP32_PICO) + // special handling for PICO-D4: gpio16+17 are in use for onboard SPI FLASH (not PSRAM) + managed_pin_type pins[] = { {16, true}, {17, true} }; + pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); +#endif //DEBUG_PRINT(F("LEDs inited. heap usage ~")); //DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); -#ifdef WLED_DEBUG +#if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) pinManager.allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output #endif #ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin @@ -398,8 +423,6 @@ void WLED::setup() DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); - for (uint8_t i=1; i= 0) { // status contains number of found networks + DEBUG_PRINT(F("WiFi scan completed: ")); DEBUG_PRINTLN(status); + int rssi = -9999; + unsigned selected = selectedWiFi; + for (int o = 0; o < status; o++) { + DEBUG_PRINT(F(" WiFi available: ")); DEBUG_PRINT(WiFi.SSID(o)); + DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(WiFi.RSSI(o)); DEBUG_PRINTLN(F("dB")); + for (unsigned n = 0; n < multiWiFi.size(); n++) + if (!strcmp(WiFi.SSID(o).c_str(), multiWiFi[n].clientSSID)) { + // find the WiFi with the strongest signal (but keep priority of entry if signal difference is not big) + if ((n < selected && WiFi.RSSI(o) > rssi-10) || WiFi.RSSI(o) > rssi) { + rssi = WiFi.RSSI(o); + selected = n; + } + break; + } + } + DEBUG_PRINT(F("Selected: ")); DEBUG_PRINT(multiWiFi[selected].clientSSID); + DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(rssi); DEBUG_PRINTLN(F("dB")); + return selected; + } + //DEBUG_PRINT(F("WiFi scan running.")); + return status; // scan is still running or there was an error } void WLED::initConnection() { + DEBUG_PRINTLN(F("initConnection() called.")); + #ifdef WLED_ENABLE_WEBSOCKETS ws.onEvent(wsEvent); #endif @@ -714,13 +782,13 @@ void WLED::initConnection() } #endif - WiFi.disconnect(true); // close old connections + WiFi.disconnect(true); // close old connections #ifdef ESP8266 WiFi.setPhyMode(force802_3g ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N); #endif - if (staticIP[0] != 0 && staticGateway[0] != 0) { - WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(1, 1, 1, 1)); + if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) { + WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress); } else { WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0)); } @@ -745,13 +813,14 @@ void WLED::initConnection() showWelcomePage = false; DEBUG_PRINT(F("Connecting to ")); - DEBUG_PRINT(clientSSID); - DEBUG_PRINTLN("..."); + DEBUG_PRINT(multiWiFi[selectedWiFi].clientSSID); + DEBUG_PRINTLN(F("...")); // convert the "serverDescription" into a valid DNS hostname (alphanumeric) char hostname[25]; prepareHostname(hostname); - WiFi.begin(clientSSID, clientPass); + WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass); // no harm if called multiple times + #ifdef ARDUINO_ARCH_ESP32 #if defined(LOLIN_WIFI_FIX) && (defined(ARDUINO_ARCH_ESP32C3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32S3)) WiFi.setTxPower(WIFI_POWER_8_5dBm); @@ -766,7 +835,8 @@ void WLED::initConnection() #ifndef WLED_DISABLE_ESPNOW if (enableESPNow) { - quickEspNow.onDataRcvd(espNowReceiveCB); + quickEspNow.onDataSent(espNowSentCB); // see udp.cpp + quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp bool espNowOK; if (apActive) { DEBUG_PRINTLN(F("ESP-NOW initing in AP mode.")); @@ -843,35 +913,26 @@ void WLED::initInterfaces() void WLED::handleConnection() { + static bool scanDone = true; static byte stacO = 0; - static uint32_t lastHeap = UINT32_MAX; - static unsigned long heapTime = 0; unsigned long now = millis(); + const bool wifiConfigured = WLED_WIFI_CONFIGURED; - if (now < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) + // ignore connection handling if WiFi is configured and scan still running + // or within first 2s if WiFi is not configured or AP is always active + if ((wifiConfigured && multiWiFi.size() > 1 && WiFi.scanComplete() < 0) || (now < 2000 && (!wifiConfigured || apBehavior == AP_BEHAVIOR_ALWAYS))) return; - if (lastReconnectAttempt == 0) { - DEBUG_PRINTLN(F("lastReconnectAttempt == 0")); + if (lastReconnectAttempt == 0 || forceReconnect) { + DEBUG_PRINTLN(F("Initial connect or forced reconnect.")); + selectedWiFi = findWiFi(); // find strongest WiFi initConnection(); + interfacesInited = false; + forceReconnect = false; + wasConnected = false; return; } - // reconnect WiFi to clear stale allocations if heap gets too low - if (now - heapTime > 5000) { - uint32_t heap = ESP.getFreeHeap(); - if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) { - DEBUG_PRINT(F("Heap too low! ")); - DEBUG_PRINTLN(heap); - forceReconnect = true; - strip.resetSegments(); - } else if (heap < MIN_HEAP_SIZE) { - strip.purgeSegments(); - } - lastHeap = heap; - heapTime = now; - } - byte stac = 0; if (apActive) { #ifdef ESP8266 @@ -885,7 +946,7 @@ void WLED::handleConnection() stacO = stac; DEBUG_PRINT(F("Connected AP clients: ")); DEBUG_PRINTLN(stac); - if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { // trying to connect, but not connected + if (!WLED_CONNECTED && wifiConfigured) { // trying to connect, but not connected if (stac) WiFi.disconnect(); // disable search so that AP can work else @@ -893,36 +954,49 @@ void WLED::handleConnection() } } } - if (forceReconnect) { - DEBUG_PRINTLN(F("Forcing reconnect.")); - initConnection(); - interfacesInited = false; - forceReconnect = false; - wasConnected = false; - return; - } + if (!Network.isConnected()) { if (interfacesInited) { + if (scanDone && multiWiFi.size() > 1) { + DEBUG_PRINTLN(F("WiFi scan initiated on disconnect.")); + findWiFi(true); // reinit scan + scanDone = false; + return; // try to connect in next iteration + } DEBUG_PRINTLN(F("Disconnected!")); + selectedWiFi = findWiFi(); initConnection(); interfacesInited = false; + scanDone = true; } //send improv failed 6 seconds after second init attempt (24 sec. after provisioning) if (improvActive > 2 && now - lastReconnectAttempt > 6000) { sendImprovStateResponse(0x03, true); improvActive = 2; } - if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && WLED_WIFI_CONFIGURED) { + if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && wifiConfigured) { if (improvActive == 2) improvActive = 3; DEBUG_PRINTLN(F("Last reconnect too old.")); + if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list initConnection(); } if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { - DEBUG_PRINTLN(F("Not connected AP.")); - initAP(); + if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) { + DEBUG_PRINTLN(F("Not connected AP.")); + initAP(); // start AP only within first 5min + } + } + if (apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT && stac == 0) { // disconnect AP after 5min if no clients connected + // if AP was enabled more than 10min after boot or if client was connected more than 10min after boot do not disconnect AP mode + if (now < 2*WLED_AP_TIMEOUT) { + dnsServer.stop(); + WiFi.softAPdisconnect(true); + apActive = false; + DEBUG_PRINTLN(F("Temporary AP disabled.")); + } } } else if (!interfacesInited) { //newly connected - DEBUG_PRINTLN(""); + DEBUG_PRINTLN(); DEBUG_PRINT(F("Connected! IP address: ")); DEBUG_PRINTLN(Network.localIP()); if (improvActive) { @@ -940,7 +1014,7 @@ void WLED::handleConnection() dnsServer.stop(); WiFi.softAPdisconnect(true); apActive = false; - DEBUG_PRINTLN(F("Access point disabled (handle).")); + DEBUG_PRINTLN(F("Access point disabled (connected).")); } } } diff --git a/wled00/wled.h b/wled00/wled.h index ed92c58ae..e22603df3 100755 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,12 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.15.0-a0 + @version 0.15.0-b1 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2401140 +#define VERSION 2403070 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -139,7 +139,7 @@ #endif #ifdef WLED_ENABLE_DMX - #ifdef ESP8266 + #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) #include "src/dependencies/dmx/ESPDMX.h" #else //ESP32 #include "src/dependencies/dmx/SparkFunDMX.h" @@ -158,7 +158,7 @@ // ESP32-WROVER features SPI RAM (aka PSRAM) which can be allocated using ps_malloc() // we can create custom PSRAMDynamicJsonDocument to use such feature (replacing DynamicJsonDocument) // The following is a construct to enable code to compile without it. -// There is a code thet will still not use PSRAM though: +// There is a code that will still not use PSRAM though: // AsyncJsonResponse is a derived class that implements DynamicJsonDocument (AsyncJson-v6.h) #if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) struct PSRAM_Allocator { @@ -179,6 +179,9 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; #define PSRAMDynamicJsonDocument DynamicJsonDocument #endif +#define FASTLED_INTERNAL //remove annoying pragma messages +#define USE_GET_MILLISECOND_TIMER +#include "FastLED.h" #include "const.h" #include "fcn_declare.h" #include "NodeStruct.h" @@ -270,9 +273,10 @@ WLED_GLOBAL char otaPass[33] _INIT(DEFAULT_OTA_PASS); // Hardware and pin config #ifndef BTNPIN -WLED_GLOBAL int8_t btnPin[WLED_MAX_BUTTONS] _INIT({0}); -#else -WLED_GLOBAL int8_t btnPin[WLED_MAX_BUTTONS] _INIT({BTNPIN}); + #define BTNPIN 0,-1 +#endif +#ifndef BTNTYPE + #define BTNTYPE BTN_TYPE_PUSH,BTN_TYPE_NONE #endif #ifndef RLYPIN WLED_GLOBAL int8_t rlyPin _INIT(-1); @@ -286,9 +290,10 @@ WLED_GLOBAL bool rlyMde _INIT(true); WLED_GLOBAL bool rlyMde _INIT(RLYMDE); #endif #ifndef IRPIN -WLED_GLOBAL int8_t irPin _INIT(-1); -#else -WLED_GLOBAL int8_t irPin _INIT(IRPIN); + #define IRPIN -1 +#endif +#ifndef IRTYPE + #define IRTYPE 0 #endif #if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || (defined(RX) && defined(TX)) @@ -306,22 +311,21 @@ WLED_GLOBAL int8_t irPin _INIT(IRPIN); WLED_GLOBAL char ntpServerName[33] _INIT("0.wled.pool.ntp.org"); // NTP server to use // WiFi CONFIG (all these can be changed via web UI, no need to set them here) -WLED_GLOBAL char clientSSID[33] _INIT(CLIENT_SSID); -WLED_GLOBAL char clientPass[65] _INIT(CLIENT_PASS); -WLED_GLOBAL char cmDNS[33] _INIT(MDNS_NAME); // mDNS address (*.local, replaced by wledXXXXXX if default is used) -WLED_GLOBAL char apSSID[33] _INIT(""); // AP off by default (unless setup) -WLED_GLOBAL byte apChannel _INIT(1); // 2.4GHz WiFi AP channel (1-13) -WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID -WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default -WLED_GLOBAL IPAddress staticIP _INIT_N((( 0, 0, 0, 0))); // static IP of ESP -WLED_GLOBAL IPAddress staticGateway _INIT_N((( 0, 0, 0, 0))); // gateway (router) IP -WLED_GLOBAL IPAddress staticSubnet _INIT_N(((255, 255, 255, 0))); // most common subnet in home networks +WLED_GLOBAL uint8_t selectedWiFi _INIT(0); +WLED_GLOBAL std::vector multiWiFi; +WLED_GLOBAL IPAddress dnsAddress _INIT_N((( 8, 8, 8, 8))); // Google's DNS +WLED_GLOBAL char cmDNS[33] _INIT(MDNS_NAME); // mDNS address (*.local, replaced by wledXXXXXX if default is used) +WLED_GLOBAL char apSSID[33] _INIT(""); // AP off by default (unless setup) +WLED_GLOBAL byte apChannel _INIT(1); // 2.4GHz WiFi AP channel (1-13) +WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID +WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default #ifdef ARDUINO_ARCH_ESP32 -WLED_GLOBAL bool noWifiSleep _INIT(true); // disabling modem sleep modes will increase heat output and power usage, but may help with connection issues +WLED_GLOBAL bool noWifiSleep _INIT(true); // disabling modem sleep modes will increase heat output and power usage, but may help with connection issues #else WLED_GLOBAL bool noWifiSleep _INIT(false); #endif WLED_GLOBAL bool force802_3g _INIT(false); +#define WLED_WIFI_CONFIGURED (strlen(multiWiFi[0].clientSSID) >= 1 && strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) != 0) #ifdef WLED_USE_ETHERNET #ifdef WLED_ETH_DEFAULT // default ethernet board type if specified @@ -374,13 +378,11 @@ WLED_GLOBAL NodesMap Nodes; WLED_GLOBAL bool nodeListEnabled _INIT(true); WLED_GLOBAL bool nodeBroadcastEnabled _INIT(true); -WLED_GLOBAL byte buttonType[WLED_MAX_BUTTONS] _INIT({BTN_TYPE_PUSH}); -#if defined(IRTYPE) && defined(IRPIN) +#ifndef WLED_DISABLE_INFRARED +WLED_GLOBAL int8_t irPin _INIT(IRPIN); WLED_GLOBAL byte irEnabled _INIT(IRTYPE); // Infrared receiver -#else -WLED_GLOBAL byte irEnabled _INIT(0); // Infrared receiver disabled #endif -WLED_GLOBAL bool irApplyToAllSelected _INIT(true); //apply IR to all selected segments +WLED_GLOBAL bool irApplyToAllSelected _INIT(true); //apply IR or ESP-NOW to all selected segments WLED_GLOBAL uint16_t udpPort _INIT(21324); // WLED notifier default port WLED_GLOBAL uint16_t udpPort2 _INIT(65506); // WLED notifier supplemental port @@ -410,14 +412,21 @@ WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black #ifdef WLED_ENABLE_DMX - #ifdef ESP8266 + #if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) WLED_GLOBAL DMXESPSerial dmx; #else //ESP32 WLED_GLOBAL SparkFunDMX dmx; #endif -WLED_GLOBAL uint16_t e131ProxyUniverse _INIT(0); // output this E1.31 (sACN) / ArtNet universe via MAX485 (0 = disabled) + WLED_GLOBAL uint16_t e131ProxyUniverse _INIT(0); // output this E1.31 (sACN) / ArtNet universe via MAX485 (0 = disabled) + // dmx CONFIG + WLED_GLOBAL byte DMXChannels _INIT(7); // number of channels per fixture + WLED_GLOBAL byte DMXFixtureMap[15] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })); + // assigns the different channels to different functions. See wled21_dmx.ino for more information. + WLED_GLOBAL uint16_t DMXGap _INIT(10); // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. + WLED_GLOBAL uint16_t DMXStart _INIT(10); // start address of the first fixture + WLED_GLOBAL uint16_t DMXStartLED _INIT(0); // LED from which DMX fixtures start #endif -WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consecutive universes) WLED_GLOBAL uint16_t e131Port _INIT(5568); // DMX in port. E1.31 default is 5568, Art-Net is 6454 WLED_GLOBAL byte e131Priority _INIT(0); // E1.31 port priority (if != 0 priority handling is active) WLED_GLOBAL E131Priority highPriority _INIT(3); // E1.31 highest priority tracking, init = timeout in seconds @@ -496,6 +505,7 @@ WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(DEFAULT_LED_COUNT - 1); WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your strip where "midnight" would be WLED_GLOBAL bool analogClockSecondsTrail _INIT(false); // Display seconds as trail of LEDs instead of a single pixel WLED_GLOBAL bool analogClock5MinuteMarks _INIT(false); // Light pixels at every 5-minute position +WLED_GLOBAL bool analogClockSolidBlack _INIT(false); // Show clock overlay only if all LEDs are solid black (effect is 0 and color is black) WLED_GLOBAL bool countdownMode _INIT(false); // Clock will count down towards date WLED_GLOBAL byte countdownYear _INIT(20), countdownMonth _INIT(1); // Countdown target date, year is last two digits @@ -519,16 +529,6 @@ WLED_GLOBAL unsigned long lastEditTime _INIT(0); WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use in usermod -#ifdef WLED_ENABLE_DMX - // dmx CONFIG - WLED_GLOBAL byte DMXChannels _INIT(7); // number of channels per fixture - WLED_GLOBAL byte DMXFixtureMap[15] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })); - // assigns the different channels to different functions. See wled21_dmx.ino for more information. - WLED_GLOBAL uint16_t DMXGap _INIT(10); // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. - WLED_GLOBAL uint16_t DMXStart _INIT(10); // start address of the first fixture - WLED_GLOBAL uint16_t DMXStartLED _INIT(0); // LED from which DMX fixtures start -#endif - // internal global variable declarations // wifi WLED_GLOBAL bool apActive _INIT(false); @@ -541,15 +541,16 @@ WLED_GLOBAL bool wasConnected _INIT(false); WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same // transitions -WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color -WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending -WLED_GLOBAL bool transitionActive _INIT(false); -WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration -WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json) +WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color +WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending +WLED_GLOBAL bool transitionActive _INIT(false); +WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration +WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json) WLED_GLOBAL unsigned long transitionStartTime; -WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f -WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt") -WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s) +WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f +WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt") +WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s) +WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random // nightlight WLED_GLOBAL bool nightlightActive _INIT(false); @@ -557,6 +558,7 @@ WLED_GLOBAL bool nightlightActiveOld _INIT(false); WLED_GLOBAL uint32_t nightlightDelayMs _INIT(10); WLED_GLOBAL byte nightlightDelayMinsDefault _INIT(nightlightDelayMins); WLED_GLOBAL unsigned long nightlightStartTime; +WLED_GLOBAL unsigned long lastNlUpdate; WLED_GLOBAL byte briNlT _INIT(0); // current nightlight brightness WLED_GLOBAL byte colNlT[] _INIT_N(({ 0, 0, 0, 0 })); // current nightlight color @@ -564,12 +566,14 @@ WLED_GLOBAL byte colNlT[] _INIT_N(({ 0, 0, 0, 0 })); // current nightligh WLED_GLOBAL unsigned long lastOnTime _INIT(0); WLED_GLOBAL bool offMode _INIT(!turnOnAtBoot); WLED_GLOBAL byte bri _INIT(briS); // global brightness (set) -WLED_GLOBAL byte briOld _INIT(0); // global brightnes while in transition loop (previous iteration) +WLED_GLOBAL byte briOld _INIT(0); // global brightness while in transition loop (previous iteration) WLED_GLOBAL byte briT _INIT(0); // global brightness during transition WLED_GLOBAL byte briLast _INIT(128); // brightness before turned off. Used for toggle function -WLED_GLOBAL byte whiteLast _INIT(128); // white channel before turned off. Used for toggle function +WLED_GLOBAL byte whiteLast _INIT(128); // white channel before turned off. Used for toggle function in ir.cpp // button +WLED_GLOBAL int8_t btnPin[WLED_MAX_BUTTONS] _INIT({BTNPIN}); +WLED_GLOBAL byte buttonType[WLED_MAX_BUTTONS] _INIT({BTNTYPE}); WLED_GLOBAL bool buttonPublishMqtt _INIT(false); WLED_GLOBAL bool buttonPressedBefore[WLED_MAX_BUTTONS] _INIT({false}); WLED_GLOBAL bool buttonLongPressed[WLED_MAX_BUTTONS] _INIT({false}); @@ -599,6 +603,7 @@ WLED_GLOBAL bool udpConnected _INIT(false), udp2Connected _INIT(false), udpRgbCo WLED_GLOBAL bool showWelcomePage _INIT(false); // hue +#ifndef WLED_DISABLE_HUESYNC WLED_GLOBAL byte hueError _INIT(HUE_ERROR_INACTIVE); // WLED_GLOBAL uint16_t hueFailCount _INIT(0); WLED_GLOBAL float hueXLast _INIT(0), hueYLast _INIT(0); @@ -608,6 +613,7 @@ WLED_GLOBAL unsigned long hueLastRequestSent _INIT(0); WLED_GLOBAL bool hueAuthRequired _INIT(false); WLED_GLOBAL bool hueReceived _INIT(false); WLED_GLOBAL bool hueStoreAllowed _INIT(false), hueNewKey _INIT(false); +#endif // countdown WLED_GLOBAL unsigned long countdownTime _INIT(1514764800L); @@ -666,10 +672,11 @@ WLED_GLOBAL DNSServer dnsServer; #ifndef WLED_LON #define WLED_LON 0.0f #endif +#define NTP_NEVER 999000000L WLED_GLOBAL bool ntpConnected _INIT(false); WLED_GLOBAL time_t localTime _INIT(0); -WLED_GLOBAL unsigned long ntpLastSyncTime _INIT(999000000L); -WLED_GLOBAL unsigned long ntpPacketSentTime _INIT(999000000L); +WLED_GLOBAL unsigned long ntpLastSyncTime _INIT(NTP_NEVER); +WLED_GLOBAL unsigned long ntpPacketSentTime _INIT(NTP_NEVER); WLED_GLOBAL IPAddress ntpServerIP; WLED_GLOBAL uint16_t ntpLocalPort _INIT(2390); WLED_GLOBAL uint16_t rolloverMillis _INIT(0); @@ -714,7 +721,9 @@ WLED_GLOBAL AsyncWebServer server _INIT_N(((80))); #ifdef WLED_ENABLE_WEBSOCKETS WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws"))); #endif +#ifndef WLED_DISABLE_HUESYNC WLED_GLOBAL AsyncClient *hueClient _INIT(NULL); +#endif WLED_GLOBAL AsyncWebHandler *editHandler _INIT(nullptr); // udp interface objects @@ -806,10 +815,12 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); #define DEBUG_PRINT(x) DEBUGOUT.print(x) #define DEBUG_PRINTLN(x) DEBUGOUT.println(x) #define DEBUG_PRINTF(x...) DEBUGOUT.printf(x) + #define DEBUG_PRINTF_P(x...) DEBUGOUT.printf_P(x) #else #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) #define DEBUG_PRINTF(x...) + #define DEBUG_PRINTF_P(x...) #endif #ifdef WLED_DEBUG_FS @@ -827,7 +838,7 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); WLED_GLOBAL unsigned long debugTime _INIT(0); WLED_GLOBAL int lastWifiState _INIT(3); WLED_GLOBAL unsigned long wifiStateChangedTime _INIT(0); - WLED_GLOBAL unsigned long loops _INIT(0); + WLED_GLOBAL unsigned loops _INIT(0); #endif #ifdef ARDUINO_ARCH_ESP32 @@ -835,7 +846,6 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); #else #define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) #endif -#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) #ifndef WLED_AP_SSID_UNIQUE #define WLED_SET_AP_SSID() do { \ @@ -843,13 +853,12 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); } while(0) #else #define WLED_SET_AP_SSID() do { \ - strcpy_P(apSSID, PSTR(WLED_AP_SSID)); \ snprintf_P(\ - apSSID+strlen(WLED_AP_SSID), \ - sizeof(apSSID)-strlen(WLED_AP_SSID), \ - PSTR("-%*s"), \ - 6, \ - escapedMac.c_str() + 6\ + apSSID, \ + sizeof(apSSID)-1, \ + PSTR("%s-%s"), \ + WLED_BRAND, \ + escapedMac.c_str()+6 \ ); \ } while(0) #endif @@ -885,6 +894,7 @@ public: void initAP(bool resetAP = false); void initConnection(); void initInterfaces(); + int8_t findWiFi(bool doScan = false); #if defined(STATUSLED) void handleStatusLED(); #endif diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 9ea838797..4f2c14d47 100755 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -74,11 +74,11 @@ void loadSettingsFromEEPROM() int lastEEPROMversion = EEPROM.read(377); //last EEPROM version before update - readStringFromEEPROM( 0, clientSSID, 32); - readStringFromEEPROM( 32, clientPass, 64); - readStringFromEEPROM( 96, cmDNS, 32); - readStringFromEEPROM(128, apSSID, 32); - readStringFromEEPROM(160, apPass, 64); + readStringFromEEPROM( 0, multiWiFi[0].clientSSID, 32); + readStringFromEEPROM( 32, multiWiFi[0].clientPass, 64); + readStringFromEEPROM( 96, cmDNS, 32); + readStringFromEEPROM(128, apSSID, 32); + readStringFromEEPROM(160, apPass, 64); nightlightDelayMinsDefault = EEPROM.read(224); nightlightDelayMins = nightlightDelayMinsDefault; @@ -365,7 +365,7 @@ void applyMacro(byte index) { // De-EEPROM routine, upgrade from previous versions to v0.11 void deEEP() { - if (WLED_FS.exists("/presets.json")) return; + if (WLED_FS.exists(FPSTR(getPresetsFileName()))) return; DEBUG_PRINTLN(F("Preset file not found, attempting to load from EEPROM")); DEBUGFS_PRINTLN(F("Allocating saving buffer for dEEP")); @@ -442,7 +442,7 @@ void deEEP() { EEPROM.end(); - File f = WLED_FS.open("/presets.json", "w"); + File f = WLED_FS.open(FPSTR(getPresetsFileName()), "w"); if (!f) { errorFlag = ERR_FS_GENERAL; releaseJSONBufferLock(); diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index c6e984035..9cca09db0 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -113,7 +113,10 @@ void handleSerial() } else if (next == '{') { //JSON API bool verboseResponse = false; - if (!requestJSONBufferLock(16)) return; + if (!requestJSONBufferLock(16)) { + Serial.println(F("{\"error\":3}")); // ERR_NOBUF + return; + } Serial.setTimeout(100); DeserializationError error = deserializeJson(*pDoc, Serial); if (error) { diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 82cc55014..4ce113cb9 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -11,22 +11,46 @@ #endif #include "html_cpal.h" -/* - * Integrated HTTP web server page declarations - */ - -bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest* request, int code, uint16_t eTagSuffix = 0); -void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int code, uint16_t eTagSuffix = 0); -void handleStaticContent(AsyncWebServerRequest *request, const String &path, int code, const String &contentType, const uint8_t *content, size_t len, bool gzip = true, uint16_t eTagSuffix = 0); - // define flash strings once (saves flash memory) static const char s_redirecting[] PROGMEM = "Redirecting..."; static const char s_content_enc[] PROGMEM = "Content-Encoding"; static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security settings!"; static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!"; +static const char s_notimplemented[] PROGMEM = "Not implemented"; +static const char s_accessdenied[] PROGMEM = "Access Denied"; +static const char s_javascript[] PROGMEM = "application/javascript"; +static const char s_json[] = "application/json"; // AsyncJson-v6.h +static const char s_html[] PROGMEM = "text/html"; +static const char s_plain[] = "text/plain"; // Espalexa.h +static const char s_css[] PROGMEM = "text/css"; +static const char s_png[] PROGMEM = "image/png"; +static const char s_gif[] PROGMEM = "image/gif"; +static const char s_jpg[] PROGMEM = "image/jpeg"; +static const char s_ico[] PROGMEM = "image/x-icon"; +//static const char s_xml[] PROGMEM = "text/xml"; +//static const char s_pdf[] PROGMEM = "application/x-pdf"; +//static const char s_zip[] PROGMEM = "application/x-zip"; +//static const char s_gz[] PROGMEM = "application/x-gzip"; + +String getFileContentType(String &filename) { + if (filename.endsWith(F(".htm"))) return FPSTR(s_html); + else if (filename.endsWith(F(".html"))) return FPSTR(s_html); + else if (filename.endsWith(F(".css"))) return FPSTR(s_css); + else if (filename.endsWith(F(".js"))) return FPSTR(s_javascript); + else if (filename.endsWith(F(".json"))) return s_json; + else if (filename.endsWith(F(".png"))) return FPSTR(s_png); + else if (filename.endsWith(F(".gif"))) return FPSTR(s_gif); + else if (filename.endsWith(F(".jpg"))) return FPSTR(s_jpg); + else if (filename.endsWith(F(".ico"))) return FPSTR(s_ico); +// else if (filename.endsWith(F(".xml"))) return FPSTR(s_xml); +// else if (filename.endsWith(F(".pdf"))) return FPSTR(s_pdf); +// else if (filename.endsWith(F(".zip"))) return FPSTR(s_zip); +// else if (filename.endsWith(F(".gz"))) return FPSTR(s_gz); + return s_plain; +} //Is this an IP? -bool isIp(String str) { +static bool isIp(String str) { for (size_t i = 0; i < str.length(); i++) { int c = str.charAt(i); if (c != '.' && (c < '0' || c > '9')) { @@ -36,9 +60,130 @@ bool isIp(String str) { return true; } -void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) { +/* + * Integrated HTTP web server page declarations + */ + +static void generateEtag(char *etag, uint16_t eTagSuffix) { + sprintf_P(etag, PSTR("%7d-%02x-%04x"), VERSION, cacheInvalidate, eTagSuffix); +} + +static void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int code, uint16_t eTagSuffix = 0) { + // Only send ETag for 200 (OK) responses + if (code != 200) return; + + // https://medium.com/@codebyamir/a-web-developers-guide-to-browser-caching-cc41f3b73e7c + #ifndef WLED_DEBUG + // this header name is misleading, "no-cache" will not disable cache, + // it just revalidates on every load using the "If-None-Match" header with the last ETag value + response->addHeader(F("Cache-Control"), F("no-cache")); + #else + response->addHeader(F("Cache-Control"), F("no-store,max-age=0")); // prevent caching if debug build + #endif + char etag[32]; + generateEtag(etag, eTagSuffix); + response->addHeader(F("ETag"), etag); +} + +static bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest *request, int code, uint16_t eTagSuffix = 0) { + // Only send 304 (Not Modified) if response code is 200 (OK) + if (code != 200) return false; + + AsyncWebHeader *header = request->getHeader(F("If-None-Match")); + char etag[32]; + generateEtag(etag, eTagSuffix); + if (header && header->value() == etag) { + AsyncWebServerResponse *response = request->beginResponse(304); + setStaticContentCacheHeaders(response, code, eTagSuffix); + request->send(response); + return true; + } + return false; +} + +/** + * Handles the request for a static file. + * If the file was found in the filesystem, it will be sent to the client. + * Otherwise it will be checked if the browser cached the file and if so, a 304 response will be sent. + * If the file was not found in the filesystem and not in the browser cache, the request will be handled as a 200 response with the content of the page. + * + * @param request The request object + * @param path If a file with this path exists in the filesystem, it will be sent to the client. Set to "" to skip this check. + * @param code The HTTP status code + * @param contentType The content type of the web page + * @param content Content of the web page + * @param len Length of the content + * @param gzip Optional. Defaults to true. If false, the gzip header will not be added. + * @param eTagSuffix Optional. Defaults to 0. A suffix that will be added to the ETag header. This can be used to invalidate the cache for a specific page. + */ +static void handleStaticContent(AsyncWebServerRequest *request, const String &path, int code, const String &contentType, const uint8_t *content, size_t len, bool gzip = true, uint16_t eTagSuffix = 0) { + if (path != "" && handleFileRead(request, path)) return; + if (handleIfNoneMatchCacheHeader(request, code, eTagSuffix)) return; + AsyncWebServerResponse *response = request->beginResponse_P(code, contentType, content, len); + if (gzip) response->addHeader(FPSTR(s_content_enc), F("gzip")); + setStaticContentCacheHeaders(response, code, eTagSuffix); + request->send(response); +} + +#ifdef WLED_ENABLE_DMX +static String dmxProcessor(const String& var) +{ + String mapJS; + if (var == F("DMXVARS")) { + mapJS += F("\nCN="); + mapJS += String(DMXChannels); + mapJS += F(";\nCS="); + mapJS += String(DMXStart); + mapJS += F(";\nCG="); + mapJS += String(DMXGap); + mapJS += F(";\nLC="); + mapJS += String(strip.getLengthTotal()); + mapJS += F(";\nvar CH=["); + for (int i=0; i<15; i++) { + mapJS += String(DMXFixtureMap[i]) + ','; + } + mapJS += F("0];"); + } + return mapJS; +} +#endif + +static String msgProcessor(const String& var) +{ + if (var == "MSG") { + String messageBody = messageHead; + messageBody += F(""); + messageBody += messageSub; + uint32_t optt = optionType; + + if (optt < 60) //redirect to settings after optionType seconds + { + messageBody += F(""); + } else if (optt < 120) //redirect back after optionType-60 seconds, unused + { + //messageBody += ""; + } else if (optt < 180) //reload parent after optionType-120 seconds + { + messageBody += F(""); + } else if (optt == 253) + { + messageBody += F("

"); //button to settings + } else if (optt == 254) + { + messageBody += F("

"); + } + return messageBody; + } + return String(); +} + +static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) { if (!correctPIN) { - if (final) request->send(401, "text/plain", FPSTR(s_unlock_cfg)); + if (final) request->send(401, FPSTR(s_plain), FPSTR(s_unlock_cfg)); return; } if (!index) { @@ -50,7 +195,7 @@ void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t request->_tempFile = WLED_FS.open(finalname, "w"); DEBUG_PRINT(F("Uploading ")); DEBUG_PRINTLN(finalname); - if (finalname.equals("/presets.json")) presetsModifiedTime = toki.second(); + if (finalname.equals(FPSTR(getPresetsFileName()))) presetsModifiedTime = toki.second(); } if (len) { request->_tempFile.write(data,len); @@ -59,10 +204,10 @@ void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t request->_tempFile.close(); if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash doReboot = true; - request->send(200, "text/plain", F("Configuration restore successful.\nRebooting...")); + request->send(200, FPSTR(s_plain), F("Configuration restore successful.\nRebooting...")); } else { if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) strip.loadCustomPalettes(); - request->send(200, "text/plain", F("File Uploaded!")); + request->send(200, FPSTR(s_plain), F("File Uploaded!")); } cacheInvalidate++; } @@ -78,26 +223,25 @@ void createEditHandler(bool enable) { editHandler = &server.addHandler(new SPIFFSEditor("","",WLED_FS));//http_username,http_password)); #endif #else - editHandler = &server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, "Not implemented", F("The FS editor is disabled in this build."), 254); + editHandler = &server.on(SET_F("/edit"), HTTP_GET, [](AsyncWebServerRequest *request){ + serveMessage(request, 501, FPSTR(s_notimplemented), F("The FS editor is disabled in this build."), 254); }); #endif } else { - editHandler = &server.on("/edit", HTTP_ANY, [](AsyncWebServerRequest *request){ - serveMessage(request, 401, "Access Denied", FPSTR(s_unlock_cfg), 254); + editHandler = &server.on(SET_F("/edit"), HTTP_ANY, [](AsyncWebServerRequest *request){ + serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254); }); } } -bool captivePortal(AsyncWebServerRequest *request) +static bool captivePortal(AsyncWebServerRequest *request) { - if (ON_STA_FILTER(request)) return false; //only serve captive in AP mode - String hostH; - if (!request->hasHeader("Host")) return false; - hostH = request->getHeader("Host")->value(); + if (!apActive) return false; //only serve captive in AP mode + if (!request->hasHeader(F("Host"))) return false; - if (!isIp(hostH) && hostH.indexOf("wled.me") < 0 && hostH.indexOf(cmDNS) < 0) { - DEBUG_PRINTLN("Captive portal"); + String hostH = request->getHeader(F("Host"))->value(); + if (!isIp(hostH) && hostH.indexOf(F("wled.me")) < 0 && hostH.indexOf(cmDNS) < 0 && hostH.indexOf(':') < 0) { + DEBUG_PRINTLN(F("Captive portal")); AsyncWebServerResponse *response = request->beginResponse(302); response->addHeader(F("Location"), F("http://4.3.2.1")); request->send(response); @@ -115,58 +259,63 @@ void initServer() #ifdef WLED_ENABLE_WEBSOCKETS #ifndef WLED_DISABLE_2D - server.on("/liveview2D", HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "", 200, "text/html", PAGE_liveviewws2D, PAGE_liveviewws2D_length); + server.on(SET_F("/liveview2D"), HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveviewws2D, PAGE_liveviewws2D_length); }); #endif #endif - server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "", 200, "text/html", PAGE_liveview, PAGE_liveview_length); + server.on(SET_F("/liveview"), HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveview, PAGE_liveview_length); }); //settings page - server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(SET_F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){ serveSettings(request); }); // "/settings/settings.js&p=x" request also handled by serveSettings() - - server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "/style.css", 200, "text/css", PAGE_settingsCss, PAGE_settingsCss_length); + static const char _style_css[] PROGMEM = "/style.css"; + server.on(_style_css, HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, FPSTR(_style_css), 200, FPSTR(s_css), PAGE_settingsCss, PAGE_settingsCss_length); }); - server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "/favicon.ico", 200, "image/x-icon", favicon, favicon_length, false); + static const char _favicon_ico[] PROGMEM = "/favicon.ico"; + server.on(_favicon_ico, HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, FPSTR(_favicon_ico), 200, F("image/x-icon"), favicon, favicon_length, false); }); - server.on("/skin.css", HTTP_GET, [](AsyncWebServerRequest *request) { - if (handleFileRead(request, "/skin.css")) return; - AsyncWebServerResponse *response = request->beginResponse(200, "text/css"); + static const char _skin_css[] PROGMEM = "/skin.css"; + server.on(_skin_css, HTTP_GET, [](AsyncWebServerRequest *request) { + if (handleFileRead(request, FPSTR(_skin_css))) return; + AsyncWebServerResponse *response = request->beginResponse(200, FPSTR(s_css)); request->send(response); }); - server.on("/welcome", HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(SET_F("/welcome"), HTTP_GET, [](AsyncWebServerRequest *request){ serveSettings(request); }); - server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(SET_F("/reset"), HTTP_GET, [](AsyncWebServerRequest *request){ serveMessage(request, 200,F("Rebooting now..."),F("Please wait ~10 seconds..."),129); doReboot = true; }); - server.on("/settings", HTTP_POST, [](AsyncWebServerRequest *request){ + server.on(SET_F("/settings"), HTTP_POST, [](AsyncWebServerRequest *request){ serveSettings(request, true); }); - server.on("/json", HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(SET_F("/json"), HTTP_GET, [](AsyncWebServerRequest *request){ serveJson(request); }); - AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request) { + AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler(F("/json"), [](AsyncWebServerRequest *request) { bool verboseResponse = false; bool isConfig = false; - if (!requestJSONBufferLock(14)) return; + if (!requestJSONBufferLock(14)) { + serveJsonError(request, 503, ERR_NOBUF); + return; + } DeserializationError error = deserializeJson(*pDoc, (uint8_t*)(request->_tempObject)); JsonObject root = pDoc->as(); @@ -178,7 +327,7 @@ void initServer() if (root.containsKey("pin")) checkSettingsPIN(root["pin"].as()); const String& url = request->url(); - isConfig = url.indexOf("cfg") > -1; + isConfig = url.indexOf(F("cfg")) > -1; if (!isConfig) { /* #ifdef WLED_DEBUG @@ -207,55 +356,56 @@ void initServer() doSerializeConfig = true; //serializeConfig(); //Save new settings to FS } } - request->send(200, "application/json", F("{\"success\":true}")); + request->send(200, s_json, F("{\"success\":true}")); }, JSON_BUFFER_SIZE); server.addHandler(handler); - server.on("/version", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, "text/plain", (String)VERSION); + server.on(SET_F("/version"), HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, FPSTR(s_plain), (String)VERSION); }); - server.on("/uptime", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, "text/plain", (String)millis()); + server.on(SET_F("/uptime"), HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, FPSTR(s_plain), (String)millis()); }); - server.on("/freeheap", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send(200, "text/plain", (String)ESP.getFreeHeap()); + server.on(SET_F("/freeheap"), HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, FPSTR(s_plain), (String)ESP.getFreeHeap()); }); #ifdef WLED_ENABLE_USERMOD_PAGE server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "", 200, "text/html", PAGE_usermod, PAGE_usermod_length); + handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_usermod, PAGE_usermod_length); }); #endif - server.on("/teapot", HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(SET_F("/teapot"), HTTP_GET, [](AsyncWebServerRequest *request){ serveMessage(request, 418, F("418. I'm a teapot."), F("(Tangible Embedded Advanced Project Of Twinkling)"), 254); }); - server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request) {}, + server.on(SET_F("/upload"), HTTP_POST, [](AsyncWebServerRequest *request) {}, [](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {handleUpload(request, filename, index, data, len, final);} ); createEditHandler(correctPIN); + static const char _update[] PROGMEM = "/update"; #ifndef WLED_DISABLE_OTA //init ota page - server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ + server.on(_update, HTTP_GET, [](AsyncWebServerRequest *request){ if (otaLock) { - serveMessage(request, 401, "Access Denied", FPSTR(s_unlock_ota), 254); + serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254); } else serveSettings(request); // checks for "upd" in URL and handles PIN }); - server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ + server.on(_update, HTTP_POST, [](AsyncWebServerRequest *request){ if (!correctPIN) { serveSettings(request, true); // handle PIN page POST request return; } if (otaLock) { - serveMessage(request, 401, "Access Denied", FPSTR(s_unlock_ota), 254); + serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254); return; } if (Update.hasError()) { @@ -295,55 +445,57 @@ void initServer() } }); #else - server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, "Not implemented", F("OTA updating is disabled in this build."), 254); + server.on(_update, HTTP_GET, [](AsyncWebServerRequest *request){ + serveMessage(request, 501, FPSTR(s_notimplemented), F("OTA updating is disabled in this build."), 254); }); #endif - #ifdef WLED_ENABLE_DMX - server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", PAGE_dmxmap , dmxProcessor); +#ifdef WLED_ENABLE_DMX + server.on(SET_F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, FPSTR(s_html), PAGE_dmxmap , dmxProcessor); }); - #else - server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, "Not implemented", F("DMX support is not enabled in this build."), 254); +#else + server.on(SET_F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){ + serveMessage(request, 501, FPSTR(s_notimplemented), F("DMX support is not enabled in this build."), 254); }); - #endif +#endif server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { if (captivePortal(request)) return; if (!showWelcomePage || request->hasArg(F("sliders"))) { - handleStaticContent(request, "/index.htm", 200, "text/html", PAGE_index, PAGE_index_L); + handleStaticContent(request, F("/index.htm"), 200, FPSTR(s_html), PAGE_index, PAGE_index_L); } else { serveSettings(request); } }); #ifdef WLED_ENABLE_PIXART - server.on("/pixart.htm", HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "/pixart.htm", 200, "text/html", PAGE_pixart, PAGE_pixart_L); - }); - #endif - -#ifndef WLED_DISABLE_PXMAGIC - server.on("/pxmagic.htm", HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "/pxmagic.htm", 200, "text/html", PAGE_pxmagic, PAGE_pxmagic_L); + static const char _pixart_htm[] PROGMEM = "/pixart.htm"; + server.on(_pixart_htm, HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, FPSTR(_pixart_htm), 200, FPSTR(s_html), PAGE_pixart, PAGE_pixart_L); }); #endif - server.on("/cpal.htm", HTTP_GET, [](AsyncWebServerRequest *request) { - handleStaticContent(request, "/cpal.htm", 200, "text/html", PAGE_cpal, PAGE_cpal_L); +#ifndef WLED_DISABLE_PXMAGIC + static const char _pxmagic_htm[] PROGMEM = "/pxmagic.htm"; + server.on(_pxmagic_htm, HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, FPSTR(_pxmagic_htm), 200, FPSTR(s_html), PAGE_pxmagic, PAGE_pxmagic_L); + }); +#endif + + static const char _cpal_htm[] PROGMEM = "/cpal.htm"; + server.on(_cpal_htm, HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, FPSTR(_cpal_htm), 200, FPSTR(s_html), PAGE_cpal, PAGE_cpal_L); }); - #ifdef WLED_ENABLE_WEBSOCKETS +#ifdef WLED_ENABLE_WEBSOCKETS server.addHandler(&ws); - #endif +#endif //called when the url is not defined here, ajax-in; get-settings server.onNotFound([](AsyncWebServerRequest *request){ - DEBUG_PRINTLN("Not-Found HTTP call:"); - DEBUG_PRINTLN("URI: " + request->url()); + DEBUG_PRINT(F("Not-Found HTTP call: ")); DEBUG_PRINTLN(request->url()); if (captivePortal(request)) return; //make API CORS compatible @@ -359,106 +511,10 @@ void initServer() #ifndef WLED_DISABLE_ALEXA if(espalexa.handleAlexaApiCall(request)) return; #endif - handleStaticContent(request, request->url(), 404, "text/html", PAGE_404, PAGE_404_length); + handleStaticContent(request, request->url(), 404, FPSTR(s_html), PAGE_404, PAGE_404_length); }); } -void generateEtag(char *etag, uint16_t eTagSuffix) { - sprintf_P(etag, PSTR("%7d-%02x-%04x"), VERSION, cacheInvalidate, eTagSuffix); -} - -bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest *request, int code, uint16_t eTagSuffix) { - // Only send 304 (Not Modified) if response code is 200 (OK) - if (code != 200) return false; - - AsyncWebHeader *header = request->getHeader("If-None-Match"); - char etag[14]; - generateEtag(etag, eTagSuffix); - if (header && header->value() == etag) { - AsyncWebServerResponse *response = request->beginResponse(304); - setStaticContentCacheHeaders(response, code, eTagSuffix); - request->send(response); - return true; - } - return false; -} - -void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int code, uint16_t eTagSuffix) { - // Only send ETag for 200 (OK) responses - if (code != 200) return; - - // https://medium.com/@codebyamir/a-web-developers-guide-to-browser-caching-cc41f3b73e7c - #ifndef WLED_DEBUG - // this header name is misleading, "no-cache" will not disable cache, - // it just revalidates on every load using the "If-None-Match" header with the last ETag value - response->addHeader(F("Cache-Control"), "no-cache"); - #else - response->addHeader(F("Cache-Control"), "no-store,max-age=0"); // prevent caching if debug build - #endif - char etag[14]; - generateEtag(etag, eTagSuffix); - response->addHeader(F("ETag"), etag); -} - -/** - * Handels the request for a static file. - * If the file was found in the filesystem, it will be sent to the client. - * Otherwise it will be checked if the browser cached the file and if so, a 304 response will be sent. - * If the file was not found in the filesystem and not in the browser cache, the request will be handled as a 200 response with the content of the page. - * - * @param request The request object - * @param path If a file with this path exists in the filesystem, it will be sent to the client. Set to "" to skip this check. - * @param code The HTTP status code - * @param contentType The content type of the web page - * @param content Content of the web page - * @param len Length of the content - * @param gzip Optional. Defaults to true. If false, the gzip header will not be added. - * @param eTagSuffix Optional. Defaults to 0. A suffix that will be added to the ETag header. This can be used to invalidate the cache for a specific page. - */ -void handleStaticContent(AsyncWebServerRequest *request, const String &path, int code, const String &contentType, const uint8_t *content, size_t len, bool gzip, uint16_t eTagSuffix) { - if (path != "" && handleFileRead(request, path)) return; - if (handleIfNoneMatchCacheHeader(request, code, eTagSuffix)) return; - AsyncWebServerResponse *response = request->beginResponse_P(code, contentType, content, len); - if (gzip) response->addHeader(FPSTR(s_content_enc), "gzip"); - setStaticContentCacheHeaders(response, code, eTagSuffix); - request->send(response); -} - - - -String msgProcessor(const String& var) -{ - if (var == "MSG") { - String messageBody = messageHead; - messageBody += F(""); - messageBody += messageSub; - uint32_t optt = optionType; - - if (optt < 60) //redirect to settings after optionType seconds - { - messageBody += F(""); - } else if (optt < 120) //redirect back after optionType-60 seconds, unused - { - //messageBody += ""; - } else if (optt < 180) //reload parent after optionType-120 seconds - { - messageBody += F(""); - } else if (optt == 253) - { - messageBody += F("

"); //button to settings - } else if (optt == 254) - { - messageBody += F("

"); - } - return messageBody; - } - return String(); -} - void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl, byte optionT) { @@ -466,15 +522,15 @@ void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& h messageSub = subl; optionType = optionT; - request->send_P(code, "text/html", PAGE_msg, msgProcessor); + request->send_P(code, FPSTR(s_html), PAGE_msg, msgProcessor); } void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t error) { AsyncJsonResponse *response = new AsyncJsonResponse(64); - if (error < ERR_NOT_IMPL) response->addHeader("Retry-After", "1"); - response->setContentType("application/json"); + if (error < ERR_NOT_IMPL) response->addHeader(F("Retry-After"), F("1")); + response->setContentType(s_json); response->setCode(code); JsonObject obj = response->getRoot(); obj[F("error")] = error; @@ -482,28 +538,6 @@ void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t erro request->send(response); } -#ifdef WLED_ENABLE_DMX -String dmxProcessor(const String& var) -{ - String mapJS; - #ifdef WLED_ENABLE_DMX - if (var == "DMXVARS") { - mapJS += "\nCN=" + String(DMXChannels) + ";\n"; - mapJS += "CS=" + String(DMXStart) + ";\n"; - mapJS += "CG=" + String(DMXGap) + ";\n"; - mapJS += "LC=" + String(strip.getLengthTotal()) + ";\n"; - mapJS += "var CH=["; - for (int i=0;i<15;i++) { - mapJS += String(DMXFixtureMap[i]) + ","; - } - mapJS += "0];"; - } - #endif - - return mapJS; -} -#endif - void serveSettingsJS(AsyncWebServerRequest* request) { @@ -512,18 +546,23 @@ void serveSettingsJS(AsyncWebServerRequest* request) byte subPage = request->arg(F("p")).toInt(); if (subPage > 10) { strcpy_P(buf, PSTR("alert('Settings for this request are not implemented.');")); - request->send(501, "application/javascript", buf); + request->send(501, FPSTR(s_javascript), buf); return; } if (subPage > 0 && !correctPIN && strlen(settingsPIN)>0) { strcpy_P(buf, PSTR("alert('PIN incorrect.');")); - request->send(401, "application/javascript", buf); + request->send(401, FPSTR(s_javascript), buf); return; } strcat_P(buf,PSTR("function GetV(){var d=document;")); getSettingsJS(subPage, buf+strlen(buf)); // this may overflow by 35bytes!!! strcat_P(buf,PSTR("}")); - request->send(200, "application/javascript", buf); + + AsyncWebServerResponse *response; + response = request->beginResponse(200, FPSTR(s_javascript), buf); + response->addHeader(F("Cache-Control"), F("no-store")); + response->addHeader(F("Expires"), F("0")); + request->send(response); } @@ -532,18 +571,22 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { const String& url = request->url(); if (url.indexOf("sett") >= 0) { - if (url.indexOf(".js") > 0) subPage = SUBPAGE_JS; - else if (url.indexOf(".css") > 0) subPage = SUBPAGE_CSS; - else if (url.indexOf("wifi") > 0) subPage = SUBPAGE_WIFI; - else if (url.indexOf("leds") > 0) subPage = SUBPAGE_LEDS; - else if (url.indexOf("ui") > 0) subPage = SUBPAGE_UI; - else if (url.indexOf("sync") > 0) subPage = SUBPAGE_SYNC; - else if (url.indexOf("time") > 0) subPage = SUBPAGE_TIME; - else if (url.indexOf("sec") > 0) subPage = SUBPAGE_SEC; - else if (url.indexOf("dmx") > 0) subPage = SUBPAGE_DMX; - else if (url.indexOf("um") > 0) subPage = SUBPAGE_UM; - else if (url.indexOf("2D") > 0) subPage = SUBPAGE_2D; - else if (url.indexOf("lock") > 0) subPage = SUBPAGE_LOCK; + if (url.indexOf(F(".js")) > 0) subPage = SUBPAGE_JS; + else if (url.indexOf(F(".css")) > 0) subPage = SUBPAGE_CSS; + else if (url.indexOf(F("wifi")) > 0) subPage = SUBPAGE_WIFI; + else if (url.indexOf(F("leds")) > 0) subPage = SUBPAGE_LEDS; + else if (url.indexOf(F("ui")) > 0) subPage = SUBPAGE_UI; + else if (url.indexOf( "sync") > 0) subPage = SUBPAGE_SYNC; + else if (url.indexOf( "time") > 0) subPage = SUBPAGE_TIME; + else if (url.indexOf(F("sec")) > 0) subPage = SUBPAGE_SEC; +#ifdef WLED_ENABLE_DMX + else if (url.indexOf( "dmx") > 0) subPage = SUBPAGE_DMX; +#endif + else if (url.indexOf( "um") > 0) subPage = SUBPAGE_UM; +#ifndef WLED_DISABLE_2D + else if (url.indexOf( "2D") > 0) subPage = SUBPAGE_2D; +#endif + else if (url.indexOf(F("lock")) > 0) subPage = SUBPAGE_LOCK; } else if (url.indexOf("/update") >= 0) subPage = SUBPAGE_UPDATE; // update page, for PIN check //else if (url.indexOf("/edit") >= 0) subPage = 10; @@ -557,7 +600,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { // if OTA locked or too frequent PIN entry requests fail hard if ((subPage == SUBPAGE_WIFI && wifiLock && otaLock) || (post && !correctPIN && millis()-lastEditTime < PIN_RETRY_COOLDOWN)) { - serveMessage(request, 401, "Access Denied", FPSTR(s_unlock_ota), 254); return; + serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254); return; } if (post) { //settings/set POST request, saving @@ -573,9 +616,13 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { case SUBPAGE_SYNC : strcpy_P(s, PSTR("Sync")); break; case SUBPAGE_TIME : strcpy_P(s, PSTR("Time")); break; case SUBPAGE_SEC : strcpy_P(s, PSTR("Security")); if (doReboot) strcpy_P(s2, PSTR("Rebooting, please wait ~10 seconds...")); break; +#ifdef WLED_ENABLE_DMX case SUBPAGE_DMX : strcpy_P(s, PSTR("DMX")); break; +#endif case SUBPAGE_UM : strcpy_P(s, PSTR("Usermods")); break; +#ifndef WLED_DISABLE_2D case SUBPAGE_2D : strcpy_P(s, PSTR("2D")); break; +#endif case SUBPAGE_PINREQ : strcpy_P(s, correctPIN ? PSTR("PIN accepted") : PSTR("PIN rejected")); break; } @@ -593,7 +640,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { } int code = 200; - String contentType = "text/html"; + String contentType = FPSTR(s_html); const uint8_t* content; size_t len; @@ -618,8 +665,8 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { serveMessage(request, 200, strlen(settingsPIN) > 0 ? PSTR("Settings locked") : PSTR("No PIN set"), FPSTR(s_redirecting), 1); return; } - case SUBPAGE_PINREQ : content = PAGE_settings_pin; len = PAGE_settings_pin_length; code = 401; break; - case SUBPAGE_CSS : content = PAGE_settingsCss; len = PAGE_settingsCss_length; contentType = "text/css"; break; + case SUBPAGE_PINREQ : content = PAGE_settings_pin; len = PAGE_settings_pin_length; code = 401; break; + case SUBPAGE_CSS : content = PAGE_settingsCss; len = PAGE_settingsCss_length; contentType = FPSTR(s_css); break; case SUBPAGE_JS : serveSettingsJS(request); return; case SUBPAGE_WELCOME : content = PAGE_welcome; len = PAGE_welcome_length; break; default: content = PAGE_settings; len = PAGE_settings_length; break; diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 7082a848d..1dd141a68 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -30,13 +30,16 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp { if (len > 0 && len < 10 && data[0] == 'p') { // application layer ping/pong heartbeat. - // client-side socket layer ping packets are unresponded (investigate) + // client-side socket layer ping packets are unanswered (investigate) client->text(F("pong")); return; } bool verboseResponse = false; - if (!requestJSONBufferLock(11)) return; + if (!requestJSONBufferLock(11)) { + client->text(F("{\"error\":3}")); // ERR_NOBUF + return; + } DeserializationError error = deserializeJson(*pDoc, data, len); JsonObject root = pDoc->as(); @@ -101,7 +104,14 @@ void sendDataWs(AsyncWebSocketClient * client) if (!ws.count()) return; AsyncWebSocketMessageBuffer * buffer; - if (!requestJSONBufferLock(12)) return; + if (!requestJSONBufferLock(12)) { + if (client) { + client->text(F("{\"error\":3}")); // ERR_NOBUF + } else { + ws.textAll(F("{\"error\":3}")); // ERR_NOBUF + } + return; + } JsonObject state = pDoc->createNestedObject("state"); serializeState(state); @@ -109,7 +119,7 @@ void sendDataWs(AsyncWebSocketClient * client) serializeInfo(info); size_t len = measureJson(*pDoc); - DEBUG_PRINTF("JSON buffer size: %u for WS request (%u).\n", pDoc->memoryUsage(), len); + DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for WS request (%u).\n"), pDoc->memoryUsage(), len); size_t heap1 = ESP.getFreeHeap(); DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); diff --git a/wled00/xml.cpp b/wled00/xml.cpp index de183c5a6..04e0ebfdf 100755 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -157,8 +157,8 @@ void appendGPIOinfo() { oappend(SET_F(",2")); // DMX hardcoded pin #endif - #ifdef WLED_DEBUG - oappend(SET_F(",")); oappend(itoa(hardwareTX,nS,10));// debug output (TX) pin + #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) + oappend(SET_F(",")); oappend(itoa(hardwareTX,nS,10)); // debug output (TX) pin #endif //Note: Using pin 3 (RX) disables Adalight / Serial JSON @@ -238,8 +238,8 @@ void getSettingsJS(byte subPage, char* dest) if (subPage == SUBPAGE_MENU) { - #ifndef WLED_DISABLE_2D // include only if 2D is compiled in - oappend(PSTR("gId('2dbtn').style.display='';")); + #ifdef WLED_DISABLE_2D // include only if 2D is not compiled in + oappend(PSTR("gId('2dbtn').style.display='none';")); #endif #ifdef WLED_ENABLE_DMX // include only if DMX is enabled oappend(PSTR("gId('dmxbtn').style.display='';")); @@ -248,23 +248,34 @@ void getSettingsJS(byte subPage, char* dest) if (subPage == SUBPAGE_WIFI) { - sappends('s',SET_F("CS"),clientSSID); - - byte l = strlen(clientPass); - char fpass[l+1]; //fill password field with *** - fpass[l] = 0; - memset(fpass,'*',l); - sappends('s',SET_F("CP"),fpass); - - char k[3]; k[2] = 0; //IP addresses - for (int i = 0; i<4; i++) - { - k[1] = 48+i; //ascii 0,1,2,3 - k[0] = 'I'; sappend('v',k,staticIP[i]); - k[0] = 'G'; sappend('v',k,staticGateway[i]); - k[0] = 'S'; sappend('v',k,staticSubnet[i]); + char nS[10]; + size_t l; + oappend(SET_F("resetWiFi(")); + oappend(itoa(WLED_MAX_WIFI_COUNT,nS,10)); + oappend(SET_F(");")); + for (size_t n = 0; n < multiWiFi.size(); n++) { + l = strlen(multiWiFi[n].clientPass); + char fpass[l+1]; //fill password field with *** + fpass[l] = 0; + memset(fpass,'*',l); + oappend(SET_F("addWiFi(\"")); + oappend(multiWiFi[n].clientSSID); + oappend(SET_F("\",\"")); + oappend(fpass); + oappend(SET_F("\",0x")); + oappend(itoa(multiWiFi[n].staticIP,nS,16)); + oappend(SET_F(",0x")); + oappend(itoa(multiWiFi[n].staticGW,nS,16)); + oappend(SET_F(",0x")); + oappend(itoa(multiWiFi[n].staticSN,nS,16)); + oappend(SET_F(");")); } + sappend('v',SET_F("D0"),dnsAddress[0]); + sappend('v',SET_F("D1"),dnsAddress[1]); + sappend('v',SET_F("D2"),dnsAddress[2]); + sappend('v',SET_F("D3"),dnsAddress[3]); + sappends('s',SET_F("CM"),cmDNS); sappend('i',SET_F("AB"),apBehavior); sappends('s',SET_F("AS"),apSSID); @@ -391,12 +402,12 @@ void getSettingsJS(byte subPage, char* dest) uint16_t speed = bus->getFrequency(); if (IS_PWM(bus->getType())) { switch (speed) { - case WLED_PWM_FREQ/3 : speed = 0; break; - case WLED_PWM_FREQ/2 : speed = 1; break; + case WLED_PWM_FREQ/2 : speed = 0; break; + case WLED_PWM_FREQ*2/3 : speed = 1; break; default: - case WLED_PWM_FREQ : speed = 2; break; - case WLED_PWM_FREQ*4/3 : speed = 3; break; - case WLED_PWM_FREQ*2 : speed = 4; break; + case WLED_PWM_FREQ : speed = 2; break; + case WLED_PWM_FREQ*2 : speed = 3; break; + case WLED_PWM_FREQ*10/3 : speed = 4; break; // uint16_t max (19531 * 3.333) } } else if (IS_DIGITAL(bus->getType()) && IS_2PIN(bus->getType())) { switch (speed) { @@ -414,6 +425,7 @@ void getSettingsJS(byte subPage, char* dest) sumMa += bus->getMaxCurrent(); } sappend('v',SET_F("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa); + sappend('c',SET_F("ABL"),BusManager::ablMilliampsMax() || sumMa > 0); sappend('c',SET_F("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0); oappend(SET_F("resetCOM(")); @@ -442,6 +454,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('v',SET_F("TD"),transitionDelayDefault); sappend('c',SET_F("PF"),strip.paletteFade); sappend('v',SET_F("TP"),randomPaletteChangeTime); + sappend('c',SET_F("TH"),useHarmonicRandomPalette); sappend('v',SET_F("BF"),briMultiplier); sappend('v',SET_F("TB"),nightlightTargetBri); sappend('v',SET_F("TL"),nightlightDelayMinsDefault); @@ -597,6 +610,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('v',SET_F("OM"),analogClock12pixel); sappend('c',SET_F("OS"),analogClockSecondsTrail); sappend('c',SET_F("O5"),analogClock5MinuteMarks); + sappend('c',SET_F("OB"),analogClockSolidBlack); sappend('c',SET_F("CE"),countdownMode); sappend('v',SET_F("CY"),countdownYear);