diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index 2b599e6f6..1dcab26ab 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,54 +32,63 @@ jobs: matrix: environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }} steps: - - uses: actions/checkout@v3 - - name: Cache pip - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- + cache: 'npm' + - run: npm ci - 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/.gitignore b/.gitignore index 789de0a9e..8a2319a72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,24 @@ -.pio .cache +.clang-format +.direnv +.DS_Store +.idea +.pio .pioenvs .piolibdeps .vscode -/wled00/Release -/wled00/extLibs -/platformio_override.ini -/wled00/my_config.h -/build_output -.DS_Store -.gitignore -.clang-format -node_modules -.idea -.direnv -wled-update.sh + esp01-update.sh -/wled00/LittleFS +platformio_override.ini replace_fs.py -wled00/wled00.ino.cpp +wled-update.sh + +/build_output/ +/node_modules/ + +/wled00/extLibs +/wled00/LittleFS +/wled00/my_config.h +/wled00/Release +/wled00/wled00.ino.cpp +/wled00/html_*.h \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2ee772ce1..f46f002b4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -9,8 +9,8 @@ ], "dependsOrder": "sequence", "problemMatcher": [ - "$platformio", - ], + "$platformio" + ] }, { "type": "PlatformIO", @@ -18,7 +18,7 @@ "task": "Build", "group": { "kind": "build", - "isDefault": true, + "isDefault": true }, "problemMatcher": [ "$platformio" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d792e73b..46f6df2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,267 @@ ## WLED changelog +#### Build 2403280 +- Individual color channel control for JSON API (fixes #3860) + - "col":[int|string|object|array, int|string|object|array, int|string|object|array] + int = Kelvin temperature or 0 for black + string = hex representation of [WW]RRGGBB + object = individual channel control {"r":0,"g":127,"b":255,"w":255}, each being optional (valid to send {}) + array = direct channel values [r,g,b,w] (w element being optional) +- runtime selection for CCT IC (Athom 15W bulb) +- #3850 (by @w00000dy) +- Rotary encoder palette count bugfix +- bugfixes and optimisations + +#### Build 2403240 +- v0.15.0-b2 +- WS2805 support (RGB + WW + CW, 600kbps) +- Unified PSRAM use +- NeoPixelBus v2.7.9 (for future WS2805 support) +- Ubiquitous PSRAM mode for all variants of ESP32 +- SSD1309_64 I2C Support for FLD Usermod (#3836 by @THATDONFC) +- Palette cycling fix (add support for `{"seg":[{"pal":"X~Y~"}]}` or `{"seg":[{"pal":"X~Yr"}]}`) +- FW1906 Support (#3810 by @deece and @Robert-github-com) +- ESPAsyncWebServer 2.2.0 (#3828 by @willmmiles) +- Bugfixes: #3843, #3844 + +#### Build 2403190 +- limit max PWM frequency (fix incorrect PWM resolution) +- Segment UI bugfix +- Updated AsyncWebServer (by @wlillmmiles) +- Simpler boot preset (fix for #3806) +- Effect: Fix for 2D Drift animation (#3816 by @BaptisteHudyma) +- Effect: Add twin option to 2D Drift +- MQTT cleanup +- DDP: Support sources that don't push (#3833 by @willmmiles) +- Usermod: Tetris AI usermod (#3711 by @muebau) + +#### Build 2403171 +- merge 0.14.2 changes into 0.15 + +#### 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 (with help from @JPZV) +- 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 +- Increased available effect data buffer (increases more if board has PSRAM) +- Custom palette editor mobile UI enhancement (by @imeszaros) +- Per port Auto Brightness Limiter (ABL) +- Use PSRAM for JSON buffer (double size, larger ledmaps, up to 2k) +- Reduced heap fragmentation by allocating ledmap array only once and not deallocating effect buffer +- HTTP retries on failed UI load +- UI Search: scroll to top (#3587 by @WoodyLetsCode) +- Return to inline iro.js and rangetouch.js (#3597 by @WoodyLetsCode) +- Better caching (#3591 by @WoodyLetsCode) +- Do not send 404 for missing `skin.css` (#3590 by @WoodyLetsCode) +- Simplified UI rework (#3511 by @WoodyLetsCode) +- Domoticz device ID for PIR and Temperature usermods +- Bugfix for UCS8904 `hasWhite()` +- Better search in UI (#3540 by @WoodyLetsCode) +- Seeding FastLED PRNG (#3552 by @TripleWhy) +- WIZ Smart Button support (#3547 by @micw) +- New button type (button switch, fix for #3537) +- Pixel Magic Tool update (#3483 by @ajotanc) +- Effect: 2D Matrix fix for gaps +- Bugfix #3526, #3533, #3561 +- Spookier Halloween Eyes (#3501) +- Compile time options for Multi Relay usermod (#3498) +- Effect: Fix for Dissolve (#3502) +- Better reverse proxy support (nested paths) +- Implement global JSON API boolean toggle (i.e. instead of "var":true or "var":false -> "var":"t"). +- Sort presets by ID +- Fix for #3641, #3312, #3367, #3637, #3646, #3447, #3632, #3496, #2922, #3593, #3514, #3522, #3578 (partial), #3606 (@WoodyLetsCode) +- Improved random bg image and added random bg image options (@WoodyLetsCode, #3481) +- Audio palettes (Audioreactive usermod, credit @netmindz) +- Better UI tooltips (@ajotnac, #3464) +- Better effect filters (filter dropdown) +- UDP sync fix (for #3487) +- Power button override (solves #3431) +- Additional HTTP request throttling (ESP8266) +- Additional UI/UX improvements +- Segment class optimisations (internal) +- ESP-NOW sync +- ESP-NOW Wiz remote JSON overrides (similar to IR JSON) & bugfixes +- Gamma correction for custom palettes (#3399). +- Restore presets from browser local storage +- Optional effect blending +- Restructured UDP Sync (internal) + - Remove sync receive + - Sync clarification +- Disallow 2D effects on non-2D segments +- Return of 2 audio simulations +- Bugfix in sync #3344 (internal) + - remove excessive segments + - ignore inactive segments if not syncing bounds + - send UDP/WS on segment change + - pop_back() when removing last segment + +#### Build 2403170 +- WLED 0.14.2 release + +#### Build 2403110 +- Beta WLED 0.14.2-b2 +- New AsyncWebServer (improved performance and reduced memory use) +- New builds for ESP8266 with 160MHz CPU clock +- Fixing stairway usermod and adding buildflags (#3758 by @lost-hope) +- Fixing a potential array bounds violation in ESPDMX +- Reduced RAM usage (moved strings and TZ data (by @willmmiles) to PROGMEM) +- LockedJsonResponse: Release early if possible (by @willmmiles) + +#### Build 2402120 +- Beta WLED 0.14.2-b1 +- Possible fix for #3589 & partial fix for #3605 +- Prevent JSON buffer clear after failed lock attempt +- Multiple analog button fix for #3549 +- UM Audioreactive: add two compiler options (#3732 by @wled-install) +- Fix for #3693 + +#### 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) +- New feature: Sort presets by ID +- New usermod: LDR sensor (#3490 by @JeffWDH) +- Effect: Twinklefox & Tinklecat metadata fix +- Effect: separate #HH and #MM for Scrolling Text (#3480) +- SSDR usermod enhancements (#3368) +- PWM fan usermod enhancements (#3414) + +#### Build 2310010, build 2310130 +- Release of WLED version 0.14.0 "Hoshi" +- Bugfixes for #3400, #3403, #3405 +- minor HTML optimizations +- audioreactive: bugfix for UDP sound sync (partly initialized packets) + +#### Build 2309240 +- Release of WLED beta version 0.14.0-b6 "Hoshi" +- Effect bugfixes and improvements (Meteor, Meteor Smooth, Scrolling Text) +- audioreactive: bugfixes for ES8388 and ES7243 init; minor improvements for analog inputs + +#### Build 2309100 +- Release of WLED beta version 0.14.0-b5 "Hoshi" +- New standard esp32 build with audioreactive +- Effect blending bugfixes, and minor optimizations + +#### Build 2309050 +- Effect blending (#3311) (finally effect transitions!) + *WARNING*: May not work well with ESP8266, with plenty of segments or usermods (low RAM condition)!!! +- Added receive and send sync groups to JSON API (#3317) (you can change sync groups using preset) +- Internal temperature usermod (#3246) +- MQTT server and topic length overrides (#3354) (new build flags) +- Animated Staircase usermod enhancement (#3348) (on/off toggle/relay control) +- Added local time info to Info page (#3351) +- New effect: Rolling Balls (a.k.a. linear bounce) (#1039) +- Various bug fixes and enhancements. + +#### Build 2308110 +- Release of WLED beta version 0.14.0-b4 "Hoshi" +- Reset effect data immediately upon mode change + +#### Build 2308030 +- Improved random palette handling and blending +- Soap bugfix +- Fix ESP-NOW crash with AP mode Always + +#### Build 2307180 +- Bus-level global buffering (#3280) +- Removed per-segment LED buffer (SEGMENT.leds) +- various fixes and improvements (ESP variants platform 5.3.0, effect optimizations, /json/cfg pin allocation) + #### Build 2307130 - larger `oappend()` stack buffer (3.5k) for ESP32 - Preset cycle bugfix (#3262) - Rotary encoder ALT fix for large LED count (#3276) - effect updates (2D Plasmaball), `blur()` speedup -- On/Off toggle from nodes view (may show unknow device type on older versions) (#3291) +- On/Off toggle from nodes view (may show unknown device type on older versions) (#3291) - various fixes and improvements (ABL, crashes when changing presets with different segments) #### Build 2306270 @@ -18,7 +274,7 @@ #### Build 2306210 - 0.14.0-b3 release -- respect global I2C in all usermods (no local initilaisation of I2C bus) +- respect global I2C in all usermods (no local initialization of I2C bus) - Multi relay usermod compile-time enabled option (-D MULTI_RELAY_ENABLED=true|false) #### Build 2306180 @@ -44,7 +300,7 @@ #### Build 2306020 - Support for segment sets (PR #3171) -- Reduce sound simulation modes to 2 to facilitiate segment sets +- Reduce sound simulation modes to 2 to facilitate segment sets - Trigger button immediately on press if all configured presets are the same (PR #3226) - Changes for allowing Alexa to change light color to White when auto-calculating from RGB (PR #3211) @@ -381,7 +637,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 @@ -910,7 +1166,7 @@ #### Build 2011040 -- Inversed Rain direction (fixes #1147) +- Inverted Rain direction (fixes #1147) #### Build 2011010 @@ -1121,7 +1377,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/CONTRIBUTING.md b/CONTRIBUTING.md index 560a70973..f813999bb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,20 @@ Here are a few suggestions to make it easier for you to contribute! +### Describe your PR + +Please add a description of your proposed code changes. It does not need to be an exhaustive essay, however a PR with no description or just a few words might not get accepted, simply because very basic information is missing. + +A good description helps us to review and understand your proposed changes. For example, you could say a few words about +* what you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.) +* how your code works (short technical summary - focus on important aspects that might not be obvious when reading the code) +* testing you performed, known limitations, open ends you possibly could not solve. +* any areas where you like to get help from an experienced maintainer (yes WLED has become big 😉) + +### Target branch for pull requests + +Please make all PRs against the `0_15` branch. + ### Code style When in doubt, it is easiest to replicate the code style you find in the files you want to edit :) @@ -73,6 +87,6 @@ Good: ``` -There is no set character limit for a comment within a line, -though as a rule of thumb you should wrap your comment if it exceeds the width of your editor window. +There is no hard character limit for a comment within a line, +though as a rule of thumb consider wrapping after 120 characters. Inline comments are OK if they describe that line only and are not exceedingly wide. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c4fe9b1c4..db66b554b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,277 +1,433 @@ { "name": "wled", - "version": "0.14.0-b3", - "lockfileVersion": 1, + "version": "0.15.0-b2", + "lockfileVersion": 3, "requires": true, - "dependencies": { - "abbrev": { + "packages": { + "": { + "name": "wled", + "version": "0.15.0-b2", + "license": "ISC", + "dependencies": { + "clean-css": "^5.3.3", + "html-minifier-terser": "^7.2.0", + "inliner": "^1.13.1", + "nodemon": "^3.0.2" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "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.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "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": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "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": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "requires": { + "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.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "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" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "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 +437,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 +780,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 +960,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.1.0", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", + "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "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 +1780,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.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "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 +1913,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.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" }, - "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 +1984,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.29.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.2.tgz", + "integrity": "sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw==", + "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 +2134,116 @@ "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": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", - "integrity": "sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=" } } } diff --git a/package.json b/package.json index d57c87d5d..6f7d634d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.0-b3", + "version": "0.15.0-b2", "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,9 @@ }, "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", - "zlib": "^1.0.5" + "nodemon": "^3.0.2" } } diff --git a/pio-scripts/build_ui.py b/pio-scripts/build_ui.py new file mode 100644 index 000000000..f3688a5d4 --- /dev/null +++ b/pio-scripts/build_ui.py @@ -0,0 +1,3 @@ +Import('env') + +env.Execute("npm run build") \ No newline at end of file diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 01223e93d..c0e85dcbb 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -4,6 +4,7 @@ import shutil import gzip OUTPUT_DIR = "build_output{}".format(os.path.sep) +#OUTPUT_DIR = os.path.join("build_output") def _get_cpp_define_value(env, define): define_list = [item[-1] for item in env["CPPDEFINES"] if item[0] == define] @@ -13,14 +14,24 @@ def _get_cpp_define_value(env, define): return None -def _create_dirs(dirs=["firmware", "map"]): - # check if output directories exist and create if necessary - if not os.path.isdir(OUTPUT_DIR): - os.mkdir(OUTPUT_DIR) - +def _create_dirs(dirs=["map", "release", "firmware"]): for d in dirs: - if not os.path.isdir("{}{}".format(OUTPUT_DIR, d)): - os.mkdir("{}{}".format(OUTPUT_DIR, d)) + os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) + +def create_release(source): + release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") + if release_name: + version = _get_cpp_define_value(env, "WLED_VERSION") + release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") + release_gz_file = release_file + ".gz" + print(f"Copying {source} to {release_file}") + shutil.copy(source, release_file) + bin_gzip(release_file, release_gz_file) + else: + variant = env["PIOENV"] + bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) + print(f"Copying {source} to {bin_file}") + shutil.copy(source, bin_file) def bin_rename_copy(source, target, env): _create_dirs() @@ -28,42 +39,21 @@ def bin_rename_copy(source, target, env): # create string with location and file names based on variant map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant) - bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) - release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") - - if release_name: - _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): - os.remove(f) - - # copy firmware.bin to firmware/.bin - shutil.copy(str(target[0]), bin_file) + create_release(str(target[0])) # copy firmware.map to map/.map if os.path.isfile("firmware.map"): shutil.move("firmware.map", map_file) -def bin_gzip(source, target, env): - _create_dirs() - variant = env["PIOENV"] - - # create string with location and file names based on variant - bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) - gzip_file = "{}firmware{}{}.bin.gz".format(OUTPUT_DIR, os.path.sep, variant) - - # check if new target files exist and remove if necessary - if os.path.isfile(gzip_file): os.remove(gzip_file) - - # write gzip firmware file - with open(bin_file,"rb") as fp: - with gzip.open(gzip_file, "wb", compresslevel = 9) as f: +def bin_gzip(source, target): + # only create gzip for esp8266 + if not env["PIOPLATFORM"] == "espressif8266": + return + + print(f"Creating gzip file {target} from {source}") + with open(source,"rb") as fp: + with gzip.open(target, "wb", compresslevel = 9) as f: shutil.copyfileobj(fp, f) -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_rename_copy, bin_gzip]) +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", bin_rename_copy) diff --git a/platformio.ini b/platformio.ini index d3b71d3c4..e334b7e35 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, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB - -# Release binaries -; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB - -# Build everything -; default_envs = esp32dev, esp8285_4CH_MagicHome, codm-controller-0_6-rev2, codm-controller-0_6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips - -# 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, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32s3_4M_PSRAM_qspi, 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 @@ -72,14 +41,13 @@ arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/ platform_wled_default = ${common.arduino_core_3_1_2} # We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization #platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 -platform_packages = platformio/framework-arduinoespressif8266 - platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 +platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 platformio/tool-esptool #@ ~1.413.0 platformio/tool-esptoolpy #@ ~1.30000.0 ## previous platform for 8266, in case of problems with the new one -## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_2_0, which does not support Ucs890x -;; platform_wled_default = ${common.arduino_core_3_2_0} +## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_0_2, which does not support Ucs890x +;; platform_wled_default = ${common.arduino_core_3_0_2} ;; platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 ;; platformio/toolchain-xtensa @ ~2.40802.200502 ;; platformio/tool-esptool @ ~1.413.0 @@ -87,11 +55,13 @@ platform_packages = platformio/framework-arduinoespressif8266 # ------------------------------------------------------------------------------ # FLAGS: DEBUG -# +# esp8266 : see https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level +# esp32 : see https://docs.platformio.org/en/latest/platforms/espressif32.html#debug-level # ------------------------------------------------------------------------------ -debug_flags = -D DEBUG=1 -D WLED_DEBUG -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM -#if needed (for memleaks etc) also add; -DDEBUG_ESP_OOM -include "umm_malloc/umm_malloc_cfg.h" -#-DDEBUG_ESP_CORE is not working right now +debug_flags = -D DEBUG=1 -D WLED_DEBUG + -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM ;; for esp8266 + # if needed (for memleaks etc) also add; -DDEBUG_ESP_OOM -include "umm_malloc/umm_malloc_cfg.h" + # -DDEBUG_ESP_CORE is not working right now # ------------------------------------------------------------------------------ # FLAGS: ldscript (available ldscripts at https://github.com/esp8266/Arduino/tree/master/tools/sdk/ld) @@ -130,11 +100,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 build_unflags = @@ -153,6 +119,7 @@ extra_scripts = post:pio-scripts/output_bins.py post:pio-scripts/strip-floats.py pre:pio-scripts/user_config_copy.py + pre:pio-scripts/build_ui.py # ------------------------------------------------------------------------------ # COMMON SETTINGS: @@ -161,10 +128,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 @@ -175,24 +140,41 @@ upload_speed = 115200 # ------------------------------------------------------------------------------ lib_compat_mode = strict lib_deps = - fastled/FastLED @ 3.5.0 + fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 - makuna/NeoPixelBus @ 2.7.5 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 + makuna/NeoPixelBus @ 2.7.9 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 + # 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 MAX1704x Lipo Monitor / Fuel Gauge uncomment following + ; https://github.com/adafruit/Adafruit_BusIO @ 1.14.5 + ; https://github.com/adafruit/Adafruit_MAX1704X @ 1.0.2 + #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 + # SHT85 + ;robtillaart/SHT85@~0.3.3 + # Audioreactive usermod + ;kosme/arduinoFFT @ 2.0.0 extra_scripts = ${scripts_defaults.extra_scripts} @@ -201,7 +183,7 @@ build_flags = -DESP8266 -DFP_IN_IROM ;-Wno-deprecated-declarations - -Wno-register ;; leaves some warnings when compiling C files: command-line option '-Wno-register' is valid for C++/ObjC++ but not for C + ;-Wno-register ;; leaves some warnings when compiling C files: command-line option '-Wno-register' is valid for C++/ObjC++ but not for C ;-Dregister= # remove warnings in C++17 due to use of deprecated register keyword by the FastLED library ;; warning: this can be dangerous -Wno-misleading-indentation ; NONOSDK22x_190703 = 2.2.2-dev(38a443e) @@ -214,6 +196,10 @@ build_flags = -DVTABLES_IN_FLASH ; restrict to minimal mime-types -DMIMETYPE_MINIMAL + ; other special-purpose framework flags (see https://docs.platformio.org/en/latest/platforms/espressif8266.html) + ; decrease code cache size and increase IRAM to fit all pixel functions + -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'" + ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown lib_deps = #https://github.com/lorol/LITTLEFS.git @@ -224,9 +210,7 @@ lib_deps = [esp32] #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip platform = espressif32@3.5.0 - platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4 - build_flags = -g -DARDUINO_ARCH_ESP32 #-DCONFIG_LITTLEFS_FOR_IDF_3_2 @@ -234,14 +218,14 @@ build_flags = -g #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x -D LOROL_LITTLEFS ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 - default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv - lib_deps = https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} - +# additional build flags for audioreactive +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 @@ -249,9 +233,8 @@ lib_deps = ;; ;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly. ;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio. -platform = espressif32@5.2.0 +platform = espressif32@5.3.0 platform_packages = - toolchain-riscv32-esp @ 8.4.0+2021r2-patch5 ; required for platform version < 5.3.0, remove this line when upgrading to platform >=5.3.0 build_flags = -g -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one -DARDUINO_ARCH_ESP32 -DESP32 @@ -265,9 +248,9 @@ lib_deps = [esp32s2] ;; generic definitions for all ESP32-S2 boards -platform = espressif32@5.2.0 +platform = espressif32@5.3.0 platform_packages = - toolchain-riscv32-esp @ 8.4.0+2021r2-patch5 ; required for platform version < 5.3.0, remove this line when upgrading to platform >=5.3.0 +default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32S2 @@ -278,16 +261,14 @@ 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} [esp32c3] ;; generic definitions for all ESP32-C3 boards -platform = espressif32@5.2.0 +platform = espressif32@5.3.0 platform_packages = - toolchain-riscv32-esp @ 8.4.0+2021r2-patch5 ; required for platform version < 5.3.0, remove this line when upgrading to platform >=5.3.0 build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32C3 @@ -297,16 +278,14 @@ 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} [esp32s3] ;; generic definitions for all ESP32-S3 boards -platform = espressif32@5.2.0 +platform = espressif32@5.3.0 platform_packages = - toolchain-riscv32-esp @ 8.4.0+2021r2-patch5 ; required for platform version < 5.3.0, remove this line when upgrading to platform >=5.3.0 build_flags = -g -DESP32 -DARDUINO_ARCH_ESP32 @@ -337,6 +316,11 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder +[env:nodemcuv2_160] +extends = env:nodemcuv2 +board_build.f_cpu = 160000000L +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D + [env:esp8266_2m] board = esp_wroom_02 platform = ${common.platform_wled_default} @@ -346,6 +330,11 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 lib_deps = ${esp8266.lib_deps} +[env:esp8266_2m_160] +extends = env:esp8266_2m +board_build.f_cpu = 160000000L +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02_160 + [env:esp01_1m_full] board = esp01_1m platform = ${common.platform_wled_default} @@ -353,45 +342,14 @@ 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 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:esp01_1m_full_160] +extends = env:esp01_1m_full +board_build.f_cpu = 160000000L +build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA + ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM [env:esp32dev] board = esp32dev @@ -403,32 +361,19 @@ lib_deps = ${esp32.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} -[env:esp32dev_qio80] +[env:esp32dev_audioreactive] 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 +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET + ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} + ${esp32.AR_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 +; board_build.f_flash = 80000000L +; board_build.flash_mode = dio [env:esp32_eth] board = esp32-poe @@ -437,21 +382,21 @@ platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 + -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only 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 = tools/WLED_ESP32-wrover_4MB.csv build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=S2_saola - -DARDUINO_USB_CDC_ON_BOOT=1 -lib_deps = ${esp32s2.lib_deps} +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_WROVER + -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html + -D LEDPIN=25 +lib_deps = ${esp32.lib_deps} [env:esp32c3dev] extends = esp32c3 @@ -460,8 +405,9 @@ platform_packages = ${esp32c3.platform_packages} framework = arduino board = esp32-c3-devkitm-1 board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv -build_flags = ${common.build_flags} ${esp32c3.build_flags} #-D WLED_RELEASE_NAME=ESP32-C3 +build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3 -D WLED_WATCHDOG_TIMEOUT=0 + -DLOLIN_WIFI_FIX ; seems to work much better with this -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB ;-DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip upload_speed = 460800 @@ -475,309 +421,83 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 ; or 460800 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB -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=0 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") - ;-D WLED_DEBUG + ;-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + ${esp32.AR_build_flags} lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} board_build.partitions = tools/WLED_ESP32_8MB.csv board_build.f_flash = 80000000L board_build.flash_mode = qio ; board_build.flash_mode = dio ;; try this if you have problems at startup monitor_filters = esp32_exception_decoder -[env:esp32s3dev_8MB_PSRAM] -;; ESP32-TinyS3 development board, with 8MB FLASH and 8MB PSRAM (memory_type: qio_opi, qio_qspi, or opi_opi) -;board = um_tinys3 ; -> needs workaround from https://github.com/Aircoookie/WLED/pull/2905#issuecomment-1328049860 -;board = esp32s3box ; -> error: 'esp32_adc2gpio' was not declared in this scope -board = esp32-s3-devkitc-1 ; -> compiles, but does not support PSRAM +[env:esp32s3dev_8MB_PSRAM_opi] +;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) +board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support +board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB 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=0 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") - ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM - -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used + ;-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") + -DBOARD_HAS_PSRAM + ${esp32.AR_build_flags} lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} board_build.partitions = tools/WLED_ESP32_8MB.csv board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder -[env:esp8285_4CH_MagicHome] -board = esp8285 -platform = ${common.platform_wled_default} -platform_packages = ${common.platform_packages} -board_build.ldscript = ${common.ldscript_1m128k} +[env:esp32s3_4M_PSRAM_qspi] +;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) +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 +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +upload_speed = 921600 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} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_PSRAM_qspi + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -D WLED_WATCHDOG_TIMEOUT=0 + ${esp32.AR_build_flags} +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} +board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder [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 -build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=LolinS2 - -DBOARD_HAS_PSRAM - -DARDUINO_USB_CDC_ON_BOOT=1 # try disabling and enabling unflag above in case of board-specific issues, will disable Serial +;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 + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 + -DBOARD_HAS_PSRAM -DLOLIN_WIFI_FIX ; seems to work much better with this - -D WLED_USE_PSRAM -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 -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 -D HW_PIN_DATASPI=11 -D HW_PIN_MISOSPI=9 ; -D STATUSLED=15 + ${esp32.AR_build_flags} 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} + ${esp32.AR_lib_deps} 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..d7d41f3a6 --- /dev/null +++ b/platformio_override.sample.ini @@ -0,0 +1,494 @@ +# 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 on classic ESP32 rev.1 (rev.3 or above has no issues) +; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue +; +; 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 dda6634a1..11c1733f8 100644 --- a/readme.md +++ b/readme.md @@ -3,7 +3,7 @@ - + @@ -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 @@ -66,7 +66,7 @@ Credits [here](https://kno.wled.ge/about/contributors/)! Join the Discord server to discuss everything about WLED! - + Check out the WLED [Discourse forum](https://wled.discourse.group)! diff --git a/requirements.txt b/requirements.txt index bc536ed07..d6f86e202 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 # @@ -12,7 +12,7 @@ anyio==3.6.2 # via starlette bottle==0.12.25 # via platformio -certifi==2022.12.7 +certifi==2023.7.22 # via requests charset-normalizer==3.1.0 # via requests @@ -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 @@ -34,7 +36,7 @@ marshmallow==3.19.0 # via platformio packaging==23.1 # via marshmallow -platformio==6.1.6 +platformio==6.1.14 # via -r requirements.in pyelftools==0.29 # via platformio @@ -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/WLED_ESP32-wrover_4MB.csv b/tools/WLED_ESP32-wrover_4MB.csv index a179a89d0..39c88e543 100644 --- a/tools/WLED_ESP32-wrover_4MB.csv +++ b/tools/WLED_ESP32-wrover_4MB.csv @@ -1,6 +1,6 @@ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, -app0, app, ota_0, 0x10000, 0x180000, -app1, app, ota_1, 0x190000,0x180000, -spiffs, data, spiffs, 0x310000,0xF0000, +app0, app, ota_0, 0x10000, 0x1A0000, +app1, app, ota_1, 0x1B0000,0x1A0000, +spiffs, data, spiffs, 0x350000,0xB0000, diff --git a/tools/WLED_ESP32_16MB_9MB_FS.csv b/tools/WLED_ESP32_16MB_9MB_FS.csv new file mode 100644 index 000000000..f2f3f7783 --- /dev/null +++ b/tools/WLED_ESP32_16MB_9MB_FS.csv @@ -0,0 +1,8 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x300000, +app1, app, ota_1, 0x310000,0x300000, +spiffs, data, spiffs, 0x610000,0x9E0000, +coredump, data, coredump,,64K +# to create/use ffat, see https://github.com/marcmerlin/esp32_fatfsimage \ No newline at end of file diff --git a/tools/WLED_ESP32_8MB.csv b/tools/WLED_ESP32_8MB.csv index 5e930b89a..3cf3afc34 100644 --- a/tools/WLED_ESP32_8MB.csv +++ b/tools/WLED_ESP32_8MB.csv @@ -3,4 +3,5 @@ nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, app0, app, ota_0, 0x10000, 0x200000, app1, app, ota_1, 0x210000,0x200000, -spiffs, data, spiffs, 0x410000,0x3F0000, \ No newline at end of file +spiffs, data, spiffs, 0x410000,0x3E0000, +coredump, data, coredump,,64K diff --git a/tools/cdata-test.js b/tools/cdata-test.js new file mode 100644 index 000000000..6f27fb717 --- /dev/null +++ b/tools/cdata-test.js @@ -0,0 +1,212 @@ +'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"); + fs.cpSync("package.json", "package.bak.json"); + }); + 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"); + fs.rmSync("package.json"); + fs.renameSync("package.bak.json", "package.json"); + }); + + // 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'); + + await 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'); + }); + + it('package.json changes', async () => { + await testFileModification('package.json', '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 90619ba67..12dda1cbe 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -2,7 +2,7 @@ * Writes compressed C arrays of data files (web interface) * How to use it? * - * 1) Install Node 11+ and npm + * 1) Install Node 20+ and npm * 2) npm install * 3) npm run build * @@ -15,26 +15,58 @@ * It uses NodeJS packages to inline, minify and GZIP files. See writeHtmlGzipped and writeChunks invocations at the bottom of the page. */ -const fs = require("fs"); +const fs = require("node:fs"); +const path = require("path"); const inliner = require("inliner"); -const zlib = require("zlib"); +const zlib = require("node: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\t\x1b[36m build 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 @@ -51,204 +83,163 @@ 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 = ${result.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) => { - try { - console.info("Reading " + srcDir + "/" + s.file + " as " + s.name); - src += specToChunk(srcDir, s); - } catch (e) { - console.warn( - "Failed " + s.name + " from " + srcDir + "/" + s.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) { + 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 +function isAnyFileInFolderNewerThan(folderPath, time) { + const files = fs.readdirSync(folderPath, { withFileTypes: true }); + for (const file of files) { + const filePath = path.join(folderPath, file.name); + if (isFileNewerThan(filePath, time)) { + return true; + } + if (file.isDirectory() && isAnyFileInFolderNewerThan(filePath, time)) { + return true; + } + } + return false; +} + +// Check if the web UI is already built +function isAlreadyBuilt(webUIPath, packageJsonPath = "package.json") { + let lastBuildTime = Infinity; + + for (const file of output) { + try { + lastBuildTime = Math.min(lastBuildTime, fs.statSync(file).mtimeMs); + } catch (e) { + if (e.code !== 'ENOENT') throw e; + console.info("File " + file + " does not exist. Rebuilding..."); + return false; + } + } + + return !isAnyFileInFolderNewerThan(webUIPath, lastBuildTime) && !isFileNewerThan(packageJsonPath, lastBuildTime) && !isFileNewerThan(__filename, 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; +} + writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index'); -writeHtmlGzipped("wled00/data/simple.htm", "wled00/html_simple.h", 'simple'); writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart'); writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal'); writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic'); -/* -writeChunks( - "wled00/data", - [ - { - file: "simple.css", - name: "PAGE_simpleCss", - method: "gzip", - filter: "css-minify", - }, - { - file: "simple.js", - name: "PAGE_simpleJs", - method: "gzip", - filter: "js-minify", - }, - { - file: "simple.htm", - name: "PAGE_simple", - method: "gzip", - filter: "html-minify-ui", - } - ], - "wled00/html_simplex.h" -); -*/ + writeChunks( "wled00/data", [ @@ -259,7 +250,7 @@ writeChunks( filter: "css-minify", mangle: (str) => str - .replace("%%","%") + .replace("%%", "%") }, { file: "settings.htm", @@ -406,16 +397,6 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; file: "favicon.ico", name: "favicon", method: "binary", - }, - { - file: "iro.js", - name: "iroJs", - method: "gzip" - }, - { - file: "rangetouch.js", - name: "rangetouchJs", - method: "gzip" } ], "wled00/html_other.h" diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 151cf1d4a..8953756d3 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -25,6 +25,7 @@ class Animated_Staircase : public Usermod { bool useUSSensorBottom = false; // using PIR or UltraSound sensor? unsigned int topMaxDist = 50; // default maximum measured distance in cm, top unsigned int bottomMaxDist = 50; // default maximum measured distance in cm, bottom + bool togglePower = false; // toggle power on/off with staircase on/off /* runtime variables */ bool initDone = false; @@ -90,7 +91,8 @@ class Animated_Staircase : public Usermod { static const char _bottomEcho_pin[]; static const char _topEchoCm[]; static const char _bottomEchoCm[]; - + static const char _togglePower[]; + void publishMqtt(bool bottom, const char* state) { #ifndef WLED_DISABLE_MQTT //Check if MQTT Connected, otherwise it will crash the 8266 @@ -131,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. * @@ -196,6 +198,7 @@ class Animated_Staircase : public Usermod { if (on) { lastSensor = topSensorRead; } else { + if (togglePower && onIndex == offIndex && offMode) toggleOnOff(); // toggle power on if off // If the bottom sensor triggered, we need to swipe up, ON swipe = bottomSensorRead; @@ -249,11 +252,14 @@ class Animated_Staircase : public Usermod { offIndex = MAX(onIndex, offIndex - 1); } } - if (oldOn != onIndex || oldOff != offIndex) updateSegments(); // reduce the number of updates to necessary ones + if (oldOn != onIndex || oldOff != offIndex) { + updateSegments(); // reduce the number of updates to necessary ones + if (togglePower && onIndex == offIndex && !offMode && !on) toggleOnOff(); // toggle power off for all segments off + } } } - // 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; @@ -291,10 +297,11 @@ class Animated_Staircase : public Usermod { offIndex = maxSegmentId = strip.getLastActiveSegmentId() + 1; // shorten the strip transition time to be equal or shorter than segment delay - transitionDelayTemp = transitionDelay = segment_delay_ms; - strip.setTransition(segment_delay_ms/100); + transitionDelay = segment_delay_ms; + strip.setTransition(segment_delay_ms); strip.trigger(); } else { + if (togglePower && !on && offMode) toggleOnOff(); // toggle power on if off // Restore segment options for (int i = 0; i <= strip.getLastActiveSegmentId(); i++) { Segment &seg = strip.getSegment(i); @@ -302,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.")); } @@ -444,6 +451,7 @@ class Animated_Staircase : public Usermod { staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1; staircase[FPSTR(_topEchoCm)] = topMaxDist; staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist; + staircase[FPSTR(_togglePower)] = togglePower; DEBUG_PRINTLN(F("Staircase config saved.")); } @@ -484,10 +492,12 @@ 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) + togglePower = top[FPSTR(_togglePower)] | togglePower; // staircase toggles power on/off + DEBUG_PRINT(FPSTR(_name)); if (!initDone) { // first run: reading from cfg.json @@ -511,7 +521,7 @@ class Animated_Staircase : public Usermod { if (changed) setup(); } // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return true; + return !top[FPSTR(_togglePower)].isNull(); } /* @@ -551,3 +561,4 @@ const char Animated_Staircase::_bottomPIRorTrigger_pin[] PROGMEM = "bottomPIR const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin"; const char Animated_Staircase::_topEchoCm[] PROGMEM = "top-dist-cm"; const char Animated_Staircase::_bottomEchoCm[] PROGMEM = "bottom-dist-cm"; +const char Animated_Staircase::_togglePower[] PROGMEM = "toggle-on-off"; 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 43648b588..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 @@ -87,7 +87,7 @@ class MyExampleUsermod : public Usermod { * readFromConfig() is called prior to setup() * You can use it to initialize variables, sensors or similar. */ - void setup() { + void setup() override { // do your set-up here //Serial.println("Hello from my usermod!"); initDone = true; @@ -98,7 +98,7 @@ class MyExampleUsermod : public Usermod { * connected() is called every time the WiFi is (re)connected * Use it to initialize network interfaces */ - void connected() { + void connected() override { //Serial.println("Connected to WiFi!"); } @@ -113,7 +113,7 @@ class MyExampleUsermod : public Usermod { * 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() { + void loop() override { // if usermod is disabled or called during strip updating just exit // NOTE: on very long strips strip.isUpdating() may always return true so update accordingly if (!enabled || strip.isUpdating()) return; @@ -131,7 +131,7 @@ class MyExampleUsermod : public Usermod { * 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) + void addToJsonInfo(JsonObject& root) override { // if "u" object does not exist yet wee need to create it JsonObject user = root["u"]; @@ -156,7 +156,7 @@ class MyExampleUsermod : public Usermod { * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void addToJsonState(JsonObject& root) + void addToJsonState(JsonObject& root) override { if (!initDone || !enabled) return; // prevent crash on boot applyPreset() @@ -171,7 +171,7 @@ class MyExampleUsermod : public Usermod { * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void readFromJsonState(JsonObject& root) + void readFromJsonState(JsonObject& root) override { if (!initDone) return; // prevent crash on boot applyPreset() @@ -220,7 +220,7 @@ class MyExampleUsermod : public Usermod { * * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ - void addToConfig(JsonObject& root) + void addToConfig(JsonObject& root) override { JsonObject top = root.createNestedObject(FPSTR(_name)); top[FPSTR(_enabled)] = enabled; @@ -253,7 +253,7 @@ class MyExampleUsermod : public Usermod { * * This function is guaranteed to be called on boot, but could also be called every time settings are updated */ - bool readFromConfig(JsonObject& root) + bool readFromConfig(JsonObject& root) override { // 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) @@ -285,7 +285,7 @@ class MyExampleUsermod : public Usermod { * it may add additional metadata for certain entry fields (adding drop down is possible) * be careful not to add too much as oappend() buffer is limited to 3k */ - void appendConfigData() + void appendConfigData() override { oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":great")); oappend(SET_F("',1,'(this is a great config value)');")); oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":testString")); oappend(SET_F("',1,'enter any string you want');")); @@ -300,7 +300,7 @@ class MyExampleUsermod : public Usermod { * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. * Commonly used for custom clocks (Cronixie, 7 segment) */ - void handleOverlayDraw() + void handleOverlayDraw() override { //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black } @@ -311,7 +311,7 @@ class MyExampleUsermod : public Usermod { * will prevent button working in a default way. * Replicating button.cpp */ - bool handleButton(uint8_t b) { + bool handleButton(uint8_t b) override { yield(); // ignore certain button types as they may have other consequences if (!enabled @@ -334,7 +334,7 @@ class MyExampleUsermod : public Usermod { * handling of MQTT message * topic only contains stripped topic (part after /wled/MAC) */ - bool onMqttMessage(char* topic, char* payload) { + bool onMqttMessage(char* topic, char* payload) override { // check if we received a command //if (strlen(topic) == 8 && strncmp_P(topic, PSTR("/command"), 8) == 0) { // String action = payload; @@ -355,7 +355,7 @@ class MyExampleUsermod : public Usermod { /** * onMqttConnect() is called when MQTT connection is established */ - void onMqttConnect(bool sessionPresent) { + void onMqttConnect(bool sessionPresent) override { // do any MQTT related initialisation here //publishMqtt("I am alive!"); } @@ -366,7 +366,7 @@ class MyExampleUsermod : public Usermod { * onStateChanged() is used to detect WLED state change * @mode parameter is CALL_MODE_... parameter used for notifications */ - void onStateChange(uint8_t mode) { + void onStateChange(uint8_t mode) override { // do something if WLED state changed (color, brightness, effect, preset, etc) } @@ -375,7 +375,7 @@ class MyExampleUsermod : public Usermod { * 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() + uint16_t getId() override { return USERMOD_ID_EXAMPLE; } 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/readme.md b/usermods/Internal_Temperature_v2/readme.md new file mode 100644 index 000000000..58a9e1939 --- /dev/null +++ b/usermods/Internal_Temperature_v2/readme.md @@ -0,0 +1,17 @@ +# Internal Temperature Usermod +This usermod adds the temperature readout to the Info tab and also publishes that over the topic `mcutemp` topic. + +## Important +A shown temp of 53,33°C might indicate that the internal temp is not supported. + +ESP8266 does not have a internal temp sensor + +ESP32S2 seems to crash on reading the sensor -> disabled + +## Installation +Add a build flag `-D USERMOD_INTERNAL_TEMPERATURE` to your `platformio.ini` (or `platformio_override.ini`). + +## Authors +Soeren Willrodt [@lost-hope](https://github.com/lost-hope) + +Dimitry Zhemkov [@dima-zhemkov](https://github.com/dima-zhemkov) \ No newline at end of file diff --git a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h new file mode 100644 index 000000000..3989e7668 --- /dev/null +++ b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h @@ -0,0 +1,117 @@ +#pragma once + +#include "wled.h" + +class InternalTemperatureUsermod : public Usermod +{ + +private: + unsigned long loopInterval = 10000; + unsigned long lastTime = 0; + bool isEnabled = false; + float temperature = 0; + + static const char _name[]; + static const char _enabled[]; + static const char _loopInterval[]; + + // 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: + void setup() + { + } + + void loop() + { + // if usermod is disabled or called during strip updating just exit + // NOTE: on very long strips strip.isUpdating() may always return true so update accordingly + if (!isEnabled || strip.isUpdating() || millis() - lastTime <= loopInterval) + return; + + lastTime = millis(); + +#ifdef ESP8266 // ESP8266 + // does not seem possible + temperature = -1; +#elif defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32S2 + temperature = -1; +#else // ESP32 ESP32S3 and ESP32C3 + temperature = roundf(temperatureRead() * 10) / 10; +#endif + +#ifndef WLED_DISABLE_MQTT + if (WLED_MQTT_CONNECTED) + { + char array[10]; + snprintf(array, sizeof(array), "%f", temperature); + publishMqtt(array); + } +#endif + } + + void addToJsonInfo(JsonObject &root) + { + if (!isEnabled) + return; + + // if "u" object does not exist yet wee need to create it + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + + JsonArray userTempArr = user.createNestedArray(FPSTR(_name)); + userTempArr.add(temperature); + userTempArr.add(F(" °C")); + + // if "sensor" object does not exist yet wee need to create it + JsonObject sensor = root[F("sensor")]; + if (sensor.isNull()) + sensor = root.createNestedObject(F("sensor")); + + JsonArray sensorTempArr = sensor.createNestedArray(FPSTR(_name)); + sensorTempArr.add(temperature); + sensorTempArr.add(F("°C")); + } + + void addToConfig(JsonObject &root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = isEnabled; + top[FPSTR(_loopInterval)] = loopInterval; + } + + bool readFromConfig(JsonObject &root) + { + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); + configComplete &= getJsonValue(top[FPSTR(_enabled)], isEnabled); + configComplete &= getJsonValue(top[FPSTR(_loopInterval)], loopInterval); + + return configComplete; + } + + uint16_t getId() + { + return USERMOD_ID_INTERNAL_TEMPERATURE; + } +}; + +const char InternalTemperatureUsermod::_name[] PROGMEM = "Internal Temperature"; +const char InternalTemperatureUsermod::_enabled[] PROGMEM = "Enabled"; +const char InternalTemperatureUsermod::_loopInterval[] PROGMEM = "Loop Interval"; + +void InternalTemperatureUsermod::publishMqtt(const char *state, bool retain) +{ +#ifndef WLED_DISABLE_MQTT + // Check if MQTT Connected, otherwise it will crash the 8266 + if (WLED_MQTT_CONNECTED) + { + char subuf[64]; + strcpy(subuf, mqttDeviceTopic); + strcat_P(subuf, PSTR("/mcutemp")); + mqtt->publish(subuf, 0, retain, state); + } +#endif +} \ No newline at end of file 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/MAX17048_v2/readme.md b/usermods/MAX17048_v2/readme.md new file mode 100644 index 000000000..958e6def2 --- /dev/null +++ b/usermods/MAX17048_v2/readme.md @@ -0,0 +1,64 @@ +# Adafruit MAX17048 Usermod (LiPo & LiIon Battery Monitor & Fuel Gauge) +This usermod reads information from an Adafruit MAX17048 and outputs the following: +- Battery Voltage +- Battery Level Percentage + + +## Dependencies +Libraries: +- `Adafruit_BusIO@~1.14.5` (by [adafruit](https://github.com/adafruit/Adafruit_BusIO)) +- `Adafruit_MAX1704X@~1.0.2` (by [adafruit](https://github.com/adafruit/Adafruit_MAX1704X)) + +These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). +Data is published over MQTT - make sure you've enabled the MQTT sync interface. + +## Compilation + +To enable, compile with `USERMOD_MAX17048` define in the build_flags (e.g. in `platformio.ini` or `platformio_override.ini`) such as in the example below: +```ini +[env:usermod_max17048_d1_mini] +extends = env:d1_mini +build_flags = + ${common.build_flags_esp8266} + -D USERMOD_MAX17048 +lib_deps = + ${esp8266.lib_deps} + https://github.com/adafruit/Adafruit_BusIO @ 1.14.5 + https://github.com/adafruit/Adafruit_MAX1704X @ 1.0.2 +``` + +### Configuration Options +The following settings can be set at compile-time but are configurable on the usermod menu (except First Monitor time): +- USERMOD_MAX17048_MIN_MONITOR_INTERVAL (the min number of milliseconds between checks, defaults to 10,000 ms) +- USERMOD_MAX17048_MAX_MONITOR_INTERVAL (the max number of milliseconds between checks, defaults to 10,000 ms) +- USERMOD_MAX17048_FIRST_MONITOR_AT + + +Additionally, the Usermod Menu allows you to: +- Enable or Disable the usermod +- Enable or Disable Home Assistant Discovery (turn on/off to sent MQTT Discovery entries for Home Assistant) +- Configure SCL/SDA GPIO Pins + +## API +The following method is available to interact with the usermod from other code modules: +- `getBatteryVoltageV` read the last battery voltage (in Volt) obtained from the sensor +- `getBatteryPercent` reads the last battery percentage obtained from the sensor + +## MQTT +MQTT topics are as follows (`` is set in MQTT section of Sync Setup menu): +Measurement type | MQTT topic +--- | --- +Battery Voltage | `/batteryVoltage` +Battery Percent | `/batteryPercent` + +## Authors +Carlos Cruz [@ccruz09](https://github.com/ccruz09) + + +## Revision History +Jan 2024 +- Added Home Assistant Discovery +- Implemented PinManager to register pins +- Added API call for other modules to read battery voltage and percentage +- Added info-screen outputs +- Updated `readme.md` \ No newline at end of file diff --git a/usermods/MAX17048_v2/usermod_max17048.h b/usermods/MAX17048_v2/usermod_max17048.h new file mode 100644 index 000000000..c3a2664ab --- /dev/null +++ b/usermods/MAX17048_v2/usermod_max17048.h @@ -0,0 +1,281 @@ +// force the compiler to show a warning to confirm that this file is included +#warning **** Included USERMOD_MAX17048 V2.0 **** + +#pragma once + +#include "wled.h" +#include "Adafruit_MAX1704X.h" + + +// the max interval to check battery level, 10 seconds +#ifndef USERMOD_MAX17048_MAX_MONITOR_INTERVAL +#define USERMOD_MAX17048_MAX_MONITOR_INTERVAL 10000 +#endif + +// the min interval to check battery level, 500 ms +#ifndef USERMOD_MAX17048_MIN_MONITOR_INTERVAL +#define USERMOD_MAX17048_MIN_MONITOR_INTERVAL 500 +#endif + +// how many seconds after boot to perform the first check, 10 seconds +#ifndef USERMOD_MAX17048_FIRST_MONITOR_AT +#define USERMOD_MAX17048_FIRST_MONITOR_AT 10000 +#endif + +/* + * Usermod to display Battery Life using Adafruit's MAX17048 LiPoly/ LiIon Fuel Gauge and Battery Monitor. + */ +class Usermod_MAX17048 : public Usermod { + + private: + + bool enabled = true; + + unsigned long maxReadingInterval = USERMOD_MAX17048_MAX_MONITOR_INTERVAL; + unsigned long minReadingInterval = USERMOD_MAX17048_MIN_MONITOR_INTERVAL; + unsigned long lastCheck = UINT32_MAX - (USERMOD_MAX17048_MAX_MONITOR_INTERVAL - USERMOD_MAX17048_FIRST_MONITOR_AT); + unsigned long lastSend = UINT32_MAX - (USERMOD_MAX17048_MAX_MONITOR_INTERVAL - USERMOD_MAX17048_FIRST_MONITOR_AT); + + + uint8_t VoltageDecimals = 3; // Number of decimal places in published voltage values + uint8_t PercentDecimals = 1; // Number of decimal places in published percent values + + // string that are used multiple time (this will save some flash memory) + static const char _name[]; + static const char _enabled[]; + static const char _maxReadInterval[]; + static const char _minReadInterval[]; + static const char _HomeAssistantDiscovery[]; + + bool monitorFound = false; + bool firstReadComplete = false; + bool initDone = false; + + Adafruit_MAX17048 maxLipo; + float lastBattVoltage = -10; + float lastBattPercent = -1; + + // MQTT and Home Assistant Variables + bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information + bool mqttInitialized = false; + + void _mqttInitialize() + { + char mqttBatteryVoltageTopic[128]; + char mqttBatteryPercentTopic[128]; + + snprintf_P(mqttBatteryVoltageTopic, 127, PSTR("%s/batteryVoltage"), mqttDeviceTopic); + snprintf_P(mqttBatteryPercentTopic, 127, PSTR("%s/batteryPercent"), mqttDeviceTopic); + + if (HomeAssistantDiscovery) { + _createMqttSensor(F("BatteryVoltage"), mqttBatteryVoltageTopic, "voltage", F("V")); + _createMqttSensor(F("BatteryPercent"), mqttBatteryPercentTopic, "battery", F("%")); + } + } + + void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) + { + String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config"); + + StaticJsonDocument<600> doc; + + doc[F("name")] = String(serverDescription) + " " + name; + doc[F("state_topic")] = topic; + doc[F("unique_id")] = String(mqttClientID) + name; + if (unitOfMeasurement != "") + doc[F("unit_of_measurement")] = unitOfMeasurement; + if (deviceClass != "") + doc[F("device_class")] = deviceClass; + doc[F("expire_after")] = 1800; + + JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device + device[F("name")] = serverDescription; + device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); + device[F("manufacturer")] = F("WLED"); + device[F("model")] = F("FOSS"); + device[F("sw_version")] = versionString; + + String temp; + serializeJson(doc, temp); + DEBUG_PRINTLN(t); + DEBUG_PRINTLN(temp); + + mqtt->publish(t.c_str(), 0, true, temp.c_str()); + } + + void publishMqtt(const char *topic, const char* state) { + #ifndef WLED_DISABLE_MQTT + //Check if MQTT Connected, otherwise it will crash the 8266 + if (WLED_MQTT_CONNECTED){ + char subuf[128]; + snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, topic); + mqtt->publish(subuf, 0, false, state); + } + #endif + } + + public: + + inline void enable(bool enable) { enabled = enable; } + + inline bool isEnabled() { return enabled; } + + void setup() { + // do your set-up here + if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; } + monitorFound = maxLipo.begin(); + initDone = true; + } + + void loop() { + // if usermod is disabled or called during strip updating just exit + // NOTE: on very long strips strip.isUpdating() may always return true so update accordingly + if (!enabled || strip.isUpdating()) return; + + unsigned long now = millis(); + + if (now - lastCheck < minReadingInterval) { return; } + + bool shouldUpdate = now - lastSend > maxReadingInterval; + + float battVoltage = maxLipo.cellVoltage(); + float battPercent = maxLipo.cellPercent(); + lastCheck = millis(); + firstReadComplete = true; + + if (shouldUpdate) + { + lastBattVoltage = roundf(battVoltage * powf(10, VoltageDecimals)) / powf(10, VoltageDecimals); + lastBattPercent = roundf(battPercent * powf(10, PercentDecimals)) / powf(10, PercentDecimals); + lastSend = millis(); + + publishMqtt("batteryVoltage", String(lastBattVoltage, VoltageDecimals).c_str()); + publishMqtt("batteryPercent", String(lastBattPercent, PercentDecimals).c_str()); + DEBUG_PRINTLN(F("Battery Voltage: ") + String(lastBattVoltage, VoltageDecimals) + F("V")); + DEBUG_PRINTLN(F("Battery Percent: ") + String(lastBattPercent, PercentDecimals) + F("%")); + } + } + + void onMqttConnect(bool sessionPresent) + { + if (WLED_MQTT_CONNECTED && !mqttInitialized) + { + _mqttInitialize(); + mqttInitialized = true; + } + } + + inline float getBatteryVoltageV() { + return (float) lastBattVoltage; + } + + inline float getBatteryPercent() { + return (float) lastBattPercent; + } + + void addToJsonInfo(JsonObject& root) + { + // if "u" object does not exist yet wee need to create it + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + + JsonArray battery_json = user.createNestedArray(F("Battery Monitor")); + if (!enabled) { + battery_json.add(F("Disabled")); + } + else if(!monitorFound) { + battery_json.add(F("MAX17048 Not Found")); + } + else if (!firstReadComplete) { + // if we haven't read the sensor yet, let the user know + // that we are still waiting for the first measurement + battery_json.add((USERMOD_MAX17048_FIRST_MONITOR_AT - millis()) / 1000); + battery_json.add(F(" sec until read")); + } else { + battery_json.add(F("Enabled")); + JsonArray voltage_json = user.createNestedArray(F("Battery Voltage")); + voltage_json.add(lastBattVoltage); + voltage_json.add(F("V")); + JsonArray percent_json = user.createNestedArray(F("Battery Percent")); + percent_json.add(lastBattPercent); + percent_json.add(F("%")); + } + } + + void addToJsonState(JsonObject& root) + { + JsonObject usermod = root[FPSTR(_name)]; + if (usermod.isNull()) + { + usermod = root.createNestedObject(FPSTR(_name)); + } + usermod[FPSTR(_enabled)] = enabled; + } + + void readFromJsonState(JsonObject& root) + { + JsonObject usermod = root[FPSTR(_name)]; + if (!usermod.isNull()) + { + if (usermod[FPSTR(_enabled)].is()) + { + enabled = usermod[FPSTR(_enabled)].as(); + } + } + } + + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_maxReadInterval)] = maxReadingInterval; + top[FPSTR(_minReadInterval)] = minReadingInterval; + top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; + DEBUG_PRINT(F(_name)); + DEBUG_PRINTLN(F(" config saved.")); + } + + bool readFromConfig(JsonObject& root) + { + JsonObject top = root[FPSTR(_name)]; + + if (top.isNull()) { + DEBUG_PRINT(F(_name)); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + bool configComplete = !top.isNull(); + + configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); + configComplete &= getJsonValue(top[FPSTR(_maxReadInterval)], maxReadingInterval, USERMOD_MAX17048_MAX_MONITOR_INTERVAL); + configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, USERMOD_MAX17048_MIN_MONITOR_INTERVAL); + configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); + + DEBUG_PRINT(FPSTR(_name)); + if (!initDone) { + // first run: reading from cfg.json + DEBUG_PRINTLN(F(" config loaded.")); + } else { + DEBUG_PRINTLN(F(" config (re)loaded.")); + // changing parameters from settings page + } + + return configComplete; + } + + uint16_t getId() + { + return USERMOD_ID_MAX17048; + } + +}; + + +// add more strings here to reduce flash memory usage +const char Usermod_MAX17048::_name[] PROGMEM = "Adafruit MAX17048 Battery Monitor"; +const char Usermod_MAX17048::_enabled[] PROGMEM = "enabled"; +const char Usermod_MAX17048::_maxReadInterval[] PROGMEM = "max-read-interval-ms"; +const char Usermod_MAX17048::_minReadInterval[] PROGMEM = "min-read-interval-ms"; +const char Usermod_MAX17048::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscovery"; 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 8a4b9a608..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,14 +53,13 @@ private: volatile unsigned long offTimerStart = 0; // off timer start time volatile bool PIRtriggered = false; // did PIR trigger? - byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE - byte sensorPinState = LOW; // current PIR sensor pin state - bool initDone = false; // status of initialization - 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 @@ -70,6 +72,7 @@ private: // Home Assistant bool HomeAssistantDiscovery = false; // is HA discovery turned on + int16_t idx = -1; // Domoticz virtual switch idx // strings to reduce flash memory usage (used more than twice) static const char _name[]; @@ -81,8 +84,8 @@ private: static const char _mqttOnly[]; static const char _offOnly[]; static const char _haDiscovery[]; - static const char _notify[]; static const char _override[]; + static const char _domoticzIDX[]; /** * check if it is daytime @@ -94,14 +97,14 @@ private: * switch strip on/off */ void switchStrip(bool switchOn); - void publishMqtt(const char* state); + void publishMqtt(bool switchOn); // Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. void publishHomeAssistantAutodiscovery(); /** * Read and update PIR sensor state. - * Initilize/reset switch off timer + * Initialize/reset switch off timer */ bool updatePIRsensorState(); @@ -117,7 +120,7 @@ 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(); + void setup() override; /** * connected() is called every time the WiFi is (re)connected @@ -128,24 +131,24 @@ public: /** * onMqttConnect() is called when MQTT connection is established */ - void onMqttConnect(bool sessionPresent); + void onMqttConnect(bool sessionPresent) override; /** * loop() is called continuously. Here you can check for events, read sensors, etc. */ - void loop(); + void loop() override; /** * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * * Add PIR sensor state and switch off timer duration to jsoninfo */ - void addToJsonInfo(JsonObject &root); + void addToJsonInfo(JsonObject &root) override; /** * onStateChanged() is used to detect WLED state change */ - void onStateChange(uint8_t mode); + void onStateChange(uint8_t mode) override; /** * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). @@ -157,17 +160,17 @@ public: * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void readFromJsonState(JsonObject &root); + void readFromJsonState(JsonObject &root) override; /** * provide the changeable values */ - void addToConfig(JsonObject &root); + void addToConfig(JsonObject &root) override; /** * provide UI information and allow extending UI options */ - void appendConfigData(); + void appendConfigData() override; /** * restore the changeable values @@ -175,13 +178,13 @@ public: * * The function should return true if configuration was successfully loaded or false if there was no configuration. */ - bool readFromConfig(JsonObject &root); + bool readFromConfig(JsonObject &root) override; /** * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). * This could be used in the future for the system to determine whether your usermod is installed. */ - uint16_t getId() { return USERMOD_ID_PIRSWITCH; } + uint16_t getId() override { return USERMOD_ID_PIRSWITCH; } }; // strings to reduce flash memory usage (used more than twice) @@ -194,8 +197,8 @@ const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only"; const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only"; const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only"; const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery"; -const char PIRsensorSwitch::_notify[] PROGMEM = "notifications"; const char PIRsensorSwitch::_override[] PROGMEM = "override"; +const char PIRsensorSwitch::_domoticzIDX[] PROGMEM = "domoticz-idx"; bool PIRsensorSwitch::isDayTime() { updateLocalTime(); @@ -235,24 +238,24 @@ void PIRsensorSwitch::switchStrip(bool switchOn) prevPlaylist = 0; prevPreset = 255; } - applyPreset(m_onPreset, NotifyUpdateMode); + applyPreset(m_onPreset, CALL_MODE_BUTTON_PRESET); return; } // preset not assigned if (bri == 0) { bri = briLast; - stateUpdated(NotifyUpdateMode); + stateUpdated(CALL_MODE_BUTTON); } } else { if (m_offPreset) { - applyPreset(m_offPreset, NotifyUpdateMode); + applyPreset(m_offPreset, CALL_MODE_BUTTON_PRESET); return; } else if (prevPlaylist) { - if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, NotifyUpdateMode); + if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, CALL_MODE_BUTTON_PRESET); prevPlaylist = 0; return; } else if (prevPreset) { - if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, NotifyUpdateMode); } + if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, CALL_MODE_BUTTON_PRESET); } else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); } prevPreset = 0; return; @@ -261,19 +264,29 @@ void PIRsensorSwitch::switchStrip(bool switchOn) if (bri != 0) { briLast = bri; bri = 0; - stateUpdated(NotifyUpdateMode); + stateUpdated(CALL_MODE_BUTTON); } } } -void PIRsensorSwitch::publishMqtt(const char* state) +void PIRsensorSwitch::publishMqtt(bool switchOn) { #ifndef WLED_DISABLE_MQTT //Check if MQTT Connected, otherwise it will crash the 8266 if (WLED_MQTT_CONNECTED) { - char buf[64]; + char buf[128]; sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40 - mqtt->publish(buf, 0, false, state); + mqtt->publish(buf, 0, false, switchOn?"on":"off"); + // Domoticz formatted message + if (idx > 0) { + StaticJsonDocument <128> msg; + msg[F("idx")] = idx; + msg[F("RSSI")] = WiFi.RSSI(); + msg[F("command")] = F("switchlight"); + msg[F("switchcmd")] = switchOn ? F("On") : F("Off"); + serializeJson(msg, buf, 128); + mqtt->publish("domoticz/in", 0, false, buf); + } } #endif } @@ -299,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); @@ -315,34 +328,36 @@ 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 if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); - publishMqtt("on"); - } else { - // start switch off timer - offTimerStart = millis(); - if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); + 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); + } } - return true; } - return false; + if (stateChanged) { + publishMqtt(!allOff); + // start switch off timer + if (allOff) offTimerStart = millis(); + } + return stateChanged; } bool PIRsensorSwitch::handleOffTimer() { if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) { offTimerStart = 0; - if (enabled == true) { - if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()) || PIRtriggered)) switchStrip(false); - else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); - publishMqtt("off"); - } + if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()) || PIRtriggered)) switchStrip(false); return true; } return false; @@ -352,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; @@ -378,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()) { @@ -392,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")); @@ -442,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) { @@ -474,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; @@ -482,21 +501,28 @@ void PIRsensorSwitch::addToConfig(JsonObject &root) top[FPSTR(_offOnly)] = m_offOnly; top[FPSTR(_override)] = m_override; top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery; - top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY); + top[FPSTR(_domoticzIDX)] = idx; DEBUG_PRINTLN(F("PIR config saved.")); } void PIRsensorSwitch::appendConfigData() { oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field + 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)]; @@ -505,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; @@ -521,33 +553,17 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) m_offOnly = top[FPSTR(_offOnly)] | m_offOnly; m_override = top[FPSTR(_override)] | m_override; HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery; - - NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY; + idx = top[FPSTR(_domoticzIDX)] | idx; if (!initDone) { // reading config prior to setup() DEBUG_PRINTLN(F(" config loaded.")); } else { - if (oldPin != PIRsensorPin || oldEnabled != enabled) { - // check if pin is OK - if (oldPin != PIRsensorPin && oldPin >= 0) { - // if we are changing pin in settings page - // deallocate old pin - pinManager.deallocatePin(oldPin, PinOwner::UM_PIR); - if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { - pinMode(PIRsensorPin, INPUT_PULLUP); - } else { - // allocation failed - PIRsensorPin = -1; - enabled = false; - } - } - if (enabled) { - sensorPinState = digitalRead(PIRsensorPin); - } - } + 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(_override)].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/PWM_fan/usermod_PWM_fan.h b/usermods/PWM_fan/usermod_PWM_fan.h index f7fe0e10f..1b78cfd4c 100644 --- a/usermods/PWM_fan/usermod_PWM_fan.h +++ b/usermods/PWM_fan/usermod_PWM_fan.h @@ -52,9 +52,15 @@ class PWMFanUsermod : public Usermod { uint8_t tachoUpdateSec = 30; float targetTemperature = 35.0; uint8_t minPWMValuePct = 0; + uint8_t maxPWMValuePct = 100; uint8_t numberOfInterrupsInOneSingleRotation = 2; // Number of interrupts ESP32 sees on tacho signal on a single fan rotation. All the fans I've seen trigger two interrups. uint8_t pwmValuePct = 0; + // constant values + static const uint8_t _pwmMaxValue = 255; + static const uint8_t _pwmMaxStepCount = 7; + float _pwmTempStepSize = 0.5f; + // strings to reduce flash memory usage (used more than twice) static const char _name[]; static const char _enabled[]; @@ -63,6 +69,7 @@ class PWMFanUsermod : public Usermod { static const char _temperature[]; static const char _tachoUpdateSec[]; static const char _minPWMValuePct[]; + static const char _maxPWMValuePct[]; static const char _IRQperRotation[]; static const char _speed[]; static const char _lock[]; @@ -156,38 +163,32 @@ class PWMFanUsermod : public Usermod { void setFanPWMbasedOnTemperature(void) { float temp = getActualTemperature(); - float difftemp = temp - targetTemperature; - // Default to run fan at full speed. - int newPWMvalue = 255; - int pwmStep = ((100 - minPWMValuePct) * newPWMvalue) / (7*100); - int pwmMinimumValue = (minPWMValuePct * newPWMvalue) / 100; + // dividing minPercent and maxPercent into equal pwmvalue sizes + int pwmStepSize = ((maxPWMValuePct - minPWMValuePct) * _pwmMaxValue) / (_pwmMaxStepCount*100); + int pwmStep = calculatePwmStep(temp - targetTemperature); + // minimum based on full speed - not entered MaxPercent + int pwmMinimumValue = (minPWMValuePct * _pwmMaxValue) / 100; + updateFanSpeed(pwmMinimumValue + pwmStep*pwmStepSize); + } - if ((temp == NAN) || (temp <= -100.0)) { + uint8_t calculatePwmStep(float diffTemp){ + if ((diffTemp == NAN) || (diffTemp <= -100.0)) { DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255.")); - } else if (difftemp <= 0.0) { - // Temperature is below target temperature. Run fan at minimum speed. - newPWMvalue = pwmMinimumValue; - } else if (difftemp <= 0.5) { - newPWMvalue = pwmMinimumValue + pwmStep; - } else if (difftemp <= 1.0) { - newPWMvalue = pwmMinimumValue + 2*pwmStep; - } else if (difftemp <= 1.5) { - newPWMvalue = pwmMinimumValue + 3*pwmStep; - } else if (difftemp <= 2.0) { - newPWMvalue = pwmMinimumValue + 4*pwmStep; - } else if (difftemp <= 2.5) { - newPWMvalue = pwmMinimumValue + 5*pwmStep; - } else if (difftemp <= 3.0) { - newPWMvalue = pwmMinimumValue + 6*pwmStep; + return _pwmMaxStepCount; } - updateFanSpeed(newPWMvalue); + if(diffTemp <=0){ + return 0; + } + int calculatedStep = (diffTemp / _pwmTempStepSize)+1; + // anything greater than max stepcount gets max + return (uint8_t)min((int)_pwmMaxStepCount,calculatedStep); } public: // gets called once at boot. Do all initialization that doesn't depend on // network here - void setup() { + void setup() override { #ifdef USERMOD_DALLASTEMPERATURE // This Usermod requires Temperature usermod tempUM = (UsermodTemperature*) usermods.lookup(USERMOD_ID_TEMPERATURE); @@ -202,12 +203,12 @@ class PWMFanUsermod : public Usermod { // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here - void connected() {} + void connected() override {} /* * Da loop. */ - void loop() { + void loop() override { if (!enabled || strip.isUpdating()) return; unsigned long now = millis(); @@ -222,7 +223,7 @@ class PWMFanUsermod : public Usermod { * 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) { + void addToJsonInfo(JsonObject& root) override { JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); @@ -271,7 +272,7 @@ class PWMFanUsermod : public Usermod { * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void readFromJsonState(JsonObject& root) { + void readFromJsonState(JsonObject& root) override { if (!initDone) return; // prevent crash on boot applyPreset() JsonObject usermod = root[FPSTR(_name)]; if (!usermod.isNull()) { @@ -304,7 +305,7 @@ class PWMFanUsermod : public Usermod { * * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ - void addToConfig(JsonObject& root) { + void addToConfig(JsonObject& root) override { JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname top[FPSTR(_enabled)] = enabled; top[FPSTR(_pwmPin)] = pwmPin; @@ -312,6 +313,7 @@ class PWMFanUsermod : public Usermod { top[FPSTR(_tachoUpdateSec)] = tachoUpdateSec; top[FPSTR(_temperature)] = targetTemperature; top[FPSTR(_minPWMValuePct)] = minPWMValuePct; + top[FPSTR(_maxPWMValuePct)] = maxPWMValuePct; top[FPSTR(_IRQperRotation)] = numberOfInterrupsInOneSingleRotation; DEBUG_PRINTLN(F("Autosave config saved.")); } @@ -326,7 +328,7 @@ class PWMFanUsermod : public Usermod { * * The function should return true if configuration was successfully loaded or false if there was no configuration. */ - bool readFromConfig(JsonObject& root) { + bool readFromConfig(JsonObject& root) override { int8_t newTachoPin = tachoPin; int8_t newPwmPin = pwmPin; @@ -345,6 +347,8 @@ class PWMFanUsermod : public Usermod { targetTemperature = top[FPSTR(_temperature)] | targetTemperature; minPWMValuePct = top[FPSTR(_minPWMValuePct)] | minPWMValuePct; minPWMValuePct = (uint8_t) min(100,max(0,(int)minPWMValuePct)); // bounds checking + maxPWMValuePct = top[FPSTR(_maxPWMValuePct)] | maxPWMValuePct; + maxPWMValuePct = (uint8_t) min(100,max((int)minPWMValuePct,(int)maxPWMValuePct)); // bounds checking numberOfInterrupsInOneSingleRotation = top[FPSTR(_IRQperRotation)] | numberOfInterrupsInOneSingleRotation; numberOfInterrupsInOneSingleRotation = (uint8_t) max(1,(int)numberOfInterrupsInOneSingleRotation); // bounds checking @@ -376,7 +380,7 @@ class PWMFanUsermod : public Usermod { * 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() { + uint16_t getId() override { return USERMOD_ID_PWM_FAN; } }; @@ -389,6 +393,7 @@ const char PWMFanUsermod::_pwmPin[] PROGMEM = "PWM-pin"; const char PWMFanUsermod::_temperature[] PROGMEM = "target-temp-C"; const char PWMFanUsermod::_tachoUpdateSec[] PROGMEM = "tacho-update-s"; const char PWMFanUsermod::_minPWMValuePct[] PROGMEM = "min-PWM-percent"; +const char PWMFanUsermod::_maxPWMValuePct[] PROGMEM = "max-PWM-percent"; const char PWMFanUsermod::_IRQperRotation[] PROGMEM = "IRQs-per-rotation"; const char PWMFanUsermod::_speed[] PROGMEM = "speed"; const char PWMFanUsermod::_lock[] PROGMEM = "lock"; 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/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 144cccbfa..281fba25d 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -132,7 +132,7 @@ class St7789DisplayUsermod : public Usermod { * 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() + void setup() override { PinManagerPinType spiPins[] = { { spi_mosi, true }, { spi_miso, false}, { spi_sclk, true } }; if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; } @@ -162,7 +162,7 @@ class St7789DisplayUsermod : public Usermod { * connected() is called every time the WiFi is (re)connected * Use it to initialize network interfaces */ - void connected() { + void connected() override { //Serial.println("Connected to WiFi!"); } @@ -176,7 +176,7 @@ class St7789DisplayUsermod : public Usermod { * 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() { + void loop() override { char buff[LINE_BUFFER_SIZE]; // Check if we time interval for redrawing passes. @@ -307,7 +307,7 @@ class St7789DisplayUsermod : public Usermod { // Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate). tft.print("Current: "); tft.setTextColor(TFT_ORANGE); - tft.print(strip.currentMilliamps); + tft.print(BusManager::currentMilliamps()); tft.print("mA"); } @@ -316,7 +316,7 @@ class St7789DisplayUsermod : public Usermod { * 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) + void addToJsonInfo(JsonObject& root) override { JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); @@ -330,7 +330,7 @@ class St7789DisplayUsermod : public Usermod { * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void addToJsonState(JsonObject& root) + void addToJsonState(JsonObject& root) override { //root["user0"] = userVar0; } @@ -340,7 +340,7 @@ class St7789DisplayUsermod : public Usermod { * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void readFromJsonState(JsonObject& root) + void readFromJsonState(JsonObject& root) override { //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!")); @@ -361,7 +361,7 @@ class St7789DisplayUsermod : public Usermod { * * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ - void addToConfig(JsonObject& root) + void addToConfig(JsonObject& root) override { JsonObject top = root.createNestedObject("ST7789"); JsonArray pins = top.createNestedArray("pin"); @@ -373,7 +373,7 @@ class St7789DisplayUsermod : public Usermod { } - void appendConfigData() { + void appendConfigData() override { oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');")); oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');")); oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');")); @@ -388,7 +388,7 @@ class St7789DisplayUsermod : public Usermod { * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) */ - bool readFromConfig(JsonObject& root) + bool readFromConfig(JsonObject& root) override { //JsonObject top = root["top"]; //userVar0 = top["great"] | 42; //The value right of the pipe "|" is the default value in case your setting was not present in cfg.json (e.g. first boot) @@ -400,7 +400,7 @@ class St7789DisplayUsermod : public Usermod { * 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() + uint16_t getId() override { return USERMOD_ID_ST7789_DISPLAY; } 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 a15baf878..5b6b21d8c 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -48,6 +48,7 @@ class UsermodTemperature : public Usermod { bool enabled = true; bool HApublished = false; + int16_t idx = -1; // Domoticz virtual sensor idx // strings to reduce flash memory usage (used more than twice) static const char _name[]; @@ -55,7 +56,11 @@ class UsermodTemperature : public Usermod { static const char _readInterval[]; 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(); @@ -74,26 +79,26 @@ class UsermodTemperature : public Usermod { inline float getTemperatureF() { return temperature * 1.8f + 32.0f; } float getTemperature(); const char *getTemperatureUnit(); - uint16_t getId() { return USERMOD_ID_TEMPERATURE; } + uint16_t getId() override { return USERMOD_ID_TEMPERATURE; } - void setup(); - void loop(); - //void connected(); + void setup() override; + void loop() override; + //void connected() override; #ifndef WLED_DISABLE_MQTT - void onMqttConnect(bool sessionPresent); + void onMqttConnect(bool sessionPresent) override; #endif - //void onUpdateBegin(bool init); + //void onUpdateBegin(bool init) override; - //bool handleButton(uint8_t b); - //void handleOverlayDraw(); + //bool handleButton(uint8_t b) override; + //void handleOverlayDraw() override; - void addToJsonInfo(JsonObject& root); - //void addToJsonState(JsonObject &root); - //void readFromJsonState(JsonObject &root); - void addToConfig(JsonObject &root); - bool readFromConfig(JsonObject &root); + void addToJsonInfo(JsonObject& root) override; + //void addToJsonState(JsonObject &root) override; + //void readFromJsonState(JsonObject &root) override; + void addToConfig(JsonObject &root) override; + bool readFromConfig(JsonObject &root) override; - void appendConfigData(); + void appendConfigData() override; }; //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 @@ -108,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) { @@ -147,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); } @@ -169,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; } } @@ -189,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); @@ -264,16 +269,25 @@ void UsermodTemperature::loop() { #ifndef WLED_DISABLE_MQTT if (WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[128]; strcpy(subuf, mqttDeviceTopic); if (temperature > -100.0f) { // dont publish super low temperature as the graph will get messed up // the DallasTemperature library returns -127C or -196.6F when problem // reading the sensor - strcat_P(subuf, PSTR("/temperature")); + 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()); + if (idx > 0) { + StaticJsonDocument <128> msg; + msg[F("idx")] = idx; + msg[F("RSSI")] = WiFi.RSSI(); + msg[F("nvalue")] = 0; + msg[F("svalue")] = String(getTemperatureC()); + serializeJson(msg, subuf, 127); + mqtt->publish("domoticz/in", 0, false, subuf); + } } else { // publish something else to indicate status? } @@ -324,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()); } @@ -356,10 +370,11 @@ 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; + top[FPSTR(_domoticzIDX)] = idx; DEBUG_PRINTLN(F("Temperature config saved.")); } @@ -381,11 +396,12 @@ 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; parasitePin = top[FPSTR(_parasitePin)] | parasitePin; + idx = top[FPSTR(_domoticzIDX)] | idx; if (!initDone) { // first run: reading from cfg.json @@ -406,7 +422,7 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { } } // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_parasitePin)].isNull(); + return !top[FPSTR(_domoticzIDX)].isNull(); } void UsermodTemperature::appendConfigData() { @@ -430,3 +446,7 @@ const char UsermodTemperature::_enabled[] PROGMEM = "enabled"; 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/TetrisAI_v2/gridbw.h b/usermods/TetrisAI_v2/gridbw.h new file mode 100644 index 000000000..af3f5bcf0 --- /dev/null +++ b/usermods/TetrisAI_v2/gridbw.h @@ -0,0 +1,117 @@ +/****************************************************************************** + * @file : gridbw.h + * @brief : contains the tetris grid as binary so black and white version + ****************************************************************************** + * @attention + * + * Copyright (c) muebau 2023 + * All rights reserved. + * + ****************************************************************************** +*/ + +#ifndef __GRIDBW_H__ +#define __GRIDBW_H__ + +#include +#include +#include "pieces.h" + +using namespace std; + +class GridBW +{ +private: +public: + uint8_t width; + uint8_t height; + std::vector pixels; + + GridBW(uint8_t width, uint8_t height): + width(width), + height(height), + pixels(height) + { + if (width > 32) + { + throw std::invalid_argument("maximal width is 32"); + } + } + + void placePiece(Piece* piece, uint8_t x, uint8_t y) + { + for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++) + { + pixels[y + (row - (4 - piece->getRotation().height))] |= piece->getGridRow(x, row, width); + } + } + + void erasePiece(Piece* piece, uint8_t x, uint8_t y) + { + for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++) + { + pixels[y + (row - (4 - piece->getRotation().height))] &= ~piece->getGridRow(x, row, width); + } + } + + bool noCollision(Piece* piece, uint8_t x, uint8_t y) + { + //if it touches a wall it is a collision + if (x > (this->width - piece->getRotation().width) || y > this->height - piece->getRotation().height) + { + return false; + } + + for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++) + { + if (piece->getGridRow(x, row, width) & pixels[y + (row - (4 - piece->getRotation().height))]) + { + return false; + } + } + return true; + } + + void findLandingPosition(Piece* piece) + { + // move down until the piece bumps into some occupied pixels or the 'wall' + while (noCollision(piece, piece->x, piece->landingY)) + { + piece->landingY++; + } + + //at this point the positon is 'in the wall' or 'over some occupied pixel' + //so the previous position was the last correct one (clamped to 0 as minimum). + piece->landingY = piece->landingY > 0 ? piece->landingY - 1 : 0; + } + + void cleanupFullLines() + { + uint8_t offset = 0; + + //from "height - 1" to "0", so from bottom row to top + for (uint8_t row = height; row-- > 0; ) + { + //full line? + if (isLineFull(row)) + { + offset++; + pixels[row] = 0x0; + continue; + } + + if (offset > 0) + { + pixels[row + offset] = pixels[row]; + pixels[row] = 0x0; + } + } + } + + bool isLineFull(uint8_t y) + { + return pixels[y] == (uint32_t)((1 << width) - 1); + } +}; + +#endif /* __GRIDBW_H__ */ \ No newline at end of file diff --git a/usermods/TetrisAI_v2/gridcolor.h b/usermods/TetrisAI_v2/gridcolor.h new file mode 100644 index 000000000..5c5ce7e42 --- /dev/null +++ b/usermods/TetrisAI_v2/gridcolor.h @@ -0,0 +1,132 @@ +/****************************************************************************** + * @file : gridcolor.h + * @brief : contains the tetris grid as 8bit indexed color version + ****************************************************************************** + * @attention + * + * Copyright (c) muebau 2023 + * All rights reserved. + * + ****************************************************************************** +*/ + +#ifndef __GRIDCOLOR_H__ +#define __GRIDCOLOR_H__ +#include +#include +#include +#include "gridbw.h" +#include "gridcolor.h" + +using namespace std; + +class GridColor +{ +private: +public: + uint8_t width; + uint8_t height; + GridBW gridBW; + std::vector pixels; + + GridColor(uint8_t width, uint8_t height): + width(width), + height(height), + gridBW(width, height), + pixels(width* height) + {} + + void clear() + { + for (uint8_t y = 0; y < height; y++) + { + gridBW.pixels[y] = 0x0; + for (int8_t x = 0; x < width; x++) + { + *getPixel(x, y) = 0; + } + } + } + + void placePiece(Piece* piece, uint8_t x, uint8_t y) + { + for (uint8_t pieceY = 0; pieceY < piece->getRotation().height; pieceY++) + { + for (uint8_t pieceX = 0; pieceX < piece->getRotation().width; pieceX++) + { + if (piece->getPixel(pieceX, pieceY)) + { + *getPixel(x + pieceX, y + pieceY) = piece->pieceData->colorIndex; + } + } + } + } + + void erasePiece(Piece* piece, uint8_t x, uint8_t y) + { + for (uint8_t pieceY = 0; pieceY < piece->getRotation().height; pieceY++) + { + for (uint8_t pieceX = 0; pieceX < piece->getRotation().width; pieceX++) + { + if (piece->getPixel(pieceX, pieceY)) + { + *getPixel(x + pieceX, y + pieceY) = 0; + } + } + } + } + + void cleanupFullLines() + { + uint8_t offset = 0; + //from "height - 1" to "0", so from bottom row to top + for (uint8_t y = height; y-- > 0; ) + { + if (gridBW.isLineFull(y)) + { + offset++; + for (uint8_t x = 0; x < width; x++) + { + pixels[y * width + x] = 0; + } + continue; + } + + if (offset > 0) + { + if (gridBW.pixels[y]) + { + for (uint8_t x = 0; x < width; x++) + { + pixels[(y + offset) * width + x] = pixels[y * width + x]; + pixels[y * width + x] = 0; + } + } + } + } + gridBW.cleanupFullLines(); + } + + uint8_t* getPixel(uint8_t x, uint8_t y) + { + return &pixels[y * width + x]; + } + + void sync() + { + for (uint8_t y = 0; y < height; y++) + { + gridBW.pixels[y] = 0x0; + for (int8_t x = 0; x < width; x++) + { + gridBW.pixels[y] <<= 1; + if (*getPixel(x, y) != 0) + { + gridBW.pixels[y] |= 0x1; + } + } + } + } +}; + +#endif /* __GRIDCOLOR_H__ */ \ No newline at end of file diff --git a/usermods/TetrisAI_v2/pieces.h b/usermods/TetrisAI_v2/pieces.h new file mode 100644 index 000000000..5d461615a --- /dev/null +++ b/usermods/TetrisAI_v2/pieces.h @@ -0,0 +1,184 @@ +/****************************************************************************** + * @file : pieces.h + * @brief : contains the tetris pieces with their colors indecies + ****************************************************************************** + * @attention + * + * Copyright (c) muebau 2022 + * All rights reserved. + * + ****************************************************************************** +*/ + +#ifndef __PIECES_H__ +#define __PIECES_H__ + +#include +#include + +#include +#include +#include +#include + +#define numPieces 7 + +struct PieceRotation +{ + uint8_t width; + uint8_t height; + uint16_t rows; +}; + +struct PieceData +{ + uint8_t rotCount; + uint8_t colorIndex; + PieceRotation rotations[4]; +}; + +PieceData piecesData[numPieces] = { + // I + { + 2, + 1, + { + { 1, 4, 0b0001000100010001}, + { 4, 1, 0b0000000000001111} + } + }, + // O + { + 1, + 2, + { + { 2, 2, 0b0000000000110011} + } + }, + // Z + { + 2, + 3, + { + { 3, 2, 0b0000000001100011}, + { 2, 3, 0b0000000100110010} + } + }, + // S + { + 2, + 4, + { + { 3, 2, 0b0000000000110110}, + { 2, 3, 0b0000001000110001} + } + }, + // L + { + 4, + 5, + { + { 2, 3, 0b0000001000100011}, + { 3, 2, 0b0000000001110100}, + { 2, 3, 0b0000001100010001}, + { 3, 2, 0b0000000000010111} + } + }, + // J + { + 4, + 6, + { + { 2, 3, 0b0000000100010011}, + { 3, 2, 0b0000000001000111}, + { 2, 3, 0b0000001100100010}, + { 3, 2, 0b0000000001110001} + } + }, + // T + { + 4, + 7, + { + { 3, 2, 0b0000000001110010}, + { 2, 3, 0b0000000100110001}, + { 3, 2, 0b0000000000100111}, + { 2, 3, 0b0000001000110010} + } + }, +}; + +class Piece +{ +private: +public: + uint8_t x; + uint8_t y; + PieceData* pieceData; + uint8_t rotation; + uint8_t landingY; + + Piece(uint8_t pieceIndex = 0): + x(0), + y(0), + rotation(0), + landingY(0) + { + this->pieceData = &piecesData[pieceIndex]; + } + + void reset() + { + this->rotation = 0; + this->x = 0; + this->y = 0; + this->landingY = 0; + } + + uint32_t getGridRow(uint8_t x, uint8_t y, uint8_t width) + { + if (x < width) + { + //shift the row with the "top-left" position to the "x" position + auto shiftx = (width - 1) - x; + auto topleftx = (getRotation().width - 1); + + auto finalShift = shiftx - topleftx; + auto row = getRow(y); + auto finalResult = row << finalShift; + + return finalResult; + } + return 0xffffffff; + } + + uint8_t getRow(uint8_t y) + { + if (y < 4) + { + return (getRotation().rows >> (12 - (4 * y))) & 0xf; + } + return 0xf; + } + + bool getPixel(uint8_t x, uint8_t y) + { + if(x > getRotation().width - 1 || y > getRotation().height - 1 ) + { + return false; + } + + if (x < 4 && y < 4) + { + return (getRow((4 - getRotation().height) + y) >> (3 - ((4 - getRotation().width) + x))) & 0x1; + } + return false; + } + + PieceRotation getRotation() + { + return this->pieceData->rotations[rotation]; + } +}; + +#endif /* __PIECES_H__ */ diff --git a/usermods/TetrisAI_v2/rating.h b/usermods/TetrisAI_v2/rating.h new file mode 100644 index 000000000..4504ff468 --- /dev/null +++ b/usermods/TetrisAI_v2/rating.h @@ -0,0 +1,64 @@ +/****************************************************************************** + * @file : rating.h + * @brief : contains the tetris rating of a grid + ****************************************************************************** + * @attention + * + * Copyright (c) muebau 2022 + * All rights reserved. + * + ****************************************************************************** +*/ + +#ifndef __RATING_H__ +#define __RATING_H__ + +#include +#include +#include +#include +#include +#include "rating.h" + +using namespace std; + +class Rating +{ +private: +public: + uint8_t minHeight; + uint8_t maxHeight; + uint16_t holes; + uint8_t fullLines; + uint16_t bumpiness; + uint16_t aggregatedHeight; + double score; + uint8_t width; + std::vector lineHights; + + Rating(uint8_t width): + width(width), + lineHights(width) + { + reset(); + } + + void reset() + { + this->minHeight = 0; + this->maxHeight = 0; + + for (uint8_t line = 0; line < this->width; line++) + { + this->lineHights[line] = 0; + } + + this->holes = 0; + this->fullLines = 0; + this->bumpiness = 0; + this->aggregatedHeight = 0; + this->score = -DBL_MAX; + } +}; + +#endif /* __RATING_H__ */ diff --git a/usermods/TetrisAI_v2/readme.md b/usermods/TetrisAI_v2/readme.md new file mode 100644 index 000000000..b21bc5fde --- /dev/null +++ b/usermods/TetrisAI_v2/readme.md @@ -0,0 +1,33 @@ +# Tetris AI effect usermod + +This usermod brings you a effect brings a self playing Tetris game. The mod needs version 0.14 or above as it is based on matrix support. The effect was tested on an ESP32 with a WS2812B 16x16 matrix. + +Version 1.0 + +## Installation + +Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'. + +## Usage + +It is best to set the background color to black, the border color to light grey and the game over color (foreground) to dark grey. + +### Sliders and boxes + +#### Sliders + +* speed: speed the game plays +* look ahead: how many pieces is the AI allowed to know the next pieces (0 - 2) +* intelligence: how good the AI will play +* Rotate color: make the colors shift (rotate) every few cicles +* Mistakes free: how many good moves between mistakes (if activated) + +#### Checkboxes + +* show next: if true a space of 5 pixels from the right is used to show the next pieces. The whole segment is used for the grid otherwise. +* show border: if true an additional column of 1 pixel is used to draw a border between the grid and the next pieces +* mistakes: if true the worst instead of the best move is choosen every few moves (read above) + +## Best results + + If the speed is set to be a little bit faster than a good human could play with maximal intelligence and very few mistakes it makes people furious/happy at a party. \ No newline at end of file diff --git a/usermods/TetrisAI_v2/tetrisai.h b/usermods/TetrisAI_v2/tetrisai.h new file mode 100644 index 000000000..ef7868a47 --- /dev/null +++ b/usermods/TetrisAI_v2/tetrisai.h @@ -0,0 +1,302 @@ +/****************************************************************************** + * @file : ai.h + * @brief : contains the heuristic + ****************************************************************************** + * @attention + * + * Copyright (c) muebau 2023 + * All rights reserved. + * + ****************************************************************************** +*/ + +#ifndef __AI_H__ +#define __AI_H__ + +#include "gridbw.h" +#include "rating.h" + +using namespace std; + +class TetrisAI +{ +private: +public: + double aHeight; + double fullLines; + double holes; + double bumpiness; + bool findWorstMove = false; + + uint8_t countOnes(uint32_t vector) + { + uint8_t count = 0; + while (vector) + { + vector &= (vector - 1); + count++; + } + return count; + } + + void updateRating(GridBW grid, Rating* rating) + { + rating->minHeight = 0; + rating->maxHeight = 0; + rating->holes = 0; + rating->fullLines = 0; + rating->bumpiness = 0; + rating->aggregatedHeight = 0; + fill(rating->lineHights.begin(), rating->lineHights.end(), 0); + + uint32_t columnvector = 0x0; + uint32_t lastcolumnvector = 0x0; + for (uint8_t row = 0; row < grid.height; row++) + { + columnvector |= grid.pixels[row]; + + //first (highest) column makes it + if (rating->maxHeight == 0 && columnvector) + { + rating->maxHeight = grid.height - row; + } + + //if column vector is full we found the minimal height (or it stays zero) + if (rating->minHeight == 0 && (columnvector == (uint32_t)((1 << grid.width) - 1))) + { + rating->minHeight = grid.height - row; + } + + //line full if all ones in mask :-) + if (grid.isLineFull(row)) + { + rating->fullLines++; + } + + //holes are basically a XOR with the "full" columns + rating->holes += countOnes(columnvector ^ grid.pixels[row]); + + //calculate the difference (XOR) between the current column vector and the last one + uint32_t columnDelta = columnvector ^ lastcolumnvector; + + //process every new column + uint8_t index = 0; + while (columnDelta) + { + //if this is a new column + if (columnDelta & 0x1) + { + //update hight of this column + rating->lineHights[(grid.width - 1) - index] = grid.height - row; + + // update aggregatedHeight + rating->aggregatedHeight += grid.height - row; + } + index++; + columnDelta >>= 1; + } + lastcolumnvector = columnvector; + } + + //compare every two columns to get the difference and add them up + for (uint8_t column = 1; column < grid.width; column++) + { + rating->bumpiness += abs(rating->lineHights[column - 1] - rating->lineHights[column]); + } + + rating->score = (aHeight * (rating->aggregatedHeight)) + (fullLines * (rating->fullLines)) + (holes * (rating->holes)) + (bumpiness * (rating->bumpiness)); + } + + TetrisAI(): TetrisAI(-0.510066, 0.760666, -0.35663, -0.184483) + {} + + TetrisAI(double aHeight, double fullLines, double holes, double bumpiness): + aHeight(aHeight), + fullLines(fullLines), + holes(holes), + bumpiness(bumpiness) + {} + + void findBestMove(GridBW grid, Piece *piece) + { + vector pieces = {*piece}; + findBestMove(grid, &pieces); + *piece = pieces[0]; + } + + void findBestMove(GridBW grid, std::vector *pieces) + { + findBestMove(grid, pieces->begin(), pieces->end()); + } + + void findBestMove(GridBW grid, std::vector::iterator start, std::vector::iterator end) + { + Rating bestRating(grid.width); + findBestMove(grid, start, end, &bestRating); + } + + void findBestMove(GridBW grid, std::vector::iterator start, std::vector::iterator end, Rating* bestRating) + { + grid.cleanupFullLines(); + Rating curRating(grid.width); + Rating deeperRating(grid.width); + Piece piece = *start; + + // for every rotation of the piece + for (piece.rotation = 0; piece.rotation < piece.pieceData->rotCount; piece.rotation++) + { + // put piece to top left corner + piece.x = 0; + piece.y = 0; + + //test for every column + for (piece.x = 0; piece.x <= grid.width - piece.getRotation().width; piece.x++) + { + //todo optimise by the use of the previous grids height + piece.landingY = 0; + //will set landingY to final position + grid.findLandingPosition(&piece); + + // draw piece + grid.placePiece(&piece, piece.x, piece.landingY); + + if(start == end - 1) + { + //at the deepest level + updateRating(grid, &curRating); + } + else + { + //go deeper to take another piece into account + findBestMove(grid, start + 1, end, &deeperRating); + curRating = deeperRating; + } + + // eraese piece + grid.erasePiece(&piece, piece.x, piece.landingY); + + if(findWorstMove) + { + //init rating for worst + if(bestRating->score == -DBL_MAX) + { + bestRating->score = DBL_MAX; + } + + // update if we found a worse one + if (bestRating->score > curRating.score) + { + *bestRating = curRating; + (*start) = piece; + } + } + else + { + // update if we found a better one + if (bestRating->score < curRating.score) + { + *bestRating = curRating; + (*start) = piece; + } + } + } + } + } + + bool findBestMoveNonBlocking(GridBW grid, std::vector::iterator start, std::vector::iterator end, Rating* bestRating) + { + //vector with pieces + //for every piece + //for every + switch (expression) + { + case INIT: + break; + + default: + break; + } + } + + bool findBestMoveNonBlocking(GridBW grid, std::vector::iterator start, std::vector::iterator end, Rating* bestRating) + { + //INIT + grid.cleanupFullLines(); + Rating curRating(grid.width); + Rating deeperRating(grid.width); + Piece piece = *start; + + // for every rotation of the piece + piece.rotation = 0; + + //HANDLE + while (piece.rotation < piece.pieceData->rotCount) + { + // put piece to top left corner + piece.x = 0; + piece.y = 0; + + //test for every column + piece.x = 0; + while (piece.x <= grid.width - piece.getRotation().width) + { + + //todo optimise by the use of the previous grids height + piece.landingY = 0; + //will set landingY to final position + grid.findLandingPosition(&piece); + + // draw piece + grid.placePiece(&piece, piece.x, piece.landingY); + + if(start == end - 1) + { + //at the deepest level + updateRating(grid, &curRating); + } + else + { + //go deeper to take another piece into account + findBestMove(grid, start + 1, end, &deeperRating); + curRating = deeperRating; + } + + // eraese piece + grid.erasePiece(&piece, piece.x, piece.landingY); + + if(findWorstMove) + { + //init rating for worst + if(bestRating->score == -DBL_MAX) + { + bestRating->score = DBL_MAX; + } + + // update if we found a worse one + if (bestRating->score > curRating.score) + { + *bestRating = curRating; + (*start) = piece; + } + } + else + { + // update if we found a better one + if (bestRating->score < curRating.score) + { + *bestRating = curRating; + (*start) = piece; + } + } + piece.x++; + } + piece.rotation++; + } + + //EXIT + + return true; + } +}; + +#endif /* __AI_H__ */ \ No newline at end of file diff --git a/usermods/TetrisAI_v2/tetrisaigame.h b/usermods/TetrisAI_v2/tetrisaigame.h new file mode 100644 index 000000000..de3c86e7e --- /dev/null +++ b/usermods/TetrisAI_v2/tetrisaigame.h @@ -0,0 +1,150 @@ +/****************************************************************************** + * @file : tetrisaigame.h + * @brief : main tetris functions + ****************************************************************************** + * @attention + * + * Copyright (c) muebau 2022 + * All rights reserved. + * + ****************************************************************************** +*/ + +#ifndef __TETRISAIGAME_H__ +#define __TETRISAIGAME_H__ + +#include +#include +#include +#include "pieces.h" +#include "gridcolor.h" +#include "tetrisbag.h" +#include "tetrisai.h" + +using namespace std; + +class TetrisAIGame +{ +private: + bool animateFallOfPiece(Piece* piece, bool skip) + { + if (skip || piece->y >= piece->landingY) + { + piece->y = piece->landingY; + grid.gridBW.placePiece(piece, piece->x, piece->landingY); + grid.placePiece(piece, piece->x, piece->y); + return false; + } + else + { + // eraese last drawing + grid.erasePiece(piece, piece->x, piece->y); + + //move piece down + piece->y++; + + // draw piece + grid.placePiece(piece, piece->x, piece->y); + + return true; + } + } + +public: + uint8_t width; + uint8_t height; + uint8_t nLookAhead; + TetrisBag bag; + GridColor grid; + TetrisAI ai; + Piece curPiece; + PieceData* piecesData; + enum States { INIT, TEST_GAME_OVER, GET_NEXT_PIECE, FIND_BEST_MOVE, ANIMATE_MOVE, ANIMATE_GAME_OVER } state = INIT; + + TetrisAIGame(uint8_t width, uint8_t height, uint8_t nLookAhead, PieceData* piecesData, uint8_t nPieces): + width(width), + height(height), + nLookAhead(nLookAhead), + bag(nPieces, 1, nLookAhead), + grid(width, height + 4), + ai(), + piecesData(piecesData) + { + } + + void nextPiece() + { + grid.cleanupFullLines(); + bag.queuePiece(); + } + + void findBestMove() + { + ai.findBestMove(grid.gridBW, &bag.piecesQueue); + } + + bool animateFall(bool skip) + { + return animateFallOfPiece(&(bag.piecesQueue[0]), skip); + } + + bool isGameOver() + { + //if there is something in the 4 lines of the hidden area the game is over + return grid.gridBW.pixels[0] || grid.gridBW.pixels[1] || grid.gridBW.pixels[2] || grid.gridBW.pixels[3]; + } + + void poll() + { + switch (state) + { + case INIT: + reset(); + state = TEST_GAME_OVER; + break; + case TEST_GAME_OVER: + if (isGameOver()) + { + state = ANIMATE_GAME_OVER; + } + else + { + state = GET_NEXT_PIECE; + } + break; + case GET_NEXT_PIECE: + nextPiece(); + state = FIND_BEST_MOVE; + break; + case FIND_BEST_MOVE: + findBestMove(); + state = ANIMATE_MOVE; + break; + case ANIMATE_MOVE: + if (!animateFall(false)) + { + state = TEST_GAME_OVER; + } + break; + case ANIMATE_GAME_OVER: + static auto curPixel = grid.pixels.size(); + grid.pixels[curPixel] = 254; + + if (curPixel == 0) + { + state = INIT; + curPixel = grid.pixels.size(); + } + curPixel--; + break; + } + } + + void reset() + { + grid.clear(); + bag.init(); + } +}; + +#endif /* __TETRISAIGAME_H__ */ diff --git a/usermods/TetrisAI_v2/tetrisbag.h b/usermods/TetrisAI_v2/tetrisbag.h new file mode 100644 index 000000000..3ecadbc0b --- /dev/null +++ b/usermods/TetrisAI_v2/tetrisbag.h @@ -0,0 +1,100 @@ +/****************************************************************************** + * @file : tetrisbag.h + * @brief : the tetris implementation of a random piece generator + ****************************************************************************** + * @attention + * + * Copyright (c) muebau 2022 + * All rights reserved. + * + ****************************************************************************** +*/ + +#ifndef __TETRISBAG_H__ +#define __TETRISBAG_H__ + +#include +#include +#include + +#include "tetrisbag.h" + +class TetrisBag +{ +private: +public: + uint8_t nPieces; + uint8_t nBagLength; + uint8_t bagIdx; + std::vector bag; + std::vector piecesQueue; + + TetrisBag(uint8_t nPieces, uint8_t nBagLength, uint8_t queueLength): + nPieces(nPieces), + nBagLength(nBagLength), + bag(nPieces * nBagLength), + piecesQueue(queueLength) + { + init(); + } + + void init() + { + //will shuffle the bag at first use + bagIdx = nPieces - 1; + + for (uint8_t bagIndex = 0; bagIndex < nPieces * nBagLength; bagIndex++) + { + bag[bagIndex] = bagIndex % nPieces; + } + + //will init the queue + for (uint8_t index = 0; index < piecesQueue.size(); index++) + { + queuePiece(); + } + } + + void shuffleBag() + { + uint8_t temp; + uint8_t swapIdx; + for (int index = nPieces - 1; index > 0; index--) + { + //get candidate to swap + swapIdx = rand() % index; + + //swap it! + temp = bag[swapIdx]; + bag[swapIdx] = bag[index]; + bag[index] = temp; + } + } + + Piece getNextPiece() + { + bagIdx++; + if (bagIdx >= nPieces) + { + shuffleBag(); + bagIdx = 0; + } + return Piece(bag[bagIdx]); + } + + void queuePiece() + { + //move vector to left + std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end()); + piecesQueue[piecesQueue.size() - 1] = getNextPiece(); + } + + void queuePiece(uint8_t idx) + { + //move vector to left + std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end()); + piecesQueue[piecesQueue.size() - 1] = Piece(idx % nPieces); + } +}; + +#endif /* __TETRISBAG_H__ */ diff --git a/usermods/TetrisAI_v2/usermod_v2_tetrisai.h b/usermods/TetrisAI_v2/usermod_v2_tetrisai.h new file mode 100644 index 000000000..1c077d048 --- /dev/null +++ b/usermods/TetrisAI_v2/usermod_v2_tetrisai.h @@ -0,0 +1,222 @@ +#pragma once + +#include "wled.h" +#include "FX.h" +#include "fcn_declare.h" + +#include "tetrisaigame.h" +// By: muebau + +typedef struct TetrisAI_data +{ + unsigned long lastTime = 0; + TetrisAIGame tetris; + uint8_t intelligence; + uint8_t rotate; + bool showNext; + bool showBorder; + uint8_t colorOffset; + uint8_t colorInc; + uint8_t mistaceCountdown; +} tetrisai_data; + +void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) +{ + SEGMENT.fill(SEGCOLOR(1)); + + //GRID + for (auto index_y = 4; index_y < tetris->grid.height; index_y++) + { + for (auto index_x = 0; index_x < tetris->grid.width; index_x++) + { + CRGB color; + if (*tetris->grid.getPixel(index_x, index_y) == 0) + { + //BG color + color = SEGCOLOR(1); + } + //game over animation + else if(*tetris->grid.getPixel(index_x, index_y) == 254) + { + //use fg + color = SEGCOLOR(0); + } + else + { + //spread the color over the whole palette + uint8_t colorIndex = *tetris->grid.getPixel(index_x, index_y) * 32; + colorIndex += tetrisai_data->colorOffset; + color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND); + } + + SEGMENT.setPixelColorXY(index_x, index_y - 4, color); + } + } + tetrisai_data->colorOffset += tetrisai_data->colorInc; + + //NEXT PIECE AREA + if (tetrisai_data->showNext) + { + //BORDER + if (tetrisai_data->showBorder) + { + //draw a line 6 pixels from right with the border color + for (auto index_y = 0; index_y < SEGMENT.virtualHeight(); index_y++) + { + SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() - 6, index_y, SEGCOLOR(2)); + } + } + + //NEXT PIECE + int piecesOffsetX = SEGMENT.virtualWidth() - 4; + int piecesOffsetY = 1; + for (uint8_t nextPieceIdx = 1; nextPieceIdx < tetris->nLookAhead; nextPieceIdx++) + { + uint8_t pieceNbrOffsetY = (nextPieceIdx - 1) * 5; + + Piece piece(tetris->bag.piecesQueue[nextPieceIdx]); + + for (uint8_t pieceY = 0; pieceY < piece.getRotation().height; pieceY++) + { + for (uint8_t pieceX = 0; pieceX < piece.getRotation().width; pieceX++) + { + if (piece.getPixel(pieceX, pieceY)) + { + uint8_t colIdx = ((piece.pieceData->colorIndex * 32) + tetrisai_data->colorOffset); + SEGMENT.setPixelColorXY(piecesOffsetX + pieceX, piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND)); + } + } + } + } + } +} + +//////////////////////////// +// 2D Tetris AI // +//////////////////////////// +uint16_t mode_2DTetrisAI() +{ + if (!strip.isMatrix || !SEGENV.allocateData(sizeof(tetrisai_data))) + { + // not a 2D set-up + SEGMENT.fill(SEGCOLOR(0)); + return 350; + } + TetrisAI_data* tetrisai_data = reinterpret_cast(SEGENV.data); + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + //range 0 - 1024ms => 1024/255 ~ 4 + uint16_t msDelayMove = 1024 - (4 * SEGMENT.speed); + int16_t msDelayGameOver = msDelayMove / 4; + + //range 0 - 2 (not including current) + uint8_t nLookAhead = SEGMENT.intensity ? (SEGMENT.intensity >> 7) + 2 : 1; + //range 0 - 16 + tetrisai_data->colorInc = SEGMENT.custom2 >> 4; + + if (!tetrisai_data->tetris || (tetrisai_data->tetris.nLookAhead != nLookAhead + || tetrisai_data->showNext != SEGMENT.check1 + || tetrisai_data->showBorder != SEGMENT.check2 + ) + ) + { + tetrisai_data->showNext = SEGMENT.check1; + tetrisai_data->showBorder = SEGMENT.check2; + + //not more than 32 as this is the limit of this implementation + uint8_t gridWidth = cols < 32 ? cols : 32; + uint8_t gridHeight = rows; + + // do we need space for the 'next' section? + if (tetrisai_data->showNext) + { + // make space for the piece and one pixel of space + gridWidth = gridWidth - 5; + + // do we need space for a border? + if (tetrisai_data->showBorder) + { + gridWidth = gridWidth - 1; + } + } + + tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces); + SEGMENT.fill(SEGCOLOR(1)); + } + + if (tetrisai_data->intelligence != SEGMENT.custom1) + { + tetrisai_data->intelligence = SEGMENT.custom1; + double dui = 0.2 - (0.2 * (tetrisai_data->intelligence / 255.0)); + + tetrisai_data->tetris.ai.aHeight = -0.510066 + dui; + tetrisai_data->tetris.ai.fullLines = 0.760666 - dui; + tetrisai_data->tetris.ai.holes = -0.35663 + dui; + tetrisai_data->tetris.ai.bumpiness = -0.184483 + dui; + } + + if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE) + { + if (millis() - tetrisai_data->lastTime > msDelayMove) + { + drawGrid(&tetrisai_data->tetris, tetrisai_data); + tetrisai_data->lastTime = millis(); + tetrisai_data->tetris.poll(); + } + } + else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_GAME_OVER) + { + if (millis() - tetrisai_data->lastTime > msDelayGameOver) + { + drawGrid(&tetrisai_data->tetris, tetrisai_data); + tetrisai_data->lastTime = millis(); + tetrisai_data->tetris.poll(); + } + } + else if (tetrisai_data->tetris.state == TetrisAIGame::FIND_BEST_MOVE) + { + if (SEGMENT.check3) + { + if(tetrisai_data->mistaceCountdown == 0) + { + tetrisai_data->tetris.ai.findWorstMove = true; + tetrisai_data->tetris.poll(); + tetrisai_data->tetris.ai.findWorstMove = false; + tetrisai_data->mistaceCountdown = SEGMENT.custom3; + } + tetrisai_data->mistaceCountdown--; + } + tetrisai_data->tetris.poll(); + } + else + { + tetrisai_data->tetris.poll(); + } + + return FRAMETIME; +} // mode_2DTetrisAI() +static const char _data_FX_MODE_2DTETRISAI[] PROGMEM = "Tetris AI@!,Look ahead,Intelligence,Rotate color,Mistake free,Show next,Border,Mistakes;Game Over,!,Border;!;2;sx=127,ix=64,c1=255,c2=0,c3=31,o1=1,o2=1,o3=0,pal=11"; + +class TetrisAIUsermod : public Usermod +{ + +private: + +public: + void setup() + { + strip.addEffect(255, &mode_2DTetrisAI, _data_FX_MODE_2DTETRISAI); + } + + void loop() + { + + } + + uint16_t getId() + { + return USERMOD_ID_TETRISAI; + } +}; 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 837668ee3..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 @@ -20,6 +24,12 @@ * .... */ +#if !defined(FFTTASK_PRIORITY) +#define FFTTASK_PRIORITY 1 // standard: looptask prio +//#define FFTTASK_PRIORITY 2 // above looptask, below asyc_tcp +//#define FFTTASK_PRIORITY 4 // above asyc_tcp +#endif + // Comment/Uncomment to toggle usb serial debugging // #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) // #define FFT_SAMPLING_LOG // FFT result debugging @@ -45,6 +55,8 @@ #define PLOT_PRINTF(x...) #endif +#define MAX_PALETTES 3 + // use audio source class (ESP32 specific) #include "audio_source.h" constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not change !) @@ -67,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 @@ -104,11 +120,11 @@ static float sampleAgc = 0.0f; // Smoothed AGC sample // peak detection 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 = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) +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 @@ -167,26 +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 -#define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc), and an a few other speedups -#define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt -#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 50% on ESP32 (as alternative to FFT_SQRT_APPROXIMATION) -#else -// lib_deps += https://github.com/blazoncek/arduinoFFT.git -#endif +// 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 @@ -195,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++) { @@ -267,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) @@ -313,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 @@ -372,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) @@ -411,7 +406,7 @@ static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // p //constexpr float beta1 = 0.8285f; // 18Khz constexpr float beta1 = 0.85f; // 20Khz - constexpr float beta2 = (1.0f - beta1) / 2.0; + constexpr float beta2 = (1.0f - beta1) / 2.0f; static float last_vals[2] = { 0.0f }; // FIR high freq cutoff filter static float lowfilt = 0.0f; // IIR low frequency cutoff filter @@ -419,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; @@ -464,17 +459,17 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p switch (FFTScalingMode) { case 1: // Logarithmic scaling - currentResult *= 0.42; // 42 is the answer ;-) - currentResult -= 8.0; // this skips the lowest row, giving some room for peaks - if (currentResult > 1.0) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function - else currentResult = 0.0; // special handling, because log(1) = 0; log(0) = undefined + currentResult *= 0.42f; // 42 is the answer ;-) + currentResult -= 8.0f; // this skips the lowest row, giving some room for peaks + if (currentResult > 1.0f) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function + else currentResult = 0.0f; // special handling, because log(1) = 0; log(0) = undefined currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies currentResult = mapf(currentResult, 0, LOG_256, 0, 255); // map [log(1) ... log(255)] to [0 ... 255] break; case 2: // Linear scaling currentResult *= 0.30f; // needs a bit more damping, get stay below 255 - currentResult -= 4.0; // giving a bit more room for peaks + currentResult -= 4.0f; // giving a bit more room for peaks if (currentResult < 1.0f) currentResult = 0.0f; currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies break; @@ -482,8 +477,8 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p // square root scaling currentResult *= 0.38f; currentResult -= 6.0f; - if (currentResult > 1.0) currentResult = sqrtf(currentResult); - else currentResult = 0.0; // special handling, because sqrt(0) = undefined + if (currentResult > 1.0f) currentResult = sqrtf(currentResult); + else currentResult = 0.0f; // special handling, because sqrt(0) = undefined currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies currentResult = mapf(currentResult, 0.0, 16.0, 0.0, 255.0); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] break; @@ -511,11 +506,11 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p // peak detection is called from FFT task when vReal[] contains valid FFT results static void detectSamplePeak(void) { bool havePeak = false; - + // softhack007: this code continuously triggers while amplitude in the selected bin is above a certain threshold. So it does not detect peaks - it detects high activity in a frequency bin. // Poor man's beat detection by seeing if sample > Average + some value. // This goes through ALL of the 255 bins - but ignores stupid settings // Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync. - if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 1) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) { + if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 4) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) { havePeak = true; } @@ -601,8 +596,15 @@ 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; // variables for UDP sound sync WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!) @@ -616,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. @@ -638,13 +640,21 @@ 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[]; + static const char _addPalettes[]; static const char UDP_SYNC_HEADER[]; static const char UDP_SYNC_HEADER_v1[]; // private methods + void removeAudioPalettes(void); + void createAudioPalettes(void); + CRGB getCRGBForBand(int x, int pal); + void fillAudioPalettes(void); //////////////////// // Debug support // @@ -734,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 @@ -758,7 +768,7 @@ class AudioReactive : public Usermod { if (time_now - last_time > 2) { last_time = time_now; - if((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { + if((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0)) { // MIC signal is "squelched" - deliver silence tmpAgc = 0; // we need to "spin down" the intgrated error buffer @@ -780,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" @@ -794,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; @@ -824,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 @@ -873,8 +883,8 @@ class AudioReactive : public Usermod { // keep "peak" sample, but decay value if current sample is below peak if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering - // another simple way to detect samplePeak - if ((binNum < 10) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { + // another simple way to detect samplePeak - cannot detect beats, but reacts on peak volume + if (((binNum < 12) || ((maxVol < 1))) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { samplePeak = true; timeOfPeak = millis(); udpSamplePeak = true; @@ -949,6 +959,8 @@ class AudioReactive : public Usermod { //DEBUGSR_PRINTLN("Transmitting UDP Mic Packet"); audioSyncPacket transmitData; + memset(reinterpret_cast(&transmitData), 0, sizeof(transmitData)); // make sure that the packet - including "invisible" padding bytes added by the compiler - is fully initialized + strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); // transmit samples that were not modified by limitSampleDynamics() transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw; @@ -964,9 +976,10 @@ class AudioReactive : public Usermod { transmitData.FFT_Magnitude = my_magnitude; transmitData.FFT_MajorPeak = FFT_MajorPeak; - fftUdp.beginMulticastPacket(); - fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); - fftUdp.endPacket(); + if (fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error + fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); + fftUdp.endPacket(); + } return; } // transmitAudioData() @@ -1071,7 +1084,7 @@ class AudioReactive : public Usermod { * You can use it to initialize variables, sensors or similar. * It is called *AFTER* readFromConfig() */ - void setup() + void setup() override { disableSoundProcessing = true; // just to be sure if (!initDone) { @@ -1149,6 +1162,13 @@ class AudioReactive : public Usermod { if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); break; #endif + case 6: + DEBUGSR_PRINTLN(F("AR: ES8388 Source")); + audioSource = new ES8388Source(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // ADC over I2S is only possible on "classic" ESP32 case 0: @@ -1156,6 +1176,7 @@ class AudioReactive : public Usermod { DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); delay(100); + useBandPassFilter = true; // PDM bandpass filter seems to help for bad quality analog if (audioSource) audioSource->initialize(audioPin); break; #endif @@ -1177,6 +1198,7 @@ class AudioReactive : public Usermod { } if (enabled) connectUDPSoundSync(); + if (enabled && addPalettes) createAudioPalettes(); initDone = true; } @@ -1185,7 +1207,7 @@ class AudioReactive : public Usermod { * connected() is called every time the WiFi is (re)connected * Use it to initialize network interfaces */ - void connected() + void connected() override { if (udpSyncConnected) { // clean-up: if open, close old UDP sync connection udpSyncConnected = false; @@ -1212,7 +1234,7 @@ class AudioReactive : public Usermod { * 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() + void loop() override { static unsigned long lastUMRun = millis(); @@ -1234,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 @@ -1265,9 +1287,10 @@ class AudioReactive : public Usermod { #ifdef WLED_DEBUG // complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second. - if ((userloopDelay > 23) && !disableSoundProcessing && (audioSyncEnabled == 0)) { - DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay); - } + // softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS + //if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) { + // DEBUG_PRINTF_P(PSTR("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n"), userloopDelay); + //} #endif // run filters, and repeat in case of loop delays (hick-up compensation) @@ -1304,6 +1327,9 @@ class AudioReactive : public Usermod { if (millis() - lastTime > delayMs) { have_new_sample = receiveAudioData(); if (have_new_sample) last_UDPTime = millis(); +#ifdef ARDUINO_ARCH_ESP32 + else fftUdp.flush(); // Flush udp input buffers if we haven't read it - avoids hickups in receive mode. Does not work on 8266. +#endif lastTime = millis(); } if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample @@ -1322,7 +1348,7 @@ class AudioReactive : public Usermod { // Info Page: keep max sample from last 5 seconds if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { sampleMaxTimer = millis(); - maxSample5sec = (0.15 * maxSample5sec) + 0.85 *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing + maxSample5sec = (0.15f * maxSample5sec) + 0.85f *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing if (sampleAvg < 1) maxSample5sec = 0; // noise gate } else { if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume @@ -1335,10 +1361,11 @@ class AudioReactive : public Usermod { lastTime = millis(); } + fillAudioPalettes(); } - bool getUMData(um_data_t **data) + bool getUMData(um_data_t **data) override { if (!data || !enabled) return false; // no pointer provided by caller or not enabled -> exit *data = um_data; @@ -1346,7 +1373,7 @@ class AudioReactive : public Usermod { } - void onUpdateBegin(bool init) + void onUpdateBegin(bool init) override { #ifdef WLED_DEBUG fftTime = sampleTime = 0; @@ -1366,10 +1393,11 @@ class AudioReactive : public Usermod { memset(fftAvg, 0, sizeof(fftAvg)); memset(fftResult, 0, sizeof(fftResult)); for(int i=(init?0:1); iisInitialized())) { - // audio source sucessfully configured + // audio source successfully configured if (audioSource->getType() == AudioSource::Type_I2SAdc) { infoArr.add(F("ADC analog")); } else { infoArr.add(F("I2S digital")); } // input level or "silence" - if (maxSample5sec > 1.0) { + if (maxSample5sec > 1.0f) { float my_usage = 100.0f * (maxSample5sec / 255.0f); snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage)); infoArr.add(myStringBuffer); @@ -1496,7 +1523,7 @@ class AudioReactive : public Usermod { } else { // error during audio source setup infoArr.add(F("not initialized")); - infoArr.add(F(" - check GPIO config")); + infoArr.add(F(" - check pin settings")); } } @@ -1563,7 +1590,7 @@ class AudioReactive : public Usermod { * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void addToJsonState(JsonObject& root) + void addToJsonState(JsonObject& root) override { if (!initDone) return; // prevent crash on boot applyPreset() JsonObject usermod = root[FPSTR(_name)]; @@ -1578,7 +1605,7 @@ class AudioReactive : public Usermod { * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void readFromJsonState(JsonObject& root) + void readFromJsonState(JsonObject& root) override { if (!initDone) return; // prevent crash on boot applyPreset() bool prevEnabled = enabled; @@ -1587,13 +1614,28 @@ class AudioReactive : public Usermod { if (usermod[FPSTR(_enabled)].is()) { enabled = usermod[FPSTR(_enabled)].as(); if (prevEnabled != enabled) onUpdateBegin(!enabled); + if (addPalettes) { + // add/remove custom/audioreactive palettes + if (prevEnabled && !enabled) removeAudioPalettes(); + if (!prevEnabled && enabled) createAudioPalettes(); + } } if (usermod[FPSTR(_inputLvl)].is()) { inputLevel = min(255,max(0,usermod[FPSTR(_inputLvl)].as())); } } + if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { + // handle removal of custom palettes from JSON call so we don't break things + removeAudioPalettes(); + } } + void onStateChange(uint8_t callMode) override { + if (initDone && enabled && addPalettes && palettes==0 && strip.customPalettes.size()<10) { + // if palettes were removed during JSON call re-add them + createAudioPalettes(); + } + } /* * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. @@ -1630,10 +1672,11 @@ class AudioReactive : public Usermod { * * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ - void addToConfig(JsonObject& root) + void addToConfig(JsonObject& root) override { JsonObject top = root.createNestedObject(FPSTR(_name)); top[FPSTR(_enabled)] = enabled; + top[FPSTR(_addPalettes)] = addPalettes; #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); @@ -1641,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; } @@ -1682,12 +1725,15 @@ class AudioReactive : public Usermod { * * This function is guaranteed to be called on boot, but could also be called every time settings are updated */ - bool readFromConfig(JsonObject& root) + bool readFromConfig(JsonObject& root) override { JsonObject top = root[FPSTR(_name)]; bool configComplete = !top.isNull(); + bool oldEnabled = enabled; + bool oldAddPalettes = addPalettes; configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); + configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin); @@ -1708,24 +1754,29 @@ 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 + if ((oldAddPalettes && !addPalettes) || (oldAddPalettes && !enabled)) removeAudioPalettes(); + if ((addPalettes && !oldAddPalettes && enabled) || (addPalettes && !oldEnabled && enabled)) createAudioPalettes(); + } // else setup() will create palettes return configComplete; } - void appendConfigData() + void appendConfigData() override { oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) @@ -1738,6 +1789,8 @@ class AudioReactive : public Usermod { #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) oappend(SET_F("addOption(dd,'Generic I2S PDM',5);")); #endif + oappend(SET_F("addOption(dd,'ES8388',6);")); + oappend(SET_F("dd=addDropdown('AudioReactive','config:AGC');")); oappend(SET_F("addOption(dd,'Off',0);")); oappend(SET_F("addOption(dd,'Normal',1);")); @@ -1778,7 +1831,7 @@ class AudioReactive : public Usermod { * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. * Commonly used for custom clocks (Cronixie, 7 segment) */ - //void handleOverlayDraw() + //void handleOverlayDraw() override //{ //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black //} @@ -1788,17 +1841,109 @@ class AudioReactive : public Usermod { * 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() + uint16_t getId() override { return USERMOD_ID_AUDIOREACTIVE; } }; +void AudioReactive::removeAudioPalettes(void) { + DEBUG_PRINTLN(F("Removing audio palettes.")); + while (palettes>0) { + strip.customPalettes.pop_back(); + DEBUG_PRINTLN(palettes); + palettes--; + } + DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(strip.customPalettes.size()); +} + +void AudioReactive::createAudioPalettes(void) { + DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(strip.customPalettes.size()); + if (palettes) return; + DEBUG_PRINTLN(F("Adding audio palettes.")); + for (int i=0; i= palettes) lastCustPalette -= palettes; + for (size_t pal=0; pal #include @@ -22,14 +22,14 @@ // see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents // and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#overview-of-all-modes -#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265) +#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265) // there are two things in these MCUs that could lead to problems with audio processing: // * no floating point hardware (FPU) support - FFT uses float calculations. If done in software, a strong slow-down can be expected (between 8x and 20x) // * single core, so FFT task might slow down other things like LED updates #if !defined(SOC_I2S_NUM) || (SOC_I2S_NUM < 1) - #error This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2. + #error This audio reactive usermod does not support ESP32-C2 or ESP32-C3. #else - #warning This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2. + #warning This audio reactive usermod does not support ESP32-C2 and ESP32-C3. #endif #endif @@ -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 @@ -71,7 +71,7 @@ * if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case. */ -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 3)) +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 4)) // espressif bug: only_left has no sound, left and right are swapped // https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138) // https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918) @@ -122,7 +122,7 @@ class AudioSource { This function needs to take care of anything that needs to be done before samples can be obtained from the microphone. */ - virtual void initialize(int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) = 0; + virtual void initialize(int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) = 0; /* Deinitialize Release all resources and deactivate any functionality that is used @@ -191,7 +191,8 @@ 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, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + 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(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 @@ -377,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 { @@ -412,6 +413,7 @@ public: }; void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + DEBUGSR_PRINTLN(F("ES7243:: initialize();")); if ((i2sckPin < 0) || (mclkPin < 0)) { DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); return; @@ -427,6 +429,122 @@ public: } }; +/* 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 { + private: + + void _es8388I2cWrite(uint8_t reg, uint8_t val) { +#ifndef ES8388_ADDR + Wire.beginTransmission(0x10); + #define ES8388_ADDR 0x10 // default address +#else + Wire.beginTransmission(ES8388_ADDR); +#endif + Wire.write((uint8_t)reg); + Wire.write((uint8_t)val); + uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK + if (i2cErr != 0) { + DEBUGSR_PRINTF("AR: ES8388 I2C write failed with error=%d (addr=0x%X, reg 0x%X, val 0x%X).\n", i2cErr, ES8388_ADDR, reg, val); + } + } + + void _es8388InitAdc() { + // https://dl.radxa.com/rock2/docs/hw/ds/ES8388%20user%20Guide.pdf Section 10.1 + // http://www.everest-semi.com/pdf/ES8388%20DS.pdf Better spec sheet, more clear. + // https://docs.google.com/spreadsheets/d/1CN3MvhkcPVESuxKyx1xRYqfUit5hOdsG45St9BCUm-g/edit#gid=0 generally + // Sets ADC to around what AudioReactive expects, and loops line-in to line-out/headphone for monitoring. + // Registries are decimal, settings are binary as that's how everything is listed in the docs + // ...which makes it easier to reference the docs. + // + _es8388I2cWrite( 8,0b00000000); // I2S to slave + _es8388I2cWrite( 2,0b11110011); // Power down DEM and STM + _es8388I2cWrite(43,0b10000000); // Set same LRCK + _es8388I2cWrite( 0,0b00000101); // Set chip to Play & Record Mode + _es8388I2cWrite(13,0b00000010); // Set MCLK/LRCK ratio to 256 + _es8388I2cWrite( 1,0b01000000); // Power up analog and lbias + _es8388I2cWrite( 3,0b00000000); // Power up ADC, Analog Input, and Mic Bias + _es8388I2cWrite( 4,0b11111100); // Power down DAC, Turn on LOUT1 and ROUT1 and LOUT2 and ROUT2 power + _es8388I2cWrite( 2,0b01000000); // Power up DEM and STM and undocumented bit for "turn on line-out amp" + + // #define use_es8388_mic + + #ifdef use_es8388_mic + // 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 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. + // + // The LyraT does a reasonable job with mic input as configured below. + + // Pick one of these. If you have to use the mics, use a LyraT over an AudioKit if you can: + _es8388I2cWrite(10,0b00000000); // Use Lin1/Rin1 for ADC input (mic on LyraT) + //_es8388I2cWrite(10,0b01010000); // Use Lin2/Rin2 for ADC input (mic *and* line-in on AudioKit) + + _es8388I2cWrite( 9,0b10001000); // Select Analog Input PGA Gain for ADC to +24dB (L+R) + _es8388I2cWrite(16,0b00000000); // Set ADC digital volume attenuation to 0dB (left) + _es8388I2cWrite(17,0b00000000); // Set ADC digital volume attenuation to 0dB (right) + _es8388I2cWrite(38,0b00011011); // Mixer - route LIN1/RIN1 to output after mic gain + + _es8388I2cWrite(39,0b01000000); // Mixer - route LIN to mixL, +6dB gain + _es8388I2cWrite(42,0b01000000); // Mixer - route RIN to mixR, +6dB gain + _es8388I2cWrite(46,0b00100001); // LOUT1VOL - 0b00100001 = +4.5dB + _es8388I2cWrite(47,0b00100001); // ROUT1VOL - 0b00100001 = +4.5dB + _es8388I2cWrite(48,0b00100001); // LOUT2VOL - 0b00100001 = +4.5dB + _es8388I2cWrite(49,0b00100001); // ROUT2VOL - 0b00100001 = +4.5dB + + // Music ALC - the mics like Auto Level Control + // You can also use this for line-in, but it's not really needed. + // + _es8388I2cWrite(18,0b11111000); // ALC: stereo, max gain +35.5dB, min gain -12dB + _es8388I2cWrite(19,0b00110000); // ALC: target -1.5dB, 0ms hold time + _es8388I2cWrite(20,0b10100110); // ALC: gain ramp up = 420ms/93ms, gain ramp down = check manual for calc + _es8388I2cWrite(21,0b00000110); // ALC: use "ALC" mode, no zero-cross, window 96 samples + _es8388I2cWrite(22,0b01011001); // ALC: noise gate threshold, PGA gain constant, noise gate enabled + #else + _es8388I2cWrite(10,0b01010000); // Use Lin2/Rin2 for ADC input ("line-in") + _es8388I2cWrite( 9,0b00000000); // Select Analog Input PGA Gain for ADC to 0dB (L+R) + _es8388I2cWrite(16,0b01000000); // Set ADC digital volume attenuation to -32dB (left) + _es8388I2cWrite(17,0b01000000); // Set ADC digital volume attenuation to -32dB (right) + _es8388I2cWrite(38,0b00001001); // Mixer - route LIN2/RIN2 to output + + _es8388I2cWrite(39,0b01010000); // Mixer - route LIN to mixL, 0dB gain + _es8388I2cWrite(42,0b01010000); // Mixer - route RIN to mixR, 0dB gain + _es8388I2cWrite(46,0b00011011); // LOUT1VOL - 0b00011110 = +0dB, 0b00011011 = LyraT balance fix + _es8388I2cWrite(47,0b00011110); // ROUT1VOL - 0b00011110 = +0dB + _es8388I2cWrite(48,0b00011110); // LOUT2VOL - 0b00011110 = +0dB + _es8388I2cWrite(49,0b00011110); // ROUT2VOL - 0b00011110 = +0dB + #endif + + } + + public: + ES8388Source(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f, bool i2sMaster=true) : + I2SSource(sampleRate, blockSize, sampleScale) { + _config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT; + }; + + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + DEBUGSR_PRINTLN(F("ES8388Source:: initialize();")); + if ((i2sckPin < 0) || (mclkPin < 0)) { + DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); + return; + } + + // First route mclk, then configure ADC over I2C, then configure I2S + _es8388InitAdc(); + I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + } + + void deinitialize() { + I2SSource::deinitialize(); + } + +}; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) #if !defined(SOC_I2S_SUPPORTS_ADC) && !defined(SOC_I2S_SUPPORTS_ADC_DAC) @@ -468,7 +586,8 @@ class I2SAdcSource : public I2SSource { /* identify Audiosource type - I2S-ADC*/ 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, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + 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(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); @@ -639,7 +758,8 @@ class SPH0654 : public I2SSource { I2SSource(sampleRate, blockSize, sampleScale) {} - void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE) { + 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 @@ -650,3 +770,4 @@ class SPH0654 : public I2SSource { #endif } }; +#endif \ No newline at end of file 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 a1e257758..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); @@ -202,14 +202,14 @@ class BobLightUsermod : public Usermod { initDone = true; } - void connected() { + void connected() override { // we can only start server when WiFi is connected if (!bob) bob = new WiFiServer(bobPort, 1); bob->begin(); bob->setNoDelay(true); } - void loop() { + void loop() override { if (!enabled || strip.isUpdating()) return; if (millis() - lastTime > 10) { lastTime = millis(); @@ -225,7 +225,7 @@ class BobLightUsermod : public Usermod { * topic only contains stripped topic (part after /wled/MAC) * topic should look like: /swipe with amessage of [up|down] */ - bool onMqttMessage(char* topic, char* payload) { + bool onMqttMessage(char* topic, char* payload) override { //if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/subtopic"), 6) == 0) { // String action = payload; // if (action == "on") { @@ -242,7 +242,7 @@ class BobLightUsermod : public Usermod { /** * subscribe to MQTT topic for controlling usermod */ - void onMqttConnect(bool sessionPresent) { + void onMqttConnect(bool sessionPresent) override { //char subuf[64]; //if (mqttDeviceTopic[0] != 0) { // strcpy(subuf, mqttDeviceTopic); @@ -252,7 +252,7 @@ class BobLightUsermod : public Usermod { } #endif - void addToJsonInfo(JsonObject& root) + void addToJsonInfo(JsonObject& root) override { JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); @@ -273,7 +273,7 @@ class BobLightUsermod : public Usermod { * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void addToJsonState(JsonObject& root) + void addToJsonState(JsonObject& root) override { } @@ -281,7 +281,7 @@ class BobLightUsermod : public Usermod { * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void readFromJsonState(JsonObject& root) { + void readFromJsonState(JsonObject& root) override { if (!initDone) return; // prevent crash on boot applyPreset() bool en = enabled; JsonObject um = root[FPSTR(_name)]; @@ -304,7 +304,7 @@ class BobLightUsermod : public Usermod { } } - void appendConfigData() { + void appendConfigData() override { //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); //oappend(SET_F("addOption(dd,'1st value',0);")); //oappend(SET_F("addOption(dd,'2nd value',1);")); @@ -315,10 +315,10 @@ class BobLightUsermod : public Usermod { oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field } - void addToConfig(JsonObject& root) { + 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; @@ -326,7 +326,7 @@ class BobLightUsermod : public Usermod { umData[F("pct")] = pct; } - bool readFromConfig(JsonObject& root) { + bool readFromConfig(JsonObject& root) override { JsonObject umData = root[FPSTR(_name)]; bool configComplete = !umData.isNull(); @@ -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); @@ -355,11 +355,11 @@ class BobLightUsermod : public Usermod { * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. * Commonly used for custom clocks (Cronixie, 7 segment) */ - void handleOverlayDraw() { + void handleOverlayDraw() override { //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black } - uint16_t getId() { return USERMOD_ID_BOBLIGHT; } + uint16_t getId() override { return USERMOD_ID_BOBLIGHT; } }; @@ -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/readme.md b/usermods/mpu6050_imu/readme.md index 412004151..b804ba602 100644 --- a/usermods/mpu6050_imu/readme.md +++ b/usermods/mpu6050_imu/readme.md @@ -20,14 +20,11 @@ react to the globes orientation. See the blog post on building it + +constexpr auto ESTIMATED_G = 9.801; // m/s^2 +constexpr auto ESTIMATED_G_COUNTS = 8350.; +constexpr auto ESTIMATED_ANGULAR_RATE = (M_PI * 2000) / (INT16_MAX * 180); // radians per second + +// Horribly lame digital filter code +// Currently implements a static IIR filter. +template +class xir_filter { + typedef Eigen::Array array_t; + const array_t a_coeff, b_coeff; + const T gain; + array_t x, y; + + public: + xir_filter(T gain_, array_t a, array_t b) : a_coeff(std::move(a)), b_coeff(std::move(b)), gain(gain_), x(array_t::Zero()), y(array_t::Zero()) {}; + + T operator()(T input) { + x.head(C-1) = x.tail(C-1); // shift by one + x(C-1) = input / gain; + y.head(C-1) = y.tail(C-1); // shift by one + y(C-1) = (x * b_coeff).sum(); + y(C-1) -= (y.head(C-1) * a_coeff.head(C-1)).sum(); + return y(C-1); + } + + T last() { return y(C-1); }; +}; + + + +class GyroSurge : public Usermod { + private: + static const char _name[]; + bool enabled = true; + + // Params + uint8_t max = 0; + float sensitivity = 0; + + // State + uint32_t last_sample; + // 100hz input + // butterworth low pass filter at 20hz + xir_filter filter = { 1., { -0.36952738, 0.19581571, 1.}, {0.20657208, 0.41314417, 0.20657208} }; + // { 1., { 0., 0., 1.}, { 0., 0., 1. } }; // no filter + + + public: + + /* + * setup() is called once at boot. WiFi is not yet connected at this point. + */ + void setup() {}; + + + /* + * 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) + * 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)); + + //save these vars persistently whenever settings are saved + top["max"] = max; + top["sensitivity"] = sensitivity; + } + + + /* + * 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 immediately after boot, or after saving on the Usermod Settings page) + * + * 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 :) + * + * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) + * + * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present + * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them + * + * This function is guaranteed to be called on boot, but could also be called every time settings are updated + */ + bool readFromConfig(JsonObject& root) + { + // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor + // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) + + JsonObject top = root[FPSTR(_name)]; + + bool configComplete = !top.isNull(); + + configComplete &= getJsonValue(top["max"], max, 0); + configComplete &= getJsonValue(top["sensitivity"], sensitivity, 10); + + return configComplete; + } + + void loop() { + // get IMU data + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_IMU)) { + // Apply max + strip.getSegment(0).fadeToBlackBy(max); + return; + } + uint32_t sample_count = *(uint32_t*)(um_data->u_data[8]); + + if (sample_count != last_sample) { + last_sample = sample_count; + // Calculate based on new data + // We use the raw gyro data (angular rate) + auto gyros = (int16_t*)um_data->u_data[4]; // 16384 == 2000 deg/s + + // Compute the overall rotation rate + // For my application (a plasma sword) we ignore X axis rotations (eg. around the long axis) + auto gyro_q = Eigen::AngleAxis { + //Eigen::AngleAxis(ESTIMATED_ANGULAR_RATE * gyros[0], Eigen::Vector3f::UnitX()) * + Eigen::AngleAxis(ESTIMATED_ANGULAR_RATE * gyros[1], Eigen::Vector3f::UnitY()) * + Eigen::AngleAxis(ESTIMATED_ANGULAR_RATE * gyros[2], Eigen::Vector3f::UnitZ()) }; + + // Filter the results + filter(std::min(sensitivity * gyro_q.angle(), 1.0f)); // radians per second +/* + Serial.printf("[%lu] Gy: %d, %d, %d -- ", millis(), (int)gyros[0], (int)gyros[1], (int)gyros[2]); + Serial.print(gyro_q.angle()); + Serial.print(", "); + Serial.print(sensitivity * gyro_q.angle()); + Serial.print(" --> "); + Serial.println(filter.last()); +*/ + } + }; // noop + + /* + * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. + * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. + * Commonly used for custom clocks (Cronixie, 7 segment) + */ + void handleOverlayDraw() + { + + // TODO: some kind of timing analysis for filtering ... + + // Calculate brightness boost + auto r_float = std::max(std::min(filter.last(), 1.0f), 0.f); + auto result = (uint8_t) (r_float * max); + //Serial.printf("[%lu] %d -- ", millis(), result); + //Serial.println(r_float); + // TODO - multiple segment handling?? + strip.getSegment(0).fadeToBlackBy(max - result); + } +}; + +const char GyroSurge::_name[] PROGMEM = "GyroSurge"; \ No newline at end of file diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h index 748ddf1a6..51dd646c7 100644 --- a/usermods/mpu6050_imu/usermod_mpu6050_imu.h +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -20,11 +20,11 @@ XCL not connected AD0 not connected INT D8 (GPIO15) Interrupt pin - + Using usermod: 1. Copy the usermod into the sketch folder (same folder as wled00.ino) 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp - 3. I2Cdev and MPU6050 must be installed as libraries, or else the .cpp/.h file + 3. I2Cdev and MPU6050 must be installed as libraries, or else the .cpp/.h file for both classes must be in the include path of your project. To install the libraries add I2Cdevlib-MPU6050@fbde122cc5 to lib_deps in the platformio.ini file. 4. You also need to change lib_compat_mode from strict to soft in platformio.ini (This ignores that I2Cdevlib-MPU6050 doesn't list platform compatibility) @@ -33,6 +33,9 @@ #include "I2Cdev.h" +#undef DEBUG_PRINT +#undef DEBUG_PRINTLN +#undef DEBUG_PRINTF #include "MPU6050_6Axis_MotionApps20.h" //#include "MPU6050.h" // not necessary if using MotionApps include file @@ -42,6 +45,23 @@ #include "Wire.h" #endif +// Restore debug macros +// MPU6050 unfortunately uses the same macro names as WLED :( +#undef DEBUG_PRINT +#undef DEBUG_PRINTLN +#undef DEBUG_PRINTF +#ifdef WLED_DEBUG + #define DEBUG_PRINT(x) DEBUGOUT.print(x) + #define DEBUG_PRINTLN(x) DEBUGOUT.println(x) + #define DEBUG_PRINTF(x...) DEBUGOUT.printf(x) +#else + #define DEBUG_PRINT(x) + #define DEBUG_PRINTLN(x) + #define DEBUG_PRINTF(x...) +#endif + + + // ================================================================ // === INTERRUPT DETECTION ROUTINE === // ================================================================ @@ -52,21 +72,31 @@ void IRAM_ATTR dmpDataReady() { } + class MPU6050Driver : public Usermod { private: MPU6050 mpu; - bool enabled = true; + + // configuration state + // default values are set in readFromConfig + // By making this a struct, we enable easy backup and comparison in the readFromConfig class + struct config_t { + bool enabled; + int8_t interruptPin; + int16_t gyro_offset[3]; + int16_t accel_offset[3]; + }; + config_t config; // MPU control/status vars + bool irqBound = false; // set true if we have bound the IRQ pin bool dmpReady = false; // set true if DMP init was successful - uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU uint8_t devStatus; // return status after each device operation (0 = success, !0 = error) uint16_t packetSize; // expected DMP packet size (default is 42 bytes) uint16_t fifoCount; // count of all bytes currently in FIFO uint8_t fifoBuffer[64]; // FIFO storage buffer - //NOTE: some of these can be removed to save memory, processing time - // if the measurement isn't needed + // TODO: some of these can be removed to save memory, processing time if the measurement isn't needed Quaternion qat; // [w, x, y, z] quaternion container float euler[3]; // [psi, theta, phi] Euler angle container float ypr[3]; // [yaw, pitch, roll] yaw/pitch/roll container @@ -75,17 +105,67 @@ class MPU6050Driver : public Usermod { VectorInt16 aaReal; // [x, y, z] gravity-free accel sensor measurements VectorInt16 aaWorld; // [x, y, z] world-frame accel sensor measurements VectorFloat gravity; // [x, y, z] gravity vector + uint32 sample_count; - static const int INTERRUPT_PIN = 15; // use pin 15 on ESP8266 + // Usermod output + um_data_t um_data; + + // config element names as progmem strs + static const char _name[]; + static const char _enabled[]; + static const char _interrupt_pin[]; + static const char _x_acc_bias[]; + static const char _y_acc_bias[]; + static const char _z_acc_bias[]; + static const char _x_gyro_bias[]; + static const char _y_gyro_bias[]; + static const char _z_gyro_bias[]; public: - //Functions called by WLED + inline bool initDone() { return um_data.u_size != 0; }; // recycle this instead of storing an extra variable + + //Functions called by WLED /* * setup() is called once at boot. WiFi is not yet connected at this point. */ void setup() { - if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; } + dmpReady = false; // Start clean + + // one time init + if (!initDone()) { + um_data.u_size = 9; + um_data.u_type = new um_types_t[um_data.u_size]; + um_data.u_data = new void*[um_data.u_size]; + um_data.u_data[0] = &qat; + um_data.u_type[0] = UMT_FLOAT_ARR; + um_data.u_data[1] = &euler; + um_data.u_type[1] = UMT_FLOAT_ARR; + um_data.u_data[2] = &ypr; + um_data.u_type[2] = UMT_FLOAT_ARR; + um_data.u_data[3] = &aa; + um_data.u_type[3] = UMT_INT16_ARR; + um_data.u_data[4] = &gy; + um_data.u_type[4] = UMT_INT16_ARR; + um_data.u_data[5] = &aaReal; + um_data.u_type[5] = UMT_INT16_ARR; + um_data.u_data[6] = &aaWorld; + um_data.u_type[6] = UMT_INT16_ARR; + um_data.u_data[7] = &gravity; + um_data.u_type[7] = UMT_FLOAT_ARR; + um_data.u_data[8] = &sample_count; + um_data.u_type[8] = UMT_UINT32; + } + + if (!config.enabled) return; + if (i2c_scl<0 || i2c_sda<0) { DEBUG_PRINTLN(F("MPU6050: I2C is no good.")); return; } + // Check the interrupt pin + if (config.interruptPin >= 0) { + irqBound = pinManager.allocatePin(config.interruptPin, false, PinOwner::UM_IMU); + if (!irqBound) { DEBUG_PRINTLN(F("MPU6050: IRQ pin already in use.")); return; } + pinMode(config.interruptPin, INPUT); + }; + #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE Wire.setClock(400000U); // 400kHz I2C clock. Comment this line if having compilation difficulties #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE @@ -95,7 +175,6 @@ class MPU6050Driver : public Usermod { // initialize device DEBUG_PRINTLN(F("Initializing I2C devices...")); mpu.initialize(); - pinMode(INTERRUPT_PIN, INPUT); // verify connection DEBUG_PRINTLN(F("Testing device connections...")); @@ -105,11 +184,16 @@ class MPU6050Driver : public Usermod { DEBUG_PRINTLN(F("Initializing DMP...")); devStatus = mpu.dmpInitialize(); - // supply your own gyro offsets here, scaled for min sensitivity - mpu.setXGyroOffset(220); - mpu.setYGyroOffset(76); - mpu.setZGyroOffset(-85); - mpu.setZAccelOffset(1788); // 1688 factory default for my test chip + // set offsets (from config) + mpu.setXGyroOffset(config.gyro_offset[0]); + mpu.setYGyroOffset(config.gyro_offset[1]); + mpu.setZGyroOffset(config.gyro_offset[2]); + mpu.setXAccelOffset(config.accel_offset[0]); + mpu.setYAccelOffset(config.accel_offset[1]); + mpu.setZAccelOffset(config.accel_offset[2]); + + // set sample rate + mpu.setRate(16); // ~100Hz // make sure it worked (returns 0 if so) if (devStatus == 0) { @@ -117,17 +201,19 @@ class MPU6050Driver : public Usermod { DEBUG_PRINTLN(F("Enabling DMP...")); mpu.setDMPEnabled(true); - // enable Arduino interrupt detection - DEBUG_PRINTLN(F("Enabling interrupt detection (Arduino external interrupt 0)...")); - attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING); - mpuIntStatus = mpu.getIntStatus(); - - // set our DMP Ready flag so the main loop() function knows it's okay to use it - DEBUG_PRINTLN(F("DMP ready! Waiting for first interrupt...")); - dmpReady = true; + mpuInterrupt = true; + if (irqBound) { + // enable Arduino interrupt detection + DEBUG_PRINTLN(F("Enabling interrupt detection (Arduino external interrupt 0)...")); + attachInterrupt(digitalPinToInterrupt(config.interruptPin), dmpDataReady, RISING); + } // get expected DMP packet size for later comparison packetSize = mpu.dmpGetFIFOPacketSize(); + + // set our DMP Ready flag so the main loop() function knows it's okay to use it + DEBUG_PRINTLN(F("DMP ready!")); + dmpReady = true; } else { // ERROR! // 1 = initial memory load failed @@ -137,6 +223,9 @@ class MPU6050Driver : public Usermod { DEBUG_PRINT(devStatus); DEBUG_PRINTLN(")"); } + + fifoCount = 0; + sample_count = 0; } /* @@ -144,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!")); } @@ -153,28 +242,31 @@ class MPU6050Driver : public Usermod { */ void loop() { // if programming failed, don't try to do anything - if (!enabled || !dmpReady || strip.isUpdating()) return; + if (!config.enabled || !dmpReady || strip.isUpdating()) return; // wait for MPU interrupt or extra packet(s) available + // mpuInterrupt is fixed on if interrupt pin is disabled if (!mpuInterrupt && fifoCount < packetSize) return; // reset interrupt flag and get INT_STATUS byte - mpuInterrupt = false; - mpuIntStatus = mpu.getIntStatus(); - - // get current FIFO count + auto mpuIntStatus = mpu.getIntStatus(); + // Update current FIFO count fifoCount = mpu.getFIFOCount(); // check for overflow (this should never happen unless our code is too inefficient) if ((mpuIntStatus & 0x10) || fifoCount == 1024) { // reset so we can continue cleanly mpu.resetFIFO(); - DEBUG_PRINTLN(F("FIFO overflow!")); + DEBUG_PRINTLN(F("MPU6050: FIFO overflow!")); - // otherwise, check for DMP data ready interrupt (this should happen frequently) - } else if (mpuIntStatus & 0x02) { - // wait for correct available data length, should be a VERY short wait - while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount(); + // otherwise, check for data ready + } else if (fifoCount >= packetSize) { + // clear local interrupt pending status, if not polling + mpuInterrupt = !irqBound; + + // DEBUG_PRINT(F("MPU6050: Processing packet: ")); + // DEBUG_PRINT(fifoCount); + // DEBUG_PRINTLN(F(" bytes in FIFO")); // read a packet from FIFO mpu.getFIFOBytes(fifoBuffer, packetSize); @@ -183,7 +275,6 @@ class MPU6050Driver : public Usermod { // (this lets us immediately read more without waiting for an interrupt) fifoCount -= packetSize; - //NOTE: some of these can be removed to save memory, processing time // if the measurement isn't needed mpu.dmpGetQuaternion(&qat, fifoBuffer); @@ -194,87 +285,141 @@ class MPU6050Driver : public Usermod { mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity); mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &qat); mpu.dmpGetYawPitchRoll(ypr, &qat, &gravity); + ++sample_count; } } - - 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"); - JsonObject imu_meas = user.createNestedObject("IMU"); - JsonArray quat_json = imu_meas.createNestedArray("Quat"); + // Unfortunately the web UI doesn't know how to print sub-objects: you just see '[object Object]' + // For now, we just put everything in the root userdata object. + //auto imu_meas = user.createNestedObject("IMU"); + auto& imu_meas = user; + + // If an element is an array, the UI expects two elements in the form [value, unit] + // Since our /value/ is an array, wrap it, eg. [[a, b, c]] + JsonArray quat_json = imu_meas.createNestedArray("Quat").createNestedArray(); quat_json.add(qat.w); quat_json.add(qat.x); quat_json.add(qat.y); quat_json.add(qat.z); - JsonArray euler_json = imu_meas.createNestedArray("Euler"); + JsonArray euler_json = imu_meas.createNestedArray("Euler").createNestedArray(); euler_json.add(euler[0]); euler_json.add(euler[1]); euler_json.add(euler[2]); - JsonArray accel_json = imu_meas.createNestedArray("Accel"); + JsonArray accel_json = imu_meas.createNestedArray("Accel").createNestedArray(); accel_json.add(aa.x); accel_json.add(aa.y); accel_json.add(aa.z); - JsonArray gyro_json = imu_meas.createNestedArray("Gyro"); + JsonArray gyro_json = imu_meas.createNestedArray("Gyro").createNestedArray(); gyro_json.add(gy.x); gyro_json.add(gy.y); gyro_json.add(gy.z); - JsonArray world_json = imu_meas.createNestedArray("WorldAccel"); + JsonArray world_json = imu_meas.createNestedArray("WorldAccel").createNestedArray(); world_json.add(aaWorld.x); world_json.add(aaWorld.y); world_json.add(aaWorld.z); - JsonArray real_json = imu_meas.createNestedArray("RealAccel"); + JsonArray real_json = imu_meas.createNestedArray("RealAccel").createNestedArray(); real_json.add(aaReal.x); real_json.add(aaReal.y); real_json.add(aaReal.z); - JsonArray grav_json = imu_meas.createNestedArray("Gravity"); + JsonArray grav_json = imu_meas.createNestedArray("Gravity").createNestedArray(); grav_json.add(gravity.x); grav_json.add(gravity.y); grav_json.add(gravity.z); - JsonArray orient_json = imu_meas.createNestedArray("Orientation"); + JsonArray orient_json = imu_meas.createNestedArray("Orientation").createNestedArray(); orient_json.add(ypr[0]); orient_json.add(ypr[1]); orient_json.add(ypr[2]); } - /* - * 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) - //{ - //if (root["bri"] == 255) DEBUG_PRINTLN(F("Don't burn down your garage!")); - //} - - /* * 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) * 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("MPU6050_IMU"); -// JsonArray pins = top.createNestedArray("pin"); -// pins.add(HW_PIN_SCL); -// pins.add(HW_PIN_SDA); -// } + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + + //save these vars persistently whenever settings are saved + top[FPSTR(_enabled)] = config.enabled; + top[FPSTR(_interrupt_pin)] = config.interruptPin; + top[FPSTR(_x_acc_bias)] = config.accel_offset[0]; + top[FPSTR(_y_acc_bias)] = config.accel_offset[1]; + top[FPSTR(_z_acc_bias)] = config.accel_offset[2]; + top[FPSTR(_x_gyro_bias)] = config.gyro_offset[0]; + top[FPSTR(_y_gyro_bias)] = config.gyro_offset[1]; + top[FPSTR(_z_gyro_bias)] = config.gyro_offset[2]; + } + + /* + * 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 immediately after boot, or after saving on the Usermod Settings page) + * + * 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 :) + * + * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) + * + * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present + * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them + * + * This function is guaranteed to be called on boot, but could also be called every time settings are updated + */ + bool readFromConfig(JsonObject& root) + { + // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor + // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) + auto old_cfg = config; + + JsonObject top = root[FPSTR(_name)]; + + bool configComplete = top.isNull(); + // Ensure default configuration is loaded + configComplete &= getJsonValue(top[FPSTR(_enabled)], config.enabled, true); + configComplete &= getJsonValue(top[FPSTR(_interrupt_pin)], config.interruptPin, -1); + configComplete &= getJsonValue(top[FPSTR(_x_acc_bias)], config.accel_offset[0], 0); + configComplete &= getJsonValue(top[FPSTR(_y_acc_bias)], config.accel_offset[1], 0); + configComplete &= getJsonValue(top[FPSTR(_z_acc_bias)], config.accel_offset[2], 0); + configComplete &= getJsonValue(top[FPSTR(_x_gyro_bias)], config.gyro_offset[0], 0); + configComplete &= getJsonValue(top[FPSTR(_y_gyro_bias)], config.gyro_offset[1], 0); + configComplete &= getJsonValue(top[FPSTR(_z_gyro_bias)], config.gyro_offset[2], 0); + + DEBUG_PRINT(FPSTR(_name)); + if (top.isNull()) { + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + } else if (!initDone()) { + DEBUG_PRINTLN(F(": config loaded.")); + } else if (memcmp(&config, &old_cfg, sizeof(config)) == 0) { + DEBUG_PRINTLN(F(": config unchanged.")); + } else { + DEBUG_PRINTLN(F(": config updated.")); + // Previously loaded and config changed + if (irqBound && ((old_cfg.interruptPin != config.interruptPin) || !config.enabled)) { + detachInterrupt(old_cfg.interruptPin); + pinManager.deallocatePin(old_cfg.interruptPin, PinOwner::UM_IMU); + irqBound = false; + } + + // Just re-init + setup(); + } + + return configComplete; + } + + bool getUMData(um_data_t **data) + { + if (!data || !config.enabled || !dmpReady) return false; // no pointer provided by caller or not enabled -> exit + *data = &um_data; + return true; + } /* * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). @@ -285,3 +430,14 @@ class MPU6050Driver : public Usermod { } }; + + +const char MPU6050Driver::_name[] PROGMEM = "MPU6050_IMU"; +const char MPU6050Driver::_enabled[] PROGMEM = "enabled"; +const char MPU6050Driver::_interrupt_pin[] PROGMEM = "interrupt_pin"; +const char MPU6050Driver::_x_acc_bias[] PROGMEM = "x_acc_bias"; +const char MPU6050Driver::_y_acc_bias[] PROGMEM = "y_acc_bias"; +const char MPU6050Driver::_z_acc_bias[] PROGMEM = "z_acc_bias"; +const char MPU6050Driver::_x_gyro_bias[] PROGMEM = "x_gyro_bias"; +const char MPU6050Driver::_y_gyro_bias[] PROGMEM = "y_gyro_bias"; +const char MPU6050Driver::_z_gyro_bias[] PROGMEM = "z_gyro_bias"; 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 71a540701..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 @@ -47,6 +47,24 @@ or You can override the default maximum number of relays (which is 4) by defining MULTI_RELAY_MAX_RELAYS. +Some settings can be defined (defaults) at compile time by setting the following defines: + +```cpp +// enable or disable HA discovery for externally controlled relays +#define MULTI_RELAY_HA_DISCOVERY true +``` + +The following definitions should be a list of values (maximum number of entries is MULTI_RELAY_MAX_RELAYS) that will be applied to the relays in order: +(e.g. assuming MULTI_RELAY_MAX_RELAYS=2) + +```cpp +#define MULTI_RELAY_PINS 12,18 +#define MULTI_RELAY_DELAYS 0,0 +#define MULTI_RELAY_EXTERNALS false,true +#define MULTI_RELAY_INVERTS false,false +``` +These can be set via your `platformio_override.ini` file or as `#define` in your `my_config.h` (remember to set `WLED_USE_MY_CONFIG` in your `platformio_override.ini`) + Example **usermods_list.cpp**: ```cpp @@ -107,4 +125,7 @@ Have fun - @blazoncek * Added button support. 2023-05 -* Added support for PCF8574 I2C port expander (multiple) \ No newline at end of file +* Added support for PCF8574 I2C port expander (multiple) + +2023-11 +* @chrisburrows Added support for compile time defaults for setting DELAY, EXTERNAL, INVERTS and HA discovery \ No newline at end of file diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index 7234df908..cb1eec8e1 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -2,6 +2,8 @@ #include "wled.h" +#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) + #ifndef MULTI_RELAY_MAX_RELAYS #define MULTI_RELAY_MAX_RELAYS 4 #else @@ -19,6 +21,22 @@ #define MULTI_RELAY_ENABLED true #endif +#ifndef MULTI_RELAY_HA_DISCOVERY + #define MULTI_RELAY_HA_DISCOVERY false +#endif + +#ifndef MULTI_RELAY_DELAYS + #define MULTI_RELAY_DELAYS 0 +#endif + +#ifndef MULTI_RELAY_EXTERNALS + #define MULTI_RELAY_EXTERNALS false +#endif + +#ifndef MULTI_RELAY_INVERTS + #define MULTI_RELAY_INVERTS false +#endif + #define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing) #define ON true @@ -86,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(); @@ -125,7 +146,7 @@ class MultiRelay : public Usermod { * 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. */ - inline uint16_t getId() { return USERMOD_ID_MULTI_RELAY; } + inline uint16_t getId() override { return USERMOD_ID_MULTI_RELAY; } /** * switch relay on/off @@ -143,22 +164,22 @@ class MultiRelay : public Usermod { * 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(); + void setup() override; /** * connected() is called every time the WiFi is (re)connected * Use it to initialize network interfaces */ - inline void connected() { InitHtmlAPIHandle(); } + inline void connected() override { InitHtmlAPIHandle(); } /** * loop() is called continuously. Here you can check for events, read sensors, etc. */ - void loop(); + void loop() override; #ifndef WLED_DISABLE_MQTT - bool onMqttMessage(char* topic, char* payload); - void onMqttConnect(bool sessionPresent); + bool onMqttMessage(char* topic, char* payload) override; + void onMqttConnect(bool sessionPresent) override; #endif /** @@ -166,31 +187,31 @@ class MultiRelay : public Usermod { * will prevent button working in a default way. * Replicating button.cpp */ - bool handleButton(uint8_t b); + bool handleButton(uint8_t b) override; /** * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. */ - void addToJsonInfo(JsonObject &root); + void addToJsonInfo(JsonObject &root) override; /** * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void addToJsonState(JsonObject &root); + void addToJsonState(JsonObject &root) override; /** * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void readFromJsonState(JsonObject &root); + void readFromJsonState(JsonObject &root) override; /** * provide the changeable values */ - void addToConfig(JsonObject &root); + void addToConfig(JsonObject &root) override; - void appendConfigData(); + void appendConfigData() override; /** * restore the changeable values @@ -198,11 +219,11 @@ class MultiRelay : public Usermod { * * The function should return true if configuration was successfully loaded or false if there was no configuration. */ - bool readFromConfig(JsonObject &root); + bool readFromConfig(JsonObject &root) override; }; -// class implementetion +// class implementation void MultiRelay::publishMqtt(int relay) { #ifndef WLED_DISABLE_MQTT @@ -243,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(); @@ -253,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); @@ -266,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); @@ -296,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); }); @@ -343,18 +364,22 @@ MultiRelay::MultiRelay() , initDone(false) , usePcf8574(USE_PCF8574) , addrPcf8574(PCF8574_ADDRESS) - , HAautodiscovery(false) + , HAautodiscovery(MULTI_RELAY_HA_DISCOVERY) , periodicBroadcastSec(60) , lastBroadcast(0) { const int8_t defPins[] = {MULTI_RELAY_PINS}; + const int8_t relayDelays[] = {MULTI_RELAY_DELAYS}; + const bool relayExternals[] = {MULTI_RELAY_EXTERNALS}; + const bool relayInverts[] = {MULTI_RELAY_INVERTS}; + for (size_t i=0; i=MULTI_RELAY_MAX_RELAYS || _relay[relay].pin<0) return; _relay[relay].state = mode; if (usePcf8574 && _relay[relay].pin >= 100) { - // we need to send all ouputs at the same time + // 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")] = "~"; @@ -512,10 +537,10 @@ void MultiRelay::setup() { * loop() is called continuously. Here you can check for events, read sensors, etc. */ void MultiRelay::loop() { - yield(); - if (!enabled || strip.isUpdating()) return; - static unsigned long lastUpdate = 0; + yield(); + if (!enabled || (strip.isUpdating() && millis() - lastUpdate < 100)) return; + if (millis() - lastUpdate < 100) return; // update only 10 times/s lastUpdate = millis(); @@ -653,8 +678,8 @@ void MultiRelay::addToJsonInfo(JsonObject &root) { uiDomString += F(",on:"); uiDomString += _relay[i].state ? "false" : "true"; uiDomString += F("}});\">"); - uiDomString += F(""); infoArr.add(uiDomString); } @@ -677,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 } @@ -781,13 +806,6 @@ bool MultiRelay::readFromConfig(JsonObject &root) { _relay[i].external = top[parName][FPSTR(_external)] | _relay[i].external; _relay[i].delay = top[parName][FPSTR(_delay_str)] | _relay[i].delay; _relay[i].button = top[parName][FPSTR(_button)] | _relay[i].button; - // begin backwards compatibility (beta) remove when 0.13 is released - parName += '-'; - _relay[i].pin = top[parName+"pin"] | _relay[i].pin; - _relay[i].invert = top[parName+FPSTR(_activeHigh)] | _relay[i].invert; - _relay[i].external = top[parName+FPSTR(_external)] | _relay[i].external; - _relay[i].delay = top[parName+FPSTR(_delay_str)] | _relay[i].delay; - // end compatibility _relay[i].delay = min(600,max(0,abs((int)_relay[i].delay))); // bounds checking max 10min } @@ -821,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/quinled-an-penta.h b/usermods/quinled-an-penta/quinled-an-penta.h index 5153ee58a..10b784334 100644 --- a/usermods/quinled-an-penta/quinled-an-penta.h +++ b/usermods/quinled-an-penta/quinled-an-penta.h @@ -84,11 +84,11 @@ class QuinLEDAnPentaUsermod : public Usermod void getCurrentUsedLedPins() { for (int8_t lp = 0; lp <= 4; lp++) currentLedPins[lp] = 0; - byte numBusses = busses.getNumBusses(); + byte numBusses = BusManager::getNumBusses(); byte numUsedPins = 0; for (int8_t b = 0; b < numBusses; b++) { - Bus* curBus = busses.getBus(b); + Bus* curBus = BusManager::getBus(b); if (curBus != nullptr) { uint8_t pins[5] = {0, 0, 0, 0, 0}; currentBussesNumPins[b] = curBus->getPins(pins); @@ -104,11 +104,11 @@ class QuinLEDAnPentaUsermod : public Usermod void getCurrentLedcValues() { - byte numBusses = busses.getNumBusses(); + byte numBusses = BusManager::getNumBusses(); byte numLedc = 0; for (int8_t b = 0; b < numBusses; b++) { - Bus* curBus = busses.getBus(b); + Bus* curBus = BusManager::getBus(b); if (curBus != nullptr) { uint32_t curPixColor = curBus->getPixelColor(0); uint8_t _data[5] = {255, 255, 255, 255, 255}; 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/sd_card/readme.md b/usermods/sd_card/readme.md index 299b68ebc..96390c05a 100644 --- a/usermods/sd_card/readme.md +++ b/usermods/sd_card/readme.md @@ -20,7 +20,7 @@ | `pinSourceSelect` | GPIO that is connected to SD's `SS`(source select) / `CS`(chip select) | 16 | | `pinSourceClock` | GPIO that is connected to SD's `SCLK` (source clock) / `CLK`(clock) | 14 | | `pinPoci` | GPIO that is connected to SD's `POCI` (Peripheral-Out-Ctrl-In) / `MISO` (deprecated) | 36 | - | `pinPico` | GPIO that is connected to SD's `PICO` (Peripheral-In-Ctrl-Out) / `MOSI` (deprecated) | 14 | + | `pinPico` | GPIO that is connected to SD's `PICO` (Peripheral-In-Ctrl-Out) / `MOSI` (deprecated) | 15 | | `sdEnable` | Enable to read data from the SD-card | true | Following new naming convention of [OSHWA](https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/) @@ -31,4 +31,4 @@ - checks if the specified file is available on the SD card ```cpp bool file_onSD(const char *filepath) {...} - ``` \ No newline at end of file + ``` 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/readme.md b/usermods/seven_segment_display_reloaded/readme.md index d373a7eee..a3398c3e5 100644 --- a/usermods/seven_segment_display_reloaded/readme.md +++ b/usermods/seven_segment_display_reloaded/readme.md @@ -24,6 +24,9 @@ Enables the inverted mode in which the background should be enabled and the digi ### Colon-blinking Enables the blinking colon(s) if they are defined +### Leading-Zero +Shows the leading zero of the hour if it exists (i.e. shows `07` instead of `7`) + ### enable-auto-brightness Enables the auto brightness feature. Can be used only when the usermod SN_Photoresistor is installed. 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 279774057..5c2fac0d4 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -17,6 +17,7 @@ private: bool umSSDRDisplayTime = false; bool umSSDRInverted = false; bool umSSDRColonblink = true; + bool umSSDRLeadingZero = false; bool umSSDREnableLDR = false; String umSSDRHours = ""; String umSSDRMinutes = ""; @@ -79,6 +80,7 @@ private: static const char _str_timeEnabled[]; static const char _str_inverted[]; static const char _str_colonblink[]; + static const char _str_leadingZero[]; static const char _str_displayMask[]; static const char _str_hours[]; static const char _str_minutes[]; @@ -105,15 +107,15 @@ private: switch (umSSDRDisplayMask[index]) { case 'h': timeVar = hourFormat12(localTime); - _showElements(&umSSDRHours, timeVar, 0, 1); + _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); break; case 'H': timeVar = hour(localTime); - _showElements(&umSSDRHours, timeVar, 0, 1); + _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); break; case 'k': timeVar = hour(localTime) + 1; - _showElements(&umSSDRHours, timeVar, 0, 0); + _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); break; case 'm': timeVar = minute(localTime); @@ -309,6 +311,9 @@ private: if (_cmpIntSetting_P(topic, payload, _str_colonblink, &umSSDRColonblink)) { return true; } + if (_cmpIntSetting_P(topic, payload, _str_leadingZero, &umSSDRLeadingZero)) { + return true; + } if (strcmp_P(topic, _str_displayMask) == 0) { umSSDRDisplayMask = String(payload); _publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask); @@ -323,6 +328,7 @@ private: _publishMQTTint_P(_str_ldrEnabled, umSSDREnableLDR); _publishMQTTint_P(_str_inverted, umSSDRInverted); _publishMQTTint_P(_str_colonblink, umSSDRColonblink); + _publishMQTTint_P(_str_leadingZero, umSSDRLeadingZero); _publishMQTTstr_P(_str_hours, umSSDRHours); _publishMQTTstr_P(_str_minutes, umSSDRMinutes); @@ -347,6 +353,7 @@ private: ssdrObj[FPSTR(_str_ldrEnabled)] = umSSDREnableLDR; ssdrObj[FPSTR(_str_inverted)] = umSSDRInverted; ssdrObj[FPSTR(_str_colonblink)] = umSSDRColonblink; + ssdrObj[FPSTR(_str_leadingZero)] = umSSDRLeadingZero; ssdrObj[FPSTR(_str_displayMask)] = umSSDRDisplayMask; ssdrObj[FPSTR(_str_hours)] = umSSDRHours; ssdrObj[FPSTR(_str_minutes)] = umSSDRMinutes; @@ -425,6 +432,8 @@ public: invert.add(umSSDRInverted); JsonArray blink = user.createNestedArray("Blinking colon"); blink.add(umSSDRColonblink); + JsonArray zero = user.createNestedArray("Show the hour leading zero"); + zero.add(umSSDRLeadingZero); JsonArray ldrEnable = user.createNestedArray("Auto Brightness enabled"); ldrEnable.add(umSSDREnableLDR); @@ -454,6 +463,7 @@ public: umSSDREnableLDR = ssdrObj[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR; umSSDRInverted = ssdrObj[FPSTR(_str_inverted)] | umSSDRInverted; umSSDRColonblink = ssdrObj[FPSTR(_str_colonblink)] | umSSDRColonblink; + umSSDRLeadingZero = ssdrObj[FPSTR(_str_leadingZero)] | umSSDRLeadingZero; umSSDRDisplayMask = ssdrObj[FPSTR(_str_displayMask)] | umSSDRDisplayMask; } } @@ -470,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; @@ -516,6 +526,7 @@ public: umSSDREnableLDR = (top[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR); umSSDRInverted = (top[FPSTR(_str_inverted)] | umSSDRInverted); umSSDRColonblink = (top[FPSTR(_str_colonblink)] | umSSDRColonblink); + umSSDRLeadingZero = (top[FPSTR(_str_leadingZero)] | umSSDRLeadingZero); umSSDRDisplayMask = top[FPSTR(_str_displayMask)] | umSSDRDisplayMask; umSSDRHours = top[FPSTR(_str_hours)] | umSSDRHours; @@ -546,6 +557,7 @@ const char UsermodSSDR::_str_name[] PROGMEM = "UsermodSSDR"; const char UsermodSSDR::_str_timeEnabled[] PROGMEM = "enabled"; const char UsermodSSDR::_str_inverted[] PROGMEM = "inverted"; const char UsermodSSDR::_str_colonblink[] PROGMEM = "Colon-blinking"; +const char UsermodSSDR::_str_leadingZero[] PROGMEM = "Leading-Zero"; const char UsermodSSDR::_str_displayMask[] PROGMEM = "Display-Mask"; const char UsermodSSDR::_str_hours[] PROGMEM = "LED-Numbers-Hours"; const char UsermodSSDR::_str_minutes[] PROGMEM = "LED-Numbers-Minutes"; 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 238ec7d9c..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,14 +86,15 @@ class StairwayWipeUsermod : public Usermod { uint16_t getId() { - return USERMOD_ID_EXAMPLE; + return USERMOD_ID_STAIRWAY_WIPE; } void startWipe() { bri = briLast; //turn on - transitionDelayTemp = 0; //no transition + jsonTransitionOnce = true; + strip.setTransition(0); //no transition effectCurrent = FX_MODE_COLOR_WIPE; resetTimebase(); //make sure wipe starts from beginning @@ -105,10 +108,11 @@ class StairwayWipeUsermod : public Usermod { void turnOff() { + jsonTransitionOnce = true; #ifdef STAIRCASE_WIPE_OFF - transitionDelayTemp = 0; //turn off immediately after wipe completed + strip.setTransition(0); //turn off immediately after wipe completed #else - transitionDelayTemp = 4000; //fade out slowly + strip.setTransition(4000); //fade out slowly #endif bri = 0; stateUpdated(CALL_MODE_NOTIFICATION); 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_animartrix/readme.md b/usermods/usermod_v2_animartrix/readme.md new file mode 100644 index 000000000..42d463c50 --- /dev/null +++ b/usermods/usermod_v2_animartrix/readme.md @@ -0,0 +1,14 @@ +# ANIMartRIX + +Addes the effects from ANIMartRIX to WLED + +CC BY-NC 3.0 licensed effects by Stefan Petrick, include this usermod only if you accept the terms! + +## Installation + +Please uncomment the two references to ANIMartRIX in your platform.ini + +lib_dep to a version of https://github.com/netmindz/animartrix.git +and the build_flags -D USERMOD_ANIMARTRIX + + diff --git a/usermods/usermod_v2_animartrix/usermod_v2_animartrix.h b/usermods/usermod_v2_animartrix/usermod_v2_animartrix.h new file mode 100644 index 000000000..d91cf6c96 --- /dev/null +++ b/usermods/usermod_v2_animartrix/usermod_v2_animartrix.h @@ -0,0 +1,456 @@ +#pragma once + +#include "wled.h" +#include + +#warning WLED usermod: CC BY-NC 3.0 licensed effects by Stefan Petrick, include this usermod only if you accept the terms! +//======================================================================================================================== + + +static const char _data_FX_mode_Module_Experiment10[] PROGMEM = "Z💡Module_Experiment10@Speed;;1;2"; +static const char _data_FX_mode_Module_Experiment9[] PROGMEM = "Z💡Module_Experiment9@Speed;;1;2"; +static const char _data_FX_mode_Module_Experiment8[] PROGMEM = "Z💡Module_Experiment8@Speed;;1;2"; +static const char _data_FX_mode_Module_Experiment7[] PROGMEM = "Z💡Module_Experiment7@Speed;;1;2"; +static const char _data_FX_mode_Module_Experiment6[] PROGMEM = "Z💡Module_Experiment6@Speed;;1;2"; +static const char _data_FX_mode_Module_Experiment5[] PROGMEM = "Z💡Module_Experiment5@Speed;;1;2"; +static const char _data_FX_mode_Module_Experiment4[] PROGMEM = "Z💡Module_Experiment4@Speed;;1;2"; +static const char _data_FX_mode_Zoom2[] PROGMEM = "Z💡Zoom2@Speed;;1;2"; +static const char _data_FX_mode_Module_Experiment3[] PROGMEM = "Z💡Module_Experiment3@Speed;;1;2"; +static const char _data_FX_mode_Module_Experiment2[] PROGMEM = "Z💡Module_Experiment2@Speed;;1;2"; +static const char _data_FX_mode_Module_Experiment1[] PROGMEM = "Z💡Module_Experiment1@Speed;;1;2"; +static const char _data_FX_mode_Parametric_Water[] PROGMEM = "Z💡Parametric_Water@Speed;;1;2"; +static const char _data_FX_mode_Water[] PROGMEM = "Z💡Water@Speed;;1;2"; +static const char _data_FX_mode_Complex_Kaleido_6[] PROGMEM = "Z💡Complex_Kaleido_6@Speed;;1;2"; +static const char _data_FX_mode_Complex_Kaleido_5[] PROGMEM = "Z💡Complex_Kaleido_5@Speed;;1;2"; +static const char _data_FX_mode_Complex_Kaleido_4[] PROGMEM = "Z💡Complex_Kaleido_4@Speed;;1;2"; +static const char _data_FX_mode_Complex_Kaleido_3[] PROGMEM = "Z💡Complex_Kaleido_3@Speed;;1;2"; +static const char _data_FX_mode_Complex_Kaleido_2[] PROGMEM = "Z💡Complex_Kaleido_2@Speed;;1;2"; +static const char _data_FX_mode_Complex_Kaleido[] PROGMEM = "Z💡Complex_Kaleido@Speed;;1;2"; +static const char _data_FX_mode_SM10[] PROGMEM = "Z💡SM10@Speed;;1;2"; +static const char _data_FX_mode_SM9[] PROGMEM = "Z💡SM9@Speed;;1;2"; +static const char _data_FX_mode_SM8[] PROGMEM = "Z💡SM8@Speed;;1;2"; +static const char _data_FX_mode_SM7[] PROGMEM = "Z💡SM7@Speed;;1;2"; +static const char _data_FX_mode_SM6[] PROGMEM = "Z💡SM6@Speed;;1;2"; +static const char _data_FX_mode_SM5[] PROGMEM = "Z💡SM5@Speed;;1;2"; +static const char _data_FX_mode_SM4[] PROGMEM = "Z💡SM4@Speed;;1;2"; +static const char _data_FX_mode_SM3[] PROGMEM = "Z💡SM3@Speed;;1;2"; +static const char _data_FX_mode_SM2[] PROGMEM = "Z💡SM2@Speed;;1;2"; +static const char _data_FX_mode_SM1[] PROGMEM = "Z💡SM1@Speed;;1;2"; +static const char _data_FX_mode_Big_Caleido[] PROGMEM = "Z💡Big_Caleido@Speed;;1;2"; +static const char _data_FX_mode_RGB_Blobs5[] PROGMEM = "Z💡RGB_Blobs5@Speed;;1;2"; +static const char _data_FX_mode_RGB_Blobs4[] PROGMEM = "Z💡RGB_Blobs4@Speed;;1;2"; +static const char _data_FX_mode_RGB_Blobs3[] PROGMEM = "Z💡RGB_Blobs3@Speed;;1;2"; +static const char _data_FX_mode_RGB_Blobs2[] PROGMEM = "Z💡RGB_Blobs2@Speed;;1;2"; +static const char _data_FX_mode_RGB_Blobs[] PROGMEM = "Z💡RGB_Blobs@Speed;;1;2"; +static const char _data_FX_mode_Polar_Waves[] PROGMEM = "Z💡Polar_Waves@Speed;;1;2"; +static const char _data_FX_mode_Slow_Fade[] PROGMEM = "Z💡Slow_Fade@Speed;;1;2"; +static const char _data_FX_mode_Zoom[] PROGMEM = "Z💡Zoom@Speed;;1;2"; +static const char _data_FX_mode_Hot_Blob[] PROGMEM = "Z💡Hot_Blob@Speed;;1;2"; +static const char _data_FX_mode_Spiralus2[] PROGMEM = "Z💡Spiralus2@Speed;;1;2"; +static const char _data_FX_mode_Spiralus[] PROGMEM = "Z💡Spiralus@Speed;;1;2"; +static const char _data_FX_mode_Yves[] PROGMEM = "Z💡Yves@Speed;;1;2"; +static const char _data_FX_mode_Scaledemo1[] PROGMEM = "Z💡Scaledemo1@Speed;;1;2"; +static const char _data_FX_mode_Lava1[] PROGMEM = "Z💡Lava1@Speed;;1;2"; +static const char _data_FX_mode_Caleido3[] PROGMEM = "Z💡Caleido3@Speed;;1;2"; +static const char _data_FX_mode_Caleido2[] PROGMEM = "Z💡Caleido2@Speed;;1;2"; +static const char _data_FX_mode_Caleido1[] PROGMEM = "Z💡Caleido1@Speed;;1;2"; +static const char _data_FX_mode_Distance_Experiment[] PROGMEM = "Z💡Distance_Experiment@Speed;;1;2"; +static const char _data_FX_mode_Center_Field[] PROGMEM = "Z💡Center_Field@Speed;;1;2"; +static const char _data_FX_mode_Waves[] PROGMEM = "Z💡Waves@Speed;;1;2"; +static const char _data_FX_mode_Chasing_Spirals[] PROGMEM = "Z💡Chasing_Spirals@Speed;;1;2"; +static const char _data_FX_mode_Rotating_Blob[] PROGMEM = "Z💡Rotating_Blob@Speed;;1;2"; + + +class ANIMartRIXMod:public ANIMartRIX { + public: + void initEffect() { + if (SEGENV.call == 0) { + init(SEGMENT.virtualWidth(), SEGMENT.virtualHeight(), false); + } + float speedFactor = 1.0; + if (SEGMENT.speed < 128) { + speedFactor = (float) map(SEGMENT.speed, 0, 127, 1, 10) / 10.0f; + } + else{ + speedFactor = map(SEGMENT.speed, 128, 255, 10, 100) / 10; + } + setSpeedFactor(speedFactor); + } + void setPixelColor(int x, int y, rgb pixel) { + SEGMENT.setPixelColorXY(x, y, CRGB(pixel.red, pixel.green, pixel.blue)); + } + void setPixelColor(int index, rgb pixel) { + SEGMENT.setPixelColor(index, CRGB(pixel.red, pixel.green, pixel.blue)); + } + + // Add any extra custom effects not part of the ANIMartRIX libary here +}; +ANIMartRIXMod anim; + +uint16_t mode_Module_Experiment10() { + anim.initEffect(); + anim.Module_Experiment10(); + return FRAMETIME; +} +uint16_t mode_Module_Experiment9() { + anim.initEffect(); + anim.Module_Experiment9(); + return FRAMETIME; +} +uint16_t mode_Module_Experiment8() { + anim.initEffect(); + anim.Module_Experiment8(); + return FRAMETIME; +} +uint16_t mode_Module_Experiment7() { + anim.initEffect(); + anim.Module_Experiment7(); + return FRAMETIME; +} +uint16_t mode_Module_Experiment6() { + anim.initEffect(); + anim.Module_Experiment6(); + return FRAMETIME; +} +uint16_t mode_Module_Experiment5() { + anim.initEffect(); + anim.Module_Experiment5(); + return FRAMETIME; +} +uint16_t mode_Module_Experiment4() { + anim.initEffect(); + anim.Module_Experiment4(); + return FRAMETIME; +} +uint16_t mode_Zoom2() { + anim.initEffect(); + anim.Zoom2(); + return FRAMETIME; +} +uint16_t mode_Module_Experiment3() { + anim.initEffect(); + anim.Module_Experiment3(); + return FRAMETIME; +} +uint16_t mode_Module_Experiment2() { + anim.initEffect(); + anim.Module_Experiment2(); + return FRAMETIME; +} +uint16_t mode_Module_Experiment1() { + anim.initEffect(); + anim.Module_Experiment1(); + return FRAMETIME; +} +uint16_t mode_Parametric_Water() { + anim.initEffect(); + anim.Parametric_Water(); + return FRAMETIME; +} +uint16_t mode_Water() { + anim.initEffect(); + anim.Water(); + return FRAMETIME; +} +uint16_t mode_Complex_Kaleido_6() { + anim.initEffect(); + anim.Complex_Kaleido_6(); + return FRAMETIME; +} +uint16_t mode_Complex_Kaleido_5() { + anim.initEffect(); + anim.Complex_Kaleido_5(); + return FRAMETIME; +} +uint16_t mode_Complex_Kaleido_4() { + anim.initEffect(); + anim.Complex_Kaleido_4(); + return FRAMETIME; +} +uint16_t mode_Complex_Kaleido_3() { + anim.initEffect(); + anim.Complex_Kaleido_3(); + return FRAMETIME; +} +uint16_t mode_Complex_Kaleido_2() { + anim.initEffect(); + anim.Complex_Kaleido_2(); + return FRAMETIME; +} +uint16_t mode_Complex_Kaleido() { + anim.initEffect(); + anim.Complex_Kaleido(); + return FRAMETIME; +} +uint16_t mode_SM10() { + anim.initEffect(); + anim.SM10(); + return FRAMETIME; +} +uint16_t mode_SM9() { + anim.initEffect(); + anim.SM9(); + return FRAMETIME; +} +uint16_t mode_SM8() { + anim.initEffect(); + anim.SM8(); + return FRAMETIME; +} +// uint16_t mode_SM7() { +// anim.initEffect(); +// anim.SM7(); +// +// return FRAMETIME; +// } +uint16_t mode_SM6() { + anim.initEffect(); + anim.SM6(); + return FRAMETIME; +} +uint16_t mode_SM5() { + anim.initEffect(); + anim.SM5(); + return FRAMETIME; +} +uint16_t mode_SM4() { + anim.initEffect(); + anim.SM4(); + return FRAMETIME; +} +uint16_t mode_SM3() { + anim.initEffect(); + anim.SM3(); + return FRAMETIME; +} +uint16_t mode_SM2() { + anim.initEffect(); + anim.SM2(); + return FRAMETIME; +} +uint16_t mode_SM1() { + anim.initEffect(); + anim.SM1(); + return FRAMETIME; +} +uint16_t mode_Big_Caleido() { + anim.initEffect(); + anim.Big_Caleido(); + return FRAMETIME; +} +uint16_t mode_RGB_Blobs5() { + anim.initEffect(); + anim.RGB_Blobs5(); + return FRAMETIME; +} +uint16_t mode_RGB_Blobs4() { + anim.initEffect(); + anim.RGB_Blobs4(); + return FRAMETIME; +} +uint16_t mode_RGB_Blobs3() { + anim.initEffect(); + anim.RGB_Blobs3(); + return FRAMETIME; +} +uint16_t mode_RGB_Blobs2() { + anim.initEffect(); + anim.RGB_Blobs2(); + return FRAMETIME; +} +uint16_t mode_RGB_Blobs() { + anim.initEffect(); + anim.RGB_Blobs(); + return FRAMETIME; +} +uint16_t mode_Polar_Waves() { + anim.initEffect(); + anim.Polar_Waves(); + return FRAMETIME; +} +uint16_t mode_Slow_Fade() { + anim.initEffect(); + anim.Slow_Fade(); + return FRAMETIME; +} +uint16_t mode_Zoom() { + anim.initEffect(); + anim.Zoom(); + return FRAMETIME; +} +uint16_t mode_Hot_Blob() { + anim.initEffect(); + anim.Hot_Blob(); + return FRAMETIME; +} +uint16_t mode_Spiralus2() { + anim.initEffect(); + anim.Spiralus2(); + return FRAMETIME; +} +uint16_t mode_Spiralus() { + anim.initEffect(); + anim.Spiralus(); + return FRAMETIME; +} +uint16_t mode_Yves() { + anim.initEffect(); + anim.Yves(); + return FRAMETIME; +} +uint16_t mode_Scaledemo1() { + anim.initEffect(); + anim.Scaledemo1(); + return FRAMETIME; +} +uint16_t mode_Lava1() { + anim.initEffect(); + anim.Lava1(); + return FRAMETIME; +} +uint16_t mode_Caleido3() { + anim.initEffect(); + anim.Caleido3(); + return FRAMETIME; +} +uint16_t mode_Caleido2() { + anim.initEffect(); + anim.Caleido2(); + return FRAMETIME; +} +uint16_t mode_Caleido1() { + anim.initEffect(); + anim.Caleido1(); + return FRAMETIME; +} +uint16_t mode_Distance_Experiment() { + anim.initEffect(); + anim.Distance_Experiment(); + return FRAMETIME; +} +uint16_t mode_Center_Field() { + anim.initEffect(); + anim.Center_Field(); + return FRAMETIME; +} +uint16_t mode_Waves() { + anim.initEffect(); + anim.Waves(); + return FRAMETIME; +} +uint16_t mode_Chasing_Spirals() { + anim.initEffect(); + anim.Chasing_Spirals(); + return FRAMETIME; +} +uint16_t mode_Rotating_Blob() { + anim.initEffect(); + anim.Rotating_Blob(); + return FRAMETIME; +} + + +class AnimartrixUsermod : public Usermod { + protected: + bool enabled = false; //WLEDMM + const char *_name; //WLEDMM + bool initDone = false; //WLEDMM + unsigned long lastTime = 0; //WLEDMM + + public: + + AnimartrixUsermod(const char *name, bool enabled) { + this->_name = name; + this->enabled = enabled; + } //WLEDMM + + + void setup() { + + strip.addEffect(255, &mode_Module_Experiment10, _data_FX_mode_Module_Experiment10); + strip.addEffect(255, &mode_Module_Experiment9, _data_FX_mode_Module_Experiment9); + strip.addEffect(255, &mode_Module_Experiment8, _data_FX_mode_Module_Experiment8); + strip.addEffect(255, &mode_Module_Experiment7, _data_FX_mode_Module_Experiment7); + strip.addEffect(255, &mode_Module_Experiment6, _data_FX_mode_Module_Experiment6); + strip.addEffect(255, &mode_Module_Experiment5, _data_FX_mode_Module_Experiment5); + strip.addEffect(255, &mode_Module_Experiment4, _data_FX_mode_Module_Experiment4); + strip.addEffect(255, &mode_Zoom2, _data_FX_mode_Zoom2); + strip.addEffect(255, &mode_Module_Experiment3, _data_FX_mode_Module_Experiment3); + strip.addEffect(255, &mode_Module_Experiment2, _data_FX_mode_Module_Experiment2); + strip.addEffect(255, &mode_Module_Experiment1, _data_FX_mode_Module_Experiment1); + strip.addEffect(255, &mode_Parametric_Water, _data_FX_mode_Parametric_Water); + strip.addEffect(255, &mode_Water, _data_FX_mode_Water); + strip.addEffect(255, &mode_Complex_Kaleido_6, _data_FX_mode_Complex_Kaleido_6); + strip.addEffect(255, &mode_Complex_Kaleido_5, _data_FX_mode_Complex_Kaleido_5); + strip.addEffect(255, &mode_Complex_Kaleido_4, _data_FX_mode_Complex_Kaleido_4); + strip.addEffect(255, &mode_Complex_Kaleido_3, _data_FX_mode_Complex_Kaleido_3); + strip.addEffect(255, &mode_Complex_Kaleido_2, _data_FX_mode_Complex_Kaleido_2); + strip.addEffect(255, &mode_Complex_Kaleido, _data_FX_mode_Complex_Kaleido); + strip.addEffect(255, &mode_SM10, _data_FX_mode_SM10); + strip.addEffect(255, &mode_SM9, _data_FX_mode_SM9); + strip.addEffect(255, &mode_SM8, _data_FX_mode_SM8); + // strip.addEffect(255, &mode_SM7, _data_FX_mode_SM7); + strip.addEffect(255, &mode_SM6, _data_FX_mode_SM6); + strip.addEffect(255, &mode_SM5, _data_FX_mode_SM5); + strip.addEffect(255, &mode_SM4, _data_FX_mode_SM4); + strip.addEffect(255, &mode_SM3, _data_FX_mode_SM3); + strip.addEffect(255, &mode_SM2, _data_FX_mode_SM2); + strip.addEffect(255, &mode_SM1, _data_FX_mode_SM1); + strip.addEffect(255, &mode_Big_Caleido, _data_FX_mode_Big_Caleido); + strip.addEffect(255, &mode_RGB_Blobs5, _data_FX_mode_RGB_Blobs5); + strip.addEffect(255, &mode_RGB_Blobs4, _data_FX_mode_RGB_Blobs4); + strip.addEffect(255, &mode_RGB_Blobs3, _data_FX_mode_RGB_Blobs3); + strip.addEffect(255, &mode_RGB_Blobs2, _data_FX_mode_RGB_Blobs2); + strip.addEffect(255, &mode_RGB_Blobs, _data_FX_mode_RGB_Blobs); + strip.addEffect(255, &mode_Polar_Waves, _data_FX_mode_Polar_Waves); + strip.addEffect(255, &mode_Slow_Fade, _data_FX_mode_Slow_Fade); + strip.addEffect(255, &mode_Zoom, _data_FX_mode_Zoom); + strip.addEffect(255, &mode_Hot_Blob, _data_FX_mode_Hot_Blob); + strip.addEffect(255, &mode_Spiralus2, _data_FX_mode_Spiralus2); + strip.addEffect(255, &mode_Spiralus, _data_FX_mode_Spiralus); + strip.addEffect(255, &mode_Yves, _data_FX_mode_Yves); + strip.addEffect(255, &mode_Scaledemo1, _data_FX_mode_Scaledemo1); + strip.addEffect(255, &mode_Lava1, _data_FX_mode_Lava1); + strip.addEffect(255, &mode_Caleido3, _data_FX_mode_Caleido3); + strip.addEffect(255, &mode_Caleido2, _data_FX_mode_Caleido2); + strip.addEffect(255, &mode_Caleido1, _data_FX_mode_Caleido1); + strip.addEffect(255, &mode_Distance_Experiment, _data_FX_mode_Distance_Experiment); + strip.addEffect(255, &mode_Center_Field, _data_FX_mode_Center_Field); + strip.addEffect(255, &mode_Waves, _data_FX_mode_Waves); + strip.addEffect(255, &mode_Chasing_Spirals, _data_FX_mode_Chasing_Spirals); + strip.addEffect(255, &mode_Rotating_Blob, _data_FX_mode_Rotating_Blob); + + initDone = true; + } + + void loop() { + if (!enabled || strip.isUpdating()) return; + + // do your magic here + if (millis() - lastTime > 1000) { + //USER_PRINTLN("I'm alive!"); + lastTime = millis(); + } + } + + void addToJsonInfo(JsonObject& root) + { + char myStringBuffer[16]; // buffer for snprintf() + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + + String uiDomString = F("Animartrix requires the Creative Commons Attribution License CC BY-NC 3.0"); + infoArr.add(uiDomString); + } + + uint16_t getId() + { + return USERMOD_ID_ANIMARTRIX; + } + +}; + + + 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/readme.md b/usermods/usermod_v2_four_line_display_ALT/readme.md index ea9f43610..a8f386dac 100644 --- a/usermods/usermod_v2_four_line_display_ALT/readme.md +++ b/usermods/usermod_v2_four_line_display_ALT/readme.md @@ -1,4 +1,4 @@ -# I2C 4 Line Display Usermod ALT +# I2C/SPI 4 Line Display Usermod ALT Thank you to the authors of the original version of these usermods. It would not have been possible without them! "usermod_v2_four_line_display" @@ -8,21 +8,20 @@ The core of these usermods are a copy of the originals. The main changes are to The display usermod UI has been completely changed. -The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. -Without the display it, functions identical to the original. +The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. +Without the display, it functions identical to the original. The original "usermod_v2_auto_save" will not work with the display just yet. Press the encoder to cycle through the options: - *Brightness - *Speed - *Intensity - *Palette - *Effect - *Main Color (only if display is used) - *Saturation (only if display is used) +* Brightness +* Speed +* Intensity +* Palette +* Effect +* Main Color (only if display is used) +* Saturation (only if display is used) -Press and hold the encoder to display Network Info - if AP is active, it will display AP, SSID and password +Press and hold the encoder to display Network Info. If AP is active, it will display AP, SSID and password Also shows if the timer is enabled @@ -30,11 +29,47 @@ Also shows if the timer is enabled ## Installation -Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions -Then to activate this alternative usermod add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file, +Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions. + +Copy the example `platformio_override.sample.ini` from the usermod_v2_rotary_encoder_ui_ALT folder to the root directory of your particular build and rename it to `platformio_override.ini`. + +This file should be placed in the same directory as `platformio.ini`. + +Then, to activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file, or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file +## Configuration + +These options are configurable in Config > Usermods + +### Usermod Setup + +* Global I2C GPIOs (HW) - Set the SDA and SCL pins + +### 4LineDisplay + +* `enabled` - enable/disable usermod +* `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) + * 8 = SPI SSD1309 128x64 (4 double-height lines) + * 9 = I2C SSD1309 128x64 (4 double-height lines) +* `pin` - GPIO pins used for display; SPI displays can use SCK, MOSI, CS, DC & RST +* `flip` - flip/rotate display 180° +* `contrast` - set display contrast (higher contrast may reduce display lifetime) +* `screenTimeOutSec` - screen saver time-out in seconds +* `sleepMode` - enable/disable screen saver +* `clockMode` - enable/disable clock display in screen saver mode +* `showSeconds` - Show seconds on the clock display +* `i2c-freq-kHz` - I2C clock frequency in kHz (may help reduce dropped frames, range: 400-3400) + + ### PlatformIO requirements Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. 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 5a99c3cdb..24eb9794f 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 // @@ -17,7 +17,7 @@ // for WLED. // // Dependencies -// * This Usermod works best, by far, when coupled +// * This Usermod works best, by far, when coupled // with RotaryEncoderUI ALT Usermod. // // Make sure to enable NTP and set your time zone in WLED Config | Time. @@ -89,7 +89,8 @@ typedef enum { SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI SSD1306_SPI64, // U8X8_SSD1306_128X64_NONAME_HW_SPI - SSD1309_SPI64 // U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI + SSD1309_SPI64, // U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI + SSD1309_64 // U8X8_SSD1309_128X64_NONAME0_HW_I2C } DisplayType; @@ -211,16 +212,16 @@ class FourLineDisplayUsermod : public Usermod { // gets called once at boot. Do all initialization that doesn't depend on // network here - void setup(); + void setup() override; // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here - void connected(); + void connected() override; /** * Da loop. */ - void loop(); + void loop() override; //function to update lastredraw inline void updateRedrawTime() { lastRedraw = millis(); } @@ -235,7 +236,7 @@ class FourLineDisplayUsermod : public Usermod { void updateSpeed(); void updateIntensity(); void drawStatusIcons(); - + /** * marks the position of the arrow showing * the current setting being changed @@ -243,11 +244,11 @@ 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) - // on the appropriate line (row). + //Display the current effect or palette (desiredEntry) + // on the appropriate line (row). void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row); /** @@ -287,60 +288,60 @@ class FourLineDisplayUsermod : public Usermod { */ bool handleButton(uint8_t b); - void onUpdateBegin(bool init); + void onUpdateBegin(bool init) override; /* * 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); + //void addToJsonInfo(JsonObject& root) override; /* * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - //void addToJsonState(JsonObject& root); + //void addToJsonState(JsonObject& root) override; /* * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - //void readFromJsonState(JsonObject& root); + //void readFromJsonState(JsonObject& root) override; - void appendConfigData(); + void appendConfigData() override; /* * 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); + void addToConfig(JsonObject& root) override; /* * 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 readFromConfig(JsonObject& root) override; /* * 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() { + uint16_t getId() override { return USERMOD_ID_FOUR_LINE_DISP; } }; @@ -494,7 +495,7 @@ void FourLineDisplayUsermod::showTime() { } if (knownHour != hourCurrent) { // only update date when hour changes - sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); + sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day } sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent); @@ -556,6 +557,7 @@ void FourLineDisplayUsermod::setup() { case SSD1306_64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(); break; case SSD1305: u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(); break; case SSD1305_64: u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(); break; + case SSD1309_64: u8x8 = (U8X8 *) new U8X8_SSD1309_128X64_NONAME0_HW_I2C(); break; // U8X8 uses global SPI variable that is attached to VSPI bus on ESP32 case SSD1306_SPI: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset case SSD1306_SPI64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset @@ -581,7 +583,7 @@ void FourLineDisplayUsermod::setup() { // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here void FourLineDisplayUsermod::connected() { - knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : + knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); networkOverlay(PSTR("NETWORK INFO"),7000); } @@ -637,7 +639,7 @@ void FourLineDisplayUsermod::redraw(bool forceRedraw) { powerON = !powerON; drawStatusIcons(); return; - } else if (knownnightlight != nightlightActive) { //trigger moon icon + } else if (knownnightlight != nightlightActive) { //trigger moon icon knownnightlight = nightlightActive; drawStatusIcons(); if (knownnightlight) { @@ -652,7 +654,7 @@ void FourLineDisplayUsermod::redraw(bool forceRedraw) { return; } else if (knownMode != effectCurrent || knownPalette != effectPalette) { if (displayTurnedOff) needRedraw = true; - else { + else { if (knownPalette != effectPalette) { showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); knownPalette = effectPalette; } if (knownMode != effectCurrent) { showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); knownMode = effectCurrent; } lastRedraw = now; @@ -703,7 +705,7 @@ void FourLineDisplayUsermod::redraw(bool forceRedraw) { drawArrow(); drawStatusIcons(); - // Second row + // Second row updateBrightness(); updateSpeed(); updateIntensity(); @@ -793,7 +795,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(); @@ -805,8 +807,8 @@ void FourLineDisplayUsermod::drawArrow() { lockRedraw = false; } -//Display the current effect or palette (desiredEntry) -// on the appropriate line (row). +//Display the current effect or palette (desiredEntry) +// on the appropriate line (row). void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { #if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) unsigned long now = millis(); @@ -857,7 +859,7 @@ void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const c while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' '; smallBuffer1[smallChars1] = 0; drawString(1, row*lineHeight, smallBuffer1, true); - while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' '; + while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' '; smallBuffer2[smallChars2] = 0; drawString(1, row*lineHeight+1, smallBuffer2, true); } @@ -1066,7 +1068,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 @@ -1081,7 +1083,7 @@ bool FourLineDisplayUsermod::handleButton(uint8_t b) { static bool buttonLongPressed = false; static unsigned long buttonPressedTime = 0; static unsigned long buttonWaitTime = 0; - bool handled = true; + bool handled = false; //momentary button logic if (isButtonPressed(b)) { //pressed @@ -1090,11 +1092,12 @@ bool FourLineDisplayUsermod::handleButton(uint8_t b) { buttonPressedBefore = true; if (now - buttonPressedTime > 600) { //long press - buttonLongPressed = true; //TODO: handleButton() handles button 0 without preset in a different way for double click //so we need to override with same behaviour - longPressAction(0); - //handled = false; + //DEBUG_PRINTLN(F("4LD action.")); + //if (!buttonLongPressed) longPressAction(0); + buttonLongPressed = true; + return false; } } else if (!isButtonPressed(b) && buttonPressedBefore) { //released @@ -1126,7 +1129,7 @@ bool FourLineDisplayUsermod::handleButton(uint8_t b) { buttonWaitTime = 0; //TODO: handleButton() handles button 0 without preset in a different way for double click //so we need to override with same behaviour - shortPressAction(0); + //shortPressAction(0); //handled = false; } return handled; @@ -1149,7 +1152,7 @@ void FourLineDisplayUsermod::onUpdateBegin(bool init) { xTaskCreatePinnedToCore( [](void * par) { // Function to implement the task // see https://www.freertos.org/vtaskdelayuntil.html - const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2; + const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2; TickType_t xLastWakeTime = xTaskGetTickCount(); for(;;) { delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. @@ -1204,6 +1207,7 @@ void FourLineDisplayUsermod::appendConfigData() { oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); oappend(SET_F("addOption(dd,'SSD1305',4);")); oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); + oappend(SET_F("addOption(dd,'SSD1309 128x64',9);")); oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);")); @@ -1217,14 +1221,14 @@ void FourLineDisplayUsermod::appendConfigData() { * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. * It will be called by WLED when settings are actually saved (for example, LED settings are saved) * If you want to force saving the current state, use serializeConfig() in your loop(). - * + * * CAUTION: serializeConfig() will initiate a filesystem write operation. * It might cause the LEDs to stutter and will cause flash wear if called too often. * Use it sparingly and always in the loop, never in network callbacks! - * + * * addToConfig() will also not yet add your setting to one of the settings pages automatically. * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. - * + * * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ void FourLineDisplayUsermod::addToConfig(JsonObject& root) { @@ -1251,7 +1255,7 @@ void FourLineDisplayUsermod::addToConfig(JsonObject& root) { /* * 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 :) @@ -1345,6 +1349,10 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); break; + case SSD1309_64: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1309_128x64_noname0, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; case SSD1306_SPI: u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino); u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset 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/platformio–override.sample.ini b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini new file mode 100644 index 000000000..6b32c71fb --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini @@ -0,0 +1,17 @@ +[platformio] +default_envs = esp32dev + +[env:esp32dev] +board = esp32dev +platform = ${esp32.platform} +build_unflags = ${common.build_unflags} +build_flags = + ${common.build_flags_esp32} + -D USERMOD_FOUR_LINE_DISPLAY -D USE_ALT_DISPlAY + -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19 +upload_speed = 460800 +lib_deps = + ${esp32.lib_deps} + U8g2@~2.34.4 + Wire + diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md index 516362380..10db879fb 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md @@ -8,18 +8,18 @@ The core of these usermods are a copy of the originals. The main changes are to The display usermod UI has been completely changed. -The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. +The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. Without the display, it functions identical to the original. The original "usermod_v2_auto_save" will not work with the display just yet. Press the encoder to cycle through the options: - *Brightness - *Speed - *Intensity - *Palette - *Effect - *Main Color (only if display is used) - *Saturation (only if display is used) +* Brightness +* Speed +* Intensity +* Palette +* Effect +* Main Color (only if display is used) +* Saturation (only if display is used) Press and hold the encoder to display Network Info if AP is active, it will display the AP, SSID and Password @@ -30,10 +30,23 @@ Also shows if the timer is enabled. ## Installation -Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions.
-To activate this alternative usermod, add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file, -or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file. +Copy the example `platformio_override.sample.ini` to the root directory of your particular build and rename it to `platformio_override.ini`. +To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file, or add `-D USE_ALT_DISPlAY` to your `platformio_override.ini` file + +### Define Your Options + +* `USERMOD_ROTARY_ENCODER_UI` - define this to have this user mod included wled00\usermods_list.cpp +* `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) +* `USE_ALT_DISPlAY` - Mandatory to use Four Line Display +* `ENCODER_DT_PIN` - defaults to 18 +* `ENCODER_CLK_PIN` - defaults to 5 +* `ENCODER_SW_PIN` - defaults to 19 +* `USERMOD_ROTARY_ENCODER_GPIO` - GPIO functionality: + `INPUT_PULLUP` to use internal pull-up + `INPUT` to use pull-up on the PCB ### PlatformIO requirements 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 b142f9037..e5a5f24f7 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. @@ -285,7 +285,7 @@ class RotaryEncoderUIUsermod : public Usermod { * 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; } + uint16_t getId() override { return USERMOD_ID_ROTARY_ENC_UI; } /** * Enable/Disable the usermod */ @@ -300,7 +300,7 @@ class RotaryEncoderUIUsermod : public Usermod { * 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(); + void setup() override; /** * connected() is called every time the WiFi is (re)connected @@ -311,11 +311,11 @@ class RotaryEncoderUIUsermod : public Usermod { /** * loop() is called continuously. Here you can check for events, read sensors, etc. */ - void loop(); + void loop() override; #ifndef WLED_DISABLE_MQTT - //bool onMqttMessage(char* topic, char* payload); - //void onMqttConnect(bool sessionPresent); + //bool onMqttMessage(char* topic, char* payload) override; + //void onMqttConnect(bool sessionPresent) override; #endif /** @@ -323,31 +323,31 @@ class RotaryEncoderUIUsermod : public Usermod { * will prevent button working in a default way. * Replicating button.cpp */ - //bool handleButton(uint8_t b); + //bool handleButton(uint8_t b) override; /** * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. */ - //void addToJsonInfo(JsonObject &root); + //void addToJsonInfo(JsonObject &root) override; /** * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - //void addToJsonState(JsonObject &root); + //void addToJsonState(JsonObject &root) override; /** * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - //void readFromJsonState(JsonObject &root); + //void readFromJsonState(JsonObject &root) override; /** * provide the changeable values */ - void addToConfig(JsonObject &root); + void addToConfig(JsonObject &root) override; - void appendConfigData(); + void appendConfigData() override; /** * restore the changeable values @@ -355,7 +355,7 @@ class RotaryEncoderUIUsermod : public Usermod { * * The function should return true if configuration was successfully loaded or false if there was no configuration. */ - bool readFromConfig(JsonObject &root); + bool readFromConfig(JsonObject &root) override; // custom methods void displayNetworkInfo(); @@ -392,20 +392,26 @@ byte RotaryEncoderUIUsermod::readPin(uint8_t pin) { * modes_alpha_indexes and palettes_alpha_indexes. */ void RotaryEncoderUIUsermod::sortModesAndPalettes() { - DEBUG_PRINTLN(F("Sorting modes and palettes.")); + DEBUG_PRINT(F("Sorting modes: ")); DEBUG_PRINTLN(strip.getModeCount()); //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); modes_qstrings = strip.getModeDataSrc(); modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); + DEBUG_PRINT(F("Sorting palettes: ")); DEBUG_PRINT(strip.getPaletteCount()); DEBUG_PRINT('/'); DEBUG_PRINTLN(strip.customPalettes.size()); palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()); - palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); // only use internal palettes - + palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); + if (strip.customPalettes.size()) { + for (int i=0; i=0) pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO); } loopTime = millis(); @@ -590,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 @@ -682,21 +688,25 @@ void RotaryEncoderUIUsermod::displayNetworkInfo() { void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() { DEBUG_PRINTLN(F("Finding current mode and palette.")); currentEffectAndPaletteInitialized = true; - for (uint8_t i = 0; i < strip.getModeCount(); i++) { + + effectCurrentIndex = 0; + for (int i = 0; i < strip.getModeCount(); i++) { if (modes_alpha_indexes[i] == effectCurrent) { effectCurrentIndex = i; + DEBUG_PRINTLN(F("Found current mode.")); break; } } - DEBUG_PRINTLN(F("Found current mode.")); - for (uint8_t i = 0; i < strip.getPaletteCount(); i++) { + effectPaletteIndex = 0; + DEBUG_PRINTLN(effectPalette); + for (uint8_t i = 0; i < strip.getPaletteCount()+strip.customPalettes.size(); i++) { if (palettes_alpha_indexes[i] == effectPalette) { effectPaletteIndex = i; + DEBUG_PRINTLN(F("Found palette.")); break; } } - DEBUG_PRINTLN(F("Found palette.")); } bool RotaryEncoderUIUsermod::changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) { @@ -731,7 +741,9 @@ void RotaryEncoderUIUsermod::changeBrightness(bool increase) { } display->updateRedrawTime(); #endif - bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0); + //bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0); + if (bri < 40) bri = max(min((increase ? bri+fadeAmount/2 : bri-fadeAmount/2), 255), 0); // slower steps when brightness < 16% + else bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0); lampUdated(); #ifdef USERMOD_FOUR_LINE_DISPLAY display->updateBrightness(); @@ -878,7 +890,7 @@ void RotaryEncoderUIUsermod::changePalette(bool increase) { } display->updateRedrawTime(); #endif - effectPaletteIndex = max(min((increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()-1), 0); + effectPaletteIndex = max(min((unsigned)(increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()+strip.customPalettes.size()-1), 0U); effectPalette = palettes_alpha_indexes[effectPaletteIndex]; stateChanged = true; if (applyToAll) { 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/platformio_override.ini b/usermods/wireguard/platformio_override.ini new file mode 100644 index 000000000..fc0ae5fc9 --- /dev/null +++ b/usermods/wireguard/platformio_override.ini @@ -0,0 +1,22 @@ +# Example PlatformIO Project Configuration Override for WireGuard +# ------------------------------------------------------------------------------ +# Copy to platformio_override.ini to activate. +# ------------------------------------------------------------------------------ +# Please visit documentation: https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = WLED_ESP32-WireGuard + +[env:WLED_ESP32-WireGuard] +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-WireGuard + -D USERMOD_WIREGUARD +lib_deps = ${esp32.lib_deps} + https://github.com/kienvu58/WireGuard-ESP32-Arduino.git +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} +upload_speed = 921600 \ No newline at end of file diff --git a/usermods/wireguard/readme.md b/usermods/wireguard/readme.md new file mode 100644 index 000000000..071bea9f9 --- /dev/null +++ b/usermods/wireguard/readme.md @@ -0,0 +1,19 @@ +# WireGuard VPN + +This usermod will connect your WLED instance to a remote WireGuard subnet. + +Configuration is performed via the Usermod menu. There are no parameters to set in code! + +## Installation + +Copy the `platformio_override.ini` file to the root project directory, review the build options, and select the `WLED_ESP32-WireGuard` environment. + + +## Author + +Aiden Vigue [vigue.me](https://vigue.me) +[@acvigue](https://github.com/acvigue) +aiden@vigue.me + + + diff --git a/usermods/wireguard/wireguard.h b/usermods/wireguard/wireguard.h new file mode 100644 index 000000000..8c88d0001 --- /dev/null +++ b/usermods/wireguard/wireguard.h @@ -0,0 +1,127 @@ +#pragma once + +#include + +#include "wled.h" + +class WireguardUsermod : public Usermod { + public: + void setup() { configTzTime(posix_tz, ntpServerName); } + + void connected() { + if (wg.is_initialized()) { + wg.end(); + } + } + + void loop() { + if (millis() - lastTime > 5000) { + if (is_enabled && WLED_CONNECTED) { + if (!wg.is_initialized()) { + struct tm timeinfo; + if (getLocalTime(&timeinfo, 0)) { + if (strlen(preshared_key) < 1) { + wg.begin(local_ip, private_key, endpoint_address, public_key, endpoint_port, NULL); + } else { + wg.begin(local_ip, private_key, endpoint_address, public_key, endpoint_port, preshared_key); + } + } + } + } + + lastTime = millis(); + } + } + + void addToJsonInfo(JsonObject& root) { + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(F("WireGuard")); + String uiDomString; + + struct tm timeinfo; + if (!getLocalTime(&timeinfo, 0)) { + uiDomString = "Time out of sync!"; + } else { + if (wg.is_initialized()) { + uiDomString = "netif up!"; + } else { + uiDomString = "netif down :("; + } + } + if (is_enabled) infoArr.add(uiDomString); + } + + void appendConfigData() { + oappend(SET_F("addInfo('WireGuard:host',1,'Server Hostname');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:port',1,'Server Port');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:ip',1,'Device IP');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:psk',1,'Pre Shared Key (optional)');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:pem',1,'Private Key');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:pub',1,'Public Key');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('WireGuard:tz',1,'POSIX timezone string');")); // 0 is field type, 1 is actual field + } + + void addToConfig(JsonObject& root) { + JsonObject top = root.createNestedObject(F("WireGuard")); + top[F("host")] = endpoint_address; + 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; + } + + bool readFromConfig(JsonObject& root) { + JsonObject top = root[F("WireGuard")]; + + 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[F("host")]; + strncpy(endpoint_address, host, 100); + + const char* ip_s = top["ip"]; + uint8_t ip[4]; + 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[F("pem")]; + strncpy(private_key, pem, 45); + + const char* pub = top[F("pub")]; + strncpy(public_key, pub, 45); + + const char* tz = top[F("tz")]; + strncpy(posix_tz, tz, 150); + + endpoint_port = top[F("port")]; + + if (!top["psk"].isNull()) { + const char* psk = top["psk"]; + strncpy(preshared_key, psk, 45); + } + + is_enabled = true; + } + + return is_enabled; + } + + uint16_t getId() { return USERMOD_ID_WIREGUARD; } + + private: + WireGuard wg; + char preshared_key[45]; + char private_key[45]; + IPAddress local_ip; + char public_key[45]; + char endpoint_address[100]; + char posix_tz[150]; + int endpoint_port = 0; + bool is_enabled = false; + unsigned long lastTime = 0; +}; \ No newline at end of file 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 8c7bf38d3..14341f5b9 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) { @@ -80,7 +80,7 @@ int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { */ uint16_t mode_static(void) { SEGMENT.fill(SEGCOLOR(0)); - return 350; + return strip.isOffRefreshRequired() ? FRAMETIME : 350; } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; @@ -177,11 +177,11 @@ uint16_t color_wipe(bool rev, bool useRandomColors) { SEGENV.step = 3; } if (SEGENV.step == 1) { //if flag set, change to new random color - SEGENV.aux1 = SEGMENT.get_random_wheel_index(SEGENV.aux0); + SEGENV.aux1 = get_random_wheel_index(SEGENV.aux0); SEGENV.step = 2; } if (SEGENV.step == 3) { - SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux1); + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux1); SEGENV.step = 0; } } @@ -271,7 +271,7 @@ uint16_t mode_random_color(void) { if (it != SEGENV.step) //new color { SEGENV.aux1 = SEGENV.aux0; - SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux0); //aux0 will store our random color wheel index + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); //aux0 will store our random color wheel index SEGENV.step = it; } @@ -289,7 +289,6 @@ uint16_t mode_dynamic(void) { if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed if(SEGENV.call == 0) { - //SEGMENT.setUpLeds(); //lossless getPixelColor() //SEGMENT.fill(BLACK); for (int i = 0; i < SEGLEN; i++) SEGENV.data[i] = random8(); } @@ -588,7 +587,7 @@ uint16_t mode_twinkle(void) { uint16_t PRNG16 = SEGENV.aux1; - for (uint16_t i = 0; i < SEGENV.aux0; i++) + for (unsigned i = 0; i < SEGENV.aux0; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 13849; // next 'random' number uint32_t p = (uint32_t)SEGLEN * (uint32_t)PRNG16; @@ -605,28 +604,36 @@ static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0"; * Dissolve function */ uint16_t dissolve(uint32_t color) { - //bool wa = (SEGCOLOR(1) != 0 && strip.getBrightness() < 255); //workaround, can't compare getPixel to color if not full brightness + uint16_t dataSize = (SEGLEN+7) >> 3; //1 bit per LED + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); //lossless getPixelColor() - SEGMENT.fill(SEGCOLOR(1)); + memset(SEGMENT.data, 0xFF, dataSize); // start by fading pixels up + SEGENV.aux0 = 1; } for (int j = 0; j <= SEGLEN / 15; j++) { if (random8() <= SEGMENT.intensity) { - for (size_t times = 0; times < 10; times++) //attempt to spawn a new pixel 10 times - { - uint16_t i = random16(SEGLEN); + for (size_t times = 0; times < 10; times++) { //attempt to spawn a new pixel 10 times + unsigned i = random16(SEGLEN); + unsigned index = i >> 3; + unsigned bitNum = i & 0x07; + bool fadeUp = bitRead(SEGENV.data[index], bitNum); if (SEGENV.aux0) { //dissolve to primary/palette - if (SEGMENT.getPixelColor(i) == SEGCOLOR(1) /*|| wa*/) { + if (fadeUp) { if (color == SEGCOLOR(0)) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } else { SEGMENT.setPixelColor(i, color); } + bitWrite(SEGENV.data[index], bitNum, false); break; //only spawn 1 new pixel per frame per 50 LEDs } } else { //dissolve to secondary - if (SEGMENT.getPixelColor(i) != SEGCOLOR(1)) { SEGMENT.setPixelColor(i, SEGCOLOR(1)); break; } + if (!fadeUp) { + SEGMENT.setPixelColor(i, SEGCOLOR(1)); break; + bitWrite(SEGENV.data[index], bitNum, true); + } } } } @@ -635,6 +642,7 @@ uint16_t dissolve(uint32_t color) { if (SEGENV.step > (255 - SEGMENT.speed) + 15U) { SEGENV.aux0 = !SEGENV.aux0; SEGENV.step = 0; + memset(SEGMENT.data, (SEGENV.aux0 ? 0xFF : 0), dataSize); // switch fading } else { SEGENV.step++; } @@ -688,7 +696,7 @@ static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,,,,,,Overlay;!,!; * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ uint16_t mode_flash_sparkle(void) { - if (!SEGMENT.check2) for(uint16_t i = 0; i < SEGLEN; i++) { + if (!SEGMENT.check2) for (int i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } @@ -715,7 +723,7 @@ uint16_t mode_hyper_sparkle(void) { if (strip.now - SEGENV.aux0 > SEGENV.step) { if (random8((255-SEGMENT.intensity) >> 4) == 0) { - for (int i = 0; i < MAX(1, SEGLEN/3); i++) { + for (int i = 0; i < max(1, SEGLEN/3); i++) { SEGMENT.setPixelColor(random16(SEGLEN), SEGCOLOR(1)); } } @@ -766,7 +774,7 @@ uint16_t mode_android(void) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } - if (SEGENV.aux1 > ((float)SEGMENT.intensity/255.0)*(float)SEGLEN) + if (SEGENV.aux1 > (SEGMENT.intensity*SEGLEN)/255) { SEGENV.aux0 = 1; } else @@ -816,21 +824,21 @@ static const char _data_FX_MODE_ANDROID[] PROGMEM = "Android@!,Width;!,!;!;;m12= */ uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); - uint16_t a = counter * SEGLEN >> 16; + uint16_t a = (counter * SEGLEN) >> 16; bool chase_random = (SEGMENT.mode == FX_MODE_CHASE_RANDOM); if (chase_random) { if (a < SEGENV.step) //we hit the start again, choose new color for Chase random { SEGENV.aux1 = SEGENV.aux0; //store previous random color - SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux0); + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); } color1 = SEGMENT.color_wheel(SEGENV.aux0); } SEGENV.step = a; // Use intensity setting to vary chase up to 1/2 string length - uint8_t size = 1 + (SEGMENT.intensity * SEGLEN >> 10); + uint8_t size = 1 + ((SEGMENT.intensity * SEGLEN) >> 10); uint16_t b = a + size; //"trail" of chase, filled with color1 if (b > SEGLEN) b -= SEGLEN; @@ -1063,7 +1071,7 @@ uint16_t mode_chase_flash_random(void) { SEGENV.aux1 = (SEGENV.aux1 + 1) % SEGLEN; if (SEGENV.aux1 == 0) { - SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux0); + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); } } return delay; @@ -1118,8 +1126,9 @@ static const char _data_FX_MODE_RUNNING_RANDOM[] PROGMEM = "Stream@!,Zone size;; uint16_t larson_scanner(bool dual) { + if (SEGLEN == 1) return mode_static(); uint16_t counter = strip.now * ((SEGMENT.speed >> 2) +8); - uint16_t index = counter * SEGLEN >> 16; + uint16_t index = (counter * SEGLEN) >> 16; SEGMENT.fade_out(SEGMENT.intensity); @@ -1206,8 +1215,6 @@ uint16_t mode_fireworks() { const uint16_t height = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); //lossless getPixelColor() - SEGMENT.fill(SEGCOLOR(1)); SEGENV.aux0 = UINT16_MAX; SEGENV.aux1 = UINT16_MAX; } @@ -1215,22 +1222,24 @@ uint16_t mode_fireworks() { bool valid1 = (SEGENV.aux0 < width*height); bool valid2 = (SEGENV.aux1 < width*height); + uint8_t x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte uint32_t sv1 = 0, sv2 = 0; - if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(SEGENV.aux0%width, SEGENV.aux0/width) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color - if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(SEGENV.aux1%width, SEGENV.aux1/width) : SEGMENT.getPixelColor(SEGENV.aux1); + if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color + if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1); if (!SEGENV.step) SEGMENT.blur(16); - if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(SEGENV.aux0%width, SEGENV.aux0/width, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur - if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(SEGENV.aux1%width, SEGENV.aux1/width, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur + if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur + if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur - for (int i=0; i> 1)) == 0) { uint16_t index = random16(width*height); - uint16_t j = index % width, k = index / width; + x = index % width; + y = index / width; uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0); - if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(j, k, col); + 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; @@ -1263,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; } @@ -1306,24 +1315,23 @@ static const char _data_FX_MODE_FIRE_FLICKER[] PROGMEM = "Fire Flicker@!,!;!;!;0 * Gradient run base function */ uint16_t gradient_base(bool loading) { + if (SEGLEN == 1) return mode_static(); uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); - uint16_t pp = counter * SEGLEN >> 16; + uint16_t pp = (counter * SEGLEN) >> 16; if (SEGENV.call == 0) pp = 0; - float val; //0.0 = sec 1.0 = pri - float brd = loading ? SEGMENT.intensity : SEGMENT.intensity/2; - if (brd <1.0) brd = 1.0; + int val; //0 = sec 1 = pri + int brd = 1 + loading ? SEGMENT.intensity/2 : SEGMENT.intensity/4; + //if (brd < 1) brd = 1; int p1 = pp-SEGLEN; int p2 = pp+SEGLEN; - for (int i = 0; i < SEGLEN; i++) - { - if (loading) - { - val = abs(((i>pp) ? p2:pp) -i); + for (int i = 0; i < SEGLEN; i++) { + if (loading) { + val = abs(((i>pp) ? p2:pp) - i); } else { - val = MIN(abs(pp-i),MIN(abs(p1-i),abs(p2-i))); + val = min(abs(pp-i),min(abs(p1-i),abs(p2-i))); } - val = (brd > val) ? val/brd * 255 : 255; + val = (brd > val) ? (val * 255) / brd : 255; SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(0), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1), val)); } @@ -1706,7 +1714,7 @@ uint16_t mode_multi_comet(void) { } comets[i]++; } else { - if(!random(SEGLEN)) { + if(!random16(SEGLEN)) { comets[i] = 0; } } @@ -1840,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; @@ -1851,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; @@ -1904,13 +1912,8 @@ static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;"; //eight colored dots, weaving in and out of sync with each other uint16_t mode_juggle(void) { if (SEGLEN == 1) return mode_static(); - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); //lossless getPixelColor() - SEGMENT.fill(BLACK); - } SEGMENT.fadeToBlackBy(192 - (3*SEGMENT.intensity/4)); - CRGB fastled_col; byte dothue = 0; for (int i = 0; i < 8; i++) { @@ -1926,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 @@ -1987,7 +2070,7 @@ uint16_t mode_fire_2012() { // Step 1. Cool down every cell a little for (int i = 0; i < SEGLEN; i++) { - uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random(4); + uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); uint8_t minTemp = (i28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness(); fract8 fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness(); - for (uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { fastled_col = SEGMENT.getPixelColor(i); prev = fastled_col; uint16_t index = i >> 3; @@ -2224,9 +2304,9 @@ uint16_t mode_colortwinkle() { } } - for (uint16_t j = 0; j <= SEGLEN / 50; j++) { + for (unsigned j = 0; j <= SEGLEN / 50; j++) { if (random8() <= SEGMENT.intensity) { - for (uint8_t times = 0; times < 5; times++) { //attempt to spawn a new pixel 5 times + for (unsigned times = 0; times < 5; times++) { //attempt to spawn a new pixel 5 times int i = random16(SEGLEN); if (SEGMENT.getPixelColor(i) == 0) { fastled_col = ColorFromPalette(SEGPALETTE, random8(), 64, NOBLEND); @@ -2275,33 +2355,35 @@ uint16_t mode_meteor() { byte* trail = SEGENV.data; - byte meteorSize= 1+ SEGLEN / 10; + const unsigned meteorSize= 1 + SEGLEN / 20; // 5% uint16_t counter = strip.now * ((SEGMENT.speed >> 2) +8); uint16_t in = counter * SEGLEN >> 16; + const int max = SEGMENT.palette==5 || !SEGMENT.check1 ? 240 : 255; // fade all leds to colors[1] in LEDs one step for (int i = 0; i < SEGLEN; i++) { - if (random8() <= 255 - SEGMENT.intensity) - { - byte meteorTrailDecay = 128 + random8(127); + if (random8() <= 255 - SEGMENT.intensity) { + byte meteorTrailDecay = 162 + random8(92); trail[i] = scale8(trail[i], meteorTrailDecay); - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, false, 0, trail[i])); + uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255); + SEGMENT.setPixelColor(i, col); } } // draw meteor - for (int j = 0; j < meteorSize; j++) { + for (unsigned j = 0; j < meteorSize; j++) { uint16_t index = in + j; if (index >= SEGLEN) { index -= SEGLEN; } - trail[index] = 240; - SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, false, 0, 255)); + trail[index] = max; + uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(index, true, false, 0, trail[index]) : SEGMENT.color_from_palette(trail[index], false, true, 255); + SEGMENT.setPixelColor(index, col); } return FRAMETIME; } -static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail length;!;!"; +static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail,,,,Gradient;;!;1"; // smooth meteor effect @@ -2313,35 +2395,35 @@ uint16_t mode_meteor_smooth() { byte* trail = SEGENV.data; - byte meteorSize= 1+ SEGLEN / 10; + const unsigned meteorSize= 1+ SEGLEN / 20; // 5% uint16_t in = map((SEGENV.step >> 6 & 0xFF), 0, 255, 0, SEGLEN -1); + const int max = SEGMENT.palette==5 || !SEGMENT.check1 ? 240 : 255; // fade all leds to colors[1] in LEDs one step for (int i = 0; i < SEGLEN; i++) { - if (trail[i] != 0 && random8() <= 255 - SEGMENT.intensity) - { - int change = 3 - random8(12); //change each time between -8 and +3 - trail[i] += change; - if (trail[i] > 245) trail[i] = 0; - if (trail[i] > 240) trail[i] = 240; - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, false, 0, trail[i])); + if (/*trail[i] != 0 &&*/ random8() <= 255 - SEGMENT.intensity) { + int change = trail[i] + 4 - random8(24); //change each time between -20 and +4 + trail[i] = constrain(change, 0, max); + uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255); + SEGMENT.setPixelColor(i, col); } } // draw meteor - for (int j = 0; j < meteorSize; j++) { + for (unsigned j = 0; j < meteorSize; j++) { uint16_t index = in + j; if (index >= SEGLEN) { index -= SEGLEN; } - trail[index] = 240; - SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, false, 0, 255)); + trail[index] = max; + uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(index, true, false, 0, trail[index]) : SEGMENT.color_from_palette(trail[index], false, true, 255); + SEGMENT.setPixelColor(index, col); } SEGENV.step += SEGMENT.speed +1; return FRAMETIME; } -static const char _data_FX_MODE_METEOR_SMOOTH[] PROGMEM = "Meteor Smooth@!,Trail length;!;!"; +static const char _data_FX_MODE_METEOR_SMOOTH[] PROGMEM = "Meteor Smooth@!,Trail,,,,Gradient;;!;1"; //Railway Crossing / Christmas Fairy lights @@ -2415,9 +2497,10 @@ uint16_t ripple_base() #ifndef WLED_DISABLE_2D if (SEGMENT.is2D()) { + propI /= 2; uint16_t cx = rippleorigin >> 8; uint16_t cy = rippleorigin & 0xFF; - uint8_t mag = scale8(cubicwave8((propF>>2)), amp); + uint8_t mag = scale8(sin8((propF>>2)), amp); if (propI > 0) SEGMENT.draw_circle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag)); } else #endif @@ -2433,7 +2516,7 @@ uint16_t ripple_base() ripplestate += rippledecay; ripples[i].state = (ripplestate > 254) ? 0 : ripplestate; } else {//randomly create new wave - if (random16(IBN + 10000) <= SEGMENT.intensity) { + if (random16(IBN + 10000) <= (SEGMENT.intensity >> (SEGMENT.is2D()*3))) { ripples[i].state = 1; ripples[i].pos = SEGMENT.is2D() ? ((random8(SEGENV.virtualWidth())<<8) | (random8(SEGENV.virtualHeight()))) : random16(SEGLEN); ripples[i].color = random8(); //color @@ -2449,6 +2532,7 @@ uint16_t ripple_base() uint16_t mode_ripple(void) { if (SEGLEN == 1) return mode_static(); if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); + else SEGMENT.fade_out(250); return ripple_base(); } static const char _data_FX_MODE_RIPPLE[] PROGMEM = "Ripple@!,Wave #,,,,,Overlay;,!;!;12"; @@ -2477,12 +2561,6 @@ static const char _data_FX_MODE_RIPPLE_RAINBOW[] PROGMEM = "Ripple Rainbow@!,Wav // // TwinkleFOX: Twinkling 'holiday' lights that fade in and out. // Colors are chosen from a palette. Read more about this effect using the link above! - -// If COOL_LIKE_INCANDESCENT is set to 1, colors will -// fade out slighted 'reddened', similar to how -// incandescent bulbs change color as they get dim down. -#define COOL_LIKE_INCANDESCENT 1 - CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) { // Overall twinkle speed (changed) @@ -2521,7 +2599,7 @@ CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) CRGB c; if (bright > 0) { c = ColorFromPalette(SEGPALETTE, hue, bright, NOBLEND); - if(COOL_LIKE_INCANDESCENT == 1) { + if (!SEGMENT.check1) { // This code takes a pixel, and if its in the 'fading down' // part of the cycle, it adjusts the color a little bit like the // way that incandescent bulbs fade toward 'red' as they dim. @@ -2607,19 +2685,38 @@ uint16_t mode_twinklefox() { return twinklefox_base(false); } -static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox@!,Twinkle rate;;!"; +static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox@!,Twinkle rate,,,,Cool;!,!;!"; uint16_t mode_twinklecat() { return twinklefox_base(true); } -static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate;;!"; +static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate,,,,Cool;!,!;!"; -//inspired by https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectBlinkingHalloweenEyes uint16_t mode_halloween_eyes() { + enum eyeState : uint8_t { + initializeOn = 0, + on, + blink, + initializeOff, + off, + + count + }; + struct EyeData { + eyeState state; + uint8_t color; + uint16_t startPos; + // duration + endTime could theoretically be replaced by a single endTime, however we would lose + // the ability to end the animation early when the user reduces the animation time. + uint16_t duration; + uint32_t startTime; + uint32_t blinkEndTime; + }; + if (SEGLEN == 1) return mode_static(); const uint16_t maxWidth = strip.isMatrix ? SEGMENT.virtualWidth() : SEGLEN; const uint16_t HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEGMENT.virtualWidth()>>4: SEGLEN>>5); @@ -2627,57 +2724,132 @@ uint16_t mode_halloween_eyes() uint16_t eyeLength = (2*HALLOWEEN_EYE_WIDTH) + HALLOWEEN_EYE_SPACE; if (eyeLength >= maxWidth) return mode_static(); //bail if segment too short + if (!SEGENV.allocateData(sizeof(EyeData))) return mode_static(); //allocation failed + EyeData& data = *reinterpret_cast(SEGENV.data); + if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); //fill background - uint8_t state = SEGENV.aux1 >> 8; - uint16_t stateTime = SEGENV.call; - if (stateTime == 0) stateTime = 2000; + data.state = static_cast(data.state % eyeState::count); + uint16_t duration = max(uint16_t{1u}, data.duration); + const uint32_t elapsedTime = strip.now - data.startTime; - if (state == 0) { //spawn eyes - SEGENV.aux0 = random16(0, maxWidth - eyeLength - 1); //start pos - SEGENV.aux1 = random8(); //color - if (strip.isMatrix) SEGMENT.offset = random16(SEGMENT.virtualHeight()-1); // a hack: reuse offset since it is not used in matrices - state = 1; - } + switch (data.state) { + case eyeState::initializeOn: { + // initialize the eyes-on state: + // - select eye position and color + // - select a duration + // - immediately switch to eyes on state. - if (state < 2) { //fade eyes - uint16_t startPos = SEGENV.aux0; - uint16_t start2ndEye = startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; + data.startPos = random16(0, maxWidth - eyeLength - 1); + data.color = random8(); + if (strip.isMatrix) SEGMENT.offset = random16(SEGMENT.virtualHeight()-1); // a hack: reuse offset since it is not used in matrices + duration = 128u + random16(SEGMENT.intensity*64u); + data.duration = duration; + data.state = eyeState::on; + [[fallthrough]]; + } + case eyeState::on: { + // eyes-on steate: + // - fade eyes in for some time + // - keep eyes on until the pre-selected duration is over + // - randomly switch to the blink (sub-)state, and initialize it with a blink duration (more precisely, a blink end time stamp) + // - never switch to the blink state if the animation just started or is about to end - uint32_t fadestage = (strip.now - SEGENV.step)*255 / stateTime; - if (fadestage > 255) fadestage = 255; - uint32_t c = color_blend(SEGMENT.color_from_palette(SEGENV.aux1 & 0xFF, false, false, 0), SEGCOLOR(1), fadestage); + uint16_t start2ndEye = data.startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; + // If the user reduces the input while in this state, limit the duration. + duration = min(duration, static_cast(128u + (SEGMENT.intensity * 64u))); - for (int i = 0; i < HALLOWEEN_EYE_WIDTH; i++) { - if (strip.isMatrix) { - SEGMENT.setPixelColorXY(startPos + i, SEGMENT.offset, c); - SEGMENT.setPixelColorXY(start2ndEye + i, SEGMENT.offset, c); - } else { - SEGMENT.setPixelColor(startPos + i, c); - SEGMENT.setPixelColor(start2ndEye + i, c); + constexpr uint32_t minimumOnTimeBegin = 1024u; + constexpr uint32_t minimumOnTimeEnd = 1024u; + const uint32_t fadeInAnimationState = elapsedTime * uint32_t{256u * 8u} / duration; + const uint32_t backgroundColor = SEGCOLOR(1); + const uint32_t eyeColor = SEGMENT.color_from_palette(data.color, false, false, 0); + uint32_t c = eyeColor; + if (fadeInAnimationState < 256u) { + c = color_blend(backgroundColor, eyeColor, fadeInAnimationState); + } else if (elapsedTime > minimumOnTimeBegin) { + const uint32_t remainingTime = (elapsedTime >= duration) ? 0u : (duration - elapsedTime); + if (remainingTime > minimumOnTimeEnd) { + if (random8() < 4u) + { + c = backgroundColor; + data.state = eyeState::blink; + data.blinkEndTime = strip.now + random8(8, 128); + } + } } + + if (c != backgroundColor) { + // render eyes + for (int i = 0; i < HALLOWEEN_EYE_WIDTH; i++) { + if (strip.isMatrix) { + SEGMENT.setPixelColorXY(data.startPos + i, SEGMENT.offset, c); + SEGMENT.setPixelColorXY(start2ndEye + i, SEGMENT.offset, c); + } else { + SEGMENT.setPixelColor(data.startPos + i, c); + SEGMENT.setPixelColor(start2ndEye + i, c); + } + } + } + break; + } + case eyeState::blink: { + // eyes-on but currently blinking state: + // - wait until the blink time is over, then switch back to eyes-on + + if (strip.now >= data.blinkEndTime) { + data.state = eyeState::on; + } + break; + } + case eyeState::initializeOff: { + // initialize eyes-off state: + // - select a duration + // - immediately switch to eyes-off state + + const uint16_t eyeOffTimeBase = SEGMENT.speed*128u; + duration = eyeOffTimeBase + random16(eyeOffTimeBase); + data.duration = duration; + data.state = eyeState::off; + [[fallthrough]]; + } + case eyeState::off: { + // eyes-off state: + // - not much to do here + + // If the user reduces the input while in this state, limit the duration. + const uint16_t eyeOffTimeBase = SEGMENT.speed*128u; + duration = min(duration, static_cast(2u * eyeOffTimeBase)); + break; + } + case eyeState::count: { + // Can't happen, not an actual state. + data.state = eyeState::initializeOn; + break; } } - if (strip.now - SEGENV.step > stateTime) { - state++; - if (state > 2) state = 0; - - if (state < 2) { - stateTime = 100 + SEGMENT.intensity*10; //eye fade time - } else { - uint16_t eyeOffTimeBase = (256 - SEGMENT.speed)*10; - stateTime = eyeOffTimeBase + random16(eyeOffTimeBase); + if (elapsedTime > duration) { + // The current state duration is over, switch to the next state. + switch (data.state) { + case eyeState::initializeOn: + case eyeState::on: + case eyeState::blink: + data.state = eyeState::initializeOff; + break; + case eyeState::initializeOff: + case eyeState::off: + case eyeState::count: + default: + data.state = eyeState::initializeOn; + break; } - SEGENV.step = strip.now; - SEGENV.call = stateTime; + data.startTime = strip.now; } - SEGENV.aux1 = (SEGENV.aux1 & 0xFF) + (state << 8); //save state - return FRAMETIME; } -static const char _data_FX_MODE_HALLOWEEN_EYES[] PROGMEM = "Halloween Eyes@Duration,Eye fade time,,,,,Overlay;!,!;!;12"; +static const char _data_FX_MODE_HALLOWEEN_EYES[] PROGMEM = "Halloween Eyes@Eye off time,Eye on time,,,,,Overlay;!,!;!;12"; //Speed slider sets amount of LEDs lit, intensity sets unlit @@ -2807,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; @@ -2855,6 +3027,107 @@ uint16_t mode_bouncing_balls(void) { static const char _data_FX_MODE_BOUNCINGBALLS[] PROGMEM = "Bouncing Balls@Gravity,# of balls,,,,,Overlay;!,!,!;!;1;m12=1"; //bar +/* + * bouncing balls on a track track Effect modified from Aircoookie's bouncing balls + * Courtesy of pjhatch (https://github.com/pjhatch) + * https://github.com/Aircoookie/WLED/pull/1039 + */ +// modified for balltrack mode +typedef struct RollingBall { + unsigned long lastBounceUpdate; + float mass; // could fix this to be = 1. if memory is an issue + float velocity; + float height; +} rball_t; + +static uint16_t rolling_balls(void) { + //allocate segment data + const uint16_t maxNumBalls = 16; // 255/16 + 1 + uint16_t dataSize = sizeof(rball_t) * maxNumBalls; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + rball_t *balls = reinterpret_cast(SEGENV.data); + + // number of balls based on intensity setting to max of 16 (cycles colors) + // non-chosen color is a random color + uint8_t numBalls = SEGMENT.intensity/16 + 1; + bool hasCol2 = SEGCOLOR(2); + + if (SEGENV.call == 0) { + SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); // start clean + for (int i = 0; i < maxNumBalls; i++) { + balls[i].lastBounceUpdate = strip.now; + balls[i].velocity = 20.0f * float(random16(1000, 10000))/10000.0f; // number from 1 to 10 + if (random8()<128) balls[i].velocity = -balls[i].velocity; // 50% chance of reverse direction + balls[i].height = (float(random16(0, 10000)) / 10000.0f); // from 0. to 1. + balls[i].mass = (float(random16(1000, 10000)) / 10000.0f); // from .1 to 1. + } + } + + float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider + + if (SEGMENT.check3) SEGMENT.fade_out(250); // 2-8 pixel trails (optional) + else { + if (!SEGMENT.check2) SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); // don't fill with background color if user wants to see trails + } + + for (int i = 0; i < numBalls; i++) { + float timeSinceLastUpdate = float((strip.now - balls[i].lastBounceUpdate))/cfac; + float thisHeight = balls[i].height + balls[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution + // test if intensity level was increased and some balls are way off the track then put them back + if (thisHeight < -0.5f || thisHeight > 1.5f) { + thisHeight = balls[i].height = (float(random16(0, 10000)) / 10000.0f); // from 0. to 1. + balls[i].lastBounceUpdate = strip.now; + } + // check if reached ends of the strip + if ((thisHeight <= 0.0f && balls[i].velocity < 0.0f) || (thisHeight >= 1.0f && balls[i].velocity > 0.0f)) { + balls[i].velocity = -balls[i].velocity; // reverse velocity + balls[i].lastBounceUpdate = strip.now; + balls[i].height = thisHeight; + } + // check for collisions + if (SEGMENT.check1) { + for (int j = i+1; j < numBalls; j++) { + if (balls[j].velocity != balls[i].velocity) { + // tcollided + balls[j].lastBounceUpdate is acutal time of collision (this keeps precision with long to float conversions) + float tcollided = (cfac*(balls[i].height - balls[j].height) + + balls[i].velocity*float(balls[j].lastBounceUpdate - balls[i].lastBounceUpdate))/(balls[j].velocity - balls[i].velocity); + + if ((tcollided > 2.0f) && (tcollided < float(strip.now - balls[j].lastBounceUpdate))) { // 2ms minimum to avoid duplicate bounces + balls[i].height = balls[i].height + balls[i].velocity*(tcollided + float(balls[j].lastBounceUpdate - balls[i].lastBounceUpdate))/cfac; + balls[j].height = balls[i].height; + balls[i].lastBounceUpdate = (unsigned long)(tcollided + 0.5f) + balls[j].lastBounceUpdate; + balls[j].lastBounceUpdate = balls[i].lastBounceUpdate; + float vtmp = balls[i].velocity; + balls[i].velocity = ((balls[i].mass - balls[j].mass)*vtmp + 2.0f*balls[j].mass*balls[j].velocity)/(balls[i].mass + balls[j].mass); + balls[j].velocity = ((balls[j].mass - balls[i].mass)*balls[j].velocity + 2.0f*balls[i].mass*vtmp) /(balls[i].mass + balls[j].mass); + thisHeight = balls[i].height + balls[i].velocity*(strip.now - balls[i].lastBounceUpdate)/cfac; + } + } + } + } + + uint32_t color = SEGCOLOR(0); + if (SEGMENT.palette) { + //color = SEGMENT.color_wheel(i*(256/MAX(numBalls, 8))); + color = SEGMENT.color_from_palette(i*255/numBalls, false, PALETTE_SOLID_WRAP, 0); + } else if (hasCol2) { + color = SEGCOLOR(i % NUM_COLORS); + } + + if (thisHeight < 0.0f) thisHeight = 0.0f; + if (thisHeight > 1.0f) thisHeight = 1.0f; + uint16_t pos = round(thisHeight * (SEGLEN - 1)); + SEGMENT.setPixelColor(pos, color); + balls[i].lastBounceUpdate = strip.now; + balls[i].height = thisHeight; + } + + return FRAMETIME; +} +static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collisions,Overlay,Trails;!,!,!;!;1;m12=1"; //bar + + /* * Sinelon stolen from FASTLED examples */ @@ -2932,7 +3205,7 @@ uint16_t mode_glitter() static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@!,!,,,,,Overlay;1,2,Glitter color;!;;pal=0,m12=0"; //pixels -//Solid colour background with glitter +//Solid colour background with glitter (can be replaced by Glitter) uint16_t mode_solid_glitter() { SEGMENT.fill(SEGCOLOR(0)); @@ -3022,10 +3295,9 @@ static const char _data_FX_MODE_POPCORN[] PROGMEM = "Popcorn@!,!,,,,,Overlay;!,! uint16_t candle(bool multi) { - if (multi) - { + if (multi && SEGLEN > 1) { //allocate segment data - uint16_t dataSize = (SEGLEN -1) *3; //max. 1365 pixels (ESP8266) + uint16_t dataSize = max(1, SEGLEN -1) *3; //max. 1365 pixels (ESP8266) if (!SEGENV.allocateData(dataSize)) return candle(false); //allocation failed } @@ -3144,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); @@ -3158,7 +3430,7 @@ uint16_t mode_starburst(void) { if (random8((144-(SEGMENT.speed >> 1))) == 0 && stars[j].birth == 0) { // Pick a random color and location. - uint16_t startPos = random16(SEGLEN-1); + uint16_t startPos = (SEGLEN > 1) ? random16(SEGLEN-1) : 0; float multiplier = (float)(random8())/255.0 * 1.0; stars[j].color = CRGB(SEGMENT.color_wheel(random8())); @@ -3252,8 +3524,8 @@ static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chanc uint16_t mode_exploding_fireworks(void) { if (SEGLEN == 1) return mode_static(); - const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; - const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + const uint16_t cols = SEGMENT.is2D() ? SEGMENT.virtualWidth() : 1; + const uint16_t rows = SEGMENT.is2D() ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); //allocate segment data uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 @@ -3284,11 +3556,11 @@ uint16_t mode_exploding_fireworks(void) if (SEGENV.aux0 < 2) { //FLARE if (SEGENV.aux0 == 0) { //init flare flare->pos = 0; - flare->posX = strip.isMatrix ? random16(2,cols-3) : (SEGMENT.intensity > random8()); // will enable random firing side on 1D + flare->posX = SEGMENT.is2D() ? random16(2,cols-3) : (SEGMENT.intensity > random8()); // will enable random firing side on 1D uint16_t peakHeight = 75 + random8(180); //0-255 peakHeight = (peakHeight * (rows -1)) >> 8; flare->vel = sqrtf(-2.0f * gravity * peakHeight); - flare->velX = strip.isMatrix ? (random8(9)-4)/32.f : 0; // no X velocity on 1D + flare->velX = SEGMENT.is2D() ? (random8(9)-4)/64.0f : 0; // no X velocity on 1D flare->col = 255; //brightness SEGENV.aux0 = 1; } @@ -3296,12 +3568,14 @@ uint16_t mode_exploding_fireworks(void) // launch if (flare->vel > 12 * gravity) { // flare - if (strip.isMatrix) SEGMENT.setPixelColorXY(int(flare->posX), rows - uint16_t(flare->pos) - 1, flare->col, flare->col, flare->col); - else SEGMENT.setPixelColor(int(flare->posX) ? rows - int(flare->pos) - 1 : int(flare->pos), flare->col, flare->col, flare->col); + if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(int(flare->posX), rows - uint16_t(flare->pos) - 1, flare->col, flare->col, flare->col); + else SEGMENT.setPixelColor((flare->posX > 0.0f) ? rows - int(flare->pos) - 1 : int(flare->pos), flare->col, flare->col, flare->col); flare->pos += flare->vel; - flare->posX += flare->velX; flare->pos = constrain(flare->pos, 0, rows-1); - flare->posX = constrain(flare->posX, 0, cols-strip.isMatrix); + if (SEGMENT.is2D()) { + flare->posX += flare->velX; + flare->posX = constrain(flare->posX, 0, cols-1); + } flare->vel += gravity; flare->col -= 2; } else { @@ -3324,12 +3598,12 @@ uint16_t mode_exploding_fireworks(void) sparks[i].posX = flare->posX; sparks[i].vel = (float(random16(20001)) / 10000.0f) - 0.9f; // from -0.9 to 1.1 sparks[i].vel *= rows<32 ? 0.5f : 1; // reduce velocity for smaller strips - sparks[i].velX = strip.isMatrix ? (float(random16(10001)) / 10000.0f) - 0.5f : 0; // from -0.5 to 0.5 + sparks[i].velX = SEGMENT.is2D() ? (float(random16(20001)) / 10000.0f) - 1.0f : 0; // from -1 to 1 sparks[i].col = 345;//abs(sparks[i].vel * 750.0); // set colors before scaling velocity to keep them bright //sparks[i].col = constrain(sparks[i].col, 0, 345); sparks[i].colIndex = random8(); sparks[i].vel *= flare->pos/rows; // proportional to height - sparks[i].velX *= strip.isMatrix ? flare->posX/cols : 0; // proportional to width + sparks[i].velX *= SEGMENT.is2D() ? flare->posX/cols : 0; // proportional to width sparks[i].vel *= -gravity *50; } //sparks[1].col = 345; // this will be our known spark @@ -3342,11 +3616,11 @@ uint16_t mode_exploding_fireworks(void) sparks[i].pos += sparks[i].vel; sparks[i].posX += sparks[i].velX; sparks[i].vel += *dying_gravity; - sparks[i].velX += strip.isMatrix ? *dying_gravity : 0; + sparks[i].velX += SEGMENT.is2D() ? *dying_gravity : 0; if (sparks[i].col > 3) sparks[i].col -= 4; if (sparks[i].pos > 0 && sparks[i].pos < rows) { - if (strip.isMatrix && !(sparks[i].posX >= 0 && sparks[i].posX < cols)) continue; + if (SEGMENT.is2D() && !(sparks[i].posX >= 0 && sparks[i].posX < cols)) continue; uint16_t prog = sparks[i].col; uint32_t spColor = (SEGMENT.palette) ? SEGMENT.color_wheel(sparks[i].colIndex) : SEGCOLOR(0); CRGB c = CRGB::Black; //HeatColor(sparks[i].col); @@ -3358,7 +3632,7 @@ uint16_t mode_exploding_fireworks(void) c.g = qsub8(c.g, cooling); c.b = qsub8(c.b, cooling * 2); } - if (strip.isMatrix) SEGMENT.setPixelColorXY(int(sparks[i].posX), rows - int(sparks[i].pos) - 1, c.red, c.green, c.blue); + if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(int(sparks[i].posX), rows - int(sparks[i].pos) - 1, c.red, c.green, c.blue); else SEGMENT.setPixelColor(int(sparks[i].posX) ? rows - int(sparks[i].pos) - 1 : int(sparks[i].pos), c.red, c.green, c.blue); } } @@ -3402,7 +3676,7 @@ uint16_t mode_drip(void) uint8_t numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3 float gravity = -0.0005 - (SEGMENT.speed/50000.0); - gravity *= SEGLEN-1; + gravity *= max(1, SEGLEN-1); int sourcedrop = 12; for (int j=0;jstack = 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); @@ -3532,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 { @@ -3586,14 +3860,14 @@ 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) { uint8_t percent = SEGMENT.intensity; percent = constrain(percent, 0, 200); - uint16_t active_leds = (percent < 100) ? SEGLEN * percent / 100.0 - : SEGLEN * (200 - percent) / 100.0; + uint16_t active_leds = (percent < 100) ? roundf(SEGLEN * percent / 100.0f) + : roundf(SEGLEN * (200 - percent) / 100.0f); uint8_t size = (1 + ((SEGMENT.speed * SEGLEN) >> 11)); if (SEGMENT.speed == 255) size = 255; @@ -3639,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); @@ -3797,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; } @@ -3883,6 +4157,7 @@ static const char _data_FX_MODE_PHASEDNOISE[] PROGMEM = "Phased Noise@!,!;!,!;!" uint16_t mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. + uint16_t prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through. for (int i = 0; i < SEGLEN; i++) { @@ -3892,6 +4167,7 @@ uint16_t mode_twinkleup(void) { // A very short twinkle routine SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri)); } + random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG return FRAMETIME; } static const char _data_FX_MODE_TWINKLEUP[] PROGMEM = "Twinkleup@!,Intensity;!,!;!;;m12=0"; @@ -3908,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))); @@ -4065,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++) { @@ -4259,8 +4535,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 @@ -4311,13 +4587,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); @@ -4374,15 +4650,15 @@ class AuroraWave { public: void init(uint32_t segment_length, CRGB color) { - ttl = random(500, 1501); + ttl = random16(500, 1501); basecolor = color; - basealpha = random(60, 101) / (float)100; + basealpha = random8(60, 101) / (float)100; age = 0; - width = random(segment_length / 20, segment_length / W_WIDTH_FACTOR); //half of width to make math easier + width = random16(segment_length / 20, segment_length / W_WIDTH_FACTOR); //half of width to make math easier if (!width) width = 1; - center = random(101) / (float)100 * segment_length; - goingleft = random(0, 2) == 0; - speed_factor = (random(10, 31) / (float)100 * W_MAX_SPEED / 255); + center = random8(101) / (float)100 * segment_length; + goingleft = random8(0, 2) == 0; + speed_factor = (random8(10, 31) / (float)100 * W_MAX_SPEED / 255); alive = true; } @@ -4467,7 +4743,7 @@ uint16_t mode_aurora(void) { waves = reinterpret_cast(SEGENV.data); for (int i = 0; i < SEGENV.aux1; i++) { - waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(random8(), false, false, random(0, 3)))); + waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(random8(), false, false, random8(0, 3)))); } } else { waves = reinterpret_cast(SEGENV.data); @@ -4479,7 +4755,7 @@ uint16_t mode_aurora(void) { if(!(waves[i].stillAlive())) { //If a wave dies, reinitialize it starts over. - waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(random8(), false, false, random(0, 3)))); + waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(random8(), false, false, random8(0, 3)))); } } @@ -4519,7 +4795,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)); } @@ -4536,7 +4812,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)); @@ -4554,8 +4830,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; @@ -4578,54 +4854,46 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef // Black hole uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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 x, y; - // initialize on first call - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } - 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); y = beatsin8(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + t * i); - SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255)); + SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(i*32, false, PALETTE_SOLID_WRAP, SEGMENT.check1?0:255)); } // inner stars for (size_t i = 0; i < 4; i++) { x = beatsin8(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + t * i); y = beatsin8(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + t * i); - SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255)); + SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(255-i*64, false, PALETTE_SOLID_WRAP, SEGMENT.check1?0:255)); } // central white dot - SEGMENT.setPixelColorXY(cols/2, rows/2, CHSV(0, 0, 255)); + SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); // blur everything a bit SEGMENT.blur(16); return FRAMETIME; } // mode_2DBlackHole() -static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Outer Y freq.,Outer X freq.,Inner X freq.,Inner Y freq.;;;2"; +static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Outer Y freq.,Outer X freq.,Inner X freq.,Inner Y freq.,Solid;!;!;2;pal=11"; //////////////////////////// // 2D Colored Bursts // //////////////////////////// uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.soulmatelights.com/gallery/819-colored-bursts , modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); SEGENV.aux0 = 0; // start with red hue } @@ -4672,21 +4940,15 @@ static const char _data_FX_MODE_2DCOLOREDBURSTS[] PROGMEM = "Colored Bursts@Spee // 2D DNA // ///////////////////// uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pastebin.com/pCkkkzcs. Updated by Preyy. WLED conversion by Andrew Tuline. - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } - 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); @@ -4698,21 +4960,20 @@ 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 - if (!strip.isMatrix) return mode_static(); // not a 2D set-up +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(); const uint16_t rows = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); 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++) { @@ -4745,44 +5006,42 @@ static const char _data_FX_MODE_2DDNASPIRAL[] PROGMEM = "DNA Spiral@Scroll speed // 2D Drift // ///////////////////////// uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmatelights.com/gallery/884-drift , Modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } + const uint16_t colsCenter = (cols>>1) + (cols%2); + const uint16_t rowsCenter = (rows>>1) + (rows%2); SEGMENT.fadeToBlackBy(128); - const uint16_t maxDim = MAX(cols, rows)/2; - unsigned long t = millis() / (32 - (SEGMENT.speed>>3)); - for (float i = 1; i < maxDim; i += 0.25) { + 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.0f; i < maxDim; i += 0.25f) { float angle = radians(t * (maxDim - i)); - uint16_t myX = (cols>>1) + (uint16_t)(sin_t(angle) * i) + (cols%2); - uint16_t myY = (rows>>1) + (uint16_t)(cos_t(angle) * i) + (rows%2); - SEGMENT.setPixelColorXY(myX, myY, ColorFromPalette(SEGPALETTE, (i * 20) + (t / 20), 255, LINEARBLEND)); + int16_t mySin = sin_t(angle) * i; + int16_t myCos = cos_t(angle) * i; + SEGMENT.setPixelColorXY(colsCenter + mySin, rowsCenter + myCos, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); + if (SEGMENT.check1) SEGMENT.setPixelColorXY(colsCenter + myCos, rowsCenter + mySin, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.intensity>>3); return FRAMETIME; } // mode_2DDrift() -static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount;;!;2"; +static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount,,,,Twin;;!;2"; ////////////////////////// // 2D Firenoise // ////////////////////////// uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline. Yet another short routine. - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); } @@ -4797,7 +5056,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 @@ -4811,16 +5070,11 @@ static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y sca // 2D Frizzles // ////////////////////////////// uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.soulmatelights.com/gallery/640-color-frizzles , Modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(16); for (size_t i = 8; i > 0; i--) { SEGMENT.addPixelColorXY(beatsin8(SEGMENT.speed/8 + i, 0, cols - 1), @@ -4843,7 +5097,7 @@ typedef struct ColorCount { } colorCount; uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ and https://github.com/DougHaber/nlife-color - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -4856,12 +5110,10 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: CRGB backgroundColor = SEGCOLOR(1); - if (SEGENV.call == 0) SEGMENT.setUpLeds(); - if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) { SEGENV.step = strip.now; SEGENV.aux0 = 0; - random16_set_seed(millis()>>2); //seed the random generator + //random16_set_seed(millis()>>2); //seed the random generator //give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen) for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { @@ -4914,7 +5166,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } // i,j // Rules of Life - uint32_t col = prevLeds[XY(x,y)]; + uint32_t col = uint32_t(prevLeds[XY(x,y)]) & 0x00FFFFFF; // uint32_t operator returns RGBA, we want RGBW -> cut off "alpha" byte uint32_t bgc = RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0); if ((col != bgc) && (neighbors < 2)) SEGMENT.setPixelColorXY(x,y, bgc); // Loneliness else if ((col != bgc) && (neighbors > 3)) SEGMENT.setPixelColorXY(x,y, bgc); // Overpopulation @@ -4951,7 +5203,7 @@ static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2 // 2D Hiphotic // ///////////////////////// uint16_t mode_2DHiphotic() { // By: ldirko https://editor.soulmatelights.com/gallery/810 , Modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -4983,7 +5235,7 @@ typedef struct Julia { } julia; uint16_t mode_2DJulia(void) { // An animated Julia set by Andrew Tuline. - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -5035,8 +5287,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); @@ -5089,13 +5341,13 @@ static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per p // 2D Lissajous // ////////////////////////////// uint16_t mode_2DLissajous(void) { // By: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); 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 ++) { @@ -5105,71 +5357,78 @@ 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; } // mode_2DLissajous() -static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,Fade rate,,,Speed;!;!;2;;c3=15"; +static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,Fade rate,,,Speed;!;!;2;c3=15"; /////////////////////// // 2D Matrix // /////////////////////// -uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi. - if (!strip.isMatrix) return mode_static(); // not a 2D set-up +uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi, and softhack007. + 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()+7) >> 3; //1 bit per LED for trails + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); + SEGENV.step = 0; } uint8_t fade = map(SEGMENT.custom1, 0, 255, 50, 250); // equals trail size uint8_t speed = (256-SEGMENT.speed) >> map(MIN(rows, 150), 0, 150, 0, 3); // slower speeds for small displays - CRGB spawnColor; - CRGB trailColor; + uint32_t spawnColor; + uint32_t trailColor; if (SEGMENT.check1) { spawnColor = SEGCOLOR(0); trailColor = SEGCOLOR(1); } else { - spawnColor = CRGB(175,255,175); - trailColor = CRGB(27,130,39); + spawnColor = RGBW32(175,255,175,0); + trailColor = RGBW32(27,130,39,0); } + bool emptyScreen = true; if (strip.now - SEGENV.step >= speed) { SEGENV.step = strip.now; - for (int row=rows-1; row>=0; row--) { - for (int col=0; col= 0; row--) { + for (int col = 0; col < cols; col++) { + unsigned index = XY(col, row) >> 3; + unsigned bitNum = XY(col, row) & 0x07; + if (bitRead(SEGENV.data[index], bitNum)) { SEGMENT.setPixelColorXY(col, row, trailColor); // create trail - if (row < rows-1) SEGMENT.setPixelColorXY(col, row+1, spawnColor); - } else { - // fade other pixels - SEGMENT.setPixelColorXY(col, row, pix.nscale8(fade)); + bitClear(SEGENV.data[index], bitNum); + if (row < rows-1) { + SEGMENT.setPixelColorXY(col, row+1, spawnColor); + index = XY(col, row+1) >> 3; + bitNum = XY(col, row+1) & 0x07; + bitSet(SEGENV.data[index], bitNum); + emptyScreen = false; + } } } } - // check for empty screen to ensure code spawn - bool emptyScreen = true; - for (int x=0; x> 3; + unsigned bitNum = XY(spawnX, 0) & 0x07; + bitSet(SEGENV.data[index], bitNum); } - } // if millis + } return FRAMETIME; } // mode_2Dmatrix() @@ -5180,7 +5439,7 @@ static const char _data_FX_MODE_2DMATRIX[] PROGMEM = "Matrix@!,Spawning rate,Tra // 2D Metaballs // ///////////////////////// uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have one of the dimensions be 2 or less. Adapted by Andrew Tuline. - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -5239,7 +5498,7 @@ static const char _data_FX_MODE_2DMETABALLS[] PROGMEM = "Metaballs@!;;!;2"; // 2D Noise // ////////////////////// uint16_t mode_2Dnoise(void) { // By Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -5248,7 +5507,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)); } } @@ -5262,19 +5521,13 @@ static const char _data_FX_MODE_2DNOISE[] PROGMEM = "Noise2D@!,Scale;;!;2"; // 2D Plasma Ball // ////////////////////////////// uint16_t mode_2DPlasmaball(void) { // By: Stepko https://editor.soulmatelights.com/gallery/659-plasm-ball , Modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } - 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); @@ -5308,7 +5561,7 @@ static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fad // return (out_max - out_min) * (x - in_min) / (in_max - in_min) + out_min; //} uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https://editor.soulmatelights.com/gallery/762-polar-lights , Modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -5316,7 +5569,6 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000}; if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); SEGENV.step = 0; } @@ -5360,18 +5612,12 @@ static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale; // 2D Pulser // ///////////////////////// uint16_t mode_2DPulser(void) { // By: ldirko https://editor.soulmatelights.com/gallery/878-pulse-test , modifed by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(8 - (SEGMENT.intensity>>5)); - uint32_t a = strip.now / (18 - SEGMENT.speed / 16); uint16_t x = (a / 14) % cols; uint16_t y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); @@ -5388,19 +5634,18 @@ static const char _data_FX_MODE_2DPULSER[] PROGMEM = "Pulser@!,Blur;;!;2"; // 2D Sindots // ///////////////////////// uint16_t mode_2DSindots(void) { // By: ldirko https://editor.soulmatelights.com/gallery/597-sin-dots , modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); } 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! @@ -5420,20 +5665,14 @@ static const char _data_FX_MODE_2DSINDOTS[] PROGMEM = "Sindots@!,Dot distance,Fa // custom3 affects the blur amount. uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://gist.github.com/kriegsman/368b316c55221134b160 // Modifed by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } - const uint8_t kBorderWidth = 2; SEGMENT.fadeToBlackBy(24); - uint8_t blurAmount = SEGMENT.custom3>>1; // reduced resolution slider SEGMENT.blur(blurAmount); @@ -5445,11 +5684,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() @@ -5460,7 +5697,7 @@ static const char _data_FX_MODE_2DSQUAREDSWIRL[] PROGMEM = "Squared Swirl@,,,,Bl // 2D Sun Radiation // ////////////////////////////// uint16_t mode_2DSunradiation(void) { // By: ldirko https://editor.soulmatelights.com/gallery/599-sun-radiation , modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -5469,11 +5706,10 @@ uint16_t mode_2DSunradiation(void) { // By: ldirko https://edi byte *bump = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); 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++) { @@ -5511,13 +5747,12 @@ static const char _data_FX_MODE_2DSUNRADIATION[] PROGMEM = "Sun Radiation@Varian // 2D Tartan // ///////////////////////// uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.soulmatelights.com/gallery/3-tartan , Modified by: Andrew Tuline - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); } @@ -5551,16 +5786,11 @@ static const char _data_FX_MODE_2DTARTAN[] PROGMEM = "Tartan@X scale,Y scale,,,S // 2D spaceships // ///////////////////////// uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [https://editor.soulmatelights.com/gallery/639-space-ships], adapted by Blaz Kristan (AKA blazoncek) - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } - uint32_t tb = strip.now >> 12; // every ~4s if (tb > SEGENV.step) { int8_t dir = ++SEGENV.aux0; @@ -5599,7 +5829,7 @@ static const char _data_FX_MODE_2DSPACESHIPS[] PROGMEM = "Spaceships@!,Blur;;!;2 //// Crazy bees by stepko (c)12.02.21 [https://editor.soulmatelights.com/gallery/651-crazy-bees], adapted by Blaz Kristan (AKA blazoncek) #define MAX_BEES 5 uint16_t mode_2Dcrazybees(void) { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -5610,15 +5840,15 @@ uint16_t mode_2Dcrazybees(void) { uint8_t posX, posY, aimX, aimY, hue; 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(); + //random16_set_seed(millis()); + 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; @@ -5626,8 +5856,7 @@ uint16_t mode_2Dcrazybees(void) { bee_t *bee = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); + 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); @@ -5635,8 +5864,8 @@ uint16_t mode_2Dcrazybees(void) { } } - if (millis() > SEGENV.step) { - SEGENV.step = millis() + (FRAMETIME * 8 / ((SEGMENT.speed>>5)+1)); + if (strip.now > SEGENV.step) { + SEGENV.step = strip.now + (FRAMETIME * 16 / ((SEGMENT.speed>>4)+1)); SEGMENT.fadeToBlackBy(32); @@ -5673,7 +5902,7 @@ static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur;;;2"; //// Ghost Rider by stepko (c)2021 [https://editor.soulmatelights.com/gallery/716-ghost-rider], adapted by Blaz Kristan (AKA blazoncek) #define LIGHTERS_AM 64 // max lighters (adequate for 32x32 matrix) uint16_t mode_2Dghostrider(void) { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -5696,13 +5925,12 @@ uint16_t mode_2Dghostrider(void) { const size_t maxLighters = min(cols + rows, LIGHTERS_AM); - if (SEGENV.call == 0) SEGMENT.setUpLeds(); if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { SEGENV.aux0 = cols; SEGENV.aux1 = rows; - SEGMENT.fill(BLACK); - random16_set_seed(strip.now); + //random16_set_seed(strip.now); lighter->angleSpeed = random8(0,20) - 10; + lighter->gAngle = random16(); lighter->Vspeed = 5; lighter->gPosX = (cols/2) * 10; lighter->gPosY = (rows/2) * 10; @@ -5710,11 +5938,12 @@ uint16_t mode_2Dghostrider(void) { lighter->lightersPosX[i] = lighter->gPosX; lighter->lightersPosY[i] = lighter->gPosY + i; lighter->time[i] = i * 2; + lighter->reg[i] = false; } } - 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); @@ -5740,7 +5969,7 @@ uint16_t mode_2Dghostrider(void) { if (lighter->reg[i]) { lighter->lightersPosY[i] = lighter->gPosY; lighter->lightersPosX[i] = lighter->gPosX; - lighter->Angle[i] = lighter->gAngle + random(-10, 10); + lighter->Angle[i] = lighter->gAngle + ((int)random8(20) - 10); lighter->time[i] = 0; lighter->reg[i] = false; } else { @@ -5763,7 +5992,7 @@ static const char _data_FX_MODE_2DGHOSTRIDER[] PROGMEM = "Ghost Rider@Fade rate, //// Floating Blobs by stepko (c)2021 [https://editor.soulmatelights.com/gallery/573-blobs], adapted by Blaz Kristan (AKA blazoncek) #define MAX_BLOBS 8 uint16_t mode_2Dfloatingblobs(void) { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -5781,11 +6010,10 @@ uint16_t mode_2Dfloatingblobs(void) { if (!SEGENV.allocateData(sizeof(blob_t))) return mode_static(); //allocation failed blob_t *blob = reinterpret_cast(SEGENV.data); - if (SEGENV.call == 0) SEGMENT.setUpLeds(); if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { SEGENV.aux0 = cols; // re-initialise if virtual size changes SEGENV.aux1 = rows; - SEGMENT.fill(BLACK); + //SEGMENT.fill(BLACK); for (size_t i = 0; i < MAX_BLOBS; i++) { blob->r[i] = random8(1, cols>8 ? (cols/4) : 2); blob->sX[i] = (float) random8(3, cols) / (float)(256 - SEGMENT.speed); // speed x @@ -5799,11 +6027,11 @@ uint16_t mode_2Dfloatingblobs(void) { } } - SEGMENT.fadeToBlackBy(20); + SEGMENT.fadeToBlackBy((SEGMENT.custom2>>3)+1); // 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 @@ -5850,25 +6078,25 @@ 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; } #undef MAX_BLOBS -static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur;!;!;2;c1=8"; +static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail;!;!;2;c1=8"; //////////////////////////// // 2D Scrolling text // //////////////////////////// uint16_t mode_2Dscrollingtext(void) { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - int letterWidth; - int letterHeight; + int letterWidth, rotLW; + int letterHeight, rotLH; switch (map(SEGMENT.custom2, 0, 255, 1, 5)) { default: case 1: letterWidth = 4; letterHeight = 6; break; @@ -5877,59 +6105,85 @@ uint16_t mode_2Dscrollingtext(void) { case 4: letterWidth = 7; letterHeight = 9; break; case 5: letterWidth = 5; letterHeight = 12; break; } - const bool zero = SEGMENT.check3; - const int yoffset = map(SEGMENT.intensity, 0, 255, -rows/2, rows/2) + (rows-letterHeight)/2; + // letters are rotated + if (((SEGMENT.custom3+1)>>3) % 2) { + rotLH = letterWidth; + rotLW = letterHeight; + } else { + rotLW = letterWidth; + rotLH = letterHeight; + } + char text[WLED_MAX_SEGNAME_LEN+1] = {'\0'}; if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; + const bool zero = strchr(text, '0') != nullptr; - if (!strlen(text) - || !strncmp_P(text,PSTR("#DATE"),5) - || !strncmp_P(text,PSTR("#DDMM"),5) - || !strncmp_P(text,PSTR("#MMDD"),5) - || !strncmp_P(text,PSTR("#TIME"),5) - || !strncmp_P(text,PSTR("#HHMM"),5)) { // fallback if empty segment name: display date and time - char sec[5]; - byte AmPmHour = hour(localTime); - boolean isitAM = true; - if (useAMPM) { - if (AmPmHour > 11) { AmPmHour -= 12; isitAM = false; } - if (AmPmHour == 0) { AmPmHour = 12; } - } - if (useAMPM) sprintf_P(sec, PSTR(" %2s"), (isitAM ? "AM" : "PM")); - else sprintf_P(sec, PSTR(":%02d"), second(localTime)); - if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, zero?PSTR("%02d.%02d.%04d"):PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); - else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, zero?PSTR("%02d.%02d"):PSTR("%d.%d"), day(localTime), month(localTime)); - else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, zero?PSTR("%02d/%02d"):PSTR("%d/%d"), month(localTime), day(localTime)); - else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, zero?PSTR("%02d:%02d%s"):PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); - else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d"):PSTR("%d:%02d"), AmPmHour, minute(localTime)); - else sprintf_P(text, zero?PSTR("%s %02d, %04d %02d:%02d%s"):PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); + char sec[5]; + int AmPmHour = hour(localTime); + bool isitAM = true; + if (useAMPM) { + if (AmPmHour > 11) { AmPmHour -= 12; isitAM = false; } + if (AmPmHour == 0) { AmPmHour = 12; } + sprintf_P(sec, PSTR(" %2s"), (isitAM ? "AM" : "PM")); + } else { + sprintf_P(sec, PSTR(":%02d"), second(localTime)); } - const int numberOfLetters = strlen(text); - if (SEGENV.step < millis()) { - if ((numberOfLetters * letterWidth) > cols) ++SEGENV.aux0 %= (numberOfLetters * letterWidth) + cols; // offset - else SEGENV.aux0 = (cols + (numberOfLetters * letterWidth))/2; + if (!strlen(text)) { // fallback if empty segment name: display date and time + sprintf_P(text, PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); + } else { + if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, zero?PSTR("%02d.%02d.%04d"):PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); + else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, zero?PSTR("%02d.%02d") :PSTR("%d.%d"), day(localTime), month(localTime)); + else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, zero?PSTR("%02d/%02d") :PSTR("%d/%d"), month(localTime), day(localTime)); + else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, zero?PSTR("%02d:%02d%s") :PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); + else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime)); + else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), AmPmHour); + else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), minute(localTime)); + } + + const int numberOfLetters = strlen(text); + 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 = strip.now % speed + 1; + if (SEGMENT.intensity == 255) { + yoffset = (2 * frac * rows)/speed - rows; + } else if (SEGMENT.intensity == 0) { + yoffset = rows - (2 * frac * rows)/speed; + } + } + + if (SEGENV.step < strip.now) { + // calculate start offset + if (width > cols) { + if (SEGMENT.check3) { + if (SEGENV.aux0 == 0) SEGENV.aux0 = width + cols - 1; + else --SEGENV.aux0; + } else ++SEGENV.aux0 %= width + cols; + } else SEGENV.aux0 = (cols + width)/2; ++SEGENV.aux1 &= 0xFF; // color shift - SEGENV.step = millis() + map(SEGMENT.speed, 0, 255, 10*FRAMETIME_FIXED, 2*FRAMETIME_FIXED); - if (!SEGMENT.check2) { - for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++ ) - SEGMENT.blendPixelColorXY(x, y, SEGCOLOR(1), 255 - (SEGMENT.custom1>>1)); - } + 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 + for (int i = 0; i < numberOfLetters; i++) { - if (int(cols) - int(SEGENV.aux0) + letterWidth*(i+1) < 0) continue; // don't draw characters off-screen + int xoffset = int(cols) - int(SEGENV.aux0) + rotLW*i; + if (xoffset + rotLW < 0) continue; // don't draw characters off-screen uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0); uint32_t col2 = BLACK; if (SEGMENT.check1 && SEGMENT.palette == 0) { col1 = SEGCOLOR(0); col2 = SEGCOLOR(2); } - SEGMENT.drawCharacter(text[i], int(cols) - int(SEGENV.aux0) + letterWidth*i, yoffset, letterWidth, letterHeight, col1, col2); + SEGMENT.drawCharacter(text[i], xoffset, yoffset, letterWidth, letterHeight, col1, col2, map(SEGMENT.custom3, 0, 31, -2, 2)); } return FRAMETIME; } -static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,,Gradient,Overlay,0;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; +static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,Overlay,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; //////////////////////////// @@ -5937,7 +6191,7 @@ static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Off //////////////////////////// //// Drift Rose by stepko (c)2021 [https://editor.soulmatelights.com/gallery/1369-drift-rose-pattern], adapted by Blaz Kristan (AKA blazoncek) uint16_t mode_2Ddriftrose(void) { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -5946,15 +6200,11 @@ uint16_t mode_2Ddriftrose(void) { const float CY = (rows-rows%2)/2.f - .5f; const float L = min(cols, rows) / 2.f; - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } - SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3)); for (size_t i = 1; i < 37; i++) { - uint32_t x = (CX + (sin_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; - uint32_t y = (CY + (cos_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; + float angle = radians(i * 10); + uint32_t x = (CX + (sin_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f; + uint32_t y = (CY + (cos_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f; SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); } SEGMENT.blur((SEGMENT.intensity>>4)+1); @@ -5963,6 +6213,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 @@ -6096,13 +6391,12 @@ static const char _data_FX_MODE_RIPPLEPEAK[] PROGMEM = "Ripple Peak@Fade rate,Ma ///////////////////////// // By: Mark Kriegsman https://gist.github.com/kriegsman/5adca44e14ad025e6d3b , modified by Andrew Tuline uint16_t mode_2DSwirl(void) { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); } @@ -6114,7 +6408,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)) { @@ -6124,14 +6417,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]; - // printUmData(); - - 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() @@ -6143,16 +6434,11 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B ///////////////////////// // By: Stepko, https://editor.soulmatelights.com/gallery/652-wave , modified by Andrew Tuline uint16_t mode_2DWaverly(void) { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); - } - um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6162,7 +6448,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 @@ -6200,6 +6486,7 @@ typedef struct Gravity { // * GRAVCENTER // /////////////////////// uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); const uint16_t dataSize = sizeof(gravity); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -6216,14 +6503,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.0); // map to pixels available in current segment + 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; @@ -6248,6 +6535,7 @@ static const char _data_FX_MODE_GRAVCENTER[] PROGMEM = "Gravcenter@Rate of fall, // * GRAVCENTRIC // /////////////////////// uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); uint16_t dataSize = sizeof(gravity); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -6266,15 +6554,15 @@ uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew //SEGMENT.fade_out(240); // twice? really? SEGMENT.fade_out(253); // 50% - float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; - segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivty" upscaling + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; + 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.0); // map to pixels availeable in current segment + 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; @@ -6345,6 +6634,7 @@ static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall, // * JUGGLES // ////////////////////// uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6356,7 +6646,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; @@ -6451,7 +6742,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. @@ -6503,10 +6794,10 @@ static const char _data_FX_MODE_NOISEMETER[] PROGMEM = "Noisemeter@Fade rate,Wid // * PIXELWAVE // ////////////////////// uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); } @@ -6523,7 +6814,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 } @@ -6579,10 +6870,11 @@ static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels /////////////////////// // Andrew's crappy peak detector. If I were 40+ years younger, I'd learn signal processing. uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); uint16_t size = 0; uint8_t fadeVal = map(SEGMENT.speed,0,255, 224, 254); - uint16_t pos = random(SEGLEN); // Set a random starting position. + uint16_t pos = random16(SEGLEN); // Set a random starting position. um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6610,7 +6902,7 @@ uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. } for (int i=0; i(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low. @@ -6663,7 +6957,7 @@ uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline. } float volumeSmth = *(float*) um_data->u_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)); @@ -6686,6 +6980,7 @@ static const char _data_FX_MODE_PIXELS[] PROGMEM = "Pixels@Fade rate,# of pixels // ** Blurz // ////////////////////// uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. + if (SEGLEN == 1) return mode_static(); // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment um_data_t *um_data; @@ -6706,7 +7001,7 @@ uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. SEGENV.step += FRAMETIME; if (SEGENV.step > SPEED_FORMULA_L) { uint16_t segLoc = random16(SEGLEN); - SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(2*fftResult[SEGENV.aux0%16]*240/(SEGLEN-1), false, PALETTE_SOLID_WRAP, 0), 2*fftResult[SEGENV.aux0%16])); + SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(2*fftResult[SEGENV.aux0%16]*240/max(1, SEGLEN-1), false, PALETTE_SOLID_WRAP, 0), 2*fftResult[SEGENV.aux0%16])); ++(SEGENV.aux0) %= 16; // make sure it doesn't cross 16 SEGENV.step = 1; @@ -6722,6 +7017,7 @@ static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur;!,Color // ** DJLight // ///////////////////////// uint16_t mode_DJLight(void) { // Written by ??? Adapted by Will Tatam. + if (SEGLEN == 1) return mode_static(); const int mid = SEGLEN / 2; um_data_t *um_data; @@ -6732,7 +7028,6 @@ uint16_t mode_DJLight(void) { // Written by ??? Adapted by Wil uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); } @@ -6756,6 +7051,7 @@ static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;1f;m12=2,s // ** Freqmap // //////////////////// uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate. + if (SEGLEN == 1) return mode_static(); // Start frequency = 60 Hz and log10(60) = 1.78 // End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10 @@ -6792,6 +7088,7 @@ static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting // ** Freqmatrix // /////////////////////// uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung. + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6801,7 +7098,6 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch float volumeSmth = *(float*)um_data->u_data[0]; if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); } @@ -6819,7 +7115,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) { @@ -6840,7 +7136,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch return FRAMETIME; } // mode_freqmatrix() -static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Time delay,Sound effect,Low bin,High bin,Sensivity;;;1f;m12=3,si=0"; // Corner, Beatsin +static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensitivity;;;1f;m12=3,si=0"; // Corner, Beatsin ////////////////////// @@ -6858,24 +7154,27 @@ uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. } float FFT_MajorPeak = *(float*)um_data->u_data[4]; float my_magnitude = *(float*)um_data->u_data[5] / 16.0f; - if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1.0f; // log10(0) is "forbidden" (throws exception) - uint16_t fadeRate = 2*SEGMENT.speed - SEGMENT.speed*SEGMENT.speed/255; // Get to 255 as quick as you can. + // this code translates to speed * (2 - speed/255) which is a) speed*2 or b) speed (when speed is 255) + // and since fade_out() can only take 0-255 it will behave incorrectly when speed > 127 + //uint16_t fadeRate = 2*SEGMENT.speed - SEGMENT.speed*SEGMENT.speed/255; // Get to 255 as quick as you can. + uint16_t fadeRate = SEGMENT.speed*SEGMENT.speed; // Get to 255 as quick as you can. + fadeRate = map(fadeRate, 0, 65535, 1, 255); - if (SEGENV.call == 0) SEGMENT.fill(BLACK); int fadeoutDelay = (256 - SEGMENT.speed) / 64; if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(fadeRate); + uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. + if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow for (int i=0; i < SEGMENT.intensity/32+1; i++) { uint16_t locn = random16(0,SEGLEN); - uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. - if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); } return FRAMETIME; } // mode_freqpixels() -static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Starting color and # of pixels;;;1f;m12=0,si=0"; // Pixels, Beatsin +static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Starting color and # of pixels;!,!,;!;1f;m12=0,si=0"; // Pixels, Beatsin ////////////////////// @@ -6894,6 +7193,7 @@ static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Sta // As a compromise between speed and accuracy we are currently sampling with 10240Hz, from which we can then determine with a 512bin FFT our max frequency is 5120Hz. // Depending on the music stream you have you might find it useful to change the frequency mapping. uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung. + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -6903,7 +7203,6 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun float volumeSmth = *(float*)um_data->u_data[0]; if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); } @@ -6921,7 +7220,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) { @@ -6944,14 +7243,14 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun return FRAMETIME; } // mode_freqwave() -static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Time delay,Sound effect,Low bin,High bin,Pre-amp;;;1f;m12=2,si=0"; // Circle, Beatsin +static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Speed,Sound effect,Low bin,High bin,Pre-amp;;;1f;m12=2,si=0"; // Circle, Beatsin /////////////////////// // ** Gravfreq // /////////////////////// uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. - + if (SEGLEN == 1) return mode_static(); uint16_t dataSize = sizeof(gravity); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Gravity* gravcen = reinterpret_cast(SEGENV.data); @@ -6968,9 +7267,9 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. SEGMENT.fade_out(250); float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; - segmentSampleAvg *= 0.125; // 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.0); // map to pixels availeable in current segment + 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. uint8_t gravity = 8 - SEGMENT.speed/32; @@ -6996,13 +7295,14 @@ 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 ////////////////////// // ** Noisemove // ////////////////////// uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuline + if (SEGLEN == 1) return mode_static(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio @@ -7010,14 +7310,12 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli } uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; - if (SEGENV.call == 0) SEGMENT.fill(BLACK); - //SEGMENT.fade_out(224); // Just in case something doesn't get faded. int fadeoutDelay = (256 - SEGMENT.speed) / 96; if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(4+ SEGMENT.speed/4); uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. for (int i=0; iu_data[2]; if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); } @@ -7276,7 +7571,7 @@ static uint8_t akemi[] PROGMEM = { }; uint16_t mode_2DAkemi(void) { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -7344,7 +7639,7 @@ static const char _data_FX_MODE_2DAKEMI[] PROGMEM = "Akemi@Color speed,Dance;Hea // https://editor.soulmatelights.com/gallery/1089-distorsion-waves // adapted for WLED by @blazoncek uint16_t mode_2Ddistortionwaves() { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -7354,7 +7649,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; @@ -7399,7 +7694,7 @@ static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@ //Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick // adapted for WLED by @blazoncek uint16_t mode_2Dsoap() { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -7418,7 +7713,6 @@ uint16_t mode_2Dsoap() { // init if (SEGENV.call == 0) { - SEGMENT.setUpLeds(); *noise32_x = random16(); *noise32_y = random16(); *noise32_z = random16(); @@ -7452,6 +7746,7 @@ uint16_t mode_2Dsoap() { int amplitude; int8_t shiftX = 0; //(SEGMENT.custom1 - 128) / 4; int8_t shiftY = 0; //(SEGMENT.custom2 - 128) / 4; + CRGB ledsbuff[MAX(cols,rows)]; amplitude = (cols >= 16) ? (cols-8)/8 : 1; for (int y = 0; y < rows; y++) { @@ -7472,9 +7767,9 @@ uint16_t mode_2Dsoap() { CRGB PixelB = CRGB::Black; if ((zF >= 0) && (zF < cols)) PixelB = SEGMENT.getPixelColorXY(zF, y); else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[XY(abs(zF),y)]*3); - CRGB pix = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); - SEGMENT.setPixelColorXY(x, y, pix); + ledsbuff[x] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); } + for (int x = 0; x < cols; x++) SEGMENT.setPixelColorXY(x, y, ledsbuff[x]); } amplitude = (rows >= 16) ? (rows-8)/8 : 1; @@ -7496,9 +7791,9 @@ uint16_t mode_2Dsoap() { CRGB PixelB = CRGB::Black; if ((zF >= 0) && (zF < rows)) PixelB = SEGMENT.getPixelColorXY(x, zF); else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[XY(x,abs(zF))]*3); - CRGB pix = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); - SEGMENT.setPixelColorXY(x, y, pix); + ledsbuff[y] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction))); } + for (int y = 0; y < rows; y++) SEGMENT.setPixelColorXY(x, y, ledsbuff[y]); } return FRAMETIME; @@ -7511,7 +7806,7 @@ static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness;;!;2"; //Stepko and Sutaburosu // adapted for WLED by @blazoncek uint16_t mode_2Doctopus() { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); @@ -7536,12 +7831,12 @@ uint16_t mode_2Doctopus() { SEGENV.aux1 = rows; *offsX = SEGMENT.custom1; *offsY = SEGMENT.custom2; - const uint8_t C_X = cols / 2 + (SEGMENT.custom1 - 128)*cols/255; - const uint8_t C_Y = rows / 2 + (SEGMENT.custom2 - 128)*rows/255; + const int C_X = (cols / 2) + ((SEGMENT.custom1 - 128)*cols)/255; + const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - rMap[XY(x, y)].angle = 40.7436f * atan2f(y - C_Y, x - C_X); // avoid 128*atan2()/PI - rMap[XY(x, y)].radius = hypotf(x - C_X, y - C_Y) * mapp; //thanks Sutaburosu + rMap[XY(x, y)].angle = 40.7436f * atan2f((y - C_Y), (x - C_X)); // avoid 128*atan2()/PI + rMap[XY(x, y)].radius = hypotf((x - C_X), (y - C_Y)) * mapp; //thanks Sutaburosu } } } @@ -7567,12 +7862,12 @@ static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offse //@Stepko (https://editor.soulmatelights.com/gallery/1704-wavingcells) // adapted for WLED by @blazoncek uint16_t mode_2Dwavingcell() { - if (!strip.isMatrix) return mode_static(); // not a 2D set-up + 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(); - 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; @@ -7592,7 +7887,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 @@ -7667,6 +7962,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_FIRE_FLICKER, &mode_fire_flicker, _data_FX_MODE_FIRE_FLICKER); addEffect(FX_MODE_GRADIENT, &mode_gradient, _data_FX_MODE_GRADIENT); addEffect(FX_MODE_LOADING, &mode_loading, _data_FX_MODE_LOADING); + addEffect(FX_MODE_ROLLINGBALLS, &rolling_balls, _data_FX_MODE_ROLLINGBALLS); addEffect(FX_MODE_FAIRY, &mode_fairy, _data_FX_MODE_FAIRY); addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS); @@ -7776,6 +8072,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 4f7bcb880..1089a0b8b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -59,10 +59,9 @@ /* Not used in all effects yet */ #define WLED_FPS 42 #define FRAMETIME_FIXED (1000/WLED_FPS) -//#define FRAMETIME _frametime #define FRAMETIME strip.getFrameTime() -/* each segment uses 52 bytes of SRAM memory, so if you're application fails because of +/* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ #ifdef ESP8266 #define MAX_NUM_SEGMENTS 16 @@ -73,9 +72,9 @@ #define MAX_NUM_SEGMENTS 32 #endif #if defined(ARDUINO_ARCH_ESP32S2) - #define MAX_SEGMENT_DATA 24576 + #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*768 // 24k by default (S2 is short on free RAM) #else - #define MAX_SEGMENT_DATA 32767 + #define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*1280 // 40k by default #endif #endif @@ -109,20 +108,15 @@ #define PINK (uint32_t)0xFF1493 #define ULTRAWHITE (uint32_t)0xFFFFFFFF #define DARKSLATEGRAY (uint32_t)0x2F4F4F -#define DARKSLATEGREY (uint32_t)0x2F4F4F +#define DARKSLATEGREY DARKSLATEGRAY -// options -// bit 7: segment is in transition mode -// bits 4-6: TBD -// bit 3: mirror effect within segment -// bit 2: segment is on -// bit 1: reverse segment -// bit 0: segment is selected +// segment options #define NO_OPTIONS (uint16_t)0x0000 -#define TRANSPOSED (uint16_t)0x0400 // rotated 90deg & reversed -#define REVERSE_Y_2D (uint16_t)0x0200 -#define MIRROR_Y_2D (uint16_t)0x0100 -#define TRANSITIONAL (uint16_t)0x0080 +#define TRANSPOSED (uint16_t)0x0100 // rotated 90deg & reversed +#define MIRROR_Y_2D (uint16_t)0x0080 +#define REVERSE_Y_2D (uint16_t)0x0040 +#define RESET_REQ (uint16_t)0x0020 +#define FROZEN (uint16_t)0x0010 #define MIRROR (uint16_t)0x0008 #define SEGMENT_ON (uint16_t)0x0004 #define REVERSE (uint16_t)0x0002 @@ -176,7 +170,7 @@ #define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 -// #define FX_MODE_POLICE 48 // removed in 0.14! +#define FX_MODE_ROLLINGBALLS 48 //was Police before 0.14 #define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity) #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) @@ -242,7 +236,7 @@ #define FX_MODE_CHUNCHUN 111 #define FX_MODE_DANCING_SHADOWS 112 #define FX_MODE_WASHING_MACHINE 113 -// #define FX_MODE_CANDY_CANE 114 // removed in 0.14! +#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) @@ -283,7 +277,7 @@ #define FX_MODE_RIPPLEPEAK 148 #define FX_MODE_2DFIRENOISE 149 #define FX_MODE_2DSQUAREDSWIRL 150 -#define FX_MODE_2DFIRE2012 151 +// #define FX_MODE_2DFIRE2012 151 #define FX_MODE_2DDNA 152 #define FX_MODE_2DMATRIX 153 #define FX_MODE_2DMETABALLS 154 @@ -293,7 +287,7 @@ #define FX_MODE_GRAVFREQ 158 #define FX_MODE_DJLIGHT 159 #define FX_MODE_2DFUNKYPLANK 160 -#define FX_MODE_2DCENTERBARS 161 +//#define FX_MODE_2DCENTERBARS 161 #define FX_MODE_2DPULSER 162 #define FX_MODE_BLURZ 163 #define FX_MODE_2DDRIFT 164 @@ -329,7 +323,7 @@ typedef enum mapping1D2D { M12_pCorner = 3 } mapping1D2D_t; -// segment, 72 bytes +// segment, 80 bytes typedef struct Segment { public: uint16_t start; // start index / start X coordinate 2D (left) @@ -348,12 +342,11 @@ typedef struct Segment { bool mirror : 1; // 3 : mirrored bool freeze : 1; // 4 : paused/frozen bool reset : 1; // 5 : indicates that Segment runtime requires reset - bool transitional: 1; // 6 : transitional (there is transition occuring) - bool reverse_y : 1; // 7 : reversed Y (2D) - bool mirror_y : 1; // 8 : mirrored Y (2D) - bool transpose : 1; // 9 : transposed (2D, swapped X & Y) - uint8_t map1D2D : 3; // 10-12 : mapping for 1D effect on 2D (0-use as strip, 1-expand vertically, 2-circular/arc, 3-rectangular/corner, ...) - uint8_t soundSim : 1; // 13 : 0-1 sound simulation types ("soft" & "hard" or "on"/"off") + bool reverse_y : 1; // 6 : reversed Y (2D) + bool mirror_y : 1; // 7 : mirrored Y (2D) + bool transpose : 1; // 8 : transposed (2D, swapped X & Y) + uint8_t map1D2D : 3; // 9-11 : mapping for 1D effect on 2D (0-use as strip, 1-expand vertically, 2-circular/arc, 3-rectangular/corner, ...) + uint8_t soundSim : 2; // 12-13 : 0-3 sound simulation types ("soft" & "hard" or "on"/"off") uint8_t set : 2; // 14-15 : 0-3 UI segment sets/groups }; }; @@ -370,7 +363,7 @@ typedef struct Segment { }; uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows - char *name; + char *name; // runtime data unsigned long next_time; // millis() of next update @@ -378,11 +371,33 @@ typedef struct Segment { uint32_t call; // call counter uint16_t aux0; // custom var uint16_t aux1; // custom var - byte* data; // effect data pointer - CRGB* leds; // local leds[] array (may be a pointer to global) - static CRGB *_globalLeds; // global leds[] array + byte *data; // effect data pointer static uint16_t maxWidth, maxHeight; // these define matrix width & height (max. segment dimensions) + typedef struct TemporarySegmentData { + uint16_t _optionsT; + uint32_t _colorT[NUM_COLORS]; + uint8_t _speedT; + uint8_t _intensityT; + uint8_t _custom1T, _custom2T; // custom FX parameters/sliders + struct { + uint8_t _custom3T : 5; // reduced range slider (0-31) + bool _check1T : 1; // checkmark 1 + bool _check2T : 1; // checkmark 2 + bool _check3T : 1; // checkmark 3 + }; + uint16_t _aux0T; + uint16_t _aux1T; + uint32_t _stepT; + uint32_t _callT; + uint8_t *_dataT; + uint16_t _dataLenT; + TemporarySegmentData() + : _dataT(nullptr) // just in case... + , _dataLenT(0) + {} + } tmpsegd_t; + private: union { uint8_t _capabilities; @@ -394,42 +409,38 @@ typedef struct Segment { uint8_t _reserved : 4; }; }; - uint16_t _dataLen; + uint16_t _dataLen; static uint16_t _usedSegmentData; - // transition data, valid only if transitional==true, holds values during transition + // perhaps this should be per segment, not static + static CRGBPalette16 _randomPalette; // actual random palette + static CRGBPalette16 _newRandomPalette; // target random palette + 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 + + // transition data, valid only if transitional==true, holds values during transition (72 bytes) struct Transition { + #ifndef WLED_DISABLE_MODE_BLEND + tmpsegd_t _segT; // previous segment environment + uint8_t _modeT; // previous mode/effect + #else uint32_t _colorT[NUM_COLORS]; + #endif 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 _modeP; // previous mode/effect - //uint16_t _aux0, _aux1; // previous mode/effect runtime data - //uint32_t _step, _call; // previous mode/effect runtime data - //byte *_data; // previous mode/effect runtime data - uint32_t _start; + 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) - : _briT(255) - , _cctT(127) - , _palT(CRGBPalette16(CRGB::Black)) + : _palT(CRGBPalette16(CRGB::Black)) , _prevPaletteBlends(0) - , _modeP(FX_MODE_STATIC) , _start(millis()) , _dur(dur) {} - Transition(uint16_t d, uint8_t b, uint8_t c, const uint32_t *o) - : _briT(b) - , _cctT(c) - , _palT(CRGBPalette16(CRGB::Black)) - , _prevPaletteBlends(0) - , _modeP(FX_MODE_STATIC) - , _start(millis()) - , _dur(d) - { - for (size_t i=0; i(%p)", (int)_dataLen, data); //Serial.println(); - //#endif - if (!Segment::_globalLeds && leds) { free(leds); leds = nullptr;} // reset to nullptr, to avoid race conditions - if (name) delete[] name; - if (_t) delete _t; + #endif + if (name) { delete[] name; name = nullptr; } + stopTransition(); deallocateData(); } @@ -497,24 +507,29 @@ typedef struct Segment { Segment& operator= (Segment &&orig) noexcept; // move assignment #ifdef WLED_DEBUG - size_t getSize() const { return sizeof(Segment) + (data?_dataLen:0) + (name?strlen(name):0) + (_t?sizeof(Transition):0) + (!Segment::_globalLeds && leds?sizeof(CRGB)*length():0); } + size_t getSize() const { return sizeof(Segment) + (data?_dataLen:0) + (name?strlen(name):0) + (_t?sizeof(Transition):0); } #endif inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); } inline bool isSelected(void) const { return selected; } + inline bool isInTransition(void) const { return _t != nullptr; } inline bool isActive(void) const { return stop > start; } inline bool is2D(void) const { return (width()>1 && height()>1); } inline bool hasRGB(void) const { return _isRGB; } inline bool hasWhite(void) const { return _hasW; } inline bool isCCT(void) const { return _isCCT; } - inline uint16_t width(void) const { return (stop > start) ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) - inline uint16_t height(void) const { return (stopY > startY) ? (stopY - startY) : 0; } // segment height (if 2D) in physical pixels // softhack007: make sure its always > 0 - inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels + inline uint16_t width(void) const { return isActive() ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) + inline uint16_t height(void) const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1) + inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels inline uint16_t groupLength(void) const { return grouping + spacing; } inline uint8_t getLightCapabilities(void) const { return _capabilities; } static uint16_t getUsedSegmentData(void) { return _usedSegmentData; } static void addUsedSegmentData(int len) { _usedSegmentData += len; } + #ifndef WLED_DISABLE_MODE_BLEND + static void modeBlend(bool blend) { _modeBlend = blend; } + #endif + static void handleRandomPalette(); void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); bool setColor(uint8_t slot, uint32_t c); //returns true if changed @@ -528,9 +543,9 @@ typedef struct Segment { // runtime data functions inline uint16_t dataSize(void) const { return _dataLen; } - bool allocateData(size_t len); - void deallocateData(void); - void resetIfRequired(void); + bool allocateData(size_t len); // allocates effect data buffer in heap and clears it + void deallocateData(void); // deallocates (frees) effect data buffer from heap + void resetIfRequired(void); // sets all SEGENV variables to 0 and clears data buffer /** * Flags that before the next effect is calculated, * the internal segment state should be reset. @@ -538,62 +553,67 @@ typedef struct Segment { * Safe to call from interrupts and network requests. */ inline void markForReset(void) { reset = true; } // setOption(SEG_OPTION_RESET, true) - void setUpLeds(void); // set up leds[] array for loseless getPixelColor() // transition functions - void startTransition(uint16_t dur); // transition has to start before actual segment values change + void startTransition(uint16_t dur); // transition has to start before actual segment values change + void stopTransition(void); // ends transition mode by destroying transition structure void handleTransition(void); - uint16_t progress(void); //transition progression between 0-65535 - uint8_t currentBri(uint8_t briNew, bool useCct = false); - uint8_t currentMode(uint8_t modeNew); - uint32_t currentColor(uint8_t slot, uint32_t colorNew); + #ifndef WLED_DISABLE_MODE_BLEND + void swapSegenv(tmpsegd_t &tmpSegD); // copies segment data into specifed buffer, if buffer is not a transition buffer, segment data is overwritten from transition buffer + void restoreSegenv(tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer + #endif + uint16_t progress(void); // transition progression between 0-65535 + uint8_t currentBri(bool useCct = false); // current segment brightness/CCT (blended while in transition) + uint8_t currentMode(void); // currently active effect/mode (while in transition) + uint32_t currentColor(uint8_t slot); // currently active segment color (blended while in transition) CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); CRGBPalette16 ¤tPalette(CRGBPalette16 &tgt, uint8_t paletteID); // 1D strip uint16_t virtualLength(void) const; void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color - void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline - void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } // automatically inline + inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } + inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } + inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } void setPixelColor(float i, uint32_t c, bool aa = true); - void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } - void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } + inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } + inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } uint32_t getPixelColor(int i); // 1D support functions (some implement 2D as well) void blur(uint8_t); void fill(uint32_t c); void fade_out(uint8_t r); void fadeToBlackBy(uint8_t fadeBy); - void blendPixelColor(int n, uint32_t color, uint8_t blend); - void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } - void addPixelColor(int n, uint32_t color, bool fast = false); - void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } // automatically inline - void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } // automatically inline - void fadePixelColor(uint16_t n, uint8_t fade); - uint8_t get_random_wheel_index(uint8_t pos); + inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } + inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } + inline void addPixelColor(int n, uint32_t color, bool fast = false) { setPixelColor(n, color_add(getPixelColor(n), color, fast)); } + inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } + inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } + inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255); uint32_t color_wheel(uint8_t pos); // 2D matrix - uint16_t virtualWidth(void) const; - uint16_t virtualHeight(void) const; - uint16_t nrOfVStrips(void) const; + uint16_t virtualWidth(void) const; // segment width in virtual pixels (accounts for groupping and spacing) + uint16_t virtualHeight(void) const; // segment height in virtual pixels (accounts for groupping and spacing) + uint16_t nrOfVStrips(void) const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D - uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment (for leds[]) + uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color - void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } // automatically inline - void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } // automatically inline + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); - void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } - void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } + inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } + inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } uint32_t getPixelColorXY(uint16_t x, uint16_t y); // 2D support functions - void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend); - void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } - void addPixelColorXY(int x, int y, uint32_t color, bool fast = false); - void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } // automatically inline - void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } - void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade); + inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, fast)); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } + inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } + inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } void box_blur(uint16_t i, bool vertical, fract8 blur_amount); // 1D box blur (with weight) void blurRow(uint16_t row, fract8 blur_amount); void blurCol(uint16_t col, fract8 blur_amount); @@ -603,42 +623,43 @@ typedef struct Segment { void draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c); - void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0)); } // automatic inline - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0); - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0)); } // automatic inline + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0)); } // automatic inline + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0); + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline void wu_pixel(uint32_t x, uint32_t y, CRGB c); void blur1d(fract8 blur_amount); // blur all rows in 1 dimension - void blur2d(fract8 blur_amount) { blur(blur_amount); } - void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } + inline void blur2d(fract8 blur_amount) { blur(blur_amount); } + inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } void nscale8(uint8_t scale); #else - uint16_t XY(uint16_t x, uint16_t y) { return x; } - void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } - void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } - void setPixelColorXY(int x, int y, CRGB c) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } - void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } - void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } - void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } - uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(x); } - void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } - void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } - void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); } - void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); } - void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } - void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } - void box_blur(uint16_t i, bool vertical, fract8 blur_amount) {} - void blurRow(uint16_t row, fract8 blur_amount) {} - void blurCol(uint16_t col, fract8 blur_amount) {} - void moveX(int8_t delta, bool wrap = false) {} - void moveY(int8_t delta, bool wrap = false) {} - void move(uint8_t dir, uint8_t delta, bool wrap = false) {} - void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c) {} - void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {} - void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) {} - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color) {} - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {} - void wu_pixel(uint32_t x, uint32_t y, CRGB c) {} + inline uint16_t XY(uint16_t x, uint16_t y) { return x; } + inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } + inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } + inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } + inline uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(x); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); } + inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } + inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } + inline void box_blur(uint16_t i, bool vertical, fract8 blur_amount) {} + inline void blurRow(uint16_t row, fract8 blur_amount) {} + inline void blurCol(uint16_t col, fract8 blur_amount) {} + inline void moveX(int8_t delta, bool wrap = false) {} + inline void moveY(int8_t delta, bool wrap = false) {} + inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {} + inline void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c) {} + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {} + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) {} + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {} + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {} + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {} + inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {} #endif } segment; //static int segSize = sizeof(Segment); @@ -661,10 +682,7 @@ class WS2812FX { // 96 bytes WS2812FX() : paletteFade(0), paletteBlend(0), - milliampsPerLed(55), cctBlending(0), - ablMilliampsMax(ABL_MILLIAMPS_DEFAULT), - currentMilliamps(0), now(millis()), timebase(0), isMatrix(false), @@ -676,6 +694,7 @@ class WS2812FX { // 96 bytes _colors_t{0,0,0}, _virtualSegmentLength(0), // true private variables + _suspend(false), _length(DEFAULT_LED_COUNT), _brightness(DEFAULT_BRIGHTNESS), _transitionDur(750), @@ -692,7 +711,15 @@ class WS2812FX { // 96 bytes customMappingSize(0), _lastShow(0), _segment_index(0), - _mainSegment(0) + _mainSegment(0), + _queuedChangesSegId(255), + _qStart(0), + _qStop(0), + _qStartY(0), + _qStopY(0), + _qGrouping(0), + _qSpacing(0), + _qOffset(0) { WS2812FX::instance = this; _mode.reserve(_modeCount); // allocate memory to prevent initial fragmentation (does not increase size()) @@ -710,99 +737,96 @@ class WS2812FX { // 96 bytes panel.clear(); #endif customPalettes.clear(); - if (useLedsArray && Segment::_globalLeds) {free(Segment::_globalLeds); Segment::_globalLeds = nullptr;} // reset to nullptr, to avoid race conditions } static WS2812FX* getInstance(void) { return instance; } void #ifdef WLED_DEBUG - printSize(), + printSize(), // prints memory usage for strip components #endif - finalizeInit(), - service(void), - setMode(uint8_t segid, uint8_t m), - setColor(uint8_t slot, uint32_t c), - setCCT(uint16_t k), - setBrightness(uint8_t b, bool direct = false), - setRange(uint16_t i, uint16_t i2, uint32_t col), - setTransitionMode(bool t), - purgeSegments(bool force = false), + finalizeInit(), // initialises strip components + service(void), // executes effect functions when due and calls strip.show() + setMode(uint8_t segid, uint8_t m), // sets effect/mode for given segment (high level API) + setColor(uint8_t slot, uint32_t c), // sets color (in slot) for given segment (high level API) + setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K) + setBrightness(uint8_t b, bool direct = false), // sets strip brightness + setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay + purgeSegments(void), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(uint8_t n), - restartRuntime(), - resetSegments(), - makeAutoSegments(bool forceReset = false), - fixInvalidSegments(), - setPixelColor(int n, uint32_t c), - show(void), - setTargetFps(uint8_t fps); + resetSegments(), // marks all segments for reset + makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs + fixInvalidSegments(), // fixes incorrect segment configuration + setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c + show(void), // initiates LED output + setTargetFps(uint8_t fps), + addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name), // add effect to the list; defined in FX.cpp + setupEffectData(void); // add default effects to the list; defined in FX.cpp - void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } - void fill(uint32_t c) { for (int i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) - void addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp - void setupEffectData(void); // add default effects to the list; defined in FX.cpp - - // outsmart the compiler :) by correctly overloading - inline void setPixelColor(int n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } - inline void setPixelColor(int n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); } - inline void trigger(void) { _triggered = true; } // Forces the next frame to be computed on all active segments. - inline void setShowCallback(show_callback cb) { _callback = cb; } - inline void setTransition(uint16_t t) { _transitionDur = t; } - inline void appendSegment(const Segment &seg = Segment()) { _segments.push_back(seg); } + inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); } + inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } + inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } + inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } + inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); } + inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) + inline void trigger(void) { _triggered = true; } // Forces the next frame to be computed on all active segments. + inline void setShowCallback(show_callback cb) { _callback = cb; } + inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms) + inline void appendSegment(const Segment &seg = Segment()) { if (_segments.size() < getMaxSegments()) _segments.push_back(seg); } + inline void suspend(void) { _suspend = true; } // will suspend (and canacel) strip.service() execution + inline void resume(void) { _suspend = false; } // will resume strip.service() execution bool + paletteFade, checkSegmentAlignment(void), hasRGBWBus(void), hasCCTBus(void), // return true if the strip is being sent pixel updates isUpdating(void), - deserializeMap(uint8_t n=0), - useLedsArray = false; + deserializeMap(uint8_t n=0); - inline bool isServicing(void) { return _isServicing; } - inline bool hasWhiteChannel(void) {return _hasWhiteChannel;} - inline bool isOffRefreshRequired(void) {return _isOffRefreshRequired;} + inline bool isServicing(void) { return _isServicing; } // returns true if strip.service() is executing + inline bool hasWhiteChannel(void) { return _hasWhiteChannel; } // returns true if strip contains separate white chanel + inline bool isOffRefreshRequired(void) { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) + inline bool isSuspended(void) { return _suspend; } // returns true if strip.service() execution is suspended + inline bool needsUpdate(void) { return _triggered; } // returns true if strip received a trigger() request uint8_t - paletteFade, paletteBlend, - milliampsPerLed, cctBlending, 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; } - inline uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) - inline uint8_t getSegmentsNum(void) { return _segments.size(); } // returns currently present segments - inline uint8_t getCurrSegmentId(void) { return _segment_index; } - inline uint8_t getMainSegmentId(void) { return _mainSegment; } - inline uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT; } // will only return built-in palette count - inline uint8_t getTargetFps() { return _targetFps; } - inline uint8_t getModeCount() { return _modeCount; } + 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) + inline uint8_t getSegmentsNum(void) { return _segments.size(); } // returns currently present segments + inline uint8_t getCurrSegmentId(void) { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) + inline uint8_t getMainSegmentId(void) { return _mainSegment; } // returns main segment index + inline uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } + inline uint8_t getTargetFps() { return _targetFps; } // returns rough FPS value for las 2s interval + inline uint8_t getModeCount() { return _modeCount; } // returns number of registered modes/effects uint16_t - ablMilliampsMax, - currentMilliamps, getLengthPhysical(void), getLengthTotal(void), // will include virtual/nonexistent pixels in matrix - getFps(); + getFps(), + getMappedPixelIndex(uint16_t index); - inline uint16_t getFrameTime(void) { return _frametime; } - inline uint16_t getMinShowDelay(void) { return MIN_SHOW_DELAY; } - inline uint16_t getLength(void) { return _length; } // 2D matrix may have less pixels than W*H - inline uint16_t getTransition(void) { return _transitionDur; } + inline uint16_t getFrameTime(void) { return _frametime; } // returns amount of time a frame should take (in ms) + inline uint16_t getMinShowDelay(void) { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) + inline uint16_t getLength(void) { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) + inline uint16_t getTransition(void) { return _transitionDur; } // returns currently set transition time (in ms) uint32_t now, timebase, getPixelColor(uint16_t); - inline uint32_t getLastShow(void) { return _lastShow; } - inline uint32_t segColor(uint8_t i) { return _colors_t[i]; } + inline uint32_t getLastShow(void) { return _lastShow; } // returns millis() timestamp of last strip.show() call + inline uint32_t segColor(uint8_t i) { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition const char * getModeData(uint8_t id = 0) { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } @@ -811,9 +835,9 @@ class WS2812FX { // 96 bytes getModeDataSrc(void) { return &(_modeData[0]); } // vectors use arrays for underlying data Segment& getSegment(uint8_t id); - inline Segment& getFirstSelectedSeg(void) { return _segments[getFirstSelectedSegId()]; } - inline Segment& getMainSegment(void) { return _segments[getMainSegmentId()]; } - inline Segment* getSegments(void) { return &(_segments[0]); } + inline Segment& getFirstSelectedSeg(void) { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" + inline Segment& getMainSegment(void) { return _segments[getMainSegmentId()]; } // returns reference to main segment + inline Segment* getSegments(void) { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) // 2D support (panels) bool @@ -849,16 +873,14 @@ class WS2812FX { // 96 bytes std::vector panel; #endif - void - setUpMatrix(), - setPixelColorXY(int x, int y, uint32_t c); + void setUpMatrix(); // sets up automatic matrix ledmap from panel configuration // outsmart the compiler :) by correctly overloading - inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } // automatically inline - inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); } + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } - uint32_t - getPixelColorXY(uint16_t, uint16_t); + inline uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x);} // end 2D support @@ -875,6 +897,8 @@ class WS2812FX { // 96 bytes friend class Segment; private: + volatile bool _suspend; + uint16_t _length; uint8_t _brightness; uint16_t _transitionDur; @@ -900,13 +924,18 @@ class WS2812FX { // 96 bytes uint16_t* customMappingTable; uint16_t customMappingSize; - uint32_t _lastShow; + unsigned long _lastShow; uint8_t _segment_index; uint8_t _mainSegment; - + uint8_t _queuedChangesSegId; + uint16_t _qStart, _qStop, _qStartY, _qStopY; + uint8_t _qGrouping, _qSpacing; + uint16_t _qOffset; +/* void - estimateCurrentAndLimitBri(void); + setUpSegmentFromQueuedChanges(void); +*/ }; extern const char JSON_mode_names[]; diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 778a28f9a..fde05928e 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -36,14 +36,9 @@ // so matrix should disable regular ledmap processing void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D - // erase old ledmap, just in case. - if (customMappingTable != nullptr) delete[] customMappingTable; - customMappingTable = nullptr; - customMappingSize = 0; - // isMatrix is set in cfg.cpp or set.cpp if (isMatrix) { - // calculate width dynamically because it will have gaps + // calculate width dynamically because it may have gaps Segment::maxWidth = 1; Segment::maxHeight = 1; for (size_t i = 0; i < panel.size(); i++) { @@ -68,15 +63,18 @@ void WS2812FX::setUpMatrix() { return; } - customMappingTable = new uint16_t[Segment::maxWidth * Segment::maxHeight]; + customMappingSize = 0; // prevent use of mapping if anything goes wrong - if (customMappingTable != nullptr) { - customMappingSize = Segment::maxWidth * Segment::maxHeight; + if (customMappingTable) delete[] customMappingTable; + customMappingTable = new uint16_t[getLengthTotal()]; + + if (customMappingTable) { + customMappingSize = getLengthTotal(); // fill with empty in case we don't fill the entire matrix - for (size_t i = 0; i< customMappingSize; i++) { - customMappingTable[i] = (uint16_t)-1; - } + unsigned matrixSize = Segment::maxWidth * Segment::maxHeight; + for (unsigned i = 0; i(); + JsonArray map = pDoc->as(); gapSize = map.size(); - if (!map.isNull() && gapSize >= customMappingSize) { // not an empty map + if (!map.isNull() && gapSize >= matrixSize) { // not an empty map gapTable = new int8_t[gapSize]; if (gapTable) for (size_t i = 0; i < gapSize; i++) { gapTable[i] = constrain(map[i], -1, 1); @@ -134,14 +132,14 @@ void WS2812FX::setUpMatrix() { #ifdef WLED_DEBUG DEBUG_PRINT(F("Matrix ledmap:")); - for (uint16_t i=0; i= _length) return; - busses.setPixelColor(index, col); -} - -// returns RGBW values of pixel -uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) { -#ifndef WLED_DISABLE_2D - uint16_t index = (y * Segment::maxWidth + x); -#else - uint16_t index = x; -#endif - if (index < customMappingSize) index = customMappingTable[index]; - if (index >= _length) return 0; - return busses.getPixelColor(index); -} /////////////////////////////////////////////////////////// // Segment:: routines @@ -188,22 +161,19 @@ uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t /*IRAM_ATTR*/ Segment::XY(uint16_t x, uint16_t y) { - uint16_t width = virtualWidth(); // segment width in logical pixels - uint16_t height = virtualHeight(); // segment height in logical pixels - if (width == 0) return 0; // softhack007 avoid div/0 - if (height == 0) return (x%width); // softhack007 avoid div/0 - return (x%width) + (y%height) * width; +uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) +{ + uint16_t width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + uint16_t height = virtualHeight(); // segment height in logical pixels (is always >= 1) + return isActive() ? (x%width) + (y%height) * width : 0; } -void /*IRAM_ATTR*/ Segment::setPixelColorXY(int x, int y, uint32_t col) +void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) { - if (Segment::maxHeight==1) return; // not a matrix set-up + if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - if (leds) leds[XY(x,y)] = col; - - uint8_t _bri_t = currentBri(on ? opacity : 0); + uint8_t _bri_t = currentBri(); if (_bri_t < 255) { byte r = scale8(R(col), _bri_t); byte g = scale8(G(col), _bri_t); @@ -220,23 +190,29 @@ void /*IRAM_ATTR*/ Segment::setPixelColorXY(int x, int y, uint32_t col) y *= groupLength(); // expand to physical pixels if (x >= width() || y >= height()) return; // if pixel would fall out of segment just exit + uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally uint16_t xX = (x+g), yY = (y+j); if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end - strip.setPixelColorXY(start + xX, startY + yY, col); +#ifndef WLED_DISABLE_MODE_BLEND + // if blending modes, blend with underlying pixel + if (_modeBlend) tmpCol = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); +#endif + + strip.setPixelColorXY(start + xX, startY + yY, tmpCol); if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); - else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); + if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); + else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); } if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); - else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); + if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); + else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, col); + strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, tmpCol); } } } @@ -245,7 +221,7 @@ void /*IRAM_ATTR*/ Segment::setPixelColorXY(int x, int y, uint32_t col) // anti-aliased version of setPixelColorXY() void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) { - if (Segment::maxHeight==1) return; // not a matrix set-up + if (!isActive()) return; // not active if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized const uint16_t cols = virtualWidth(); @@ -287,9 +263,9 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) } // returns RGBW values of pixel -uint32_t Segment::getPixelColorXY(uint16_t x, uint16_t y) { - int i = XY(x,y); - if (leds) return RGBW32(leds[i].r, leds[i].g, leds[i].b, 0); +uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) { + if (!isActive()) return 0; // not active + if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed @@ -299,38 +275,9 @@ uint32_t Segment::getPixelColorXY(uint16_t x, uint16_t y) { return strip.getPixelColorXY(start + x, startY + y); } -// Blends the specified color with the existing pixel color. -void Segment::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { - setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); -} - -// Adds the specified color with the existing pixel color perserving color balance. -void Segment::addPixelColorXY(int x, int y, uint32_t color, bool fast) { - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - uint32_t col = getPixelColorXY(x,y); - uint8_t r = R(col); - uint8_t g = G(col); - uint8_t b = B(col); - uint8_t w = W(col); - if (fast) { - r = qadd8(r, R(color)); - g = qadd8(g, G(color)); - b = qadd8(b, B(color)); - w = qadd8(w, W(color)); - col = RGBW32(r,g,b,w); - } else { - col = color_add(col, color); - } - setPixelColorXY(x, y, col); -} - -void Segment::fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { - CRGB pix = CRGB(getPixelColorXY(x,y)).nscale8_video(fade); - setPixelColorXY(x, y, pix); -} - // blurRow: perform a blur on a row of a rectangular matrix void Segment::blurRow(uint16_t row, fract8 blur_amount) { + if (!isActive() || blur_amount == 0) return; // not active const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); @@ -339,9 +286,9 @@ void Segment::blurRow(uint16_t row, fract8 blur_amount) { uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; CRGB carryover = CRGB::Black; - for (uint_fast16_t x = 0; x < cols; x++) { + for (unsigned x = 0; x < cols; x++) { CRGB cur = getPixelColorXY(x, row); - uint32_t before = uint32_t(cur); // remember color before blur + CRGB before = cur; // remember color before blur CRGB part = cur; part.nscale8(seep); cur.nscale8(keep); @@ -350,7 +297,7 @@ void Segment::blurRow(uint16_t row, fract8 blur_amount) { CRGB prev = CRGB(getPixelColorXY(x-1, row)) + part; setPixelColorXY(x-1, row, prev); } - if (before != uint32_t(cur)) // optimization: only set pixel if color has changed + if (before != cur) // optimization: only set pixel if color has changed setPixelColorXY(x, row, cur); carryover = part; } @@ -358,6 +305,7 @@ void Segment::blurRow(uint16_t row, fract8 blur_amount) { // blurCol: perform a blur on a column of a rectangular matrix void Segment::blurCol(uint16_t col, fract8 blur_amount) { + if (!isActive() || blur_amount == 0) return; // not active const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); @@ -366,10 +314,10 @@ void Segment::blurCol(uint16_t col, fract8 blur_amount) { uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; CRGB carryover = CRGB::Black; - for (uint_fast16_t y = 0; y < rows; y++) { + for (unsigned y = 0; y < rows; y++) { CRGB cur = getPixelColorXY(col, y); CRGB part = cur; - uint32_t before = uint32_t(cur); // remember color before blur + CRGB before = cur; // remember color before blur part.nscale8(seep); cur.nscale8(keep); cur += carryover; @@ -377,7 +325,7 @@ void Segment::blurCol(uint16_t col, fract8 blur_amount) { CRGB prev = CRGB(getPixelColorXY(col, y-1)) + part; setPixelColorXY(col, y-1, prev); } - if (before != uint32_t(cur)) // optimization: only set pixel if color has changed + if (before != cur) // optimization: only set pixel if color has changed setPixelColorXY(col, y, cur); carryover = part; } @@ -385,6 +333,7 @@ void Segment::blurCol(uint16_t col, fract8 blur_amount) { // 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur]) void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { + if (!isActive() || blur_amount == 0) return; // not active const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); const uint16_t dim1 = vertical ? rows : cols; @@ -394,7 +343,7 @@ void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { const float keep = 3.f - 2.f*seep; // 1D box blur CRGB tmp[dim1]; - for (uint16_t j = 0; j < dim1; j++) { + for (int j = 0; j < dim1; j++) { uint16_t x = vertical ? i : j; uint16_t y = vertical ? j : i; int16_t xp = vertical ? x : x-1; // "signed" to prevent underflow @@ -410,7 +359,7 @@ void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { b = (curr.b*keep + (prev.b + next.b)*seep) / 3; tmp[j] = CRGB(r,g,b); } - for (uint16_t j = 0; j < dim1; j++) { + for (int j = 0; j < dim1; j++) { uint16_t x = vertical ? i : j; uint16_t y = vertical ? j : i; setPixelColorXY(x, y, tmp[j]); @@ -433,10 +382,11 @@ void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { void Segment::blur1d(fract8 blur_amount) { const uint16_t rows = virtualHeight(); - for (uint16_t y = 0; y < rows; y++) blurRow(y, blur_amount); + for (unsigned y = 0; y < rows; y++) blurRow(y, blur_amount); } void Segment::moveX(int8_t delta, bool wrap) { + if (!isActive()) return; // not active const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); if (!delta || abs(delta) >= cols) return; @@ -454,6 +404,7 @@ void Segment::moveX(int8_t delta, bool wrap) { } void Segment::moveY(int8_t delta, bool wrap) { + if (!isActive()) return; // not active const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); if (!delta || abs(delta) >= rows) return; @@ -489,6 +440,7 @@ void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { } void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { + if (!isActive() || radius == 0) return; // not active // Bresenham’s Algorithm int d = 3 - (2*radius); int y = radius, x = 0; @@ -513,6 +465,7 @@ void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { + if (!isActive() || radius == 0) return; // not active const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); for (int16_t y = -radius; y <= radius; y++) { @@ -526,15 +479,17 @@ void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { } void Segment::nscale8(uint8_t scale) { + if (!isActive()) return; // not active const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); - for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { setPixelColorXY(x, y, CRGB(getPixelColorXY(x, y)).nscale8(scale)); } } //line function void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) { + if (!isActive()) return; // not active const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; @@ -558,7 +513,8 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 // draws a raster font character on canvas // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM -void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2) { +void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate) { + if (!isActive()) return; // not active if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported chr -= 32; // align with font table entries const uint16_t cols = virtualWidth(); @@ -570,9 +526,6 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, //if (w<5 || w>6 || h!=8) return; for (int i = 0; i= rows) break; // drawing off-screen uint8_t bits = 0; switch (font) { case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); break; // 5x8 font @@ -584,8 +537,16 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, } col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND); for (int j = 0; j= 0 || x0 < cols) && ((bits>>(j+(8-w))) & 0x01)) { // bit set & drawing on-screen + int x0, y0; + switch (rotate) { + case -1: x0 = x + (h-1) - i; y0 = y + (w-1) - j; break; // -90 deg + case -2: + case 2: x0 = x + j; y0 = y + (h-1) - i; break; // 180 deg + case 1: x0 = x + i; y0 = y + j; break; // +90 deg + default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation + } + if (x0 < 0 || x0 >= cols || y0 < 0 || y0 >= rows) continue; // drawing off-screen + if (((bits>>(j+(8-w))) & 0x01)) { // bit set setPixelColorXY(x0, y0, col); } } @@ -594,6 +555,7 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, #define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8)) void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu + if (!isActive()) return; // not active // extract the fractional parts and derive their inverses uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy; // calculate the intensities for each affected pixel diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 4224f2545..c9dd082ea 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -74,103 +74,115 @@ // Segment class implementation /////////////////////////////////////////////////////////////////////////////// uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] -CRGB *Segment::_globalLeds = nullptr; uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; uint16_t Segment::maxHeight = 1; +CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); +CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); +uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment +uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only) + +#ifndef WLED_DISABLE_MODE_BLEND +bool Segment::_modeBlend = false; +#endif + // copy constructor Segment::Segment(const Segment &orig) { - //DEBUG_PRINTLN(F("-- Copy segment constructor --")); + //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; data = nullptr; _dataLen = 0; - _t = nullptr; - if (leds && !Segment::_globalLeds) leds = nullptr; if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } - if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); } - if (orig.leds && !Segment::_globalLeds && length() > 0) { leds = (CRGB*)malloc(sizeof(CRGB)*length()); if (leds) memcpy(leds, orig.leds, sizeof(CRGB)*length()); } } // move constructor Segment::Segment(Segment &&orig) noexcept { - //DEBUG_PRINTLN(F("-- Move segment constructor --")); + //DEBUG_PRINTF_P(PSTR("-- Move segment constructor: %p -> %p\n"), &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); - orig.leds = nullptr; + orig._t = nullptr; // old segment cannot be in transition any more orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; - orig._t = nullptr; } // copy assignment Segment& Segment::operator= (const Segment &orig) { - //DEBUG_PRINTLN(F("-- Copying segment --")); + //DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this); if (this != &orig) { // clean destination - if (name) delete[] name; - if (_t) delete _t; - if (leds && !Segment::_globalLeds) {free(leds); leds=nullptr;} + if (name) { delete[] name; name = nullptr; } + stopTransition(); deallocateData(); // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); // erase pointers to allocated data - name = nullptr; data = nullptr; _dataLen = 0; - _t = nullptr; - if (!Segment::_globalLeds) leds = nullptr; // copy source data if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } - if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); } - if (orig.leds && !Segment::_globalLeds && length() > 0) { leds = (CRGB*)malloc(sizeof(CRGB)*length()); if (leds) memcpy(leds, orig.leds, sizeof(CRGB)*length()); } } return *this; } // move assignment Segment& Segment::operator= (Segment &&orig) noexcept { - //DEBUG_PRINTLN(F("-- Moving segment --")); + //DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this); if (this != &orig) { - if (name) delete[] name; // free old name + if (name) { delete[] name; name = nullptr; } // free old name + stopTransition(); deallocateData(); // free old runtime data - if (_t) delete _t; - if (leds && !Segment::_globalLeds) {free(leds); leds=nullptr;} memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; - orig._t = nullptr; - orig.leds = nullptr; + orig._t = nullptr; // old segment cannot be in transition } return *this; } -bool Segment::allocateData(size_t len) { - if (data && _dataLen == len) return true; //already allocated - deallocateData(); - if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) return false; //not enough memory +// allocates effect data buffer on heap and initialises (erases) it +bool IRAM_ATTR Segment::allocateData(size_t len) { + if (len == 0) return false; // nothing to do + if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation) + if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation + return true; + } + //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_P(PSTR("%d/%d !!!\n"), len, Segment::getUsedSegmentData()); + errorFlag = ERR_NORAM; + return false; + } // do not use SPI RAM on ESP32 since it is slow - //#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) - //if (psramFound()) - // data = (byte*) ps_malloc(len); - //else - //#endif - data = (byte*) malloc(len); - if (!data) return false; //allocation failed + data = (byte*)calloc(len, sizeof(byte)); + if (!data) { DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); return false; } // allocation failed Segment::addUsedSegmentData(len); + //DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data); _dataLen = len; - memset(data, 0, len); return true; } -void Segment::deallocateData() { - if (!data) return; - free(data); +void IRAM_ATTR Segment::deallocateData() { + if (!data) { _dataLen = 0; return; } + //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(PSTR("(%p): "), this); + DEBUG_PRINT(F("inconsistent UsedSegmentData ")); + DEBUG_PRINTF_P(PSTR("(%d/%d)"), _dataLen, Segment::getUsedSegmentData()); + DEBUG_PRINTLN(F(", cowardly refusing to free nothing.")); + } data = nullptr; - Segment::addUsedSegmentData(-_dataLen); + Segment::addUsedSegmentData(_dataLen <= Segment::getUsedSegmentData() ? -_dataLen : -Segment::getUsedSegmentData()); _dataLen = 0; } @@ -182,38 +194,14 @@ void Segment::deallocateData() { * may free that data buffer. */ void Segment::resetIfRequired() { - if (reset) { - if (leds && !Segment::_globalLeds) { free(leds); leds = nullptr; } - //if (transitional && _t) { transitional = false; delete _t; _t = nullptr; } - deallocateData(); - next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; - reset = false; // setOption(SEG_OPTION_RESET, false); - } + if (!reset) return; + //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; } -void Segment::setUpLeds() { - // deallocation happens in resetIfRequired() as it is called when segment changes or in destructor - if (Segment::_globalLeds) - #ifndef WLED_DISABLE_2D - leds = &Segment::_globalLeds[start + startY*Segment::maxWidth]; - #else - leds = &Segment::_globalLeds[start]; - #endif - else if (leds == nullptr && length() > 0) { //softhack007 quickfix - avoid malloc(0) which is undefined behaviour (should not happen, but i've seen it) - //#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) - //if (psramFound()) - // leds = (CRGB*)ps_malloc(sizeof(CRGB)*length()); // softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards - //else - //#endif - leds = (CRGB*)malloc(sizeof(CRGB)*length()); - } -} - -CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { - static unsigned long _lastPaletteChange = 0; // perhaps it should be per segment - static CRGBPalette16 randomPalette = CRGBPalette16(DEFAULT_COLOR); - static CRGBPalette16 prevRandomPalette = CRGBPalette16(CRGB(BLACK)); - byte tcp[72]; +CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; //default palette. Differs depending on effect @@ -233,29 +221,9 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { switch (pal) { case 0: //default palette. Exceptions for specific effects above targetPalette = PartyColors_p; break; - case 1: {//periodically replace palette with a random one. Transition palette change in 500ms - uint32_t timeSinceLastChange = millis() - _lastPaletteChange; - if (timeSinceLastChange > randomPaletteChangeTime * 1000U) { - prevRandomPalette = randomPalette; - randomPalette = 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(); - timeSinceLastChange = 0; - } - if (timeSinceLastChange <= 250) { - targetPalette = prevRandomPalette; - // there needs to be 255 palette blends (48) for full blend but that is too resource intensive - // so 128 is a compromise (we need to perform full blend of the two palettes as each segment can have random - // palette selected but only 2 static palettes are used) - size_t noOfBlends = ((128U * timeSinceLastChange) / 250U); - for (size_t i=0; i245) { targetPalette = strip.customPalettes[255-pal]; // we checked bounds above } else { + byte tcp[72]; memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72); targetPalette.loadDynamicGradientPalette(tcp); } @@ -305,84 +274,206 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { } void Segment::startTransition(uint16_t dur) { - if (!dur) { - transitional = false; - if (_t) { - delete _t; - _t = nullptr; - } + if (dur == 0) { + if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransition() return; } - if (transitional && _t) return; // already in transition no need to store anything + if (isInTransition()) return; // already in transition no need to store anything // starting a transition has to occur before change so we get current values 1st _t = new Transition(dur); // no previous transition running if (!_t) return; // failed to allocate data - CRGBPalette16 _palT = CRGBPalette16(DEFAULT_COLOR); loadPalette(_palT, palette); - _t->_briT = on ? opacity : 0; - _t->_cctT = cct; - _t->_palT = _palT; - _t->_modeP = mode; - for (size_t i=0; i_colorT[i] = colors[i]; - transitional = true; // setOption(SEG_OPTION_TRANSITIONAL, true); -} - -// transition progression between 0-65535 -uint16_t Segment::progress() { - if (!transitional || !_t) return 0xFFFFU; - uint32_t timeNow = millis(); - if (timeNow - _t->_start > _t->_dur || _t->_dur == 0) return 0xFFFFU; - return (timeNow - _t->_start) * 0xFFFFU / _t->_dur; -} - -uint8_t Segment::currentBri(uint8_t briNew, bool useCct) { - uint32_t prog = progress(); - if (transitional && _t && prog < 0xFFFFU) { - if (useCct) return ((briNew * prog) + _t->_cctT * (0xFFFFU - prog)) >> 16; - else return ((briNew * prog) + _t->_briT * (0xFFFFU - prog)) >> 16; + //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t); + loadPalette(_t->_palT, palette); + _t->_briT = on ? opacity : 0; + _t->_cctT = cct; +#ifndef WLED_DISABLE_MODE_BLEND + if (modeBlending) { + swapSegenv(_t->_segT); + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_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; + } + } } else { - return briNew; + for (size_t i=0; i_segT._colorT[i] = colors[i]; + } +#else + for (size_t i=0; i_colorT[i] = colors[i]; +#endif +} + +void Segment::stopTransition() { + if (isInTransition()) { + //DEBUG_PRINTF_P(PSTR("-- Stopping transition: %p\n"), this); + #ifndef WLED_DISABLE_MODE_BLEND + if (_t->_segT._dataT && _t->_segT._dataLenT > 0) { + //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; + } + #endif + delete _t; + _t = nullptr; } } -uint8_t Segment::currentMode(uint8_t newMode) { - return (progress()>32767U) ? newMode : _t->_modeP; // change effect in the middle of transition +void Segment::handleTransition() { + uint16_t _progress = progress(); + if (_progress == 0xFFFFU) stopTransition(); } -uint32_t Segment::currentColor(uint8_t slot, uint32_t colorNew) { - return transitional && _t ? color_blend(_t->_colorT[slot], colorNew, progress(), true) : colorNew; +// transition progression between 0-65535 +uint16_t IRAM_ATTR Segment::progress() { + if (isInTransition()) { + unsigned long timeNow = millis(); + if (_t->_dur > 0 && timeNow - _t->_start < _t->_dur) return (timeNow - _t->_start) * 0xFFFFU / _t->_dur; + } + return 0xFFFFU; } -CRGBPalette16 &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { +#ifndef WLED_DISABLE_MODE_BLEND +void Segment::swapSegenv(tmpsegd_t &tmpSeg) { + //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_segT)) { + // swap SEGENV with transitional data + options = _t->_segT._optionsT; + for (size_t i=0; i_segT._colorT[i]; + speed = _t->_segT._speedT; + intensity = _t->_segT._intensityT; + custom1 = _t->_segT._custom1T; + custom2 = _t->_segT._custom2T; + custom3 = _t->_segT._custom3T; + check1 = _t->_segT._check1T; + check2 = _t->_segT._check2T; + check3 = _t->_segT._check3T; + aux0 = _t->_segT._aux0T; + aux1 = _t->_segT._aux1T; + step = _t->_segT._stepT; + call = _t->_segT._callT; + data = _t->_segT._dataT; + _dataLen = _t->_segT._dataLenT; + } +} + +void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { + //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_P(PSTR("--- data re-allocated: (%p) %p -> %p\n"), this, _t->_segT._dataT, data); + _t->_segT._dataT = data; + _t->_segT._dataLenT = _dataLen; + } + options = tmpSeg._optionsT; + for (size_t i=0; i_cctT : _t->_briT) * (0xFFFFU - prog); + return curBri / 0xFFFFU; + } + return (useCct ? cct : (on ? opacity : 0)); +} + +uint8_t IRAM_ATTR Segment::currentMode() { +#ifndef WLED_DISABLE_MODE_BLEND + uint16_t prog = progress(); + if (modeBlending && prog < 0xFFFFU) return _t->_modeT; +#endif + return mode; +} + +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 + return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot]; +#endif +} + +CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { loadPalette(targetPalette, pal); - if (transitional && _t && progress() < 0xFFFFU) { + uint16_t prog = progress(); + if (strip.paletteFade && prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms - uint32_t timeMS = millis() - _t->_start; - uint16_t noOfBlends = (255U * timeMS / _t->_dur) - _t->_prevPaletteBlends; + uint16_t noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; for (int i=0; i_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, targetPalette, 48); targetPalette = _t->_palT; // copy transitioning/temporary palette } return targetPalette; } -void Segment::handleTransition() { - if (!transitional) return; - uint16_t _progress = progress(); - if (_progress == 0xFFFFU) transitional = false; // finish transitioning segment - if (_t) { // thanks to @nXm AKA https://github.com/NMeirer - if (_progress >= 32767U && _t->_modeP != mode) markForReset(); - if (_progress == 0xFFFFU) { - delete _t; - _t = nullptr; - } +// relies on WS2812FX::service() to call it for each frame +void Segment::handleRandomPalette() { + // is it time to generate a new palette? + if ((uint16_t)((uint16_t)(millis() / 1000U) - _lastPaletteChange) > randomPaletteChangeTime){ + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = (uint16_t)(millis() / 1000U); + _lastPaletteBlend = (uint16_t)((uint16_t)millis() - 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 ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update + _lastPaletteBlend = (uint16_t)millis(); + } + nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } +// segId is given when called from network callback, changes are queued if that segment is currently in its effect function void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { - //return if neither bounds nor grouping have changed + // return if neither bounds nor grouping have changed bool boundsUnchanged = (start == i1 && stop == i2); #ifndef WLED_DISABLE_2D if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D @@ -391,10 +482,28 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t && (!grp || (grouping == grp && spacing == spc)) && (ofs == UINT16_MAX || ofs == offset)) return; - if (stop) fill(BLACK); //turn old segment range off + stateChanged = true; // send UDP/WS broadcast + + if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing) + if (grp) { // prevent assignment of 0 + grouping = grp; + spacing = spc; + } else { + grouping = 1; + spacing = 0; + } + if (ofs < UINT16_MAX) offset = ofs; + + DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); + DEBUG_PRINT(','); DEBUG_PRINT(i2); + DEBUG_PRINT(F(" -> ")); DEBUG_PRINT(i1Y); + DEBUG_PRINT(','); DEBUG_PRINTLN(i2Y); + markForReset(); + if (boundsUnchanged) return; + + // apply change immediately if (i2 <= i1) { //disable segment stop = 0; - markForReset(); return; } if (i1 < Segment::maxWidth || (i1 >= Segment::maxWidth*Segment::maxHeight && i1 < strip.getLengthTotal())) start = i1; // Segment::maxWidth equals strip.getLengthTotal() for 1D @@ -407,13 +516,12 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t stopY = i2Y > Segment::maxHeight ? Segment::maxHeight : MAX(1,i2Y); } #endif - if (grp) { - grouping = grp; - spacing = spc; + // safety check + if (start >= stop || startY >= stopY) { + stop = 0; + return; } - if (ofs < UINT16_MAX) offset = ofs; - markForReset(); - if (!boundsUnchanged) refreshLightCapabilities(); + refreshLightCapabilities(); } @@ -453,38 +561,40 @@ void Segment::setOption(uint8_t n, bool val) { if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change if (val) options |= 0x01 << n; else options &= ~(0x01 << n); - if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET || n == SEG_OPTION_TRANSITIONAL)) stateChanged = true; // send UDP/WS broadcast + if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast } void Segment::setMode(uint8_t fx, bool loadDefaults) { + // skip reserved + while (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4) == 0) fx++; + if (fx >= strip.getModeCount()) fx = 0; // set solid mode // if we have a valid mode & is not reserved - if (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4)) { - if (fx != mode) { - startTransition(strip.getTransition()); // set effect transitions - //markForReset(); // transition will handle this - mode = fx; - - // load default values from effect string - if (loadDefaults) { - int16_t sOpt; - sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED; - sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY; - sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1; - sOpt = extractModeDefaults(fx, "c2"); custom2 = (sOpt >= 0) ? sOpt : DEFAULT_C2; - sOpt = extractModeDefaults(fx, "c3"); custom3 = (sOpt >= 0) ? sOpt : DEFAULT_C3; - sOpt = extractModeDefaults(fx, "o1"); check1 = (sOpt >= 0) ? (bool)sOpt : false; - sOpt = extractModeDefaults(fx, "o2"); check2 = (sOpt >= 0) ? (bool)sOpt : false; - sOpt = extractModeDefaults(fx, "o3"); check3 = (sOpt >= 0) ? (bool)sOpt : false; - sOpt = extractModeDefaults(fx, "m12"); if (sOpt >= 0) map1D2D = constrain(sOpt, 0, 7); - sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) soundSim = constrain(sOpt, 0, 1); - sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) reverse = (bool)sOpt; - sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt; - sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0); - } - stateChanged = true; // send UDP/WS broadcast + if (fx != mode) { +#ifndef WLED_DISABLE_MODE_BLEND + if (modeBlending) startTransition(strip.getTransition()); // set effect transitions +#endif + mode = fx; + // load default values from effect string + if (loadDefaults) { + int16_t sOpt; + sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED; + sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY; + sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1; + sOpt = extractModeDefaults(fx, "c2"); custom2 = (sOpt >= 0) ? sOpt : DEFAULT_C2; + sOpt = extractModeDefaults(fx, "c3"); custom3 = (sOpt >= 0) ? sOpt : DEFAULT_C3; + sOpt = extractModeDefaults(fx, "o1"); check1 = (sOpt >= 0) ? (bool)sOpt : false; + sOpt = extractModeDefaults(fx, "o2"); check2 = (sOpt >= 0) ? (bool)sOpt : false; + sOpt = extractModeDefaults(fx, "o3"); check3 = (sOpt >= 0) ? (bool)sOpt : false; + sOpt = extractModeDefaults(fx, "m12"); if (sOpt >= 0) map1D2D = constrain(sOpt, 0, 7); else map1D2D = M12_Pixels; // reset mapping if not defined (2D FX may not work) + sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) soundSim = constrain(sOpt, 0, 3); + sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) reverse = (bool)sOpt; + sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt; + sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0); } + markForReset(); + stateChanged = true; // send UDP/WS broadcast } } @@ -499,21 +609,21 @@ void Segment::setPalette(uint8_t pal) { } // 2D matrix -uint16_t Segment::virtualWidth() const { +uint16_t IRAM_ATTR Segment::virtualWidth() const { uint16_t groupLen = groupLength(); uint16_t vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED return vWidth; } -uint16_t Segment::virtualHeight() const { +uint16_t IRAM_ATTR Segment::virtualHeight() const { uint16_t groupLen = groupLength(); uint16_t vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED return vHeight; } -uint16_t Segment::nrOfVStrips() const { +uint16_t IRAM_ATTR Segment::nrOfVStrips() const { uint16_t vLen = 1; #ifndef WLED_DISABLE_2D if (is2D()) { @@ -528,7 +638,7 @@ uint16_t Segment::nrOfVStrips() const { } // 1D strip -uint16_t Segment::virtualLength() const { +uint16_t IRAM_ATTR Segment::virtualLength() const { #ifndef WLED_DISABLE_2D if (is2D()) { uint16_t vW = virtualWidth(); @@ -546,8 +656,7 @@ uint16_t Segment::virtualLength() const { return vLen; } #endif - uint16_t groupLen = groupLength(); - if (groupLen < 1) groupLen = 1; // prevent division by zero - better safe than sorry ... + uint16_t groupLen = groupLength(); // is always >= 1 uint16_t vLength = (length() + groupLen - 1) / groupLen; if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED return vLength; @@ -555,6 +664,7 @@ uint16_t Segment::virtualLength() const { void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) { + if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) #endif @@ -622,10 +732,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } #endif - if (leds) leds[i] = col; - uint16_t len = length(); - uint8_t _bri_t = currentBri(on ? opacity : 0); + uint8_t _bri_t = currentBri(); if (_bri_t < 255) { byte r = scale8(R(col), _bri_t); byte g = scale8(G(col), _bri_t); @@ -645,19 +753,26 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } i += start; // starting pixel in a group + uint32_t tmpCol = col; // set all the pixels in the group for (int j = 0; j < grouping; j++) { - uint16_t indexSet = i + ((reverse) ? -j : j); + unsigned indexSet = i + ((reverse) ? -j : j); if (indexSet >= start && indexSet < stop) { if (mirror) { //set the corresponding mirrored pixel - uint16_t indexMir = stop - indexSet + start - 1; + unsigned indexMir = stop - indexSet + start - 1; indexMir += offset; // offset/phase if (indexMir >= stop) indexMir -= len; // wrap - strip.setPixelColor(indexMir, col); +#ifndef WLED_DISABLE_MODE_BLEND + if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); +#endif + strip.setPixelColor(indexMir, tmpCol); } indexSet += offset; // offset/phase if (indexSet >= stop) indexSet -= len; // wrap - strip.setPixelColor(indexSet, col); +#ifndef WLED_DISABLE_MODE_BLEND + if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); +#endif + strip.setPixelColor(indexSet, tmpCol); } } } @@ -665,6 +780,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) // anti-aliased normalized version of setPixelColor() void Segment::setPixelColor(float i, uint32_t col, bool aa) { + if (!isActive()) return; // not active int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows) i -= int(i); @@ -694,8 +810,9 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) } } -uint32_t Segment::getPixelColor(int i) +uint32_t IRAM_ATTR Segment::getPixelColor(int i) { + if (!isActive()) return 0; // not active #ifndef WLED_DISABLE_2D int vStrip = i>>16; #endif @@ -723,8 +840,6 @@ uint32_t Segment::getPixelColor(int i) } #endif - if (leds) return RGBW32(leds[i].r, leds[i].g, leds[i].b, 0); - if (reverse) i = virtualLength() - i - 1; i *= groupLength(); i += start; @@ -752,10 +867,11 @@ uint8_t Segment::differs(Segment& b) const { if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; - //bit pattern: (msb first) set:2, sound:1, mapping:3, transposed, mirrorY, reverseY, [transitional, reset,] paused, mirrored, on, reverse, [selected] - if ((options & 0b1111111110011110U) != (b.options & 0b1111111110011110U)) d |= SEG_DIFFERS_OPT; + //bit pattern: (msb first) + // set:2, sound:2, mapping:3, transposed, mirrorY, reverseY, [reset,] paused, mirrored, on, reverse, [selected] + if ((options & 0b1111111111011110U) != (b.options & 0b1111111111011110U)) d |= SEG_DIFFERS_OPT; if ((options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL; - for (uint8_t i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; + for (unsigned i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; return d; } @@ -765,11 +881,15 @@ void Segment::refreshLightCapabilities() { uint16_t segStartIdx = 0xFFFFU; uint16_t segStopIdx = 0; + if (!isActive()) { + _capabilities = 0; + return; + } + if (start < Segment::maxWidth * Segment::maxHeight) { // we are withing 2D matrix (includes 1D segments) for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) { - uint16_t index = x + Segment::maxWidth * y; - if (index < strip.customMappingSize) index = strip.customMappingTable[index]; // convert logical address to physical + uint16_t index = strip.getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical if (index < 0xFFFFU) { if (segStartIdx > index) segStartIdx = index; if (segStopIdx < index) segStopIdx = index; @@ -782,8 +902,8 @@ void Segment::refreshLightCapabilities() { segStopIdx = stop; } - for (uint8_t b = 0; b < busses.getNumBusses(); b++) { - Bus *bus = busses.getBus(b); + for (unsigned b = 0; b < BusManager::getNumBusses(); b++) { + Bus *bus = BusManager::getBus(b); if (bus == nullptr || bus->getLength()==0) break; if (!bus->isOk()) continue; if (bus->getStart() >= segStopIdx) continue; @@ -809,52 +929,25 @@ void Segment::refreshLightCapabilities() { * Fills segment with color */ void Segment::fill(uint32_t c) { + if (!isActive()) return; // not active const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); const uint16_t rows = virtualHeight(); // will be 1 for 1D - for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { if (is2D()) setPixelColorXY(x, y, c); else setPixelColor(x, c); } } -// Blends the specified color with the existing pixel color. -void Segment::blendPixelColor(int n, uint32_t color, uint8_t blend) { - setPixelColor(n, color_blend(getPixelColor(n), color, blend)); -} - -// Adds the specified color with the existing pixel color perserving color balance. -void Segment::addPixelColor(int n, uint32_t color, bool fast) { - uint32_t col = getPixelColor(n); - uint8_t r = R(col); - uint8_t g = G(col); - uint8_t b = B(col); - uint8_t w = W(col); - if (fast) { - r = qadd8(r, R(color)); - g = qadd8(g, G(color)); - b = qadd8(b, B(color)); - w = qadd8(w, W(color)); - col = RGBW32(r,g,b,w); - } else { - col = color_add(col, color); - } - setPixelColor(n, col); -} - -void Segment::fadePixelColor(uint16_t n, uint8_t fade) { - CRGB pix = CRGB(getPixelColor(n)).nscale8_video(fade); - setPixelColor(n, pix); -} - /* * fade out function, higher rate = quicker fade */ void Segment::fade_out(uint8_t rate) { + if (!isActive()) return; // not active const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); const uint16_t rows = virtualHeight(); // will be 1 for 1D rate = (255-rate) >> 1; - float mappedRate = float(rate) +1.1; + float mappedRate = float(rate) +1.1f; uint32_t color = colors[1]; // SEGCOLOR(1); // target color int w2 = W(color); @@ -862,7 +955,7 @@ void Segment::fade_out(uint8_t rate) { int g2 = G(color); int b2 = B(color); - for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); int w1 = W(color); int r1 = R(color); @@ -887,53 +980,44 @@ void Segment::fade_out(uint8_t rate) { // fades all pixels to black using nscale8() void Segment::fadeToBlackBy(uint8_t fadeBy) { - if (fadeBy == 0) return; // optimization - no scaling to apply + if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); const uint16_t rows = virtualHeight(); // will be 1 for 1D - for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { - if (is2D()) setPixelColorXY(x, y, CRGB(getPixelColorXY(x,y)).nscale8(255-fadeBy)); - else setPixelColor(x, CRGB(getPixelColor(x)).nscale8(255-fadeBy)); + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { + if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy)); + else setPixelColor(x, color_fade(getPixelColor(x), 255-fadeBy)); } } /* * blurs segment content, source: FastLED colorutils.cpp */ -void Segment::blur(uint8_t blur_amount) -{ - if (blur_amount == 0) return; // optimization: 0 means "don't blur" +void Segment::blur(uint8_t blur_amount) { + if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur" #ifndef WLED_DISABLE_2D if (is2D()) { // compatibility with 2D - const uint_fast16_t cols = virtualWidth(); - const uint_fast16_t rows = virtualHeight(); - for (uint_fast16_t i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows - for (uint_fast16_t k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns + const unsigned cols = virtualWidth(); + const unsigned rows = virtualHeight(); + for (unsigned i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows + for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns return; } #endif uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; - CRGB carryover = CRGB::Black; - uint_fast16_t vlength = virtualLength(); - for(uint_fast16_t i = 0; i < vlength; i++) - { - CRGB cur = CRGB(getPixelColor(i)); - CRGB part = cur; - uint32_t before = uint32_t(cur); // remember color before blur - part.nscale8(seep); - cur.nscale8(keep); - cur += carryover; - if(i > 0) { + uint32_t carryover = BLACK; + unsigned vlength = virtualLength(); + for (unsigned i = 0; i < vlength; i++) { + uint32_t cur = getPixelColor(i); + uint32_t part = color_fade(cur, seep); + cur = color_add(color_fade(cur, keep), carryover, true); + if (i > 0) { uint32_t c = getPixelColor(i-1); - uint8_t r = R(c); - uint8_t g = G(c); - uint8_t b = B(c); - setPixelColor((uint16_t)(i-1), qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue)); + setPixelColor(i-1, color_add(c, part, true)); } - if (before != uint32_t(cur)) // optimization: only set pixel if color has changed - setPixelColor((uint16_t)i,cur.red, cur.green, cur.blue); + setPixelColor(i, cur); carryover = part; } } @@ -944,34 +1028,20 @@ 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) { - return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3); + if (pos < 85) { + return RGBW32((255 - pos * 3), 0, (pos * 3), w); } else if(pos < 170) { pos -= 85; - return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3); + return RGBW32(0, (pos * 3), (255 - pos * 3), w); } else { pos -= 170; - return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0); + return RGBW32((pos * 3), (255 - pos * 3), 0, w); } } -/* - * Returns a new, random wheel index with a minimum distance of 42 from pos. - */ -uint8_t Segment::get_random_wheel_index(uint8_t pos) { - uint8_t r = 0, x = 0, y = 0, d = 0; - - while(d < 42) { - r = random8(); - x = abs(pos - r); - y = 255 - x; - d = MIN(x, y); - } - return r; -} - /* * Gets a single color from the currently selected palette. * @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically. @@ -981,26 +1051,21 @@ uint8_t Segment::get_random_wheel_index(uint8_t pos) { * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) * @returns Single color from palette */ -uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) -{ +uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) { + uint32_t color = gamma32(currentColor(mcol)); + // default palette or no RGB support on segment - if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { - uint32_t color = currentColor(mcol, colors[mcol]); - color = gamma32(color); - if (pbri == 255) return color; - return RGBW32(scale8_video(R(color),pbri), scale8_video(G(color),pbri), scale8_video(B(color),pbri), scale8_video(W(color),pbri)); - } + if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); uint8_t paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); - if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" - CRGB fastled_col; + // 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; - if (transitional && _t) curPal = _t->_palT; - else loadPalette(curPal, palette); - fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global + curPal = currentPalette(curPal, palette); + CRGB fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global - return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, 0); + return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); } @@ -1009,8 +1074,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ /////////////////////////////////////////////////////////////////////////////// //do not call this method from system context (network callback) -void WS2812FX::finalizeInit(void) -{ +void WS2812FX::finalizeInit(void) { //reset segment runtimes for (segment &seg : _segments) { seg.markForReset(); @@ -1025,26 +1089,32 @@ void WS2812FX::finalizeInit(void) _hasWhiteChannel = _isOffRefreshRequired = false; //if busses failed to load, add default (fresh install, FS issue, ...) - if (busses.getNumBusses() == 0) { + if (BusManager::getNumBusses() == 0) { DEBUG_PRINTLN(F("No busses, init default")); const uint8_t defDataPins[] = {DATA_PINS}; const uint16_t defCounts[] = {PIXEL_COUNTS}; const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0])); const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); uint16_t prevLen = 0; - for (uint8_t i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { + for (int i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { uint8_t defPin[] = {defDataPins[i]}; + // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware + // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc + if (pinManager.isPinAllocated(defPin[0])) { + defPin[0] = 1; // start with GPIO1 and work upwards + while (pinManager.isPinAllocated(defPin[0]) && defPin[0] < WLED_NUM_PINS) defPin[0]++; + } uint16_t start = prevLen; uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; prevLen += count; BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY); - if (busses.add(defCfg) == -1) break; + if (BusManager::add(defCfg) == -1) break; } } _length = 0; - for (uint8_t i=0; igetStart() + bus->getLength() > MAX_LEDS) break; //RGBW mode is enabled if at least one of the strips is RGBW @@ -1062,46 +1132,28 @@ void WS2812FX::finalizeInit(void) #endif } - if (isMatrix) setUpMatrix(); - else { - Segment::maxWidth = _length; - Segment::maxHeight = 1; - } - - //initialize leds array. TBD: realloc if nr of leds change - if (Segment::_globalLeds) { - purgeSegments(true); - free(Segment::_globalLeds); - Segment::_globalLeds = nullptr; - } - if (useLedsArray) { - size_t arrSize = sizeof(CRGB) * getLengthTotal(); - // softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards (see setUpLeds()) - //#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) - //if (psramFound()) - // Segment::_globalLeds = (CRGB*) ps_malloc(arrSize); - //else - //#endif - Segment::_globalLeds = (CRGB*) malloc(arrSize); - if (Segment::_globalLeds && (arrSize > 0)) memset(Segment::_globalLeds, 0, arrSize); - } + Segment::maxWidth = _length; + Segment::maxHeight = 1; //segments are created in makeAutoSegments(); DEBUG_PRINTLN(F("Loading custom palettes")); loadCustomPalettes(); // (re)load all custom palettes DEBUG_PRINTLN(F("Loading custom ledmaps")); - deserializeMap(); // (re)load default ledmap + deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist) } void WS2812FX::service() { - uint32_t nowUp = millis(); // Be aware, millis() rolls over every 49 days + unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days now = nowUp + timebase; - if (nowUp - _lastShow < MIN_SHOW_DELAY) return; + if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return; bool doShow = false; _isServicing = true; _segment_index = 0; + for (segment &seg : _segments) { + if (_suspend) return; // immediately stop processing segments if suspend requested during service() + // process transition (mode changes in the middle of transition) seg.handleTransition(); // reset the segment runtime data if needed @@ -1110,28 +1162,45 @@ void WS2812FX::service() { if (!seg.isActive()) continue; // last condition ensures all solid segments are updated at the same time - if(nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) + if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { - if (seg.grouping == 0) seg.grouping = 1; //sanity check doShow = true; uint16_t delay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen - _virtualSegmentLength = seg.virtualLength(); - _colors_t[0] = seg.currentColor(0, seg.colors[0]); - _colors_t[1] = seg.currentColor(1, seg.colors[1]); - _colors_t[2] = seg.currentColor(2, seg.colors[2]); - seg.currentPalette(_currentPalette, seg.palette); - - if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(seg.cct, true), correctWB); - for (uint8_t c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]); - - // effect blending (execute previous effect) - // actual code may be a bit more involved as effects have runtime data including allocated memory - //if (seg.transitional && seg._modeP) (*_mode[seg._modeP])(progress()); - delay = (*_mode[seg.currentMode(seg.mode)])(); - if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; - if (seg.transitional && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + int16_t oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) + _virtualSegmentLength = seg.virtualLength(); //SEGLEN + _colors_t[0] = gamma32(seg.currentColor(0)); + _colors_t[1] = gamma32(seg.currentColor(1)); + _colors_t[2] = gamma32(seg.currentColor(2)); + seg.currentPalette(_currentPalette, seg.palette); // we need to pass reference + // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio + // when cctFromRgb is true we implicitly calculate WW and CW from RGB values + if (cctFromRgb) BusManager::setSegmentCCT(-1); + else BusManager::setSegmentCCT(seg.currentBri(true), correctWB); + // Effect blending + // When two effects are being blended, each may have different segment data, this + // data needs to be saved first and then restored before running previous mode. + // The blending will largely depend on the effect behaviour since actual output (LEDs) may be + // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer + // would need to be allocated for each effect and then blended together for each pixel. + [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition + delay = (*_mode[seg.mode])(); // run new/current mode +#ifndef WLED_DISABLE_MODE_BLEND + if (modeBlending && seg.mode != tmpMode) { + Segment::tmpsegd_t _tmpSegData; + Segment::modeBlend(true); // set semaphore + seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) + _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) + uint16_t d2 = (*_mode[tmpMode])(); // run old mode + seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) + delay = MIN(delay,d2); // use shortest delay + Segment::modeBlend(false); // unset semaphore + } +#endif + seg.call++; + if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } seg.next_time = nowUp + delay; @@ -1139,135 +1208,50 @@ void WS2812FX::service() { _segment_index++; } _virtualSegmentLength = 0; - busses.setSegmentCCT(-1); - if(doShow) { + _isServicing = false; + _triggered = false; + + #ifdef WLED_DEBUG + if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + #endif + if (doShow) { yield(); + Segment::handleRandomPalette(); // slowly transtion random palette; move it into for loop when each segment has individual random palette show(); } - _triggered = false; - _isServicing = false; + #ifdef WLED_DEBUG + if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + #endif } -void IRAM_ATTR WS2812FX::setPixelColor(int i, uint32_t col) -{ - if (i < customMappingSize) i = customMappingTable[i]; +void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) { + i = getMappedPixelIndex(i); if (i >= _length) return; - busses.setPixelColor(i, col); + BusManager::setPixelColor(i, col); } -uint32_t WS2812FX::getPixelColor(uint16_t i) -{ - if (i < customMappingSize) i = customMappingTable[i]; +uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) { + i = getMappedPixelIndex(i); if (i >= _length) return 0; - return busses.getPixelColor(i); -} - - -//DISCLAIMER -//The following function attemps to calculate the current LED power usage, -//and will limit the brightness to stay below a set amperage threshold. -//It is NOT a measurement and NOT guaranteed to stay within the ablMilliampsMax margin. -//Stay safe with high amperage and have a reasonable safety margin! -//I am NOT to be held liable for burned down garages! - -//fine tune power estimation constants for your setup -#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA) - //you can set it to 0 if the ESP is powered by USB and the LEDs by external - -void WS2812FX::estimateCurrentAndLimitBri() { - //power limit calculation - //each LED can draw up 195075 "power units" (approx. 53mA) - //one PU is the power it takes to have 1 channel 1 step brighter per brightness step - //so A=2,R=255,G=0,B=0 would use 510 PU per LED (1mA is about 3700 PU) - bool useWackyWS2815PowerModel = false; - byte actualMilliampsPerLed = milliampsPerLed; - - if(milliampsPerLed == 255) { - useWackyWS2815PowerModel = true; - actualMilliampsPerLed = 12; // from testing an actual strip - } - - if (ablMilliampsMax < 150 || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation - currentMilliamps = 0; - busses.setBrightness(_brightness); - return; - } - - uint16_t pLen = getLengthPhysical(); - uint32_t puPerMilliamp = 195075 / actualMilliampsPerLed; - uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power - if (powerBudget > puPerMilliamp * pLen) { //each LED uses about 1mA in standby, exclude that from power budget - powerBudget -= puPerMilliamp * pLen; - } else { - powerBudget = 0; - } - - uint32_t powerSum = 0; - - for (uint_fast8_t bNum = 0; bNum < busses.getNumBusses(); bNum++) { - Bus *bus = busses.getBus(bNum); - if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses - uint16_t len = bus->getLength(); - uint32_t busPowerSum = 0; - for (uint_fast16_t i = 0; i < len; i++) { //sum up the usage of each LED - uint32_t c = bus->getPixelColor(i); - byte r = R(c), g = G(c), b = B(c), w = W(c); - - if(useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation - busPowerSum += (MAX(MAX(r,g),b)) * 3; - } else { - busPowerSum += (r + g + b + w); - } - } - - if (bus->hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less - busPowerSum *= 3; - busPowerSum = busPowerSum >> 2; //same as /= 4 - } - powerSum += busPowerSum; - } - - uint32_t powerSum0 = powerSum; - //powerSum *= _brightness; // for NPBrightnessBus - powerSum *= 255; // no need to scale down powerSum - NPB-LG getPixelColor returns colors scaled down by brightness - - if (powerSum > powerBudget) //scale brightness down to stay in current limit - { - float scale = (float)powerBudget / (float)powerSum; - uint16_t scaleI = scale * 255; - uint8_t scaleB = (scaleI > 255) ? 255 : scaleI; - uint8_t newBri = scale8(_brightness, scaleB); - // to keep brightness uniform, sets virtual busses too - softhack007: apply reductions immediately - if (scaleB < 255) busses.setBrightness(scaleB, true); // NPB-LG has already applied brightness, so its suffifient to post-apply scaling ==> use scaleB instead of newBri - busses.setBrightness(newBri, false); // set new brightness for next frame - //currentMilliamps = (powerSum0 * newBri) / puPerMilliamp; // for NPBrightnessBus - currentMilliamps = (powerSum0 * scaleB) / puPerMilliamp; // for NPBus-LG - } else { - currentMilliamps = powerSum / puPerMilliamp; - busses.setBrightness(_brightness, false); // set new brightness for next frame - } - currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate - currentMilliamps += pLen; //add standby power back to estimate + return BusManager::getPixelColor(i); } void WS2812FX::show(void) { - - // avoid race condition, caputre _callback value + // avoid race condition, capture _callback value show_callback callback = _callback; if (callback) callback(); - estimateCurrentAndLimitBri(); - // some buses send asynchronously and this method will return before // all of the data has been sent. // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods - busses.show(); - unsigned long now = millis(); - unsigned long diff = now - _lastShow; - uint16_t fpsCurr = 200; + BusManager::show(); + + unsigned long showNow = millis(); + size_t diff = showNow - _lastShow; + size_t fpsCurr = 200; if (diff > 0) fpsCurr = 1000 / diff; - _cumulativeFps = (3 * _cumulativeFps + fpsCurr) >> 2; - _lastShow = now; + _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) + _lastShow = showNow; } /** @@ -1275,12 +1259,12 @@ void WS2812FX::show(void) { * On some hardware (ESP32), strip updates are done asynchronously. */ bool WS2812FX::isUpdating() { - return !busses.canAllShow(); + return !BusManager::canAllShow(); } /** * 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; @@ -1298,9 +1282,7 @@ void WS2812FX::setMode(uint8_t segid, uint8_t m) { if (m >= getModeCount()) m = getModeCount() - 1; if (_segments[segid].mode != m) { - _segments[segid].startTransition(_transitionDur); // set effect transitions - //_segments[segid].markForReset(); - _segments[segid].mode = m; + _segments[segid].setMode(m); // do not load defaults } } @@ -1323,6 +1305,8 @@ void WS2812FX::setCCT(uint16_t k) { } } +// direct=true either expects the caller to call show() themselves (realtime modes) or be ok waiting for the next frame for the change to apply +// direct=false immediately triggers an effect redraw void WS2812FX::setBrightness(uint8_t b, bool direct) { if (gammaCorrectBri) b = gamma8(b); if (_brightness == b) return; @@ -1332,12 +1316,12 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { seg.freeze = false; } } - if (direct) { - // would be dangerous if applied immediately (could exceed ABL), but will not output until the next show() - busses.setBrightness(b); - } else { + // setting brightness with NeoPixelBusLg has no effect on already painted pixels, + // so we need to force an update to existing buffer + BusManager::setBrightness(b); + if (!direct) { unsigned long t = millis(); - if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon + if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon } } @@ -1349,8 +1333,7 @@ uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) { return totalLC; } -uint8_t WS2812FX::getFirstSelectedSegId(void) -{ +uint8_t WS2812FX::getFirstSelectedSegId(void) { size_t i = 0; for (segment &seg : _segments) { if (seg.isActive() && seg.isSelected()) return i; @@ -1391,8 +1374,8 @@ uint16_t WS2812FX::getLengthTotal(void) { uint16_t WS2812FX::getLengthPhysical(void) { uint16_t len = 0; - for (size_t b = 0; b < busses.getNumBusses(); b++) { - Bus *bus = busses.getBus(b); + for (size_t b = 0; b < BusManager::getNumBusses(); b++) { + Bus *bus = BusManager::getBus(b); if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses len += bus->getLength(); } @@ -1403,8 +1386,8 @@ uint16_t WS2812FX::getLengthPhysical(void) { //returns if there is an RGBW bus (supports RGB and White, not only white) //not influenced by auto-white mode, also true if white slider does not affect output white channel bool WS2812FX::hasRGBWBus(void) { - for (size_t b = 0; b < busses.getNumBusses(); b++) { - Bus *bus = busses.getBus(b); + for (size_t b = 0; b < BusManager::getNumBusses(); b++) { + Bus *bus = BusManager::getBus(b); if (bus == nullptr || bus->getLength()==0) break; if (bus->hasRGB() && bus->hasWhite()) return true; } @@ -1413,30 +1396,26 @@ bool WS2812FX::hasRGBWBus(void) { bool WS2812FX::hasCCTBus(void) { if (cctFromRgb && !correctWB) return false; - for (size_t b = 0; b < busses.getNumBusses(); b++) { - Bus *bus = busses.getBus(b); + for (size_t b = 0; b < BusManager::getNumBusses(); b++) { + Bus *bus = BusManager::getBus(b); if (bus == nullptr || bus->getLength()==0) break; - switch (bus->getType()) { - case TYPE_ANALOG_5CH: - case TYPE_ANALOG_2CH: - return true; - } + if (bus->hasCCT()) return true; } return false; } -void WS2812FX::purgeSegments(bool force) { +void WS2812FX::purgeSegments() { // remove all inactive segments (from the back) int deleted = 0; if (_segments.size() <= 1) return; for (size_t i = _segments.size()-1; i > 0; i--) - if (_segments[i].stop == 0 || force) { + if (_segments[i].stop == 0) { deleted++; _segments.erase(_segments.begin() + i); } if (deleted) { _segments.shrink_to_fit(); - if (_mainSegment >= _segments.size()) setMainSegmentId(0); + setMainSegmentId(0); } } @@ -1444,13 +1423,17 @@ Segment& WS2812FX::getSegment(uint8_t id) { return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors } -void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { - if (n >= _segments.size()) return; - _segments[n].setUp(i1, i2, grouping, spacing, offset, startY, stopY); -} - -void WS2812FX::restartRuntime() { - for (segment &seg : _segments) seg.markForReset(); +// sets new segment bounds, queues if that segment is currently running +void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { + if (segId >= getSegmentsNum()) { + if (i2 <= i1) return; // do not append empty/inactive segments + appendSegment(Segment(0, strip.getLengthTotal())); + segId = getSegmentsNum()-1; // segments are added at the end of list + } + suspend(); + _segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY); + resume(); + if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector } void WS2812FX::resetSegments() { @@ -1461,6 +1444,7 @@ void WS2812FX::resetSegments() { segment seg = Segment(0, _length); #endif _segments.push_back(seg); + _segments.shrink_to_fit(); // just in case ... _mainSegment = 0; } @@ -1479,8 +1463,8 @@ void WS2812FX::makeAutoSegments(bool forceReset) { } #endif - for (size_t i = s; i < busses.getNumBusses(); i++) { - Bus* b = busses.getBus(i); + for (size_t i = s; i < BusManager::getNumBusses(); i++) { + Bus* b = BusManager::getBus(i); segStarts[s] = b->getStart(); segStops[s] = segStarts[s] + b->getLength(); @@ -1569,8 +1553,8 @@ void WS2812FX::fixInvalidSegments() { bool WS2812FX::checkSegmentAlignment() { bool aligned = false; for (segment &seg : _segments) { - for (uint8_t b = 0; bgetStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true; } if (seg.start == 0 && seg.stop == _length) aligned = true; @@ -1579,42 +1563,22 @@ 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) - { - for (uint16_t x = i; x <= i2; x++) setPixelColor(x, col); - } else - { - for (uint16_t x = i2; x <= i; x++) setPixelColor(x, col); - } -} - -void WS2812FX::setTransitionMode(bool t) { - for (segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); + if (i2 < i) std::swap(i,i2); + for (unsigned x = i; x <= i2; x++) setPixelColor(x, col); } #ifdef WLED_DEBUG 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 (useLedsArray) 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 @@ -1633,7 +1597,7 @@ void WS2812FX::loadCustomPalettes() { if (readObjectFromFile(fileName, nullptr, &pDoc)) { JsonArray pal = pDoc[F("palette")]; - if (!pal.isNull() && pal.size()>4) { // not an empty palette (at least 2 entries) + if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries) if (pal[0].is() && pal[1].is()) { // we have an array of index & hex strings size_t palSize = MIN(pal.size(), 36); @@ -1642,21 +1606,23 @@ void WS2812FX::loadCustomPalettes() { uint8_t rgbw[] = {0,0,0,0}; 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] = 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])); + for (size_t c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component + 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); palSize -= palSize % 4; // make sure size is multiple of 4 for (size_t i=0; i()<256; i+=4) { tcp[ i ] = (uint8_t) pal[ i ].as(); // index - tcp[i+1] = (uint8_t) pal[i+1].as(); // R - tcp[i+2] = (uint8_t) pal[i+2].as(); // G - tcp[i+3] = (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])); + 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_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)); + } else { + DEBUG_PRINTLN(F("Wrong palette format.")); } } } else { @@ -1672,47 +1638,49 @@ bool WS2812FX::deserializeMap(uint8_t n) { char fileName[32]; strcpy_P(fileName, PSTR("/ledmap")); if (n) sprintf(fileName +7, "%d", n); - strcat(fileName, ".json"); + strcat_P(fileName, PSTR(".json")); bool isFile = WLED_FS.exists(fileName); - if (!isFile) { - // erase custom mapping if selecting nonexistent ledmap.json (n==0) - if (!isMatrix && !n && customMappingTable != nullptr) { - customMappingSize = 0; - delete[] customMappingTable; - customMappingTable = nullptr; - } + customMappingSize = 0; // prevent use of mapping if anything goes wrong + + if (!isFile && n==0 && isMatrix) { + setUpMatrix(); return false; } - if (!requestJSONBufferLock(7)) return false; + if (!isFile || !requestJSONBufferLock(7)) return false; // this will trigger setUpMatrix() when called from wled.cpp - if (!readObjectFromFile(fileName, nullptr, &doc)) { + if (!readObjectFromFile(fileName, nullptr, pDoc)) { + DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName); releaseJSONBufferLock(); - return false; //if file does not exist just exit + return false; // if file does not load properly then exit } - DEBUG_PRINT(F("Reading LED map from ")); - DEBUG_PRINTLN(fileName); + if (customMappingTable) delete[] customMappingTable; + customMappingTable = new uint16_t[getLengthTotal()]; - // erase old custom ledmap - if (customMappingTable != nullptr) { - customMappingSize = 0; - delete[] customMappingTable; - customMappingTable = nullptr; - } - - JsonArray map = doc[F("map")]; - if (!map.isNull() && map.size()) { // not an empty map - customMappingSize = map.size(); - customMappingTable = new uint16_t[customMappingSize]; - for (uint16_t i=0; ias(); + JsonArray map = root[F("map")]; + if (!map.isNull() && map.size()) { // not an empty map + customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal()); + for (unsigned i=0; i 0); +} + +uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) { + // convert logical address to physical + if (index < customMappingSize + && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; + + return index; } diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index d11182aa5..82e81a387 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -9,10 +9,10 @@ #include "bus_wrapper.h" #include "bus_manager.h" +extern bool cctICused; + //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 +32,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 @@ -53,7 +55,8 @@ void ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) { if (len == 0) { return; } - if (colorOrder > COL_ORDER_MAX) { + // upper nibble contains W swap information + if ((colorOrder & 0x0F) > COL_ORDER_MAX) { return; } _mappings[_count].start = start; @@ -63,12 +66,13 @@ void ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) { } uint8_t IRAM_ATTR ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const { - if (_count == 0) return defaultColorOrder; - // upper nibble containd W swap information - uint8_t swapW = defaultColorOrder >> 4; - for (uint8_t i = 0; i < _count; i++) { - if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) { - return _mappings[i].colorOrder | (swapW << 4); + if (_count > 0) { + // upper nibble contains W swap information + // when ColorOrderMap's upper nibble contains value >0 then swap information is used from it, otherwise global swap is used + for (unsigned i = 0; i < _count; i++) { + if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) { + return _mappings[i].colorOrder | ((_mappings[i].colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0)); + } } } return defaultColorOrder; @@ -77,7 +81,7 @@ uint8_t IRAM_ATTR ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaul uint32_t Bus::autoWhiteCalc(uint32_t c) { uint8_t aWM = _autoWhiteMode; - if (_gAWM < 255) aWM = _gAWM; + if (_gAWM < AW_GLOBAL_DISABLED) aWM = _gAWM; if (aWM == RGBW_MODE_MANUAL_ONLY) return c; uint8_t w = W(c); //ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) @@ -91,101 +95,269 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) { return RGBW32(r, g, b, w); } +uint8_t *Bus::allocData(size_t size) { + if (_data) free(_data); // should not happen, but for safety + return _data = (uint8_t *)(size>0 ? calloc(size, sizeof(uint8_t)) : nullptr); +} -BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) : Bus(bc.type, bc.start, bc.autoWhite), _colorOrderMap(com) { + +BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) +: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814)) +, _skip(bc.skipAmount) //sacrificial pixels +, _colorOrder(bc.colorOrder) +, _milliAmpsPerLed(bc.milliAmpsPerLed) +, _milliAmpsMax(bc.milliAmpsMax) +, _colorOrderMap(com) +{ if (!IS_DIGITAL(bc.type) || !bc.count) return; if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; _frequencykHz = 0U; _pins[0] = bc.pins[0]; if (IS_2PIN(bc.type)) { if (!pinManager.allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { - cleanup(); return; + cleanup(); + return; } _pins[1] = bc.pins[1]; _frequencykHz = bc.frequency ? bc.frequency : 2000U; // 2MHz clock if undefined } - reversed = bc.reversed; - _needsRefresh = bc.refreshReq || bc.type == TYPE_TM1814; - _skip = bc.skipAmount; //sacrificial pixels - _len = bc.count + _skip; _iType = PolyBus::getI(bc.type, _pins, nr); if (_iType == I_NONE) return; - uint16_t lenToCreate = _len; - if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus - _busPtr = PolyBus::create(_iType, _pins, lenToCreate, nr, _frequencykHz); + if (bc.doubleBuffer && !allocData(bc.count * Bus::getNumberOfChannels(bc.type))) return; + //_buffering = bc.doubleBuffer; + uint16_t lenToCreate = bc.count; + if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus + _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr, _frequencykHz); _valid = (_busPtr != nullptr); - _colorOrder = bc.colorOrder; - DEBUG_PRINTF("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n", _valid?"S":"Uns", nr, _len, bc.type, _pins[0],_pins[1],_iType); + DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], IS_2PIN(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); +} + +//fine tune power estimation constants for your setup +//you can set it to 0 if the ESP is powered by USB and the LEDs by external +#ifndef MA_FOR_ESP + #ifdef ESP8266 + #define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA) + #else + #define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA) + #endif +#endif + +//DISCLAIMER +//The following function attemps to calculate the current LED power usage, +//and will limit the brightness to stay below a set amperage threshold. +//It is NOT a measurement and NOT guaranteed to stay within the ablMilliampsMax margin. +//Stay safe with high amperage and have a reasonable safety margin! +//I am NOT to be held liable for burned down garages or houses! + +// To disable brightness limiter we either set output max current to 0 or single LED current to 0 +uint8_t BusDigital::estimateCurrentAndLimitBri() { + bool useWackyWS2815PowerModel = false; + byte actualMilliampsPerLed = _milliAmpsPerLed; + + if (_milliAmpsMax < MA_FOR_ESP/BusManager::getNumBusses() || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation + return _bri; + } + + if (_milliAmpsPerLed == 255) { + useWackyWS2815PowerModel = true; + actualMilliampsPerLed = 12; // from testing an actual strip + } + + size_t powerBudget = (_milliAmpsMax - MA_FOR_ESP/BusManager::getNumBusses()); //80/120mA for ESP power + if (powerBudget > getLength()) { //each LED uses about 1mA in standby, exclude that from power budget + powerBudget -= getLength(); + } else { + powerBudget = 0; + } + + uint32_t busPowerSum = 0; + for (unsigned i = 0; i < getLength(); i++) { //sum up the usage of each LED + uint32_t c = getPixelColor(i); // always returns original or restored color without brightness scaling + byte r = R(c), g = G(c), b = B(c), w = W(c); + + if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation + busPowerSum += (max(max(r,g),b)) * 3; + } else { + busPowerSum += (r + g + b + w); + } + } + + if (hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less + busPowerSum *= 3; + busPowerSum >>= 2; //same as /= 4 + } + + // powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps + busPowerSum = (busPowerSum * actualMilliampsPerLed) / 765; + _milliAmpsTotal = busPowerSum * _bri / 255; + + uint8_t newBri = _bri; + if (busPowerSum * _bri / 255 > powerBudget) { //scale brightness down to stay in current limit + float scale = (float)(powerBudget * 255) / (float)(busPowerSum * _bri); + if (scale >= 1.0f) return _bri; + _milliAmpsTotal = ceilf((float)_milliAmpsTotal * scale); + uint8_t scaleB = min((int)(scale * 255), 255); + newBri = unsigned(_bri * scaleB) / 256 + 1; + } + return newBri; } void BusDigital::show() { - PolyBus::show(_busPtr, _iType); + _milliAmpsTotal = 0; + if (!_valid) return; + + uint8_t cctWW = 0, cctCW = 0; + uint8_t newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal + if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits + + if (_data) { + size_t channels = getNumberOfChannels(); + int16_t oldCCT = Bus::_cct; // temporarily save bus CCT + for (size_t i=0; i<_len; i++) { + size_t offset = i * channels; + uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); + uint32_t c; + if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs (_len is always a multiple of 3) + switch (i%3) { + case 0: c = RGBW32(_data[offset] , _data[offset+1], _data[offset+2], 0); break; + case 1: c = RGBW32(_data[offset-1], _data[offset] , _data[offset+1], 0); break; + case 2: c = RGBW32(_data[offset-2], _data[offset-1], _data[offset] , 0); break; + } + } else { + if (hasRGB()) c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], hasWhite() ? _data[offset+3] : 0); + else c = RGBW32(0, 0, 0, _data[offset]); + } + if (hasCCT()) { + // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT + // we need to extract and appy CCT value for each pixel individually even though all buses share the same _cct variable + // TODO: there is an issue if CCT is calculated from RGB value (_cct==-1), we cannot do that with double buffer + Bus::_cct = _data[offset+channels-1]; + Bus::calculateCCT(c, cctWW, cctCW); + } + uint16_t pix = i; + if (_reversed) pix = _len - pix -1; + pix += _skip; + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); + } + #if !defined(STATUSLED) || STATUSLED>=0 + if (_skip) PolyBus::setPixelColor(_busPtr, _iType, 0, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black + #endif + for (int i=1; i<_skip; i++) PolyBus::setPixelColor(_busPtr, _iType, i, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black + Bus::_cct = oldCCT; + } else { + if (newBri < _bri) { + uint16_t hwLen = _len; + if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus + for (unsigned i = 0; i < hwLen; i++) { + // use 0 as color order, actual order does not matter here as we just update the channel values as-is + uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri); + if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); // this will unfortunately corrupt (segment) CCT data on every bus + PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness + } + } + } + PolyBus::show(_busPtr, _iType, !_data); // faster if buffer consistency is not important (use !_buffering this causes 20% FPS drop) + // restore bus brightness to its original value + // this is done right after show, so this is only OK if LED updates are completed before show() returns + // or async show has a separate buffer (ESP32 RMT and I2S are ok) + if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, _bri); } bool BusDigital::canShow() { + if (!_valid) return true; return PolyBus::canShow(_busPtr, _iType); } -void BusDigital::setBrightness(uint8_t b, bool immediate) { +void BusDigital::setBrightness(uint8_t b) { + if (_bri == b) return; //Fix for turning off onboard LED breaking bus #ifdef LED_BUILTIN - if (_bri == 0 && b > 0) { - if (_pins[0] == LED_BUILTIN || _pins[1] == LED_BUILTIN) PolyBus::begin(_busPtr, _iType, _pins); + if (_bri == 0) { // && b > 0, covered by guard if above + if (_pins[0] == LED_BUILTIN || _pins[1] == LED_BUILTIN) reinit(); } #endif - Bus::setBrightness(b, immediate); - PolyBus::setBrightness(_busPtr, _iType, b, immediate); + Bus::setBrightness(b); + PolyBus::setBrightness(_busPtr, _iType, b); } //If LEDs are skipped, it is possible to use the first as a status LED. //TODO only show if no new show due in the next 50ms void BusDigital::setStatusPixel(uint32_t c) { - if (_skip && canShow()) { + if (_valid && _skip) { PolyBus::setPixelColor(_busPtr, _iType, 0, c, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); - PolyBus::show(_busPtr, _iType); + if (canShow()) PolyBus::show(_busPtr, _iType); } } void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { - if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814 || _type == TYPE_WS2812_1CH_X3) c = autoWhiteCalc(c); - if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT - if (reversed) pix = _len - pix -1; - else pix += _skip; - uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); - if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs - uint16_t pOld = pix; - pix = IC_INDEX_WS2812_1CH_3X(pix); - uint32_t cOld = PolyBus::getPixelColor(_busPtr, _iType, pix, co); - switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set) - case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break; - case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break; - case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; + if (!_valid) return; + uint8_t cctWW = 0, cctCW = 0; + if (hasWhite()) c = autoWhiteCalc(c); + if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT + if (_data) { + size_t offset = pix * getNumberOfChannels(); + if (hasRGB()) { + _data[offset++] = R(c); + _data[offset++] = G(c); + _data[offset++] = B(c); } + if (hasWhite()) _data[offset++] = W(c); + // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT + // we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio) + if (hasCCT()) _data[offset] = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it + } else { + if (_reversed) pix = _len - pix -1; + pix += _skip; + uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); + if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs + uint16_t pOld = pix; + pix = IC_INDEX_WS2812_1CH_3X(pix); + uint32_t cOld = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, pix, co),_bri); + switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set) + case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break; + case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break; + case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; + } + } + if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); } - PolyBus::setPixelColor(_busPtr, _iType, pix, c, co); } -uint32_t BusDigital::getPixelColor(uint16_t pix) { - if (reversed) pix = _len - pix -1; - else pix += _skip; - uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); - if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs - uint16_t pOld = pix; - pix = IC_INDEX_WS2812_1CH_3X(pix); - uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, pix, co); - switch (pOld % 3) { // get only the single channel - case 0: c = RGBW32(G(c), G(c), G(c), G(c)); break; - case 1: c = RGBW32(R(c), R(c), R(c), R(c)); break; - case 2: c = RGBW32(B(c), B(c), B(c), B(c)); break; +// returns original color if global buffering is enabled, else returns lossly restored color from bus +uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) { + if (!_valid) return 0; + if (_data) { + size_t offset = pix * getNumberOfChannels(); + uint32_t c; + if (!hasRGB()) { + c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]); + } else { + c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], hasWhite() ? _data[offset+3] : 0); + } + return c; + } else { + if (_reversed) pix = _len - pix -1; + pix += _skip; + uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); + uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri); + if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs + uint8_t r = R(c); + uint8_t g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed? + uint8_t b = _reversed ? G(c) : B(c); + switch (pix % 3) { // get only the single channel + case 0: c = RGBW32(g, g, g, g); break; + case 1: c = RGBW32(r, r, r, r); break; + case 2: c = RGBW32(b, b, b, b); break; + } } return c; } - return PolyBus::getPixelColor(_busPtr, _iType, pix, co); } uint8_t BusDigital::getPins(uint8_t* pinArray) { uint8_t numPins = IS_2PIN(_type) ? 2 : 1; - for (uint8_t i = 0; i < numPins; i++) pinArray[i] = _pins[i]; + for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; return numPins; } @@ -196,6 +368,7 @@ void BusDigital::setColorOrder(uint8_t colorOrder) { } void BusDigital::reinit() { + if (!_valid) return; PolyBus::begin(_busPtr, _iType, _pins); } @@ -205,89 +378,83 @@ void BusDigital::cleanup() { _iType = I_NONE; _valid = false; _busPtr = nullptr; + if (_data != nullptr) freeData(); pinManager.deallocatePin(_pins[1], PinOwner::BusDigital); pinManager.deallocatePin(_pins[0], PinOwner::BusDigital); } -BusPwm::BusPwm(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { - _valid = false; +BusPwm::BusPwm(BusConfig &bc) +: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed) +{ if (!IS_PWM(bc.type)) return; uint8_t numPins = NUM_PWM_PINS(bc.type); _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; #ifdef ESP8266 - analogWriteRange(255); //same range as one RGB channel + // 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 (uint8_t i = 0; i < numPins; i++) { + for (unsigned i = 0; i < numPins; i++) { uint8_t currentPin = bc.pins[i]; if (!pinManager.allocatePin(currentPin, true, PinOwner::BusPwm)) { - deallocatePins(); return; + deallocatePins(); return; } _pins[i] = currentPin; //store only after allocatePin() succeeds #ifdef ESP8266 pinMode(_pins[i], OUTPUT); #else - ledcSetup(_ledcStart + i, _frequency, 8); + ledcSetup(_ledcStart + i, _frequency, _depth); ledcAttachPin(_pins[i], _ledcStart + i); #endif } - reversed = bc.reversed; + _data = _pwmdata; // avoid malloc() and use stack _valid = true; } void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); - if (_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { - c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { + c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT } uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); uint8_t w = W(c); - uint8_t cct = 0; //0 - full warm white, 255 - full cold white - if (_cct > -1) { - if (_cct >= 1900) cct = (_cct - 1900) >> 5; - else if (_cct < 256) cct = _cct; - } else { - cct = (approximateKelvinFromRGB(c) - 1900) >> 5; - } - - uint8_t ww, cw; - #ifdef WLED_USE_IC_CCT - ww = w; - cw = cct; - #else - //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) - if (cct < _cctBlend) ww = 255; - else ww = ((255-cct) * 255) / (255 - _cctBlend); - - if ((255-cct) < _cctBlend) cw = 255; - else cw = (cct * 255) / (255 - _cctBlend); - - ww = (w * ww) / 255; //brightness scaling - cw = (w * cw) / 255; - #endif switch (_type) { case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation _data[0] = w; break; case TYPE_ANALOG_2CH: //warm white + cold white - _data[1] = cw; - _data[0] = ww; + if (cctICused) { + _data[0] = w; + _data[1] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; + } else { + Bus::calculateCCT(c, _data[0], _data[1]); + } break; case TYPE_ANALOG_5CH: //RGB + warm white + cold white - _data[4] = cw; - w = ww; + if (cctICused) + _data[4] = Bus::_cct < 0 || Bus::_cct > 255 ? 127 : Bus::_cct; + else + Bus::calculateCCT(c, w, _data[4]); case TYPE_ANALOG_4CH: //RGBW _data[3] = w; case TYPE_ANALOG_3CH: //standard dumb RGB @@ -302,12 +469,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); - for (uint8_t i = 0; i < numPins; i++) { - uint8_t scaled = (_data[i] * _bri) / 255; - if (reversed) scaled = 255 - scaled; + unsigned maxBri = (1<<_depth) - 1; + #ifdef ESP8266 + unsigned pwmBri = (unsigned)(roundf(powf((float)_bri / 255.0f, 1.7f) * (float)maxBri)); // 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++) { + unsigned scaled = (_data[i] * pwmBri) / 255; + if (_reversed) scaled = maxBri - scaled; #ifdef ESP8266 analogWrite(_pins[i], scaled); #else @@ -319,7 +523,7 @@ void BusPwm::show() { uint8_t BusPwm::getPins(uint8_t* pinArray) { if (!_valid) return 0; uint8_t numPins = NUM_PWM_PINS(_type); - for (uint8_t i = 0; i < numPins; i++) { + for (unsigned i = 0; i < numPins; i++) { pinArray[i] = _pins[i]; } return numPins; @@ -327,7 +531,7 @@ uint8_t BusPwm::getPins(uint8_t* pinArray) { void BusPwm::deallocatePins() { uint8_t numPins = NUM_PWM_PINS(_type); - for (uint8_t i = 0; i < numPins; i++) { + for (unsigned i = 0; i < numPins; i++) { pinManager.deallocatePin(_pins[i], PinOwner::BusPwm); if (!pinManager.isPinOk(_pins[i])) continue; #ifdef ESP8266 @@ -342,8 +546,10 @@ void BusPwm::deallocatePins() { } -BusOnOff::BusOnOff(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { - _valid = false; +BusOnOff::BusOnOff(BusConfig &bc) +: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed) +, _onoffdata(0) +{ if (bc.type != TYPE_ONOFF) return; uint8_t currentPin = bc.pins[0]; @@ -352,7 +558,7 @@ BusOnOff::BusOnOff(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { } _pin = currentPin; //store only after allocatePin() succeeds pinMode(_pin, OUTPUT); - reversed = bc.reversed; + _data = &_onoffdata; // avoid malloc() and use stack _valid = true; } @@ -363,18 +569,17 @@ void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { uint8_t g = G(c); uint8_t b = B(c); uint8_t w = W(c); - - _data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; + _data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; } uint32_t BusOnOff::getPixelColor(uint16_t pix) { if (!_valid) return 0; - return RGBW32(_data, _data, _data, _data); + return RGBW32(_data[0], _data[0], _data[0], _data[0]); } void BusOnOff::show() { if (!_valid) return; - digitalWrite(_pin, reversed ? !(bool)_data : (bool)_data); + digitalWrite(_pin, _reversed ? !(bool)_data[0] : (bool)_data[0]); } uint8_t BusOnOff::getPins(uint8_t* pinArray) { @@ -384,13 +589,19 @@ uint8_t BusOnOff::getPins(uint8_t* pinArray) { } -BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { - _valid = false; +BusNetwork::BusNetwork(BusConfig &bc) +: Bus(bc.type, bc.start, bc.autoWhite, bc.count) +, _broadcastLock(false) +{ switch (bc.type) { case TYPE_NET_ARTNET_RGB: _rgbw = false; _UDPtype = 2; break; + case TYPE_NET_ARTNET_RGBW: + _rgbw = true; + _UDPtype = 2; + break; case TYPE_NET_E131_RGB: _rgbw = false; _UDPtype = 1; @@ -401,19 +612,14 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { break; } _UDPchannels = _rgbw ? 4 : 3; - _data = (byte *)malloc(bc.count * _UDPchannels); - if (_data == nullptr) return; - memset(_data, 0, bc.count * _UDPchannels); - _len = bc.count; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); - _broadcastLock = false; - _valid = true; + _valid = (allocData(_len * _UDPchannels) != nullptr); } void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; - if (hasWhite()) c = autoWhiteCalc(c); - if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + if (_rgbw) c = autoWhiteCalc(c); + if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT uint16_t offset = pix * _UDPchannels; _data[offset] = R(c); _data[offset+1] = G(c); @@ -424,7 +630,7 @@ void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { uint32_t BusNetwork::getPixelColor(uint16_t pix) { if (!_valid || pix >= _len) return 0; uint16_t offset = pix * _UDPchannels; - return RGBW32(_data[offset], _data[offset+1], _data[offset+2], _rgbw ? (_data[offset+3] << 24) : 0); + return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (_rgbw ? _data[offset+3] : 0)); } void BusNetwork::show() { @@ -435,7 +641,7 @@ void BusNetwork::show() { } uint8_t BusNetwork::getPins(uint8_t* pinArray) { - for (uint8_t i = 0; i < 4; i++) { + for (unsigned i = 0; i < 4; i++) { pinArray[i] = _client[i]; } return 4; @@ -444,36 +650,33 @@ uint8_t BusNetwork::getPins(uint8_t* pinArray) { void BusNetwork::cleanup() { _type = I_NONE; _valid = false; - if (_data != nullptr) free(_data); - _data = nullptr; + freeData(); } //utility to get the approx. memory usage of a given BusConfig uint32_t BusManager::memUsage(BusConfig &bc) { - uint8_t type = bc.type; + if (bc.type == TYPE_ONOFF || IS_PWM(bc.type)) return 5; + uint16_t len = bc.count + bc.skipAmount; - if (type > 15 && type < 32) { // digital types - if (type == TYPE_UCS8903 || type == TYPE_UCS8904) len *= 2; // 16-bit LEDs + uint16_t channels = Bus::getNumberOfChannels(bc.type); + uint16_t multiplier = 1; + if (IS_DIGITAL(bc.type)) { // digital types + if (IS_16BIT(bc.type)) len *= 2; // 16-bit LEDs #ifdef ESP8266 if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem - if (type > 28) return len*20; //RGBW - return len*15; + multiplier = 5; } - if (type > 28) return len*4; //RGBW - return len*3; - #else //ESP32 RMT uses double buffer? - if (type > 28) return len*8; //RGBW - return len*6; + #else //ESP32 RMT uses double buffer, I2S uses 5x buffer + multiplier = 2; #endif } - if (type > 31 && type < 48) return 5; - return len*3; //RGB + return len * channels * multiplier; //RGB } int BusManager::add(BusConfig &bc) { if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; - if (bc.type >= TYPE_NET_DDP_RGB && bc.type < 96) { + if (IS_VIRTUAL(bc.type)) { busses[numBusses] = new BusNetwork(bc); } else if (IS_DIGITAL(bc.type)) { busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap); @@ -490,24 +693,27 @@ void BusManager::removeAll() { DEBUG_PRINTLN(F("Removing all.")); //prevents crashes due to deleting busses while in use. while (!canAllShow()) yield(); - for (uint8_t i = 0; i < numBusses; i++) delete busses[i]; + for (unsigned i = 0; i < numBusses; i++) delete busses[i]; numBusses = 0; } void BusManager::show() { - for (uint8_t i = 0; i < numBusses; i++) { + _milliAmpsUsed = 0; + for (unsigned i = 0; i < numBusses; i++) { busses[i]->show(); + _milliAmpsUsed += busses[i]->getUsedCurrent(); } + if (_milliAmpsUsed) _milliAmpsUsed += MA_FOR_ESP; } void BusManager::setStatusPixel(uint32_t c) { - for (uint8_t i = 0; i < numBusses; i++) { + for (unsigned i = 0; i < numBusses; i++) { busses[i]->setStatusPixel(c); } } -void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c, int16_t cct) { - for (uint8_t i = 0; i < numBusses; i++) { +void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c) { + for (unsigned i = 0; i < numBusses; i++) { Bus* b = busses[i]; uint16_t bstart = b->getStart(); if (pix < bstart || pix >= bstart + b->getLength()) continue; @@ -515,9 +721,9 @@ void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c, int16_t cct) } } -void BusManager::setBrightness(uint8_t b, bool immediate) { - for (uint8_t i = 0; i < numBusses; i++) { - busses[i]->setBrightness(b, immediate); +void BusManager::setBrightness(uint8_t b) { + for (unsigned i = 0; i < numBusses; i++) { + busses[i]->setBrightness(b); } } @@ -526,12 +732,12 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { if (cct >= 0) { //if white balance correction allowed, save as kelvin value instead of 0-255 if (allowWBCorrection) cct = 1900 + (cct << 5); - } else cct = -1; + } else cct = -1; // will use kelvin approximation from RGB Bus::setCCT(cct); } uint32_t BusManager::getPixelColor(uint16_t pix) { - for (uint8_t i = 0; i < numBusses; i++) { + for (unsigned i = 0; i < numBusses; i++) { Bus* b = busses[i]; uint16_t bstart = b->getStart(); if (pix < bstart || pix >= bstart + b->getLength()) continue; @@ -541,7 +747,7 @@ uint32_t BusManager::getPixelColor(uint16_t pix) { } bool BusManager::canAllShow() { - for (uint8_t i = 0; i < numBusses; i++) { + for (unsigned i = 0; i < numBusses; i++) { if (!busses[i]->canShow()) return false; } return true; @@ -555,7 +761,7 @@ Bus* BusManager::getBus(uint8_t busNr) { //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) uint16_t BusManager::getTotalLength() { uint16_t len = 0; - for (uint8_t i=0; igetLength(); + for (unsigned i=0; igetLength(); return len; } @@ -563,3 +769,11 @@ uint16_t BusManager::getTotalLength() { int16_t Bus::_cct = -1; uint8_t Bus::_cctBlend = 0; uint8_t Bus::_gAWM = 255; + +uint16_t BusDigital::_milliAmpsTotal = 0; + +uint8_t BusManager::numBusses = 0; +Bus* BusManager::busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES]; +ColorOrderMap BusManager::colorOrderMap = {}; +uint16_t BusManager::_milliAmpsUsed = 0; +uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT; \ No newline at end of file diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 4a8da6051..c128f8c09 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -7,6 +7,9 @@ #include "const.h" +//colors.cpp +uint16_t approximateKelvinFromRGB(uint32_t rgb); + #define GET_BIT(var,bit) (((var)>>(bit))&0x01) #define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit))) #define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit)))) @@ -18,6 +21,10 @@ #define IC_INDEX_WS2812_2CH_3X(i) ((i)*2/3) #define WS2812_2CH_3X_SPANS_2_ICS(i) ((i)&0x01) // every other LED zone is on two different ICs +// flag for using double buffering in BusDigital +extern bool useGlobalLedBuffer; + + //temporary struct for passing bus configuration to bus struct BusConfig { uint8_t type; @@ -28,17 +35,31 @@ struct BusConfig { uint8_t skipAmount; bool refreshReq; uint8_t autoWhite; - uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; + uint8_t pins[5] = {255, 255, 255, 255, 255}; uint16_t frequency; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U) { + bool doubleBuffer; + uint8_t milliAmpsPerLed; + uint16_t milliAmpsMax; + + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=55, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) + : count(len) + , start(pstart) + , colorOrder(pcolorOrder) + , reversed(rev) + , skipAmount(skip) + , autoWhite(aw) + , frequency(clock_kHz) + , doubleBuffer(dblBfr) + , milliAmpsPerLed(maPerLed) + , milliAmpsMax(maMax) + { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) - count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; frequency = clock_kHz; - uint8_t nPins = 1; - if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address - else if (type > 47) nPins = 2; - else if (type > 40 && type < 46) nPins = NUM_PWM_PINS(type); - for (uint8_t i = 0; i < nPins; i++) pins[i] = ppins[i]; + size_t nPins = 1; + if (IS_VIRTUAL(type)) nPins = 4; //virtual network bus. 4 "pins" store IP address + else if (IS_2PIN(type)) nPins = 2; + else if (IS_PWM(type)) nPins = NUM_PWM_PINS(type); + for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; } //validates start and length and extends total if needed @@ -54,6 +75,7 @@ struct BusConfig { } }; + // Defines an LED Strip and its color ordering. struct ColorOrderMapEntry { uint16_t start; @@ -64,9 +86,7 @@ struct ColorOrderMapEntry { struct ColorOrderMap { void add(uint16_t start, uint16_t len, uint8_t colorOrder); - uint8_t count() const { - return _count; - } + uint8_t count() const { return _count; } void reset() { _count = 0; @@ -87,59 +107,74 @@ struct ColorOrderMap { ColorOrderMapEntry _mappings[WLED_MAX_COLOR_ORDER_MAPPINGS]; }; + //parent class of BusDigital, BusPwm, and BusNetwork class Bus { public: - Bus(uint8_t type, uint16_t start, uint8_t aw) - : _bri(255) - , _len(1) + Bus(uint8_t type, uint16_t start, uint8_t aw, uint16_t len = 1, bool reversed = false, bool refresh = false) + : _type(type) + , _bri(255) + , _start(start) + , _len(len) + , _reversed(reversed) , _valid(false) - , _needsRefresh(false) + , _needsRefresh(refresh) + , _data(nullptr) // keep data access consistent across all types of buses { - _type = type; - _start = start; - _autoWhiteMode = Bus::hasWhite(_type) ? aw : RGBW_MODE_MANUAL_ONLY; + _autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY; }; virtual ~Bus() {} //throw the bus under the bus virtual void show() = 0; - virtual bool canShow() { return true; } - virtual void setStatusPixel(uint32_t c) {} + virtual bool canShow() { return true; } + virtual void setStatusPixel(uint32_t c) {} virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; virtual uint32_t getPixelColor(uint16_t pix) { return 0; } - virtual void setBrightness(uint8_t b, bool immediate=false) { _bri = b; }; - virtual void cleanup() = 0; - virtual uint8_t getPins(uint8_t* pinArray) { return 0; } - virtual uint16_t getLength() { return _len; } - virtual void setColorOrder() {} - virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } - virtual uint8_t skippedLeds() { return 0; } - virtual uint16_t getFrequency() { return 0U; } - inline uint16_t getStart() { return _start; } - inline void setStart(uint16_t start) { _start = start; } - inline uint8_t getType() { return _type; } - inline bool isOk() { return _valid; } - inline bool isOffRefreshRequired() { return _needsRefresh; } + virtual void setBrightness(uint8_t b) { _bri = b; }; + virtual uint8_t getPins(uint8_t* pinArray) { return 0; } + virtual uint16_t getLength() { return _len; } + virtual void setColorOrder(uint8_t co) {} + virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } + virtual uint8_t skippedLeds() { return 0; } + virtual uint16_t getFrequency() { return 0U; } + virtual uint16_t getLEDCurrent() { return 0; } + virtual uint16_t getUsedCurrent() { return 0; } + virtual uint16_t getMaxCurrent() { return 0; } + virtual uint8_t getNumberOfChannels() { return hasWhite(_type) + 3*hasRGB(_type) + hasCCT(_type); } + static inline uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } + inline void setReversed(bool reversed) { _reversed = reversed; } + inline uint16_t getStart() { return _start; } + inline void setStart(uint16_t start) { _start = start; } + inline uint8_t getType() { return _type; } + inline bool isOk() { return _valid; } + inline bool isReversed() { return _reversed; } + inline bool isOffRefreshRequired() { return _needsRefresh; } bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; } - virtual bool hasRGB() { - if ((_type >= TYPE_WS2812_1CH && _type <= TYPE_WS2812_WWA) || _type == TYPE_ANALOG_1CH || _type == TYPE_ANALOG_2CH || _type == TYPE_ONOFF) return false; + virtual bool hasRGB(void) { return Bus::hasRGB(_type); } + static bool hasRGB(uint8_t type) { + if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF) return false; return true; } - virtual bool hasWhite() { return Bus::hasWhite(_type); } + virtual bool hasWhite(void) { return Bus::hasWhite(_type); } static bool hasWhite(uint8_t type) { - if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true; // digital types with white channel + if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || + type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || + type == TYPE_FW1906 || type == TYPE_WS2805) return true; // digital types with white channel if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; // analog types with white channel - if (type == TYPE_NET_DDP_RGBW) return true; // network types with white channel + if (type == TYPE_NET_DDP_RGBW || type == TYPE_NET_ARTNET_RGBW) return true; // network types with white channel return false; } - virtual bool hasCCT() { - if (_type == TYPE_WS2812_2CH_X3 || _type == TYPE_WS2812_WWA || - _type == TYPE_ANALOG_2CH || _type == TYPE_ANALOG_5CH) return true; + virtual bool hasCCT(void) { return Bus::hasCCT(_type); } + static bool hasCCT(uint8_t type) { + if (type == TYPE_WS2812_2CH_X3 || type == TYPE_WS2812_WWA || + type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || + type == TYPE_FW1906 || type == TYPE_WS2805 ) return true; return false; } - static void setCCT(uint16_t cct) { + static int16_t getCCT() { return _cct; } + static void setCCT(int16_t cct) { _cct = cct; } static void setCCTBlend(uint8_t b) { @@ -150,112 +185,129 @@ class Bus { if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; #endif } + static void calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { + uint8_t cct = 0; //0 - full warm white, 255 - full cold white + uint8_t w = byte(c >> 24); + + if (_cct > -1) { + if (_cct >= 1900) cct = (_cct - 1900) >> 5; + else if (_cct < 256) cct = _cct; + } else { + cct = (approximateKelvinFromRGB(c) - 1900) >> 5; + } + + //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) + if (cct < _cctBlend) ww = 255; + else ww = ((255-cct) * 255) / (255 - _cctBlend); + if ((255-cct) < _cctBlend) cw = 255; + else cw = (cct * 255) / (255 - _cctBlend); + + ww = (w * ww) / 255; //brightness scaling + cw = (w * cw) / 255; + } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; } inline static void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } inline static uint8_t getGlobalAWMode() { return _gAWM; } - bool reversed = false; - protected: uint8_t _type; uint8_t _bri; uint16_t _start; uint16_t _len; + bool _reversed; bool _valid; bool _needsRefresh; uint8_t _autoWhiteMode; + uint8_t *_data; + // global Auto White Calculation override static uint8_t _gAWM; + // _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()): + // -1 means to extract approximate CCT value in K from RGB (in calcualteCCT()) + // [0,255] is the exact CCT value where 0 means warm and 255 cold + // [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin()) static int16_t _cct; + // _cctBlend determines WW/CW blending: + // 0 - linear (CCT 127 => 50% warm, 50% cold) + // 63 - semi additive/nonlinear (CCT 127 => 66% warm, 66% cold) + // 127 - additive CCT blending (CCT 127 => 100% warm, 100% cold) static uint8_t _cctBlend; uint32_t autoWhiteCalc(uint32_t c); + uint8_t *allocData(size_t size = 1); + void freeData() { if (_data != nullptr) free(_data); _data = nullptr; } }; class BusDigital : public Bus { public: BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com); + ~BusDigital() { cleanup(); } - inline void show(); - - bool canShow(); - - void setBrightness(uint8_t b, bool immediate); - - void setStatusPixel(uint32_t c); - - void setPixelColor(uint16_t pix, uint32_t c); - - uint32_t getPixelColor(uint16_t pix); - - uint8_t getColorOrder() { - return _colorOrder; - } - - uint16_t getLength() { - return _len - _skip; - } - - uint8_t getPins(uint8_t* pinArray); - - void setColorOrder(uint8_t colorOrder); - - uint8_t skippedLeds() { - return _skip; - } - - uint16_t getFrequency() { return _frequencykHz; } - + void show() override; + bool canShow() override; + void setBrightness(uint8_t b) override; + void setStatusPixel(uint32_t c) override; + void setPixelColor(uint16_t pix, uint32_t c) override; + void setColorOrder(uint8_t colorOrder) override; + uint32_t getPixelColor(uint16_t pix) override; + uint8_t getColorOrder() override { return _colorOrder; } + uint8_t getPins(uint8_t* pinArray) override; + uint8_t skippedLeds() override { return _skip; } + uint16_t getFrequency() override { return _frequencykHz; } + uint8_t estimateCurrentAndLimitBri(); + uint16_t getLEDCurrent() override { return _milliAmpsPerLed; } + uint16_t getUsedCurrent() override { return _milliAmpsTotal; } + uint16_t getMaxCurrent() override { return _milliAmpsMax; } void reinit(); - void cleanup(); - ~BusDigital() { - cleanup(); - } - private: - uint8_t _colorOrder = COL_ORDER_GRB; - uint8_t _pins[2] = {255, 255}; - uint8_t _iType = 0; //I_NONE; - uint8_t _skip = 0; - uint16_t _frequencykHz = 0U; - void * _busPtr = nullptr; + uint8_t _skip; + uint8_t _colorOrder; + uint8_t _pins[2]; + uint8_t _iType; + uint16_t _frequencykHz; + uint8_t _milliAmpsPerLed; + uint16_t _milliAmpsMax; + void * _busPtr; const ColorOrderMap &_colorOrderMap; + + static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() + + inline uint32_t restoreColorLossy(uint32_t c, uint8_t restoreBri) { + if (restoreBri < 255) { + 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 slightly improves recovery / stops degradation on re-scale + } + } + return c; + } }; class BusPwm : public Bus { public: BusPwm(BusConfig &bc); + ~BusPwm() { cleanup(); } - void setPixelColor(uint16_t pix, uint32_t c); - - //does no index check - uint32_t getPixelColor(uint16_t pix); - - void show(); - - uint8_t getPins(uint8_t* pinArray); - - uint16_t getFrequency() { return _frequency; } - - void cleanup() { - deallocatePins(); - } - - ~BusPwm() { - cleanup(); - } + void setPixelColor(uint16_t pix, uint32_t c) override; + uint32_t getPixelColor(uint16_t pix) override; //does no index check + uint8_t getPins(uint8_t* pinArray) override; + uint16_t getFrequency() override { return _frequency; } + void show() override; + void cleanup() { deallocatePins(); } private: - uint8_t _pins[5] = {255, 255, 255, 255, 255}; - uint8_t _data[5] = {0}; + uint8_t _pins[5]; + uint8_t _pwmdata[5]; #ifdef ARDUINO_ARCH_ESP32 - uint8_t _ledcStart = 255; + uint8_t _ledcStart; #endif - uint16_t _frequency = 0U; + uint8_t _depth; + uint16_t _frequency; void deallocatePins(); }; @@ -264,66 +316,40 @@ class BusPwm : public Bus { class BusOnOff : public Bus { public: BusOnOff(BusConfig &bc); + ~BusOnOff() { cleanup(); } - void setPixelColor(uint16_t pix, uint32_t c); - - uint32_t getPixelColor(uint16_t pix); - - void show(); - - uint8_t getPins(uint8_t* pinArray); - - void cleanup() { - pinManager.deallocatePin(_pin, PinOwner::BusOnOff); - } - - ~BusOnOff() { - cleanup(); - } + void setPixelColor(uint16_t pix, uint32_t c) override; + uint32_t getPixelColor(uint16_t pix) override; + uint8_t getPins(uint8_t* pinArray) override; + void show() override; + void cleanup() { pinManager.deallocatePin(_pin, PinOwner::BusOnOff); } private: - uint8_t _pin = 255; - uint8_t _data = 0; + uint8_t _pin; + uint8_t _onoffdata; }; class BusNetwork : public Bus { public: BusNetwork(BusConfig &bc); + ~BusNetwork() { cleanup(); } - bool hasRGB() { return true; } - bool hasWhite() { return _rgbw; } - - void setPixelColor(uint16_t pix, uint32_t c); - - uint32_t getPixelColor(uint16_t pix); - - void show(); - - bool canShow() { - // this should be a return value from UDP routine if it is still sending data out - return !_broadcastLock; - } - - uint8_t getPins(uint8_t* pinArray); - - uint16_t getLength() { - return _len; - } - + bool hasRGB() override { return true; } + bool hasWhite() override { return _rgbw; } + bool canShow() override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out + void setPixelColor(uint16_t pix, uint32_t c) override; + uint32_t getPixelColor(uint16_t pix) override; + uint8_t getPins(uint8_t* pinArray) override; + void show() override; void cleanup(); - ~BusNetwork() { - cleanup(); - } - private: IPAddress _client; uint8_t _UDPtype; uint8_t _UDPchannels; bool _rgbw; bool _broadcastLock; - byte *_data; }; @@ -333,52 +359,46 @@ class BusManager { //utility to get the approx. memory usage of a given BusConfig static uint32_t memUsage(BusConfig &bc); + static uint16_t currentMilliamps(void) { return _milliAmpsUsed; } + static uint16_t ablMilliampsMax(void) { return _milliAmpsMax; } - int add(BusConfig &bc); + static int add(BusConfig &bc); //do not call this method from system context (network callback) - void removeAll(); + static void removeAll(); - void show(); + static void show(); + static bool canAllShow(); + static void setStatusPixel(uint32_t c); + static void setPixelColor(uint16_t pix, uint32_t c); + static void setBrightness(uint8_t b); + // for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K + // WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT() + static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); + static void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} + static uint32_t getPixelColor(uint16_t pix); + static inline int16_t getSegmentCCT() { return Bus::getCCT(); } - void setStatusPixel(uint32_t c); - - void setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1); - - void setBrightness(uint8_t b, bool immediate=false); // immediate=true is for use in ABL, it applies brightness immediately (warning: inefficient) - - void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); - - uint32_t getPixelColor(uint16_t pix); - - bool canAllShow(); - - Bus* getBus(uint8_t busNr); + static Bus* getBus(uint8_t busNr); //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) - uint16_t getTotalLength(); + static uint16_t getTotalLength(); + static uint8_t getNumBusses() { return numBusses; } - inline void updateColorOrderMap(const ColorOrderMap &com) { - memcpy(&colorOrderMap, &com, sizeof(ColorOrderMap)); - } - - inline const ColorOrderMap& getColorOrderMap() const { - return colorOrderMap; - } - - inline uint8_t getNumBusses() { - return numBusses; - } + static void updateColorOrderMap(const ColorOrderMap &com) { memcpy(&colorOrderMap, &com, sizeof(ColorOrderMap)); } + static const ColorOrderMap& getColorOrderMap() { return colorOrderMap; } private: - uint8_t numBusses = 0; - Bus* busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES]; - ColorOrderMap colorOrderMap; + static uint8_t numBusses; + static Bus* busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES]; + static ColorOrderMap colorOrderMap; + static uint16_t _milliAmpsUsed; + static uint16_t _milliAmpsMax; - inline uint8_t getNumVirtualBusses() { + static uint8_t getNumVirtualBusses() { int j = 0; for (int i=0; igetType() >= TYPE_NET_DDP_RGB && busses[i]->getType() < 96) j++; return j; } }; -#endif \ No newline at end of file +#endif diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index b19997ce7..ebbeca4ad 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -2,6 +2,7 @@ #define BusWrapper_h #include "NeoPixelBusLg.h" +#include "bus_manager.h" // temporary - these defines should actually be set in platformio.ini // C3: I2S0 and I2S1 methods not supported (has one I2S bus) @@ -63,43 +64,64 @@ #define I_8266_U1_UCS_4 54 #define I_8266_DM_UCS_4 55 #define I_8266_BB_UCS_4 56 +//FW1906 GRBCW +#define I_8266_U0_FW6_5 66 +#define I_8266_U1_FW6_5 67 +#define I_8266_DM_FW6_5 68 +#define I_8266_BB_FW6_5 69 +//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 +//WS2805 +#define I_8266_U0_2805_5 89 +#define I_8266_U1_2805_5 90 +#define I_8266_DM_2805_5 91 +#define I_8266_BB_2805_5 92 /*** 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 //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 //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 //TM1814 (RGBW) #define I_32_RN_TM1_4 33 #define I_32_I0_TM1_4 34 #define I_32_I1_TM1_4 35 -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //TM1829 (RGB) #define I_32_RN_TM2_3 36 #define I_32_I0_TM2_3 37 #define I_32_I1_TM2_3 38 -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8903 (RGB) #define I_32_RN_UCS_3 57 #define I_32_I0_UCS_3 58 #define I_32_I1_UCS_3 59 -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8904 (RGBW) #define I_32_RN_UCS_4 60 #define I_32_I0_UCS_4 61 #define I_32_I1_UCS_4 62 -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +//FW1906 GRBCW +#define I_32_RN_FW6_5 63 +#define I_32_I0_FW6_5 64 +#define I_32_I1_FW6_5 65 +//APA106 +#define I_32_RN_APA106_3 85 +#define I_32_I0_APA106_3 86 +#define I_32_I1_APA106_3 87 +//WS2805 +#define I_32_RN_2805_5 93 +#define I_32_I0_2805_5 94 +#define I_32_I1_2805_5 95 + //APA102 #define I_HS_DOT_3 39 //hardware SPI @@ -162,6 +184,21 @@ #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) +//FW1906 GRBCW +#define B_8266_U0_FW6_5 NeoPixelBusLg //esp8266, gpio1 +#define B_8266_U1_FW6_5 NeoPixelBusLg //esp8266, gpio2 +#define B_8266_DM_FW6_5 NeoPixelBusLg //esp8266, gpio3 +#define B_8266_BB_FW6_5 NeoPixelBusLg //esp8266, bb +//WS2805 GRBCW +#define B_8266_U0_2805_5 NeoPixelBusLg //esp8266, gpio1 +#define B_8266_U1_2805_5 NeoPixelBusLg //esp8266, gpio2 +#define B_8266_DM_2805_5 NeoPixelBusLg //esp8266, gpio3 +#define B_8266_BB_2805_5 NeoPixelBusLg //esp8266, bb #endif /*** ESP32 Neopixel methods ***/ @@ -169,67 +206,102 @@ //RGB #define B_32_RN_NEO_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS -#define B_32_I0_NEO_3 NeoPixelBusLg +#define B_32_I0_NEO_3 NeoPixelBusLg +//#define B_32_I0_NEO_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS -#define B_32_I1_NEO_3 NeoPixelBusLg +#define B_32_I1_NEO_3 NeoPixelBusLg +//#define B_32_I1_NEO_3 NeoPixelBusLg // parallel I2S #endif -//#define B_32_BB_NEO_3 NeoPixelBusLg // NeoEsp8266BitBang800KbpsMethod //RGBW -#define B_32_RN_NEO_4 NeoPixelBusLg +#define B_32_RN_NEO_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS -#define B_32_I0_NEO_4 NeoPixelBusLg +#define B_32_I0_NEO_4 NeoPixelBusLg +//#define B_32_I0_NEO_4 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS -#define B_32_I1_NEO_4 NeoPixelBusLg +#define B_32_I1_NEO_4 NeoPixelBusLg +//#define B_32_I1_NEO_4 NeoPixelBusLg // parallel I2S #endif -//#define B_32_BB_NEO_4 NeoPixelBusLg // NeoEsp8266BitBang800KbpsMethod //400Kbps #define B_32_RN_400_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_400_3 NeoPixelBusLg +//#define B_32_I0_400_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_400_3 NeoPixelBusLg +//#define B_32_I1_400_3 NeoPixelBusLg // parallel I2S #endif -//#define B_32_BB_400_3 NeoPixelBusLg // NeoEsp8266BitBang400KbpsMethod //TM1814 (RGBW) #define B_32_RN_TM1_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM1_4 NeoPixelBusLg +//#define B_32_I0_TM1_4 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_TM1_4 NeoPixelBusLg +//#define B_32_I1_TM1_4 NeoPixelBusLg // parallel I2S #endif -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //TM1829 (RGB) #define B_32_RN_TM2_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM2_3 NeoPixelBusLg +//#define B_32_I0_TM2_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_TM2_3 NeoPixelBusLg +//#define B_32_I1_TM2_3 NeoPixelBusLg // parallel I2S #endif -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8903 #define B_32_RN_UCS_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_UCS_3 NeoPixelBusLg +//#define B_32_I0_UCS_3 NeoPixelBusLg // parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_UCS_3 NeoPixelBusLg +//#define B_32_I1_UCS_3 NeoPixelBusLg // parallel I2S #endif -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8904 #define B_32_RN_UCS_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_UCS_4 NeoPixelBusLg +//#define B_32_I0_UCS_4 NeoPixelBusLg// parallel I2S #endif #ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_UCS_4 NeoPixelBusLg +//#define B_32_I1_UCS_4 NeoPixelBusLg// parallel I2S +#endif +#define B_32_RN_APA106_3 NeoPixelBusLg +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_APA106_3 NeoPixelBusLg +//#define B_32_I0_APA106_3 NeoPixelBusLg // parallel I2S +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_APA106_3 NeoPixelBusLg +//#define B_32_I1_APA106_3 NeoPixelBusLg // parallel I2S +#endif +//FW1906 GRBCW +#define B_32_RN_FW6_5 NeoPixelBusLg +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_FW6_5 NeoPixelBusLg +//#define B_32_I0_FW6_5 NeoPixelBusLg // parallel I2S +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_FW6_5 NeoPixelBusLg +//#define B_32_I1_FW6_5 NeoPixelBusLg // parallel I2S +#endif +//WS2805 RGBWC +#define B_32_RN_2805_5 NeoPixelBusLg +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_2805_5 NeoPixelBusLg +//#define B_32_I0_2805_5 NeoPixelBusLg // parallel I2S +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_2805_5 NeoPixelBusLg +//#define B_32_I1_2805_5 NeoPixelBusLg // parallel I2S #endif -//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) - #endif //APA102 @@ -268,6 +340,7 @@ //handles pointer type conversion for all possible bus types class PolyBus { public: + // initialize SPI bus speed for DotStar methods template static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz = 0U) { @@ -280,6 +353,7 @@ class PolyBus { #endif if (clock_kHz) dotStar_strip->SetMethodSettings(NeoSpiSettings((uint32_t)clock_kHz*1000)); } + // Begin & initialize the PixelSettings for TM1814 strips. template static void beginTM1814(void* busPtr) { @@ -288,6 +362,7 @@ class PolyBus { // Max current for each LED (22.5 mA). tm1814_strip->SetPixelSettings(NeoTm1814Settings(/*R*/225, /*G*/225, /*B*/225, /*W*/225)); } + static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) { switch (busType) { case I_NONE: break; @@ -325,6 +400,18 @@ 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; + case I_8266_U0_FW6_5: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_FW6_5: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_FW6_5: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_FW6_5: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_2805_5: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_2805_5: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_2805_5: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_2805_5: (static_cast(busPtr))->Begin(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Begin(); break; @@ -334,7 +421,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->Begin(); break; case I_32_RN_NEO_4: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->Begin(); break; @@ -342,7 +428,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_NEO_4: (static_cast(busPtr))->Begin(); break; case I_32_RN_400_3: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break; @@ -350,7 +435,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->Begin(); break; case I_32_RN_TM1_4: beginTM1814(busPtr); break; case I_32_RN_TM2_3: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -368,7 +452,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_UCS_3: (static_cast(busPtr))->Begin(); break; case I_32_RN_UCS_4: (static_cast(busPtr))->Begin(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->Begin(); break; @@ -376,7 +459,27 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: (static_cast(busPtr))->Begin(); break; #endif -// case I_32_BB_UCS_4: (static_cast(busPtr))->Begin(); break; + case I_32_RN_FW6_5: (static_cast(busPtr))->Begin(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: (static_cast(busPtr))->Begin(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: (static_cast(busPtr))->Begin(); break; + #endif + 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_RN_2805_5: (static_cast(busPtr))->Begin(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: (static_cast(busPtr))->Begin(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: (static_cast(busPtr))->Begin(); break; + #endif // 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; @@ -390,7 +493,8 @@ class PolyBus { case I_SS_WS1_3: (static_cast(busPtr))->Begin(); break; case I_SS_P98_3: (static_cast(busPtr))->Begin(); break; } - }; + } + static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) { void* busPtr = nullptr; switch (busType) { @@ -424,6 +528,18 @@ 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; + case I_8266_U0_FW6_5: busPtr = new B_8266_U0_FW6_5(len, pins[0]); break; + case I_8266_U1_FW6_5: busPtr = new B_8266_U1_FW6_5(len, pins[0]); break; + case I_8266_DM_FW6_5: busPtr = new B_8266_DM_FW6_5(len, pins[0]); break; + case I_8266_BB_FW6_5: busPtr = new B_8266_BB_FW6_5(len, pins[0]); break; + case I_8266_U0_2805_5: busPtr = new B_8266_U0_2805_5(len, pins[0]); break; + case I_8266_U1_2805_5: busPtr = new B_8266_U1_2805_5(len, pins[0]); break; + case I_8266_DM_2805_5: busPtr = new B_8266_DM_2805_5(len, pins[0]); break; + case I_8266_BB_2805_5: busPtr = new B_8266_BB_2805_5(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; @@ -433,7 +549,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: busPtr = new B_32_I1_NEO_3(len, pins[0]); break; #endif -// case I_32_BB_NEO_3: busPtr = new B_32_BB_NEO_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break; @@ -441,7 +556,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: busPtr = new B_32_I1_NEO_4(len, pins[0]); break; #endif -// case I_32_BB_NEO_4: busPtr = new B_32_BB_NEO_4(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break; @@ -449,7 +563,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: busPtr = new B_32_I1_400_3(len, pins[0]); break; #endif -// case I_32_BB_400_3: busPtr = new B_32_BB_400_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_TM2_3: busPtr = new B_32_RN_TM2_3(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -467,7 +580,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: busPtr = new B_32_I1_UCS_3(len, pins[0]); break; #endif -// case I_32_BB_UCS_3: busPtr = new B_32_BB_UCS_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_UCS_4: busPtr = new B_32_RN_UCS_4(len, pins[0], (NeoBusChannel)channel); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: busPtr = new B_32_I0_UCS_4(len, pins[0]); break; @@ -475,7 +587,27 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: busPtr = new B_32_I1_UCS_4(len, pins[0]); break; #endif -// case I_32_BB_UCS_4: busPtr = new B_32_BB_UCS_4(len, pins[0], (NeoBusChannel)channel); break; + 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_RN_FW6_5: busPtr = new B_32_RN_FW6_5(len, pins[0], (NeoBusChannel)channel); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: busPtr = new B_32_I0_FW6_5(len, pins[0]); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: busPtr = new B_32_I1_FW6_5(len, pins[0]); break; + #endif + case I_32_RN_2805_5: busPtr = new B_32_RN_2805_5(len, pins[0], (NeoBusChannel)channel); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: busPtr = new B_32_I0_2805_5(len, pins[0]); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: busPtr = new B_32_I1_2805_5(len, pins[0]); break; + #endif #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; @@ -491,104 +623,134 @@ class PolyBus { } begin(busPtr, busType, pins, clock_kHz); return busPtr; - }; - static void show(void* busPtr, uint8_t busType) { + } + + static void show(void* busPtr, uint8_t busType, bool consistent = true) { switch (busType) { case I_NONE: break; #ifdef ESP8266 - case I_8266_U0_NEO_3: (static_cast(busPtr))->Show(); break; - case I_8266_U1_NEO_3: (static_cast(busPtr))->Show(); break; - case I_8266_DM_NEO_3: (static_cast(busPtr))->Show(); break; - case I_8266_BB_NEO_3: (static_cast(busPtr))->Show(); break; - case I_8266_U0_NEO_4: (static_cast(busPtr))->Show(); break; - case I_8266_U1_NEO_4: (static_cast(busPtr))->Show(); break; - case I_8266_DM_NEO_4: (static_cast(busPtr))->Show(); break; - case I_8266_BB_NEO_4: (static_cast(busPtr))->Show(); break; - case I_8266_U0_400_3: (static_cast(busPtr))->Show(); break; - case I_8266_U1_400_3: (static_cast(busPtr))->Show(); break; - case I_8266_DM_400_3: (static_cast(busPtr))->Show(); break; - case I_8266_BB_400_3: (static_cast(busPtr))->Show(); break; - case I_8266_U0_TM1_4: (static_cast(busPtr))->Show(); break; - case I_8266_U1_TM1_4: (static_cast(busPtr))->Show(); break; - case I_8266_DM_TM1_4: (static_cast(busPtr))->Show(); break; - case I_8266_BB_TM1_4: (static_cast(busPtr))->Show(); break; - case I_8266_U0_TM2_3: (static_cast(busPtr))->Show(); break; - case I_8266_U1_TM2_3: (static_cast(busPtr))->Show(); break; - case I_8266_DM_TM2_3: (static_cast(busPtr))->Show(); break; - case I_8266_BB_TM2_3: (static_cast(busPtr))->Show(); break; - case I_8266_U0_UCS_3: (static_cast(busPtr))->Show(); break; - case I_8266_U1_UCS_3: (static_cast(busPtr))->Show(); break; - case I_8266_DM_UCS_3: (static_cast(busPtr))->Show(); break; - case I_8266_BB_UCS_3: (static_cast(busPtr))->Show(); break; - case I_8266_U0_UCS_4: (static_cast(busPtr))->Show(); break; - case I_8266_U1_UCS_4: (static_cast(busPtr))->Show(); break; - case I_8266_DM_UCS_4: (static_cast(busPtr))->Show(); break; - case I_8266_BB_UCS_4: (static_cast(busPtr))->Show(); break; + case I_8266_U0_NEO_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_NEO_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_NEO_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_NEO_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_NEO_4: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_NEO_4: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_NEO_4: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_NEO_4: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_400_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_400_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_400_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_400_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_TM1_4: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_TM1_4: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_TM1_4: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_TM1_4: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_UCS_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_UCS_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_UCS_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_UCS_3: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_UCS_4: (static_cast(busPtr))->Show(consistent); break; + 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; + case I_8266_U0_FW6_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_FW6_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_FW6_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_FW6_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U0_2805_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_U1_2805_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_DM_2805_5: (static_cast(busPtr))->Show(consistent); break; + case I_8266_BB_2805_5: (static_cast(busPtr))->Show(consistent); break; #endif #ifdef ARDUINO_ARCH_ESP32 - case I_32_RN_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_RN_NEO_3: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_I0_NEO_3: (static_cast(busPtr))->Show(consistent); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_I1_NEO_3: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->Show(); break; - case I_32_RN_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_RN_NEO_4: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->Show(consistent); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_I1_NEO_4: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_NEO_4: (static_cast(busPtr))->Show(); break; - case I_32_RN_400_3: (static_cast(busPtr))->Show(); break; + case I_32_RN_400_3: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: (static_cast(busPtr))->Show(); break; + case I_32_I0_400_3: (static_cast(busPtr))->Show(consistent); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: (static_cast(busPtr))->Show(); break; + case I_32_I1_400_3: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->Show(); break; - case I_32_RN_TM1_4: (static_cast(busPtr))->Show(); break; - case I_32_RN_TM2_3: (static_cast(busPtr))->Show(); break; + case I_32_RN_TM1_4: (static_cast(busPtr))->Show(consistent); break; + case I_32_RN_TM2_3: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_TM1_4: (static_cast(busPtr))->Show(); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->Show(); break; + case I_32_I0_TM1_4: (static_cast(busPtr))->Show(consistent); break; + case I_32_I0_TM2_3: (static_cast(busPtr))->Show(consistent); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: (static_cast(busPtr))->Show(); break; - case I_32_I1_TM2_3: (static_cast(busPtr))->Show(); break; + case I_32_I1_TM1_4: (static_cast(busPtr))->Show(consistent); break; + case I_32_I1_TM2_3: (static_cast(busPtr))->Show(consistent); break; #endif - case I_32_RN_UCS_3: (static_cast(busPtr))->Show(); break; + case I_32_RN_UCS_3: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_UCS_3: (static_cast(busPtr))->Show(); break; + case I_32_I0_UCS_3: (static_cast(busPtr))->Show(consistent); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: (static_cast(busPtr))->Show(); break; + case I_32_I1_UCS_3: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_UCS_3: (static_cast(busPtr))->Show(); break; - case I_32_RN_UCS_4: (static_cast(busPtr))->Show(); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->Show(consistent); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_UCS_4: (static_cast(busPtr))->Show(); break; + case I_32_I0_UCS_4: (static_cast(busPtr))->Show(consistent); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: (static_cast(busPtr))->Show(); break; + case I_32_I1_UCS_4: (static_cast(busPtr))->Show(consistent); break; + #endif + 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_RN_FW6_5: (static_cast(busPtr))->Show(consistent); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: (static_cast(busPtr))->Show(consistent); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: (static_cast(busPtr))->Show(consistent); break; + #endif + case I_32_RN_2805_5: (static_cast(busPtr))->Show(consistent); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: (static_cast(busPtr))->Show(consistent); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: (static_cast(busPtr))->Show(consistent); break; #endif -// case I_32_BB_UCS_4: (static_cast(busPtr))->Show(); break; #endif - case I_HS_DOT_3: (static_cast(busPtr))->Show(); break; - case I_SS_DOT_3: (static_cast(busPtr))->Show(); break; - case I_HS_LPD_3: (static_cast(busPtr))->Show(); break; - case I_SS_LPD_3: (static_cast(busPtr))->Show(); break; - case I_HS_LPO_3: (static_cast(busPtr))->Show(); break; - case I_SS_LPO_3: (static_cast(busPtr))->Show(); break; - case I_HS_WS1_3: (static_cast(busPtr))->Show(); break; - case I_SS_WS1_3: (static_cast(busPtr))->Show(); break; - case I_HS_P98_3: (static_cast(busPtr))->Show(); break; - case I_SS_P98_3: (static_cast(busPtr))->Show(); break; + case I_HS_DOT_3: (static_cast(busPtr))->Show(consistent); break; + case I_SS_DOT_3: (static_cast(busPtr))->Show(consistent); break; + case I_HS_LPD_3: (static_cast(busPtr))->Show(consistent); break; + case I_SS_LPD_3: (static_cast(busPtr))->Show(consistent); break; + case I_HS_LPO_3: (static_cast(busPtr))->Show(consistent); break; + case I_SS_LPO_3: (static_cast(busPtr))->Show(consistent); break; + case I_HS_WS1_3: (static_cast(busPtr))->Show(consistent); break; + case I_SS_WS1_3: (static_cast(busPtr))->Show(consistent); break; + case I_HS_P98_3: (static_cast(busPtr))->Show(consistent); break; + case I_SS_P98_3: (static_cast(busPtr))->Show(consistent); break; } - }; + } + static bool canShow(void* busPtr, uint8_t busType) { switch (busType) { case I_NONE: return true; @@ -620,6 +782,18 @@ 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; + case I_8266_U0_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_2805_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_2805_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_2805_5: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_2805_5: return (static_cast(busPtr))->CanShow(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break; @@ -629,7 +803,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_NEO_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_NEO_4: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: return (static_cast(busPtr))->CanShow(); break; @@ -637,7 +810,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_NEO_4: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_400_3: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: return (static_cast(busPtr))->CanShow(); break; @@ -645,7 +817,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_400_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_TM1_4: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_TM2_3: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -663,7 +834,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_UCS_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_UCS_4: return (static_cast(busPtr))->CanShow(); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: return (static_cast(busPtr))->CanShow(); break; @@ -671,7 +841,27 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: return (static_cast(busPtr))->CanShow(); break; #endif -// case I_32_BB_UCS_4: return (static_cast(busPtr))->CanShow(); break; + 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_RN_FW6_5: return (static_cast(busPtr))->CanShow(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: return (static_cast(busPtr))->CanShow(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: return (static_cast(busPtr))->CanShow(); break; + #endif + case I_32_RN_2805_5: return (static_cast(busPtr))->CanShow(); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: return (static_cast(busPtr))->CanShow(); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: return (static_cast(busPtr))->CanShow(); break; + #endif #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; case I_SS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -685,13 +875,15 @@ class PolyBus { case I_SS_P98_3: return (static_cast(busPtr))->CanShow(); break; } return true; - }; - static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co) { + } + + static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co, uint16_t wwcw = 0) { uint8_t r = c >> 16; uint8_t g = c >> 8; uint8_t b = c >> 0; uint8_t w = c >> 24; RgbwColor col; + uint8_t cctWW = wwcw & 0xFF, cctCW = (wwcw>>8) & 0xFF; // reorder channels to selected order switch (co & 0x0F) { @@ -741,6 +933,18 @@ 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; + case I_8266_U0_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_U1_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_DM_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_BB_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_U0_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_U1_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_DM_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_8266_BB_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -750,7 +954,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_32_RN_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; @@ -758,7 +961,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; #endif -// case I_32_BB_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_32_RN_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -766,7 +968,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(colB)); break; case I_32_RN_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_32_RN_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -784,7 +985,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; #endif -// case I_32_BB_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; case I_32_RN_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; @@ -792,7 +992,27 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; #endif -// case I_32_BB_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + 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_RN_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #endif + case I_32_RN_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + #endif #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; @@ -805,104 +1025,134 @@ class PolyBus { case I_HS_P98_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_SS_P98_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; } - }; - static void setBrightness(void* busPtr, uint8_t busType, uint8_t b, bool immediate) { // immediate=true is for use in ABL, it applies brightness immediately (warning: inefficient) + } + + static void setBrightness(void* busPtr, uint8_t busType, uint8_t b) { switch (busType) { case I_NONE: break; #ifdef ESP8266 - case I_8266_U0_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U1_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_DM_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_BB_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U0_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U1_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_DM_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_BB_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U0_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U1_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_DM_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_BB_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U0_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U1_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_DM_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_BB_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U0_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U1_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_DM_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_BB_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U0_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U1_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_DM_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_BB_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U0_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_U1_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_DM_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_8266_BB_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_8266_U0_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_400_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_400_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_400_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_400_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; + 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; + case I_8266_U0_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U0_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_U1_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_DM_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + case I_8266_BB_2805_5: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifdef ARDUINO_ARCH_ESP32 - case I_32_RN_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I0_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I1_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_NEO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_32_RN_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I1_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_NEO_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_32_RN_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_400_3: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I0_400_3: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I1_400_3: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_400_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_32_RN_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_32_RN_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_RN_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I0_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I0_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_TM1_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_32_I1_TM2_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I1_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I1_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; #endif - case I_32_RN_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I0_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I1_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_UCS_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_32_RN_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_RN_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I0_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; #endif #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_32_I1_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; + #endif + 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_RN_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; + #endif + case I_32_RN_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: (static_cast(busPtr))->SetLuminance(b); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: (static_cast(busPtr))->SetLuminance(b); break; #endif -// case I_32_BB_UCS_4: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; #endif - case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_SS_DOT_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_HS_LPD_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_SS_LPD_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_HS_LPO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_SS_LPO_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_HS_WS1_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_SS_WS1_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_HS_P98_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; - case I_SS_P98_3: (static_cast(busPtr))->SetLuminance(b); if (immediate) (static_cast(busPtr))->ApplyPostAdjustments(); break; + case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_SS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_HS_LPD_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_SS_LPD_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_HS_LPO_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_SS_LPO_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_HS_WS1_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_SS_WS1_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_HS_P98_3: (static_cast(busPtr))->SetLuminance(b); break; + case I_SS_P98_3: (static_cast(busPtr))->SetLuminance(b); break; } - }; + } + static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { RgbwColor col(0,0,0,0); switch (busType) { @@ -936,6 +1186,18 @@ 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; + case I_8266_U0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_U1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_DM_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_BB_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_U0_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_U1_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_DM_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_8266_BB_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -945,7 +1207,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif -// case I_32_BB_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -953,7 +1214,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif -// case I_32_BB_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -961,7 +1221,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif -// case I_32_BB_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -979,7 +1238,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,0); } break; #endif -// case I_32_BB_UCS_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; @@ -987,7 +1245,27 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R>>8,c.G>>8,c.B>>8,c.W>>8); } break; #endif -// case I_32_BB_UCS_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + 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_RN_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + #endif + case I_32_RN_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + #endif #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; @@ -1053,6 +1331,18 @@ 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; + case I_8266_U0_FW6_5: delete (static_cast(busPtr)); break; + case I_8266_U1_FW6_5: delete (static_cast(busPtr)); break; + case I_8266_DM_FW6_5: delete (static_cast(busPtr)); break; + case I_8266_BB_FW6_5: delete (static_cast(busPtr)); break; + case I_8266_U0_2805_5: delete (static_cast(busPtr)); break; + case I_8266_U1_2805_5: delete (static_cast(busPtr)); break; + case I_8266_DM_2805_5: delete (static_cast(busPtr)); break; + case I_8266_BB_2805_5: delete (static_cast(busPtr)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: delete (static_cast(busPtr)); break; @@ -1062,7 +1352,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_NEO_3: delete (static_cast(busPtr)); break; case I_32_RN_NEO_4: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: delete (static_cast(busPtr)); break; @@ -1070,7 +1359,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_NEO_4: delete (static_cast(busPtr)); break; case I_32_RN_400_3: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: delete (static_cast(busPtr)); break; @@ -1078,7 +1366,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_400_3: delete (static_cast(busPtr)); break; case I_32_RN_TM1_4: delete (static_cast(busPtr)); break; case I_32_RN_TM2_3: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS @@ -1096,7 +1383,6 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_3: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_UCS_3: delete (static_cast(busPtr)); break; case I_32_RN_UCS_4: delete (static_cast(busPtr)); break; #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_UCS_4: delete (static_cast(busPtr)); break; @@ -1104,7 +1390,27 @@ class PolyBus { #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_UCS_4: delete (static_cast(busPtr)); break; #endif -// case I_32_BB_UCS_4: delete (static_cast(busPtr)); break; + 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_RN_FW6_5: delete (static_cast(busPtr)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_FW6_5: delete (static_cast(busPtr)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_FW6_5: delete (static_cast(busPtr)); break; + #endif + case I_32_RN_2805_5: delete (static_cast(busPtr)); break; + #ifndef WLED_NO_I2S0_PIXELBUS + case I_32_I0_2805_5: delete (static_cast(busPtr)); break; + #endif + #ifndef WLED_NO_I2S1_PIXELBUS + case I_32_I1_2805_5: delete (static_cast(busPtr)); break; + #endif #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; case I_SS_DOT_3: delete (static_cast(busPtr)); break; @@ -1164,13 +1470,19 @@ 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; + case TYPE_FW1906: + return I_8266_U0_FW6_5 + offset; + case TYPE_WS2805: + return I_8266_U0_2805_5 + offset; } #else //ESP32 - uint8_t offset = 0; //0 = RMT (num 0-7) 8 = I2S0 9 = I2S1 + uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S0 (used by Audioreactive), 2 = I2S1 #if defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32-S2 only has 4 RMT channels if (num > 4) return I_NONE; - if (num > 3) offset = 1; // only one I2S + if (num > 3) offset = 1; // only one I2S (use last to allow Audioreactive) #elif defined(CONFIG_IDF_TARGET_ESP32C3) // On ESP32-C3 only the first 2 RMT channels are usable for transmitting if (num > 1) return I_NONE; @@ -1182,7 +1494,8 @@ class PolyBus { #else // standard ESP32 has 8 RMT and 2 I2S channels if (num > 9) return I_NONE; - if (num > 7) offset = num -7; + if (num > 8) offset = 1; + if (num == 0) offset = 2; // prefer I2S1 for 1st bus (less flickering but more RAM needed) #endif switch (busType) { case TYPE_WS2812_1CH_X3: @@ -1202,11 +1515,16 @@ 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; + case TYPE_FW1906: + return I_32_RN_FW6_5 + offset; + case TYPE_WS2805: + return I_32_RN_2805_5 + offset; } #endif } return I_NONE; } }; - #endif diff --git a/wled00/button.cpp b/wled00/button.cpp index d45274a62..ce47a17ac 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -94,10 +94,15 @@ bool isButtonPressed(uint8_t i) if (digitalRead(pin) == HIGH) return true; break; case BTN_TYPE_TOUCH: + case BTN_TYPE_TOUCH_SWITCH: #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if (touchRead(pin) <= touchThreshold) return true; + #ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt) + if (touchInterruptGetLastStatus(pin)) return true; + #else + if (digitalPinToTouchChannel(btnPin[i]) >= 0 && touchRead(pin) <= touchThreshold) return true; + #endif #endif - break; + break; } return false; } @@ -106,6 +111,7 @@ void handleSwitch(uint8_t b) { // isButtonPressed() handles inverted/noninverted logic if (buttonPressedBefore[b] != isButtonPressed(b)) { + DEBUG_PRINT(F("Switch: State changed ")); DEBUG_PRINTLN(b); buttonPressedTime[b] = millis(); buttonPressedBefore[b] = !buttonPressedBefore[b]; } @@ -113,12 +119,15 @@ void handleSwitch(uint8_t b) if (buttonLongPressed[b] == buttonPressedBefore[b]) return; if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) + DEBUG_PRINT(F("Switch: Activating ")); DEBUG_PRINTLN(b); if (!buttonPressedBefore[b]) { // on -> off + DEBUG_PRINT(F("Switch: On -> Off ")); DEBUG_PRINTLN(b); if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET); else { //turn on if (!bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);} } } else { // off -> on + DEBUG_PRINT(F("Switch: Off -> On ")); DEBUG_PRINTLN(b); if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET); else { //turn off if (bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);} @@ -150,9 +159,12 @@ void handleAnalog(uint8_t b) static float filteredReading[WLED_MAX_BUTTONS] = {0.0f}; uint16_t rawReading; // raw value from analogRead, scaled to 12bit + DEBUG_PRINT(F("Analog: Reading button ")); DEBUG_PRINTLN(b); + #ifdef ESP8266 rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit #else + if ((btnPin[b] < 0) || (digitalPinToAnalogChannel(btnPin[b]) < 0)) return; // pin must support analog ADC - newer esp32 frameworks throw lots of warnings otherwise rawReading = analogRead(btnPin[b]); // collect at full 12bit resolution #endif yield(); // keep WiFi task running - analog read may take several millis on ESP8266 @@ -167,25 +179,28 @@ 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: 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()?) //unsigned long wait_started = millis(); //while(strip.isUpdating() && (millis() - wait_started < STRIP_WAIT_TIME)) { // delay(1); //} - //if (strip.isUpdating()) return; // give up oldRead[b] = aRead; // if no macro for "short press" and "long press" is defined use brightness control if (!macroButton[b] && !macroLongPress[b]) { + DEBUG_PRINT(F("Analog: Action = ")); DEBUG_PRINTLN(macroDoublePress[b]); // if "double press" macro defines which option to change if (macroDoublePress[b] >= 250) { // global brightness if (aRead == 0) { briLast = bri; bri = 0; - } else{ + } else { bri = aRead; } } else if (macroDoublePress[b] == 249) { @@ -214,6 +229,7 @@ void handleAnalog(uint8_t b) updateInterfaces(CALL_MODE_BUTTON); } } else { + DEBUG_PRINTLN(F("Analog: No action")); //TODO: // we can either trigger a preset depending on the level (between short and long entries) // or use it for RGBW direct control @@ -223,11 +239,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; } // button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0) - if (buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_PIR_SENSOR) { + if (buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_TOUCH_SWITCH || buttonType[b] == BTN_TYPE_PIR_SENSOR) { handleSwitch(b); continue; } @@ -262,7 +277,7 @@ void handleButton() shortPressAction(b); buttonPressedBefore[b] = true; buttonPressedTime[b] = now; // continually update (for debouncing to work in release handler) - return; + continue; } if (!buttonPressedBefore[b]) buttonPressedTime[b] = now; @@ -283,7 +298,7 @@ void handleButton() // released after rising-edge short press action if (macroButton[b] && macroButton[b] == macroLongPress[b] && macroButton[b] == macroDoublePress[b]) { if (dur > WLED_DEBOUNCE_THRESHOLD) buttonPressedBefore[b] = false; // debounce, blocks button for 50 ms once it has been released - return; + continue; } if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} // too short "press", debounce @@ -322,6 +337,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 @@ -330,10 +348,10 @@ void handleButton() void esp32RMTInvertIdle() { bool idle_out; - for (uint8_t u = 0; u < busses.getNumBusses(); u++) + for (uint8_t u = 0; u < BusManager::getNumBusses(); u++) { if (u > 7) return; // only 8 RMT channels, TODO: ESP32 variants have less RMT channels - Bus *bus = busses.getBus(u); + Bus *bus = BusManager::getBus(u); if (!bus || bus->getLength()==0 || !IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType())) continue; //assumes that bus number to rmt channel mapping stays 1:1 rmt_channel_t ch = static_cast(u); @@ -371,7 +389,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); @@ -388,4 +406,9 @@ void handleIO() } offMode = true; } -} \ No newline at end of file +} + +void IRAM_ATTR touchButtonISR() +{ + // used for ESP32 S2 and S3: nothing to do, ISR is just used to update registers of HAL driver +} diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 15ef0e1f0..530777ab5 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -31,25 +31,48 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { getStringFromJson(cmDNS, id[F("mdns")], 33); getStringFromJson(serverDescription, id[F("name")], 33); getStringFromJson(alexaInvocationName, id[F("inv")], 33); -#ifdef WLED_ENABLE_SIMPLE_UI CJSON(simplifiedUI, id[F("sui")]); + + JsonObject nw = doc["nw"]; +#ifndef WLED_DISABLE_ESPNOW + CJSON(enableESPNow, nw[F("espnow")]); + getStringFromJson(linked_remote, nw[F("linked_remote")], 13); + linked_remote[12] = '\0'; #endif - JsonObject nw_ins_0 = doc["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"]; @@ -74,23 +97,24 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { noWifiSleep = doc[F("wifi")][F("sleep")] | !noWifiSleep; // inverted noWifiSleep = !noWifiSleep; - //int wifi_phy = doc[F("wifi")][F("phy")]; //force phy mode n? + force802_3g = doc[F("wifi")][F("phy")] | force802_3g; //force phy mode g? JsonObject hw = doc[F("hw")]; // initialize LED pins and lengths prior to other HW (except for ethernet) JsonObject hw_led = hw["led"]; - uint8_t autoWhiteMode = RGBW_MODE_MANUAL_ONLY; - CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); - CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); - Bus::setGlobalAWMode(hw_led[F("rgbwm")] | 255); + uint16_t total = hw_led[F("total")] | strip.getLengthTotal(); + uint16_t ablMilliampsMax = hw_led[F("maxpwr")] | BusManager::ablMilliampsMax(); + BusManager::setMilliampsMax(ablMilliampsMax); + Bus::setGlobalAWMode(hw_led[F("rgbwm")] | AW_GLOBAL_DISABLED); CJSON(correctWB, hw_led["cct"]); CJSON(cctFromRgb, hw_led[F("cr")]); + CJSON(cctICused, hw_led[F("ic")]); CJSON(strip.cctBlending, hw_led[F("cb")]); Bus::setCCTBlend(strip.cctBlending); strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS - CJSON(strip.useLedsArray, hw_led[F("ld")]); + CJSON(useGlobalLedBuffer, hw_led[F("ld")]); #ifndef WLED_DISABLE_2D // 2D Matrix Settings @@ -133,8 +157,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (fromFS || !ins.isNull()) { uint8_t s = 0; // bus iterator - if (fromFS) busses.removeAll(); // can't safely manipulate busses directly in network callback - uint32_t mem = 0; + if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback + uint32_t mem = 0, globalBufMem = 0; + uint16_t maxlen = 0; bool busesChanged = false; for (JsonObject elm : ins) { if (s >= WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES) break; @@ -149,23 +174,34 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } uint16_t length = elm["len"] | 1; - uint8_t colorOrder = (int)elm[F("order")]; + uint8_t colorOrder = (int)elm[F("order")]; // contains white channel swap option in upper nibble uint8_t skipFirst = elm[F("skip")]; uint16_t start = elm["start"] | 0; if (length==0 || start + length > MAX_LEDS) continue; // zero length or we reached max. number of LEDs, just stop 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 + // To disable brightness limiter we either set output max current to 0 or single LED current to 0 (we choose output max current) + if (IS_PWM(ledType) || IS_ONOFF(ledType) || IS_VIRTUAL(ledType)) { // analog and virtual + maPerLed = 0; + maMax = 0; + } ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh - uint8_t AWmode = elm[F("rgbwm")] | autoWhiteMode; if (fromFS) { - BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz); + BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax); mem += BusManager::memUsage(bc); - if (mem <= MAX_LED_MEMORY) if (busses.add(bc) == -1) break; // finalization will be done in WLED::beginStrip() + if (useGlobalLedBuffer && start + length > maxlen) { + maxlen = start + length; + globalBufMem = maxlen * 4; + } + if (mem + globalBufMem <= MAX_LED_MEMORY) if (BusManager::add(bc) == -1) break; // finalization will be done in WLED::beginStrip() } else { if (busConfigs[s] != nullptr) delete busConfigs[s]; - busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode); + busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax); busesChanged = true; } s++; @@ -173,7 +209,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { doInitBusses = busesChanged; // finalization done in beginStrip() } - if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus + if (hw_led["rev"]) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus // read color order map configuration JsonArray hw_com = hw[F("com")]; @@ -188,15 +224,19 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { com.add(start, len, colorOrder); s++; } - busses.updateColorOrderMap(com); + BusManager::updateColorOrderMap(com); } // read multiple button configuration JsonObject btn_obj = hw["btn"]; + CJSON(touchThreshold, btn_obj[F("tt")]); 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 + } uint8_t s = 0; for (JsonObject btn : hw_btn_ins) { CJSON(buttonType[s], btn["type"]); @@ -208,10 +248,19 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (((buttonType[s] == BTN_TYPE_ANALOG) || (buttonType[s] == BTN_TYPE_ANALOG_INVERTED)) && (digitalPinToAnalogChannel(btnPin[s]) < 0)) { // not an ADC analog pin - DEBUG_PRINTF("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n", btnPin[s], s); + DEBUG_PRINT(F("PIN ALLOC error: GPIO")); DEBUG_PRINT(btnPin[s]); + DEBUG_PRINT(F("for analog button #")); DEBUG_PRINT(s); + DEBUG_PRINTLN(F(" is not an analog pin!")); btnPin[s] = -1; pinManager.deallocatePin(pin,PinOwner::Button); } + //if touch pin, enable the touch interrupt on ESP32 S2 & S3 + #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so + if ((buttonType[s] == BTN_TYPE_TOUCH || buttonType[s] == BTN_TYPE_TOUCH_SWITCH)) + { + touchAttachInterrupt(btnPin[s], touchButtonISR, 256 + (touchThreshold << 4)); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000) + } + #endif else #endif { @@ -246,24 +295,35 @@ 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; } } } - 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); if (pinManager.allocatePin(hw_ir_pin, false, PinOwner::IR)) { irPin = hw_ir_pin; } else { @@ -271,11 +331,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } } CJSON(irEnabled, hw["ir"]["type"]); + #endif CJSON(irApplyToAllSelected, hw["ir"]["sel"]); JsonObject relay = hw[F("relay")]; int hw_relay_pin = relay["pin"] | -2; if (hw_relay_pin > -2) { + pinManager.deallocatePin(rlyPin, PinOwner::Relay); if (pinManager.allocatePin(hw_relay_pin,true, PinOwner::Relay)) { rlyPin = hw_relay_pin; pinMode(rlyPin, OUTPUT); @@ -297,7 +359,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); @@ -347,10 +409,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject light_tr = light["tr"]; CJSON(fadeTransition, light_tr["mode"]); + CJSON(modeBlending, light_tr["fx"]); int tdd = light_tr["dur"] | -1; if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100; + 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"]); @@ -372,24 +437,25 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(udpPort, if_sync[F("port0")]); // 21324 CJSON(udpPort2, if_sync[F("port1")]); // 65506 - JsonObject if_sync_recv = if_sync["recv"]; +#ifndef WLED_DISABLE_ESPNOW + CJSON(useESPNowSync, if_sync[F("espnow")]); +#endif + + 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"]); CJSON(receiveGroups, if_sync_recv["grp"]); CJSON(receiveSegmentOptions, if_sync_recv["seg"]); CJSON(receiveSegmentBounds, if_sync_recv["sb"]); - //! following line might be a problem if called after boot - receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects || receiveSegmentOptions); - JsonObject if_sync_send = if_sync["send"]; - prev = notifyDirectDefault; - CJSON(notifyDirectDefault, if_sync_send[F("dir")]); - if (notifyDirectDefault != prev) notifyDirect = notifyDirectDefault; + JsonObject if_sync_send = if_sync[F("send")]; + CJSON(sendNotifications, if_sync_send["en"]); + sendNotificationsRT = sendNotifications; + CJSON(notifyDirect, if_sync_send[F("dir")]); CJSON(notifyButton, if_sync_send["btn"]); CJSON(notifyAlexa, if_sync_send["va"]); CJSON(notifyHue, if_sync_send["hue"]); - CJSON(notifyMacro, if_sync_send["macro"]); CJSON(syncGroups, if_sync_send["grp"]); if (if_sync_send[F("twice")]) udpNumRetries = 1; // import setting from 0.13 and earlier CJSON(udpNumRetries, if_sync_send["ret"]); @@ -399,13 +465,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(nodeBroadcastEnabled, if_nodes[F("bcast")]); JsonObject if_live = interfaces["live"]; - CJSON(receiveDirect, if_live["en"]); + CJSON(receiveDirect, if_live["en"]); // UDP/Hyperion realtime CJSON(useMainSegmentOnly, if_live[F("mso")]); + CJSON(realtimeRespectLedMaps, if_live[F("rlm")]); CJSON(e131Port, if_live["port"]); // 5568 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")]); @@ -432,24 +499,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { #ifdef WLED_ENABLE_MQTT JsonObject if_mqtt = interfaces["mqtt"]; CJSON(mqttEnabled, if_mqtt["en"]); - getStringFromJson(mqttServer, if_mqtt[F("broker")], 33); + getStringFromJson(mqttServer, if_mqtt[F("broker")], MQTT_MAX_SERVER_LEN+1); CJSON(mqttPort, if_mqtt["port"]); // 1883 getStringFromJson(mqttUser, if_mqtt[F("user")], 41); getStringFromJson(mqttPass, if_mqtt["psk"], 65); //normally not present due to security getStringFromJson(mqttClientID, if_mqtt[F("cid")], 41); - getStringFromJson(mqttDeviceTopic, if_mqtt[F("topics")][F("device")], 33); // "wled/test" - getStringFromJson(mqttGroupTopic, if_mqtt[F("topics")][F("group")], 33); // "" + getStringFromJson(mqttDeviceTopic, if_mqtt[F("topics")][F("device")], MQTT_MAX_TOPIC_LEN+1); // "wled/test" + getStringFromJson(mqttGroupTopic, if_mqtt[F("topics")][F("group")], MQTT_MAX_TOPIC_LEN+1); // "" CJSON(retainMqttMsg, if_mqtt[F("rtn")]); #endif -#ifndef WLED_DISABLE_ESPNOW - JsonObject remote = doc["remote"]; - CJSON(enable_espnow_remote, remote[F("remote_enabled")]); - getStringFromJson(linked_remote, remote[F("linked_remote")], 13); -#endif - - #ifndef WLED_DISABLE_HUESYNC JsonObject if_hue = interfaces["hue"]; CJSON(huePollingEnabled, if_hue["en"]); @@ -486,6 +546,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")]; @@ -575,20 +636,23 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { return (doc["sv"] | true); } + +static const char s_cfg_json[] PROGMEM = "/cfg.json"; + void deserializeConfigFromFS() { bool success = deserializeConfigSec(); + #ifdef WLED_ADD_EEPROM_SUPPORT if (!success) { //if file does not exist, try reading from EEPROM - #ifdef WLED_ADD_EEPROM_SUPPORT deEEPSettings(); return; - #endif } + #endif if (!requestJSONBufferLock(1)) return; DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); - success = readObjectFromFile("/cfg.json", nullptr, &doc); + 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 @@ -609,7 +673,8 @@ void deserializeConfigFromFS() { // NOTE: This routine deserializes *and* applies the configuration // Therefore, must also initialize ethernet from this function - bool needsSave = deserializeConfig(doc.as(), true); + JsonObject root = pDoc->as(); + bool needsSave = deserializeConfig(root, true); releaseJSONBufferLock(); if (needsSave) serializeConfig(); // usermods required new parameters @@ -622,39 +687,47 @@ void serializeConfig() { if (!requestJSONBufferLock(2)) return; - JsonArray rev = doc.createNestedArray("rev"); + JsonObject root = pDoc->to(); + + JsonArray rev = root.createNestedArray("rev"); rev.add(1); //major settings revision rev.add(0); //minor settings revision - doc[F("vid")] = VERSION; + root[F("vid")] = VERSION; - JsonObject id = doc.createNestedObject("id"); + JsonObject id = root.createNestedObject("id"); id[F("mdns")] = cmDNS; id[F("name")] = serverDescription; id[F("inv")] = alexaInvocationName; -#ifdef WLED_ENABLE_SIMPLE_UI id[F("sui")] = simplifiedUI; + + JsonObject nw = root.createNestedObject("nw"); +#ifndef WLED_DISABLE_ESPNOW + nw[F("espnow")] = enableESPNow; + nw[F("linked_remote")] = linked_remote; #endif - JsonObject nw = doc.createNestedObject("nw"); - JsonArray nw_ins = nw.createNestedArray("ins"); - - 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]); + 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 ap = doc.createNestedObject("ap"); + JsonArray dns = nw.createNestedArray(F("dns")); + for (size_t i = 0; i < 4; i++) { + dns.add(dnsAddress[i]); + } + + JsonObject ap = root.createNestedObject("ap"); ap[F("ssid")] = apSSID; ap[F("pskl")] = strlen(apPass); ap[F("chan")] = apChannel; @@ -667,12 +740,12 @@ void serializeConfig() { ap_ip.add(2); ap_ip.add(1); - JsonObject wifi = doc.createNestedObject("wifi"); + JsonObject wifi = root.createNestedObject(F("wifi")); wifi[F("sleep")] = !noWifiSleep; - //wifi[F("phy")] = 1; + wifi[F("phy")] = force802_3g; #ifdef WLED_USE_ETHERNET - JsonObject ethernet = doc.createNestedObject("eth"); + JsonObject ethernet = root.createNestedObject("eth"); ethernet["type"] = ethernetType; if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { JsonArray pins = ethernet.createNestedArray("pin"); @@ -695,18 +768,19 @@ void serializeConfig() { } #endif - JsonObject hw = doc.createNestedObject("hw"); + JsonObject hw = root.createNestedObject(F("hw")); JsonObject hw_led = hw.createNestedObject("led"); - hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade - hw_led[F("maxpwr")] = strip.ablMilliampsMax; - hw_led[F("ledma")] = strip.milliampsPerLed; + hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL + hw_led[F("maxpwr")] = BusManager::ablMilliampsMax(); + hw_led[F("ledma")] = 0; // no longer used hw_led["cct"] = correctWB; hw_led[F("cr")] = cctFromRgb; + hw_led[F("ic")] = cctICused; hw_led[F("cb")] = strip.cctBlending; hw_led["fps"] = strip.getTargetFps(); hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override - hw_led[F("ld")] = strip.useLedsArray; + hw_led[F("ld")] = useGlobalLedBuffer; #ifndef WLED_DISABLE_2D // 2D Matrix Settings @@ -730,8 +804,8 @@ void serializeConfig() { JsonArray hw_led_ins = hw_led.createNestedArray("ins"); - for (uint8_t s = 0; s < busses.getNumBusses(); s++) { - Bus *bus = busses.getBus(s); + for (uint8_t s = 0; s < BusManager::getNumBusses(); s++) { + Bus *bus = BusManager::getBus(s); if (!bus || bus->getLength()==0) break; JsonObject ins = hw_led_ins.createNestedObject(); ins["start"] = bus->getStart(); @@ -741,16 +815,18 @@ void serializeConfig() { uint8_t nPins = bus->getPins(pins); for (uint8_t i = 0; i < nPins; i++) ins_pin.add(pins[i]); ins[F("order")] = bus->getColorOrder(); - ins["rev"] = bus->reversed; + ins["rev"] = bus->isReversed(); ins[F("skip")] = bus->skippedLeds(); ins["type"] = bus->getType() & 0x7F; ins["ref"] = bus->isOffRefreshRequired(); ins[F("rgbwm")] = bus->getAutoWhiteMode(); ins[F("freq")] = bus->getFrequency(); + ins[F("maxpwr")] = bus->getMaxCurrent(); + ins[F("ledma")] = bus->getLEDCurrent(); } JsonArray hw_com = hw.createNestedArray(F("com")); - const ColorOrderMap& com = busses.getColorOrderMap(); + const ColorOrderMap& com = BusManager::getColorOrderMap(); for (uint8_t s = 0; s < com.count(); s++) { const ColorOrderMapEntry *entry = com.get(s); if (!entry) break; @@ -783,8 +859,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")); @@ -805,7 +883,7 @@ void serializeConfig() { //JsonObject hw_status = hw.createNestedObject("status"); //hw_status["pin"] = -1; - JsonObject light = doc.createNestedObject(F("light")); + JsonObject light = root.createNestedObject(F("light")); light[F("scale-bri")] = briMultiplier; light[F("pal-mode")] = strip.paletteBlend; light[F("aseg")] = autoSegments; @@ -817,9 +895,11 @@ void serializeConfig() { JsonObject light_tr = light.createNestedObject("tr"); light_tr["mode"] = fadeTransition; + light_tr["fx"] = modeBlending; 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; @@ -827,18 +907,22 @@ void serializeConfig() { light_nl[F("tbri")] = nightlightTargetBri; light_nl["macro"] = macroNl; - JsonObject def = doc.createNestedObject("def"); + JsonObject def = root.createNestedObject("def"); def["ps"] = bootPreset; def["on"] = turnOnAtBoot; def["bri"] = briS; - JsonObject interfaces = doc.createNestedObject("if"); + JsonObject interfaces = root.createNestedObject("if"); JsonObject if_sync = interfaces.createNestedObject("sync"); if_sync[F("port0")] = udpPort; if_sync[F("port1")] = udpPort2; - JsonObject if_sync_recv = if_sync.createNestedObject("recv"); +#ifndef WLED_DISABLE_ESPNOW + if_sync[F("espnow")] = useESPNowSync; +#endif + + JsonObject if_sync_recv = if_sync.createNestedObject(F("recv")); if_sync_recv["bri"] = receiveNotificationBrightness; if_sync_recv["col"] = receiveNotificationColor; if_sync_recv["fx"] = receiveNotificationEffects; @@ -846,12 +930,12 @@ 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; if_sync_send["va"] = notifyAlexa; if_sync_send["hue"] = notifyHue; - if_sync_send["macro"] = notifyMacro; if_sync_send["grp"] = syncGroups; if_sync_send["ret"] = udpNumRetries; @@ -860,8 +944,9 @@ void serializeConfig() { if_nodes[F("bcast")] = nodeBroadcastEnabled; JsonObject if_live = interfaces.createNestedObject("live"); - if_live["en"] = receiveDirect; + if_live["en"] = receiveDirect; // UDP/Hyperion realtime if_live[F("mso")] = useMainSegmentOnly; + if_live[F("rlm")] = realtimeRespectLedMaps; if_live["port"] = e131Port; if_live[F("mc")] = e131Multicast; @@ -878,6 +963,7 @@ void serializeConfig() { if_live[F("no-gc")] = arlsDisableGammaCorrection; if_live[F("offset")] = arlsOffset; +#ifndef WLED_DISABLE_ALEXA JsonObject if_va = interfaces.createNestedObject("va"); if_va[F("alexa")] = alexaEnabled; @@ -886,6 +972,7 @@ void serializeConfig() { if_va_macros.add(macroAlexaOff); if_va["p"] = alexaNumPresets; +#endif #ifdef WLED_ENABLE_MQTT JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); @@ -902,20 +989,13 @@ void serializeConfig() { if_mqtt_topics[F("group")] = mqttGroupTopic; #endif -#ifndef WLED_DISABLE_ESPNOW - JsonObject remote = doc.createNestedObject(F("remote")); - remote[F("remote_enabled")] = enable_espnow_remote; - remote[F("linked_remote")] = linked_remote; -#endif - - #ifndef WLED_DISABLE_HUESYNC JsonObject if_hue = interfaces.createNestedObject("hue"); if_hue["en"] = huePollingEnabled; 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; @@ -935,7 +1015,7 @@ void serializeConfig() { if_ntp[F("ln")] = longitude; if_ntp[F("lt")] = latitude; - JsonObject ol = doc.createNestedObject("ol"); + JsonObject ol = root.createNestedObject("ol"); ol[F("clock")] = overlayCurrent; ol[F("cntdwn")] = countdownMode; @@ -944,8 +1024,9 @@ void serializeConfig() { ol[F("o12pix")] = analogClock12pixel; ol[F("o5m")] = analogClock5MinuteMarks; ol[F("osec")] = analogClockSecondsTrail; + ol[F("osb")] = analogClockSolidBlack; - JsonObject timers = doc.createNestedObject(F("timers")); + JsonObject timers = root.createNestedObject(F("timers")); JsonObject cntdwn = timers.createNestedObject(F("cntdwn")); JsonArray goal = cntdwn.createNestedArray(F("goal")); @@ -973,14 +1054,14 @@ void serializeConfig() { } } - JsonObject ota = doc.createNestedObject("ota"); + JsonObject ota = root.createNestedObject("ota"); ota[F("lock")] = otaLock; ota[F("lock-wifi")] = wifiLock; ota[F("pskl")] = strlen(otaPass); ota[F("aota")] = aOtaEnabled; #ifdef WLED_ENABLE_DMX - JsonObject dmx = doc.createNestedObject("dmx"); + JsonObject dmx = root.createNestedObject("dmx"); dmx[F("chan")] = DMXChannels; dmx[F("gap")] = DMXGap; dmx["start"] = DMXStart; @@ -994,36 +1075,50 @@ void serializeConfig() { dmx[F("e131proxy")] = e131ProxyUniverse; #endif - JsonObject usermods_settings = doc.createNestedObject("um"); + JsonObject usermods_settings = root.createNestedObject("um"); usermods.addToConfig(usermods_settings); - File f = WLED_FS.open("/cfg.json", "w"); - if (f) serializeJson(doc, f); + File f = WLED_FS.open(FPSTR(s_cfg_json), "w"); + if (f) serializeJson(root, f); f.close(); releaseJSONBufferLock(); 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, &doc); + bool success = readObjectFromFile(s_wsec_json, nullptr, pDoc); if (!success) { releaseJSONBufferLock(); return false; } - JsonObject nw_ins_0 = doc["nw"]["ins"][0]; - getStringFromJson(clientPass, nw_ins_0["psk"], 65); + JsonObject root = pDoc->as(); - JsonObject ap = doc["ap"]; + 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); - JsonObject interfaces = doc["if"]; + [[maybe_unused]] JsonObject interfaces = root["if"]; #ifdef WLED_ENABLE_MQTT JsonObject if_mqtt = interfaces["mqtt"]; @@ -1034,10 +1129,10 @@ bool deserializeConfigSec() { getStringFromJson(hueApiKey, interfaces["hue"][F("key")], 47); #endif - getStringFromJson(settingsPIN, doc["pin"], 5); + getStringFromJson(settingsPIN, root["pin"], 5); correctPIN = !strlen(settingsPIN); - JsonObject ota = doc["ota"]; + JsonObject ota = root["ota"]; getStringFromJson(otaPass, ota[F("pwd")], 33); CJSON(otaLock, ota[F("lock")]); CJSON(wifiLock, ota[F("lock-wifi")]); @@ -1052,17 +1147,20 @@ void serializeConfigSec() { if (!requestJSONBufferLock(4)) return; - JsonObject nw = doc.createNestedObject("nw"); + JsonObject root = pDoc->to(); + + JsonObject nw = root.createNestedObject("nw"); JsonArray nw_ins = nw.createNestedArray("ins"); + for (size_t i = 0; i < multiWiFi.size(); i++) { + JsonObject wifi = nw_ins.createNestedObject(); + wifi[F("psk")] = multiWiFi[i].clientPass; + } - JsonObject nw_ins_0 = nw_ins.createNestedObject(); - nw_ins_0["psk"] = clientPass; - - JsonObject ap = doc.createNestedObject("ap"); + JsonObject ap = root.createNestedObject("ap"); ap["psk"] = apPass; - JsonObject interfaces = doc.createNestedObject("if"); + [[maybe_unused]] JsonObject interfaces = root.createNestedObject("if"); #ifdef WLED_ENABLE_MQTT JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); if_mqtt["psk"] = mqttPass; @@ -1072,16 +1170,16 @@ void serializeConfigSec() { if_hue[F("key")] = hueApiKey; #endif - doc["pin"] = settingsPIN; + root["pin"] = settingsPIN; - JsonObject ota = doc.createNestedObject("ota"); + JsonObject ota = root.createNestedObject("ota"); ota[F("pwd")] = otaPass; ota[F("lock")] = otaLock; ota[F("lock-wifi")] = wifiLock; ota[F("aota")] = aOtaEnabled; - File f = WLED_FS.open("/wsec.json", "w"); - if (f) serializeJson(doc, f); + 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 8c4baabb5..3ed54d959 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -35,26 +35,171 @@ uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) * color add function that preserves ratio * idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule */ -uint32_t color_add(uint32_t c1, uint32_t c2) +uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) { - uint32_t r = R(c1) + R(c2); - uint32_t g = G(c1) + G(c2); - uint32_t b = B(c1) + B(c2); - uint32_t w = W(c1) + W(c2); - uint16_t max = r; - if (g > max) max = g; - if (b > max) max = b; - if (w > max) max = w; - if (max < 256) return RGBW32(r, g, b, w); - else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max); + if (fast) { + uint8_t r = R(c1); + uint8_t g = G(c1); + uint8_t b = B(c1); + uint8_t w = W(c1); + r = qadd8(r, R(c2)); + g = qadd8(g, G(c2)); + b = qadd8(b, B(c2)); + w = qadd8(w, W(c2)); + return RGBW32(r,g,b,w); + } else { + uint32_t r = R(c1) + R(c2); + uint32_t g = G(c1) + G(c2); + uint32_t b = B(c1) + B(c2); + uint32_t w = W(c1) + W(c2); + uint16_t max = r; + if (g > max) max = g; + if (b > max) max = b; + if (w > max) max = w; + if (max < 256) return RGBW32(r, g, b, w); + else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max); + } +} + +/* + * fades color toward black + * if using "video" method the resulting color will never become black unless it is already black + */ +uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) +{ + uint8_t r = R(c1); + uint8_t g = G(c1); + uint8_t b = B(c1); + uint8_t w = W(c1); + if (video) { + r = scale8_video(r, amount); + g = scale8_video(g, amount); + b = scale8_video(b, amount); + w = scale8_video(w, amount); + } else { + r = scale8(r, amount); + g = scale8(g, amount); + b = scale8(b, amount); + w = scale8(w, amount); + } + return RGBW32(r, g, b, w); } void setRandomColor(byte* rgb) { - lastRandomIndex = strip.getMainSegment().get_random_wheel_index(lastRandomIndex); + lastRandomIndex = get_random_wheel_index(lastRandomIndex); 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 91f3fde53..0ce7b27d5 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 @@ -32,24 +53,16 @@ #define WLED_MAX_BUSSES 3 // will allow 2 digital & 1 analog (or the other way around) #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB - #if defined(USERMOD_AUDIOREACTIVE) // requested by @softhack007 https://github.com/blazoncek/WLED/issues/33 - #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog - #define WLED_MIN_VIRTUAL_BUSSES 4 - #else - #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog - #define WLED_MIN_VIRTUAL_BUSSES 3 - #endif + // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) + #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog + #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog #define WLED_MIN_VIRTUAL_BUSSES 4 #else - #if defined(USERMOD_AUDIOREACTIVE) // requested by @softhack007 https://github.com/blazoncek/WLED/issues/33 - #define WLED_MAX_BUSSES 8 - #define WLED_MIN_VIRTUAL_BUSSES 2 - #else - #define WLED_MAX_BUSSES 10 - #define WLED_MIN_VIRTUAL_BUSSES 0 - #endif + // the 10th digital bus (I2S0) will prevent Audioreactive usermod from functioning (it is last used though) + #define WLED_MAX_BUSSES 10 + #define WLED_MIN_VIRTUAL_BUSSES 0 #endif #endif #else @@ -72,6 +85,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 @@ -147,13 +165,25 @@ #define USERMOD_ID_SD_CARD 37 //Usermod "usermod_sd_card.h" #define USERMOD_ID_PWM_OUTPUTS 38 //Usermod "usermod_pwm_outputs.h #define USERMOD_ID_SHT 39 //Usermod "usermod_sht.h -#define USERMOD_ID_KLIPPER 40 // Usermod Klipper percentage +#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_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" +#define USERMOD_ID_TETRISAI 47 //Usermod "usermod_v2_tetris.h" +#define USERMOD_ID_MAX17048 48 //Usermod "usermod_max17048.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 @@ -164,7 +194,7 @@ #define CALL_MODE_NO_NOTIFY 5 #define CALL_MODE_FX_CHANGED 6 //no longer used #define CALL_MODE_HUE 7 -#define CALL_MODE_PRESET_CYCLE 8 +#define CALL_MODE_PRESET_CYCLE 8 //no longer used #define CALL_MODE_BLYNK 9 //no longer used #define CALL_MODE_ALEXA 10 #define CALL_MODE_WS_SEND 11 //special call mode, not for notifier, updates websocket only @@ -204,8 +234,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 @@ -222,7 +252,7 @@ #define TYPE_NONE 0 //light is not configured #define TYPE_RESERVED 1 //unused. Might indicate a "virtual" light -//Digital types (data pin only) (16-31) +//Digital types (data pin only) (16-39) #define TYPE_WS2812_1CH 18 //white-only chips (1 channel per IC) (unused) #define TYPE_WS2812_1CH_X3 19 //white-only chips (3 channels per IC) #define TYPE_WS2812_2CH_X3 20 //CCT chips (1st IC controls WW + CW of 1st zone and CW of 2nd zone, 2nd IC controls WW of 2nd zone and WW + CW of 3rd zone) @@ -232,11 +262,14 @@ #define TYPE_WS2811_400KHZ 24 //half-speed WS2812 protocol, used by very old WS2811 units #define TYPE_TM1829 25 #define TYPE_UCS8903 26 -#define TYPE_UCS8904 29 +#define TYPE_APA106 27 +#define TYPE_FW1906 28 //RGB + CW + WW + unused channel (6 channels per IC) +#define TYPE_UCS8904 29 //first RGBW digital type (hardcoded in busmanager.cpp, memUsage()) #define TYPE_SK6812_RGBW 30 #define TYPE_TM1814 31 -//"Analog" types (PWM) (32-47) -#define TYPE_ONOFF 40 //binary output (relays etc.) +#define TYPE_WS2805 32 //RGB + WW + CW +//"Analog" types (40-47) +#define TYPE_ONOFF 40 //binary output (relays etc.; NOT PWM) #define TYPE_ANALOG_1CH 41 //single channel PWM. Uses value of brightest RGBW channel #define TYPE_ANALOG_2CH 42 //analog WW + CW #define TYPE_ANALOG_3CH 43 //analog RGB @@ -253,11 +286,16 @@ #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_DIGITAL(t) ((t) & 0x10) //digital are 16-31 and 48-63 -#define IS_PWM(t) ((t) > 40 && (t) < 46) -#define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only -#define IS_2PIN(t) ((t) > 47) +#define IS_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 +#define IS_2PIN(t) ((t) > 47 && (t) < 64) +#define IS_16BIT(t) ((t) == TYPE_UCS8903 || (t) == TYPE_UCS8904) +#define IS_ONOFF(t) ((t) == 40) +#define IS_PWM(t) ((t) > 40 && (t) < 46) //does not include on/Off type +#define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only +#define IS_VIRTUAL(t) ((t) >= 80 && (t) < 96) //this was a poor choice a better would be 96-111 //Color orders #define COL_ORDER_GRB 0 //GRB(w),defaut @@ -268,6 +306,10 @@ #define COL_ORDER_GBR 5 #define COL_ORDER_MAX 5 +//ESP-NOW +#define ESP_NOW_STATE_UNINIT 0 +#define ESP_NOW_STATE_ON 1 +#define ESP_NOW_STATE_ERROR 2 //Button type #define BTN_TYPE_NONE 0 @@ -279,21 +321,23 @@ #define BTN_TYPE_TOUCH 6 #define BTN_TYPE_ANALOG 7 #define BTN_TYPE_ANALOG_INVERTED 8 +#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 @@ -311,22 +355,22 @@ #define SEG_OPTION_MIRROR 3 //Indicates that the effect will be mirrored within the segment #define SEG_OPTION_FREEZE 4 //Segment contents will not be refreshed #define SEG_OPTION_RESET 5 //Segment runtime requires reset -#define SEG_OPTION_TRANSITIONAL 6 -#define SEG_OPTION_REVERSED_Y 7 -#define SEG_OPTION_MIRROR_Y 8 -#define SEG_OPTION_TRANSPOSED 9 +#define SEG_OPTION_REVERSED_Y 6 +#define SEG_OPTION_MIRROR_Y 7 +#define SEG_OPTION_TRANSPOSED 8 //Segment differs return byte #define SEG_DIFFERS_BRI 0x01 // opacity #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 //Playlist option byte #define PL_OPTION_SHUFFLE 0x01 +#define PL_OPTION_RESTORE 0x02 // Segment capability byte #define SEG_CAPABILITY_RGB 0x01 @@ -336,14 +380,17 @@ // WLED Error modes #define ERR_NONE 0 // All good :) #define ERR_DENIED 1 // Permission denied -#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?) OBSOLETE +#define ERR_CONCURRENCY 2 // Conurrency (client active) #define ERR_NOBUF 3 // JSON buffer was not released in time, request cannot be handled at this time +#define ERR_NOT_IMPL 4 // Not implemented +#define ERR_NORAM 8 // effect RAM depleted #define ERR_JSON 9 // JSON parsing failed (input too large?) #define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?) #define ERR_FS_QUOTA 11 // The FS is full or the maximum file size is reached #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_GENERAL 19 // A general unspecified filesystem error occured +#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 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) @@ -372,7 +419,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 @@ -403,7 +451,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 @@ -441,7 +489,11 @@ #ifdef ESP8266 #define JSON_BUFFER_SIZE 10240 #else - #define JSON_BUFFER_SIZE 24576 + #if defined(ARDUINO_ARCH_ESP32S2) + #define JSON_BUFFER_SIZE 24576 + #else + #define JSON_BUFFER_SIZE 32767 + #endif #endif //#define MIN_HEAP_SIZE (8k for AsyncWebServer) @@ -456,10 +508,10 @@ //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(CONFIG_IDF_TARGET_ESP32C3) //|| (defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(ARDUINO_ESP32_PICO) + #define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board #else - #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards + #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) #endif #endif @@ -481,7 +533,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 @@ -504,7 +556,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 5a8c801e5..a4b913592 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -1,6 +1,7 @@ + @@ -45,6 +46,7 @@ width: 7px; top: 50%; transform: translateY(-50%); + touch-action: none; } .color-picker-marker { height: 7px; @@ -94,9 +96,14 @@ line-height: 1; } .wrap { - width: 800px; + width: 100%; margin: 0 auto; } + @media (min-width: 800px) { + .wrap { + width: 800px; + } + } .palette { height: 20px; } @@ -136,6 +143,9 @@ .sendSpan, .editSpan{ cursor: pointer; } + h1 { + font-size: 1.6rem; + } @@ -191,6 +201,7 @@ var gradientLength = rect.width; var mOffs = Math.round((gradientLength / 256) / 2) - 5; var paletteArray = []; //Holds the palettes after load. + var paletteName = []; // Holds the names of the palettes after load. var svgSave = '' var svgEdit = '' @@ -349,24 +360,31 @@ var gradientLength = maxX - minX + 1; elmnt.onmousedown = dragMouseDown; + elmnt.ontouchstart = dragMouseDown; function dragMouseDown(e) { removeTrashcan(event) e = e || window.event; - e.preventDefault(); + var isTouch = e.type.startsWith('touch'); + if (!isTouch) e.preventDefault(); // get the mouse cursor position at startup: - mousePos = e.clientX; + mousePos = isTouch ? e.touches[0].clientX : e.clientX; d.onmouseup = closeDragElement; + d.ontouchcancel = closeDragElement; + d.ontouchend = closeDragElement; // call a function whenever the cursor moves: d.onmousemove = elementDrag; + d.ontouchmove = elementDrag; } function elementDrag(e) { e = e || window.event; - e.preventDefault(); + var isTouch = e.type.startsWith('touch'); + if (!isTouch) e.preventDefault(); // calculate the new cursor position: - posNew = mousePos - e.clientX; - mousePos = e.clientX; + var clientX = isTouch ? e.touches[0].clientX : e.clientX; + posNew = mousePos - clientX; + mousePos = clientX; mousePosInGradient = mousePos - (minX + 1) truePos = Math.round((mousePosInGradient/gradientLength)*256); @@ -393,7 +411,10 @@ function closeDragElement() { /* stop moving when mouse button is released:*/ d.onmouseup = null; + d.ontouchcancel = null; + d.ontouchend = null; d.onmousemove = null; + d.ontouchmove = null; } } @@ -485,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"); @@ -500,8 +521,10 @@ if (hst.length > 0 ) { try { var arr = []; - const response = await fetch('http://'+hst+'/json/info'); - const json = await response.json(); + const responseInfo = await fetch('http://'+hst+'/json/info'); + const responsePalettes = await fetch('http://'+hst+'/json/palettes'); + const json = await responseInfo.json(); + paletteName = await responsePalettes.json(); cpalc = json.cpalcount; fetchPalettes(cpalc-1); } catch (error) { @@ -521,6 +544,7 @@ const json = await response.json(); paletteArray.push(json); } catch (error) { + cpalc--; //remove audio/dynamically generated palettes console.error(`Error fetching JSON from ${url}: `, error); } } @@ -530,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 @@ -540,6 +564,7 @@ alert("The cache of palettes are missig from your browser. You should probably return to the main page and let it load properly for the palettes cache to regenerate before returning here.","Missing cached palettes!") } else { for (const key in wledPalx.p) { + wledPalx.p[key].name = paletteName[key]; if (key > 245) { delete wledPalx.p[key]; continue; @@ -571,8 +596,11 @@ } const pArray = Object.entries(wledPalx.p).map(([key, value]) => ({ - [key]: value.flat() + [key]: value.flat(), + name: value.name })); + // Sort pArray by name + pArray.sort((a, b) => a.name.localeCompare(b.name)); paletteArray.push( ...pArray); } @@ -614,6 +642,9 @@ editSpan.id = `editSpan${i}`; editSpan.onclick = function() {loadForEdit(i)}; editSpan.setAttribute('title', `Copy slot ${i} palette to editor`); + if (paletteArray[i].name) { + editSpan.setAttribute('title', `Copy ${paletteArray[i].name} palette to editor`); + } editSpan.innerHTML = svgEdit; editSpan.classList.add("editSpan") diff --git a/wled00/data/index.css b/wled00/data/index.css index 049e6f03e..fa6e20077 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -20,8 +20,8 @@ --c-g: #2c1; --c-l: #48a; --c-y: #a90; - --t-b: 0.5; - --c-o: rgba(34, 34, 34, 0.9); + --t-b: .5; + --c-o: rgba(34, 34, 34, .9); --c-tb : rgba(34, 34, 34, var(--t-b)); --c-tba: rgba(102, 102, 102, var(--t-b)); --c-tbh: rgba(51, 51, 51, var(--t-b)); @@ -33,7 +33,8 @@ --bbp: 9px 0 7px 0; --bhd: none; --sgp: "block"; - --bmt: 0px; + --bmt: 0; + --sti: 42px; } html { @@ -88,7 +89,7 @@ a, a:visited { } button { - outline: none; + outline: 0; cursor: pointer; } @@ -219,7 +220,7 @@ button { .pop-c span { padding: 2px 6px; } - + .search-icon { position: absolute; top: 8px; @@ -238,7 +239,7 @@ button { .flr { color: var(--c-f); transform: rotate(0deg); - transition: transform 0.3s; + transition: transform .3s; position: absolute; top: 0; right: 0; @@ -258,13 +259,13 @@ button { #liveview { height: 4px; width: 100%; - border: 0px; + border: 0; } #liveview2D { height: 90%; width: 90%; - border: 0px; + border: 0; position: absolute; top: 50%; left: 50%; @@ -287,8 +288,8 @@ button { .tab button { background-color: transparent; float: left; - border: none; - transition: color 0.3s, background-color 0.3s; + border: 0; + transition: color .3s, background-color .3s; font-size: 17px; color: var(--c-c); min-width: 44px; @@ -301,7 +302,7 @@ button { .bot button { padding: var(--bbp); - width:25%; + width: 25%; margin: 0; } @@ -336,18 +337,23 @@ button { width: 100%; width: calc(100%/var(--n)); box-sizing: border-box; - border: 0px; - overflow: auto; + border: 0; + overflow-y: auto; + overflow-x: hidden; height: 100%; overscroll-behavior: none; padding: 0 4px; -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 { @@ -359,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; } @@ -368,6 +374,11 @@ button { padding: 5px 0 0; } +/* Quick load magin for simplified UI */ +.simplified #pql, .simplified #palw, .simplified #fx { + margin-bottom: 8px; +} + .smooth { transition: transform calc(var(--f, 1)*.5s) ease-out } .tab-label { @@ -388,8 +399,8 @@ button { align-items: center; justify-content: center; z-index: 11; - opacity: 0.95; - transition: 0.7s; + opacity: .95; + transition: .7s; pointer-events: none; } @@ -399,7 +410,7 @@ button { position: sticky !important; top: 0; z-index: 2; - margin: 0 auto auto; + margin: 0 auto auto; } .staybot { @@ -411,6 +422,7 @@ button { position: sticky; bottom: 0; max-width: 300px; + z-index: 2; } #sliders .labels { @@ -456,65 +468,56 @@ button { padding: 4px 2px; position: relative; opacity: 1; - transition: opacity 0.5s linear, height 0.5s, transform 0.5s; + transition: opacity .5s linear, height .25s, transform .25s; } .filter { z-index: 1; - overflow: hidden; + /*overflow: visible;*/ + border-radius: 0 0 16px 16px; + max-width: 220px; + height: 54px; + line-height: 1.5; + padding-bottom: 8px; + pointer-events: none; } -/* Tooltip text */ -.slider .tooltiptext, .option .tooltiptext { +/* New tooltip */ +.tooltip { + position: absolute; + opacity: 0; visibility: hidden; + transition: opacity .25s ease, visibility .25s ease; background-color: var(--c-5); - /*border: 2px solid var(--c-2);*/ box-shadow: 4px 4px 10px 4px var(--c-1); color: var(--c-f); text-align: center; - padding: 4px 8px; + padding: 8px 16px; border-radius: 6px; - - /* Position the tooltip text */ - width: 160px; - position: absolute; z-index: 1; - bottom: 80%; - left: 50%; - margin-left: -92px; - - /* Ensure tooltip goes away when mouse leaves control */ pointer-events: none; +} - /* Fade in tooltip */ - opacity: 0; - transition: opacity 0.75s; -} -.option .tooltiptext { - bottom: 120%; -} -/* Tooltip arrow */ -.slider .tooltiptext::after, .option .tooltiptext::after { + .tooltip::after { content: ""; position: absolute; - top: 100%; - left: 50%; - margin-left: -5px; - border-width: 5px; - border-style: solid; + border: 8px solid; border-color: var(--c-5) transparent transparent transparent; -} -/* Show the tooltip text when you mouse over the tooltip container */ -.slider:hover .tooltiptext, .option .check:hover .tooltiptext { - visibility: visible; + top: 100%; + left: calc(50% - 8px); + z-index: 0; + } + +.tooltip.visible { opacity: 1; + visibility: visible; } .fade { visibility: hidden; /* hide it */ opacity: 0; /* make it transparent */ transform: scaleY(0); /* shrink content */ - height: 0px; /* force other elements to move */ + height: 0; /* force other elements to move */ padding: 0; /* remove empty space */ } @@ -542,24 +545,24 @@ button { #toast.show { opacity: 1; - animation: fadein 0.5s, fadein 0.5s 2.5s reverse; + animation: fadein .5s, fadein .5s 2.5s reverse; } #toast.error { opacity: 1; background-color: #b21; - animation: fadein 0.5s; + animation: fadein .5s; } .modal { - position:fixed; - left: 0px; - bottom: 0px; - right: 0px; + position: fixed; + left: 0; + bottom: 0; + right: 0; top: calc(var(--th) - 1px); background-color: var(--c-o); transform: translateY(100%); - transition: transform 0.4s; + transition: transform .4s; padding: 8px; font-size: 20px; overflow: auto; @@ -620,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; @@ -641,7 +642,7 @@ button { } #heart { - transition: color 0.9s; + transition: color .9s; font-size: 16px; color: #f00; } @@ -720,7 +721,7 @@ input[type=range] { } input[type=range]:focus { - outline: none; + outline: 0; } input[type=range]::-webkit-slider-runnable-track { width: 100%; @@ -743,7 +744,7 @@ input[type=range]::-moz-range-track { background-color: rgba(0, 0, 0, 0); } input[type=range]::-moz-range-thumb { - border: 0px solid rgba(0, 0, 0, 0); + border: 0 solid rgba(0, 0, 0, 0); height: 16px; width: 16px; border-radius: 50%; @@ -761,39 +762,43 @@ input[type=range]::-moz-range-thumb { } #Colors .sliderwrap { - margin: 4px 0 0; + margin: 2px 0 0; } -/* Dynamically hide brightness slider label */ +/* Dynamically hide labels */ .hd { display: var(--bhd); } +/* Do not hide quick load label in simplified mode on small screen widths */ +.simplified #pql .hd { + display: var(--bhd) !important; +} #briwrap { - min-width: 267px; + min-width: 300px; float: right; margin-top: var(--bmt); } #picker { - margin-top: 8px !important; - max-width: 260px; + 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); cursor: pointer; border-radius: 25px; - transition-duration: 0.3s; + transition-duration: .3s; -webkit-backface-visibility: hidden; - -webkit-transform:translate3d(0,0,0); + -webkit-transform: translate3d(0,0,0); backface-visibility: hidden; - transform:translate3d(0,0,0); + transform: translate3d(0,0,0); overflow: hidden; text-overflow: ellipsis; border: 1px solid var(--c-3); @@ -829,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; @@ -855,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 { @@ -893,21 +907,18 @@ select { cursor: pointer; border: 0 solid var(--c-2); border-radius: 20px; - transition-duration: 0.5s; + transition-duration: .5s; -webkit-backface-visibility: hidden; - -webkit-transform:translate3d(0,0,0); - -webkit-appearance: none; - -moz-appearance: none; + -webkit-transform: translate3d(0,0,0); + -webkit-appearance: none; + -moz-appearance: none; backface-visibility: hidden; - transform:translate3d(0,0,0); + transform: translate3d(0,0,0); text-overflow: ellipsis; } #tt { text-align: center; } -.cl { - background-color: #000; -} select.sel-p, select.sel-pl, select.sel-ple { margin: 5px 0; width: 100%; @@ -917,15 +928,15 @@ div.sel-p { position: relative; } div.sel-p:after { - content: ""; - position: absolute; - right: 10px; - top: 22px; - width: 0; - height: 0; - border-left: 8px solid transparent; - border-right: 8px solid transparent; - border-top: 8px solid var(--c-f); + content: ""; + position: absolute; + right: 10px; + top: 22px; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid var(--c-f); } select.sel-ple { text-align: center; @@ -942,13 +953,13 @@ input[type=number], input[type=text] { background: var(--c-3); color: var(--c-f); - border: 0px solid var(--c-2); + border: 0 solid var(--c-2); border-radius: 10px; padding: 8px; /*margin: 6px 6px 6px 0;*/ font-size: 19px; - transition: background-color 0.2s; - outline: none; + transition: background-color .2s; + outline: 0; -webkit-appearance: textfield; -moz-appearance: textfield; appearance: textfield; @@ -988,7 +999,7 @@ textarea { height: 90px; border-radius: 5px; border: 2px solid var(--c-5); - outline: none; + outline: 0; resize: none; font-size: 19px; padding: 5px; @@ -1010,7 +1021,7 @@ textarea { width: 50px !important; } -.segname, .pname, .bname { +.segname, .pname { white-space: nowrap; text-align: center; overflow: hidden; @@ -1020,9 +1031,6 @@ textarea { max-width: 170px; position: relative; } -.bname { - padding: 0 24px; -} .segname .flr, .pname .flr { transform: rotate(0deg); @@ -1034,13 +1042,14 @@ textarea { /*padding: 1px 0 1px 20px;*/ display: var(--sgp); width: 100%; + position: relative; } .pname { top: 1px; } .plname { - top:0; + top: 0; } /* preset id number */ @@ -1056,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; } @@ -1120,8 +1126,8 @@ textarea { } .revchkl { - padding: 4px 0px 0px 35px; - margin-bottom: 0px; + padding: 4px 0 0 35px; + margin-bottom: 0; margin-top: 8px; } @@ -1217,9 +1223,9 @@ TD .checkmark, TD .radiomark { .seg, .pres { background-color: var(--c-2); /*color: var(--c-f);*/ /* seems to affect only the Add segment button, which should be same color as reset segments */ - border: 0px solid var(--c-f); + border: 0 solid var(--c-f); text-align: left; - transition: background-color 0.5s; + transition: background-color .5s; border-radius: 21px; } @@ -1236,14 +1242,19 @@ TD .checkmark, TD .radiomark { /* checkmark labels */ .filter .fchkl, .option .ochkl { display: inline-block; - min-width: 0.7em; - padding: 1px 4px 4px 32px; + min-width: .7em; + padding: 1px 4px 1px 32px; text-align: left; line-height: 24px; vertical-align: middle; -webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */ filter: grayscale(100%); } +.filter .fchkl { + margin: 0 4px; + min-width: 20px; + pointer-events: auto; +} .lbl-l { font-size: 13px; @@ -1262,27 +1273,30 @@ TD .checkmark, TD .radiomark { /* list wrapper */ .list { position: relative; - transition: background-color 0.5s; - margin: auto auto 10px; + transition: background-color .5s; + margin: auto auto 10px; line-height: 24px; } /* list item */ .lstI { align-items: center; - cursor: pointer; + cursor: pointer; background-color: var(--c-2); overflow: hidden; 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); + width: 100%; } -#segutil .lstI { - margin-top: 0; +/* Simplify segments */ +.simplified #segcont .lstI { + margin-top: 4px; + min-height: unset; } /* selected item/element */ @@ -1292,7 +1306,7 @@ TD .checkmark, TD .radiomark { #segcont .seg:hover:not([class*="expanded"]), .lstI:hover:not([class*="expanded"]) { - background: var(--c-5); + background: var(--c-5); } .selected .checkmark, @@ -1312,7 +1326,7 @@ TD .checkmark, TD .radiomark { .lstI.sticky, .lstI.selected { z-index: 1; - box-shadow: 0px 0px 10px 4px var(--c-1); + box-shadow: 0 0 10px 4px var(--c-1); } #pcont .selected:not([class*="expanded"]) { @@ -1320,20 +1334,27 @@ TD .checkmark, TD .radiomark { top: 42px; } -#fxlist .lstI.selected { - top: 84px; -} - -#fxlist .lstI.sticky { - top: 42px; -} - +#fxlist .lstI.selected, #pallist .lstI.selected { - top: 84px; + top: calc(var(--sti) + 42px); } +dialog::backdrop { + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} +dialog { + max-height: 70%; + border: 0; + border-radius: 10px; + background: linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.1)), var(--c-3); + box-shadow: 4px 4px 10px 4px var(--c-1); + color: var(--c-f); +} + +#fxlist .lstI.sticky, #pallist .lstI.sticky { - top: 42px; + top: var(--sti); } /* list item content */ @@ -1369,8 +1390,8 @@ TD .checkmark, TD .radiomark { display: block; width: 100%; box-sizing: border-box; - padding: 8px 40px 8px 44px; - margin: 5px auto 0; + padding: 8px 40px 8px 44px; + margin: 4px auto 12px; text-align: left; border-radius: 21px; background: var(--c-2); @@ -1388,6 +1409,13 @@ TD .checkmark, TD .radiomark { background-color: var(--c-3); } +#fxFind.fnd input[type="text"] { + margin-bottom: 0; +} +#fxFind { + margin-bottom: 12px; +} + /* segment & preset inner/expanded content */ .segin, .presin { @@ -1456,7 +1484,7 @@ TD .checkmark, TD .radiomark { } ::-webkit-scrollbar-thumb { background: var(--c-sb); - opacity: 0.2; + opacity: .2; border-radius: 5px; } ::-webkit-scrollbar-thumb:hover { @@ -1482,7 +1510,7 @@ TD .checkmark, TD .radiomark { @media all and (max-width: 335px) { .sliderbubble { - display: none; + display: none; } } @@ -1493,7 +1521,7 @@ TD .checkmark, TD .radiomark { #info .infobtn, #nodes .infobtn { width: 145px; } - #info div, #nodes div { + #info div, #nodes div, #nodes a.btn { max-width: 320px; } } @@ -1539,9 +1567,6 @@ TD .checkmark, TD .radiomark { max-width: 280px; font-size: 18px; } - #picker { - width: 230px; - } #putil .btn-s { width: 114px; } diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 0cf48d6e6..4a532abb7 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -7,52 +7,9 @@ WLED - - +
Loading WLED UI...
@@ -72,8 +29,8 @@

Brightness

-
- +
+
@@ -88,99 +45,92 @@
-
-
+
+
- Hue
-
+
- Saturation
-
+
- Value/Brightness
-
+
- Kelvin/Temperature
-
-
+
- Red channel
-
+
- Green channel
-
+
- Blue channel
- -
+
- White channel
- -
+
- White balance
-
-
-
-
-
-

-
-
-
-
+
+
+
+
+
+

+
+
+
+
R
- - - + + +

- +
-

Color palette

+
+ + + +
+

Color palette

@@ -189,7 +139,7 @@
-
-
- - -

Effect mode

-
- +
+ - + +
+ + + + + + +
-
-
- - - - - - -
- -
+ +
- Effect speed
- -
+ +
- Effect intensity
-
+
- Custom 1
-
+
- Custom 2
-
+
- Custom 3
-
+

Segments

Loading...
@@ -367,7 +309,7 @@

- Made with ❤︎ by Aircoookie and the WLED community + Made with ❤︎ by Aircoookie and the WLED community
+
+ + + + diff --git a/wled00/data/index.js b/wled00/data/index.js index 145ed2f22..03ee276a8 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -1,6 +1,6 @@ //page js var loc = false, locip, locproto = "http:"; -var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true; +var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false/*, syncTglRecv = true*/; var hasWhite = false, hasRGB = false, hasCCT = false; var nlDur = 60, nlTar = 0; var nlMode = false; @@ -12,8 +12,11 @@ var currentPreset = -1; var lastUpdate = 0; var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0; var pcMode = false, pcModeA = false, lastw = 0, wW; +var simplifiedUI = false; var tr = 7; var d = document; +const ranges = RangeTouch.setup('input[type="range"]', {}); +var retry = false; var palettesData; var fxdata = []; var pJson = {}, eJson = {}, lJson = {}; @@ -22,23 +25,34 @@ var pN = "", pI = 0, pNum = 0; var pmt = 1, pmtLS = 0, pmtLast = 0; var lastinfo = {}; var isM = false, mw = 0, mh=0; -var ws, cpick, ranges, wsRpt=0; +var ws, wsRpt=0; var cfg = { - theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}}, + theme:{base:"dark", bg:{url:"", rnd: false, rndGrayscale: false, rndBlur: false}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}}, comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, - labels:true, pcmbot:false, pid:true, seglen:false, segpwr:false, segexp:false, - css:true, hdays:false, fxdef:true} + labels:true, pcmbot:false, pid:true, seglen:false, segpwr:false, segexp:false, + css:true, hdays:false, fxdef:true, on:0, off:0, idsort: false} }; +// [year, month (0 -> January, 11 -> December), day, duration in days, image url] var hol = [ - [0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas - [0,2,17,1,"https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day - [2025,3,20,2,"https://aircoookie.github.io/easter.png"], - [2023,3,9,2,"https://aircoookie.github.io/easter.png"], - [2024,2,31,2,"https://aircoookie.github.io/easter.png"], - [0,6,4,1,"https://initiate.alphacoders.com/download/wallpaper/516792/images/jpg/510921363292536"], // 4th of July - [0,0,1,1,"https://initiate.alphacoders.com/download/wallpaper/1198800/images/jpg/2522807481585600"] // new year + [0, 11, 24, 4, "https://aircoookie.github.io/xmas.png"], // christmas + [0, 2, 17, 1, "https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day + [2025, 3, 20, 2, "https://aircoookie.github.io/easter.png"], // easter 2025 + [2024, 2, 31, 2, "https://aircoookie.github.io/easter.png"], // easter 2024 + [0, 6, 4, 1, "https://images.alphacoders.com/516/516792.jpg"], // 4th of July + [0, 0, 1, 1, "https://images.alphacoders.com/119/1198800.jpg"] // new year ]; +var cpick = new iro.ColorPicker("#picker", { + width: 260, + wheelLightness: false, + wheelAngle: 270, + wheelDirection: "clockwise", + layout: [{ + component: iro.ui.Wheel, + options: {} + }] +}); + function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();} function sCol(na, col) {d.documentElement.style.setProperty(na, col);} function gId(c) {return d.getElementById(c);} @@ -109,6 +123,7 @@ function tglLabels() function tglRgb() { cfg.comp.colors.rgb = !cfg.comp.colors.rgb; + cfg.comp.colors.picker = !cfg.comp.colors.picker; applyCfg(); } @@ -160,19 +175,19 @@ function cTheme(light) { } } -function loadBg(iUrl) -{ - let bg = gId('bg'); - let img = d.createElement("img"); +function loadBg() { + const { url: iUrl, rnd: iRnd } = cfg.theme.bg; + const bg = gId('bg'); + const img = d.createElement("img"); img.src = iUrl; - if (iUrl == "" || iUrl==="https://picsum.photos/1920/1080") { - var today = new Date(); - for (var h of (hol||[])) { - var yr = h[0]==0 ? today.getFullYear() : h[0]; - var hs = new Date(yr,h[1],h[2]); - var he = new Date(hs); - he.setDate(he.getDate() + h[3]); - if (today>=hs && today<=he) img.src = h[4]; + if (!iUrl || iRnd) { + const today = new Date(); + for (const holiday of (hol || [])) { + const year = holiday[0] == 0 ? today.getFullYear() : holiday[0]; + const holidayStart = new Date(year, holiday[1], holiday[2]); + const holidayEnd = new Date(holidayStart); + holidayEnd.setDate(holidayEnd.getDate() + holiday[3]); + if (today >= holidayStart && today <= holidayEnd) img.src = holiday[4]; } } img.addEventListener('load', (e) => { @@ -180,7 +195,6 @@ function loadBg(iUrl) if (isNaN(a)) a = 0.6; bg.style.opacity = a; bg.style.backgroundImage = `url(${img.src})`; - img = null; gId('namelabel').style.color = "var(--c-c)"; // improve namelabel legibility on background image }); } @@ -217,13 +231,11 @@ function onLoad() // detect reverse proxy and/or HTTPS let pathn = l.pathname; let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/"); - //if (paths[0]==="sliders") paths.shift(); - //while (paths[0]==="") paths.shift(); locproto = l.protocol; locip = l.hostname + (l.port ? ":" + l.port : ""); if (paths.length > 0 && paths[0]!=="") { loc = true; - locip += "/" + paths[0]; + locip += "/" + paths.join('/'); } else if (locproto==="https:") { loc = true; } @@ -231,7 +243,9 @@ function onLoad() var sett = localStorage.getItem('wledUiCfg'); if (sett) cfg = mergeDeep(cfg, JSON.parse(sett)); + tooltip(); resetPUtil(); + initFilters(); if (localStorage.getItem('pcm') == "true" || (!/Mobi/.test(navigator.userAgent) && localStorage.getItem('pcm') == null)) togglePcMode(true); applyCfg(); @@ -251,14 +265,15 @@ function onLoad() console.log("No array of holidays in holidays.json. Defaults loaded."); }) .finally(()=>{ - loadBg(cfg.theme.bg.url); + loadBg(); }); } else - loadBg(cfg.theme.bg.url); - if (cfg.comp.css) loadSkinCSS('skinCss'); + loadBg(); selectSlot(0); updateTablinks(0); + cpick.on("input:end", () => {setColor(1);}); + cpick.on("color:change", () => {updatePSliders()}); pmtLS = localStorage.getItem('wledPmt'); // Load initial data @@ -267,11 +282,10 @@ function onLoad() loadFXData(()=>{ // load and populate effects loadFX(()=>{ - setTimeout(()=>{ // ESP8266 can't handle quick requests - loadPalettesData(()=>{ - requestJson();// will load presets and create WS - }); - },100); + loadPalettesData(()=>{ + requestJson();// will load presets and create WS + if (cfg.comp.css) setTimeout(()=>{loadSkinCSS('skinCss')},50); + }); }); }); }); @@ -280,11 +294,10 @@ function onLoad() d.addEventListener("visibilitychange", handleVisibilityChange, false); //size(); gId("cv").style.opacity=0; - var sls = d.querySelectorAll('input[type="range"]'); - for (var sl of sls) { + d.querySelectorAll('input[type="range"]').forEach((sl)=>{ sl.addEventListener('touchstart', toggleBubble); sl.addEventListener('touchend', toggleBubble); - } + }); } function updateTablinks(tabI) @@ -307,7 +320,6 @@ function openTab(tabI, force = false) var timeout; function showToast(text, error = false) { - if (error) gId('connind').style.backgroundColor = "var(--c-r)"; var x = gId('toast'); //if (error) text += ''; x.innerHTML = text; @@ -320,6 +332,7 @@ function showToast(text, error = false) function showErrorToast() { + gId('connind').style.backgroundColor = "var(--c-r)"; showToast('Connection to light failed!', true); } @@ -426,18 +439,30 @@ function presetError(empty) if (hasBackup) { cn += `

`; if (empty) - cn += `However, there is backup preset data of a previous installation available.
- (Saving a preset will hide this and overwrite the backup)`; + cn += `However, there is backup preset data of a previous installation available.
(Saving a preset will hide this and overwrite the backup)`; else cn += `Here is a backup of the last known good state:`; - cn += `
- `; + cn += `
`; + cn += `
`; } cn += `
`; gId('pcont').innerHTML = cn; if (hasBackup) gId('bck').value = bckstr; } +function restore(txt) { + var req = new XMLHttpRequest(); + req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)}); + req.addEventListener('error', function(e){showToast(e.stack,true);}); + req.open("POST", getURL("/upload")); + var formData = new FormData(); + var b = new Blob([txt], {type: "application/json"}); + formData.append("data", b, '/presets.json'); + req.send(formData); + setTimeout(loadPresets, 2000); + return false; +} + function loadPresets(callback = null) { // 1st boot (because there is a callback) @@ -486,8 +511,13 @@ function loadPalettes(callback = null) .then((json)=>{ lJson = Object.entries(json); populatePalettes(); + retry = false; }) .catch((e)=>{ + if (!retry) { + retry = true; + setTimeout(loadPalettes, 500); // retry + } showToast(e, true); }) .finally(()=>{ @@ -508,9 +538,13 @@ function loadFX(callback = null) .then((json)=>{ eJson = Object.entries(json); populateEffects(); + retry = false; }) .catch((e)=>{ - //setTimeout(loadFX, 250); // retry + if (!retry) { + retry = true; + setTimeout(loadFX, 500); // retry + } showToast(e, true); }) .finally(()=>{ @@ -533,10 +567,14 @@ function loadFXData(callback = null) // add default value for Solid fxdata.shift() fxdata.unshift(";!;"); + retry = false; }) .catch((e)=>{ fxdata = []; - //setTimeout(loadFXData, 250); // retry + if (!retry) { + retry = true; + setTimeout(loadFXData, 500); // retry + } showToast(e, true); }) .finally(()=>{ @@ -566,8 +604,7 @@ function populatePresets(fromls) if (!pJson) {setTimeout(loadPresets,250); return;} delete pJson["0"]; var cn = ""; - var arr = Object.entries(pJson); - arr.sort(cmpP); + var arr = Object.entries(pJson).sort(cmpP); pQL = []; var is = []; pNum = 0; @@ -581,7 +618,7 @@ function populatePresets(fromls) cn += `
`; if (cfg.comp.pid) cn += `
${i}
`; - cn += `
${isPlaylist(i)?"":""}${pName(i)} + cn += `
${i==lastinfo.leds.bootps?"":""}${isPlaylist(i)?"":""}${pName(i)}
@@ -611,23 +648,24 @@ function parseInfo(i) { if (name === "Dinnerbone") d.documentElement.style.transform = "rotate(180deg)"; // Minecraft easter egg if (i.live) name = "(Live) " + name; if (loc) name = "(L) " + name; - d.title = name; - ledCount = i.leds.count; - syncTglRecv = i.str; - maxSeg = i.leds.maxseg; - pmt = i.fs.pmt; + d.title = name; + simplifiedUI = i.simplifiedui; + ledCount = i.leds.count; + //syncTglRecv = i.str; + maxSeg = i.leds.maxseg; + pmt = i.fs.pmt; gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none"; // do we have a matrix set-up mw = i.leds.matrix ? i.leds.matrix.w : 0; mh = i.leds.matrix ? i.leds.matrix.h : 0; isM = mw>0 && mh>0; if (!isM) { - gId("filter0D").classList.remove('hide'); - gId("filter1D").classList.add('hide'); + //gId("filter0D").classList.remove('hide'); + //gId("filter1D").classList.add('hide'); gId("filter2D").classList.add('hide'); } else { - gId("filter0D").classList.add('hide'); - gId("filter1D").classList.remove('hide'); + //gId("filter0D").classList.add('hide'); + //gId("filter1D").classList.remove('hide'); gId("filter2D").classList.remove('hide'); } // if (i.noaudio) { @@ -656,8 +694,6 @@ function parseInfo(i) { function populateInfo(i) { var cn=""; - var heap = i.freeheap/1024; - heap = heap.toFixed(1); var pwr = i.leds.pwr; var pwru = "Not calculated"; if (pwr > 1000) {pwr /= 1000; pwr = pwr.toFixed((pwr > 10) ? 0 : 1); pwru = pwr + " A";} @@ -672,8 +708,6 @@ function populateInfo(i) } } var vcn = "Kuuhaku"; - if (i.ver.startsWith("0.14.")) vcn = "Hoshi"; -// if (i.ver.includes("-bl")) vcn = "Supāku"; if (i.cn) vcn = i.cn; cn += `v${i.ver} "${vcn}"

@@ -683,17 +717,20 @@ ${i.opt&0x100?inforow("Debug","
`+ ``+ @@ -799,7 +837,7 @@ function populateSegments(s) ``+ ``+ ``+ - ``+ + ``+ ``+ `
`+ `
`+ @@ -822,6 +860,7 @@ function populateSegments(s) } gId('segcont').innerHTML = cn; + gId("segcont").classList.remove("hide"); let noNewSegs = (lowestUnused >= maxSeg); resetUtil(noNewSegs); if (gId('selall')) gId('selall').checked = true; @@ -830,11 +869,14 @@ function populateSegments(s) updateLen(i); updateTrail(gId(`seg${i}bri`)); gId(`segr${i}`).classList.add("hide"); + //if (i 1) ? "block":"none"; // rsbtn parent @@ -848,6 +890,7 @@ function populateSegments(s) } else { gId("ledmap").classList.add('hide'); } + tooltip("#Segments"); } function populateEffects() @@ -907,7 +950,7 @@ function populatePalettes() for (let pa of lJson) { html += generateListItemHtml( 'palette', - pa[0], + pa[0], pa[1], 'setPalette', `
` @@ -915,8 +958,9 @@ function populatePalettes() } gId('pallist').innerHTML=html; // append custom palettes (when loading for the 1st time) - if (!isEmpty(lastinfo) && lastinfo.cpalcount) { - for (let j = 0; j0) gId("rmPal").classList.remove("hide"); + else gId("rmPal").classList.add("hide"); } function redrawPalPrev() @@ -1035,7 +1081,7 @@ function populateNodes(i,n) for (var o of n.nodes) { if (o.name) { let onoff = ``; - 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++; } @@ -1073,7 +1119,7 @@ function updateTrail(e) { if (e==null) return; let sd = e.parentNode.getElementsByClassName('sliderdisplay')[0]; - if (sd && getComputedStyle(sd).getPropertyValue("--bg") !== "none") { + if (sd && getComputedStyle(sd).getPropertyValue("--bg").trim() !== "none") { // trim() for Safari var max = e.hasAttribute('max') ? e.attributes.max.value : 255; var perc = Math.round(e.value * 100 / max); if (perc < 50) perc += 2; @@ -1193,6 +1239,7 @@ function updateUI() gId('buttonPower').className = (isOn) ? 'active':''; gId('buttonNl').className = (nlA) ? 'active':''; gId('buttonSync').className = (syncSend) ? 'active':''; + gId('pxmb').style.display = (isM) ? "inline-block" : "none"; updateSelectedFx(); updateSelectedPalette(selectedPal); // must be after updateSelectedFx() to un-hide color slots for * palettes @@ -1208,7 +1255,7 @@ function updateUI() if (hasRGB) { updateTrail(gId('sliderR')); updateTrail(gId('sliderG')); - updateTrail(gId('sliderB')); + updateTrail(gId('sliderB')); } if (hasWhite) updateTrail(gId('sliderW')); @@ -1242,12 +1289,18 @@ function updateSelectedPalette(s) var selectedPalette = parent.querySelector(`.lstI[data-id="${s}"]`); if (selectedPalette) parent.querySelector(`.lstI[data-id="${s}"]`).classList.add('selected'); + // Display selected palette name on button in simplified UI + let selectedName = selectedPalette.querySelector(".lstIname").innerText; + if (simplifiedUI) { + gId("palwbtn").innerText = "Palette: " + selectedName; + } + // in case of special palettes (* Colors...), force show color selectors (if hidden by effect data) let cd = gId('csl').children; // color selectors if (s > 1 && s < 6) { cd[0].classList.remove('hide'); // * Color 1 if (s > 2) cd[1].classList.remove('hide'); // * Color 1 & 2 - if (s == 5) cd[2].classList.remove('hide'); // all colors + if (s > 3) cd[2].classList.remove('hide'); // all colors } else { for (let i of cd) if (i.dataset.hide == '1') i.classList.add('hide'); } @@ -1270,20 +1323,28 @@ function updateSelectedFx() selectedEffect.classList.add('selected'); setEffectParameters(selectedFx); // hide non-0D effects if segment only has 1 pixel (0D) - var fxs = parent.querySelectorAll('.lstI'); - for (const fx of fxs) { - if (!fx.dataset.opt) continue; - let opts = fx.dataset.opt.split(";"); - if (fx.dataset.id>0) { - if (segLmax==0) fx.classList.add('hide'); // none of the segments selected (hide all effects) - else { - if ((segLmax==1 && (!opts[3] || opts[3].indexOf("0")<0)) || (!isM && opts[3] && ((opts[3].indexOf("2")>=0 && opts[3].indexOf("1")<0)))) fx.classList.add('hide'); - else fx.classList.remove('hide'); + parent.querySelectorAll('.lstI').forEach((fx)=>{ + let ds = fx.dataset; + if (ds.opt) { + let opts = ds.opt.split(";"); + if (ds.id>0) { + if (segLmax==0) fx.classList.add('hide'); // none of the segments selected (hide all effects) + else { + if ((segLmax==1 && (!opts[3] || opts[3].indexOf("0")<0)) || (!isM && opts[3] && ((opts[3].indexOf("2")>=0 && opts[3].indexOf("1")<0)))) fx.classList.add('hide'); + else fx.classList.remove('hide'); + } } } - } - // hide 2D mapping and/or sound simulation options + }); var selectedName = selectedEffect.querySelector(".lstIname").innerText; + + // Display selected effect name on button in simplified UI + let selectedNameOnlyAscii = selectedName.replace(/[^\x00-\x7F]/g, ""); + if (simplifiedUI) { + gId("fxbtn").innerText = "Effect: " + selectedNameOnlyAscii; + } + + // hide 2D mapping and/or sound simulation options var segs = gId("segcont").querySelectorAll(`div[data-map="map2D"]`); for (const seg of segs) if (selectedName.indexOf("\u25A6")<0) seg.classList.remove('hide'); else seg.classList.add('hide'); var segs = gId("segcont").querySelectorAll(`div[data-snd="si"]`); @@ -1301,7 +1362,7 @@ function displayRover(i,s) function cmpP(a, b) { - if (!a[1].n) return (a[0] > b[0]); + if (cfg.comp.idsort || !a[1].n) return (parseInt(a[0]) > parseInt(b[0])); // sort playlists first, followed by presets with characters and last presets with special 1st character const c = a[1].n.charCodeAt(0); const d = b[1].n.charCodeAt(0); @@ -1380,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); @@ -1390,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); @@ -1403,7 +1464,7 @@ function readState(s,command=false) if (s.seg.length>2) d.querySelectorAll(".pop").forEach((e)=>{e.classList.remove("hide");}); - var cd = gId('csl').children; + var cd = gId('csl').querySelectorAll("button"); for (let e = cd.length-1; e >= 0; e--) { cd[e].dataset.r = i.col[e][0]; cd[e].dataset.g = i.col[e][1]; @@ -1424,25 +1485,31 @@ function readState(s,command=false) gId('checkO3').checked = !(!i.o3); if (s.error && s.error != 0) { - var errstr = ""; - switch (s.error) { - case 10: - errstr = "Could not mount filesystem!"; - break; - case 11: - errstr = "Not enough space to save preset!"; - break; - case 12: - errstr = "Preset not found."; - break; - case 13: - errstr = "Missing ir.json."; - break; - case 19: - errstr = "A filesystem error has occured."; - break; + var errstr = ""; + switch (s.error) { + case 8: + errstr = "Effect RAM depleted!"; + break; + case 9: + errstr = "JSON parsing error!"; + break; + case 10: + errstr = "Could not mount filesystem!"; + break; + case 11: + errstr = "Not enough space to save preset!"; + break; + case 12: + errstr = "Preset not found."; + break; + case 13: + errstr = "Missing ir.json."; + break; + case 19: + errstr = "A filesystem error has occured."; + break; } - showToast('Error ' + s.error + ": " + errstr, true); + showToast('Error ' + s.error + ": " + errstr, true); } selectedPal = i.pal; @@ -1460,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 @@ -1484,46 +1551,49 @@ function setEffectParameters(idx) var paOnOff = (effectPars.length<3 || effectPars[2]=='')?[]:effectPars[2].split(","); // set html slider items on/off - let nSliders = 5; - for (let i=0; ii && slOnOff[i] != "")) { - if (slOnOff.length>i && slOnOff[i]!="!") label.innerHTML = slOnOff[i]; - else if (i==0) label.innerHTML = "Effect speed"; - else if (i==1) label.innerHTML = "Effect intensity"; - else label.innerHTML = "Custom" + (i-1); - slider.classList.remove('hide'); - } else { - slider.classList.add('hide'); - } - } - if (slOnOff.length>5) { // up to 3 checkboxes + let sliders = d.querySelectorAll("#sliders .sliderwrap"); + sliders.forEach((slider, i)=>{ + 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("title", text); + slider.parentElement.classList.remove('hide'); + } else + slider.parentElement.classList.add('hide'); + }); + + if (slOnOff.length > 5) { // up to 3 checkboxes gId('fxopt').classList.remove('fade'); - for (let i = 0; i<3; i++) { + let checks = d.querySelectorAll("#sliders .ochkl"); + checks.forEach((check, i)=>{ + let text = check.getAttribute("title"); if (5+i5+i && slOnOff[5+i]!="!") text = slOnOff[5+i]; + check.setAttribute("title", text); + check.classList.remove('hide'); } else - gId('opt'+i).classList.add('hide'); - } - } else { - gId('fxopt').classList.add('fade'); - } + check.classList.add('hide'); + }); + } else gId('fxopt').classList.add('fade'); // set the bottom position of selected effect (sticky) as the top of sliders div - setInterval(()=>{ + function setSelectedEffectPosition() { + if (simplifiedUI) return; let top = parseInt(getComputedStyle(gId("sliders")).height); top += 5; let sel = d.querySelector('#fxlist .selected'); if (sel) sel.style.bottom = top + "px"; // we will need to remove this when unselected (in setFX()) - },750); + } + + setSelectedEffectPosition(); + setInterval(setSelectedEffectPosition,750); // set html color items on/off var cslLabel = ''; var sep = ''; var cslCnt = 0, oCsel = csel; - for (let i=0; i{ var btn = gId("csl" + i); // if no controlDefined or coOnOff has a value if (coOnOff.length>i && coOnOff[i] != "") { @@ -1553,12 +1623,16 @@ function setEffectParameters(idx) btn.dataset.hide = 1; btn.innerHTML = `${i+1}`; // name hidden buttons 1..3 for * palettes } - } + }); gId("cslLabel").innerHTML = cslLabel; + if (cslLabel!=="") gId("cslLabel").classList.remove("hide"); + else gId("cslLabel").classList.add("hide"); // set palette on/off var palw = gId("palw"); // wrapper var pall = gId("pall"); // label + var icon = ' '; + var text = 'Color palette'; // if not controlDefined or palette has a value if (hasRGB && ((!controlDefined) || (paOnOff.length>0 && paOnOff[0]!="" && isNaN(paOnOff[0])))) { palw.style.display = "inline-block"; @@ -1568,13 +1642,17 @@ function setEffectParameters(idx) var v = Math.max(0,Math.min(255,parseInt(paOnOff[0].substr(dPos+1)))); paOnOff[0] = paOnOff[0].substring(0,dPos); } - if (paOnOff.length>0 && paOnOff[0] != "!") pall.innerHTML = paOnOff[0]; - else pall.innerHTML = ' Color palette'; + if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0]; } else { // disable palette list - pall.innerHTML = ' Color palette not used'; + text += ' not used'; palw.style.display = "none"; + // Close palette dialog if not available + if (gId("palw").lastElementChild.tagName == "DIALOG") { + gId("palw").lastElementChild.close(); + } } + pall.innerHTML = icon + text; // not all color selectors shown, hide palettes created from color selectors // NOTE: this will disallow user to select "* Color ..." palettes which may be undesirable in some cases or for some users //for (let e of (gId('pallist').querySelectorAll('.lstI')||[])) { @@ -1638,6 +1716,7 @@ function requestJson(command=null) parseInfo(i); populatePalettes(i); if (isInfo) populateInfo(i); + if (simplifiedUI) simplifyUI(); } var s = json.state ? json.state : json; readState(s); @@ -1650,8 +1729,13 @@ function requestJson(command=null) }); },25); reqsLegal = true; + retry = false; }) .catch((e)=>{ + if (!retry) { + retry = true; + setTimeout(requestJson,500); + } showToast(e, true); }); } @@ -1665,6 +1749,8 @@ function togglePower() obj.seg = []; obj.seg[0] = {"id": lastinfo.liveseg, "frz": false}; } + if (cfg.comp.on >0 && isOn) obj = {"ps": cfg.comp.on }; // don't use setPreset() + if (cfg.comp.off>0 && !isOn) obj = {"ps": cfg.comp.off}; // don't use setPreset() requestJson(obj); } @@ -1687,7 +1773,7 @@ function toggleSync() if (syncSend) showToast('Other lights in the network will now sync to this one.'); else showToast('This light and other lights in the network will no longer sync.'); var obj = {"udpn": {"send": syncSend}}; - if (syncTglRecv) obj.udpn.recv = syncSend; + //if (syncTglRecv) obj.udpn.recv = syncSend; requestJson(obj); } @@ -1699,7 +1785,7 @@ function toggleLiveview() let wsOn = ws && ws.readyState === WebSocket.OPEN; var lvID = "liveview"; - if (isM && wsOn) { + if (isM && wsOn) { lvID += "2D"; if (isLv) gId('klv2D').innerHTML = ``; gId('mlv2D').style.transform = (isLv) ? "translateY(0px)":"translateY(100%)"; @@ -1734,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({ @@ -1783,7 +1868,7 @@ function resetUtil(off=false) + '' + `
Add segment
` + '
' - + `` + + `` + '
' + '
'; } @@ -1792,12 +1877,12 @@ function makePlSel(el, incPl=false) { var plSelContent = ""; delete pJson["0"]; // remove filler preset - var arr = Object.entries(pJson); - for (var a of arr) { + Object.entries(pJson).sort(cmpP).forEach((a)=>{ var n = a[1].n ? a[1].n : "Preset " + a[0]; - if (!incPl && a[1].playlist && a[1].playlist.ps) continue; // remove playlists, sub-playlists not yet supported - plSelContent += `` - } + if (cfg.comp.idsort) n = a[0] + ' ' + n; + if (!(!incPl && a[1].playlist && a[1].playlist.ps)) // skip playlists, sub-playlists not yet supported + plSelContent += ``; + }); return plSelContent; } @@ -1806,21 +1891,19 @@ function refreshPlE(p) var plEDiv = gId(`ple${p}`); if (!plEDiv) return; var content = "
Playlist entries
"; - for (var i = 0; i < plJson[p].ps.length; i++) { - content += makePlEntry(p,i); - } + plJson[p].ps.forEach((e,i)=>{content += makePlEntry(p,i);}); + content += `
`; plEDiv.innerHTML = content; var dels = plEDiv.getElementsByClassName("btn-pl-del"); if (dels.length < 2) dels[0].style.display = "none"; - var sels = gId(`seg${p+100}`).getElementsByClassName("sel"); - for (var i of sels) { + d.querySelectorAll(`#seg${p+100} .sel`).forEach((i)=>{ if (i.dataset.val) { if (parseInt(i.dataset.val) > 0) i.value = i.dataset.val; else plJson[p].ps[i.dataset.index] = parseInt(i.value); } - } + }); } // p: preset ID, i: ps index @@ -1876,6 +1959,7 @@ function plR(p) function makeP(i,pl) { var content = ""; + const bps = lastinfo.leds.bootps; if (pl) { if (i===0) plJson[0] = { ps: [1], @@ -1886,7 +1970,7 @@ function makeP(i,pl) end: 0 }; var rep = plJson[i].repeat ? plJson[i].repeat : 0; - content = + content = `
@@ -1908,23 +1992,17 @@ ${makePlSel(plJson[i].end?plJson[i].end:0, true)} } else { content = ` `; @@ -1940,15 +2018,18 @@ ${makePlSel(plJson[i].end?plJson[i].end:0, true)}
(leave empty for no Quick load button)
API command
${content}
+
Save to ID 0)?i:getLowestUnusedP()}>
@@ -2078,7 +2159,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}; } @@ -2244,6 +2325,12 @@ function setFX(ind = null) } else { d.querySelector(`#fxlist input[name="fx"][value="${ind}"]`).checked = true; } + + // Close effect dialog in simplified UI + if (simplifiedUI) { + gId("fx").lastElementChild.close(); + } + var obj = {"seg": {"fx": parseInt(ind), "fxdef": cfg.comp.fxdef}}; // fxdef sets effect parameters to default values requestJson(obj); } @@ -2256,6 +2343,11 @@ function setPalette(paletteId = null) d.querySelector(`#pallist input[name="palette"][value="${paletteId}"]`).checked = true; } + // Close palette dialog in simplified UI + if (simplifiedUI) { + gId("palw").lastElementChild.close(); + } + var obj = {"seg": {"pal": paletteId}}; requestJson(obj); } @@ -2359,8 +2451,9 @@ function saveP(i,pl) if (gId(`p${i}lmp`) && gId(`p${i}lmp`).value!=="") obj.ledmap = parseInt(gId(`p${i}lmp`).value); } } - - obj.psave = pI; obj.n = pN; + if (gId(`p${i}bps`).checked) obj.bootps = pI; + obj.psave = pI; + obj.n = pN; var pQN = gId(`p${i}ql`).value; if (pQN.length > 0) obj.ql = pQN; @@ -2420,8 +2513,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(); @@ -2703,39 +2796,108 @@ function hideModes(txt) } } */ -function search(f,l=null) -{ - f.nextElementSibling.style.display=(f.value!=='')?'block':'none'; - if (!l) return; - var el = gId(l).querySelectorAll('.lstI'); +function search(field, listId = null) { + field.nextElementSibling.style.display = (field.value !== '') ? 'block' : 'none'; + if (!listId) return; + + 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; }); + } + + // do not search if filter is active + if (gId("filters").querySelectorAll("input[type=checkbox]:checked").length) return; + + const listItems = gId(listId).querySelectorAll('.lstI'); // filter list items but leave (Default & Solid) always visible - for (i = (l==='pcont'?0:1); i < el.length; i++) { - var it = el[i]; - var itT = it.querySelector('.lstIname').innerText.toUpperCase(); - it.style.display = (itT.indexOf(f.value.toUpperCase())<0) ? 'none' : ''; + for (i = (listId === 'pcont' ? 0 : 1); i < listItems.length; i++) { + const listItem = listItems[i]; + const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); + const searchIndex = listItemName.indexOf(field.value.toUpperCase()); + listItem.style.display = (searchIndex < 0) ? 'none' : ''; + listItem.dataset.searchIndex = searchIndex; + } + + // sort list items by search index and name + const sortedListItems = Array.from(listItems).sort((a, b) => { + const aSearchIndex = parseInt(a.dataset.searchIndex); + const bSearchIndex = parseInt(b.dataset.searchIndex); + + if (aSearchIndex !== bSearchIndex) { + return aSearchIndex - bSearchIndex; + } + + const aName = a.querySelector('.lstIname').innerText.toUpperCase(); + const bName = b.querySelector('.lstIname').innerText.toUpperCase(); + + return aName.localeCompare(bName); + }); + sortedListItems.forEach(item => { + gId(listId).append(item); + }); + + // scroll to first search result + const firstVisibleItem = sortedListItems.find(item => item.style.display !== 'none' && !item.classList.contains('sticky') && !item.classList.contains('selected')); + if (firstVisibleItem && search) { + firstVisibleItem.scrollIntoView({ behavior: "instant", block: "center" }); } } -function clean(c) -{ - c.style.display='none'; - var i=c.previousElementSibling; - i.value=''; - i.focus(); - i.dispatchEvent(new Event('input')); - if (i.parentElement.id=='fxFind') { - gId("filters").querySelectorAll("input[type=checkbox]").forEach((e)=>{e.checked=false;}); +function clean(clearButton) { + clearButton.style.display = 'none'; + const inputField = clearButton.previousElementSibling; + inputField.value = ''; + search(inputField, clearButton.parentElement.nextElementSibling.id); +} + +function initFilters() { + gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { e.checked = false; }); +} + +function filterFocus(e) { + const f = gId("filters"); + if (e.type === "focus") f.classList.remove('fade'); // immediately show (still has transition) + // compute sticky top (with delay for transition) + setTimeout(() => { + const sti = parseInt(getComputedStyle(d.documentElement).getPropertyValue('--sti')) + (e.type === "focus" ? 1 : -1) * f.offsetHeight; + sCol('--sti', sti + "px"); + }, 252); + if (e.type === "blur") { + setTimeout(() => { + if (e.target === document.activeElement && document.hasFocus()) return; + // do not hide if filter is active + if (gId("filters").querySelectorAll("input[type=checkbox]:checked").length) return; + f.classList.add('fade'); + }, 255); // wait with hiding } } -function filterFx(o) -{ - if (!o) return; - let i = gId('fxFind').children[0]; - i.value=!o.checked?'':o.dataset.flt; - i.focus(); - i.dispatchEvent(new Event('input')); - gId("filters").querySelectorAll("input[type=checkbox]").forEach((e)=>{if(e!==o)e.checked=false;}); +function filterFx() { + const inputField = gId('fxFind').children[0]; + inputField.value = ''; + inputField.focus(); + clean(inputField.nextElementSibling); + const listItems = gId("fxlist").querySelectorAll('.lstI'); + for (let i = 1; i < listItems.length; i++) { + const listItem = listItems[i]; + const listItemName = listItem.querySelector('.lstIname').innerText; + let hide = false; + gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = true; }); + listItem.style.display = hide ? 'none' : ''; + } +} + +function preventBlur(e) { + if (e.target === gId("fxFind").children[0] || e.target === gId("filters")) return; + e.preventDefault(); } // make sure "dur" and "transition" are arrays with at least the length of "ps" @@ -2833,7 +2995,7 @@ function hasIroClass(classList) //required by rangetouch.js function lock(e) { - if (pcMode) return; + if (pcMode || simplifiedUI) return; var l = e.target.classList; var pl = e.target.parentElement.classList; @@ -2847,7 +3009,7 @@ function lock(e) //required by rangetouch.js function move(e) { - if(!locked || pcMode) return; + if(!locked || pcMode || simplifiedUI) return; var clientX = unify(e).clientX; var dx = clientX - x0; var s = Math.sign(dx); @@ -2893,7 +3055,7 @@ function togglePcMode(fromB = false) gId('buttonPcm').className = (pcMode) ? "active":""; gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto"; sCol('--bh', gId('bot').clientHeight + "px"); - _C.style.width = (pcMode)?'100%':'400%'; + _C.style.width = (pcMode || simplifiedUI)?'100%':'400%'; } function mergeDeep(target, ...sources) @@ -2914,6 +3076,139 @@ function mergeDeep(target, ...sources) return mergeDeep(target, ...sources); } +function tooltip(cont=null) +{ + 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("title"); + + // prevent default title popup + element.removeAttribute("title"); + + let { top, left, width } = element.getBoundingClientRect(); + + d.body.appendChild(tooltip); + + const { offsetHeight, offsetWidth } = tooltip; + + const offset = element.classList.contains("sliderwrap") ? 4 : 10; + top -= offsetHeight + offset; + left += (width - offsetWidth) / 2; + + tooltip.style.top = top + "px"; + tooltip.style.left = left + "px"; + tooltip.classList.add("visible"); + }); + + element.addEventListener("mouseout", ()=>{ + const tooltips = d.querySelectorAll('.tooltip'); + tooltips.forEach((tooltip)=>{ + tooltip.classList.remove("visible"); + d.body.removeChild(tooltip); + }); + // restore title + element.setAttribute("title", element.getAttribute("data-title")); + }); + }); +}; + +// Transforms the default UI into the simple UI +function simplifyUI() { + // Create dropdown dialog + function createDropdown(id, buttonText, dialogElements = null) { + // Create dropdown dialog + const dialog = document.createElement("dialog"); + // Move every dialogElement to the dropdown dialog or if none are given, move all children of the element with the given id + if (dialogElements) { + dialogElements.forEach((e) => { + dialog.appendChild(e); + }); + } else { + while (gId(id).firstChild) { + dialog.appendChild(gId(id).firstChild); + } + } + + // Create button for the dropdown + const btn = document.createElement("button"); + btn.id = id + "btn"; + btn.classList.add("btn"); + btn.innerText = buttonText; + function toggleDialog(e) { + if (e.target != btn && e.target != dialog) return; + if (dialog.open) { + dialog.close(); + return; + } + // Prevent autofocus on dialog open + dialog.inert = true; + dialog.showModal(); + dialog.inert = false; + clean(dialog.firstElementChild.children[1]); + dialog.scrollTop = 0; + }; + btn.addEventListener("click", toggleDialog); + dialog.addEventListener("click", toggleDialog); + + // Add the dialog and button to the element with the given id + gId(id).append(btn); + gId(id).append(dialog); + } + + // Check if the UI was already simplified + if (gId("Colors").classList.contains("simplified")) return; + + // Disable PC Mode as it does not exist in simple UI + if (pcMode) togglePcMode(true); + _C.style.width = '100%' + _C.style.setProperty('--n', 1); + + gId("Colors").classList.add("simplified"); + // Put effects below palett list + gId("Colors").append(gId("fx")); + gId("Colors").append(gId("sliders")); + // Put segments before palette list + gId("Colors").insertBefore(gId("segcont"), gId("pall")); + // Put preset quick load before palette list and segemts + gId("Colors").insertBefore(gId("pql"), gId("pall")); + + // Create dropdown for palette list + createDropdown("palw", "Change palette"); + createDropdown("fx", "Change effect", [gId("fxFind"), gId("fxlist")]); + + // Hide pallete label + gId("pall").style.display = "none"; + gId("Colors").insertBefore(document.createElement("br"), gId("pall")); + // Hide effect label + gId("modeLabel").style.display = "none"; + + // Hide buttons in top bar + gId("buttonNl").style.display = "none"; + gId("buttonSync").style.display = "none"; + gId("buttonSr").style.display = "none"; + gId("buttonPcm").style.display = "none"; + + // Hide bottom bar + gId("bot").style.display = "none"; + document.documentElement.style.setProperty('--bh', '0px'); + + // Hide other tabs + gId("Effects").style.display = "none"; + gId("Segments").style.display = "none"; + gId("Presets").style.display = "none"; + + // Hide filter options + gId("filters").style.display = "none"; + + // Hide buttons for pixel art and custom palettes (add / delete) + gId("btns").style.display = "none"; +} + size(); _C.style.setProperty('--n', N); diff --git a/wled00/data/liveview.htm b/wled00/data/liveview.htm index 60e9fb03e..8c10ba962 100644 --- a/wled00/data/liveview.htm +++ b/wled00/data/liveview.htm @@ -6,23 +6,33 @@ WLED Live Preview -
+ - \ No newline at end of file + 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;yPixel Magic Tool - - @@ -542,75 +569,21 @@
- - - + PIXEL MAGIC TOOL + It is a tool that converts any image into code in + JSON WLED format for + 2D Matrix panels
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- +
+ +
-
+
-
+
-
+
-