diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..d09c8a605 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,22 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile + +# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6 +ARG VARIANT="3" +FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} + +# [Option] Install Node.js +ARG INSTALL_NODE="true" +ARG NODE_VERSION="lts/*" +RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. +# COPY requirements.txt /tmp/pip-tmp/ +# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ +# && rm -rf /tmp/pip-tmp + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..f77dc4de4 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,47 @@ +{ + "name": "Python 3", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9 + "VARIANT": "3", + // Options + "INSTALL_NODE": "true", + "NODE_VERSION": "lts/*" + } + }, + + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", + "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", + "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", + "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", + "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", + "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", + "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "platformio.platformio-ide" + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "npm install", + + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} + diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 000000000..c3536b2ca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,27 @@ +--- +name: Bug +about: Noticed an issue with your lights? +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. Please quickly search existing issues first! + +**To Reproduce** +Steps to reproduce the behavior, if consistently possible + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**WLED version** + - Board: [e.g. Wemos D1, ESP32 dev] + - Version [e.g. 0.10.0, dev200603] + - Format [e.g. Binary, self-compiled] + +**Additional context** +Anything else you'd like to say about the problem? + +Thank you for your help! diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..67deb6e18 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an improvement idea for WLED! +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +Thank you for your ideas for making WLED better! diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 000000000..94f92a612 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,19 @@ +--- +name: Question +about: Have a question about using WLED? +title: '' +labels: question +assignees: '' + +--- + +**Take a look at the wiki and FAQ, perhaps your question is already answered!** +[FAQ](https://github.com/Aircoookie/WLED/wiki/FAQ) + +**Please consider asking your question on the WLED forum or Discord** +[Forum](https://wled.discourse.group/) +[Discord](https://discord.gg/KuqP7NE) +[What to post where?](https://github.com/Aircoookie/WLED/issues/658) + +**If you do not like to use these platforms, delete this template and ask away!** +Please keep in mind though that the issue section is generally not the preferred place for general questions. diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..811db619a --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,20 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 120 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - keep + - enhancement + - confirmed +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + Hey! This issue has been open for quite some time without any new comments now. + It will be closed automatically in a week if no further activity occurs. + + Thank you for using WLED! +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml new file mode 100644 index 000000000..4158dd7ac --- /dev/null +++ b/.github/workflows/wled-ci.yml @@ -0,0 +1,31 @@ +name: PlatformIO CI + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Cache PlatformIO + uses: actions/cache@v2 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + - name: Run PlatformIO + run: pio run \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2116e6930..7c0dd1e5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,16 @@ .pio +.cache .pioenvs .piolibdeps .vscode !.vscode/extensions.json /wled00/Release /wled00/extLibs +/platformio_override.ini +/wled00/my_config.h +/build_output +.DS_Store +.gitignore +.clang-format +node_modules +.idea diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 000000000..29d75d19d --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,5 @@ +FROM gitpod/workspace-full + +USER gitpod + +RUN pip3 install -U platformio diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000..cc416b1c2 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,12 @@ +tasks: + - command: platformio run + +image: + file: .gitpod.Dockerfile + +vscode: + extensions: + - ms-vscode.cpptools@0.26.3:u3GsZ5PK12Ddr79vh4TWgQ== + - eamodio.gitlens@10.2.1:e0IYyp0efFqVsrZwsIe8CA== + - Atishay-Jain.All-Autocomplete@0.0.23:fbZNfSpnd8XkAHGfAPS2rA== + - 2gua.rainbow-brackets@0.0.6:Tbu8dTz0i+/bgcKQTQ5b8g== diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5d9762615..000000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Continuous Integration (CI) is the practice, in software -# engineering, of merging all developer working copies with a shared mainline -# several times a day < https://docs.platformio.org/page/ci/index.html > -# -# Documentation: -# -# * Travis CI Embedded Builds with PlatformIO -# < https://docs.travis-ci.com/user/integration/platformio/ > -# -# * PlatformIO integration with Travis CI -# < https://docs.platformio.org/page/ci/travis.html > -# -# * User Guide for `platformio ci` command -# < https://docs.platformio.org/page/userguide/cmd_ci.html > -# -# -# Please choose one of the following templates (proposed below) and uncomment -# it (remove "# " before each line) or use own configuration according to the -# Travis CI documentation (see above). -# - -language: python -python: - - "2.7" -sudo: false -cache: - directories: - - "~/.platformio" -env: - - PLATFORMIO_CI_SRC=wled00 -install: - - pip install -U platformio - - platformio update -script: - - platformio ci --project-conf=./platformio.ini diff --git a/.vs/wled00/v15/.suo b/.vs/wled00/v15/.suo deleted file mode 100644 index ed79ba2af..000000000 Binary files a/.vs/wled00/v15/.suo and /dev/null differ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8281e64cc..0f0d7401d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,7 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ] -} \ No newline at end of file + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..2ee772ce1 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build: HTML and binary", + "dependsOn": [ + "Build: HTML only", + "Build: binary only" + ], + "dependsOrder": "sequence", + "problemMatcher": [ + "$platformio", + ], + }, + { + "type": "PlatformIO", + "label": "Build: binary only", + "task": "Build", + "group": { + "kind": "build", + "isDefault": true, + }, + "problemMatcher": [ + "$platformio" + ], + "presentation": { + "panel": "shared" + } + }, + { + "type": "npm", + "script": "build", + "group": "build", + "problemMatcher": [], + "label": "Build: HTML only", + "detail": "npm run build", + "presentation": { + "panel": "shared" + } + } + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..03bde2e6b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,608 @@ +## WLED changelog + +### Builds after release 0.12.0 + +#### Build 2104030 + +- Fixed ESP32 crash on Drip effect with reversed segment (#1854) +- Added flag `WLED_DISABLE_BROWNOUT_DET` to disable ESP32 brownout detector (off by default) + +### WLED release 0.12.0 + +#### Build 2104020 + +- Allow clearing button/IR/relay pin on platforms that don't support negative numbers +- Removed AUX pin +- Hid some easter eggs, only to be found at easter + +### Development versions between 0.11.1 and 0.12.0 releases + +#### Build 2103310 + +- Version bump to 0.12.0 "Hikari" +- Fixed LED settings submission in iOS app + +#### Build 2103300 + +- Version bump to 0.12.0-b5 "Hikari" +- Update to core espressif32@3.2 +- Fixed IR pin not configurable + +#### Build 2103290 + +- Version bump to 0.12.0-b4 "Hikari" +- Experimental use of espressif32@3.1.1 +- Fixed RGBW mode disabled after LED settings saved +- Fixed infrared support not compiled in if IRPIN is not defined + +#### Build 2103230 + +- Fixed current estimation + +#### Build 2103220 + +- Version bump to 0.12.0-b2 "Hikari" +- Worked around an issue causing a critical decrease in framerate (wled.cpp l.240 block) +- Bump to Espalexa v2.7.0, fixing discovery + +#### Build 2103210 + +- Version bump to 0.12.0-b1 "Hikari" +- More colors visible on Palette preview +- Fixed chevron icon not included +- Fixed color order override +- Cleanup + +#### Build 2103200 + +- Version bump to 0.12.0-b0 "Hikari" +- Added palette preview and search (PR #1637) +- Added Reverse checkbox for PWM busses - reverses logic level for on +- Fixed various problems with the Playlist feature (PR #1724) +- Replaced "Layer" icon with "i" icon for Info button +- Chunchun effect more fitting for various segment lengths (PR #1804) +- Removed global reverse (in favor of individual bus reverse) +- Removed some unused icons from UI icon font + +#### Build 2103130 + +- Added options for Auto Node discovery +- Optimized strings (no string both F() and raw) + +#### Build 2103090 + +- Added Auto Node discovery (PR #1683) +- Added tooltips to quick color selectors for accessibility + +#### Build 2103060 + +- Auto start field population in bus config + +#### Build 2103050 + +- Fixed incorrect over-memory indication in LED settings on ESP32 + +#### Build 2103041 + +- Added destructor for BusPwm (fixes #1789) + +#### Build 2103040 + +- Fixed relay mode inverted when upgrading from 0.11.0 +- Fixed no more than 2 pins per bus configurable in UI +- Changed to non-linear IR brightness steps (PR #1742) +- Fixed various warnings (PR #1744) +- Added UDP DNRGBW Mode (PR #1704) +- Added dynamic LED mapping with ledmap.json file (PR #1738) +- Added support for QuinLED-ESP32-Ethernet board +- Added support for WESP32 ethernet board (PR #1764) +- Added Caching for main UI (PR #1704) +- Added Tetrix mode (PR #1729) +- Added memory check on Bus creation + +#### Build 2102050 + +- Version bump to 0.12.0-a0 "Hikari" +- Added FPS indication in info +- Bumped max outputs from 7 to 10 busses for ESP32 + +#### Build 2101310 + +- First alpha configurable multipin + +#### Build 2101130 + +- Added color transitions for all segments and slots and for segment brightness +- Fixed bug that prevented setting a boot preset higher than 25 + +#### Build 2101040 + +- Replaced Red & Blue effect with Aurora effect (PR #1589) +- Fixed HTTP changing segments uncommanded (#1618) +- Updated copyright year and contributor page link + +#### Build 2012311 + +- Fixed Countdown mode + +#### Build 2012310 + +- (Hopefully actually) fixed display of usermod values in info screen + +#### Build 2012240 + +- Fixed display of usermod values in info screen +- 4 more effects now use FRAMETIME +- Remove unsupported environments from platformio.ini + +#### Build 2012210 + +- Split index.htm in separate CSS + JS files (PR #1542) +- Minify UI HTML, saving >1.5kB flash +- Fixed JShint warnings + +#### Build 2012180 + +- Boot brightness 0 will now use the brightness from preset +- Add iOS scrolling momentum (from PR #1528) + +### WLED release 0.11.1 + +#### Build 2012180 + +- Release of WLED 0.11.1 "Mirai" +- Fixed AP hide not saving (fixes #1520) +- Fixed MQTT password re-transmitted to HTML +- Hide Update buttons while uploading, accept .bin +- Make sure AP password is at least 8 characters long + +### Development versions after 0.11.0 release + +#### Build 2012160 + +- Bump Espalexa to 2.5.0, fixing discovery (PR Espalexa/#152, originally PR #1497) + +#### Build 2012150 + +- Added Blends FX (PR #1491) +- Fixed an issue that made it impossible to deactivate timed presets + +#### Build 2012140 + +- Added Preset ID quick display option (PR #1462) +- Fixed LEDs not turning on when using gamma correct brightness and LEDPIN 2 (default) +- Fixed notifier applying main segment to selected segments on notification with FX/Col disabled + +#### Build 2012130 + +- Fixed RGBW mode not saved between reboots (fixes #1457) +- Added brightness scaling in palette function for default (PR #1484) + +#### Build 2012101 + +- Fixed preset cycle default duration rounded down to nearest 10sec interval (#1458) +- Enabled E1.31/DDP/Art-Net in AP mode + +#### Build 2012100 + +- Fixed multi-segment preset cycle +- Fixed EEPROM (pre-0.11 settings) not cleared on factory reset +- Fixed an issue with intermittent crashes on FX change (PR #1465) +- Added function to know if strip is updating (PR #1466) +- Fixed using colorwheel sliding the UI (PR #1459) +- Fixed analog clock settings not saving (PR #1448) +- Added Temperature palette (PR #1430) +- Added Candy cane FX (PR #1445) + +#### Build 2012020 + +- UDP `parsePacket()` with sync disabled (#1390) +- Added Multi RGBW DMX mode (PR #1383) + +#### Build 2012010 + +- Fixed compilation for analog (PWM) LEDs + +### WLED version 0.11.0 + +#### Build 2011290 + +- Release of WLED 0.11.0 "Mirai" +- Workaround for weird empty %f Espalexa issue +- Fixed crash on saving preset with HTTP API `PS` +- Improved performance for color changes in non-main segment + +#### Build 2011270 + +- Added tooltips for speed and intensity sliders (PR #1378) +- Moved color order to NpbWrapper.h +- Added compile time define to override the color order for a specific range + +#### Build 2011260 + +- Add `live` property to state, allowing toggling of realtime (not incl. in state resp.) +- PIO environment changes + +#### Build 2011230 + +- Version bump to 0.11.0 "Mirai" +- Improved preset name sorting +- Fixed Preset cycle not working beyond preset 16 + +### Development versions between 0.10.2 and 0.11.0 releases + +#### Build 2011220 + +- Fixed invalid save when modifying preset before refresh (might be related to #1361) +- Fixed brightness factor ignored on realtime timeout (fixes #1363) +- Fixed Phase and Chase effects with LED counts >256 (PR #1366) + +#### Build 2011210 + +- Fixed Brightness slider beneath color wheel not working (fixes #1360) +- Fixed invalid UI state after saving modified preset + +#### Build 2011200 + +- Added HEX color receiving to JSON API with `"col":["RRGGBBWW"]` format +- Moved Kelvin color receiving in JSON API from `"col":[[val]]` to `"col":[val]` format + _Notice:_ This is technically a breaking change. Since no release was made since the introduction and the Kelvin property was not previously documented in the wiki, + impact should be minimal. +- BTNPIN can now be disabled by setting to -1 (fixes #1237) + +#### Build 2011180 + +- Platformio.ini updates and streamlining (PR #1266) +- my_config.h custom compile settings system (not yet used for much, adapted from PR #1266) +- Added Hawaii timezone (HST) +- Linebreak after 5 quick select buttons + +#### Build 2011154 + +- Fixed RGBW saved incorrectly +- Fixed pmt caching requesting /presets.json too often +- Fixed deEEP not copying the first segment of EEPROM preset 16 + +#### Build 2011153 + +- Fixed an ESP32 end-of-file issue +- Fixed strip.isRgbw not read from cfg.json + +#### Build 2011152 + +- Version bump to 0.11.0p "Mirai" +- Increased max. num of segments to 12 (ESP8266) / 16 (ESP32) +- Up to 250 presets stored in the `presets.json` file in filesystem +- Complete overhaul of the Presets UI tab +- Updated iro.js to v5 (fixes black color wheel) +- Added white temperature slider to color wheel +- Add JSON settings serialization/deserialization to cfg.json and wsec.json +- Added deEEP to convert the EEPROM settings and presets to files +- Playlist support - JSON only for now +- New v2 usermod methods `addToConfig()` and `readFromConfig()` (see EXAMPLE_v2 for doc) +- Added Ethernet support for ESP32 (PR #1316) +- IP addresses are now handled by the `Network` class +- New `esp32_poe` PIO environment +- Use EspAsyncWebserver Aircoookie fork v.2.0.0 (hiding wsec.json) +- Removed `WLED_DISABLE_FILESYSTEM` and `WLED_ENABLE_FS_SERVING` defines as they are now required +- Added pin manager +- UI performance improvements (no drop shadows) +- More explanatory error messages in UI +- Improved candle brightness +- Return remaining nightlight time `nl.rem` in JSON API (PR #1302) +- UI sends timestamp with every command, allowing for timed presets without using NTP +- Added gamma calculation (yet unused) +- Added LED type definitions to const.h (yet unused) +- Added nicer 404 page +- Removed `NP` and `MS=` macro HTTP API commands +- Removed macros from Time settings + +#### Build 2011120 + +- Added the ability for the /api MQTT topic to receive JSON API payloads + +#### Build 2011040 + +- Inversed Rain direction (fixes #1147) + +#### Build 2011010 + +- Re-added previous C9 palette +- Renamed new C9 palette + +#### Build 2010290 + +- Colorful effect now supports palettes +- Added C9 2 palette (#1291) +- Improved C9 palette brightness by 12% +- Disable onboard LED if LEDs are off (PR #1245) +- Added optional status LED (PR #1264) +- Realtime max. brightness now honors brightness factor (fixes #1271) +- Updated ArduinoJSON to 6.17.0 + +#### Build 2010020 + +- Fixed interaction of `T` and `NL` HTTP API commands (#1214) +- Fixed an issue where Sunrise mode nightlight does not activate if toggled on simultaneously + +#### Build 2009291 + +- Fixed MQTT bootloop (no F() macro, #1199) + +#### Build 2009290 + +- Added basic DDP protocol support +- Added Washing Machine effect (PR #1208) + +#### Build 2009260 + +- Added Loxone parser (PR #1185) +- Added support for kelvin input via `K=` HTTP and `"col":[[val]]` JSON API calls + _Notice:_ `"col":[[val]]` removed in build 2011200, use `"col":[val]` +- Added supplementary UDP socket (#1205) +- TMP2.net receivable by default +- UDP sockets accept HTTP and JSON API commands +- Fixed missing timezones (#1201) + +#### Build 2009202 + +- Fixed LPD8806 compilation + +#### Build 2009201 + +- Added support for preset cycle toggling using CY=2 +- Added ESP32 touch pin support (#1190) +- Fixed modem sleep on ESP8266 (#1184) + +#### Build 2009200 + +- Increased available heap memory by 4kB +- Use F() macro for the majority of strings +- Restructure timezone code +- Restructured settings saved code +- Updated ArduinoJSON to 6.16.1 + +#### Build 2009170 + +- New WLED logo on Welcome screen (#1164) +- Fixed 170th pixel dark in E1.31 + +#### Build 2009100 + +- Fixed sunrise mode not reinitializing +- Fixed passwords not clearable + +#### Build 2009070 + +- New Segments are now initialized with default speed and intensity + +#### Build 2009030 + +- Fixed bootloop if mDNS is used on builds without OTA support + +### WLED version 0.10.2 + +#### Build 2008310 + +- Added new logo +- Maximum GZIP compression (#1126) +- Enable WebSockets by default + +### Development versions between 0.10.0 and 0.10.2 releases + +#### Build 2008300 + +- Added new UI customization options to UI settings +- Added Dancing Shadows effect (#1108) +- Preset cycle is now paused if lights turned off or nightlight active +- Removed `esp01` and `esp01_ota` envs from travis build (need too much flash) + +#### Build 2008290 + +- Added individual LED control support to JSON API +- Added internal Segment Freeze/Pause option + +#### Build 2008250 + +- Made `platformio_override.ini` example easier to use by including the `default_envs` property +- FastLED uses `now` as timer, so effects using e.g. `beatsin88()` will sync correctly +- Extended the speed range of Pacifica effect +- Improved TPM2.net receiving (#1100) +- Fixed exception on empty MQTT payload (#1101) + +#### Build 2008200 + +- Added segment mirroring to web UI +- Fixed segment mirroring when in reverse mode + +#### Build 2008140 + +- Removed verbose live mode info from `` in HTTP API response + +#### Build 2008100 + +- Fixed Auto White mode setting (fixes #1088) + +#### Build 2008070 + +- Added segment mirroring (`mi` property) (#1017) +- Fixed DMX settings page not displayed (#1070) +- Fixed ArtNet multi universe and improve code style (#1076) +- Renamed global var `local` to `localTime` (#1078) + +#### Build 2007190 + +- Fixed hostname containing illegal characters (#1035) + +#### Build 2006251 + +- Added `SV=2` to HTTP API, allow selecting single segment only + +#### Build 2006250 + +- Fix Alexa not turning off white channel (fixes #1012) + +#### Build 2006220 + +- Added Sunrise nightlight mode +- Added Chunchun effect +- Added `LO` (live override) command to HTTP API +- Added `mode` to `nl` object of JSON state API, deprecating `fade` +- Added light color scheme support to web UI (click sun next to brightness slider) +- Added option to hide labels in web UI (click flame icon next to intensity slider) +- Added hex color input (click palette icon next to palette select) (resolves #506) +- Added support for RGB sliders (need to set in localstorage) +- Added support for custom background color or image (need to set in localstorage) +- Added option to hide bottom tab bar in PC mode (need to set in localstorage) +- Fixed transition lag with multiple segments (fixes #985) +- Changed Nightlight wording (resolves #940) + +#### Build 2006060 + +- Added five effects by Andrew Tuline (Phased, Phased Noise, Sine, Noise Pal and Twinkleup) +- Added two new effects by Aircoookie (Sunrise and Flow) +- Added US-style sequence to traffic light effect +- Merged pull request #964 adding 9 key IR remote + +#### Build 2005280 + +- Added v2 usermod API +- Added v2 example usermod `usermod_v2_example` in the usermods folder as prelimary documentation +- Added DS18B20 Temperature usermod with Info page support +- Disabled MQTT on ESP01 build to make room in flash + +#### Build 2005230 + +- Fixed TPM2 + +#### Build 2005220 + +- Added TPM2.NET protocol support (need to set WLED broadcast UDP port to 65506) +- Added TPM2 protocol support via Serial +- Support up to 6553 seconds preset cycle durations (backend, NOT yet in UI) +- Merged pull request #591 fixing WS2801 color order +- Merged pull request #858 adding fully featured travis builds +- Merged pull request #862 adding DMX proxy feature + +#### Build 2005100 + +- Update to Espalexa v2.4.6 (+1.6kB free heap memory) +- Added `m5atom` PlatformIO environment + +#### Build 2005090 + +- Default to ESP8266 Arduino core v2.7.1 in PlatformIO +- Fixed Preset Slot 16 always indicating as empty (#891) +- Disabled Alexa emulation by default (causes bootloop for some users) +- Added BWLT11 and SHOJO_PCB defines to NpbWrapper +- Merged pull request #898 adding Solid Glitter effect + +### WLED version 0.10.0 + +#### Build 2005030 + +- DMX Single RGW and Single DRGB modes now support an additional white channel +- Improved palettes derived from set colors and changed their names + +### Development versions between 0.9.1 and 0.10.0 release + +#### Build 2005020 + +- Added ACST and ACST/ACDT timezones + +#### Build 2005010 + +- Added module info page to web UI +- Added realtime override functionality to web UI +- Added individial 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 + +#### Build 2004300 + +- Added realtime override option and `lor` JSON property +- Added `lm` (live mode) and `lip` (live IP) properties to info in JSON API +- Added reset commands to APIs +- Added `json/si`, returning state and info, but no FX or Palette lists +- Added rollover detection to millis(). Can track uptimes longer than 49 days +- Attempted to fix Wifi issues with Unifi brand APs + +#### Build 2004230 + +- Added brightness and power for individual segments +- Added `on` and `bri` properties to Segment object in JSON API +- Added `C3` an `SB` commands to HTTP get API +- Merged pull request #865 for 5CH_Shojo_PCB environment + +#### Build 2004220 + +- Added Candle Multi effect +- Added Palette capability to Pacifica effect + +#### Build 2004190 + +- Added TM1814 type LED defines + +#### Build 2004120 + +- Added Art-Net support +- Added OTA platform to platformio.ini + +#### Build 2004100 + +- Fixed DMX output compilation +- Added DMX start LED setting + +#### Build 2004061 + +- Fixed RBG and BGR getPixelColor (#825) +- Improved formatting + +#### Build 2004060 + +- Consolidated global variables in wled.h + +#### Build 2003300 + +- Major change of project structure from .ino to .cpp and func_declare.h + +#### Build 2003262 + +- Fixed compilation for Analog LEDs +- Fixed sync settings network port fields too small + +#### Build 2003261 + +- Fixed live preview not displaying whole light if over 255 LEDs + +#### Build 2003251 + +- Added Pacifica effect (tentative, doesn't yet support other colors) +- Added Atlantica palette +- Fixed ESP32 build of Espalexa + +#### Build 2003222 + +- Fixed Alexa Whites on non-RGBW lights (bump Espalexa to 2.4.5) + +#### Build 2003221 + +- Moved Cronixie driver from FX library to drawOverlay handler + +#### Build 2003211 + +- Added custom mapping compile define to FX_fcn.h +- Merged pull request #784 by @TravisDean: Fixed initialization bug when toggling skip first +- Added link to youtube videos by Room31 to readme + +#### Build 2003141 + +- Fixed color of main segment returned in JSON API during transition not being target color (closes #765) +- Fixed arlsLock() being called after pixels set in E1.31 (closes #772) +- Fixed HTTP API calls not having an effect if no segment selected (now applies to main segment) + +#### Build 2003121 + +- Created changelog.md - make tracking changes to code easier +- Merged pull request #766 by @pille: Fix E1.31 out-of sequence detection + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..fbf0ddcff --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at dev.aircoookie@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/images/Readme.md b/images/Readme.md new file mode 100644 index 000000000..738a84f64 --- /dev/null +++ b/images/Readme.md @@ -0,0 +1,5 @@ +### Additional Logos + +Additional awesome logos for WLED can be found here [Aircoookie/Akemi](https://github.com/Aircoookie/Akemi). + + diff --git a/images/macbook-pro-space-gray-on-the-wooden-table.jpg b/images/macbook-pro-space-gray-on-the-wooden-table.jpg new file mode 100644 index 000000000..25f46cc1b Binary files /dev/null and b/images/macbook-pro-space-gray-on-the-wooden-table.jpg differ diff --git a/images/walking-with-iphone-x.jpg b/images/walking-with-iphone-x.jpg new file mode 100644 index 000000000..90a205b19 Binary files /dev/null and b/images/walking-with-iphone-x.jpg differ diff --git a/images/wled_logo.png b/images/wled_logo.png new file mode 100644 index 000000000..116eda0a8 Binary files /dev/null and b/images/wled_logo.png differ diff --git a/images/wled_logo_akemi.png b/images/wled_logo_akemi.png new file mode 100644 index 000000000..4f52d3708 Binary files /dev/null and b/images/wled_logo_akemi.png differ diff --git a/images/wled_logo_clean.png b/images/wled_logo_clean.png new file mode 100644 index 000000000..26a0ebeda Binary files /dev/null and b/images/wled_logo_clean.png differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..c6b006a93 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2312 @@ +{ + "name": "wled", + "version": "0.11.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "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": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "requires": { + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "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=" + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "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": { + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "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==" + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } + } + }, + "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" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "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" + } + }, + "charset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==" + }, + "cheerio": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", + "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", + "requires": { + "css-select": "~1.0.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "~3.8.1", + "lodash": "^3.2.0" + } + }, + "chokidar": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", + "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "clap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", + "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", + "requires": { + "chalk": "^1.1.3" + } + }, + "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": { + "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==" + } + } + }, + "cli-boxes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", + "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "coa": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", + "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "requires": { + "q": "^1.1.2" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + }, + "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": { + "delayed-stream": "~1.0.0" + } + }, + "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==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "configstore": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz", + "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=", + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "object-assign": "^4.0.1", + "os-tmpdir": "^1.0.0", + "osenv": "^0.1.0", + "uuid": "^2.0.1", + "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=" + } + } + }, + "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=" + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + }, + "css-select": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", + "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "1.0", + "domutils": "1.4", + "nth-check": "~1.0.0" + } + }, + "css-what": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", + "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=" + }, + "csso": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-2.0.0.tgz", + "integrity": "sha1-F4tDpEYhIhwndWCG9THgL0KQDug=", + "requires": { + "clap": "^1.0.9", + "source-map": "^0.5.3" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "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==" + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "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" + } + } + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "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" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "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": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "es6-promise": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", + "integrity": "sha1-lu258v2wGZWCKyY92KratnSBgbw=" + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + }, + "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=" + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "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==" + }, + "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": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "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": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "optional": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", + "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", + "requires": { + "ini": "^1.3.5" + } + }, + "got": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", + "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=", + "requires": { + "duplexify": "^3.2.0", + "infinity-agent": "^2.0.0", + "is-redirect": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "nested-error-stacks": "^1.0.0", + "object-assign": "^3.0.0", + "prepend-http": "^1.0.0", + "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=" + } + } + }, + "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==" + }, + "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" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" + }, + "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" + } + } + } + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "requires": { + "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-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "infinity-agent": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz", + "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "inliner": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/inliner/-/inliner-1.13.1.tgz", + "integrity": "sha1-5QApgev1Dp2fMTcRSBz/Ei1PP8s=", + "requires": { + "ansi-escapes": "^1.4.0", + "ansi-styles": "^2.2.1", + "chalk": "^1.1.3", + "charset": "^1.0.0", + "cheerio": "^0.19.0", + "debug": "^2.2.0", + "es6-promise": "^2.3.0", + "iconv-lite": "^0.4.11", + "jschardet": "^1.3.0", + "lodash.assign": "^3.2.0", + "lodash.defaults": "^3.1.2", + "lodash.foreach": "^3.0.3", + "mime": "^1.3.4", + "minimist": "^1.1.3", + "request": "^2.74.0", + "svgo": "^0.6.6", + "then-fs": "^2.0.0", + "uglify-js": "^2.8.0", + "update-notifier": "^0.5.0" + } + }, + "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": { + "binary-extensions": "^2.0.0" + } + }, + "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-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "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==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, + "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-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==" + }, + "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=" + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + }, + "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" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jschardet": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.6.0.tgz", + "integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==" + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "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": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "requires": { + "json-buffer": "3.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "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": { + "package-json": "^1.0.0" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "lodash._arrayeach": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", + "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" + }, + "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": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "lodash._baseeach": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", + "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", + "requires": { + "lodash.keys": "^3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + }, + "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": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "requires": { + "lodash._baseassign": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash.defaults": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", + "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", + "requires": { + "lodash.assign": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "lodash.foreach": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.3.tgz", + "integrity": "sha1-b9fvt5aRrs1n/erCdhyY5wHWw5o=", + "requires": { + "lodash._arrayeach": "^3.0.0", + "lodash._baseeach": "^3.0.0", + "lodash._bindcallback": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, + "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==" + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "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" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "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": { + "inherits": "~2.0.1" + } + }, + "nodemon": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", + "integrity": "sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ==", + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "requires": { + "package-json": "^6.3.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "requires": { + "rc": "^1.2.8" + } + }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "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" + } + }, + "update-notifier": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", + "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", + "requires": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, + "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": { + "boolbase": "~1.0.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "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=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, + "package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz", + "integrity": "sha1-yOysCUInzfdqMWh07QXifMk5oOA=", + "requires": { + "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" + } + } + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + }, + "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" + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "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": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "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==" + }, + "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==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "pupa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", + "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", + "requires": { + "escape-goat": "^2.0.0" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "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": { + "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" + } + } + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "registry-auth-token": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", + "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "requires": { + "rc": "^1.0.1" + } + }, + "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" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "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": { + "align-text": "^0.1.1" + } + }, + "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==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "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.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "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" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "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==" + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.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==" + }, + "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-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "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" + } + }, + "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=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "svgo": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.6.6.tgz", + "integrity": "sha1-s0CIkDbyD5tEdUMHfQ9Vc+0ETAg=", + "requires": { + "coa": "~1.0.1", + "colors": "~1.1.2", + "csso": "~2.0.0", + "js-yaml": "~3.6.0", + "mkdirp": "~0.5.1", + "sax": "~1.2.1", + "whet.extend": "~0.9.9" + } + }, + "term-size": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", + "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==" + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "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==" + } + } + }, + "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": { + "promise": ">=3.2 <8" + } + }, + "timed-out": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", + "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=" + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + }, + "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": { + "is-number": "^7.0.0" + } + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + } + }, + "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": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + } + }, + "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=", + "optional": true + }, + "undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "requires": { + "debug": "^2.2.0" + } + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "update-notifier": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz", + "integrity": "sha1-B7XcIGazYnqztPUwEw9+3doHpMw=", + "requires": { + "chalk": "^1.0.0", + "configstore": "^1.0.0", + "is-npm": "^1.0.0", + "latest-version": "^1.0.0", + "repeating": "^1.1.2", + "semver-diff": "^2.0.0", + "string-length": "^1.0.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": { + "punycode": "^2.1.0" + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "requires": { + "prepend-http": "^2.0.0" + }, + "dependencies": { + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "whet.extend": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", + "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=" + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "requires": { + "string-width": "^4.0.0" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "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": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, + "xdg-basedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", + "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", + "requires": { + "os-homedir": "^1.0.0" + } + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "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 new file mode 100644 index 000000000..5b75307b2 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "wled", + "version": "0.12.0", + "description": "Tools for WLED project", + "main": "tools/cdata.js", + "directories": { + "lib": "lib", + "test": "test" + }, + "scripts": { + "build": "node tools/cdata.js", + "dev": "nodemon -e js,html,htm,css,png,jpg,gif,ico,js -w tools/ -w wled00/data/ -x node tools/cdata.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Aircoookie/WLED.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/Aircoookie/WLED/issues" + }, + "homepage": "https://github.com/Aircoookie/WLED#readme", + "dependencies": { + "clean-css": "^4.2.3", + "html-minifier-terser": "^5.1.1", + "inliner": "^1.13.1", + "nodemon": "^2.0.4", + "zlib": "^1.0.5" + } +} diff --git a/pio-scripts/gzip-firmware.py b/pio-scripts/gzip-firmware.py new file mode 100644 index 000000000..2d0283011 --- /dev/null +++ b/pio-scripts/gzip-firmware.py @@ -0,0 +1,23 @@ +Import('env') +import os +import shutil +import gzip + +OUTPUT_DIR = "build_output{}".format(os.path.sep) + +def bin_gzip(source, target, env): + variant = str(target[0]).split(os.path.sep)[2] + + # create string with location and file names based on variant + bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) + gzip_file = "{}firmware{}{}.bin.gz".format(OUTPUT_DIR, os.path.sep, variant) + + # check if new target files exist and remove if necessary + if os.path.isfile(gzip_file): os.remove(gzip_file) + + # write gzip firmware file + with open(bin_file,"rb") as fp: + with gzip.open(gzip_file, "wb", compresslevel = 9) as f: + shutil.copyfileobj(fp, f) + +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_gzip]) diff --git a/pio-scripts/name-firmware.py b/pio-scripts/name-firmware.py new file mode 100644 index 000000000..90ea9867f --- /dev/null +++ b/pio-scripts/name-firmware.py @@ -0,0 +1,34 @@ +Import('env') +import os +import shutil + +OUTPUT_DIR = "build_output{}".format(os.path.sep) + +def bin_rename_copy(source, target, env): + variant = str(target[0]).split(os.path.sep)[2] + + # check if output directories exist and create if necessary + if not os.path.isdir(OUTPUT_DIR): + os.mkdir(OUTPUT_DIR) + + for d in ['firmware', 'map']: + if not os.path.isdir("{}{}".format(OUTPUT_DIR, d)): + os.mkdir("{}{}".format(OUTPUT_DIR, d)) + + # create string with location and file names based on variant + map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant) + bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant) + + # check if new target files exist and remove if necessary + for f in [map_file, bin_file]: + if os.path.isfile(f): + os.remove(f) + + # copy firmware.bin to firmware/.bin + shutil.copy(str(target[0]), bin_file) + + # copy firmware.map to map/.map + if os.path.isfile("firmware.map"): + shutil.move("firmware.map", map_file) + +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_rename_copy]) diff --git a/pio-scripts/obj-dump.py b/pio-scripts/obj-dump.py new file mode 100644 index 000000000..91bc3de58 --- /dev/null +++ b/pio-scripts/obj-dump.py @@ -0,0 +1,9 @@ +# Little convenience script to get an object dump + +Import('env') + +def obj_dump_after_elf(source, target, env): + print("Create firmware.asm") + env.Execute("xtensa-lx106-elf-objdump "+ "-D " + str(target[0]) + " > "+ "${PROGNAME}.asm") + +env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", [obj_dump_after_elf]) diff --git a/pio-scripts/strip-floats.py b/pio-scripts/strip-floats.py new file mode 100644 index 000000000..da916ebe2 --- /dev/null +++ b/pio-scripts/strip-floats.py @@ -0,0 +1,15 @@ +Import('env') + +# +# Dump build environment (for debug) +#print env.Dump() +# + +flags = " ".join(env['LINKFLAGS']) +flags = flags.replace("-u _printf_float", "") +flags = flags.replace("-u _scanf_float", "") +newflags = flags.split() + +env.Replace( + LINKFLAGS=newflags +) \ No newline at end of file diff --git a/pio-scripts/user_config_copy.py b/pio-scripts/user_config_copy.py new file mode 100644 index 000000000..1251ca178 --- /dev/null +++ b/pio-scripts/user_config_copy.py @@ -0,0 +1,9 @@ +Import('env') +import os +import shutil + +# copy WLED00/my_config_sample.h to WLED00/my_config.h +if os.path.isfile("wled00/my_config.h"): + print ("*** use existing my_config.h ***") +else: + shutil.copy("wled00/my_config_sample.h", "wled00/my_config.h") diff --git a/platformio.ini b/platformio.ini index dd9bb0bc3..ea5972ea4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,258 +2,507 @@ ; Please visit documentation: https://docs.platformio.org/page/projectconf.html [platformio] -src_dir = ./wled00 -data_dir = ./wled00/data -;lib_extra_dirs = ./wled00/src -lib_dir = ./wled00/src -; Please uncomment one of the 5 lines below to select your board -default_envs = nodemcuv2 -; default_envs = esp01 -; default_envs = esp01_1m +# ------------------------------------------------------------------------------ +# ENVIRONMENTS +# +# Please uncomment one of the lines below to select your board(s) +# ------------------------------------------------------------------------------ + +# Travis CI binaries (comment this out with a ';' when building for your own board) +;default_envs = travis_esp8266, travis_esp32 + +# Release binaries +default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth + +# Single binaries (uncomment your board) +; default_envs = nodemcuv2 +; 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_4CH_H801 ; default_envs = esp8285_5CH_H801 +; default_envs = d1_mini_5CH_Shojo_PCB +; default_envs = wemos_shield_esp32 +; default_envs = m5atom +; default_envs = esp32_eth + +src_dir = ./wled00 +data_dir = ./wled00/data +build_cache_dir = ~/.buildcache +extra_configs = + platformio_override.ini [common] -framework = arduino -monitor_speed = 115200 -board_build.flash_mode = dout -upload_speed = 115200 -upload_speed_fast = 921600 -build_flags = - -w ; supresses all C/C++ warnings - ; -D VERSION=0.8.5 - ; -D DEBUG - #build_flags for the IRremoteESP8266 library (enabled decoders have to appear here) - -D _IR_ENABLE_DEFAULT_=false - -D DECODE_HASH=true - -D DECODE_NEC=true - -D DECODE_SONY=true - -D DECODE_SAMSUNG=true - -D DECODE_LG=true - -# TODO replace libs in /lib with managed libs in here if possible. -# If they are not changed it's just a matter of setting the correct version and change the import statement -lib_deps_external = - #Blynk@0.5.4(changed) - #E131@1.0.0(changed) - FastLED@3.3.2 - NeoPixelBus@2.5.6 - ESPAsyncTCP@1.2.0 - ESPAsyncUDP@697c75a025 - AsyncTCP@1.0.3 - Esp Async WebServer@1.2.0 - #ArduinoJson@5.13.5 - #IRremoteESP8266@2.7.2 - https://github.com/crankyoldgit/IRremoteESP8266.git - #Time@1.5 - #Timezone@1.2.1 - #For use SSD1306 OLED display uncomment following - #U8g2@~2.27.2 - #For Dallas sensor uncomment following 2 lines - #DallasTemperature@~3.8.0 - #OneWire@~2.3.5 -[common:esp8266] # ------------------------------------------------------------------------------ # PLATFORM: # !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266 -# We use Arduino Core 2.5.0 (platformIO 2.0.4) as default # -# arduino core 2.3.0 = platformIO 1.5.0 -# arduino core 2.4.0 = platformIO 1.6.0 -# arduino core 2.4.1 = platformIO 1.7.3 -# arduino core 2.4.2 = platformIO 1.8.0 -# arduino core 2.5.0 = platformIO 2.0.4 -# arduino core stage = platformIO feature#stage +# arduino core 2.6.3 = platformIO 2.3.2 +# arduino core 2.7.0 = platformIO 2.5.0 # ------------------------------------------------------------------------------ -arduino_core_2_3_0 = espressif8266@1.5.0 -arduino_core_2_4_0 = espressif8266@1.6.0 -arduino_core_2_4_1 = espressif8266@1.7.3 -arduino_core_2_4_2 = espressif8266@1.8.0 -arduino_core_2_5_0 = espressif8266@2.0.4 -arduino_core_2_5_2 = espressif8266@2.2.3 -arduino_core_2_6_1 = espressif8266@2.3.0 -arduino_core_2_6_2 = espressif8266@2.3.1 -arduino_core_stage = https://github.com/platformio/platform-espressif8266.git#feature/stage -platform = ${common:esp8266.arduino_core_2_6_2} -build_flags = - -D ESP8266 - -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH - -Wl,-Teagle.flash.4m1m.ld ;;;; Required for core > v2.5.0 or staging version 4MB Flash 3MB SPIFFs +arduino_core_2_6_3 = espressif8266@2.3.3 +arduino_core_2_7_4 = espressif8266@2.6.2 -[common:esp8266_1M] -platform = espressif8266@1.8.0 -build_flags = - -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH - -Wl,-Teagle.flash.1m0.ld ;;;; Compile with no SPIFFS to leave space for OTA - ; -D WLED_DISABLE_MOBILE_UI - ; -D WLED_DISABLE_OTA - ; -D WLED_DISABLE_ALEXA - -D WLED_DISABLE_BLYNK - -D WLED_DISABLE_CRONIXIE - ; -D WLED_DISABLE_HUESYNC - ; -D WLED_DISABLE_INFRARED +# Development platforms +arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop +arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage -[common:esp8266_512k] -platform = espressif8266@1.8.0 -build_flags = - -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH - -Wl,-Teagle.flash.512k0.ld ;;;; Compile with no SPIFFS - ; -D WLED_DISABLE_MOBILE_UI - -D WLED_DISABLE_OTA - ; -D WLED_DISABLE_ALEXA - -D WLED_DISABLE_BLYNK - -D WLED_DISABLE_CRONIXIE - -D WLED_DISABLE_HUESYNC - ; -D WLED_DISABLE_INFRARED +# Platform to use for ESP8266 +platform_wled_default = ${common.arduino_core_2_7_4} +# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization +platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 + platformio/toolchain-xtensa @ ~2.40802.200502 + platformio/tool-esptool @ ~1.413.0 + platformio/tool-esptoolpy @ ~1.30000.0 -[common:esp32] -platform = espressif32@1.11.1 -build_flags = - -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH - -D ARDUINO_ARCH_ESP32 +# ------------------------------------------------------------------------------ +# FLAGS: DEBUG +# +# ------------------------------------------------------------------------------ +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 + +# ------------------------------------------------------------------------------ +# FLAGS: ldscript (available ldscripts at https://github.com/esp8266/Arduino/tree/master/tools/sdk/ld) +# ldscript_512k ( 512 KB) = 487 KB sketch, 4 KB eeprom, no spiffs, 16 KB reserved +# ldscript_1m0m (1024 KB) = 999 KB sketch, 4 KB eeprom, no spiffs, 16 KB reserved +# ldscript_2m1m (2048 KB) = 1019 KB sketch, 4 KB eeprom, 1004 KB spiffs, 16 KB reserved +# ldscript_4m1m (4096 KB) = 1019 KB sketch, 4 KB eeprom, 1002 KB spiffs, 16 KB reserved, 2048 KB empty/ota? +# ldscript_4m3m (4096 KB) = 1019 KB sketch, 4 KB eeprom, 3040 KB spiffs, 16 KB reserved +# +# Available lwIP variants (macros): +# -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH = v1.4 Higher Bandwidth (default) +# -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY = v2 Lower Memory +# -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH = v2 Higher Bandwidth +# -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH +# +# BearSSL performance: +# When building with -DSECURE_CLIENT=SECURE_CLIENT_BEARSSL, please add `board_build.f_cpu = 160000000` to the environment configuration +# +# BearSSL ciphers: +# When building on core >= 2.5, you can add the build flag -DBEARSSL_SSL_BASIC in order to build BearSSL with a limited set of ciphers: +# TLS_RSA_WITH_AES_128_CBC_SHA256 / AES128-SHA256 +# TLS_RSA_WITH_AES_256_CBC_SHA256 / AES256-SHA256 +# TLS_RSA_WITH_AES_128_CBC_SHA / AES128-SHA +# TLS_RSA_WITH_AES_256_CBC_SHA / AES256-SHA +# This reduces the OTA size with ~45KB, so it's especially useful on low memory boards (512k/1m). +# ------------------------------------------------------------------------------ +build_flags = + -DMQTT_MAX_PACKET_SIZE=1024 + -DSECURE_CLIENT=SECURE_CLIENT_BEARSSL + -DBEARSSL_SSL_BASIC + -D CORE_DEBUG_LEVEL=0 + -D NDEBUG + #build_flags for the IRremoteESP8266 library (enabled decoders have to appear here) + -D _IR_ENABLE_DEFAULT_=false + -D DECODE_HASH=true + -D DECODE_NEC=true + -D DECODE_SONY=true + -D DECODE_SAMSUNG=true + -D DECODE_LG=true + -DWLED_USE_MY_CONFIG + ; -D USERMOD_SENSORSTOMQTT + +build_unflags = + +# enables all features for travis CI +build_flags_all_features = + -D WLED_USE_ANALOG_LED + -D WLED_USE_H801 + -D WLED_ENABLE_5CH_LEDS + -D WLED_ENABLE_ADALIGHT + -D WLED_ENABLE_DMX + -D WLED_ENABLE_MQTT + -D WLED_ENABLE_WEBSOCKETS + +build_flags_esp8266 = ${common.build_flags} ${esp8266.build_flags} +build_flags_esp32 = ${common.build_flags} ${esp32.build_flags} + +ldscript_1m128k = eagle.flash.1m128.ld +ldscript_2m512k = eagle.flash.2m512.ld +ldscript_2m1m = eagle.flash.2m1m.ld +ldscript_4m1m = eagle.flash.4m1m.ld + +[esp8266] +build_flags = + -DESP8266 + -DFP_IN_IROM +; NONOSDK22x_190703 = 2.2.2-dev(38a443e) + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 +; lwIP 2 - Higher Bandwidth no Features +; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH +; lwIP 1.4 - Higher Bandwidth (Aircoookie has) + -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH +; VTABLES in Flash + -DVTABLES_IN_FLASH +; restrict to minimal mime-types + -DMIMETYPE_MINIMAL + +[esp32] +build_flags = -g + -DARDUINO_ARCH_ESP32 + -DCONFIG_LITTLEFS_FOR_IDF_3_2 + +[scripts_defaults] +extra_scripts = pio-scripts/name-firmware.py + pio-scripts/gzip-firmware.py + pio-scripts/strip-floats.py + pio-scripts/user_config_copy.py + +# ------------------------------------------------------------------------------ +# COMMON SETTINGS: +# ------------------------------------------------------------------------------ +[env] +framework = arduino +board_build.flash_mode = dout +monitor_speed = 115200 +# slow upload speed (comment this out with a ';' when building for development use) +upload_speed = 115200 +# fast upload speed (remove ';' when building for development use) +; upload_speed = 921600 + +# ------------------------------------------------------------------------------ +# LIBRARIES: required dependencies +# Please note that we don't always use the latest version of a library. +# +# The following libraries have been included (and some of them changd) in the source: +# ArduinoJson@5.13.5, Blynk@0.5.4(changed), E131@1.0.0(changed), Time@1.5, Timezone@1.2.1 +# ------------------------------------------------------------------------------ +lib_compat_mode = strict +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.2 + #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line + #TFT_eSPI + #For use SSD1306 OLED display uncomment following + #U8g2@~2.27.2 + #For Dallas sensor uncomment following 2 lines + #OneWire@~2.3.5 + #milesburton/DallasTemperature@^3.9.0 + #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 + +lib_ignore = + AsyncTCP + +extra_scripts = ${scripts_defaults.extra_scripts} + +# ------------------------------------------------------------------------------ +# WLED BUILDS +# ------------------------------------------------------------------------------ -# see: http://docs.platformio.org/en/latest/platforms/espressif8266.html [env:nodemcuv2] board = nodemcuv2 -platform = ${common:esp8266.platform} -monitor_speed = ${common.monitor_speed} -upload_speed = ${common.upload_speed} -framework = ${common.framework} -build_flags = - ${common.build_flags} - ${common:esp8266.build_flags} -lib_deps = - ${common.lib_deps_external} -lib_compat_mode = strict -lib_ignore = AsynTCP +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} -[env:d1_mini] -board = d1_mini -platform = ${common:esp8266.platform} -monitor_speed = ${common.monitor_speed} -upload_speed = ${common.upload_speed} -framework = ${common.framework} -build_flags = - ${common.build_flags} - ${common:esp8266.build_flags} -lib_deps = - ${common.lib_deps_external} -lib_compat_mode = strict -lib_ignore = AsynTCP - -[env:esp01_1m] +[env:esp01_1m_full] board = esp01_1m -platform = ${common:esp8266_1M.platform} -monitor_speed = ${common.monitor_speed} -upload_speed = ${common.upload_speed} -framework = ${common.framework} -build_flags = - ${common.build_flags} - ${common:esp8266_1M.build_flags} - # disable IR because there is no pin for it - -D WLED_DISABLE_INFRARED -lib_deps = - ${common.lib_deps_external} -lib_compat_mode = strict -lib_ignore = AsynTCP - -[env:esp01] -board = esp01 -platform = ${common:esp8266_512k.platform} -monitor_speed = ${common.monitor_speed} -upload_speed = ${common.upload_speed} -framework = ${common.framework} -build_flags = - ${common.build_flags} - ${common:esp8266_512k.build_flags} - -D WLED_DISABLE_INFRARED -lib_deps = - ${common.lib_deps_external} -lib_compat_mode = strict -lib_ignore = AsynTCP +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 [env:esp07] board = esp07 -platform = ${common:esp8266.platform} -monitor_speed = ${common.monitor_speed} -upload_speed = ${common.upload_speed} -framework = ${common.framework} -build_flags = - ${common.build_flags} - ${common:esp8266.build_flags} -lib_deps = - ${common.lib_deps_external} -lib_compat_mode = strict -lib_ignore = AsynTCP +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} + +[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} +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} + +[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 -# see: http://docs.platformio.org/en/latest/platforms/espressif32.html [env:esp32dev] board = esp32dev -platform = ${common:esp32.platform} -monitor_speed = ${common.monitor_speed} -upload_speed = ${common.upload_speed_fast} -framework = ${common.framework} -build_flags = - ${common.build_flags} - ${common:esp32.build_flags} -lib_deps = - ${common.lib_deps_external} +platform = espressif32@3.2 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} lib_ignore = + ESPAsyncTCP + ESPAsyncUDP + +[env:esp32_eth] +board = esp32-poe +platform = espressif32@3.2 +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 +lib_ignore = + ESPAsyncTCP ESPAsyncUDP -lib_compat_mode = strict [env:esp8285_4CH_MagicHome] board = esp8285 -platform = ${common:esp8266_1M.platform} -monitor_speed = ${common.monitor_speed} -upload_speed = ${common.upload_speed} -framework = ${common.framework} -build_flags = - ${common.build_flags} - ${common:esp8266_1M.build_flags} - -D WLED_DISABLE_HUESYNC - -D WLED_USE_ANALOG_LEDS -lib_deps = - ${common.lib_deps_external} -lib_compat_mode = strict -lib_ignore = AsynTCP +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_1m128k} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS [env:esp8285_4CH_H801] board = esp8285 -platform = ${common:esp8266_1M.platform} -monitor_speed = ${common.monitor_speed} -upload_speed = ${common.upload_speed} -framework = ${common.framework} -build_flags = - ${common.build_flags} - ${common:esp8266_1M.build_flags} - -D WLED_DISABLE_HUESYNC - -D WLED_USE_ANALOG_LEDS - -D WLED_USE_H801 -lib_deps = - ${common.lib_deps_external} -lib_compat_mode = strict -lib_ignore = AsynTCP +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_1m128k} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS -D WLED_USE_H801 [env:esp8285_5CH_H801] board = esp8285 -platform = ${common:esp8266_1M.platform} -monitor_speed = ${common.monitor_speed} -upload_speed = ${common.upload_speed} -framework = ${common.framework} -build_flags = - ${common.build_flags} - ${common:esp8266_1M.build_flags} - -D WLED_DISABLE_HUESYNC - -D WLED_USE_ANALOG_LEDS - -D WLED_USE_H801 - -D WLED_ENABLE_5CH_LEDS -lib_deps = - ${common.lib_deps_external} -lib_compat_mode = strict -lib_ignore = AsynTCP - +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_1m128k} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS -D WLED_USE_H801 -D WLED_ENABLE_5CH_LEDS + +[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_ANALOG_LEDS -D WLED_USE_SHOJO_PCB -D WLED_ENABLE_5CH_LEDS + +# ------------------------------------------------------------------------------ +# 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} + +[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} + +[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 + +# ------------------------------------------------------------------------------ +# custom board configurations +# ------------------------------------------------------------------------------ + +[env:custom_LEDPIN_4] +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=4 -D IRPIN=5 + +[env:custom_LEDPIN_16] +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=16 + + +[env:custom_LEDPIN_3] +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=3 + +[env:custom_APA102] +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 USE_APA102 + +[env:custom_WS2801] +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 USE_WS2801 + +[env:custom32_LEDPIN_16] +board = esp32dev +platform = espressif32@3.2 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D LEDPIN=16 -D RLYPIN=19 +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP + +[env:custom32_APA102] +board = esp32dev +platform = espressif32@3.2 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D USE_APA102 +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP + +[env:custom32_TOUCHPIN_T0] +board = esp32dev +platform = espressif32@3.2 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D TOUCHPIN=T0 +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP + +[env:wemos_shield_esp32] +board = esp32dev +platform = espressif32@3.2 +upload_port = /dev/cu.SLAB_USBtoUART +monitor_port = /dev/cu.SLAB_USBtoUART +upload_speed = 460800 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D LEDPIN=16 -D RLYPIN=19 -D BTNPIN=17 +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP + +[env:m5atom] +board = esp32dev +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39 +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP +platform = espressif32@3.2 + +[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 + +# ------------------------------------------------------------------------------ +# travis test board configurations +# ------------------------------------------------------------------------------ + +[env:travis_esp8266] +extends = env:d1_mini +build_type = debug +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} ${common.debug_flags} ${common.build_flags_all_features} + +[env:travis_esp32] +extends = env:esp32dev +; build_type = debug +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} ${common.debug_flags} ${common.build_flags_all_features} + +# ------------------------------------------------------------------------------ +# codm pixel controller board configurations +# ------------------------------------------------------------------------------ + +[env:codm-controller-0.4] +board = esp_wroom_02 +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_2m512k} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 + +[env:codm-controller-0.4-WS2801] +board = esp_wroom_02 +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_2m512k} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D USE_WS2801 -D CLKPIN=13 -D DATAPIN=3 + +[env:codm-controller-0.4-APA102] +board = esp_wroom_02 +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_2m512k} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D USE_APA102 -D CLKPIN=13 -D DATAPIN=3 + +[env:codm-controller-0.5] +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} + +[env:codm-controller-0.5-WS2801] +board = esp_wroom_02 +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_2m512k} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D USE_WS2801 #-D CLKPIN=0 -D DATAPIN=2 + +[env:codm-controller-0.5-APA102] +board = esp_wroom_02 +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_2m512k} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} -D USE_APA102 #-D CLKPIN=0 -D DATAPIN=2 diff --git a/platformio_override.ini.sample b/platformio_override.ini.sample new file mode 100644 index 000000000..c9dab5487 --- /dev/null +++ b/platformio_override.ini.sample @@ -0,0 +1,50 @@ +# 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} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} +; ********************************************************************* +; *** Use custom settings from file my_config.h + -DWLED_USE_MY_CONFIG +; ********************************************************************* +; -D WLED_DISABLE_OTA +; -D WLED_DISABLE_ALEXA +; -D WLED_DISABLE_BLYNK +; -D WLED_DISABLE_CRONIXIE +; -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), uncomment the following +; PWM pins 5,12,13,15 are used with Magic Home LED Controller (default) + -D WLED_USE_ANALOG_LEDS +; for the H801 controller (PINs 15,13,12,14 (W2 = 04)) uncomment this +; -D WLED_USE_H801 +; for the BW-LT11 controller (PINs 12,4,14,5 ) uncomment this +; -D WLED_USE_BWLT11 +; and to enable channel 5 for RGBW-CT led strips this +; -D WLED_USE_5CH_LEDS diff --git a/readme.md b/readme.md index 044344ea1..4aa968882 100644 --- a/readme.md +++ b/readme.md @@ -1,69 +1,101 @@ -![WLED logo](https://raw.githubusercontent.com/Aircoookie/WLED/master/wled_logo.png) +

+ + + + + + + + -[![](https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square)](https://github.com/Aircoookie/WLED/releases) -[![](https://img.shields.io/discourse/topics?colorB=blue&label=forum&server=https%3A%2F%2Fwled.discourse.group%2F&style=flat-square)](https://wled.discourse.group) -[![](https://img.shields.io/discord/473448917040758787.svg?colorB=blue&label=discord&style=flat-square)](https://discord.gg/KuqP7NE) -[![](https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square)](https://github.com/Aircoookie/WLED/wiki) -[![](https://img.shields.io/badge/app-wled-blue.svg?style=flat-square)](https://github.com/Aircoookie/WLED-App) +

+ +# Welcome to my project WLED! ✨ -## Welcome to my project WLED! +A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! -A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812, APA102) LEDs! - -### Features: -- WS2812FX library integrated for almost 100 special effects -- FastLED noise effects and palettes +## ⚙️ Features +- WS2812FX library integrated for over 100 special effects +- FastLED noise effects and 50 palettes - Modern UI with color, effect and segment controls - Segments to set different effects and colors to parts of the LEDs - Settings page - configuration over network - Access Point and station mode - automatic failsafe AP +- Up to 10 LED outputs per instance - Support for RGBW strips -- 16 user presets to save and load colors/effects easily, supports cycling through them. -- Macro functions to automatically execute API calls +- 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 - Configurable analog clock + support for the Cronixie kit by Diamex - Configurable Auto Brightness limit for safer operation +- Filesystem-based config for easier backup of presets and settings -### Supported light control interfaces: -- WLED app for Android and iOS +## 💡 Supported light control interfaces +- WLED app for [Android](https://play.google.com/store/apps/details?id=com.aircoookie.WLED) and [iOS](https://apps.apple.com/us/app/wled/id1475695033) - JSON and HTTP request APIs - MQTT - Blynk IoT -- E1.31 -- Hyperion +- E1.31, Art-Net, DDP and TPM2.net +- [Hyperion](https://github.com/hyperion-project/hyperion.ng) - UDP realtime - Alexa voice control (including dimming and color) - Sync to Philips hue lights -- Adalight (PC ambilight via serial) +- Adalight (PC ambilight via serial) and TPM2 - Sync color of multiple WLED devices (UDP notifier) - Infrared remotes (24-key RGB, receiver required) - Simple timers/schedules (time from NTP, timezones/DST supported) -### Quick start guide and documentation: +## 📲 Quick start guide and documentation See the [wiki](https://github.com/Aircoookie/WLED/wiki)! -DrZzs has made some excellent video guides: -[Introduction, hardware and installation](https://www.youtube.com/watch?v=tXvtxwK3jRk) -[Settings, tips and tricks](https://www.youtube.com/watch?v=6eCE2BpLaUQ) +[On this page](https://github.com/Aircoookie/WLED/wiki/Learning-the-ropes) you can find excellent tutorials made by the community and helpful tools to help you get your new lamp up and running! -If you'd rather read, here is a very [detailed step-by-step beginner tutorial](https://tynick.com/blog/11-03-2019/getting-started-with-wled-on-esp8266/) by tynick! +## 🖼️ Images + -### Other +## 💾 Compatible LED Strips +Type | Voltage | Comments +|---|---|---| +WS2812B | 5v | +WS2813 | 5v | +SK6812 | 5v | RGBW +APA102 | 5v | C/D +WS2801 | 5v | C/D +LPD8806 | 5v | C/D +TM1814 | 12v | RGBW +WS2811 | 12v | 3-LED segments +WS2815 | 12v | +GS8208 | 12v | +Analog/non-addressable | any | Requires additional circuitry + +## 🧊 Compatible PC RGB Fans and ARGB accessories +Brand | Model | Comments +|---|---|---| +Corsair | HD120 Fan | Uses WS2812B, data-in only +PCCOOLER | Moonlight 5-pack Fans | Uses WS2812B, includes Data-out connector to keep each fan uniquely addressable if wired in series like traditional LED strips +Any | 5v 3-pin ARGB for PC | Any PC RGB device that supports the 5v 3-pin ARGB motherboard header should work fine with WLED. All the major motherboard vendors support the Corsair HD120 and PCCOOLER fans listed, so we can safely assume any device that supports motherboard ARGB 5V 3-Pin standard will work with WLED. + + +## ✌️ Other Licensed under the MIT license Credits [here](https://github.com/Aircoookie/WLED/wiki/Contributors-&-About)! Uses Linearicons by Perxis! -Join the Discord [server](https://discord.gg/KuqP7NE) to discuss everything about WLED! +Join the Discord server to discuss everything about WLED! + + + Check out the WLED [Discourse forum](https://wled.discourse.group)! You can also send me mails to [dev.aircoookie@gmail.com](mailto:dev.aircoookie@gmail.com), but please only do so if you want to talk to me privately. If WLED really brightens up your every day, you can [![](https://img.shields.io/badge/send%20me%20a%20small%20gift-paypal-blue.svg?style=flat-square)](https://paypal.me/aircoookie) -*Disclaimer:* -If you are sensitive to photoeleptic seizures it is not recommended that you use this software. -In case you still want to try, don't use strobe, lighting or noise modes or high effect speed settings. -As per the MIT license, i assume no liability for any damage to you or any other person or equipment. + +*Disclaimer:* +If you are sensitive to photosensitive epilepsy it is not recommended that you use this software. +In case you still want to try, don't use strobe, lighting or noise modes or high effect speed settings. +As per the MIT license, I assume no liability for any damage to you or any other person or equipment. diff --git a/tools/cdata.js b/tools/cdata.js new file mode 100644 index 000000000..201193f6f --- /dev/null +++ b/tools/cdata.js @@ -0,0 +1,424 @@ +/** + * Writes compressed C arrays of data files (web interface) + * How to use it? + * + * 1) Install Node 11+ and npm + * 2) npm install + * 3) npm run build + * + * If you change data folder often, you can run it in monitoring mode (it will recompile and update *.h on every file change) + * + * > npm run dev + * + * How it works? + * + * 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 packageJson = require("../package.json"); + +/** + * + */ +function hexdump(buffer) { + let lines = []; + + for (let i = 0; i < buffer.length; i += 16) { + let block = buffer.slice(i, i + 16); // cut buffer into blocks of 16 + let hexArray = []; + + for (let value of block) { + hexArray.push("0x" + value.toString(16).padStart(2, "0")); + } + + let hexString = hexArray.join(", "); + let line = ` ${hexString}`; + lines.push(line); + } + + return lines.join(",\n"); +} + +const inliner = require("inliner"); +const zlib = require("zlib"); + +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); + } + + let version = packageJson.version; + if (version) { + html = strReplace(html, "##VERSION##", version); + } + + return html; +} + +function writeHtmlGzipped(sourceFile, resultFile) { + 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; + } + + 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://github.com/Aircoookie/WLED/wiki/Add-own-functionality#web-ui + * to find out how to easily modify the web UI source! + */ + +// Autogenerated from ${sourceFile}, do not edit!! +const uint16_t PAGE_index_L = ${result.length}; +const uint8_t PAGE_index[] PROGMEM = { +${array} +}; +`; + console.info("Writing " + resultFile); + fs.writeFileSync(resultFile, src); + }); + }); +} + +const CleanCSS = require("clean-css"); +const MinifyHTML = require("html-minifier-terser").minify; + +function filter(str, type) { + str = adoptVersionAndRepo(str); + + if (type === undefined) { + return str; + } else if (type == "css-minify") { + return new CleanCSS({}).minify(str).styles; + } 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; + } +} + +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 || "" + }"; + +`; + return s.mangle ? s.mangle(chunk) : 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 s.mangle ? s.mangle(chunk) : 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://github.com/Aircoookie/WLED/wiki/Add-own-functionality#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 + ); + } + }); + console.info("Writing " + src.length + " characters into " + resultFile); + fs.writeFileSync(resultFile, src); +} + +writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h"); + +writeChunks( + "wled00/data", + [ + { + file: "style.css", + name: "PAGE_settingsCss", + prepend: "=====()=====", + method: "plaintext", + filter: "css-minify", + }, + { + file: "settings.htm", + name: "PAGE_settings", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => + str + .replace("%", "%%") + .replace(/User Interface\<\/button\>\<\/form\>/gms, "User Interface\<\/button\>\<\/form\>%DMXMENU%"), + }, + { + file: "settings_wifi.htm", + name: "PAGE_settings_wifi", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => + str + .replace(/\/gms, "") + .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") + .replace( + /function GetV().*\<\/script\>/gms, + "function GetV() {var d=document;\n" + ), + }, + { + file: "settings_leds.htm", + name: "PAGE_settings_leds", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => + str + .replace(/\/gms, "") + .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") + .replace( + /function GetV().*\<\/script\>/gms, + "function GetV() {var d=document;\n" + ), + }, + { + file: "settings_dmx.htm", + name: "PAGE_settings_dmx", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => { + const nocss = str + .replace(/\/gms, "") + .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") + .replace( + /function GetV().*\<\/script\>/gms, + "function GetV() {var d=document;\n" + ); + return ` +#ifdef WLED_ENABLE_DMX +${nocss} +#else +const char PAGE_settings_dmx[] PROGMEM = R"=====()====="; +#endif +`; + }, + }, + { + file: "settings_ui.htm", + name: "PAGE_settings_ui", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => + str + .replace(/\/gms, "") + .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") + .replace( + /function GetV().*\<\/script\>/gms, + "function GetV() {var d=document;\n" + ), + }, + { + file: "settings_sync.htm", + name: "PAGE_settings_sync", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => + str + .replace(/\/gms, "") + .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") + .replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"), + }, + { + file: "settings_time.htm", + name: "PAGE_settings_time", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => + str + .replace(/\/gms, "") + .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") + .replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"), + }, + { + file: "settings_sec.htm", + name: "PAGE_settings_sec", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => + str + .replace(/\/gms, "") + .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") + .replace( + /function GetV().*\<\/script\>/gms, + "function GetV() {var d=document;\n" + ), + }, + ], + "wled00/html_settings.h" +); + +writeChunks( + "wled00/data", + [ + { + file: "usermod.htm", + name: "PAGE_usermod", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => + str.replace(/fetch\("http\:\/\/.*\/win/gms, 'fetch("/win'), + }, + { + file: "msg.htm", + name: "PAGE_msg", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => str.replace(/\.*\<\/body\>/gms, "

%MSG%"), + }, + { + file: "dmxmap.htm", + name: "PAGE_dmxmap", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + mangle: (str) => ` +#ifdef WLED_ENABLE_DMX +${str.replace(/function FM\(\)[ ]?\{/gms, "function FM() {%DMXVARS%\n")} +#else +const char PAGE_dmxmap[] PROGMEM = R"=====()====="; +#endif +`, + }, + { + file: "update.htm", + name: "PAGE_update", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + }, + { + file: "welcome.htm", + name: "PAGE_welcome", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + }, + { + file: "liveview.htm", + name: "PAGE_liveview", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + }, + { + file: "liveviewws.htm", + name: "PAGE_liveviewws", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + }, + { + file: "404.htm", + name: "PAGE_404", + prepend: "=====(", + append: ")=====", + method: "plaintext", + filter: "html-minify", + }, + { + file: "favicon.ico", + name: "favicon", + method: "binary", + }, + ], + "wled00/html_other.h" +); diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h new file mode 100644 index 000000000..9717589da --- /dev/null +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -0,0 +1,427 @@ +/* + * Usermod for detecting people entering/leaving a staircase and switching the + * staircase on/off. + * + * Edit the Animated_Staircase_config.h file to compile this usermod for your + * specific configuration. + * + * See the accompanying README.md file for more info. + */ +#pragma once +#include "wled.h" +#include "Animated_Staircase_config.h" +#define USERMOD_ID_ANIMATED_STAIRCASE 1011 + +/* Initial configuration (available in API and stored in flash) */ +bool enabled = true; // Enable this usermod +unsigned long segment_delay_ms = 150; // Time between switching each segment +unsigned long on_time_ms = 5 * 1000; // The time for the light to stay on +#ifndef TOP_PIR_PIN +unsigned int topMaxTimeUs = 1749; // default echo timout, top +#endif +#ifndef BOTTOM_PIR_PIN +unsigned int bottomMaxTimeUs = 1749; // default echo timout, bottom +#endif + +// Time between checking of the sensors +const int scanDelay = 50; + +class Animated_Staircase : public Usermod { + private: + // Lights on or off. + // Flipping this will start a transition. + bool on = false; + + // Swipe direction for current transition +#define SWIPE_UP true +#define SWIPE_DOWN false + bool swipe = SWIPE_UP; + + // Indicates which Sensor was seen last (to determine + // the direction when swiping off) +#define LOWER false +#define UPPER true + bool lastSensor = LOWER; + + // Time of the last transition action + unsigned long lastTime = 0; + + // Time of the last sensor check + unsigned long lastScanTime = 0; + + // Last time the lights were switched on or off + unsigned long lastSwitchTime = 0; + + // segment id between onIndex and offIndex are on. + // controll the swipe by setting/moving these indices around. + // onIndex must be less than or equal to offIndex + byte onIndex = 0; + byte offIndex = 0; + + // The maximum number of configured segments. + // Dynamically updated based on user configuration. + byte maxSegmentId = 1; + byte mainSegmentId = 0; + + bool saveState = false; + + // These values are used by the API to read the + // last sensor state, or trigger a sensor + // through the API + bool topSensorRead = false; + bool topSensorWrite = false; + bool bottomSensorRead = false; + bool bottomSensorWrite = false; + + void updateSegments() { + mainSegmentId = strip.getMainSegmentId(); + WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId); + WS2812FX::Segment* segments = strip.getSegments(); + for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) { + if (!segments->isActive()) { + maxSegmentId = i - 1; + break; + } + + if (i >= onIndex && i < offIndex) { + segments->setOption(SEG_OPTION_ON, 1, 1); + + // We may need to copy mode and colors from segment 0 to make sure + // changes are propagated even when the config is changed during a wipe + // segments->mode = mainsegment.mode; + // segments->colors[0] = mainsegment.colors[0]; + } else { + segments->setOption(SEG_OPTION_ON, 0, 1); + } + // Always mark segments as "transitional", we are animating the staircase + segments->setOption(SEG_OPTION_TRANSITIONAL, 1, 1); + } + colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE); + } + + /* + * Detects if an object is within ultrasound range. + * signalPin: The pin where the pulse is sent + * echoPin: The pin where the echo is received + * maxTimeUs: Detection timeout in microseconds. If an echo is + * 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. + * Since the sound has to travel back and forth, the detection + * distance for the sensor in cm is (0.0343 * maxTimeUs) / 2. + * + * For practical reasons, here are some useful distances: + * + * Distance = maxtime + * 5 cm = 292 uS + * 10 cm = 583 uS + * 20 cm = 1166 uS + * 30 cm = 1749 uS + * 50 cm = 2915 uS + * 100 cm = 5831 uS + */ + bool ultrasoundRead(uint8_t signalPin, + uint8_t echoPin, + unsigned int maxTimeUs) { + digitalWrite(signalPin, HIGH); + delayMicroseconds(10); + digitalWrite(signalPin, LOW); + return pulseIn(echoPin, HIGH, maxTimeUs) > 0; + } + + void checkSensors() { + if ((millis() - lastScanTime) > scanDelay) { + lastScanTime = millis(); + +#ifdef BOTTOM_PIR_PIN + bottomSensorRead = bottomSensorWrite || (digitalRead(BOTTOM_PIR_PIN) == HIGH); +#else + bottomSensorRead = bottomSensorWrite || ultrasoundRead(BOTTOM_TRIGGER_PIN, BOTTOM_ECHO_PIN, bottomMaxTimeUs); +#endif + +#ifdef TOP_PIR_PIN + topSensorRead = topSensorWrite || (digitalRead(TOP_PIR_PIN) == HIGH); +#else + topSensorRead = topSensorWrite || ultrasoundRead(TOP_TRIGGER_PIN, TOP_ECHO_PIN, topMaxTimeUs); +#endif + + // Values read, reset the flags for next API call + topSensorWrite = false; + bottomSensorWrite = false; + + if (topSensorRead != bottomSensorRead) { + lastSwitchTime = millis(); + + if (on) { + lastSensor = topSensorRead; + } else { + // If the bottom sensor triggered, we need to swipe up, ON + swipe = bottomSensorRead; + + if (swipe) { + Serial.println("ON -> Swipe up."); + } else { + Serial.println("ON -> Swipe down."); + } + + if (onIndex == offIndex) { + // Position the indices for a correct on-swipe + if (swipe == SWIPE_UP) { + onIndex = mainSegmentId; + } else { + onIndex = maxSegmentId+1; + } + offIndex = onIndex; + } + on = true; + } + } + } + } + + void autoPowerOff() { + if (on && ((millis() - lastSwitchTime) > on_time_ms)) { + // Swipe OFF in the direction of the last sensor detection + swipe = lastSensor; + on = false; + + if (swipe) { + Serial.println("OFF -> Swipe up."); + } else { + Serial.println("OFF -> Swipe down."); + } + } + } + + void updateSwipe() { + if ((millis() - lastTime) > segment_delay_ms) { + lastTime = millis(); + + byte oldOnIndex = onIndex; + byte oldOffIndex = offIndex; + + if (on) { + // Turn on all segments + onIndex = MAX(mainSegmentId, onIndex - 1); + offIndex = MIN(maxSegmentId + 1, offIndex + 1); + } else { + if (swipe == SWIPE_UP) { + onIndex = MIN(offIndex, onIndex + 1); + } else { + offIndex = MAX(onIndex, offIndex - 1); + } + } + + updateSegments(); + } + } + + void writeSettingsToJson(JsonObject& root) { + JsonObject staircase = root["staircase"]; + if (staircase.isNull()) { + staircase = root.createNestedObject("staircase"); + } + staircase["enabled"] = enabled; + staircase["segment-delay-ms"] = segment_delay_ms; + staircase["on-time-s"] = on_time_ms / 1000; + +#ifdef TOP_TRIGGER_PIN + staircase["top-echo-us"] = topMaxTimeUs; +#endif +#ifdef BOTTOM_TRIGGER_PIN + staircase["bottom-echo-us"] = bottomMaxTimeUs; +#endif + } + + void writeSensorsToJson(JsonObject& root) { + JsonObject staircase = root["staircase"]; + if (staircase.isNull()) { + staircase = root.createNestedObject("staircase"); + } + staircase["top-sensor"] = topSensorRead; + staircase["bottom-sensor"] = bottomSensorRead; + } + + bool readSettingsFromJson(JsonObject& root) { + JsonObject staircase = root["staircase"]; + bool changed = false; + + bool shouldEnable = staircase["enabled"] | enabled; + if (shouldEnable != enabled) { + enable(shouldEnable); + changed = true; + } + + unsigned long c_segment_delay_ms = staircase["segment-delay-ms"] | segment_delay_ms; + if (c_segment_delay_ms != segment_delay_ms) { + segment_delay_ms = c_segment_delay_ms; + changed = true; + } + + unsigned long c_on_time_ms = (staircase["on-time-s"] | (on_time_ms / 1000)) * 1000; + if (c_on_time_ms != on_time_ms) { + on_time_ms = c_on_time_ms; + changed = true; + } + +#ifdef TOP_TRIGGER_PIN + unsigned int c_topMaxTimeUs = staircase["top-echo-us"] | topMaxTimeUs; + if (c_topMaxTimeUs != topMaxTimeUs) { + topMaxTimeUs = c_topMaxTimeUs; + changed = true; + } +#endif +#ifdef BOTTOM_TRIGGER_PIN + unsigned int c_bottomMaxTimeUs = staircase["bottom-echo-us"] | bottomMaxTimeUs; + if (c_bottomMaxTimeUs != bottomMaxTimeUs) { + bottomMaxTimeUs = c_bottomMaxTimeUs; + changed = true; + } +#endif + + return changed; + } + + void readSensorsFromJson(JsonObject& root) { + JsonObject staircase = root["staircase"]; + bottomSensorWrite = bottomSensorRead || (staircase["bottom-sensor"].as()); + topSensorWrite = topSensorRead || (staircase["top-sensor"].as()); + } + + void enable(bool enable) { + if (enable) { + Serial.println("Animated Staircase enabled."); + Serial.print("Delay between steps: "); + Serial.print(segment_delay_ms, DEC); + Serial.print(" milliseconds.\nStairs switch off after: "); + Serial.print(on_time_ms / 1000, DEC); + Serial.println(" seconds."); + +#ifdef BOTTOM_PIR_PIN + pinMode(BOTTOM_PIR_PIN, INPUT); +#else + pinMode(BOTTOM_TRIGGER_PIN, OUTPUT); + pinMode(BOTTOM_ECHO_PIN, INPUT); +#endif + +#ifdef TOP_PIR_PIN + pinMode(TOP_PIR_PIN, INPUT); +#else + pinMode(TOP_TRIGGER_PIN, OUTPUT); + pinMode(TOP_ECHO_PIN, INPUT); +#endif + } else { + // Restore segment options + WS2812FX::Segment mainsegment = strip.getSegment(mainSegmentId); + WS2812FX::Segment* segments = strip.getSegments(); + for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) { + if (!segments->isActive()) { + maxSegmentId = i - 1; + break; + } + segments->setOption(SEG_OPTION_ON, 1, 1); + } + colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE); + Serial.println("Animated Staircase disabled."); + } + enabled = enable; + } + + public: + void setup() { enable(enabled); } + + void loop() { + // Write changed settings from to flash (see readFromJsonState()) + if (saveState) { + serializeConfig(); + saveState = false; + } + + if (!enabled) { + return; + } + + checkSensors(); + autoPowerOff(); + updateSwipe(); + + } + + uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; } + + /* + * Shows configuration settings to the json API. This object looks like: + * + * "staircase" : { + * "enabled" : true + * "segment-delay-ms" : 150, + * "on-time-s" : 5 + * } + * + */ + void addToJsonState(JsonObject& root) { + writeSettingsToJson(root); + writeSensorsToJson(root); + Serial.println("Staircase config exposed in API."); + } + + /* + * Reads configuration settings from the json API. + * See void addToJsonState(JsonObject& root) + */ + void readFromJsonState(JsonObject& root) { + // The call to serializeConfig() must be done in the main loop, + // so we set a flag to signal the main loop to save state. + saveState = readSettingsFromJson(root); + readSensorsFromJson(root); + Serial.println("Staircase config read from API."); + } + + /* + * Writes the configuration to internal flash memory. + */ + void addToConfig(JsonObject& root) { + writeSettingsToJson(root); + Serial.println("Staircase config saved."); + } + + /* + * Reads the configuration to internal flash memory before setup() is called. + */ + void readFromConfig(JsonObject& root) { + readSettingsFromJson(root); + Serial.println("Staircase config loaded."); + } + + /* + * Shows the delay between steps and power-off time in the "info" + * tab of the web-UI. + */ + void addToJsonInfo(JsonObject& root) { + JsonObject staircase = root["u"]; + if (staircase.isNull()) { + staircase = root.createNestedObject("u"); + } + + if (enabled) { + JsonArray usermodEnabled = + staircase.createNestedArray("Staircase enabled"); // name + usermodEnabled.add("yes"); // value + + JsonArray segmentDelay = + staircase.createNestedArray("Delay between stairs"); // name + segmentDelay.add(segment_delay_ms); // value + segmentDelay.add(" milliseconds"); // unit + + JsonArray onTime = + staircase.createNestedArray("Power-off stairs after"); // name + onTime.add(on_time_ms / 1000); // value + onTime.add(" seconds"); // unit + } else { + JsonArray usermodEnabled = + staircase.createNestedArray("Staircase enabled"); // name + usermodEnabled.add("no"); // value + } + } +}; \ No newline at end of file diff --git a/usermods/Animated_Staircase/Animated_Staircase_config.h b/usermods/Animated_Staircase/Animated_Staircase_config.h new file mode 100644 index 000000000..980507477 --- /dev/null +++ b/usermods/Animated_Staircase/Animated_Staircase_config.h @@ -0,0 +1,21 @@ +/* + * Animated_Staircase compiletime confguration. + * + * Please see README.md on how to change this file. + */ + +// Please change the pin numbering below to match your board. +#define TOP_PIR_PIN D5 +#define BOTTOM_PIR_PIN D6 + +// Or uncumment and a pir and use an ultrasound HC-SR04 sensor, +// see README.md for details +#ifndef TOP_PIR_PIN +#define TOP_TRIGGER_PIN D2 +#define TOP_ECHO_PIN D3 +#endif + +#ifndef BOTTOM_PIR_PIN +#define BOTTOM_TRIGGER_PIN D4 +#define BOTTOM_ECHO_PIN D5 +#endif \ No newline at end of file diff --git a/usermods/Animated_Staircase/README.md b/usermods/Animated_Staircase/README.md new file mode 100644 index 000000000..6e84b5444 --- /dev/null +++ b/usermods/Animated_Staircase/README.md @@ -0,0 +1,203 @@ +# Usermod Animated Staircase +This usermod makes your staircase look cool by switching it on with an animation. It uses +PIR or ultrasonic sensors at the top and bottom of your stairs to: + +- Light up the steps in your walking direction, leading the way. +- Switch off the steps after you, in the direction of the last detected movement. +- Always switch on when one of the sensors detects movement, even if an effect + is still running. It can therewith handle multiple people on the stairs gracefully. + +The Animated Staircase can be controlled by the WLED API. Change settings such as +speed, on/off time and distance settings 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). + +Before compiling, you have to make the following modifications: + +Edit `usermods_list.cpp`: +1. Open `wled00/usermods_list.cpp` +2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file +3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function. + +Edit `Animated_Staircase_config.h`: +1. Open `usermods/Animated_Staircase/Animated_Staircase_config.h` +2. To use PIR sensors, change these lines to match your setup: + Using D7 and D6 pin notation as used on several boards: + + ```cpp + #define TOP_PIR_PIN D7 + #define BOTTOM_PIR_PIN D6 + ``` + + Or using GPIO numbering for pins 25 and 26: + ```cpp + #define TOP_PIR_PIN 26 + #define BOTTOM_PIR_PIN 25 + ``` + + To use Ultrasonic HC-SR04 sensors instead of (one of the) PIR sensors, + uncomment one of the PIR sensor lines and adjust the pin numbers for the + connected Ultrasonic sensor. In the example below we use an Ultrasonic + sensor at the bottom of the stairs: + + ```cpp + #define TOP_PIR_PIN 32 + //#define BOTTOM_PIR_PIN D6 /* This PIR sensor is disabled */ + + #ifndef TOP_PIR_PIN + #define TOP_SIGNAL_PIN D2 + #define TOP_ECHO_PIN D3 + #endif + + #ifndef BOTTOM_PIR_PIN /* If the bottom PIR is disabled, */ + #define BOTTOM_SIGNAL_PIN 25 /* This Ultrasonic sensor is used */ + #define BOTTOM_ECHO_PIN 26 + #endif + ``` + +After these modifications, compile and upload your WLED binary to your board +and check the WLED info page to see if this usermod is enabled. + +## Hardware installation +1. Stick the LED strip under each step of the stairs. +2. Connect the ESP8266 pin D4 or ESP32 pin D2 to the first LED data pin at the bottom step + of your stairs. +3. Connect the data-out pin at the end of each strip per step to the data-in pin on the + other end of the next step, creating one large virtual LED strip. +4. Mount sensors of choice at the bottom and top of the stairs and connect them to the ESP. +5. To make sure all LEDs get enough power and have your staircase lighted evenly, power each + step from one side, using at least AWG14 or 2.5mm^2 cable. Don't connect them serial as you + do for the datacable! + +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 + lowest segment id. +2. Save your segments into a preset. +3. Ideally, add the preset in the config > LED setup menu to the "apply + preset **n** at boot" setting. + +## Changing behavior through API +The Staircase settings can be changed through the WLED JSON api. + +**NOTE:** We are using [curl](https://curl.se/) to send HTTP POSTs to the WLED API. +If you're using Windows and want to use the curl commands, replace the `\` with a `^` +or remove them and put everything on one line. + + +| Setting | Description | Default | +|------------------|---------------------------------------------------------------|---------| +| enabled | Enable or disable the usermod | true | +| segment-delay-ms | Delay (milliseconds) between switching on/off each step | 150 | +| on-time-s | Time (seconds) the stairs stay lit after last detection | 5 | +| bottom-echo-us | Detection range of ultrasonic sensor | 1749 | +| bottomsensor | Manually trigger a down to up animation via API | false | +| topsensor | Manually trigger an up to down animation via API | false | + + +To read the current settings, open a browser to `http://xxx.xxx.xxx.xxx/json/state` (use your WLED +device IP address). The device will respond with a json object containing all WLED settings. +The staircase settings and sensor states are inside the WLED status element: + +```json +{ + "state": { + "staircase": { + "enabled": true, + "segment-delay-ms": 150, + "on-time-s": 5, + "bottomsensor": false, + "topsensor": false + }, +} +``` + +### Enable/disable the usermod +By disabling the usermod you will be able to keep the LED's on, independent from the sensor +activity. This enables to play with the lights without the usermod switching them on or off. + +To disable the usermod: + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d {"staircase":{"enabled":false}} \ + xxx.xxx.xxx.xxx/json/state +``` + +To enable the usermod again, use `"enabled":true`. + +### Changing animation parameters +To change the delay between the steps to (for example) 100 milliseconds and the on-time to +10 seconds: + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase":{"segment-delay-ms":100,"on-time-s":10}}' \ + xxx.xxx.xxx.xxx/json/state +``` + +### Changing detection range of the ultrasonic HC-SR04 sensor +When an ultrasonic sensor is enabled in `Animated_Staircase_config.h`, you'll see a +`bottom-echo-us` setting appear in the json api: + +```json +{ + "state": { + "staircase": { + "enabled": true, + "segment-delay-ms": 150, + "on-time-s": 5, + "bottom-echo-us": 1749 + }, +} +``` + +If the HC-SR04 sensor detects an echo within 1749 microseconds (corresponding to ~30 cm +detection range from the sensor), it will trigger switching on the staircase. This setting +can be changed through the API with an HTTP POST: + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase":{"bottom-echo-us":1166}}' \ + xxx.xxx.xxx.xxx/json/state +``` + +Calculating the detection range can be performed as follows: The speed of sound is 343m/s at 20 +degrees Centigrade. Since the sound has to travel back and forth, the detection range for the +sensor in cm is (0.0343 * maxTimeUs) / 2. To get you started, please find delays and distances below: + +| Distance | Detection time | +|---------:|----------------:| +| 5 cm | 292 uS | +| 10 cm | 583 uS | +| 20 cm | 1166 uS | +| 30 cm | 1749 uS | +| 50 cm | 2915 uS | +| 100 cm | 5831 uS | + +**Please note:** that using an HC-SR04 sensor, particularly when detecting echos at longer +distances creates delays in the WLED software, and _might_ introduce timing hickups in your animations or +a less responsive web interface. It is therefore advised to keep the detection time as short as possible. + +### Animation triggering through the API +Instead of stairs activation by one of the sensors, you can also trigger the animation through +the API. To simulate triggering the bottom sensor, use: + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase":{"bottomsensor":true}}' \ + xxx.xxx.xxx.xxx/json/state +``` + +Likewise, to trigger the top sensor, use: + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"staircase":{"topsensor":true}}' \ + xxx.xxx.xxx.xxx/json/state +``` + +Have fun with this usermod.
+www.rolfje.com diff --git a/usermods/Artemis_reciever/readme.md b/usermods/Artemis_reciever/readme.md new file mode 100644 index 000000000..11b949085 --- /dev/null +++ b/usermods/Artemis_reciever/readme.md @@ -0,0 +1,5 @@ +Usermod to allow WLED to receive via UDP port from RGB.NET (and therefore add as a device to be controlled within artemis on PC) + +This is only a very simple code to support a single led strip, it does not support the full function of the RGB.NET sketch for esp8266 only what is needed to be used with Artemis. It will show as a ws281x device in artemis when you provide the correct hostname or ip. Artemis queries the number of LEDs via the web interface (/config) but communication to set the LEDs is all done via the UDP interface. + +To install, copy the usermod.cpp file to wled00 folder and recompile \ No newline at end of file diff --git a/usermods/Artemis_reciever/usermod.cpp b/usermods/Artemis_reciever/usermod.cpp new file mode 100644 index 000000000..227368525 --- /dev/null +++ b/usermods/Artemis_reciever/usermod.cpp @@ -0,0 +1,93 @@ +/* + * RGB.NET (artemis) receiver + * + * This works via the UDP, http is not supported apart from reporting LED count + * + * + */ +#include "wled.h" +#include + +WiFiUDP UDP; +const unsigned int RGBNET_localUdpPort = 1872; // local port to listen on +unsigned char RGBNET_packet[770]; +long lastTime = 0; +int delayMs = 10; +bool isRGBNETUDPEnabled; + +void RGBNET_readValues() { + + int RGBNET_packetSize = UDP.parsePacket(); + if (RGBNET_packetSize) { + // receive incoming UDP packets + int sequenceNumber = UDP.read(); + int channel = UDP.read(); + + //channel data is not used we only supports one channel + int len = UDP.read(RGBNET_packet, ledCount*3); + if(len==0){ + return; + } + + for (int i = 0; i < len; i=i+3) { + strip.setPixelColor(i/3, RGBNET_packet[i], RGBNET_packet[i+1], RGBNET_packet[i+2], 0); + } + //strip.show(); + } +} + +//update LED strip +void RGBNET_show() { + strip.show(); + lastTime = millis(); +} + +//This function provides a json with info on the number of LEDs connected +// it is needed by artemis to know how many LEDs to display on the surface +void handleConfig(AsyncWebServerRequest *request) +{ + String config = (String)"{\ + \"channels\": [\ + {\ + \"channel\": 1,\ + \"leds\": " + ledCount + "\ + },\ + {\ + \"channel\": 2,\ + \"leds\": " + "0" + "\ + },\ + {\ + \"channel\": 3,\ + \"leds\": " + "0" + "\ + },\ + {\ + \"channel\": 4,\ + \"leds\": " + "0" + "\ + }\ + ]\ +}"; + request->send(200, "application/json", config); +} + + +void userSetup() +{ + server.on("/config", HTTP_GET, [](AsyncWebServerRequest *request){ + handleConfig(request); + }); +} + +void userConnected() +{ + // new wifi, who dis? + UDP.begin(RGBNET_localUdpPort); + isRGBNETUDPEnabled = true; +} + +void userLoop() +{ + RGBNET_readValues(); + if (millis()-lastTime > delayMs) { + RGBNET_show(); + } +} \ No newline at end of file diff --git a/usermods/BME280_v2/README.md b/usermods/BME280_v2/README.md new file mode 100644 index 000000000..216ca6300 --- /dev/null +++ b/usermods/BME280_v2/README.md @@ -0,0 +1,40 @@ +Hello! I have written a v2 usermod for the BME280/BMP280 sensor based on the [existing v1 usermod](https://github.com/Aircoookie/WLED/blob/master/usermods/Wemos_D1_mini%2BWemos32_mini_shield/usermod_bme280.cpp). It is not just a refactor, there are many changes which I made to fit my use case, and I hope they will fit the use cases of others as well! Most notably, this usermod is *just* for the BME280 and does not control a display like in the v1 usermod designed for the WeMos shield. + +- Requires libraries `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280)) and `Wire`. Please add these under `lib_deps` in your `platform.ini` (or `platform_override.ini`). +- Data is published over MQTT so make sure you've enabled the MQTT sync interface. +- This usermod also writes to serial (GPIO1 on ESP8266). Please make sure nothing else listening on the serial TX pin of your board will get confused by log messages! + +To enable, compile with `USERMOD_BME280` defined (i.e. `platformio_override.ini`) +```ini +build_flags = + ${common.build_flags_esp8266} + -D USERMOD_BME280 +``` +or define `USERMOD_BME280` in `my_config.h` +```c++ +#define USERMOD_BME280 +``` + +Changes include: +- Adjustable measure intervals + - Temperature and pressure have separate intervals due to pressure not frequently changing at any constant altitude +- Adjustment of number of decimal places in published sensor values + - Separate adjustment for temperature, humidity and pressure values + - Values are rounded to the specified number of decimal places +- Pressure measured in units of hPa instead of Pa +- Calculation of heat index (apparent temperature) and dew point + - These, along with humidity measurements, are disabled if the sensor is a BMP280 +- 16x oversampling of sensor during measurement +- Values are only published if they are different from the previous value +- Values are published on startup (continually until the MQTT broker acknowledges a successful publication) + +Adjustments are made through preprocessor definitions at the start of the class definition. + +MQTT topics are as follows: +Measurement type | MQTT topic +--- | --- +Temperature | `/temperature` +Humidity | `/humidity` +Pressure | `/pressure` +Heat index | `/heat_index` +Dew point | `/dew_point` \ No newline at end of file diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h new file mode 100644 index 000000000..80a31a4f0 --- /dev/null +++ b/usermods/BME280_v2/usermod_bme280.h @@ -0,0 +1,212 @@ +#pragma once + +#include "wled.h" +#include +#include +#include // BME280 sensor +#include // BME280 extended measurements + +class UsermodBME280 : public Usermod +{ +private: +// User-defined configuration +#define Celsius // Show temperature mesaurement in Celcius. Comment out for Fahrenheit +#define TemperatureDecimals 1 // Number of decimal places in published temperaure values +#define HumidityDecimals 0 // Number of decimal places in published humidity values +#define PressureDecimals 2 // Number of decimal places in published pressure values +#define TemperatureInterval 5 // Interval to measure temperature (and humidity, dew point if available) in seconds +#define PressureInterval 300 // Interval to measure pressure in seconds + +// Sanity checks +#if !defined(TemperatureDecimals) || TemperatureDecimals < 0 + #define TemperatureDecimals 0 +#endif +#if !defined(HumidityDecimals) || HumidityDecimals < 0 + #define HumidityDecimals 0 +#endif +#if !defined(PressureDecimals) || PressureDecimals < 0 + #define PressureDecimals 0 +#endif +#if !defined(TemperatureInterval) || TemperatureInterval < 0 + #define TemperatureInterval 1 +#endif +#if !defined(PressureInterval) || PressureInterval < 0 + #define PressureInterval TemperatureInterval +#endif + +#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards + uint8_t SCL_PIN = 22; + uint8_t SDA_PIN = 21; +#else // ESP8266 boards + uint8_t SCL_PIN = 5; + uint8_t SDA_PIN = 4; + //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 +#endif + + // BME280 sensor settings + BME280I2C::Settings settings{ + BME280::OSR_X16, // Temperature oversampling x16 + BME280::OSR_X16, // Humidity oversampling x16 + BME280::OSR_X16, // Pressure oversampling x16 + // Defaults + BME280::Mode_Forced, + BME280::StandbyTime_1000ms, + BME280::Filter_Off, + BME280::SpiEnable_False, + BME280I2C::I2CAddr_0x76 // I2C address. I2C specific. Default 0x76 + }; + + BME280I2C bme{settings}; + + uint8_t SensorType; + + // Measurement timers + long timer; + long lastTemperatureMeasure = 0; + long lastPressureMeasure = 0; + + // Current sensor values + float SensorTemperature; + float SensorHumidity; + float SensorHeatIndex; + float SensorDewPoint; + float SensorPressure; + // Track previous sensor values + float lastTemperature; + float lastHumidity; + float lastHeatIndex; + float lastDewPoint; + float lastPressure; + + // Store packet IDs of MQTT publications + uint16_t mqttTemperaturePub = 0; + uint16_t mqttPressurePub = 0; + + void UpdateBME280Data(int SensorType) + { + float _temperature, _humidity, _pressure; + #ifdef Celsius + BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); + EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius); + #else + BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit); + EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Fahrenheit); + #endif + BME280::PresUnit presUnit(BME280::PresUnit_hPa); + + bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit); + + SensorTemperature = _temperature; + SensorHumidity = _humidity; + SensorPressure = _pressure; + if (SensorType == 1) + { + SensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit); + SensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit); + } + } + +public: + void setup() + { + Wire.begin(SDA_PIN, SCL_PIN); + + if (!bme.begin()) + { + SensorType = 0; + Serial.println("Could not find BME280I2C sensor!"); + } + else + { + switch (bme.chipModel()) + { + case BME280::ChipModel_BME280: + SensorType = 1; + Serial.println("Found BME280 sensor! Success."); + break; + case BME280::ChipModel_BMP280: + SensorType = 2; + Serial.println("Found BMP280 sensor! No Humidity available."); + break; + default: + SensorType = 0; + Serial.println("Found UNKNOWN sensor! Error!"); + } + } + } + + void loop() + { + // BME280 sensor MQTT publishing + // Check if sensor present and MQTT Connected, otherwise it will crash the MCU + if (SensorType != 0 && mqtt != nullptr) + { + // Timer to fetch new temperature, humidity and pressure data at intervals + timer = millis(); + + if (timer - lastTemperatureMeasure >= TemperatureInterval * 1000 || mqttTemperaturePub == 0) + { + lastTemperatureMeasure = timer; + + UpdateBME280Data(SensorType); + + float Temperature = roundf(SensorTemperature * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); + float Humidity, HeatIndex, DewPoint; + + // If temperature has changed since last measure, create string populated with device topic + // from the UI and values read from sensor, then publish to broker + if (Temperature != lastTemperature) + { + String topic = String(mqttDeviceTopic) + "/temperature"; + mqttTemperaturePub = mqtt->publish(topic.c_str(), 0, false, String(Temperature, TemperatureDecimals).c_str()); + } + + lastTemperature = Temperature; // Update last sensor temperature for next loop + + if (SensorType == 1) // Only if sensor is a BME280 + { + Humidity = roundf(SensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals); + HeatIndex = roundf(SensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); + DewPoint = roundf(SensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals); + + if (Humidity != lastHumidity) + { + String topic = String(mqttDeviceTopic) + "/humidity"; + mqtt->publish(topic.c_str(), 0, false, String(Humidity, HumidityDecimals).c_str()); + } + + if (HeatIndex != lastHeatIndex) + { + String topic = String(mqttDeviceTopic) + "/heat_index"; + mqtt->publish(topic.c_str(), 0, false, String(HeatIndex, TemperatureDecimals).c_str()); + } + + if (DewPoint != lastDewPoint) + { + String topic = String(mqttDeviceTopic) + "/dew_point"; + mqtt->publish(topic.c_str(), 0, false, String(DewPoint, TemperatureDecimals).c_str()); + } + + lastHumidity = Humidity; + lastHeatIndex = HeatIndex; + lastDewPoint = DewPoint; + } + } + + if (timer - lastPressureMeasure >= PressureInterval * 1000 || mqttPressurePub == 0) + { + lastPressureMeasure = timer; + + float Pressure = roundf(SensorPressure * pow(10, PressureDecimals)) / pow(10, PressureDecimals); + + if (Pressure != lastPressure) + { + String topic = String(mqttDeviceTopic) + "/pressure"; + mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(Pressure, PressureDecimals).c_str()); + } + + lastPressure = Pressure; + } + } + } +}; \ No newline at end of file diff --git a/usermods/DHT/platformio_override.ini b/usermods/DHT/platformio_override.ini new file mode 100644 index 000000000..1771fd17a --- /dev/null +++ b/usermods/DHT/platformio_override.ini @@ -0,0 +1,22 @@ +; Options +; ------- +; USERMOD_DHT - define this to have this user mod included wled00\usermods_list.cpp +; USERMOD_DHT_DHTTYPE - DHT model: 11, 21, 22 for DHT11, DHT21, or DHT22, defaults to 22/DHT22 +; USERMOD_DHT_PIN - pin to which DTH is connected, defaults to Q2 pin on QuinLed Dig-Uno's board +; USERMOD_DHT_CELSIUS - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported +; USERMOD_DHT_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds +; USERMOD_DHT_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 90 seconds +; USERMOD_DHT_STATS - For debug, report delay stats + +[env:d1_mini_usermod_dht_C] +extends = env:d1_mini +build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS +lib_deps = ${env.lib_deps} + https://github.com/alwynallan/DHT_nonblocking + +[env:custom32_LEDPIN_16_usermod_dht_C] +extends = env:custom32_LEDPIN_16 +build_flags = ${env:custom32_LEDPIN_16.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS -D USERMOD_DHT_STATS +lib_deps = ${env.lib_deps} + https://github.com/alwynallan/DHT_nonblocking + diff --git a/usermods/DHT/readme.md b/usermods/DHT/readme.md new file mode 100644 index 000000000..5a123d4bd --- /dev/null +++ b/usermods/DHT/readme.md @@ -0,0 +1,41 @@ +# DHT Temperature/Humidity sensor usermod + +This usermod will read from an attached DHT22 or DHT11 humidity and temperature sensor. +The sensor readings are displayed in the Info section of the web UI. + +If sensor is not detected after a while (10 update intervals), this usermod will be disabled. + +## Installation + +Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`. + +### Define Your Options + +* `USERMOD_DHT` - define this to have this user mod included wled00\usermods_list.cpp +* `USERMOD_DHT_DHTTYPE` - DHT model: 11, 21, 22 for DHT11, DHT21, or DHT22, defaults to 22/DHT22 +* `USERMOD_DHT_PIN` - pin to which DTH is connected, defaults to Q2 pin on QuinLed Dig-Uno's board +* `USERMOD_DHT_CELSIUS` - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported +* `USERMOD_DHT_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60 seconds +* `USERMOD_DHT_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 90 seconds +* `USERMOD_DHT_STATS` - For debug, report delay stats + +## Project link + +* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link + +### PlatformIO requirements + +If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:d1_mini_usermod_dht_C`. If not, you can add the libraries and dependencies into `platformio.ini` as you see fit. + + +## Change Log + +2020-02-04 +* Change default QuinLed pin to Q2 +* Instead of trying to keep updates at constant cadence, space readings out by measurement interval; hope this helps to avoid occasional bursts of readings with errors +* Add some more (optional) stats +2020-02-03 +* Due to poor readouts on ESP32 with previous DHT library, rewrote to use https://github.com/alwynallan/DHT_nonblocking +* The new library serializes/delays up to 5ms for the sensor readout +2020-02-02 +* Created diff --git a/usermods/DHT/usermod_dht.h b/usermods/DHT/usermod_dht.h new file mode 100644 index 000000000..9f46734f8 --- /dev/null +++ b/usermods/DHT/usermod_dht.h @@ -0,0 +1,216 @@ +#pragma once + +#include "wled.h" + +#include + +// USERMOD_DHT_DHTTYPE: +// 11 // DHT 11 +// 21 // DHT 21 +// 22 // DHT 22 (AM2302), AM2321 *** default +#ifndef USERMOD_DHT_DHTTYPE +#define USERMOD_DHT_DHTTYPE 22 +#endif + +#if USERMOD_DHT_DHTTYPE == 11 +#define DHTTYPE DHT_TYPE_11 +#elif USERMOD_DHT_DHTTYPE == 21 +#define DHTTYPE DHT_TYPE_21 +#elif USERMOD_DHT_DHTTYPE == 22 +#define DHTTYPE DHT_TYPE_22 +#endif + +// Connect pin 1 (on the left) of the sensor to +5V +// NOTE: If using a board with 3.3V logic like an Arduino Due connect pin 1 +// to 3.3V instead of 5V! +// Connect pin 2 of the sensor to whatever your DHTPIN is +// NOTE: Pin defaults below are for QuinLed Dig-Uno's Q2 on the board +// Connect pin 4 (on the right) of the sensor to GROUND +// NOTE: If using a bare sensor (AM*), Connect a 10K resistor from pin 2 +// (data) to pin 1 (power) of the sensor. DHT* boards have the pullup already + +#ifdef USERMOD_DHT_PIN +#define DHTPIN USERMOD_DHT_PIN +#else +#ifdef ARDUINO_ARCH_ESP32 +#define DHTPIN 21 +#else //ESP8266 boards +#define DHTPIN 4 +#endif +#endif + +// the frequency to check sensor, 1 minute +#ifndef USERMOD_DHT_MEASUREMENT_INTERVAL +#define USERMOD_DHT_MEASUREMENT_INTERVAL 60000 +#endif + +// how many seconds after boot to take first measurement, 90 seconds +// 90 gives enough time to OTA update firmware if this crashses +#ifndef USERMOD_DHT_FIRST_MEASUREMENT_AT +#define USERMOD_DHT_FIRST_MEASUREMENT_AT 90000 +#endif + +// from COOLDOWN_TIME in dht_nonblocking.cpp +#define DHT_TIMEOUT_TIME 10000 + +DHT_nonblocking dht_sensor(DHTPIN, DHTTYPE); + +class UsermodDHT : public Usermod { + private: + unsigned long nextReadTime = 0; + unsigned long lastReadTime = 0; + float humidity, temperature = 0; + bool initializing = true; + bool disabled = false; + #ifdef USERMOD_DHT_STATS + unsigned long nextResetStatsTime = 0; + uint16_t updates = 0; + uint16_t clean_updates = 0; + uint16_t errors = 0; + unsigned long maxDelay = 0; + unsigned long currentIteration = 0; + unsigned long maxIteration = 0; + #endif + + public: + void setup() { + nextReadTime = millis() + USERMOD_DHT_FIRST_MEASUREMENT_AT; + lastReadTime = millis(); + #ifdef USERMOD_DHT_STATS + nextResetStatsTime = millis() + 60*60*1000; + #endif + } + + void loop() { + if (disabled) { + return; + } + if (millis() < nextReadTime) { + return; + } + + #ifdef USERMOD_DHT_STATS + if (millis() >= nextResetStatsTime) { + nextResetStatsTime += 60*60*1000; + errors = 0; + updates = 0; + clean_updates = 0; + } + unsigned long dcalc = millis(); + if (currentIteration == 0) { + currentIteration = millis(); + } + #endif + + float tempC; + if (dht_sensor.measure(&tempC, &humidity)) { + #ifdef USERMOD_DHT_CELSIUS + temperature = tempC; + #else + temperature = tempC * 9 / 5 + 32; + #endif + + nextReadTime = millis() + USERMOD_DHT_MEASUREMENT_INTERVAL; + lastReadTime = millis(); + initializing = false; + + #ifdef USERMOD_DHT_STATS + unsigned long icalc = millis() - currentIteration; + if (icalc > maxIteration) { + maxIteration = icalc; + } + if (icalc > DHT_TIMEOUT_TIME) { + errors += icalc/DHT_TIMEOUT_TIME; + } else { + clean_updates += 1; + } + updates += 1; + currentIteration = 0; + + #endif + } + + #ifdef USERMOD_DHT_STATS + dcalc = millis() - dcalc; + if (dcalc > maxDelay) { + maxDelay = dcalc; + } + #endif + + if (((millis() - lastReadTime) > 10*USERMOD_DHT_MEASUREMENT_INTERVAL)) { + disabled = true; + } + } + + void addToJsonInfo(JsonObject& root) { + if (disabled) { + return; + } + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray temp = user.createNestedArray("Temperature"); + JsonArray hum = user.createNestedArray("Humidity"); + + #ifdef USERMOD_DHT_STATS + JsonArray next = user.createNestedArray("next"); + if (nextReadTime >= millis()) { + next.add((nextReadTime - millis()) / 1000); + next.add(" sec until read"); + } else { + next.add((millis() - nextReadTime) / 1000); + next.add(" sec active reading"); + } + + JsonArray last = user.createNestedArray("last"); + last.add((millis() - lastReadTime) / 60000); + last.add(" min since read"); + + JsonArray err = user.createNestedArray("errors"); + err.add(errors); + err.add(" Errors"); + + JsonArray upd = user.createNestedArray("updates"); + upd.add(updates); + upd.add(" Updates"); + + JsonArray cupd = user.createNestedArray("cleanUpdates"); + cupd.add(clean_updates); + cupd.add(" Updates"); + + JsonArray iter = user.createNestedArray("maxIter"); + iter.add(maxIteration); + iter.add(" ms"); + + JsonArray delay = user.createNestedArray("maxDelay"); + delay.add(maxDelay); + delay.add(" ms"); + #endif + + if (initializing) { + // if we haven't read the sensor yet, let the user know + // that we are still waiting for the first measurement + temp.add((nextReadTime - millis()) / 1000); + temp.add(" sec until read"); + hum.add((nextReadTime - millis()) / 1000); + hum.add(" sec until read"); + return; + } + + hum.add(humidity); + hum.add("%"); + + temp.add(temperature); + #ifdef USERMOD_DHT_CELSIUS + temp.add("°C"); + #else + temp.add("°F"); + #endif + } + + uint16_t getId() + { + return USERMOD_ID_DHT; + } + +}; diff --git a/usermods/ESP32_TouchBrightnessControl/readme.md b/usermods/ESP32_TouchBrightnessControl/readme.md new file mode 100644 index 000000000..f210b3320 --- /dev/null +++ b/usermods/ESP32_TouchBrightnessControl/readme.md @@ -0,0 +1,19 @@ +# ESP32 Touch Brightness Control + +Toggle On/Off with a long press (800ms) +Switch through 5 brightness levels (defined in usermod_touchbrightness.h, values 0-255) with a short (100ms) touch + +## Installation + +Copy 'usermod_touchbrightness.h' to the wled00 directory. +in 'usermod_list.cpp' add this: + +> #include "usermod_touchbrightness.h" +above "void registerUsermods()" + +and + +> usermods.add(new TouchBrightnessControl()); +inside the "registerUsermods()" function + + diff --git a/usermods/ESP32_TouchBrightnessControl/usermod_touchbrightness.h b/usermods/ESP32_TouchBrightnessControl/usermod_touchbrightness.h new file mode 100644 index 000000000..1b7795925 --- /dev/null +++ b/usermods/ESP32_TouchBrightnessControl/usermod_touchbrightness.h @@ -0,0 +1,89 @@ +// +// usermod_touchbrightness.h +// github.com/aircoookie/WLED +// +// Created by Justin Kühner on 14.09.2020. +// Copyright © 2020 NeariX. All rights reserved. +// https://github.com/NeariX67/ +// Discord: @NeariX#4799 + + +#pragma once + +#include "wled.h" + +#define threshold 40 //Increase value if touches falsely accur. Decrease value if actual touches are not recognized +#define touchPin T0 //T0 = D4 / GPIO4 + +//Define the 5 brightness levels +//Long press to turn off / on +#define brightness1 51 +#define brightness2 102 +#define brightness3 153 +#define brightness4 204 +#define brightness5 255 + + +#ifdef ESP32 + + +class TouchBrightnessControl : public Usermod { + private: + unsigned long lastTime = 0; //Interval + unsigned long lastTouch = 0; //Timestamp of last Touch + unsigned long lastRelease = 0; //Timestamp of last Touch release + boolean released = true; //current Touch state (touched/released) + uint16_t touchReading = 0; //sensor reading, maybe use uint8_t??? + uint16_t touchDuration = 0; //duration of last touch + public: + + void setup() { + lastTouch = millis(); + lastRelease = millis(); + lastTime = millis(); + } + + void loop() { + if (millis() - lastTime >= 50) { //Check every 50ms if a touch occurs + lastTime = millis(); + touchReading = touchRead(touchPin); //Read touch sensor on pin T0 (GPIO4 / D4) + + if(touchReading < threshold && released) { //Touch started + released = false; + lastTouch = millis(); + } + else if(touchReading >= threshold && !released) { //Touch released + released = true; + lastRelease = millis(); + touchDuration = lastRelease - lastTouch; //Calculate duration + } + + //Serial.println(touchDuration); + + if(touchDuration >= 800 && released) { //Toggle power if button press is longer than 800ms + touchDuration = 0; //Reset touch duration to avoid multiple actions on same touch + toggleOnOff(); + colorUpdated(2); //Refresh values + } + else if(touchDuration >= 100 && released) { //Switch to next brightness if touch is between 100 and 800ms + touchDuration = 0; //Reset touch duration to avoid multiple actions on same touch + if(bri < brightness1) { + bri = brightness1; + } else if(bri >= brightness1 && bri < brightness2) { + bri = brightness2; + } else if(bri >= brightness2 && bri < brightness3) { + bri = brightness3; + } else if(bri >= brightness3 && bri < brightness4) { + bri = brightness4; + } else if(bri >= brightness4 && bri < brightness5) { + bri = brightness5; + } else if(bri >= brightness5) { + bri = brightness1; + } + colorUpdated(2); //Refresh values + } + + } + } +}; +#endif diff --git a/usermods/EXAMPLE_v2/readme.md b/usermods/EXAMPLE_v2/readme.md new file mode 100644 index 000000000..8917a1fba --- /dev/null +++ b/usermods/EXAMPLE_v2/readme.md @@ -0,0 +1,10 @@ +# Usermods API v2 example usermod + +In this usermod file you can find the documentation on how to take advantage of the new version 2 usermods! + +## Installation + +Copy `usermod_v2_example.h` to the wled00 directory. +Uncomment the corresponding lines in `usermods_list.cpp` and compile! +_(You shouldn't need to actually install this, it does nothing useful)_ + diff --git a/usermods/EXAMPLE_v2/usermod_v2_example.h b/usermods/EXAMPLE_v2/usermod_v2_example.h new file mode 100644 index 000000000..ef68e907c --- /dev/null +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -0,0 +1,155 @@ +#pragma once + +#include "wled.h" + +/* + * Usermods allow you to add own functionality to WLED more easily + * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality + * + * This is an example for a v2 usermod. + * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. + * Multiple v2 usermods can be added to one compilation easily. + * + * Creating a usermod: + * This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template. + * Please remember to rename the class and file to a descriptive name. + * You may also use multiple .h and .cpp files. + * + * Using a 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 + */ + +//class name. Use something descriptive and leave the ": public Usermod" part :) +class MyExampleUsermod : public Usermod { + private: + //Private class members. You can declare variables and functions only accessible to your usermod here + unsigned long lastTime = 0; + public: + //Functions called by WLED + + /* + * 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() { + //Serial.println("Hello from my usermod!"); + } + + + /* + * 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 (millis() - lastTime > 1000) { + //Serial.println("I'm alive!"); + lastTime = millis(); + } + } + + + /* + * 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() 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("exampleUsermod"); + top["great"] = userVar0; //save this var persistently whenever settings are 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 :) + */ + void readFromConfig(JsonObject& root) + { + 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) + } + + + /* + * 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_EXAMPLE; + } + + //More methods can be added in the future, this example will then be extended. + //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! +}; \ No newline at end of file diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/assets/controller.jpg b/usermods/Enclosure_with_OLED_temp_ESP07/assets/controller.jpg index d518ca3e3..755bfefb3 100644 Binary files a/usermods/Enclosure_with_OLED_temp_ESP07/assets/controller.jpg and b/usermods/Enclosure_with_OLED_temp_ESP07/assets/controller.jpg differ diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/assets/pcb.png b/usermods/Enclosure_with_OLED_temp_ESP07/assets/pcb.png index cf146918a..a4b81ad05 100644 Binary files a/usermods/Enclosure_with_OLED_temp_ESP07/assets/pcb.png and b/usermods/Enclosure_with_OLED_temp_ESP07/assets/pcb.png differ diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 817fb7e4a..94d1c1f2b 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -1,30 +1,40 @@ # Almost universal controller board for outdoor applications This usermod is using ideas from @mrVanboy and @400killer + +Installation of file: Copy and replace file in wled00 directory. + +For BME280 sensor use usermod_bme280.cpp. Copy to wled00 and rename to usermod.cpp + ## Project repository - [Original repository](https://github.com/srg74/Controller-for-WLED-firmware) - Main controller repository ## Features -* SSD1306 128x32 and 128x64 I2C OLED display -* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) -* Auto display shutoff for saving display lifetime -* Dallas temperature sensor -* Reporting temperature to MQTT broker +- SSD1306 128x32 and 128x64 I2C OLED display +- On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) +- Auto display shutoff for saving display lifetime +- Dallas temperature sensor +- Reporting temperature to MQTT broker ## Hardware ![Hardware connection](assets/controller.jpg) ## Functionality checked with -* ESP-07S -* PlatformIO -* SSD1306 128x32 I2C OLED display -* DS18B20 (temperature sensor) -* KY-022 (infrared receiver) -* Push button (N.O. momentary switch) +- ESP-07S +- PlatformIO +- SSD1306 128x32 I2C OLED display +- DS18B20 (temperature sensor) +- BME280 (temperature, humidity and pressure sensor) +- KY-022 (infrared receiver) +- Push button (N.O. momentary switch) -### Platformio requirements -Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: +For Dallas sensor uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: ```ini # platformio.ini ... +[platformio] +... +default_envs = esp07 +; default_envs = d1_mini +... [common] ... lib_deps_external = @@ -36,3 +46,23 @@ lib_deps_external = OneWire@~2.3.5 ... ``` + +For BME280 sensor uncomment `U8g2@~2.27.3`,`BME280@~3.0.0 under` `[common]` section in `platformio.ini`: +```ini +# platformio.ini +... +[platformio] +... +default_envs = esp07 +; default_envs = d1_mini +... +[common] +... +lib_deps_external = + ... + #For use SSD1306 OLED display uncomment following + U8g2@~2.27.3 + #For BME280 sensor uncomment following + BME280@~3.0.0 +... +``` diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino b/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp similarity index 98% rename from usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino rename to usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp index 2c55a9526..9724a1b7b 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino +++ b/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp @@ -1,3 +1,5 @@ +#include "wled.h" +#include #include // from https://github.com/olikraus/u8g2/ #include //Dallastemperature sensor //The SCL and SDA pins are defined here. @@ -25,6 +27,7 @@ U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_ void userSetup() { sensor.begin(); //Start Dallas temperature sensor u8x8.begin(); + //u8x8.setFlipMode(1); //Uncoment 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); diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp b/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp new file mode 100644 index 000000000..c39c74d2c --- /dev/null +++ b/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp @@ -0,0 +1,266 @@ +#include "wled.h" +#include +#include // from https://github.com/olikraus/u8g2/ +#include +#include //BME280 sensor + +void UpdateBME280Data(); + +#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit +BME280I2C bme; // Default : forced mode, standby time = 1000 ms + // Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off, + +#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards +uint8_t SCL_PIN = 22; +uint8_t SDA_PIN = 21; +#else //ESP8266 boards +uint8_t SCL_PIN = 5; +uint8_t SDA_PIN = 4; +// uint8_t RST_PIN = 16; // Uncoment 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 + +// 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" +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" +//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" +//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 + +// BME280 sensor timer +long tempTimer = millis(); +long lastMeasure = 0; + +float SensorPressure(NAN); +float SensorTemperature(NAN); +float SensorHumidity(NAN); + +void userSetup() { + u8x8.begin(); + u8x8.setPowerSave(0); + u8x8.setFlipMode(1); + u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 + u8x8.setFont(u8x8_font_chroma48medium8_r); + u8x8.drawString(0, 0, "Loading..."); + Wire.begin(SDA_PIN,SCL_PIN); + +while(!bme.begin()) + { + Serial.println("Could not find BME280I2C sensor!"); + delay(1000); + } +switch(bme.chipModel()) + { + case BME280::ChipModel_BME280: + Serial.println("Found BME280 sensor! Success."); + break; + case BME280::ChipModel_BMP280: + Serial.println("Found BMP280 sensor! No Humidity available."); + break; + default: + Serial.println("Found UNKNOWN sensor! Error!"); + } +} + +// gets called every time WiFi is (re-)connected. Initialize own network +// interfaces here +void userConnected() {} + +// needRedraw marks if redraw is required to prevent often redrawing. +bool needRedraw = true; + +// Next variables hold the previous known values to determine if redraw is +// required. +String knownSsid = ""; +IPAddress knownIp; +uint8_t knownBrightness = 0; +uint8_t knownMode = 0; +uint8_t knownPalette = 0; + +long lastUpdate = 0; +long lastRedraw = 0; +bool displayTurnedOff = false; +// How often we are redrawing screen +#define USER_LOOP_REFRESH_RATE_MS 5000 + +void userLoop() { + +// BME280 sensor MQTT publishing + tempTimer = millis(); +// Timer to publish new sensor data every 60 seconds + if (tempTimer - lastMeasure > 60000) + { + lastMeasure = tempTimer; + +// Check if MQTT Connected, otherwise it will crash the 8266 + if (mqtt != nullptr) + { + UpdateBME280Data(); + float board_temperature = SensorTemperature; + float board_pressure = SensorPressure; + float board_humidity = SensorHumidity; + +// Create string populated with user defined device topic from the UI, and the read temperature, humidity and pressure. Then publish to MQTT server. + String t = String(mqttDeviceTopic); + t += "/temperature"; + mqtt->publish(t.c_str(), 0, true, String(board_temperature).c_str()); + String p = String(mqttDeviceTopic); + p += "/pressure"; + mqtt->publish(p.c_str(), 0, true, String(board_pressure).c_str()); + String h = String(mqttDeviceTopic); + h += "/humidity"; + mqtt->publish(h.c_str(), 0, true, String(board_humidity).c_str()); + } + } + + // Check if we time interval for redrawing passes. + if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + return; + } + lastUpdate = millis(); + + // Turn off display after 3 minutes with no change. + if(!displayTurnedOff && millis() - lastRedraw > 3*60*1000) { + u8x8.setPowerSave(1); + displayTurnedOff = true; + } + + // Check if values which are shown on display changed from the last time. + if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { + needRedraw = true; + } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { + needRedraw = true; + } else if (knownBrightness != bri) { + needRedraw = true; + } else if (knownMode != strip.getMode()) { + needRedraw = true; + } else if (knownPalette != strip.getSegment(0).palette) { + needRedraw = true; + } + + if (!needRedraw) { + return; + } + needRedraw = false; + + if (displayTurnedOff) + { + u8x8.setPowerSave(0); + displayTurnedOff = false; + } + lastRedraw = millis(); + + // Update last known values. + #if defined(ESP8266) + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + #else + knownSsid = WiFi.SSID(); + #endif + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + knownBrightness = bri; + knownMode = strip.getMode(); + knownPalette = strip.getSegment(0).palette; + u8x8.clear(); + u8x8.setFont(u8x8_font_chroma48medium8_r); + + // 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 + if (knownSsid.length() > u8x8.getCols()) + u8x8.print("~"); + + // Second row with IP or Psssword + u8x8.setCursor(1, 1); + // Print password in AP mode and if led is OFF. + if (apActive && bri == 0) + u8x8.print(apPass); + else + u8x8.print(knownIp); + + // Third row with mode name + u8x8.setCursor(2, 2); + uint8_t qComma = 0; + bool insideQuotes = false; + uint8_t printedChars = 0; + char singleJsonSymbol; + + // Find the mode name in JSON + for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownMode)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + // Fourth row with palette name + u8x8.setCursor(2, 3); + qComma = 0; + insideQuotes = false; + printedChars = 0; + // Looking for palette name in JSON. + for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownPalette)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + + u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); + u8x8.drawGlyph(0, 0, 80); // wifi icon + u8x8.drawGlyph(0, 1, 68); // home icon + u8x8.setFont(u8x8_font_open_iconic_weather_2x2); + u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon +} + +void UpdateBME280Data() { + float temp(NAN), hum(NAN), pres(NAN); +#ifdef Celsius + BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); +#else + BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit); +#endif + BME280::PresUnit presUnit(BME280::PresUnit_Pa); + bme.read(pres, temp, hum, tempUnit, presUnit); + SensorTemperature=temp; + SensorHumidity=hum; + SensorPressure=pres; +} diff --git a/usermods/Fix_unreachable_netservices_v2/readme.md b/usermods/Fix_unreachable_netservices_v2/readme.md new file mode 100644 index 000000000..f7b721dd8 --- /dev/null +++ b/usermods/Fix_unreachable_netservices_v2/readme.md @@ -0,0 +1,69 @@ +# Fix unreachable net services V2 + +**Attention: This usermod compiles only for ESP8266** + +This usermod-v2 modification performs a ping request to the local IP address every 60 seconds. By this procedure the net services of WLED remains accessible in some problematic WLAN environments. + +The modification works with static or DHCP IP address configuration. + +_Story:_ + +Unfortunately, with all ESP projects where a web server or other network services are running, I have the problem that after some time the web server is no longer accessible. Now I found out that the connection is at least reestablished when a ping request is executed by the device. + +With this modification, in the worst case, the network functions are not available for 60 seconds until the next ping request. + +## Webinterface + +The number of pings and reconnects is displayed on the info page in the web interface. +The ping delay can be changed. Changes persist after a reboot. + +## JSON API + +The usermod supports the following state changes: + +| JSON key | Value range | Description | +|-------------|------------------|---------------------------------| +| PingDelayMs | 5000 to 18000000 | Deactivdate/activate the sensor | + + Changes also persist after a reboot. + +## Installation + +1. Copy the file `usermod_Fix_unreachable_netservices.h` to the `wled00` directory. +2. Register the usermod by adding `#include "usermod_Fix_unreachable_netservices.h"` in the top and `registerUsermod(new FixUnreachableNetServices());` in the bottom of `usermods_list.cpp`. + +Example **usermods_list.cpp**: + +```cpp +#include "wled.h" +/* + * Register your v2 usermods here! + * (for v1 usermods using just usermod.cpp, you can ignore this file) + */ + +/* + * Add/uncomment your usermod filename here (and once more below) + * || || || + * \/ \/ \/ + */ +//#include "usermod_v2_example.h" +//#include "usermod_temperature.h" +//#include "usermod_v2_empty.h" +#include "usermod_Fix_unreachable_netservices.h" + +void registerUsermods() +{ + /* + * Add your usermod class name here + * || || || + * \/ \/ \/ + */ + //usermods.add(new MyExampleUsermod()); + //usermods.add(new UsermodTemperature()); + //usermods.add(new UsermodRenameMe()); + usermods.add(new FixUnreachableNetServices()); + +} +``` + +Hopefully I can help someone with that - @gegu diff --git a/usermods/Fix_unreachable_netservices_v2/usermod_Fix_unreachable_netservices.h b/usermods/Fix_unreachable_netservices_v2/usermod_Fix_unreachable_netservices.h new file mode 100644 index 000000000..cb2f1b0c3 --- /dev/null +++ b/usermods/Fix_unreachable_netservices_v2/usermod_Fix_unreachable_netservices.h @@ -0,0 +1,168 @@ +#pragma once + +#include "wled.h" +#if defined(ESP32) +#warning "Usermod FixUnreachableNetServices works only with ESP8266 builds" +class FixUnreachableNetServices : public Usermod +{ +}; +#endif + +#if defined(ESP8266) +#include + +/* + * This usermod performs a ping request to the local IP address every 60 seconds. + * By this procedure the net services of WLED remains accessible in some problematic WLAN environments. + * + * Usermods allow you to add own functionality to WLED more easily + * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality + * + * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. + * Multiple v2 usermods can be added to one compilation easily. + * + * Creating a usermod: + * This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template. + * Please remember to rename the class and file to a descriptive name. + * You may also use multiple .h and .cpp files. + * + * Using a 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 + */ + +class FixUnreachableNetServices : public Usermod +{ +private: + //Private class members. You can declare variables and functions only accessible to your usermod here + unsigned long m_lastTime = 0; + + // declare required variables + unsigned long m_pingDelayMs = 60000; + unsigned long m_connectedWiFi = 0; + ping_option m_pingOpt; + unsigned int m_pingCount = 0; + bool m_updateConfig = false; + +public: + //Functions called by WLED + + /** + * 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() + { + //Serial.println("Hello from my usermod!"); + } + + /** + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() + { + //Serial.println("Connected to WiFi!"); + + ++m_connectedWiFi; + + // initialize ping_options structure + memset(&m_pingOpt, 0, sizeof(struct ping_option)); + m_pingOpt.count = 1; + m_pingOpt.ip = WiFi.localIP(); + } + + /** + * loop + */ + void loop() + { + if (m_connectedWiFi > 0 && millis() - m_lastTime > m_pingDelayMs) + { + ping_start(&m_pingOpt); + m_lastTime = millis(); + ++m_pingCount; + } + if (m_updateConfig) + { + serializeConfig(); + m_updateConfig = false; + } + } + + /** + * 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) + { + //this code adds "u":{"⚡ Ping fix pings": m_pingCount} to the info object + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + + String uiDomString = "⚡ Ping fix pings\ +Delay sec"; + + JsonArray infoArr = user.createNestedArray(uiDomString); //name + infoArr.add(m_pingCount); //value + + //this code adds "u":{"⚡ Reconnects": m_connectedWiFi - 1} to the info object + infoArr = user.createNestedArray("⚡ Reconnects"); //name + infoArr.add(m_connectedWiFi - 1); //value + } + + /** + * 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["PingDelay"] = (m_pingDelayMs/1000); + } + + /** + * 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["PingDelay"] != nullptr) + { + m_pingDelayMs = (1000 * max(1UL, min(300UL, root["PingDelay"].as()))); + m_updateConfig = true; + } + } + + /** + * provide the changeable values + */ + void addToConfig(JsonObject &root) + { + JsonObject top = root.createNestedObject("FixUnreachableNetServices"); + top["PingDelayMs"] = m_pingDelayMs; + } + + /** + * restore the changeable values + */ + void readFromConfig(JsonObject &root) + { + JsonObject top = root["FixUnreachableNetServices"]; + m_pingDelayMs = top["PingDelayMs"] | m_pingDelayMs; + m_pingDelayMs = max(5000UL, min(18000000UL, m_pingDelayMs)); + } + + /** + * 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_FIXNETSERVICES; + } +}; +#endif diff --git a/usermods/PIR_sensor_mqtt_v1/README.md b/usermods/PIR_sensor_mqtt_v1/README.md new file mode 100644 index 000000000..475c11b02 --- /dev/null +++ b/usermods/PIR_sensor_mqtt_v1/README.md @@ -0,0 +1,9 @@ +# PIR sensor with MQTT + +This simple usermod allows attaching a PIR sensor like the AM312 and publish the readings over MQTT. A message is sent when motion is detected as well as when motion has stopped. + +This usermod has only been tested with the AM312 sensor though should work for any other PIR sensor. Note that this does not control the LED strip directly, it only publishes MQTT readings for use with other integrations like Home Assistant. + +## Installation + +Copy and replace the file `usermod.cpp` in wled00 directory. diff --git a/usermods/PIR_sensor_mqtt_v1/usermod.cpp b/usermods/PIR_sensor_mqtt_v1/usermod.cpp new file mode 100644 index 000000000..426a80332 --- /dev/null +++ b/usermods/PIR_sensor_mqtt_v1/usermod.cpp @@ -0,0 +1,55 @@ +#include "wled.h" +/* + * This v1 usermod 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) + * If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE) + * + * Consider the v2 usermod API if you need a more advanced feature set! + */ + +//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) + +// PIR sensor pin +const int MOTION_PIN = 16; + // MQTT topic for sensor values +const char MQTT_TOPIC[] = "/motion"; + +int prevState = LOW; + +//gets called once at boot. Do all initialization that doesn't depend on network here +void userSetup() +{ + pinMode(MOTION_PIN, INPUT); +} + +//gets called every time WiFi is (re-)connected. Initialize own network interfaces here +void userConnected() +{ + +} + +void publishMqtt(String state) +{ + //Check if MQTT Connected, otherwise it will crash the 8266 + if (mqtt != nullptr){ + char subuf[38]; + strcpy(subuf, mqttDeviceTopic); + strcat(subuf, MQTT_TOPIC); + mqtt->publish(subuf, 0, true, state.c_str()); + } +} + +//loop. You can use "if (WLED_CONNECTED)" to check for successful connection +void userLoop() +{ + if (digitalRead(MOTION_PIN) == HIGH && prevState == LOW) { // Motion detected + publishMqtt("ON"); + prevState = HIGH; + } + if (digitalRead(MOTION_PIN) == LOW && prevState == HIGH) { // Motion stopped + publishMqtt("OFF"); + prevState = LOW; + } +} + diff --git a/usermods/PIR_sensor_switch/PIR_Highlight_Standby b/usermods/PIR_sensor_switch/PIR_Highlight_Standby new file mode 100644 index 000000000..ddbbddeae --- /dev/null +++ b/usermods/PIR_sensor_switch/PIR_Highlight_Standby @@ -0,0 +1,347 @@ +#pragma once + +#include "wled.h" + +/* + * -------------------- + * Rawframe edit: + * - TESTED ON WLED VS.0.10.1 - WHERE ONLY PRESET 16 SAVES SEGMENTS - some macros may not be needed if this changes. + * - Code has been modified as my usage changed, as such it has poor use of functions vs if thens, but feel free to change it for me :) + * + * Edited to SWITCH between two lighting scenes/modes : STANDBY and HIGHLIGHT + * + * Usage: + * - Standby is the default mode and Highlight is activated when the PIR detects activity. + * - PIR delay now set to same value as Nightlight feature on boot but otherwise controlled as normal. + * - Standby and Highlight brightness can be set on the fly (default values set on boot via macros calling presets). + * - Macros are used to set Standby and Highlight states (macros can load saved presets etc). + * + * - Macro short button press = Highlight state default (used on boot only and sets default brightness). + * - Macro double button press = Standby state default (used on boot only and sets default brightness). + * - Macro long button press = Highlight state (after boot). + * - Macro 16 = Standby state (after boot). + * + * ! It is advised not to set 'Apply preset at boot' or a boot macro (that activates a preset) as we will call our own macros on boot. + * + * - When the strip is off before PIR activates the strip will return to off for Standby mode, and vice versa. + * - When the strip is turned off while in Highlight mode, it will return to standby mode. (This behaviour could be changed easily if for some reason you wanted the lights to go out when the pir is activated). + * - Macros can be chained so you could do almost anything, such as have standby mode also turn on the nightlight function with a new time delay. + * + * Segment Notes: + * - It's easier to save the segment selections in preset than apply via macro while we a limited to preset 16. (Ie, instead of selecting sections at the point of activating standby/highlight modes). + * - Because only preset 16 saves segments, for now we are having to use addiotional macros to control segments where they are involved. Macros can be chained so this works but it would be better if macros also accepted json-api commands. (Testing http api segement behaviour of SS with SB left me a little confused). + * + * Future: + * - Maybe a second timer/timetable that turns on/off standby mode also after set inactivity period / date & times. For now this can be achieved others ways so may not be worth eating more processing power. + * + * -------------------- + * + * This usermod handles PIR sensor states. + * The strip will be switched on and the off timer will be resetted when the sensor goes HIGH. + * When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off. + * + * + * Usermods allow you to add own functionality to WLED more easily + * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality + * + * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. + * Multiple v2 usermods can be added to one compilation easily. + * + * Creating a usermod: + * This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template. + * Please remember to rename the class and file to a descriptive name. + * You may also use multiple .h and .cpp files. + * + * Using a 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 + */ + +class PIRsensorSwitch : public Usermod { + private: + // PIR sensor pin + const uint8_t PIRsensorPin = 13; // D7 on D1 mini + // notification mode for colorUpdated() + const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE + // 1 min delay before switch off after the sensor state goes LOW + uint32_t m_switchOffDelay = 60000; + // off timer start time + uint32_t m_offTimerStart = 0; + // current PIR sensor pin state + byte m_PIRsensorPinState = LOW; + // PIR sensor enabled - ISR attached + bool m_PIRenabled = true; + // temp standby brightness store. initial value set as nightlight default target brightness + byte briStandby _INIT(nightlightTargetBri); + // temp hightlight brightness store. initial value set as current brightness + byte briHighlight _INIT(bri); + // highlight active/deactive monitor + bool highlightActive = false; + // wled on/off state in standby mode + bool standbyoff = false; + + /* + * return or change if new PIR sensor state is available + */ + static volatile bool newPIRsensorState(bool changeState = false, bool newState = false) { + static volatile bool s_PIRsensorState = false; + if (changeState) { + s_PIRsensorState = newState; + } + return s_PIRsensorState; + } + + /* + * PIR sensor state has changed + */ + static void IRAM_ATTR ISR_PIRstateChange() { + newPIRsensorState(true, true); + } + + /* + * switch strip on/off + */ + // now allowing adjustable standby and highlight brightness + void switchStrip(bool switchOn) { + //if (switchOn && bri == 0) { + if (switchOn) { // **pir sensor is on and activated** + //bri = briLast; + if (bri != 0) { // is WLED currently on + if (highlightActive) { // and is Highlight already on + briHighlight = bri; // then update highlight brightness with current brightness + } + else { + briStandby = bri; // else update standby brightness with current brightness + } + } + else { // WLED is currently off + if (!highlightActive) { // and Highlight is not already on + briStandby = briLast; // then update standby brightness with last active brightness (before turned off) + standbyoff = true; + } + else { // and Highlight is already on + briHighlight = briLast; // then set hightlight brightness to last active brightness (before turned off) + } + } + applyMacro(16); // apply highlight lighting without brightness + if (bri != briHighlight) { + bri = briHighlight; // set current highlight brightness to last set highlight brightness + } + colorUpdated(NotifyUpdateMode); + highlightActive = true; // flag highlight is on + } + else { // **pir timer has elapsed** + //briLast = bri; + //bri = 0; + if (bri != 0) { // is WLED currently on + briHighlight = bri; // update highlight brightness with current brightness + if (!standbyoff) { // + bri = briStandby; // set standby brightness to last set standby brightness + } + else { // + briLast = briStandby; // set standby off brightness + bri = 0; // set power off in standby + standbyoff = false; // turn off flag + } + applyMacro(macroLongPress); // apply standby lighting without brightness + } + else { // WLED is currently off + briHighlight = briLast; // set last active brightness (before turned off) to highlight lighting brightness + if (!standbyoff) { // + bri = briStandby; // set standby brightness to last set standby brightness + } + else { // + briLast = briStandby; // set standby off brightness + bri = 0; // set power off in standby + standbyoff = false; // turn off flag + } + applyMacro(macroLongPress); // apply standby lighting without brightness + } + colorUpdated(NotifyUpdateMode); + highlightActive = false; // flag highlight is off + } + } + + /* + * Read and update PIR sensor state. + * Initilize/reset switch off timer + */ + bool updatePIRsensorState() { + if (newPIRsensorState()) { + m_PIRsensorPinState = digitalRead(PIRsensorPin); + + if (m_PIRsensorPinState == HIGH) { + m_offTimerStart = 0; + switchStrip(true); + } + else if (bri != 0) { + // start switch off timer + m_offTimerStart = millis(); + } + newPIRsensorState(true, false); + return true; + } + return false; + } + + /* + * switch off the strip if the delay has elapsed + */ + bool handleOffTimer() { + if (m_offTimerStart > 0) { + if ((millis() - m_offTimerStart > m_switchOffDelay) || bri == 0 ) { // now also checking for manual power off during highlight mode + switchStrip(false); + m_offTimerStart = 0; + return true; + } + } + return false; + } + + public: + //Functions called by WLED + + /* + * 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() { + // PIR Sensor mode INPUT_PULLUP + pinMode(PIRsensorPin, INPUT_PULLUP); + // assign interrupt function and set CHANGE mode + attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); + // set delay to nightlight default duration on boot (after which json PIRoffSec overides if needed) + m_switchOffDelay = (nightlightDelayMins*60000); + applyMacro(macroButton); // apply default highlight lighting + briHighlight = bri; + applyMacro(macroDoublePress); // apply default standby lighting with brightness + briStandby = bri; + } + + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() { + + } + + + /* + * loop() is called continuously. Here you can check for events, read sensors, etc. + */ + void loop() { + if (!updatePIRsensorState()) { + handleOffTimer(); + } + } + + /* + * 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) + { + //this code adds "u":{"⏲ PIR sensor state":uiDomString} to the info object + // the value contains a button to toggle the sensor enabled/disabled + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray("⏲ PIR sensor state"); //name + String uiDomString = ""; + infoArr.add(uiDomString); //value + + //this code adds "u":{"⏲ switch off timer":uiDomString} to the info object + infoArr = user.createNestedArray("⏲ switch off timer"); //name + + // off timer + if (m_offTimerStart > 0) { + uiDomString = ""; + unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000; + if (offSeconds >= 3600) { + uiDomString += (offSeconds / 3600); + uiDomString += " hours "; + offSeconds %= 3600; + } + if (offSeconds >= 60) { + uiDomString += (offSeconds / 60); + offSeconds %= 60; + } else if (uiDomString.length() > 0){ + uiDomString += 0; + } + if (uiDomString.length() > 0){ + uiDomString += " min "; + } + uiDomString += (offSeconds); + infoArr.add(uiDomString + " sec"); + } else { + infoArr.add("inactive"); + } + } + + + /* + * 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 + * Add "PIRenabled" to json state. This can be used to disable/enable the sensor. + * Add "PIRoffSec" to json state. This can be used to adjust milliseconds . + */ + void addToJsonState(JsonObject& root) + { + root["PIRenabled"] = m_PIRenabled; + root["PIRoffSec"] = (m_switchOffDelay / 1000); + } + + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + * Read "PIRenabled" from json state and switch enable/disable the PIR sensor. + * Read "PIRoffSec" from json state and adjust milliseconds . + */ + void readFromJsonState(JsonObject& root) + { + if (root["PIRoffSec"] != nullptr) { + m_switchOffDelay = (1000 * max(60UL, min(43200UL, root["PIRoffSec"].as()))); + } + + if (root["PIRenabled"] != nullptr) { + if (root["PIRenabled"] && !m_PIRenabled) { + attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); + newPIRsensorState(true, true); + } + else if(m_PIRenabled) { + detachInterrupt(PIRsensorPin); + } + m_PIRenabled = root["PIRenabled"]; + } + } + + + /* + * 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; + } + + //More methods can be added in the future, this example will then be extended. + //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! +}; diff --git a/usermods/PIR_sensor_switch/readme.md b/usermods/PIR_sensor_switch/readme.md new file mode 100644 index 000000000..3d00b5054 --- /dev/null +++ b/usermods/PIR_sensor_switch/readme.md @@ -0,0 +1,109 @@ +# PIR sensor switch + +This usermod-v2 modification allows the connection of a PIR sensor to switch on the LED strip when motion is detected. The switch-off occurs ten minutes after no more motion is detected. + +_Story:_ + +I use the PIR Sensor to automatically turn on the WLED analog clock in my home office room when I am there. +The LED strip is switched [using a relay](https://github.com/Aircoookie/WLED/wiki/Control-a-relay-with-WLED) to keep the power consumption low when it is switched off. + +## Webinterface + +The info page in the web interface shows the items below + +- the state of the sensor. By clicking on the state the sensor can be deactivated/activated. Changes persist after a reboot. +**I recommend to deactivate the sensor before an OTA update and activate it again afterwards**. +- the remaining time of the off timer. + +## JSON API + +The usermod supports the following state changes: + +| JSON key | Value range | Description | +|------------|-------------|---------------------------------| +| PIRenabled | bool | Deactivdate/activate the sensor | +| PIRoffSec | 60 to 43200 | Off timer seconds | + + Changes also persist after a reboot. + +## Sensor connection + +My setup uses an HC-SR501 sensor, a HC-SR505 should also work. + +The usermod uses GPIO13 (D1 mini pin D7) for the sensor signal. +[This example page](http://www.esp8266learning.com/wemos-mini-pir-sensor-example.php) describes how to connect the sensor. + +Use the potentiometers on the sensor to set the time-delay to the minimum and the sensitivity to about half, or slightly above. + +## Usermod installation + +1. Copy the file `usermod_PIR_sensor_switch.h` to the `wled00` directory. +2. Register the usermod by adding `#include "usermod_PIR_sensor_switch.h"` in the top and `registerUsermod(new PIRsensorSwitch());` in the bottom of `usermods_list.cpp`. + +Example **usermods_list.cpp**: + +```cpp +#include "wled.h" +/* + * Register your v2 usermods here! + * (for v1 usermods using just usermod.cpp, you can ignore this file) + */ + +/* + * Add/uncomment your usermod filename here (and once more below) + * || || || + * \/ \/ \/ + */ +//#include "usermod_v2_example.h" +//#include "usermod_temperature.h" +//#include "usermod_v2_empty.h" +#include "usermod_PIR_sensor_switch.h" + +void registerUsermods() +{ + /* + * Add your usermod class name here + * || || || + * \/ \/ \/ + */ + //usermods.add(new MyExampleUsermod()); + //usermods.add(new UsermodTemperature()); + //usermods.add(new UsermodRenameMe()); + usermods.add(new PIRsensorSwitch()); + +} +``` + +## API to enable/disable the PIR sensor from outside. For example from another usermod. + +The class provides the static method `PIRsensorSwitch* PIRsensorSwitch::GetInstance()` to get a pointer to the usermod object. + +To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available. + +### There are two options to get access to the usermod instance: + +1. Include `usermod_PIR_sensor_switch.h` **before** you include the other usermod in `usermods_list.cpp' + +or + +2. Use `#include "usermod_PIR_sensor_switch.h"` at the top of the `usermod.h` where you need it. + +**Example usermod.h :** +```cpp +#include "wled.h" + +#include "usermod_PIR_sensor_switch.h" + +class MyUsermod : public Usermod { + //... + + void togglePIRSensor() { + if (PIRsensorSwitch::GetInstance() != nullptr) { + PIRsensorSwitch::GetInstance()->EnablePIRsensor(!PIRsensorSwitch::GetInstance()->PIRsensorEnabled()); + } + } + //... +}; +``` + +Have fun - @gegu diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h new file mode 100644 index 000000000..421528bfe --- /dev/null +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -0,0 +1,366 @@ +#pragma once + +#include "wled.h" + +/* + * This usermod handles PIR sensor states. + * The strip will be switched on and the off timer will be resetted when the sensor goes HIGH. + * When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off. + * + * + * Usermods allow you to add own functionality to WLED more easily + * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality + * + * v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example. + * Multiple v2 usermods can be added to one compilation easily. + * + * Creating a usermod: + * This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template. + * Please remember to rename the class and file to a descriptive name. + * You may also use multiple .h and .cpp files. + * + * Using a 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 + */ + +class PIRsensorSwitch : public Usermod +{ +public: + /** + * constructor + */ + PIRsensorSwitch() + { + // set static instance pointer + PIRsensorSwitchInstance(this); + } + /** + * desctructor + */ + ~PIRsensorSwitch() + { + PIRsensorSwitchInstance(nullptr, true); + ; + } + + /** + * return the instance pointer of the class + */ + static PIRsensorSwitch *GetInstance() { return PIRsensorSwitchInstance(); } + + /** + * Enable/Disable the PIR sensor + */ + void EnablePIRsensor(bool enable) { m_PIRenabled = enable; } + /** + * Get PIR sensor enabled/disabled state + */ + bool PIRsensorEnabled() { return m_PIRenabled; } + +private: + // PIR sensor pin + const uint8_t PIRsensorPin = 13; // D7 on D1 mini + // notification mode for colorUpdated() + const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE + // delay before switch off after the sensor state goes LOW + uint32_t m_switchOffDelay = 600000; + // off timer start time + uint32_t m_offTimerStart = 0; + // current PIR sensor pin state + byte m_PIRsensorPinState = LOW; + // PIR sensor enabled - ISR attached + bool m_PIRenabled = true; + // state if serializeConfig() should be called + bool m_updateConfig = false; + + /** + * return or change if new PIR sensor state is available + */ + static volatile bool newPIRsensorState(bool changeState = false, bool newState = false); + + /** + * PIR sensor state has changed + */ + static void IRAM_ATTR ISR_PIRstateChange(); + + /** + * Set/get instance pointer + */ + static PIRsensorSwitch *PIRsensorSwitchInstance(PIRsensorSwitch *pInstance = nullptr, bool bRemoveInstance = false); + + /** + * switch strip on/off + */ + void switchStrip(bool switchOn) + { + if (switchOn && bri == 0) + { + bri = briLast; + colorUpdated(NotifyUpdateMode); + } + else if (!switchOn && bri != 0) + { + briLast = bri; + bri = 0; + colorUpdated(NotifyUpdateMode); + } + } + + /** + * Read and update PIR sensor state. + * Initilize/reset switch off timer + */ + bool updatePIRsensorState() + { + if (newPIRsensorState()) + { + m_PIRsensorPinState = digitalRead(PIRsensorPin); + + if (m_PIRsensorPinState == HIGH) + { + m_offTimerStart = 0; + switchStrip(true); + } + else if (bri != 0) + { + // start switch off timer + m_offTimerStart = millis(); + } + newPIRsensorState(true, false); + return true; + } + return false; + } + + /** + * switch off the strip if the delay has elapsed + */ + bool handleOffTimer() + { + if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay) + { + if (m_PIRenabled == true) + { + switchStrip(false); + } + m_offTimerStart = 0; + return true; + } + return false; + } + +public: + //Functions called by WLED + + /** + * 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() + { + // PIR Sensor mode INPUT_PULLUP + pinMode(PIRsensorPin, INPUT_PULLUP); + if (m_PIRenabled) + { + // assign interrupt function and set CHANGE mode + attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); + } + } + + /** + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() + { + } + + /** + * loop() is called continuously. Here you can check for events, read sensors, etc. + */ + void loop() + { + if (!updatePIRsensorState()) + { + handleOffTimer(); + if (m_updateConfig) + { + serializeConfig(); + m_updateConfig = false; + } + } + } + + /** + * 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) + { + //this code adds "u":{"⏲ PIR sensor state":uiDomString} to the info object + // the value contains a button to toggle the sensor enabled/disabled + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray("⏲ PIR sensor state"); //name + String uiDomString = ""; + infoArr.add(uiDomString); //value + + //this code adds "u":{"⏲ switch off timer":uiDomString} to the info object + uiDomString = "⏲ switch off timer\ +after min"; + infoArr = user.createNestedArray(uiDomString); //name + + // off timer + if (m_offTimerStart > 0) + { + uiDomString = ""; + unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000; + if (offSeconds >= 3600) + { + uiDomString += (offSeconds / 3600); + uiDomString += " hours "; + offSeconds %= 3600; + } + if (offSeconds >= 60) + { + uiDomString += (offSeconds / 60); + offSeconds %= 60; + } + else if (uiDomString.length() > 0) + { + uiDomString += 0; + } + if (uiDomString.length() > 0) + { + uiDomString += " min "; + } + uiDomString += (offSeconds); + infoArr.add(uiDomString + " sec"); + } + else + { + infoArr.add("inactive"); + } + } + + /** + * 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 + * Add "PIRenabled" to json state. This can be used to disable/enable the sensor. + * Add "PIRoffSec" to json state. This can be used to adjust milliseconds. + */ + void addToJsonState(JsonObject &root) + { + root["PIRenabled"] = m_PIRenabled; + root["PIRoffSec"] = (m_switchOffDelay / 1000); + } + + /** + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + * Read "PIRenabled" from json state and switch enable/disable the PIR sensor. + * Read "PIRoffSec" from json state and adjust milliseconds. + */ + void readFromJsonState(JsonObject &root) + { + if (root["PIRoffSec"] != nullptr) + { + m_switchOffDelay = (1000 * max(60UL, min(43200UL, root["PIRoffSec"].as()))); + m_updateConfig = true; + } + + if (root["PIRenabled"] != nullptr) + { + if (root["PIRenabled"] && !m_PIRenabled) + { + attachInterrupt(digitalPinToInterrupt(PIRsensorPin), ISR_PIRstateChange, CHANGE); + newPIRsensorState(true, true); + } + else if (m_PIRenabled) + { + detachInterrupt(PIRsensorPin); + } + m_PIRenabled = root["PIRenabled"]; + m_updateConfig = true; + } + } + + /** + * provide the changeable values + */ + void addToConfig(JsonObject &root) + { + JsonObject top = root.createNestedObject("PIRsensorSwitch"); + top["PIRenabled"] = m_PIRenabled; + top["PIRoffSec"] = m_switchOffDelay; + } + + /** + * restore the changeable values + */ + void readFromConfig(JsonObject &root) + { + JsonObject top = root["PIRsensorSwitch"]; + m_PIRenabled = (top["PIRenabled"] != nullptr ? top["PIRenabled"] : true); + m_switchOffDelay = top["PIRoffSec"] | m_switchOffDelay; + } + + /** + * 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; + } +}; + +////////////////////////////////////////////////////// +// PIRsensorSwitch static method implementations + +volatile bool PIRsensorSwitch::newPIRsensorState(bool changeState, bool newState) +{ + static volatile bool s_PIRsensorState = false; + if (changeState) + { + s_PIRsensorState = newState; + } + return s_PIRsensorState; +} + +void IRAM_ATTR PIRsensorSwitch::ISR_PIRstateChange() +{ + newPIRsensorState(true, true); +} + +PIRsensorSwitch *PIRsensorSwitch::PIRsensorSwitchInstance(PIRsensorSwitch *pInstance, bool bRemoveInstance) +{ + static PIRsensorSwitch *s_pPIRsensorSwitch = nullptr; + if (pInstance != nullptr || bRemoveInstance) + { + s_pPIRsensorSwitch = pInstance; + } + return s_pPIRsensorSwitch; +} diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt b/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.md similarity index 50% rename from usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt rename to usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.md index 612873360..60fc31f73 100644 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt +++ b/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.md @@ -1,8 +1,34 @@ -These files allow WLED 0.8.6 to report the temp sensor on the Quinled board to MQTT. I use it to report the board temp to Home Assistant via MQTT, so it will send notifications if something happens and the board start to heat up. +# QuinLED Dig Uno board +These files allow WLED 0.9.1 to report the temp sensor on the Quinled board to MQTT. I use it to report the board temp to Home Assistant via MQTT, so it will send notifications if something happens and the board start to heat up. This code uses Aircookie's WLED software. It has a premade file for user modifications. I use it to publish the temperature from the dallas temperature sensor on the Quinled board. The entries for the top of the WLED00 file, initializes the required libraries, and variables for the sensor. The .ino file waits for 60 seconds, and checks to see if the MQTT server is connected (thanks Aircoookie). It then poles the sensor, and published it using the MQTT service already running, using the main topic programmed in the WLED UI. -To install: +Installation of file: Copy and replace file in wled00 directory -Add the entries in the WLED00 file to the top of the same file from Aircoookies WLED. -Replace the WLED06_usermod.ino file in Aircoookies WLED folder. +## Project link + +* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link + +### Platformio requirements + +Uncomment `DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: + +```ini +# platformio.ini +... +[platformio] +... +; default_envs = esp07 +default_envs = d1_mini +... +[common] +... +lib_deps_external = + ... + #For use SSD1306 OLED display uncomment following + U8g2@~2.27.3 + #For Dallas sensor uncomment following 2 lines + DallasTemperature@~3.8.0 + OneWire@~2.3.5 +... +``` diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino b/usermods/QuinLED_Dig_Uno_Temp_MQTT/usermod.cpp similarity index 61% rename from usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino rename to usermods/QuinLED_Dig_Uno_Temp_MQTT/usermod.cpp index 1309a4f83..5b4e2e5c7 100644 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino +++ b/usermods/QuinLED_Dig_Uno_Temp_MQTT/usermod.cpp @@ -1,8 +1,21 @@ -//starts Dallas Temp service on boot +#include +#include "wled.h" +//Intiating code for QuinLED Dig-Uno temp sensor +//Uncomment Celsius if that is your prefered temperature scale +#include //Dallastemperature sensor +#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards +OneWire oneWire(18); +#else //ESP8266 boards +OneWire oneWire(14); +#endif +DallasTemperature sensor(&oneWire); +long temptimer = millis(); +long lastMeasure = 0; +#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit void userSetup() { // Start the DS18B20 sensor - sensors.begin(); + sensor.begin(); } //gets called every time WiFi is (re-)connected. Initialize own network interfaces here @@ -21,11 +34,11 @@ void userLoop() //Check if MQTT Connected, otherwise it will crash the 8266 if (mqtt != nullptr){ - sensors.requestTemperatures(); + sensor.requestTemperatures(); //Gets prefered temperature scale based on selection in definitions section #ifdef Celsius - float board_temperature = sensors.getTempCByIndex(0); + float board_temperature = sensor.getTempCByIndex(0); #else float board_temperature = sensors.getTempFByIndex(0); #endif diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled00.txt b/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled00.txt deleted file mode 100644 index 661a7e249..000000000 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled00.txt +++ /dev/null @@ -1,8 +0,0 @@ -//Intiating code for QuinLED Dig-Uno temp sensor -//Uncomment Celsius if that is your prefered temperature scale -#include -OneWire oneWire(14); -DallasTemperature sensors(&oneWire); -long temptimer = millis(); -long lastMeasure = 0; -//#define Celsius diff --git a/usermods/TTGO-T-Display/README.md b/usermods/TTGO-T-Display/README.md new file mode 100644 index 000000000..872beeb8c --- /dev/null +++ b/usermods/TTGO-T-Display/README.md @@ -0,0 +1,91 @@ +# TTGO T-Display ESP32 with 240x135 TFT via SPI with TFT_eSPI +This usermod allows use of the TTGO T-Display ESP32 module with integrated 240x135 display +for controlling WLED and showing the following information: +* Current SSID +* IP address if obtained + * If connected to a network, current brightness % is shown + * in AP mode AP IP and password are shown +* Current effect +* Current palette +* Estimated current in mA is shown (NOTE: for this to be a reasonable value, the correct LED type must be specified in the LED Prefs section) + +Button pin is mapped to the onboard button next to the side actuated reset button of the TTGO T-Display board. + +I have designed a 3D printed case around this board and an ["ElectroCookie"](https://amzn.to/2WCNeeA) project board, a [level shifter](https://amzn.to/3hbKu18), a [buck regulator](https://amzn.to/3mLMy0W), and a DC [power jack](https://amzn.to/3phj9NZ). I use 12V WS2815 LED strips for my projects, and power them with 12V power supplies, so the regulator drops the voltage to the 5V level I need to power the ESP module and the level shifter. If there is any interest in this case, which elevates the board and display on some custom extended headers to make place the screen at the top of the enclosure (with accessible buttons), let me know, and I could post the STL files. It is a bit tricky to get the height correct, so I also designed a one-time use 3D printed solder fixture to set the board in the right location and at the correct height for the housing. (It is one-time use because it has to be cut off after soldering to be able to remove it). I didn't think the effort to make it in multiple pieces was worthwhile. + +Usermod based on a rework of the ssd1306_i2c_oled_u8g2 usermod from the WLED repo. + +## Hardware +![Hardware](assets/ttgo_hardware1.png) +![Hardware](assets/ttgo-tdisplay-enclosure1a.png) +![Hardware](assets/ttgo-tdisplay-enclosure2a.png) +![Hardware](assets/ttgo-tdisplay-enclosure3a.png) +![Hardware](assets/ttgo-tdisplay-enclosure3a.png) + +## Github reference for TTGO-Tdisplay + +* [TTGO T-Display](https://github.com/Xinyuan-LilyGO/TTGO-T-Display) + +## Requirements +Functionality checked with: +* TTGO T-Display +* PlatformIO +* Group of 4 individual Neopixels from Adafruit, and a several full strings of 12v WS2815 LEDs. +* The hardware design shown above should be limited to shorter strings. For larger strings, I use a different setup with a dedicated 12v power supply and power them directly off the supply (in addition to dropping the 12v supply down to 5v with a buck regulator for the ESP module and level shifter). + +## Setup Needed: +* As with all usermods, copy the usermod.cpp file from the TTGO-T-Display usermod folder to the wled00 folder (replacing the default usermod.cpp file). + +## Platformio Requirements +### Platformio.ini changes +Under the root folder of the project, in the `platformio.ini` file, uncomment the `TFT_eSPI` line within the [common] section, under `lib_deps`: +```ini +# platformio.ini +... +[common] +... +lib_deps = + ... + #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line + #TFT_eSPI +... +``` + +Also, while in the `platformio.ini` file, you must change the environment setup to build for just the esp32dev platform as follows: + +Comment out the line described below: +```ini +# Travis CI binaries (comment this out when building for single board) +; default_envs = travis_esp8266, esp01, esp01_1m_ota, travis_esp32 +``` +and UNCOMMENT the following line in the 'Single binaries' section: +```ini +default_envs = esp32dev +``` +Save the `platformio.ini` file. Once this is saved, the required library files should be automatically downloaded for modifications in a later step. + +### Platformio_overrides.ini (added) +Copy the `platformio_overrides.ini` file which is contained in the `usermods/TTGO-T-Display/` folder into the root of your project folder. This file contains an override that remaps the button pin of WLED to use the on-board button to the right of the USB-C connector (when viewed with the port oriented downward - see hardware photo). + +### TFT_eSPI Library Adjustments (board selection) +We need to modify a file in the `TFT_eSPI` library to select the correct board. If you followed the directions to modify and save the `platformio.ini` file above, the `User_Setup_Select.h` file can be found in the `/.pio/libdeps/esp32dev/TFT_eSPI_ID1559` folder. + +Modify the `User_Setup_Select.h` file as follows: +* Comment out the following line (which is the 'default' setup file): +```ini +//#include // Default setup is root library folder +``` +* Uncomment the following line (which points to the setup file for the TTGO T-Display): +```ini +#include // Setup file for ESP32 and TTGO T-Display ST7789V SPI bus TFT +``` + +Run the build and it should complete correctly. If you see a failure like this: +```ini +xtensa-esp32-elf-g++: error: wled00\wled00.ino.cpp: No such file or directory +xtensa-esp32-elf-g++: fatal error: no input files +``` +Just try building again - I find that sometimes this happens on the first build attempt and subsequent attempts will build correctly. + +## Arduino IDE +- UNTESTED \ No newline at end of file diff --git a/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure1a.png b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure1a.png new file mode 100644 index 000000000..5c2c2bef4 Binary files /dev/null and b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure1a.png differ diff --git a/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure2a.png b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure2a.png new file mode 100644 index 000000000..ac76ade44 Binary files /dev/null and b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure2a.png differ diff --git a/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure3a.png b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure3a.png new file mode 100644 index 000000000..21c416f72 Binary files /dev/null and b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure3a.png differ diff --git a/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure4a.png b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure4a.png new file mode 100644 index 000000000..a098fc23d Binary files /dev/null and b/usermods/TTGO-T-Display/assets/ttgo-tdisplay-enclosure4a.png differ diff --git a/usermods/TTGO-T-Display/assets/ttgo_hardware1.png b/usermods/TTGO-T-Display/assets/ttgo_hardware1.png new file mode 100644 index 000000000..3d2b940d6 Binary files /dev/null and b/usermods/TTGO-T-Display/assets/ttgo_hardware1.png differ diff --git a/usermods/TTGO-T-Display/platformio_override.ini b/usermods/TTGO-T-Display/platformio_override.ini new file mode 100644 index 000000000..7e42d9a54 --- /dev/null +++ b/usermods/TTGO-T-Display/platformio_override.ini @@ -0,0 +1,8 @@ +[env:esp32dev] +build_flags = ${common.build_flags_esp32} +; PIN defines - uncomment and change, if needed: +; -D LEDPIN=2 + -D BTNPIN=35 +; -D IRPIN=4 +; -D RLYPIN=12 +; -D RLYMDE=1 diff --git a/usermods/TTGO-T-Display/usermod.cpp b/usermods/TTGO-T-Display/usermod.cpp new file mode 100644 index 000000000..75e90b1eb --- /dev/null +++ b/usermods/TTGO-T-Display/usermod.cpp @@ -0,0 +1,238 @@ + +/* + * 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 + */ + +/* + * Pin 2 of the TTGO T-Display serves as the data line for the LED string. + * Pin 35 is set up as the button pin in the platformio_overrides.ini file. + * The button can be set up via the macros section in the web interface. + * I use the button to cycle between presets. + * The Pin 35 button is the one on the RIGHT side of the USB-C port on the board, + * when the port is oriented downwards. See readme.md file for photo. + * The display is set up to turn off after 5 minutes, and turns on automatically + * when a change in the dipslayed info is detected (within a 5 second interval). + */ + + +//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) + +#include "wled.h" +#include +#include +#include "WiFi.h" +#include + +#ifndef TFT_DISPOFF +#define TFT_DISPOFF 0x28 +#endif + +#ifndef TFT_SLPIN +#define TFT_SLPIN 0x10 +#endif + +#define TFT_MOSI 19 +#define TFT_SCLK 18 +#define TFT_CS 5 +#define TFT_DC 16 +#define TFT_RST 23 + +#define TFT_BL 4 // Display backlight control pin +#define ADC_EN 14 // Used for enabling battery voltage measurements - not used in this program + +TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke custom library + +//gets called once at boot. Do all initialization that doesn't depend on network here +void userSetup() { + Serial.begin(115200); + Serial.println("Start"); + tft.init(); + tft.setRotation(3); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip. + tft.fillScreen(TFT_BLACK); + tft.setTextSize(2); + tft.setTextColor(TFT_WHITE); + tft.setCursor(1, 10); + tft.setTextDatum(MC_DATUM); + tft.setTextSize(3); + tft.print("Loading..."); + + if (TFT_BL > 0) { // TFT_BL has been set in the TFT_eSPI library in the User Setup file TTGO_T_Display.h + pinMode(TFT_BL, OUTPUT); // Set backlight pin to output mode + digitalWrite(TFT_BL, HIGH); // Turn backlight on. + } + + // tft.setRotation(3); +} + +// gets called every time WiFi is (re-)connected. Initialize own network +// interfaces here +void userConnected() {} + +// needRedraw marks if redraw is required to prevent often redrawing. +bool needRedraw = true; + +// Next variables hold the previous known values to determine if redraw is +// required. +String knownSsid = ""; +IPAddress knownIp; +uint8_t knownBrightness = 0; +uint8_t knownMode = 0; +uint8_t knownPalette = 0; +uint8_t tftcharwidth = 19; // Number of chars that fit on screen with text size set to 2 + +long lastUpdate = 0; +long lastRedraw = 0; +bool displayTurnedOff = false; +// How often we are redrawing screen +#define USER_LOOP_REFRESH_RATE_MS 5000 + +void userLoop() { + + // Check if we time interval for redrawing passes. + if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + return; + } + lastUpdate = millis(); + + // Turn off display after 5 minutes with no change. + if(!displayTurnedOff && millis() - lastRedraw > 5*60*1000) { + digitalWrite(TFT_BL, LOW); // Turn backlight off. + displayTurnedOff = true; + } + + // Check if values which are shown on display changed from the last time. + if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { + needRedraw = true; + } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { + needRedraw = true; + } else if (knownBrightness != bri) { + needRedraw = true; + } else if (knownMode != strip.getMode()) { + needRedraw = true; + } else if (knownPalette != strip.getSegment(0).palette) { + needRedraw = true; + } + + if (!needRedraw) { + return; + } + needRedraw = false; + + if (displayTurnedOff) + { + digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); // Turn backlight on. + displayTurnedOff = false; + } + lastRedraw = millis(); + + // Update last known values. + #if defined(ESP8266) + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + #else + knownSsid = WiFi.SSID(); + #endif + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + knownBrightness = bri; + knownMode = strip.getMode(); + knownPalette = strip.getSegment(0).palette; + + tft.fillScreen(TFT_BLACK); + tft.setTextSize(2); + // 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 + if (knownSsid.length() > tftcharwidth) + tft.print("~"); + + // Second row with AP IP and Password or IP + tft.setTextSize(2); + tft.setCursor(1, 24); + // Print AP IP and password in AP mode or knownIP if AP not active. + // if (apActive && bri == 0) + // tft.print(apPass); + // else + // tft.print(knownIp); + + if (apActive) { + tft.print("AP IP: "); + tft.print(knownIp); + tft.setCursor(1,46); + tft.print("AP Pass:"); + tft.print(apPass); + } + else { + tft.print("IP: "); + tft.print(knownIp); + tft.setCursor(1,46); + //tft.print("Signal Strength: "); + //tft.print(i.wifi.signal); + tft.print("Brightness: "); + tft.print(((float(bri)/255)*100)); + tft.print("%"); + } + + // Third row with mode name + tft.setCursor(1, 68); + uint8_t qComma = 0; + bool insideQuotes = false; + uint8_t printedChars = 0; + char singleJsonSymbol; + // Find the mode name in JSON + for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownMode)) + break; + tft.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > tftcharwidth - 1)) + break; + } + // Fourth row with palette name + tft.setCursor(1, 90); + qComma = 0; + insideQuotes = false; + printedChars = 0; + // Looking for palette name in JSON. + for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownPalette)) + break; + tft.print(singleJsonSymbol); + printedChars++; + } + // The following is modified from the code from the u8g2/u8g8 based code (knownPalette was knownMode) + if ((qComma > knownPalette) || (printedChars > tftcharwidth - 1)) + break; + } + // Fifth row with estimated mA usage + tft.setCursor(1, 112); + // Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate). + tft.print(strip.currentMilliamps); + tft.print("mA (estimated)"); + +} diff --git a/usermods/Temperature/platformio_override.ini b/usermods/Temperature/platformio_override.ini new file mode 100644 index 000000000..d9e3fbace --- /dev/null +++ b/usermods/Temperature/platformio_override.ini @@ -0,0 +1,13 @@ +; Options +; ------- +; USERMOD_DALLASTEMPERATURE - define this to have this user mod included wled00\usermods_list.cpp +; USERMOD_DALLASTEMPERATURE_CELSIUS - define this to report temperatures in degrees celsius, otherwise fahrenheit will be reported +; USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds +; USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 20 seconds +; +[env:d1_mini_usermod_dallas_temperature_C] +extends = env:d1_mini +build_flags = ${common.build_flags_esp8266} -D USERMOD_DALLASTEMPERATURE -D USERMOD_DALLASTEMPERATURE_CELSIUS +lib_deps = ${env.lib_deps} + milesburton/DallasTemperature@^3.9.0 + OneWire@~2.3.5 diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md new file mode 100644 index 000000000..5b7f5b958 --- /dev/null +++ b/usermods/Temperature/readme.md @@ -0,0 +1,58 @@ +# Temperature usermod + +Based on the excellent `QuinLED_Dig_Uno_Temp_MQTT` by srg74 and 400killer! +This usermod will read from an attached DS18B20 temperature sensor (as available on the QuinLED Dig-Uno) +The temperature is displayed both in the Info section of the web UI as well as published to the `/temperature` MQTT topic if enabled. +This usermod will be expanded with support for different sensor types in the future. + +If temperature sensor is not detected during boot, this usermod will be disabled. + +## Installation + +Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`. + +### Define Your Options + +* `USERMOD_DALLASTEMPERATURE` - define this to have this user mod included wled00\usermods_list.cpp +* `USERMOD_DALLASTEMPERATURE_CELSIUS` - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported +* `USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60 seconds +* `USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 20 seconds + +## Project link + +* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link + +### PlatformIO requirements + +If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:d1_mini_usermod_dallas_temperature_C`. + + +If you are not using `platformio_override.ini`, you might have to uncomment `DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: + +```ini +# platformio.ini +... +[platformio] +... +; default_envs = esp07 +default_envs = d1_mini +... +[common] +... +lib_deps = + ... + #For use SSD1306 OLED display uncomment following + U8g2@~2.27.3 + #For Dallas sensor uncomment following 2 lines + DallasTemperature@~3.8.0 + OneWire@~2.3.5 +... +``` + +## Change Log + +2020-09-12 +* Changed to use async, non-blocking implementation +* Do not report low temperatures that indicate an error to mqtt +* Disable plugin if temperature sensor not detected +* Report the number of seconds until the first read in the info screen instead of sensor error diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h new file mode 100644 index 000000000..1ce0322e8 --- /dev/null +++ b/usermods/Temperature/usermod_temperature.h @@ -0,0 +1,173 @@ +#pragma once + +#include "wled.h" + +#include //DS18B20 + +//Pin defaults for QuinLed Dig-Uno +#ifndef TEMPERATURE_PIN +#ifdef ARDUINO_ARCH_ESP32 +#define TEMPERATURE_PIN 18 +#else //ESP8266 boards +#define TEMPERATURE_PIN 14 +#endif +#endif + +// the frequency to check temperature, 1 minute +#ifndef USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL +#define USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL 60000 +#endif + +// how many seconds after boot to take first measurement, 20 seconds +#ifndef USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT +#define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000 +#endif + +OneWire oneWire(TEMPERATURE_PIN); +DallasTemperature sensor(&oneWire); + +class UsermodTemperature : public Usermod { + private: + // The device's unique 64-bit serial code stored in on-board ROM. + // Reading directly from the sensor device address is faster than + // reading from index. When reading by index, DallasTemperature + // must first look up the device address at the specified index. + DeviceAddress sensorDeviceAddress; + // set last reading as "40 sec before boot", so first reading is taken after 20 sec + unsigned long lastMeasurement = UINT32_MAX - (USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT); + // last time requestTemperatures was called + // used to determine when we can read the sensors temperature + // we have to wait at least 93.75 ms after requestTemperatures() is called + unsigned long lastTemperaturesRequest; + float temperature = -100; // default to -100, DS18B20 only goes down to -50C + // indicates requestTemperatures has been called but the sensor measurement is not complete + bool waitingForConversion = false; + // flag to indicate we have finished the first getTemperature call + // allows this library to report to the user how long until the first + // measurement + bool getTemperatureComplete = false; + // flag set at startup if DS18B20 sensor not found, avoids trying to keep getting + // temperature if flashed to a board without a sensor attached + bool disabled = false; + + void requestTemperatures() { + // there is requestTemperaturesByAddress however it + // appears to do more work, + // TODO: measure exection time difference + sensor.requestTemperatures(); + lastTemperaturesRequest = millis(); + waitingForConversion = true; + } + + void getTemperature() { + if (strip.isUpdating()) return; + #ifdef USERMOD_DALLASTEMPERATURE_CELSIUS + temperature = sensor.getTempC(sensorDeviceAddress); + #else + temperature = sensor.getTempF(sensorDeviceAddress); + #endif + + lastMeasurement = millis(); + waitingForConversion = false; + getTemperatureComplete = true; + } + + public: + + + void setup() { + sensor.begin(); + + // get the unique 64-bit serial code stored in on-board ROM + // if getAddress returns false, the sensor was not found + disabled = !sensor.getAddress(sensorDeviceAddress, 0); + + if (!disabled) { + DEBUG_PRINTLN(F("Dallas Temperature found")); + // set the resolution for this specific device + sensor.setResolution(sensorDeviceAddress, 9, true); + // do not block waiting for reading + sensor.setWaitForConversion(false); + // allocate pin & prevent other use + if (!pinManager.allocatePin(TEMPERATURE_PIN,false)) + disabled = true; + } else { + DEBUG_PRINTLN(F("Dallas Temperature not found")); + } + } + + void loop() { + if (disabled || strip.isUpdating()) return; + + unsigned long now = millis(); + + // check to see if we are due for taking a measurement + // lastMeasurement will not be updated until the conversion + // is complete the the reading is finished + if (now - lastMeasurement < USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL) return; + + // we are due for a measurement, if we are not already waiting + // for a conversion to complete, then make a new request for temps + if (!waitingForConversion) + { + requestTemperatures(); + return; + } + + // we were waiting for a conversion to complete, have we waited log enough? + if (now - lastTemperaturesRequest >= 94 /* 93.75ms per the datasheet */) + { + getTemperature(); + + if (WLED_MQTT_CONNECTED) { + char subuf[38]; + strcpy(subuf, mqttDeviceTopic); + if (-100 <= temperature) { + // dont publish super low temperature as the graph will get messed up + // the DallasTemperature library returns -127C or -196.6F when problem + // reading the sensor + strcat_P(subuf, PSTR("/temperature")); + mqtt->publish(subuf, 0, true, String(temperature).c_str()); + } else { + // publish something else to indicate status? + } + } + } + } + + void addToJsonInfo(JsonObject& root) { + // dont add temperature to info if we are disabled + if (disabled) return; + + JsonObject user = root[F("u")]; + if (user.isNull()) user = root.createNestedObject(F("u")); + + JsonArray temp = user.createNestedArray(F("Temperature")); + + if (!getTemperatureComplete) { + // if we haven't read the sensor yet, let the user know + // that we are still waiting for the first measurement + temp.add((USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - millis()) / 1000); + temp.add(F(" sec until read")); + return; + } + + if (temperature <= -100) { + temp.add(0); + temp.add(F(" Sensor Error!")); + return; + } + + temp.add(temperature); + #ifdef USERMOD_DALLASTEMPERATURE_CELSIUS + temp.add(F("°C")); + #else + temp.add(F("°F")); + #endif + } + + uint16_t getId() + { + return USERMOD_ID_TEMPERATURE; + } +}; diff --git a/usermods/Temperature/usermods_list.cpp b/usermods/Temperature/usermods_list.cpp new file mode 100644 index 000000000..50dd7816b --- /dev/null +++ b/usermods/Temperature/usermods_list.cpp @@ -0,0 +1,31 @@ +#include "wled.h" +/* + * Register your v2 usermods here! + */ + +/* + * Add/uncomment your usermod filename here (and once more below) + * || || || + * \/ \/ \/ + */ +//#include "usermod_v2_example.h" +#ifdef USERMOD_DALLASTEMPERATURE +#include "../usermods/Temperature/usermod_temperature.h" +#endif + +//#include "usermod_v2_empty.h" + +void registerUsermods() +{ + /* + * Add your usermod class name here + * || || || + * \/ \/ \/ + */ + //usermods.add(new MyExampleUsermod()); +#ifdef USERMOD_DALLASTEMPERATURE + usermods.add(new UsermodTemperature()); +#endif + + //usermods.add(new UsermodRenameMe()); +} \ No newline at end of file diff --git a/usermods/UserModv2_SunRiseAndSet/README.md b/usermods/UserModv2_SunRiseAndSet/README.md new file mode 100644 index 000000000..e989f0890 --- /dev/null +++ b/usermods/UserModv2_SunRiseAndSet/README.md @@ -0,0 +1,15 @@ +WLED v2 UserMod for running macros at sunrise and sunset. + +At the time of this text, this user mod requires code to be changed to set certain variables: + 1. To reflect the user's graphical location (latitude/longitude) used for calculating apparent sunrise/sunset + 2. To specify which macros will be run at sunrise and/or sunset. (defaults to 15 at sunrise and 16 at sunset) + 3. To optionally provide an offset from sunrise/sunset, in minutes (max of +/- 2 hours), when the macro will be run. + +In addition, WLED must be configured to get time from NTP (and the time must be retrieved via NTP.) + +Please open the UserMod_SunRiseAndSet.h file for instructions on what needs to be changed, where to copy files, etc. + +If this usermod proves useful enough, the code might eventually be updated to allow prompting for the required information +via the web interface and to store settings in EEPROM instead of hard-coding in the .h file. + +This usermod has only been tested on the esp32dev platform, but there's no reason it wouldn't work on other platforms. diff --git a/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h b/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h new file mode 100644 index 000000000..62176ce92 --- /dev/null +++ b/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h @@ -0,0 +1,166 @@ +#pragma once + +#include "wled.h" +#include + +/* + * + * REQUIREMENTS: + * The Dusk2Dawn library must be installed. This can be found at https://github.com/dmkishi/Dusk2Dawn. The 1.0.1 version of this library found via + * Arduino or platformio library managers is buggy and won't compile. The latest version from github should be used. + * + * NTP must be enabled and functional. It simply makes no sense to have events on sunrise/sunset when an accurate time isn't available. + * + * The user's geographical latitude and longitude must be configured (in decimal, not degrees/minutes/etc) using m_fLatitude and m_fLongitude + * + * if desired, an offset of up to +/- 2 hours can be specified for each of sunrise/sunset using m_sunriseOffset and m_sunsetOffset (defaults to 0) + * + * The specific macro to run at sunrise and/or sunset can be changed using m_sunriseMacro and m_sunsetMacro. (defaults to 15 and 16) + * + * From the Dusk2Dawn library: + * HINT: An easy way to find the longitude and latitude for any location is + * to find the spot in Google Maps, right click the place on the map, and + * select "What's here?". At the bottom, you’ll see a card with the + * coordinates. + * + * Once configured, copy UserMod_SunRiseAndSet.h to the sketch file (the same folder as wled00.ino exists), + * and then edit "usermods_list.cpp": + * Add '#include "UserMod_SunRiseAndSet.h"' in the 'includes' area + * Add 'usermods.add(new UserMod_SunRiseAndSet());' in the registerUsermods() area + * + */ + +class UserMod_SunRiseAndSet : public Usermod +{ +private: + + /**** USER SETTINGS ****/ + + float m_fLatitude = 40.6; // latitude where sunrise/set are calculated + float m_fLongitude = -79.80; // longitude where sunrise/set are calculated + int8_t m_sunriseOffset = 0; // offset from sunrise, in minutes, when macro should be run (negative for before sunrise, positive for after sunrise) + int8_t m_sunsetOffset = 0; // offset from sunset, in minutes, when macro should be run (negative for before sunset, positive for after sunset) + uint8_t m_sunriseMacro = 15; // macro number to run at sunrise + uint8_t m_sunsetMacro = 16; // macro number to run at sunset + + /**** END OF USER SETTINGS. DO NOT EDIT BELOW THIS LINE! ****/ + + + Dusk2Dawn *m_pD2D = NULL; // this must be dynamically allocated in order for parameters to be loaded from EEPROM + + int m_nUserSunrise = -1; // time, in minutes from midnight, of sunrise + int m_nUserSunset = -1; // time, in minutes from midnight, of sunset + + byte m_nLastRunMinute = -1; // indicates what minute the userloop was last run - used so that the code only runs once per minute + +public: + + virtual void setup(void) + { + /* TODO: From EEPROM, load the following variables: + * + * int16_t latitude16 = 4060; // user provided latitude, multiplied by 100 and rounded + * int16_t longitude16 = -7980; // user provided longitude, multiplied by 100 and rounded. + * int8_t sunrise_offset = 0; // number of minutes to offset the sunrise macro trigger (positive for minutes after sunrise, negative for minutes before) + * int8_t sunset_offset = 0; // number of minutes to offset the sunset macro trigger (positive for minutes after sunset, negative for minutes before) + * + * then: + * m_fLatitude = (float)latitude / 100.0; + * m_fLongitude = (float)longitude / 100.0; + * m_sunriseOffset = sunrise_offset; + * m_sunsetOffset = sunset_offset; + */ + + if ((0.0 != m_fLatitude) || (0.0 != m_fLongitude)) + { + m_pD2D = new Dusk2Dawn (m_fLatitude, m_fLongitude, 0 /* UTC */); + // can't really check for failures. if the alloc fails, the mod just doesn't work. + } + } + + void loop(void) + { + // without NTP, or a configured lat/long, none of this stuff is going to work... + // As an alternative, need to figure out how to determine if the user has manually set the clock or not. + if (m_pD2D && (999000000L != ntpLastSyncTime)) + { + // to prevent needing to import all the timezone stuff from other modules, work completely in UTC + time_t timeUTC = now(); + tmElements_t tmNow; + breakTime(timeUTC, tmNow); + int nCurMinute = tmNow.Minute; + + if (m_nLastRunMinute != nCurMinute) //only check once a new minute begins + { + m_nLastRunMinute = nCurMinute; + int numMinutes = (60 * tmNow.Hour) + m_nLastRunMinute; // how many minutes into the day are we? + + // check to see if sunrise/sunset should be re-determined. Only do this if neither sunrise nor sunset + // are set. That happens when the device has just stated, and after both sunrise/sunset have already run. + if ((-1 == m_nUserSunrise) && (-1 == m_nUserSunset)) + { + m_nUserSunrise = m_pD2D->sunrise(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; + m_nUserSunset = m_pD2D->sunset(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; + if (m_nUserSunrise > numMinutes) // has sunrise already passed? if so, recompute for tomorrow + { + breakTime(timeUTC + (60*60*24), tmNow); + m_nUserSunrise = m_pD2D->sunrise(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; + if (m_nUserSunset > numMinutes) // if sunset has also passed, recompute that as well + { + m_nUserSunset = m_pD2D->sunset(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; + } + } + // offset by user provided values. becuase the offsets are signed bytes, the max offset is just over 2 hours. + m_nUserSunrise += m_sunriseOffset; + m_nUserSunset += m_sunsetOffset; + } + + if (numMinutes == m_nUserSunrise) // Good Morning! + { + if (m_sunriseMacro) + applyMacro(m_sunriseMacro); // run macro 15 + m_nUserSunrise = -1; + } + else if (numMinutes == m_nUserSunset) // Good Night! + { + if (m_sunsetMacro) + applyMacro(m_sunsetMacro); // run macro 16 + m_nUserSunset = -1; + } + } // if (m_nLastRunMinute != nCurMinute) + } // if (m_pD2D && (999000000L != ntpLastSyncTime)) + } + + void addToJsonState(JsonObject& root) + { + JsonObject user = root["SunRiseAndSet"]; + if (user.isNull()) user = root.createNestedObject("SunRiseAndSet"); + + char buf[10]; + if (-1 != m_nUserSunrise) + { + snprintf(buf, 10, "%02d:%02d UTC", m_nUserSunrise / 60, m_nUserSunrise % 60); + user["rise"] = buf; + } + if (-1 != m_nUserSunset) + { + snprintf(buf, 10, "%02d:%02d UTC", m_nUserSunset / 60, m_nUserSunset % 60); + user["set"] = buf; + } + JsonObject vars = user.createNestedObject("vars"); + vars["lat"] = m_fLatitude; + vars["long"] = m_fLongitude; + vars["rise_mac"] = m_sunriseMacro; + vars["set_mac"] = m_sunsetMacro; + vars["rise_off"] = m_sunriseOffset; + vars["set_off"] = m_sunsetOffset; + } + + ~UserMod_SunRiseAndSet(void) + { + if (m_pD2D) delete m_pD2D; + } +}; + + + diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md new file mode 100644 index 000000000..eebc50da2 --- /dev/null +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -0,0 +1,71 @@ +# Wemos D1 mini and Wemos32 mini shield +- Installation of file: Copy and replace file in wled00 directory +- For BME280 sensor use usermod_bme280.cpp. Copy to wled00 and rename to usermod.cpp +- Added third choice of controller Heltec WiFi-Kit-8. Totally DIY but with OLED display. +## Project repository +- [Original repository](https://github.com/srg74/WLED-wemos-shield) - WLED Wemos shield repository +- [Wemos shield project Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) +- [Precompiled WLED firmware](https://github.com/srg74/WLED-wemos-shield/tree/master/resources/Firmware) +## Features +- SSD1306 128x32 or 128x64 I2C OLED display +- On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) +- Auto display shutoff for saving display lifetime +- Dallas temperature sensor +- Reporting temperature to MQTT broker +- Relay for energy saving + +## Hardware +![Shield](https://github.com/srg74/WLED-wemos-shield/blob/master/resources/Images/Assembly_8.jpg) + +## Functionality checked with + +- Wemos D1 mini original v3.1 and clones +- Wemos32 mini +- PlatformIO +- SSD1306 128x32 I2C OLED display +- DS18B20 (temperature sensor) +- BME280 (temperature, humidity and pressure sensor) +- Push button (N.O. momentary switch) + +### Platformio requirements + +For Dallas sensor uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: +```ini +# platformio.ini +... +[platformio] +... +; default_envs = esp07 +default_envs = d1_mini +... +[common] +... +lib_deps_external = + ... + #For use SSD1306 OLED display uncomment following + U8g2@~2.27.3 + #For Dallas sensor uncomment following 2 lines + DallasTemperature@~3.8.0 + OneWire@~2.3.5 +... +``` + +For BME280 sensor uncomment `U8g2@~2.27.3`,`BME280@~3.0.0 under` `[common]` section in `platformio.ini`: +```ini +# platformio.ini +... +[platformio] +... +; default_envs = esp07 +default_envs = d1_mini +... +[common] +... +lib_deps_external = + ... + #For use SSD1306 OLED display uncomment following + U8g2@~2.27.3 + #For BME280 sensor uncomment following + BME280@~3.0.0 +... +``` diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp new file mode 100644 index 000000000..a93b20c99 --- /dev/null +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp @@ -0,0 +1,246 @@ +#include "wled.h" +#include +#include // from https://github.com/olikraus/u8g2/ +#include // Dallas temperature sensor + +//Dallas sensor quick reading. Credit to - Author: Peter Scargill, August 17th, 2013 +int16_t Dallas(int x, byte start) +{ + OneWire DallasSensor(x); + byte i; + byte data[2]; + int16_t result; + do + { + DallasSensor.reset(); + DallasSensor.write(0xCC); + DallasSensor.write(0xBE); + for ( i = 0; i < 2; i++) data[i] = DallasSensor.read(); + result=(data[1]<<8)|data[0]; + result>>=4; if (data[1]&128) result|=61440; + if (data[0]&8) ++result; + DallasSensor.reset(); + DallasSensor.write(0xCC); + DallasSensor.write(0x44,1); + if (start) delay(1000); + } while (start--); + return result; +} +#ifdef ARDUINO_ARCH_ESP32 +uint8_t SCL_PIN = 22; +uint8_t SDA_PIN = 21; +uint8_t DALLAS_PIN =23; +#else +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 +#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 + +// Dallas sensor reading timer +long temptimer = millis(); +long lastMeasure = 0; +#define Celsius // Show temperature mesaurement in Celcius 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" +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" +//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" +//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() { +//Serial.begin(115200); + + Dallas (DALLAS_PIN,1); + u8x8.begin(); + u8x8.setPowerSave(0); + u8x8.setFlipMode(1); + u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 + u8x8.setFont(u8x8_font_chroma48medium8_r); + u8x8.drawString(0, 0, "Loading..."); +} + +// gets called every time WiFi is (re-)connected. Initialize own network +// interfaces here +void userConnected() {} + +// needRedraw marks if redraw is required to prevent often redrawing. +bool needRedraw = true; + +// Next variables hold the previous known values to determine if redraw is +// required. +String knownSsid = ""; +IPAddress knownIp; +uint8_t knownBrightness = 0; +uint8_t knownMode = 0; +uint8_t knownPalette = 0; + +long lastUpdate = 0; +long lastRedraw = 0; +bool displayTurnedOff = false; +// How often we are redrawing screen +#define USER_LOOP_REFRESH_RATE_MS 5000 + +void userLoop() { + +//----> Dallas temperature sensor MQTT publishing + temptimer = millis(); +// Timer to publishe new temperature every 60 seconds + if (temptimer - lastMeasure > 60000) + { + lastMeasure = temptimer; +//Check if MQTT Connected, otherwise it will crash the 8266 + if (mqtt != nullptr) + { +// Serial.println(Dallas(DALLAS_PIN,0)); +//Gets prefered temperature scale based on selection in definitions section + #ifdef Celsius + int16_t board_temperature = Dallas(DALLAS_PIN,0); + #else + int16_t board_temperature = (Dallas(DALLAS_PIN,0)* 1.8 + 32); + #endif +//Create character string populated with user defined device topic from the UI, and the read temperature. Then publish to MQTT server. + String t = String(mqttDeviceTopic); + t += "/temperature"; + mqtt->publish(t.c_str(), 0, true, String(board_temperature).c_str()); + } + } + + // Check if we time interval for redrawing passes. + if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + return; + } + lastUpdate = millis(); + + // Turn off display after 3 minutes with no change. + if(!displayTurnedOff && millis() - lastRedraw > 3*60*1000) { + u8x8.setPowerSave(1); + displayTurnedOff = true; + } + + // Check if values which are shown on display changed from the last time. + if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { + needRedraw = true; + } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { + needRedraw = true; + } else if (knownBrightness != bri) { + needRedraw = true; + } else if (knownMode != strip.getMode()) { + needRedraw = true; + } else if (knownPalette != strip.getSegment(0).palette) { + needRedraw = true; + } + + if (!needRedraw) { + return; + } + needRedraw = false; + + if (displayTurnedOff) + { + u8x8.setPowerSave(0); + displayTurnedOff = false; + } + lastRedraw = millis(); + + // Update last known values. + #ifdef ARDUINO_ARCH_ESP32 + knownSsid = WiFi.SSID(); + #else + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + #endif + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + knownBrightness = bri; + knownMode = strip.getMode(); + knownPalette = strip.getSegment(0).palette; + u8x8.clear(); + u8x8.setFont(u8x8_font_chroma48medium8_r); + + // 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 + if (knownSsid.length() > u8x8.getCols()) + u8x8.print("~"); + + // Second row with IP or Psssword + u8x8.setCursor(1, 1); + // Print password in AP mode and if led is OFF. + if (apActive && bri == 0) + u8x8.print(apPass); + else + u8x8.print(knownIp); + + // Third row with mode name + u8x8.setCursor(2, 2); + uint8_t qComma = 0; + bool insideQuotes = false; + uint8_t printedChars = 0; + char singleJsonSymbol; + + // Find the mode name in JSON + for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownMode)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + // Fourth row with palette name + u8x8.setCursor(2, 3); + qComma = 0; + insideQuotes = false; + printedChars = 0; + // Looking for palette name in JSON. + for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownPalette)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + + u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); + u8x8.drawGlyph(0, 0, 80); // wifi icon + u8x8.drawGlyph(0, 1, 68); // home icon + u8x8.setFont(u8x8_font_open_iconic_weather_2x2); + u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon +} diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp new file mode 100644 index 000000000..15ec58add --- /dev/null +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp @@ -0,0 +1,268 @@ +#include "wled.h" +#include +#include // from https://github.com/olikraus/u8g2/ +#include +#include //BME280 sensor + +void UpdateBME280Data(); + +#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit +BME280I2C bme; // Default : forced mode, standby time = 1000 ms + // Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off, + +#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards +uint8_t SCL_PIN = 22; +uint8_t SDA_PIN = 21; +#else //ESP8266 boards +uint8_t SCL_PIN = 5; +uint8_t SDA_PIN = 4; +// uint8_t RST_PIN = 16; // Uncoment 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 + +// 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" +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" +//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" +//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 + +// BME280 sensor timer +long tempTimer = millis(); +long lastMeasure = 0; + +float SensorPressure(NAN); +float SensorTemperature(NAN); +float SensorHumidity(NAN); + +void userSetup() { + u8x8.begin(); + u8x8.setPowerSave(0); + u8x8.setFlipMode(1); + u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 + u8x8.setFont(u8x8_font_chroma48medium8_r); + u8x8.drawString(0, 0, "Loading..."); + Wire.begin(SDA_PIN,SCL_PIN); + +while(!bme.begin()) + { + Serial.println("Could not find BME280I2C sensor!"); + delay(1000); + } +switch(bme.chipModel()) + { + case BME280::ChipModel_BME280: + Serial.println("Found BME280 sensor! Success."); + break; + case BME280::ChipModel_BMP280: + Serial.println("Found BMP280 sensor! No Humidity available."); + break; + default: + Serial.println("Found UNKNOWN sensor! Error!"); + } +} + +// gets called every time WiFi is (re-)connected. Initialize own network +// interfaces here +void userConnected() {} + +// needRedraw marks if redraw is required to prevent often redrawing. +bool needRedraw = true; + +// Next variables hold the previous known values to determine if redraw is +// required. +String knownSsid = ""; +IPAddress knownIp; +uint8_t knownBrightness = 0; +uint8_t knownMode = 0; +uint8_t knownPalette = 0; + +long lastUpdate = 0; +long lastRedraw = 0; +bool displayTurnedOff = false; +// How often we are redrawing screen +#define USER_LOOP_REFRESH_RATE_MS 5000 + +void userLoop() { + +// BME280 sensor MQTT publishing + tempTimer = millis(); +// Timer to publish new sensor data every 60 seconds + if (tempTimer - lastMeasure > 60000) + { + lastMeasure = tempTimer; + +// Check if MQTT Connected, otherwise it will crash the 8266 + if (mqtt != nullptr) + { + UpdateBME280Data(); + float board_temperature = SensorTemperature; + float board_pressure = SensorPressure; + float board_humidity = SensorHumidity; + +// Create string populated with user defined device topic from the UI, and the read temperature, humidity and pressure. Then publish to MQTT server. + String t = String(mqttDeviceTopic); + t += "/temperature"; + mqtt->publish(t.c_str(), 0, true, String(board_temperature).c_str()); + String p = String(mqttDeviceTopic); + p += "/pressure"; + mqtt->publish(p.c_str(), 0, true, String(board_pressure).c_str()); + String h = String(mqttDeviceTopic); + h += "/humidity"; + mqtt->publish(h.c_str(), 0, true, String(board_humidity).c_str()); + } + } + + // Check if we time interval for redrawing passes. + if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + return; + } + lastUpdate = millis(); + + // Turn off display after 3 minutes with no change. + if(!displayTurnedOff && millis() - lastRedraw > 3*60*1000) { + u8x8.setPowerSave(1); + displayTurnedOff = true; + } + + // Check if values which are shown on display changed from the last time. + if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { + needRedraw = true; + } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { + needRedraw = true; + } else if (knownBrightness != bri) { + needRedraw = true; + } else if (knownMode != strip.getMode()) { + needRedraw = true; + } else if (knownPalette != strip.getSegment(0).palette) { + needRedraw = true; + } + + if (!needRedraw) { + return; + } + needRedraw = false; + + if (displayTurnedOff) + { + u8x8.setPowerSave(0); + displayTurnedOff = false; + } + lastRedraw = millis(); + + // Update last known values. + #if defined(ESP8266) + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + #else + knownSsid = WiFi.SSID(); + #endif + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + knownBrightness = bri; + knownMode = strip.getMode(); + knownPalette = strip.getSegment(0).palette; + u8x8.clear(); + u8x8.setFont(u8x8_font_chroma48medium8_r); + + // 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 + if (knownSsid.length() > u8x8.getCols()) + u8x8.print("~"); + + // Second row with IP or Psssword + u8x8.setCursor(1, 1); + // Print password in AP mode and if led is OFF. + if (apActive && bri == 0) + u8x8.print(apPass); + else + u8x8.print(knownIp); + + // Third row with mode name + u8x8.setCursor(2, 2); + uint8_t qComma = 0; + bool insideQuotes = false; + uint8_t printedChars = 0; + char singleJsonSymbol; + + // Find the mode name in JSON + for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownMode)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + // Fourth row with palette name + u8x8.setCursor(2, 3); + qComma = 0; + insideQuotes = false; + printedChars = 0; + // Looking for palette name in JSON. + for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownPalette)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + + u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); + u8x8.drawGlyph(0, 0, 80); // wifi icon + u8x8.drawGlyph(0, 1, 68); // home icon + u8x8.setFont(u8x8_font_open_iconic_weather_2x2); + u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon +} + +void UpdateBME280Data() { + float temp(NAN), hum(NAN), pres(NAN); +#ifdef Celsius + BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); + BME280::PresUnit presUnit(BME280::PresUnit_Pa); + bme.read(pres, temp, hum, tempUnit, presUnit); +#else + BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit); + BME280::PresUnit presUnit(BME280::PresUnit_Pa); + bme.read(pres, temp, hum, tempUnit, presUnit); +#endif + SensorTemperature=temp; + SensorHumidity=hum; + SensorPressure=pres; +} diff --git a/usermods/battery_keypad_controller/README.md b/usermods/battery_keypad_controller/README.md new file mode 100644 index 000000000..77e3c1e46 --- /dev/null +++ b/usermods/battery_keypad_controller/README.md @@ -0,0 +1,12 @@ +# Battery powered controller with keypad + +I'm using this controller for a festival totem. Runs on 3 18650 Cells, can deliver >5A current. + +Via keypad one can select 8 presets, change effect, effect speed, effect intensity and palette. Brightness can be +adjusted with a potentiometer. + +## Pictures + +![bat-key-ctrl-1](assets/bat-key-ctrl-1.jpg) +![bat-key-ctrl-2](assets/bat-key-ctrl-2.jpg) +![bat-key-ctrl-3](assets/bat-key-ctrl-3.jpg) diff --git a/usermods/battery_keypad_controller/assets/bat-key-ctrl-1.jpg b/usermods/battery_keypad_controller/assets/bat-key-ctrl-1.jpg new file mode 100644 index 000000000..459151e8d Binary files /dev/null and b/usermods/battery_keypad_controller/assets/bat-key-ctrl-1.jpg differ diff --git a/usermods/battery_keypad_controller/assets/bat-key-ctrl-2.jpg b/usermods/battery_keypad_controller/assets/bat-key-ctrl-2.jpg new file mode 100644 index 000000000..b89302a8c Binary files /dev/null and b/usermods/battery_keypad_controller/assets/bat-key-ctrl-2.jpg differ diff --git a/usermods/battery_keypad_controller/assets/bat-key-ctrl-3.jpg b/usermods/battery_keypad_controller/assets/bat-key-ctrl-3.jpg new file mode 100644 index 000000000..6e2a5bf00 Binary files /dev/null and b/usermods/battery_keypad_controller/assets/bat-key-ctrl-3.jpg differ diff --git a/usermods/battery_keypad_controller/wled06_usermod.ino b/usermods/battery_keypad_controller/wled06_usermod.ino new file mode 100644 index 000000000..877713b61 --- /dev/null +++ b/usermods/battery_keypad_controller/wled06_usermod.ino @@ -0,0 +1,151 @@ +/* + * WLED usermod for keypad and brightness-pot. + * 3'2020 https://github.com/hobbyquaker + */ + +#include +const byte keypad_rows = 4; +const byte keypad_cols = 4; +char keypad_keys[keypad_rows][keypad_cols] = { + {'1', '2', '3', 'A'}, + {'4', '5', '6', 'B'}, + {'7', '8', '9', 'C'}, + {'*', '0', '#', 'D'} +}; + +byte keypad_colPins[keypad_rows] = {D3, D2, D1, D0}; +byte keypad_rowPins[keypad_cols] = {D7, D6, D5, D4}; + +Keypad myKeypad = Keypad(makeKeymap(keypad_keys), keypad_rowPins, keypad_colPins, keypad_rows, keypad_cols); + +void userSetup() +{ + +} + +void userConnected() +{ + +} + +long lastTime = 0; +int delayMs = 20; //we want to do something every 2 seconds + +void userLoop() +{ + if (millis()-lastTime > delayMs) + { + + long analog = analogRead(0); + int new_bri = 1; + if (analog > 900) { + new_bri = 255; + } else if (analog > 30) { + new_bri = dim8_video(map(analog, 31, 900, 16, 255)); + } + if (bri != new_bri) { + bri = new_bri; + colorUpdated(1); + + } + + char myKey = myKeypad.getKey(); + if (myKey != NULL) { + switch (myKey) { + case '1': + applyPreset(1); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case '2': + applyPreset(2); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case '3': + applyPreset(3); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case '4': + applyPreset(4); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case '5': + applyPreset(5); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case '6': + applyPreset(6); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case 'A': + applyPreset(7); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case 'B': + applyPreset(8); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + + case '7': + effectCurrent += 1; + if (effectCurrent >= MODE_COUNT) effectCurrent = 0; + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case '*': + effectCurrent -= 1; + if (effectCurrent < 0) effectCurrent = (MODE_COUNT-1); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + + case '8': + if (effectSpeed < 240) { + effectSpeed += 12; + } else if (effectSpeed < 255) { + effectSpeed += 1; + } + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case '0': + if (effectSpeed > 15) { + effectSpeed -= 12; + } else if (effectSpeed > 0) { + effectSpeed -= 1; + } + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + + case '9': + if (effectIntensity < 240) { + effectIntensity += 12; + } else if (effectIntensity < 255) { + effectIntensity += 1; + } + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case '#': + if (effectIntensity > 15) { + effectIntensity -= 12; + } else if (effectIntensity > 0) { + effectIntensity -= 1; + } + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + + case 'C': + effectPalette += 1; + if (effectPalette >= 50) effectPalette = 0; + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + case 'D': + effectPalette -= 1; + if (effectPalette <= 0) effectPalette = 50; + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + break; + + } + + } + + lastTime = millis(); + } + +} \ No newline at end of file diff --git a/usermods/blynk_relay_control/wled06_usermod.ino b/usermods/blynk_relay_control/wled06_usermod.ino index 1004b1ed3..d4028ea5d 100644 --- a/usermods/blynk_relay_control/wled06_usermod.ino +++ b/usermods/blynk_relay_control/wled06_usermod.ino @@ -1,7 +1,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 wled01_eeprom.h) + * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) * bytes 2400+ are currently ununsed, but might be used for future wled features */ diff --git a/usermods/buzzer/usermod_v2_buzzer.h b/usermods/buzzer/usermod_v2_buzzer.h new file mode 100644 index 000000000..ebd8dcb15 --- /dev/null +++ b/usermods/buzzer/usermod_v2_buzzer.h @@ -0,0 +1,81 @@ +#pragma once + +#include "wled.h" +#include "Arduino.h" + +#include + +#define USERMOD_ID_BUZZER 900 +#ifndef USERMOD_BUZZER_PIN +#define USERMOD_BUZZER_PIN GPIO_NUM_32 +#endif + +/* + * Usermods allow you to add own functionality to WLED more easily + * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality + * + * Using a 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 + */ + +class BuzzerUsermod : public Usermod { + private: + unsigned long lastTime_ = 0; + unsigned long delay_ = 0; + std::deque> sequence_ {}; + 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() { + // Setup the pin, and default to LOW + pinMode(USERMOD_BUZZER_PIN, OUTPUT); + digitalWrite(USERMOD_BUZZER_PIN, LOW); + + // Beep on startup + sequence_.push_back({ HIGH, 50 }); + sequence_.push_back({ LOW, 0 }); + } + + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() { + // Double beep on WiFi + sequence_.push_back({ LOW, 100 }); + sequence_.push_back({ HIGH, 50 }); + sequence_.push_back({ LOW, 30 }); + sequence_.push_back({ HIGH, 50 }); + sequence_.push_back({ LOW, 0 }); + } + + /* + * loop() is called continuously. Here you can check for events, read sensors, etc. + */ + void loop() { + if (sequence_.size() < 1) return; // Wait until there is a sequence + if (millis() - lastTime_ <= delay_) return; // Wait until delay has elapsed + + auto event = sequence_.front(); + sequence_.pop_front(); + + digitalWrite(USERMOD_BUZZER_PIN, event.first); + delay_ = event.second; + + lastTime_ = millis(); + } + + + /* + * 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_BUZZER; + } +}; \ No newline at end of file diff --git a/usermods/esp32_multistrip/NpbWrapper.h b/usermods/esp32_multistrip/NpbWrapper.h new file mode 100644 index 000000000..84cf8ac0d --- /dev/null +++ b/usermods/esp32_multistrip/NpbWrapper.h @@ -0,0 +1,515 @@ +//this code is a modified version of https://github.com/Makuna/NeoPixelBus/issues/103 +#ifndef NpbWrapper_h +#define NpbWrapper_h + +// make sure we're using esp32 platform +#ifndef ARDUINO_ARCH_ESP32 + #error This version of NbpWrapper.h only works with ESP32 hardware. +#endif + +#ifndef NUM_STRIPS + #error Need to define number of LED strips using build flag -D NUM_STRIPS=4 for 4 LED strips +#endif + +#ifndef PIXEL_COUNTS + #error Need to define pixel counts using build flag -D PIXEL_COUNTS="25, 25, 25, 25" for 4 LED strips with 25 LEDs each +#endif + +#ifndef DATA_PINS + #error Need to define data pins using build flag -D DATA_PINS="1, 2, 3, 4" if LED strips are on data pins 1, 2, 3, and 4 +#endif + +// //PIN CONFIGURATION +#ifndef LEDPIN + #define LEDPIN 1 // Legacy pin def required by some other portions of code. This pin is not used do drive LEDs. +#endif + +#ifndef IRPIN + #define IRPIN -1 //infrared pin (-1 to disable) MagicHome: 4, H801 Wifi: 0 +#endif + +#ifndef RLYPIN + #define RLYPIN -1 //pin for relay, will be set HIGH if LEDs are on (-1 to disable). Also usable for standby leds, triggers,... +#endif + +#ifndef AUXPIN + #define AUXPIN -1 //debug auxiliary output pin (-1 to disable) +#endif + +#ifndef RLYMDE + #define RLYMDE 1 //mode for relay, 0: LOW if LEDs are on 1: HIGH if LEDs are on +#endif + +#include +#include "const.h" + +const uint8_t numStrips = NUM_STRIPS; // max 8 strips allowed on esp32 +const uint16_t pixelCounts[numStrips] = {PIXEL_COUNTS}; // number of pixels on each strip +const uint8_t dataPins[numStrips] = {DATA_PINS}; // change these pins based on your board + +#define PIXELFEATURE3 NeoGrbFeature +#define PIXELFEATURE4 NeoGrbwFeature + +// ESP32 has 8 RMT interfaces available, each of which can drive a strip of pixels +// Convenience #defines for creating NeoPixelBrightnessBus on each RMT interface for both GRB and GRBW LED strips +#define NeoPixelBrightnessBusGrbRmt0 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbRmt1 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbRmt2 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbRmt3 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbRmt4 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbRmt5 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbRmt6 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbRmt7 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbwRmt0 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbwRmt1 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbwRmt2 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbwRmt3 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbwRmt4 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbwRmt5 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbwRmt6 NeoPixelBrightnessBus +#define NeoPixelBrightnessBusGrbwRmt7 NeoPixelBrightnessBus + +enum NeoPixelType +{ + NeoPixelType_None = 0, + NeoPixelType_Grb = 1, + NeoPixelType_Grbw = 2, + NeoPixelType_End = 3 +}; + +class NeoPixelWrapper +{ +public: + NeoPixelWrapper() : + _type(NeoPixelType_None) + { + // On initialization fill in the pixelStripStartIdx array with the beginning index of each strip + // relative to th entire array. + uint16_t totalPixels = 0; + for (uint8_t idx = 0; idx < numStrips; idx++) + { + pixelStripStartIdx[idx] = totalPixels; + totalPixels += pixelCounts[idx]; + } + } + + ~NeoPixelWrapper() + { + cleanup(); + } + + void Begin(NeoPixelType type, uint16_t pixelCount) + { + + cleanup(); + + _type = type; + + switch (_type) + { + case NeoPixelType_Grb: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: pGrb0 = new NeoPixelBrightnessBusGrbRmt0(pixelCounts[idx], dataPins[idx]); pGrb0->Begin(); break; + case 1: pGrb1 = new NeoPixelBrightnessBusGrbRmt1(pixelCounts[idx], dataPins[idx]); pGrb1->Begin(); break; + case 2: pGrb2 = new NeoPixelBrightnessBusGrbRmt2(pixelCounts[idx], dataPins[idx]); pGrb2->Begin(); break; + case 3: pGrb3 = new NeoPixelBrightnessBusGrbRmt3(pixelCounts[idx], dataPins[idx]); pGrb3->Begin(); break; + case 4: pGrb4 = new NeoPixelBrightnessBusGrbRmt4(pixelCounts[idx], dataPins[idx]); pGrb4->Begin(); break; + case 5: pGrb5 = new NeoPixelBrightnessBusGrbRmt5(pixelCounts[idx], dataPins[idx]); pGrb5->Begin(); break; + case 6: pGrb6 = new NeoPixelBrightnessBusGrbRmt6(pixelCounts[idx], dataPins[idx]); pGrb6->Begin(); break; + case 7: pGrb7 = new NeoPixelBrightnessBusGrbRmt7(pixelCounts[idx], dataPins[idx]); pGrb7->Begin(); break; + } + } + break; + } + + case NeoPixelType_Grbw: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: pGrbw0 = new NeoPixelBrightnessBusGrbwRmt0(pixelCounts[idx], dataPins[idx]); pGrbw0->Begin(); break; + case 1: pGrbw1 = new NeoPixelBrightnessBusGrbwRmt1(pixelCounts[idx], dataPins[idx]); pGrbw1->Begin(); break; + case 2: pGrbw2 = new NeoPixelBrightnessBusGrbwRmt2(pixelCounts[idx], dataPins[idx]); pGrbw2->Begin(); break; + case 3: pGrbw3 = new NeoPixelBrightnessBusGrbwRmt3(pixelCounts[idx], dataPins[idx]); pGrbw3->Begin(); break; + case 4: pGrbw4 = new NeoPixelBrightnessBusGrbwRmt4(pixelCounts[idx], dataPins[idx]); pGrbw4->Begin(); break; + case 5: pGrbw5 = new NeoPixelBrightnessBusGrbwRmt5(pixelCounts[idx], dataPins[idx]); pGrbw5->Begin(); break; + case 6: pGrbw6 = new NeoPixelBrightnessBusGrbwRmt6(pixelCounts[idx], dataPins[idx]); pGrbw6->Begin(); break; + case 7: pGrbw7 = new NeoPixelBrightnessBusGrbwRmt7(pixelCounts[idx], dataPins[idx]); pGrbw7->Begin(); break; + } + } + break; + } + } + } + + void Show() + { + switch (_type) + { + case NeoPixelType_Grb: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: pGrb0->Show(); break; + case 1: pGrb1->Show(); break; + case 2: pGrb2->Show(); break; + case 3: pGrb3->Show(); break; + case 4: pGrb4->Show(); break; + case 5: pGrb5->Show(); break; + case 6: pGrb6->Show(); break; + case 7: pGrb7->Show(); break; + } + } + break; + } + case NeoPixelType_Grbw: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: pGrbw0->Show(); break; + case 1: pGrbw1->Show(); break; + case 2: pGrbw2->Show(); break; + case 3: pGrbw3->Show(); break; + case 4: pGrbw4->Show(); break; + case 5: pGrbw5->Show(); break; + case 6: pGrbw6->Show(); break; + case 7: pGrbw7->Show(); break; + } + } + break; + } + } + } + + bool CanShow() + { + bool canShow = true; + switch (_type) + { + case NeoPixelType_Grb: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: canShow &= pGrb0->CanShow(); break; + case 1: canShow &= pGrb1->CanShow(); break; + case 2: canShow &= pGrb2->CanShow(); break; + case 3: canShow &= pGrb3->CanShow(); break; + case 4: canShow &= pGrb4->CanShow(); break; + case 5: canShow &= pGrb5->CanShow(); break; + case 6: canShow &= pGrb6->CanShow(); break; + case 7: canShow &= pGrb7->CanShow(); break; + } + } + break; + } + case NeoPixelType_Grbw: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: canShow &= pGrbw0->CanShow(); break; + case 1: canShow &= pGrbw1->CanShow(); break; + case 2: canShow &= pGrbw2->CanShow(); break; + case 3: canShow &= pGrbw3->CanShow(); break; + case 4: canShow &= pGrbw4->CanShow(); break; + case 5: canShow &= pGrbw5->CanShow(); break; + case 6: canShow &= pGrbw6->CanShow(); break; + case 7: canShow &= pGrbw7->CanShow(); break; + } + } + break; + } + } + return canShow; + } + + void SetPixelColorRaw(uint16_t indexPixel, RgbwColor c) + { + // figure out which strip this pixel index is on + uint8_t stripIdx = 0; + for (uint8_t idx = 0; idx < numStrips; idx++) + { + if (indexPixel >= pixelStripStartIdx[idx]) + { + stripIdx = idx; + } + else + { + break; + } + } + // subtract strip start index so we're addressing just this strip instead of all pixels on all strips + indexPixel -= pixelStripStartIdx[stripIdx]; + switch (_type) + { + case NeoPixelType_Grb: + { + RgbColor rgb = RgbColor(c.R, c.G, c.B); + switch (stripIdx) + { + case 0: pGrb0->SetPixelColor(indexPixel, rgb); break; + case 1: pGrb1->SetPixelColor(indexPixel, rgb); break; + case 2: pGrb2->SetPixelColor(indexPixel, rgb); break; + case 3: pGrb3->SetPixelColor(indexPixel, rgb); break; + case 4: pGrb4->SetPixelColor(indexPixel, rgb); break; + case 5: pGrb5->SetPixelColor(indexPixel, rgb); break; + case 6: pGrb6->SetPixelColor(indexPixel, rgb); break; + case 7: pGrb7->SetPixelColor(indexPixel, rgb); break; + } + break; + } + case NeoPixelType_Grbw: + { + switch (stripIdx) + { + case 0: pGrbw0->SetPixelColor(indexPixel, c); break; + case 1: pGrbw1->SetPixelColor(indexPixel, c); break; + case 2: pGrbw2->SetPixelColor(indexPixel, c); break; + case 3: pGrbw3->SetPixelColor(indexPixel, c); break; + case 4: pGrbw4->SetPixelColor(indexPixel, c); break; + case 5: pGrbw5->SetPixelColor(indexPixel, c); break; + case 6: pGrbw6->SetPixelColor(indexPixel, c); break; + case 7: pGrbw7->SetPixelColor(indexPixel, c); break; + } + break; + } + } + } + + void SetPixelColor(uint16_t indexPixel, RgbwColor c) + { + /* + Set pixel color with necessary color order conversion. + */ + + RgbwColor col; + + uint8_t co = _colorOrder; + #ifdef COLOR_ORDER_OVERRIDE + if (indexPixel >= COO_MIN && indexPixel < COO_MAX) co = COO_ORDER; + #endif + + //reorder channels to selected order + switch (co) + { + case 0: col.G = c.G; col.R = c.R; col.B = c.B; break; //0 = GRB, default + case 1: col.G = c.R; col.R = c.G; col.B = c.B; break; //1 = RGB, common for WS2811 + case 2: col.G = c.B; col.R = c.R; col.B = c.G; break; //2 = BRG + case 3: col.G = c.R; col.R = c.B; col.B = c.G; break; //3 = RBG + case 4: col.G = c.B; col.R = c.G; col.B = c.R; break; //4 = BGR + default: col.G = c.G; col.R = c.B; col.B = c.R; break; //5 = GBR + } + col.W = c.W; + + SetPixelColorRaw(indexPixel, col); + } + + void SetBrightness(byte b) + { + switch (_type) + { + case NeoPixelType_Grb: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: pGrb0->SetBrightness(b); break; + case 1: pGrb1->SetBrightness(b); break; + case 2: pGrb2->SetBrightness(b); break; + case 3: pGrb3->SetBrightness(b); break; + case 4: pGrb4->SetBrightness(b); break; + case 5: pGrb5->SetBrightness(b); break; + case 6: pGrb6->SetBrightness(b); break; + case 7: pGrb7->SetBrightness(b); break; + } + } + break; + } + case NeoPixelType_Grbw: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: pGrbw0->SetBrightness(b); break; + case 1: pGrbw1->SetBrightness(b); break; + case 2: pGrbw2->SetBrightness(b); break; + case 3: pGrbw3->SetBrightness(b); break; + case 4: pGrbw4->SetBrightness(b); break; + case 5: pGrbw5->SetBrightness(b); break; + case 6: pGrbw6->SetBrightness(b); break; + case 7: pGrbw7->SetBrightness(b); break; + } + } + break; + } + } + } + + void SetColorOrder(byte colorOrder) + { + _colorOrder = colorOrder; + } + + uint8_t GetColorOrder() + { + return _colorOrder; + } + + RgbwColor GetPixelColorRaw(uint16_t indexPixel) const + { + // figure out which strip this pixel index is on + uint8_t stripIdx = 0; + for (uint8_t idx = 0; idx < numStrips; idx++) + { + if (indexPixel >= pixelStripStartIdx[idx]) + { + stripIdx = idx; + } + else + { + break; + } + } + // subtract strip start index so we're addressing just this strip instead of all pixels on all strips + indexPixel -= pixelStripStartIdx[stripIdx]; + switch (_type) + { + case NeoPixelType_Grb: + { + switch (stripIdx) + { + case 0: return pGrb0->GetPixelColor(indexPixel); + case 1: return pGrb1->GetPixelColor(indexPixel); + case 2: return pGrb2->GetPixelColor(indexPixel); + case 3: return pGrb3->GetPixelColor(indexPixel); + case 4: return pGrb4->GetPixelColor(indexPixel); + case 5: return pGrb5->GetPixelColor(indexPixel); + case 6: return pGrb6->GetPixelColor(indexPixel); + case 7: return pGrb7->GetPixelColor(indexPixel); + } + break; + } + case NeoPixelType_Grbw: + switch (stripIdx) + { + case 0: return pGrbw0->GetPixelColor(indexPixel); + case 1: return pGrbw1->GetPixelColor(indexPixel); + case 2: return pGrbw2->GetPixelColor(indexPixel); + case 3: return pGrbw3->GetPixelColor(indexPixel); + case 4: return pGrbw4->GetPixelColor(indexPixel); + case 5: return pGrbw5->GetPixelColor(indexPixel); + case 6: return pGrbw6->GetPixelColor(indexPixel); + case 7: return pGrbw7->GetPixelColor(indexPixel); + } + break; + } + return 0; + } + + // NOTE: Due to feature differences, some support RGBW but the method name + // here needs to be unique, thus GetPixeColorRgbw + uint32_t GetPixelColorRgbw(uint16_t indexPixel) const + { + RgbwColor col = GetPixelColorRaw(indexPixel); + uint8_t co = _colorOrder; + #ifdef COLOR_ORDER_OVERRIDE + if (indexPixel >= COO_MIN && indexPixel < COO_MAX) co = COO_ORDER; + #endif + + switch (co) + { + // W G R B + case 0: return ((col.W << 24) | (col.G << 8) | (col.R << 16) | (col.B)); //0 = GRB, default + case 1: return ((col.W << 24) | (col.R << 8) | (col.G << 16) | (col.B)); //1 = RGB, common for WS2811 + case 2: return ((col.W << 24) | (col.B << 8) | (col.R << 16) | (col.G)); //2 = BRG + case 3: return ((col.W << 24) | (col.B << 8) | (col.G << 16) | (col.R)); //3 = RBG + case 4: return ((col.W << 24) | (col.R << 8) | (col.B << 16) | (col.G)); //4 = BGR + case 5: return ((col.W << 24) | (col.G << 8) | (col.B << 16) | (col.R)); //5 = GBR + } + + return 0; + + } + + +private: + NeoPixelType _type; + byte _colorOrder = 0; + + uint16_t pixelStripStartIdx[numStrips]; + + // pointers for every possible type for up to 8 strips + NeoPixelBrightnessBusGrbRmt0 *pGrb0; + NeoPixelBrightnessBusGrbRmt1 *pGrb1; + NeoPixelBrightnessBusGrbRmt2 *pGrb2; + NeoPixelBrightnessBusGrbRmt3 *pGrb3; + NeoPixelBrightnessBusGrbRmt4 *pGrb4; + NeoPixelBrightnessBusGrbRmt5 *pGrb5; + NeoPixelBrightnessBusGrbRmt6 *pGrb6; + NeoPixelBrightnessBusGrbRmt7 *pGrb7; + NeoPixelBrightnessBusGrbwRmt0 *pGrbw0; + NeoPixelBrightnessBusGrbwRmt1 *pGrbw1; + NeoPixelBrightnessBusGrbwRmt2 *pGrbw2; + NeoPixelBrightnessBusGrbwRmt3 *pGrbw3; + NeoPixelBrightnessBusGrbwRmt4 *pGrbw4; + NeoPixelBrightnessBusGrbwRmt5 *pGrbw5; + NeoPixelBrightnessBusGrbwRmt6 *pGrbw6; + NeoPixelBrightnessBusGrbwRmt7 *pGrbw7; + + void cleanup() + { + switch (_type) + { + case NeoPixelType_Grb: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: delete pGrb0; pGrb0 = NULL; break; + case 1: delete pGrb1; pGrb1 = NULL; break; + case 2: delete pGrb2; pGrb2 = NULL; break; + case 3: delete pGrb3; pGrb3 = NULL; break; + case 4: delete pGrb4; pGrb4 = NULL; break; + case 5: delete pGrb5; pGrb5 = NULL; break; + case 6: delete pGrb6; pGrb6 = NULL; break; + case 7: delete pGrb7; pGrb7 = NULL; break; + } + } + break; + } + case NeoPixelType_Grbw: + { + for (uint8_t idx = 0; idx < numStrips; idx++) + { + switch (idx) + { + case 0: delete pGrbw0; pGrbw0 = NULL; break; + case 1: delete pGrbw1; pGrbw1 = NULL; break; + case 2: delete pGrbw2; pGrbw2 = NULL; break; + case 3: delete pGrbw3; pGrbw3 = NULL; break; + case 4: delete pGrbw4; pGrbw4 = NULL; break; + case 5: delete pGrbw5; pGrbw5 = NULL; break; + case 6: delete pGrbw6; pGrbw6 = NULL; break; + case 7: delete pGrbw7; pGrbw7 = NULL; break; + } + } + } + } + } +}; +#endif diff --git a/usermods/esp32_multistrip/README.md b/usermods/esp32_multistrip/README.md new file mode 100644 index 000000000..87b895284 --- /dev/null +++ b/usermods/esp32_multistrip/README.md @@ -0,0 +1,22 @@ +# esp32_multistrip + +This usermod enables up to 8 data pins to be used from an esp32 module to drive separate LED strands. This only works with one-wire LEDs like the WS2812. + +The esp32 RMT hardware is used for data output. See here for hardware driver implementation details: https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods + +Pass the following variables to the compiler as build flags: + + - `ESP32_MULTISTRIP` + - Define this to use usermod NpbWrapper.h instead of default one in WLED. + - `NUM_STRIPS` + - Number of strips in use + - `PIXEL_COUNTS` + - List of pixel counts in each strip + - `DATA_PINS` + - List of data pins each strip is attached to. There may be board-specific restrictions on which pins can be used for RTM. + +From the perspective of WLED software, the LEDs are addressed as one long strand. The modified NbpWrapper.h file addresses the appropriate strand from the overall LED index based on the number of LEDs defined in each strand. + +See `platformio_override.ini` for example configuration. + +Tested on low cost ESP-WROOM-32 dev boards from Amazon, such as those sold by KeeYees. diff --git a/usermods/esp32_multistrip/platformio_override.ini b/usermods/esp32_multistrip/platformio_override.ini new file mode 100644 index 000000000..afdef6766 --- /dev/null +++ b/usermods/esp32_multistrip/platformio_override.ini @@ -0,0 +1,16 @@ +; Example platformio_override.ini that shows how to configure your environment to use the multistrip usermod. +; Copy this file to the base wled directory that contains platformio.ini. +; Multistrip requires ESP32 because it has many more pins that can be used as LED outputs. +; Need to define NUM_STRIPS, PIXEL_COUNTS, and DATA_PINS as shown below. + +[platformio] +default_envs = esp32_multistrip + +[env:esp32_multistrip] +extends=env:esp32dev +build_flags = ${env:esp32dev.build_flags} + -D ESP32_MULTISTRIP ; define this variable to use ESP32_MULTISTRIP usermod + -D NUM_STRIPS=4 ; number of pixel strips in use + -D PIXEL_COUNTS="50, 50, 50, 50" ; number of pixels in each strip + -D DATA_PINS="25, 26, 32, 33" ; esp32 pins used for each pixel strip. available pins depends on esp32 module. + \ No newline at end of file diff --git a/usermods/mpu6050_imu/readme.md b/usermods/mpu6050_imu/readme.md new file mode 100644 index 000000000..adb19ef8e --- /dev/null +++ b/usermods/mpu6050_imu/readme.md @@ -0,0 +1,94 @@ +# MPU-6050 Six-Axis (Gyro + Accelerometer) Driver + +This usermod-v2 modification allows the connection of a MPU-6050 IMU sensor to +allow for effects that are controlled by the orientation or motion of the WLED Device. + +The MPU6050 has a built in "Digital Motion Processor" which does a lot of the heavy +lifting in integrating the gyro and accel measurements to get potentially more +useful gravity vector and orientation output. + +It is pretty straightforward to comment out some of the variables being read off the device if they're not needed to save CPU/Mem/Bandwidth. + +_Story:_ + +As a memento to a long trip I was on, I built an icosahedron globe. I put lights inside to indicate cities I travelled to. + +I wanted to integrate an IMU to allow either on-board, or off-board effects that would +react to the globes orientation. See the blog post on building it or a video demo . + +## Adding Dependencies + +I2Cdev and MPU6050 must be installed. + +To install them, add I2Cdevlib-MPU6050@fbde122cc5 to lib_deps in the platformio.ini file. + +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) + +For example: + +``` +lib_compat_mode = soft +lib_deps = + FastLED@3.3.2 + NeoPixelBus@2.5.7 + ESPAsyncTCP@1.2.0 + ESPAsyncUDP@697c75a025 + AsyncTCP@1.0.3 + Esp Async WebServer@1.2.0 + IRremoteESP8266@2.7.3 + I2Cdevlib-MPU6050@fbde122cc5 +``` + +## Wiring + +The connections needed to the MPU6050 are as follows: +``` + VCC VU (5V USB) Not available on all boards so use 3.3V if needed. + GND G Ground + SCL D1 (GPIO05) I2C clock + SDA D2 (GPIO04) I2C data + XDA not connected + XCL not connected + AD0 not connected + INT D8 (GPIO15) Interrupt pin +``` + +You could probably modify the code not to need an interrupt, but I used the +setup directly from the example. + +## JSON API + +This code adds: +```json +"u":{ + "IMU":{ + "Quat": [w, x, y, z], + "Euler": [psi, theta, phi], + "Gyro": [x, y, z], + "Accel": [x, y, z], + "RealAccel": [x, y, z], + "WorldAccel": [x, y, z], + "Gravity": [x, y, z], + "Orientation": [yaw, pitch, roll] + } +} +``` +to the info object + +## Usermod installation + +1. Copy the file `usermod_mpu6050_imu.h` to the `wled00` directory. +2. Register the usermod by adding `#include "usermod_mpu6050_imu.h.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`. + +Example **usermods_list.cpp**: + +```cpp +#include "wled.h" + +#include "usermod_mpu6050_imu.h" + +void registerUsermods() +{ + usermods.add(new MPU6050Driver()); +} +``` diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h new file mode 100644 index 000000000..965ab41b9 --- /dev/null +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -0,0 +1,274 @@ +#pragma once + +#include "wled.h" + +/* This driver reads quaternion data from the MPU6060 and adds it to the JSON + This example is adapted from: + https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050/examples/MPU6050_DMP6_ESPWiFi + + Tested with a d1 mini esp-12f + + GY-521 NodeMCU + MPU6050 devkit 1.0 + board Lolin Description + ======= ========== ==================================================== + VCC VU (5V USB) Not available on all boards so use 3.3V if needed. + GND G Ground + SCL D1 (GPIO05) I2C clock + SDA D2 (GPIO04) I2C data + XDA not connected + 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 + 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) + 5. Wire up the MPU6050 as detailed above. +*/ + +#include "I2Cdev.h" + +#include "MPU6050_6Axis_MotionApps20.h" +//#include "MPU6050.h" // not necessary if using MotionApps include file + +// Arduino Wire library is required if I2Cdev I2CDEV_ARDUINO_WIRE implementation +// is used in I2Cdev.h +#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE + #include "Wire.h" +#endif + +// ================================================================ +// === INTERRUPT DETECTION ROUTINE === +// ================================================================ + +volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high +void IRAM_ATTR dmpDataReady() { + mpuInterrupt = true; +} + + +class MPU6050Driver : public Usermod { + private: + MPU6050 mpu; + + // MPU control/status vars + 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 + 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 + VectorInt16 aa; // [x, y, z] accel sensor measurements + VectorInt16 gy; // [x, y, z] gyro sensor measurements + 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 + + static const int INTERRUPT_PIN = 15; // use pin 15 on ESP8266 + + public: + //Functions called by WLED + + /* + * setup() is called once at boot. WiFi is not yet connected at this point. + */ + void setup() { + // join I2C bus (I2Cdev library doesn't do this automatically) + #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE + Wire.begin(); + Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties + #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE + Fastwire::setup(400, true); + #endif + + // initialize device + Serial.println(F("Initializing I2C devices...")); + mpu.initialize(); + pinMode(INTERRUPT_PIN, INPUT); + + // verify connection + Serial.println(F("Testing device connections...")); + Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); + + // load and configure the DMP + Serial.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 + + // make sure it worked (returns 0 if so) + if (devStatus == 0) { + // turn on the DMP, now that it's ready + Serial.println(F("Enabling DMP...")); + mpu.setDMPEnabled(true); + + // enable Arduino interrupt detection + Serial.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 + Serial.println(F("DMP ready! Waiting for first interrupt...")); + dmpReady = true; + + // get expected DMP packet size for later comparison + packetSize = mpu.dmpGetFIFOPacketSize(); + } else { + // ERROR! + // 1 = initial memory load failed + // 2 = DMP configuration updates failed + // (if it's going to break, usually the code will be 1) + Serial.print(F("DMP Initialization failed (code ")); + Serial.print(devStatus); + Serial.println(F(")")); + } + } + + /* + * 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. + */ + void loop() { + // if programming failed, don't try to do anything + if (!dmpReady) return; + + // wait for MPU interrupt or extra packet(s) available + if (!mpuInterrupt && fifoCount < packetSize) return; + + // reset interrupt flag and get INT_STATUS byte + mpuInterrupt = false; + mpuIntStatus = mpu.getIntStatus(); + + // get 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(); + Serial.println(F("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(); + + // read a packet from FIFO + mpu.getFIFOBytes(fifoBuffer, packetSize); + + // track FIFO count here in case there is > 1 packet available + // (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); + mpu.dmpGetEuler(euler, &qat); + mpu.dmpGetGravity(&gravity, &qat); + mpu.dmpGetGyro(&gy, fifoBuffer); + mpu.dmpGetAccel(&aa, fifoBuffer); + mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity); + mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &qat); + mpu.dmpGetYawPitchRoll(ypr, &qat, &gravity); + } + } + + + + 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 imu_meas = user.createNestedObject("IMU"); + JsonArray quat_json = imu_meas.createNestedArray("Quat"); + 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"); + euler_json.add(euler[0]); + euler_json.add(euler[1]); + euler_json.add(euler[2]); + JsonArray accel_json = imu_meas.createNestedArray("Accel"); + accel_json.add(aa.x); + accel_json.add(aa.y); + accel_json.add(aa.z); + JsonArray gyro_json = imu_meas.createNestedArray("Gyro"); + gyro_json.add(gy.x); + gyro_json.add(gy.y); + gyro_json.add(gy.z); + JsonArray world_json = imu_meas.createNestedArray("WorldAccel"); + world_json.add(aaWorld.x); + world_json.add(aaWorld.y); + world_json.add(aaWorld.z); + JsonArray real_json = imu_meas.createNestedArray("RealAccel"); + real_json.add(aaReal.x); + real_json.add(aaReal.y); + real_json.add(aaReal.z); + JsonArray grav_json = imu_meas.createNestedArray("Gravity"); + grav_json.add(gravity.x); + grav_json.add(gravity.y); + grav_json.add(gravity.z); + JsonArray orient_json = imu_meas.createNestedArray("Orientation"); + 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) Serial.println(F("Don't burn down your garage!")); + } + + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + */ + uint16_t getId() + { + return USERMOD_ID_IMU; + } + +}; \ No newline at end of file diff --git a/usermods/mqtt_switch_v2/README.md b/usermods/mqtt_switch_v2/README.md new file mode 100644 index 000000000..dc0e259fb --- /dev/null +++ b/usermods/mqtt_switch_v2/README.md @@ -0,0 +1,50 @@ +# MQTT controllable switches +This usermod allows controlling switches (e.g. relays) via MQTT. + +## Usermod installation + +1. Copy the file `usermod_mqtt_switch.h` to the `wled00` directory. +2. Register the usermod by adding `#include "usermod_mqtt_switch.h"` in the top and `registerUsermod(new UsermodMqttSwitch());` in the bottom of `usermods_list.cpp`. + + +Example `usermods_list.cpp`: + +``` +#include "wled.h" +#include "usermod_mqtt_switch.h" + +void registerUsermods() +{ + usermods.add(new UsermodMqttSwitch()); +} +``` + +## Define pins +Add a define for MQTTSWITCHPINS to platformio_override.ini. +The following example defines 3 switches connected to the GPIO pins 13, 5 and 2: + +``` +[env:livingroom] +board = esp12e +platform = ${common.platform_wled_default} +board_build.ldscript = ${common.ldscript_4m1m} +build_flags = ${common.build_flags_esp8266} + -D LEDPIN=3 + -D BTNPIN=4 + -D RLYPIN=12 + -D RLYMDE=1 + -D STATUSPIN=15 + -D MQTTSWITCHPINS="13, 5, 2" +``` + +Pins can be inverted by setting `MQTTSWITCHINVERT`. For example `-D MQTTSWITCHINVERT="false, false, true"` would invert the switch on pin 2 in the previous example. + +The default state after booting before any MQTT message can be set by `MQTTSWITCHDEFAULTS`. For example `-D MQTTSWITCHDEFAULTS="ON, OFF, OFF"` would power on the switch on pin 13 and power off switches on pins 5 and 2. + +## MQTT topics +This usermod listens on `[mqttDeviceTopic]/switch/0/set` (where 0 is replaced with the index of the switch) for commands. Anything starting with `ON` turns on the switch, everything else turns it off. +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. + diff --git a/usermods/mqtt_switch_v2/usermod_mqtt_switch.h b/usermods/mqtt_switch_v2/usermod_mqtt_switch.h new file mode 100644 index 000000000..40241206d --- /dev/null +++ b/usermods/mqtt_switch_v2/usermod_mqtt_switch.h @@ -0,0 +1,157 @@ +#pragma once + +#include "wled.h" +#ifndef WLED_ENABLE_MQTT +#error "This user mod requires MQTT to be enabled." +#endif + +#ifndef MQTTSWITCHPINS +#error "Please define MQTTSWITCHPINS in platformio_override.ini. e.g. -D MQTTSWITCHPINS="12, 0, 2" " +// The following define helps Eclipse's C++ parser but is never used in production due to the #error statement on the line before +#define MQTTSWITCHPINS 12, 0, 2 +#endif + +// Default behavior: All outputs active high +#ifndef MQTTSWITCHINVERT +#define MQTTSWITCHINVERT +#endif + +// Default behavior: All outputs off +#ifndef MQTTSWITCHDEFAULTS +#define MQTTSWITCHDEFAULTS +#endif + +static const uint8_t switchPins[] = { MQTTSWITCHPINS }; +//This is a hack to get the number of pins defined by the user +#define NUM_SWITCH_PINS (sizeof(switchPins)) +static const bool switchInvert[NUM_SWITCH_PINS] = { MQTTSWITCHINVERT}; +//Make settings in config file more readable +#define ON 1 +#define OFF 0 +static const bool switchDefaults[NUM_SWITCH_PINS] = { MQTTSWITCHDEFAULTS}; +#undef ON +#undef OFF + +class UsermodMqttSwitch: public Usermod +{ +private: + bool mqttInitialized; + bool switchState[NUM_SWITCH_PINS]; + +public: + UsermodMqttSwitch() : + mqttInitialized(false) + { + } + + void setup() + { + for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { + setState(pinNr, switchDefaults[pinNr]); + pinMode(switchPins[pinNr], OUTPUT); + } + } + + void loop() + { + if (!mqttInitialized) { + mqttInit(); + return; // Try again in next loop iteration + } + } + + void mqttInit() + { + if (!mqtt) + return; + mqtt->onMessage( + std::bind(&UsermodMqttSwitch::onMqttMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5, std::placeholders::_6)); + mqtt->onConnect(std::bind(&UsermodMqttSwitch::onMqttConnect, this, std::placeholders::_1)); + mqttInitialized = true; + } + + void onMqttConnect(bool sessionPresent); + + void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total); + void updateState(uint8_t pinNr); + + void setState(uint8_t pinNr, bool active) + { + if (pinNr > NUM_SWITCH_PINS) + return; + switchState[pinNr] = active; + digitalWrite((char) switchPins[pinNr], (char) (switchInvert[pinNr] ? !active : active)); + updateState(pinNr); + } +}; + +inline void UsermodMqttSwitch::onMqttConnect(bool sessionPresent) +{ + if (mqttDeviceTopic[0] == 0) + return; + + for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { + char buf[128]; + StaticJsonDocument<1024> json; + sprintf(buf, "%s Switch %d", serverDescription, pinNr + 1); + json[F("name")] = buf; + + sprintf(buf, "%s/switch/%d", mqttDeviceTopic, pinNr); + json["~"] = buf; + strcat(buf, "/set"); + mqtt->subscribe(buf, 0); + + json[F("stat_t")] = "~/state"; + json[F("cmd_t")] = "~/set"; + json[F("pl_off")] = F("OFF"); + json[F("pl_on")] = F("ON"); + + char uid[16]; + sprintf(uid, "%s_sw%d", escapedMac.c_str(), pinNr); + json[F("unique_id")] = uid; + + strcpy(buf, mqttDeviceTopic); + strcat(buf, "/status"); + json[F("avty_t")] = buf; + json[F("pl_avail")] = F("online"); + json[F("pl_not_avail")] = F("offline"); + //TODO: dev + sprintf(buf, "homeassistant/switch/%s/config", uid); + char json_str[1024]; + size_t payload_size = serializeJson(json, json_str); + mqtt->publish(buf, 0, true, json_str, payload_size); + updateState(pinNr); + } +} + +inline void UsermodMqttSwitch::onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) +{ + //Note: Payload is not necessarily null terminated. Check "len" instead. + for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { + char buf[64]; + sprintf(buf, "%s/switch/%d/set", mqttDeviceTopic, pinNr); + if (strcmp(topic, buf) == 0) { + //Any string starting with "ON" is interpreted as ON, everything else as OFF + setState(pinNr, len >= 2 && payload[0] == 'O' && payload[1] == 'N'); + break; + } + } +} + +inline void UsermodMqttSwitch::updateState(uint8_t pinNr) +{ + if (!mqttInitialized) + return; + + if (pinNr > NUM_SWITCH_PINS) + return; + + char buf[64]; + sprintf(buf, "%s/switch/%d/state", mqttDeviceTopic, pinNr); + if (switchState[pinNr]) { + mqtt->publish(buf, 0, false, "ON"); + } else { + mqtt->publish(buf, 0, false, "OFF"); + } +} diff --git a/usermods/photoresistor_sensor_mqtt_v1/README.md b/usermods/photoresistor_sensor_mqtt_v1/README.md new file mode 100644 index 000000000..33d96bc74 --- /dev/null +++ b/usermods/photoresistor_sensor_mqtt_v1/README.md @@ -0,0 +1,11 @@ +# Photoresister sensor with MQTT + +This simple usermod allows attaching a photoresistor sensor like the KY-018 and publish the readings in percentage over MQTT. The frequency of MQTT messages can be modified, and there is a threshold value that can be set so that significant changes in the readings can be published immediately instead of waiting for the next update. This was found to be a good compromise between spamming MQTT messages and delayed updates. + +I also found it useful to limit the frequency of analog pin reads because otherwise the board hangs. + +This usermod has only been tested with the KY-018 sensor though should work for any other analog pin sensor. Note that this does not control the LED strip directly, it only publishes MQTT readings for use with other integrations like Home Assistant. + +## Installation + +Copy and replace the file `usermod.cpp` in wled00 directory. diff --git a/usermods/photoresistor_sensor_mqtt_v1/usermod.cpp b/usermods/photoresistor_sensor_mqtt_v1/usermod.cpp new file mode 100644 index 000000000..fff7118f3 --- /dev/null +++ b/usermods/photoresistor_sensor_mqtt_v1/usermod.cpp @@ -0,0 +1,70 @@ +#include "wled.h" +/* + * This v1 usermod 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) + * If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE) + * + * Consider the v2 usermod API if you need a more advanced feature set! + */ + +//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) + +const int LIGHT_PIN = A0; // define analog pin +const long UPDATE_MS = 30000; // Upper threshold between mqtt messages +const char MQTT_TOPIC[] = "/light"; // MQTT topic for sensor values +const int CHANGE_THRESHOLD = 5; // Change threshold in percentage to send before UPDATE_MS + +// variables +long lastTime = 0; +long timeDiff = 0; +long readTime = 0; +int lightValue = 0; +float lightPercentage = 0; +float lastPercentage = 0; + +//gets called once at boot. Do all initialization that doesn't depend on network here +void userSetup() +{ + pinMode(LIGHT_PIN, INPUT); +} + +//gets called every time WiFi is (re-)connected. Initialize own network interfaces here +void userConnected() +{ + +} + +void publishMqtt(float state) +{ + //Check if MQTT Connected, otherwise it will crash the 8266 + if (mqtt != nullptr){ + char subuf[38]; + strcpy(subuf, mqttDeviceTopic); + strcat(subuf, MQTT_TOPIC); + mqtt->publish(subuf, 0, true, String(state).c_str()); + } +} + +//loop. You can use "if (WLED_CONNECTED)" to check for successful connection +void userLoop() +{ + // Read only every 500ms, otherwise it causes the board to hang + if (millis() - readTime > 500) + { + readTime = millis(); + timeDiff = millis() - lastTime; + + // Convert value to percentage + lightValue = analogRead(LIGHT_PIN); + lightPercentage = ((float)lightValue * -1 + 1024)/(float)1024 *100; + + // Send MQTT message on significant change or after UPDATE_MS + if (abs(lightPercentage - lastPercentage) > CHANGE_THRESHOLD || timeDiff > UPDATE_MS) + { + publishMqtt(lightPercentage); + lastTime = millis(); + lastPercentage = lightPercentage; + } + } +} diff --git a/usermods/project_cars_shiftlight/readme.md b/usermods/project_cars_shiftlight/readme.md new file mode 100644 index 000000000..4490a2ba1 --- /dev/null +++ b/usermods/project_cars_shiftlight/readme.md @@ -0,0 +1,23 @@ +### Shift Light for Project Cars + +Turn your WLED lights into a rev light and shift indicator for Project Cars. + +It is pretty straight forward to use. + +1. Make sure, your WLED device and your PC/console are on the same network and can talk to each other + +2. Go to the gameplay settings menu in PCARS and enable UDP. There are 9 numbers you can choose from. This is the refresh rate. The lower the number, the better. But you might run into problems at faster rates. + +| Number | Updates/Second | +| ------ | -------------- | +| 1 | 60 | +| 2 | 50 | +| 3 | 40 | +| 4 | 30 | +| 5 | 20 | +| 6 | 15 | +| 7 | 10 | +| 8 | 05 | +| 9 | 1 | + +3. once you enter a race, WLED should automatically shift to PCARS mode. Done. diff --git a/usermods/project_cars_shiftlight/wled06_usermod.ino b/usermods/project_cars_shiftlight/wled06_usermod.ino new file mode 100644 index 000000000..0edad7ddd --- /dev/null +++ b/usermods/project_cars_shiftlight/wled06_usermod.ino @@ -0,0 +1,95 @@ +/* + * Car rev display and shift indicator for Project Cars + * + * This works via the UDP telemetry function. You'll need to enable it in the settings of the game. + * I've had good results with settings around 5 (20 fps). + * + */ +const uint8_t PCARS_dimcolor = 20; +WiFiUDP UDP; +const unsigned int PCARS_localUdpPort = 5606; // local port to listen on +char PCARS_packet[2048]; + +char PCARS_tempChar[2]; // Temporary array for u16 conversion + +u16 PCARS_RPM; +u16 PCARS_maxRPM; + +long PCARS_lastRead = millis() - 2001; +float PCARS_rpmRatio; + +void PCARS_readValues() { + + int PCARS_packetSize = UDP.parsePacket(); + if (PCARS_packetSize) { + int len = UDP.read(PCARS_packet, PCARS_packetSize); + if (len > 0) { + PCARS_packet[len] = 0; + } + if (len == 1367) { // Telemetry packet. Ignoring everything else. + PCARS_lastRead = millis(); + + realtimeLock(realtimeTimeoutMs, REALTIME_MODE_GENERIC); + // current RPM + memcpy(&PCARS_tempChar, &PCARS_packet[124], 2); + PCARS_RPM = (PCARS_tempChar[1] << 8) + PCARS_tempChar[0]; + + // max RPM + memcpy(&PCARS_tempChar, &PCARS_packet[126], 2); + PCARS_maxRPM = (PCARS_tempChar[1] << 8) + PCARS_tempChar[0]; + + if (PCARS_maxRPM) { + PCARS_rpmRatio = constrain((float)PCARS_RPM / (float)PCARS_maxRPM, 0, 1); + } else { + PCARS_rpmRatio = 0.0; + } + } + } +} +void PCARS_buildcolorbars() { + boolean activated = false; + float ledratio = 0; + + for (uint16_t i = 0; i < ledCount; i++) { + if (PCARS_rpmRatio < .95 || (millis() % 100 > 70 )) { + + ledratio = (float)i / (float)ledCount; + if (ledratio < PCARS_rpmRatio) { + activated = true; + } else { + activated = false; + } + if (ledratio > 0.66) { + setRealtimePixel(i, 0, 0, PCARS_dimcolor + ((255 - PCARS_dimcolor)*activated), 0); + } else if (ledratio > 0.33) { + setRealtimePixel(i, PCARS_dimcolor + ((255 - PCARS_dimcolor)*activated), 0, 0, 0); + } else { + setRealtimePixel(i, 0, PCARS_dimcolor + ((255 - PCARS_dimcolor)*activated), 0, 0); + } + } + else { + setRealtimePixel(i, 0, 0, 0, 0); + + } + } + colorUpdated(5); + strip.show(); +} + +void userSetup() +{ + UDP.begin(PCARS_localUdpPort); +} + +void userConnected() +{ + // new wifi, who dis? +} + +void userLoop() +{ + PCARS_readValues(); + if (PCARS_lastRead > millis() - 2000) { + PCARS_buildcolorbars(); + } +} \ No newline at end of file diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/README.md b/usermods/quinled_digquad_preassembled_unofficial_v0.1/README.md new file mode 100644 index 000000000..39ae4edd1 --- /dev/null +++ b/usermods/quinled_digquad_preassembled_unofficial_v0.1/README.md @@ -0,0 +1,37 @@ +# QuinLED-Dig-Quad Preassembled Unofficial Build + +This usermod targets the [Preassembled QuinLED-Dig-Quad](https://quinled.info/pre-assembled-quinled-dig-quad/). Tested on board revision v1r6b, +and includes the following features: + + * **Multi-channel Support** - enabling use of LED1, LED2, LED3, LED4 pins to work using segments + * **Temperature Sensor Support** - pulls readings from the built-in temperature sensor and adds the reading to the *Info* page in the UI + +## Background + +As a starting point, you should check out this awesome video from Quindor: [How to compile WLED yourself](https://quinled.info/2020/12/22/livestream-wled-compile/). The usermod you are reading now just provides some shortcuts for parts of what were covered in that video. + +## Build Firmware with Multi-channel and Temp Support + +1. Copy the `platformio_override.ini` file to the project's root directory +1. If using VS Code with the PlatformIO plugin like in the video, you will now see this new project task listed in the PLATFORMIO panel at the bottom as `env:QL-DigQuad-Pre-v0.1` (you probably need to hit the refresh button) + + + +1. Edit this file from the root directory as needed: + + + + * `PIXEL_COUNTS` may need to be adjusted for your set-up. E.g. I have lots of LEDs in Channel 1, but that's probably unusual for most + * `DATA_PINS` may need to be changed to "16,3,1,26" instead of "16,1,3,26" apparently depending on the board revision or some such + +1. Build the mod (e.g. click `Build` from the project task circled above) and update your firmware using the `QL-DigQuad-Pre-v0.1` file, e.g. using _Manual OTA_ from the Config menu. Based on the video and my own experience, you might need to build twice 🤷‍♂️. + +## Observing Temperature + +Hopefully you can now see the Temperature listed in the Info page. If not, use Chrome Developer Tools to find the current temperature + +1. Open the Developer Tools Console +2. Enter `lastinfo.u.Temperature` to view the Temperature array + + + diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/json-temp.png b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/json-temp.png new file mode 100644 index 000000000..66e501123 Binary files /dev/null and b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/json-temp.png differ diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/params.png b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/params.png new file mode 100644 index 000000000..64233f86b Binary files /dev/null and b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/params.png differ diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/pio-screenshot.png b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/pio-screenshot.png new file mode 100644 index 000000000..e178ed160 Binary files /dev/null and b/usermods/quinled_digquad_preassembled_unofficial_v0.1/images/pio-screenshot.png differ diff --git a/usermods/quinled_digquad_preassembled_unofficial_v0.1/platformio_override.ini b/usermods/quinled_digquad_preassembled_unofficial_v0.1/platformio_override.ini new file mode 100644 index 000000000..6f416668f --- /dev/null +++ b/usermods/quinled_digquad_preassembled_unofficial_v0.1/platformio_override.ini @@ -0,0 +1,16 @@ +; QuinLED-Dig-Quad Preassembled Unofficial + +[env:QL-DigQuad-Pre-v0.1] +extends = env:esp32dev +build_flags = ${common.build_flags_esp32} + -D ESP32_MULTISTRIP + -D NUM_STRIPS=4 + -D PIXEL_COUNTS="600, 300, 300, 300" + -D DATA_PINS="16,1,3,26" + -D RLYPIN=19 + -D BTNPIN=17 + -D USERMOD_DALLASTEMPERATURE + -D USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL=10000 +lib_deps = ${env.lib_deps} + milesburton/DallasTemperature@^3.9.0 + OneWire@~2.3.5 \ No newline at end of file diff --git a/usermods/readme.md b/usermods/readme.md index 8cd2d486f..0c56efaed 100644 --- a/usermods/readme.md +++ b/usermods/readme.md @@ -1,18 +1,21 @@ ### Usermods -This folder serves as a repository for usermods (custom `wled06_usermod.ino` files)! +This folder serves as a repository for usermods (custom `usermod.cpp` files)! If you have created an usermod that you believe is useful (for example to support a particular sensor, display, feature...), feel free to contribute by opening a pull request! In order for other people to be able to have fun with your usermod, please keep these points in mind: - Create a folder in this folder with a descriptive name (for example `usermod_ds18b20_temp_sensor_mqtt`) -- Include your custom `wled06_usermod.ino` file -- If your usermod requieres changes to other WLED files, please write a `readme.md` outlining the steps one has to take to use the usermod +- Include your custom files +- If your usermod requires changes to other WLED files, please write a `readme.md` outlining the steps one has to take to use the usermod - Create a pull request! - If your feature is useful for the majority of WLED users, I will consider adding it to the base code! While I do my best to not break too much, keep in mind that as WLED is being updated, usermods might break. I am not actively maintaining any usermod in this directory, that is your responsibility as the creator of the usermod. -Thank you for your help :) \ No newline at end of file +For new usermods, I would recommend trying out the new v2 usermod API, which allows installing multiple usermods at once and new functions! +You can take a look at `EXAMPLE_v2` for some documentation and at `Temperature` for a completed v2 usermod! + +Thank you for your help :) diff --git a/usermods/rotary_encoder_change_brightness/usermod.cpp b/usermods/rotary_encoder_change_brightness/usermod.cpp new file mode 100644 index 000000000..8fae61204 --- /dev/null +++ b/usermods/rotary_encoder_change_brightness/usermod.cpp @@ -0,0 +1,62 @@ +#include "wled.h" +/* + * 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 + */ + +//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) + +/* +** Rotary Encoder Example +** Use the Sparkfun Rotary Encoder to vary brightness of LED +** +** Sample the encoder at 500Hz using the millis() function +*/ + +int fadeAmount = 5; // how many points to fade the Neopixel with each step +unsigned long currentTime; +unsigned long loopTime; +const int pinA = D6; // DT from encoder +const int pinB = D7; // CLK from encoder + +unsigned char Enc_A; +unsigned char Enc_B; +unsigned char Enc_A_prev = 0; + +//gets called once at boot. Do all initialization that doesn't depend on network here +void userSetup() { + pinMode(pinA, INPUT_PULLUP); + pinMode(pinB, INPUT_PULLUP); + currentTime = millis(); + loopTime = currentTime; +} + +//gets called every time WiFi is (re-)connected. Initialize own network interfaces here +void userConnected() { +} + +//loop. You can use "if (WLED_CONNECTED)" to check for successful connection +void userLoop() { + currentTime = millis(); // get the current elapsed time + if(currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz + { + 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 + if(bri + fadeAmount <= 255) bri += fadeAmount; // increase the brightness, dont go over 255 + + } else if (Enc_B == LOW) { // B is low so counter-clockwise + if(bri - fadeAmount >= 0) bri -= fadeAmount; // decrease the brightness, dont go below 0 + } + } + Enc_A_prev = Enc_A; // Store value of A for next time + loopTime = currentTime; // Updates loopTime + + //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) + // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa + colorUpdated(6); + } +} diff --git a/usermods/rotary_encoder_change_brightness/usermode_rotary_set.h b/usermods/rotary_encoder_change_brightness/usermode_rotary_set.h new file mode 100644 index 000000000..5c95573d7 --- /dev/null +++ b/usermods/rotary_encoder_change_brightness/usermode_rotary_set.h @@ -0,0 +1,211 @@ +#pragma once + +#include "wled.h" + +//v2 usermod that allows to change brightness and color using a rotary encoder, +//change between modes by pressing a button (many encoder have one included) +class RotaryEncoderSet : public Usermod +{ +private: + //Private class members. You can declare variables and functions only accessible to your usermod here + unsigned long lastTime = 0; + /* +** Rotary Encoder Example +** Use the Sparkfun Rotary Encoder to vary brightness of LED +** +** Sample the encoder at 500Hz using the millis() function +*/ + + int fadeAmount = 5; // how many points to fade the Neopixel with each step + unsigned long currentTime; + unsigned long loopTime; + const int pinA = 5; // DT from encoder + const int pinB = 18; // CLK from encoder + const int pinC = 23; // SW from encoder + unsigned char select_state = 0; // 0 = brightness 1 = color + unsigned char button_state = HIGH; + unsigned char prev_button_state = HIGH; + CRGB fastled_col; + CHSV prim_hsv; + int16_t new_val; + + unsigned char Enc_A; + unsigned char Enc_B; + unsigned char Enc_A_prev = 0; + +public: + //Functions called by WLED + + /* + * 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() + { + //Serial.println("Hello from my usermod!"); + pinMode(pinA, INPUT_PULLUP); + pinMode(pinB, INPUT_PULLUP); + pinMode(pinC, INPUT_PULLUP); + currentTime = millis(); + loopTime = currentTime; + } + + /* + * 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() + { + currentTime = millis(); // get the current elapsed time + + 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) + { + if (select_state == 1) + { + select_state = 0; + } + else + { + select_state = 1; + } + prev_button_state = button_state; + } + 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 + if (select_state == 0) + { + if (bri + fadeAmount <= 255) + bri += fadeAmount; // increase the brightness, dont go over 255 + } + else + { + fastled_col.red = col[0]; + fastled_col.green = col[1]; + fastled_col.blue = col[2]; + prim_hsv = rgb2hsv_approximate(fastled_col); + new_val = (int16_t)prim_hsv.h + fadeAmount; + if (new_val > 255) + new_val -= 255; // roll-over if bigger than 255 + if (new_val < 0) + new_val += 255; // roll-over if smaller than 0 + prim_hsv.h = (byte)new_val; + hsv2rgb_rainbow(prim_hsv, fastled_col); + col[0] = fastled_col.red; + col[1] = fastled_col.green; + col[2] = fastled_col.blue; + } + } + else if (Enc_B == LOW) + { // B is low so counter-clockwise + if (select_state == 0) + { + if (bri - fadeAmount >= 0) + bri -= fadeAmount; // decrease the brightness, dont go below 0 + } + else + { + fastled_col.red = col[0]; + fastled_col.green = col[1]; + fastled_col.blue = col[2]; + prim_hsv = rgb2hsv_approximate(fastled_col); + new_val = (int16_t)prim_hsv.h - fadeAmount; + if (new_val > 255) + new_val -= 255; // roll-over if bigger than 255 + if (new_val < 0) + new_val += 255; // roll-over if smaller than 0 + prim_hsv.h = (byte)new_val; + hsv2rgb_rainbow(prim_hsv, fastled_col); + col[0] = fastled_col.red; + col[1] = fastled_col.green; + col[2] = fastled_col.blue; + } + } + //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) + // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa + colorUpdated(NOTIFIER_CALL_MODE_BUTTON); + updateInterfaces() + } + Enc_A_prev = Enc_A; // Store value of A for next time + loopTime = currentTime; // Updates loopTime + } + } + + /* + * 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!")); + } + + /* + * 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 0xABCD; + } + + //More methods can be added in the future, this example will then be extended. + //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! +}; diff --git a/usermods/rotary_encoder_change_effect/wled06_usermod.ino b/usermods/rotary_encoder_change_effect/wled06_usermod.ino index dbde9560a..f004ba2f4 100644 --- a/usermods/rotary_encoder_change_effect/wled06_usermod.ino +++ b/usermods/rotary_encoder_change_effect/wled06_usermod.ino @@ -39,7 +39,7 @@ void userLoop() { //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa - colorUpdated(6); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); lastTime = millis(); } } diff --git a/usermods/sensors_to_mqtt/readme.md b/usermods/sensors_to_mqtt/readme.md new file mode 100644 index 000000000..7f2d64071 --- /dev/null +++ b/usermods/sensors_to_mqtt/readme.md @@ -0,0 +1,87 @@ +# Sensors To Home Assistant (or mqtt) + +This usermod will publish values of the BMP280, CCS811 and Si7021 sensors to Home Assistant via MQTT. + +Its using home assistant automatic device discovery feature. + +The use of Home Assistant is not mandatory; it will publish the sensor values via MQTT just fine without it. + +Its resusing the mqtt connection set in the WLED web user interface. + +## Maintainer + +twitter.com/mpronk89 + +## Features + +- Reads BMP280, CCS811 and Si7021 senors +- Publishes via MQTT, configured via webui of wled +- Announces device in Home Assistant for easy setup +- Efficient energy usage +- Updates every 60 seconds + +## Example mqtt topics: + +`$mqttDeviceTopic` is set in webui of WLED! + +``` +temperature: $mqttDeviceTopic/temperature +pressure: $mqttDeviceTopic/pressure +humidity: $mqttDeviceTopic/humidity +tvoc: $mqttDeviceTopic/tvoc +eCO2: $mqttDeviceTopic/eco2 +IAQ: $mqttDeviceTopic/iaq +``` + +# Installation + +## Hardware + +### Requirements + +1. BMP280/CCS811/Si7021 sensor. E.g. https://aliexpress.com/item/32979998543.html +2. A microcontroller which can talk i2c, e.g. esp32 + +### installation + +Attach the sensor to the i2c interface. + +Default PINs esp32: + +``` +SCL_PIN = 22; +SDA_PIN = 21; +``` + +Default PINs ESP8266: + +``` +SCL_PIN = 5; +SDA_PIN = 4; +``` + +## Enable in WLED + +1. Copy `usermod_v2_SensorsToMqtt.h` into the `wled00` directory. +2. Add to `build_flags` in platformio.ini: + +``` + -D USERMOD_SENSORSTOMQTT +``` + +3. And add to `lib_deps` in platformio.ini: + +``` + adafruit/Adafruit BMP280 Library @ 2.1.0 + adafruit/Adafruit CCS811 Library @ 1.0.4 + adafruit/Adafruit Si7021 Library @ 1.4.0 +``` + +The #ifdefs in `usermods_list.cpp` should do the rest :) + +# Credits + +- Aircoookie for making WLED +- Other usermod creators for example code +- Bouke_Regnerus for https://community.home-assistant.io/t/example-indoor-air-quality-text-sensor-using-ccs811-sensor/125854 +- You, for reading this diff --git a/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h new file mode 100644 index 000000000..dd7aedc1f --- /dev/null +++ b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h @@ -0,0 +1,284 @@ +#pragma once + +#include "wled.h" +#include +#include +#include +#include +#include +#include + +Adafruit_BMP280 bmp; +Adafruit_Si7021 si7021; +Adafruit_CCS811 ccs811; + +#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards +uint8_t SCL_PIN = 22; +uint8_t SDA_PIN = 21; +#else //ESP8266 boards +uint8_t SCL_PIN = 5; +uint8_t SDA_PIN = 4; +#endif + +class UserMod_SensorsToMQTT : public Usermod +{ +private: + bool initialized = false; + bool mqttInitialized = false; + float SensorPressure = 0; + float SensorTemperature = 0; + float SensorHumidity = 0; + char *SensorIaq = "Unknown"; + String mqttTemperatureTopic = ""; + String mqttHumidityTopic = ""; + String mqttPressureTopic = ""; + String mqttTvocTopic = ""; + String mqttEco2Topic = ""; + String mqttIaqTopic = ""; + unsigned int SensorTvoc = 0; + unsigned int SensorEco2 = 0; + unsigned long nextMeasure = 0; + + void _initialize() + { + initialized = bmp.begin(BMP280_ADDRESS_ALT); + bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */ + Adafruit_BMP280::SAMPLING_X16, /* Temp. oversampling */ + Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */ + Adafruit_BMP280::FILTER_X16, /* Filtering. */ + Adafruit_BMP280::STANDBY_MS_2000); /* Refresh values every 20 seconds */ + + initialized &= si7021.begin(); + initialized &= ccs811.begin(); + ccs811.setDriveMode(CCS811_DRIVE_MODE_10SEC); /* Refresh values every 10s */ + Serial.print(initialized); + } + + void _mqttInitialize() + { + mqttTemperatureTopic = String(mqttDeviceTopic) + "/temperature"; + mqttPressureTopic = String(mqttDeviceTopic) + "/pressure"; + mqttHumidityTopic = String(mqttDeviceTopic) + "/humidity"; + mqttTvocTopic = String(mqttDeviceTopic) + "/tvoc"; + mqttEco2Topic = String(mqttDeviceTopic) + "/eco2"; + mqttIaqTopic = String(mqttDeviceTopic) + "/iaq"; + + String t = String("homeassistant/sensor/") + mqttClientID + "/temperature/config"; + + _createMqttSensor("temperature", mqttTemperatureTopic, "temperature", "°C"); + _createMqttSensor("pressure", mqttPressureTopic, "pressure", "hPa"); + _createMqttSensor("humidity", mqttHumidityTopic, "humidity", "%"); + _createMqttSensor("tvoc", mqttTvocTopic, "", "ppb"); + _createMqttSensor("eco2", mqttEco2Topic, "", "ppm"); + _createMqttSensor("iaq", mqttIaqTopic, "", ""); + } + + void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) + { + String t = String("homeassistant/sensor/") + mqttClientID + "/" + name + "/config"; + + StaticJsonDocument<300> doc; + + doc["name"] = name; + doc["state_topic"] = topic; + doc["unique_id"] = String(mqttClientID) + name; + if (unitOfMeasurement != "") + doc["unit_of_measurement"] = unitOfMeasurement; + if (deviceClass != "") + doc["device_class"] = deviceClass; + doc["expire_after"] = 1800; + + 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["sw_version"] = VERSION; + device["name"] = mqttClientID; + + String temp; + serializeJson(doc, temp); + Serial.println(t); + Serial.println(temp); + + mqtt->publish(t.c_str(), 0, true, temp.c_str()); + } + + void _updateSensorData() + { + SensorTemperature = bmp.readTemperature(); + SensorHumidity = si7021.readHumidity(); + SensorPressure = (bmp.readPressure() / 100.0F); + ccs811.setEnvironmentalData(SensorHumidity, SensorTemperature); + ccs811.readData(); + SensorTvoc = ccs811.getTVOC(); + SensorEco2 = ccs811.geteCO2(); + SensorIaq = _getIaqIndex(SensorHumidity, SensorTvoc, SensorEco2); + + Serial.printf("%f c, %f humidity, %f hPA, %u tvoc, %u Eco2, %s iaq\n", + SensorTemperature, SensorHumidity, SensorPressure, + SensorTvoc, SensorEco2, SensorIaq); + } + + /** + * Credits: Bouke_Regnerus @ https://community.home-assistant.io/t/example-indoor-air-quality-text-sensor-using-ccs811-sensor/125854 + */ + char *_getIaqIndex(float humidity, int tvoc, int eco2) + { + int iaq_index = 0; + + /* + * Transform indoor humidity values to IAQ points according to Indoor Air Quality UK: + * http://www.iaquk.org.uk/ + */ + if (humidity < 10 or humidity > 90) + { + iaq_index += 1; + } + else if (humidity < 20 or humidity > 80) + { + iaq_index += 2; + } + else if (humidity < 30 or humidity > 70) + { + iaq_index += 3; + } + else if (humidity < 40 or humidity > 60) + { + iaq_index += 4; + } + else if (humidity >= 40 and humidity <= 60) + { + iaq_index += 5; + } + + /* + * Transform eCO2 values to IAQ points according to Indoor Air Quality UK: + * http://www.iaquk.org.uk/ + */ + if (eco2 <= 600) + { + iaq_index += 5; + } + else if (eco2 <= 800) + { + iaq_index += 4; + } + else if (eco2 <= 1500) + { + iaq_index += 3; + } + else if (eco2 <= 1800) + { + iaq_index += 2; + } + else if (eco2 > 1800) + { + iaq_index += 1; + } + + /* + * Transform TVOC values to IAQ points according to German environmental guidelines: + * https://www.repcomsrl.com/wp-content/uploads/2017/06/Environmental_Sensing_VOC_Product_Brochure_EN.pdf + */ + if (tvoc <= 65) + { + iaq_index += 5; + } + else if (tvoc <= 220) + { + iaq_index += 4; + } + else if (tvoc <= 660) + { + iaq_index += 3; + } + else if (tvoc <= 2200) + { + iaq_index += 2; + } + else if (tvoc > 2200) + { + iaq_index += 1; + } + + if (iaq_index <= 6) + { + return "Unhealty"; + } + else if (iaq_index <= 9) + { + return "Poor"; + } + else if (iaq_index <= 12) + { + return "Moderate"; + } + else if (iaq_index <= 14) + { + return "Good"; + } + else if (iaq_index > 14) + { + return "Excellent"; + } + } + +public: + void setup() + { + Serial.println("Starting!"); + Wire.begin(SDA_PIN, SCL_PIN); + Serial.println("Initializing sensors.. "); + _initialize(); + } + + // gets called every time WiFi is (re-)connected. + void connected() + { + nextMeasure = millis() + 5000; // Schedule next measure in 5 seconds + } + + void loop() + { + unsigned long tempTimer = millis(); + + if (tempTimer > nextMeasure) + { + nextMeasure = tempTimer + 60000; // Schedule next measure in 60 seconds + + if (!initialized) + { + Serial.println("Error! Sensors not initialized in loop()!"); + _initialize(); + return; // lets try again next loop + } + + if (mqtt != nullptr && mqtt->connected()) + { + if (!mqttInitialized) + { + _mqttInitialize(); + mqttInitialized = true; + } + + // Update sensor data + _updateSensorData(); + + // Create string populated with user defined device topic from the UI, + // and the read temperature, humidity and pressure. + // Then publish to MQTT server. + mqtt->publish(mqttTemperatureTopic.c_str(), 0, true, String(SensorTemperature).c_str()); + mqtt->publish(mqttPressureTopic.c_str(), 0, true, String(SensorPressure).c_str()); + mqtt->publish(mqttHumidityTopic.c_str(), 0, true, String(SensorHumidity).c_str()); + mqtt->publish(mqttTvocTopic.c_str(), 0, true, String(SensorTvoc).c_str()); + mqtt->publish(mqttEco2Topic.c_str(), 0, true, String(SensorEco2).c_str()); + mqtt->publish(mqttIaqTopic.c_str(), 0, true, String(SensorIaq).c_str()); + } + else + { + Serial.println("Missing MQTT connection. Not publishing data"); + mqttInitialized = false; + } + } + } +}; diff --git a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h new file mode 100644 index 000000000..8d8bcf0b5 --- /dev/null +++ b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h @@ -0,0 +1,136 @@ +#include "wled.h" + +/* + * Usermods allow you to add own functionality to WLED more easily + * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality + * + * This is Stairway-Wipe as a v2 usermod. + * + * Using this usermod: + * 1. Copy the usermod into the sketch folder (same folder as wled00.ino) + * 2. Register the usermod by adding #include "stairway-wipe-usermod-v2.h" in the top and registerUsermod(new StairwayWipeUsermod()) in the bottom of usermods_list.cpp + */ + +class StairwayWipeUsermod : public Usermod { + private: + //Private class members. You can declare variables and functions only accessible to your usermod here + unsigned long lastTime = 0; + byte wipeState = 0; //0: inactive 1: wiping 2: solid + unsigned long timeStaticStart = 0; + uint16_t previousUserVar0 = 0; + +//comment this out if you want the turn off effect to be just fading out instead of reverse wipe +#define STAIRCASE_WIPE_OFF + public: + + 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 + //has to be set to 2 if movement is detected on the PIR that is the opposite side + //can be set to 0 if no movement is detected. Otherwise LEDs will turn off after a configurable timeout (userVar1 seconds) + + if (userVar0 > 0) + { + if ((previousUserVar0 == 1 && userVar0 == 2) || (previousUserVar0 == 2 && userVar0 == 1)) wipeState = 3; //turn off if other PIR triggered + previousUserVar0 = userVar0; + + if (wipeState == 0) { + startWipe(); + wipeState = 1; + } else if (wipeState == 1) { //wiping + uint32_t cycleTime = 360 + (255 - effectSpeed)*75; //this is how long one wipe takes (minus 25 ms to make sure we switch in time) + if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete + effectCurrent = FX_MODE_STATIC; + timeStaticStart = millis(); + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); + wipeState = 2; + } + } else if (wipeState == 2) { //static + if (userVar1 > 0) //if U1 is not set, the light will stay on until second PIR or external command is triggered + { + if (millis() - timeStaticStart > userVar1*1000) wipeState = 3; + } + } else if (wipeState == 3) { //switch to wipe off + #ifdef STAIRCASE_WIPE_OFF + effectCurrent = FX_MODE_COLOR_WIPE; + strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); + wipeState = 4; + #else + turnOff(); + #endif + } else { //wiping off + if (millis() + strip.timebase > (725 + (255 - effectSpeed)*150)) turnOff(); //wipe complete + } + } else { + wipeState = 0; //reset for next time + if (previousUserVar0) { + #ifdef STAIRCASE_WIPE_OFF + userVar0 = previousUserVar0; + wipeState = 3; + #else + turnOff(); + #endif + } + previousUserVar0 = 0; + } +} + + 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!")); + } + + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject("exampleUsermod"); + top["great"] = userVar0; //save this var persistently whenever settings are saved + } + + void readFromConfig(JsonObject& root) + { + 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) + } + + uint16_t getId() + { + return USERMOD_ID_EXAMPLE; + } + + + void startWipe() + { + bri = briLast; //turn on + transitionDelayTemp = 0; //no transition + effectCurrent = FX_MODE_COLOR_WIPE; + resetTimebase(); //make sure wipe starts from beginning + + //set wipe direction + WS2812FX::Segment& seg = strip.getSegment(0); + bool doReverse = (userVar0 == 2); + seg.setOption(1, doReverse); + + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); + } + + void turnOff() + { + #ifdef STAIRCASE_WIPE_OFF + transitionDelayTemp = 0; //turn off immediately after wipe completed + #else + transitionDelayTemp = 4000; //fade out slowly + #endif + bri = 0; + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); + wipeState = 0; + userVar0 = 0; + previousUserVar0 = 0; + } + + + + //More methods can be added in the future, this example will then be extended. + //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! +}; diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino index ab61c5afe..0cc85df74 100644 --- a/usermods/stairway_wipe_basic/wled06_usermod.ino +++ b/usermods/stairway_wipe_basic/wled06_usermod.ino @@ -1,7 +1,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 wled01_eeprom.h) + * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) * bytes 2400+ are currently ununsed, but might be used for future wled features */ @@ -47,7 +47,7 @@ void userLoop() if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete effectCurrent = FX_MODE_STATIC; timeStaticStart = millis(); - colorUpdated(3); + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); wipeState = 2; } } else if (wipeState == 2) { //static @@ -59,7 +59,7 @@ void userLoop() #ifdef STAIRCASE_WIPE_OFF effectCurrent = FX_MODE_COLOR_WIPE; strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit - colorUpdated(3); + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); wipeState = 4; #else turnOff(); @@ -93,7 +93,7 @@ void startWipe() bool doReverse = (userVar0 == 2); seg.setOption(1, doReverse); - colorUpdated(3); + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); } void turnOff() @@ -104,7 +104,7 @@ void turnOff() transitionDelayTemp = 4000; //fade out slowly #endif bri = 0; - colorUpdated(3); + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); wipeState = 0; userVar0 = 0; previousUserVar0 = 0; diff --git a/usermods/usermod_v2_auto_save/readme.md b/usermods/usermod_v2_auto_save/readme.md new file mode 100644 index 000000000..5c835c60e --- /dev/null +++ b/usermods/usermod_v2_auto_save/readme.md @@ -0,0 +1,45 @@ +# Auto Save + +v2 Usermod to automatically save settings +to preset number AUTOSAVE_PRESET_NUM after a change to any of + +* brightness +* effect speed +* effect intensity +* mode (effect) +* palette + +but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle" +period in case there are other changes (any change will +extend the "settle" window). + +It will additionally load preset AUTOSAVE_PRESET_NUM at startup. +during the first `loop()`. Reasoning below. + +AutoSaveUsermod is standalone, but if FourLineDisplayUsermod is installed, it will notify the user of the saved changes. + +Note: I don't love that WLED doesn't respect the brightness of the preset being auto loaded, so the AutoSaveUsermod will set the AUTOSAVE_PRESET_NUM preset in the first loop, so brightness IS honored. This means WLED will effectively ignore Default brightness and Apply N preset at boot when the AutoSaveUsermod is installed. + +## 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_AUTO_SAVE` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp +* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) +* `AUTOSAVE_SETTLE_MS` - Minimum time to wave before auto saving, defaults to 10000 (10s) +* `AUTOSAVE_PRESET_NUM` - Preset number to auto-save to, auto-load at startup from, defaults to 99 + +### 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_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h new file mode 100644 index 000000000..bd7ea6d81 --- /dev/null +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -0,0 +1,192 @@ +#pragma once + +#include "wled.h" + +// +// v2 Usermod to automatically save settings +// to preset number AUTOSAVE_PRESET_NUM after a change to any of +// +// * brightness +// * effect speed +// * effect intensity +// * mode (effect) +// * palette +// +// but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle" +// period in case there are other changes (any change will +// extend the "settle" window). +// +// It will additionally load preset AUTOSAVE_PRESET_NUM at startup. +// during the first `loop()`. Reasoning below. +// +// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod +// is installed, it will notify the user of the saved changes. +// +// Note: I don't love that WLED doesn't respect the brightness +// of the preset being auto loaded, so the AutoSaveUsermod +// will set the AUTOSAVE_PRESET_NUM preset in the first loop, +// so brightness IS honored. This means WLED will effectively +// ignore Default brightness and Apply N preset at boot when +// the AutoSaveUsermod is installed. + +//How long to wait after settings change to auto-save +#ifndef AUTOSAVE_SETTLE_MS +#define AUTOSAVE_SETTLE_MS 10*1000 +#endif + +//Preset number to save to +#ifndef AUTOSAVE_PRESET_NUM +#define AUTOSAVE_PRESET_NUM 99 +#endif + +// "Auto save MM-DD HH:MM:SS" +#define PRESET_NAME_BUFFER_SIZE 25 + +class AutoSaveUsermod : public Usermod { + private: + // If we've detected the need to auto save, this will + // be non zero. + unsigned long autoSaveAfter = 0; + + char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; + + bool firstLoop = true; + + uint8_t knownBrightness = 0; + uint8_t knownEffectSpeed = 0; + uint8_t knownEffectIntensity = 0; + uint8_t knownMode = 0; + uint8_t knownPalette = 0; + +#ifdef USERMOD_FOUR_LINE_DISLAY + FourLineDisplayUsermod* display; +#endif + + public: + // gets called once at boot. Do all initialization that doesn't depend on + // network here + void setup() { +#ifdef USERMOD_FOUR_LINE_DISLAY + // This Usermod has enhanced funcionality if + // FourLineDisplayUsermod is available. + display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); +#endif + } + + // gets called every time WiFi is (re-)connected. Initialize own network + // interfaces here + void connected() {} + + /** + * Da loop. + */ + void loop() { + unsigned long now = millis(); + uint8_t currentMode = strip.getMode(); + uint8_t currentPalette = strip.getSegment(0).palette; + if (firstLoop) { + firstLoop = false; + applyPreset(AUTOSAVE_PRESET_NUM); + knownBrightness = bri; + knownEffectSpeed = effectSpeed; + knownEffectIntensity = effectIntensity; + knownMode = currentMode; + knownPalette = currentPalette; + return; + } + + unsigned long wouldAutoSaveAfter = now + AUTOSAVE_SETTLE_MS; + if (knownBrightness != bri) { + knownBrightness = bri; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownEffectSpeed != effectSpeed) { + knownEffectSpeed = effectSpeed; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownEffectIntensity != effectIntensity) { + knownEffectIntensity = effectIntensity; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownMode != currentMode) { + knownMode = currentMode; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownPalette != currentPalette) { + knownPalette = currentPalette; + autoSaveAfter = wouldAutoSaveAfter; + } + + if (autoSaveAfter && now > autoSaveAfter) { + autoSaveAfter = 0; + // Time to auto save. You may have some flickry? + saveSettings(); + displayOverlay(); + } + } + + void saveSettings() { + updateLocalTime(); + sprintf(presetNameBuffer, + "Auto save %02d-%02d %02d:%02d:%02d", + month(localTime), day(localTime), + hour(localTime), minute(localTime), second(localTime)); + savePreset(AUTOSAVE_PRESET_NUM, true, presetNameBuffer); + } + + void displayOverlay() { +#ifdef USERMOD_FOUR_LINE_DISLAY + if (display != nullptr) { + display->wakeDisplay(); + display->overlay("Settings", "Auto Saved", 1500); + } +#endif + } + + /* + * 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) { + } + + /* + * 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) { + } + + /* + * 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 :) + */ + void readFromConfig(JsonObject& root) { + } + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() { + return USERMOD_ID_AUTO_SAVE; + } + +}; \ No newline at end of file diff --git a/usermods/usermod_v2_four_line_display/readme.md b/usermods/usermod_v2_four_line_display/readme.md new file mode 100644 index 000000000..3198b2be5 --- /dev/null +++ b/usermods/usermod_v2_four_line_display/readme.md @@ -0,0 +1,39 @@ +# Rotary Encoder UI Usermod + +First, thanks to the authors of the ssd11306_i2c_oled_u8g2 mod. + +This usermod provides a four line display using either +128x32 or 128x64 OLED displays. +It's 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_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells Rotary Encoder usermod, if installed, that the display is available +* `FLD_PIN_SCL` - The display SCL pin, defaults to 5 +* `FLD_PIN_SDA` - The display SDA pin, defaults to 4 +* `FLIP_MODE` - Set to 0 or 1 +* `LINE_HEIGHT` - Set to 1 or 2 + +There are other `#define` values in the Usermod that might be of interest. + +### 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`. + +## Change Log + +2021-02 +* First public release 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 new file mode 100644 index 000000000..0b59af551 --- /dev/null +++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h @@ -0,0 +1,526 @@ +#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 5 +#endif + +#ifndef FLD_PIN_SDA +#define FLD_PIN_SDA 4 +#endif + +// U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8( +// U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA); +U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8( + U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA); + +// Screen upside down? Change to 0 or 1 +#ifndef FLIP_MODE +#define FLIP_MODE 0 +#endif + +// LINE_HEIGHT 1 is single height, for 128x32 displays. +// LINE_HEIGHT 2 makes the 128x64 screen display at double height. +#ifndef LINE_HEIGHT +#define LINE_HEIGHT 2 +#endif + +// If you aren't also including RotaryEncoderUIUsermod +// you probably want to set both +// SLEEP_MODE_ENABLED false +// CLOCK_MODE_ENABLED false +// as you will never be able wake the display / disable the clock. +#ifdef USERMOD_ROTARY_ENCODER_UI +#ifndef SLEEP_MODE_ENABLED +#define SLEEP_MODE_ENABLED true +#endif +#ifndef CLOCK_MODE_ENABLED +#define CLOCK_MODE_ENABLED true +#endif +#else +#define SLEEP_MODE_ENABLED false +#define CLOCK_MODE_ENABLED false +#endif + +// When to time out to the clock or blank the screen +// if SLEEP_MODE_ENABLED. +#define SCREEN_TIMEOUT_MS 15*1000 + +#define TIME_INDENT 0 +#define DATE_INDENT 2 + +// Minimum time between redrawing screen in ms +#define USER_LOOP_REFRESH_RATE_MS 1000 + +#if LINE_HEIGHT == 2 +#define DRAW_STRING draw1x2String +#define DRAW_GLYPH draw1x2Glyph +#define DRAW_BIG_STRING draw2x2String +#else +#define DRAW_STRING drawString +#define DRAW_GLYPH drawGlyph +#define DRAW_BIG_STRING draw2x2String +#endif + +// Extra char (+1) for null +#define LINE_BUFFER_SIZE 16+1 +#define FLD_LINE_3_BRIGHTNESS 0 +#define FLD_LINE_3_EFFECT_SPEED 1 +#define FLD_LINE_3_EFFECT_INTENSITY 2 +#define FLD_LINE_3_PALETTE 3 + +#if LINE_HEIGHT == 2 +#define TIME_LINE 1 +#else +#define TIME_LINE 0 +#endif + +class FourLineDisplayUsermod : public Usermod { + private: + unsigned long lastTime = 0; + + // needRedraw marks if redraw is required to prevent often redrawing. + bool needRedraw = 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; + long lastUpdate = 0; + long lastRedraw = 0; + long overlayUntil = 0; + byte lineThreeType = FLD_LINE_3_BRIGHTNESS; + // Set to 2 or 3 to mark lines 2 or 3. Other values ignored. + byte markLineNum = 0; + + char lineBuffer[LINE_BUFFER_SIZE]; + + char **modes_qstrings = nullptr; + char **palettes_qstrings = nullptr; + + // If display does not work or looks corrupted check the + // constructor reference: + // https://github.com/olikraus/u8g2/wiki/u8x8setupcpp + // or check the gallery: + // https://github.com/olikraus/u8g2/wiki/gallery + public: + + // gets called once at boot. Do all initialization that doesn't depend on + // network here + void setup() { + u8x8.begin(); + u8x8.setFlipMode(FLIP_MODE); + u8x8.setPowerSave(0); + u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 + u8x8.setFont(u8x8_font_chroma48medium8_r); + u8x8.DRAW_STRING(0, 0*LINE_HEIGHT, "Loading..."); + + ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT); + modes_qstrings = modeSortUsermod->getModesQStrings(); + palettes_qstrings = modeSortUsermod->getPalettesQStrings(); + } + + // gets called every time WiFi is (re-)connected. Initialize own network + // interfaces here + void connected() {} + + /** + * Da loop. + */ + void loop() { + if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + return; + } + lastUpdate = millis(); + + redraw(false); + } + + /** + * Redraw the screen (but only if things have changed + * or if forceRedraw). + */ + void redraw(bool forceRedraw) { + if (overlayUntil > 0) { + if (millis() >= 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) { + needRedraw = true; + } else if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { + needRedraw = true; + } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { + needRedraw = true; + } else if (knownBrightness != bri) { + needRedraw = true; + } else if (knownEffectSpeed != effectSpeed) { + needRedraw = true; + } else if (knownEffectIntensity != effectIntensity) { + needRedraw = true; + } else if (knownMode != strip.getMode()) { + needRedraw = true; + } else if (knownPalette != strip.getSegment(0).palette) { + needRedraw = true; + } + + if (!needRedraw) { + // Nothing to change. + // Turn off display after 3 minutes with no change. + if(SLEEP_MODE_ENABLED && !displayTurnedOff && + (millis() - lastRedraw > SCREEN_TIMEOUT_MS)) { + // We will still check if there is a change in redraw() + // and turn it back on if it changed. + sleepOrClock(true); + } + else if (displayTurnedOff && CLOCK_MODE_ENABLED) { + showTime(); + } + return; + } + needRedraw = false; + lastRedraw = millis(); + + if (displayTurnedOff) + { + // Turn the display back on + sleepOrClock(false); + } + + // Update last known values. + #if defined(ESP8266) + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + #else + knownSsid = WiFi.SSID(); + #endif + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + knownBrightness = bri; + knownMode = strip.getMode(); + knownPalette = strip.getSegment(0).palette; + knownEffectSpeed = effectSpeed; + knownEffectIntensity = effectIntensity; + + // Do the actual drawing + u8x8.clear(); + u8x8.setFont(u8x8_font_chroma48medium8_r); + + // First row with Wifi name + String ssidString = knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0); + u8x8.DRAW_STRING(1, 0*LINE_HEIGHT, ssidString.c_str()); + // Print `~` char to indicate that SSID is longer, than owr dicplay + if (knownSsid.length() > u8x8.getCols()) { + u8x8.DRAW_STRING(u8x8.getCols() - 1, 0*LINE_HEIGHT, "~"); + } + + // Second row with IP or Psssword + // Print password in AP mode and if led is OFF. + if (apActive && bri == 0) { + u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, apPass); + } + else { + String ipString = knownIp.toString(); + u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, ipString.c_str()); + } + + // Third row with mode name + showCurrentEffectOrPalette(modes_qstrings[knownMode], 2); + + switch(lineThreeType) { + case FLD_LINE_3_BRIGHTNESS: + sprintf(lineBuffer, "Brightness %d", bri); + u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); + break; + case FLD_LINE_3_EFFECT_SPEED: + sprintf(lineBuffer, "FX Speed %d", effectSpeed); + u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); + break; + case FLD_LINE_3_EFFECT_INTENSITY: + sprintf(lineBuffer, "FX Intense %d", effectIntensity); + u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); + break; + case FLD_LINE_3_PALETTE: + showCurrentEffectOrPalette(palettes_qstrings[knownPalette], 3); + break; + } + + u8x8.setFont(u8x8_font_open_iconic_arrow_1x1); + u8x8.DRAW_GLYPH(0, markLineNum*LINE_HEIGHT, 66); // arrow icon + + u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); + u8x8.DRAW_GLYPH(0, 0*LINE_HEIGHT, 80); // wifi icon + u8x8.DRAW_GLYPH(0, 1*LINE_HEIGHT, 68); // home icon + } + + /** + * Display the current effect or palette (desiredEntry) + * on the appropriate line (row). + * + * TODO: Should we cache the current effect and + * TODO: palette name? This seems expensive. + */ + void showCurrentEffectOrPalette(char *qstring, uint8_t row) { + uint8_t printedChars = 1; + char singleJsonSymbol; + int i = 0; + while (true) { + singleJsonSymbol = pgm_read_byte_near(qstring + i); + if (singleJsonSymbol == '"' || singleJsonSymbol == '\0' ) { + break; + } + u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol); + printedChars++; + if ( (printedChars > u8x8.getCols() - 2)) { + break; + } + i++; + } + } + + /** + * 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 (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 (displayTurnedOff) { + // Turn the display back on + sleepOrClock(false); + } + + // Print the overlay + u8x8.clear(); + u8x8.setFont(u8x8_font_chroma48medium8_r); + if (line1) { + u8x8.DRAW_STRING(0, 1*LINE_HEIGHT, line1); + } + if (line2) { + u8x8.DRAW_STRING(0, 2*LINE_HEIGHT, line2); + } + overlayUntil = millis() + showHowLong; + } + + /** + * Specify what data should be defined on line 3 + * (the last line). + */ + void setLineThreeType(byte newLineThreeType) { + if (newLineThreeType == FLD_LINE_3_BRIGHTNESS || + newLineThreeType == FLD_LINE_3_EFFECT_SPEED || + newLineThreeType == FLD_LINE_3_EFFECT_INTENSITY || + newLineThreeType == FLD_LINE_3_PALETTE) { + lineThreeType = newLineThreeType; + } + else { + // Unknown value. + lineThreeType = FLD_LINE_3_BRIGHTNESS; + } + } + + /** + * Line 2 or 3 (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; + } + } + + /* + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. + * Below it is shown how this could be used for e.g. a light sensor + */ + /* + void addToJsonInfo(JsonObject& root) + { + int reading = 20; + //this code adds "u":{"Light":[20," lux"]} to the info object + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray lightArr = user.createNestedArray("Light"); //name + lightArr.add(reading); //value + lightArr.add(" lux"); //unit + } + */ + + /** + * Enable sleep (turn the display off) or clock mode. + */ + void sleepOrClock(bool enabled) { + if (enabled) { + if (CLOCK_MODE_ENABLED) { + showTime(); + } + else { + u8x8.setPowerSave(1); + } + displayTurnedOff = true; + } + else { + if (!CLOCK_MODE_ENABLED) { + u8x8.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() { + updateLocalTime(); + byte minuteCurrent = minute(localTime); + byte hourCurrent = hour(localTime); + if (knownMinute == minuteCurrent && knownHour == hourCurrent) { + // Time hasn't changed. + return; + } + knownMinute = minuteCurrent; + knownHour = hourCurrent; + + u8x8.clear(); + u8x8.setFont(u8x8_font_chroma48medium8_r); + + int currentMonth = month(localTime); + sprintf(lineBuffer, "%s %d", monthShortStr(currentMonth), day(localTime)); + u8x8.DRAW_BIG_STRING(DATE_INDENT, TIME_LINE*LINE_HEIGHT, lineBuffer); + + byte 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(lineBuffer, "%02d:%02d %s", showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : ""); + // For time, we always use LINE_HEIGHT of 2 since + // we are printing it big. + u8x8.DRAW_BIG_STRING(TIME_INDENT + (useAMPM ? 0 : 2), (TIME_LINE + 1) * 2, lineBuffer); + } + + /* + * 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) { + } + + /* + * 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) { + } + + /* + * 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 :) + */ + void readFromConfig(JsonObject& root) { + } + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() { + return USERMOD_ID_FOUR_LINE_DISP; + } + +}; \ No newline at end of file diff --git a/usermods/usermod_v2_mode_sort/readme.md b/usermods/usermod_v2_mode_sort/readme.md new file mode 100644 index 000000000..b4fe90e73 --- /dev/null +++ b/usermods/usermod_v2_mode_sort/readme.md @@ -0,0 +1,33 @@ +# 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 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. diff --git a/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h new file mode 100644 index 000000000..2be7ce84c --- /dev/null +++ b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h @@ -0,0 +1,248 @@ +#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); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + if (insideQuotes) { + // We have a new mode or palette + modeStrings[modeIndex] = (char *)(json + i + 1); + } + break; + case '[': + break; + case ']': + complete = true; + break; + case ',': + 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_rotary_encoder_ui/platformio_override.ini.sample b/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample new file mode 100644 index 000000000..cc39d65cf --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample @@ -0,0 +1,48 @@ +[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_DISLAY -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_DISLAY -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 new file mode 100644 index 000000000..4477590b5 --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui/readme.md @@ -0,0 +1,33 @@ +# 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_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) +* `ENCODER_DT_PIN` - The encoders DT pin, defaults to 12 +* `ENCODER_CLK_PIN` - The encoders CLK pin, defaults to 14 +* `ENCODER_SW_PIN` - The encoders SW pin, defaults to 13 + +### 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 new file mode 100644 index 000000000..6dc2a1be3 --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h @@ -0,0 +1,401 @@ +#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_DISLAY +// These constants won't be defined if we aren't using FourLineDisplay. +#define FLD_LINE_3_BRIGHTNESS 0 +#define FLD_LINE_3_EFFECT_SPEED 0 +#define FLD_LINE_3_EFFECT_INTENSITY 0 +#define FLD_LINE_3_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; + const int pinA = ENCODER_DT_PIN; // DT from encoder + const int pinB = ENCODER_CLK_PIN; // CLK from encoder + const int 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_DISLAY + 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 currentEffectAndPaleeteInitialized = false; + uint8_t effectCurrentIndex = 0; + uint8_t effectPaletteIndex = 0; + +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() + { + pinMode(pinA, INPUT_PULLUP); + pinMode(pinB, INPUT_PULLUP); + pinMode(pinC, INPUT_PULLUP); + 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_DISLAY + // 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->setLineThreeType(FLD_LINE_3_BRIGHTNESS); + display->setMarkLine(3); + } +#endif + } + + /* + * 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() + { + 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 (!currentEffectAndPaleeteInitialized) { + 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_3_BRIGHTNESS, 3); + break; + case 1: + changedState = changeState("Select FX", FLD_LINE_3_EFFECT_SPEED, 2); + break; + case 2: + changedState = changeState("FX Speed", FLD_LINE_3_EFFECT_SPEED, 3); + break; + case 3: + changedState = changeState("FX Intensity", FLD_LINE_3_EFFECT_INTENSITY, 3); + break; + case 4: + changedState = changeState("Palette", FLD_LINE_3_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() { + currentEffectAndPaleeteInitialized = 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_DISLAY + if (display != nullptr) { + if (display->wakeDisplay()) { + // Throw away wake up input + return false; + } + display->overlay("Mode change", stateName, 1500); + display->setLineThreeType(lineThreeMode); + display->setMarkLine(markedLine); + } + #endif + return true; + } + + void lampUdated() { + bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette); + + //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) + // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa + colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE); + updateInterfaces(NOTIFIER_CALL_MODE_DIRECT_CHANGE); + } + + void changeBrightness(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISLAY + 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_DISLAY + 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_DISLAY + 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_DISLAY + 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_DISLAY + 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!")); + } + + /* + * 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; + } +}; diff --git a/usermods/word-clock-matrix/Word Clock Baffle.stl b/usermods/word-clock-matrix/Word Clock Baffle.stl new file mode 100644 index 000000000..ed34a68f8 Binary files /dev/null and b/usermods/word-clock-matrix/Word Clock Baffle.stl differ diff --git a/usermods/word-clock-matrix/readme.md b/usermods/word-clock-matrix/readme.md new file mode 100644 index 000000000..d226537f4 --- /dev/null +++ b/usermods/word-clock-matrix/readme.md @@ -0,0 +1,6 @@ +## Word clock usermod + +By @bwente + +See https://www.hackster.io/bwente/word-clock-with-just-two-components-073834 for the hardware guide! +Includes a customizable feature to lower the brightness at night. diff --git a/usermods/word-clock-matrix/word clock stencil.svg b/usermods/word-clock-matrix/word clock stencil.svg new file mode 100644 index 000000000..32e3e656e --- /dev/null +++ b/usermods/word-clock-matrix/word clock stencil.svg @@ -0,0 +1,846 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/usermods/word-clock-matrix/word-clock-matrix.cpp b/usermods/word-clock-matrix/word-clock-matrix.cpp new file mode 100644 index 000000000..aadfb8b18 --- /dev/null +++ b/usermods/word-clock-matrix/word-clock-matrix.cpp @@ -0,0 +1,305 @@ +#include "wled.h" +/* + * This v1 usermod 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) + * If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE) + * + * Consider the v2 usermod API if you need a more advanced feature set! + */ + + +uint8_t minuteLast = 99; +int dayBrightness = 128; +int nightBrightness = 16; + +//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) + +//gets called once at boot. Do all initialization that doesn't depend on network here +void userSetup() +{ +saveMacro(14, "A=128", false); +saveMacro(15, "A=64", false); +saveMacro(16, "A=16", false); + +saveMacro(1, "&FX=0&R=255&G=255&B=255", false); + +//strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); + + //select first two segments (background color + FX settable) + WS2812FX::Segment &seg = strip.getSegment(0); + seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); + strip.getSegment(0).setOption(0, false); + strip.getSegment(0).setOption(2, false); + //other segments are text + for (int i = 1; i < 10; i++) + { + WS2812FX::Segment &seg = strip.getSegment(i); + seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); + strip.getSegment(i).setOption(0, true); + strip.setBrightness(128); + } +} + +//gets called every time WiFi is (re-)connected. Initialize own network interfaces here +void userConnected() +{ +} + +void selectWordSegments(bool state) +{ + for (int i = 1; i < 10; i++) + { + //WS2812FX::Segment &seg = strip.getSegment(i); + strip.getSegment(i).setOption(0, state); + // strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); + //seg.mode = 12; + //seg.palette = 1; + //strip.setBrightness(255); + } + strip.getSegment(0).setOption(0, !state); +} + +void hourChime() +{ + //strip.resetSegments(); + selectWordSegments(true); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); + savePreset(13, false); + selectWordSegments(false); + //strip.getSegment(0).setOption(0, true); + strip.getSegment(0).setOption(2, true); + applyPreset(12); + colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED); +} + +void displayTime(byte hour, byte minute) +{ + bool isToHour = false; //true if minute > 30 + strip.setSegment(0, 0, 64); // background + strip.setSegment(1, 0, 2); //It is + + strip.setSegment(2, 0, 0); + strip.setSegment(3, 0, 0); //disable minutes + strip.setSegment(4, 0, 0); //past + strip.setSegment(6, 0, 0); //to + strip.setSegment(8, 0, 0); //disable o'clock + + if (hour < 24) //valid time, display + { + if (minute == 30) + { + strip.setSegment(2, 3, 6); //half + strip.setSegment(3, 0, 0); //minutes + } + else if (minute == 15 || minute == 45) + { + strip.setSegment(3, 0, 0); //minutes + } + else if (minute == 10) + { + //strip.setSegment(5, 6, 8); //ten + } + else if (minute == 5) + { + //strip.setSegment(5, 16, 18); //five + } + else if (minute == 0) + { + strip.setSegment(3, 0, 0); //minutes + //hourChime(); + } + else + { + strip.setSegment(3, 18, 22); //minutes + } + + //past or to? + if (minute == 0) + { //full hour + strip.setSegment(3, 0, 0); //disable minutes + strip.setSegment(4, 0, 0); //disable past + strip.setSegment(6, 0, 0); //disable to + strip.setSegment(8, 60, 64); //o'clock + } + else if (minute > 34) + { + //strip.setSegment(6, 22, 24); //to + //minute = 60 - minute; + isToHour = true; + } + else + { + //strip.setSegment(4, 24, 27); //past + //isToHour = false; + } + } + else + { //temperature display + } + + //byte minuteRem = minute %10; + + if (minute <= 4) + { + strip.setSegment(3, 0, 0); //nothing + strip.setSegment(5, 0, 0); //nothing + strip.setSegment(6, 0, 0); //nothing + strip.setSegment(8, 60, 64); //o'clock + } + else if (minute <= 9) + { + strip.setSegment(5, 16, 18); // five past + strip.setSegment(4, 24, 27); //past + } + else if (minute <= 14) + { + strip.setSegment(5, 6, 8); // ten past + strip.setSegment(4, 24, 27); //past + } + else if (minute <= 19) + { + strip.setSegment(5, 8, 12); // quarter past + strip.setSegment(3, 0, 0); //minutes + strip.setSegment(4, 24, 27); //past + } + else if (minute <= 24) + { + strip.setSegment(5, 12, 16); // twenty past + strip.setSegment(4, 24, 27); //past + } + else if (minute <= 29) + { + strip.setSegment(5, 12, 18); // twenty-five past + strip.setSegment(4, 24, 27); //past + } + else if (minute <= 34) + { + strip.setSegment(5, 3, 6); // half past + strip.setSegment(3, 0, 0); //minutes + strip.setSegment(4, 24, 27); //past + } + else if (minute <= 39) + { + strip.setSegment(5, 12, 18); // twenty-five to + strip.setSegment(6, 22, 24); //to + } + else if (minute <= 44) + { + strip.setSegment(5, 12, 16); // twenty to + strip.setSegment(6, 22, 24); //to + } + else if (minute <= 49) + { + strip.setSegment(5, 8, 12); // quarter to + strip.setSegment(3, 0, 0); //minutes + strip.setSegment(6, 22, 24); //to + } + else if (minute <= 54) + { + strip.setSegment(5, 6, 8); // ten to + strip.setSegment(6, 22, 24); //to + } + else if (minute <= 59) + { + strip.setSegment(5, 16, 18); // five to + strip.setSegment(6, 22, 24); //to + } + + //hours + if (hour > 23) + return; + if (isToHour) + hour++; + if (hour > 12) + hour -= 12; + if (hour == 0) + hour = 12; + + switch (hour) + { + case 1: + strip.setSegment(7, 27, 29); + break; //one + case 2: + strip.setSegment(7, 35, 37); + break; //two + case 3: + strip.setSegment(7, 29, 32); + break; //three + case 4: + strip.setSegment(7, 32, 35); + break; //four + case 5: + strip.setSegment(7, 37, 40); + break; //five + case 6: + strip.setSegment(7, 43, 45); + break; //six + case 7: + strip.setSegment(7, 40, 43); + break; //seven + case 8: + strip.setSegment(7, 45, 48); + break; //eight + case 9: + strip.setSegment(7, 48, 50); + break; //nine + case 10: + strip.setSegment(7, 54, 56); + break; //ten + case 11: + strip.setSegment(7, 50, 54); + break; //eleven + case 12: + strip.setSegment(7, 56, 60); + break; //twelve + } + +selectWordSegments(true); +applyMacro(1); +} + +void timeOfDay() { +// NOT USED: use timed macros instead + //Used to set brightness dependant of time of day - lights dimmed at night + + //monday to thursday and sunday + + if ((weekday(localTime) == 6) | (weekday(localTime) == 7)) { + if (hour(localTime) > 0 | hour(localTime) < 8) { + strip.setBrightness(nightBrightness); + } + else { + strip.setBrightness(dayBrightness); + } + } + else { + if (hour(localTime) < 6 | hour(localTime) >= 22) { + strip.setBrightness(nightBrightness); + } + else { + strip.setBrightness(dayBrightness); + } + } +} + +//loop. You can use "if (WLED_CONNECTED)" to check for successful connection +void userLoop() +{ + if (minute(localTime) != minuteLast) + { + updateLocalTime(); + //timeOfDay(); + minuteLast = minute(localTime); + displayTime(hour(localTime), minute(localTime)); + if (minute(localTime) == 0){ + hourChime(); + } + if (minute(localTime) == 1){ + //turn off background segment; + strip.getSegment(0).setOption(2, false); + //applyPreset(13); + } + } +} diff --git a/wled00/FX.cpp b/wled00/FX.cpp index bbc63cc45..72f884bc2 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -25,17 +25,17 @@ */ #include "FX.h" +#include "tv_colors.h" #define IBN 5100 #define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3) - /* * No blinking. Just plain old static light. */ uint16_t WS2812FX::mode_static(void) { fill(SEGCOLOR(0)); - return (SEGMENT.getOption(7)) ? FRAMETIME : 500; //update faster if in transition + return (SEGMENT.getOption(SEG_OPTION_TRANSITIONAL)) ? FRAMETIME : 500; //update faster if in transition } @@ -233,9 +233,9 @@ uint16_t WS2812FX::mode_random_color(void) { /* * Lights every LED in a random color. Changes all LED at the same time -// * to new random colors. + * to new random colors. */ -uint16_t WS2812FX::mode_dynamic(void) { +uint16_t WS2812FX::dynamic(boolean smooth=false) { if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed if(SEGENV.call == 0) { @@ -252,12 +252,31 @@ uint16_t WS2812FX::mode_dynamic(void) { SEGENV.step = it; } - for (uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_wheel(SEGENV.data[i])); - } + if (smooth) { + for (uint16_t i = 0; i < SEGLEN; i++) { + blendPixelColor(i, color_wheel(SEGENV.data[i]),16); + } + } else { + for (uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_wheel(SEGENV.data[i])); + } + } return FRAMETIME; } +/* + * Original effect "Dynamic" + */ +uint16_t WS2812FX::mode_dynamic(void) { + return dynamic(false); +} + +/* + * effect "Dynamic" with smoth color-fading + */ +uint16_t WS2812FX::mode_dynamic_smooth(void) { + return dynamic(true); + } /* * Does the "standby-breathing" of well known i-Devices. @@ -379,7 +398,7 @@ uint16_t WS2812FX::mode_rainbow_cycle(void) { /* * theater chase function */ -uint16_t WS2812FX::theater_chase(uint32_t color1, uint32_t color2, bool dopalette) { +uint16_t WS2812FX::theater_chase(uint32_t color1, uint32_t color2, bool do_palette) { byte gap = 2 + ((255 - SEGMENT.intensity) >> 5); uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*2; uint32_t it = now / cycleTime; @@ -391,7 +410,7 @@ uint16_t WS2812FX::theater_chase(uint32_t color1, uint32_t color2, bool dopalett for(uint16_t i = 0; i < SEGLEN; i++) { if((i % gap) == SEGENV.aux0) { - if (dopalette) + if (do_palette) { setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } else { @@ -540,7 +559,7 @@ uint16_t WS2812FX::dissolve(uint32_t color) { } } - if (SEGENV.call > (255 - SEGMENT.speed) + 15) + if (SEGENV.call > (255 - SEGMENT.speed) + 15U) { SEGENV.aux0 = !SEGENV.aux0; SEGENV.call = 0; @@ -588,7 +607,7 @@ uint16_t WS2812FX::mode_sparkle(void) { /* - * Lights all LEDs in the color. Flashes single white pixels randomly. + * Lights all LEDs in the color. Flashes single col 1 pixels randomly. (List name: Sparkle Dark) * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ uint16_t WS2812FX::mode_flash_sparkle(void) { @@ -596,12 +615,14 @@ uint16_t WS2812FX::mode_flash_sparkle(void) { setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } - if(random8(5) == 0) { - SEGENV.aux0 = random16(SEGLEN); // aux0 stores the random led index - setPixelColor(SEGENV.aux0, SEGCOLOR(1)); - return 20; - } - return 20 + (uint16_t)(255-SEGMENT.speed); + if (now - SEGENV.aux0 > SEGENV.step) { + if(random8((255-SEGMENT.intensity) >> 4) == 0) { + setPixelColor(random16(SEGLEN), SEGCOLOR(1)); //flash + } + SEGENV.step = now; + SEGENV.aux0 = 255-SEGMENT.speed; + } + return FRAMETIME; } @@ -614,13 +635,16 @@ uint16_t WS2812FX::mode_hyper_sparkle(void) { setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } - if(random8(5) < 2) { - for(uint16_t i = 0; i < max(1, SEGLEN/3); i++) { - setPixelColor(random16(SEGLEN), SEGCOLOR(1)); + if (now - SEGENV.aux0 > SEGENV.step) { + if(random8((255-SEGMENT.intensity) >> 4) == 0) { + for(uint16_t i = 0; i < MAX(1, SEGLEN/3); i++) { + setPixelColor(random16(SEGLEN), SEGCOLOR(1)); + } } - return 20; + SEGENV.step = now; + SEGENV.aux0 = 255-SEGMENT.speed; } - return 20 + (uint16_t)(255-SEGMENT.speed); + return FRAMETIME; } @@ -631,22 +655,25 @@ uint16_t WS2812FX::mode_multi_strobe(void) { for(uint16_t i = 0; i < SEGLEN; i++) { setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } - //blink(SEGCOLOR(0), SEGCOLOR(1), true, true); - uint16_t delay = 50 + 20*(uint16_t)(255-SEGMENT.speed); - uint16_t count = 2 * ((SEGMENT.speed / 10) + 1); - if(SEGENV.step < count) { - if((SEGENV.step & 1) == 0) { - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, SEGCOLOR(0)); - } - delay = 20; + SEGENV.aux0 = 50 + 20*(uint16_t)(255-SEGMENT.speed); + uint16_t count = 2 * ((SEGMENT.intensity / 10) + 1); + if(SEGENV.aux1 < count) { + if((SEGENV.aux1 & 1) == 0) { + fill(SEGCOLOR(0)); + SEGENV.aux0 = 15; } else { - delay = 50; + SEGENV.aux0 = 50; } } - SEGENV.step = (SEGENV.step + 1) % (count + 1); - return delay; + + if (now - SEGENV.aux0 > SEGENV.step) { + SEGENV.aux1++; + if (SEGENV.aux1 > count) SEGENV.aux1 = 0; + SEGENV.step = now; + } + + return FRAMETIME; } /* @@ -704,58 +731,68 @@ uint16_t WS2812FX::mode_android(void) { * color1 = background color * color2 and color3 = colors of two adjacent leds */ -uint16_t WS2812FX::chase(uint32_t color1, uint32_t color2, uint32_t color3, bool dopalette) { +uint16_t WS2812FX::chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { uint16_t counter = now * ((SEGMENT.speed >> 2) + 1); 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 = get_random_wheel_index(SEGENV.aux0); + } + color1 = color_wheel(SEGENV.aux0); + } SEGENV.step = a; - uint8_t size = 1 + (SEGMENT.intensity * SEGLEN >> 10); - if (SEGENV.call == 0) {SEGENV.aux0 = 0; SEGENV.aux1 = a;} + // Use intensity setting to vary chase up to 1/2 string length - uint16_t b = (a + size) % SEGLEN; - uint16_t c = (b + size) % SEGLEN; + uint8_t size = 1 + (SEGMENT.intensity * SEGLEN >> 10); - if (dopalette) color1 = color_from_palette(a, true, PALETTE_SOLID_WRAP, 1); + uint16_t b = a + size; //"trail" of chase, filled with color1 + if (b > SEGLEN) b -= SEGLEN; + uint16_t c = b + size; + if (c > SEGLEN) c -= SEGLEN; - setPixelColor(a, color1); - if (SEGENV.aux0 == 0) { // catch the first pixels after color change from "chase random" (because they have the "old" color) - for (uint16_t i = 0; i < a; i++) { - uint32_t color = getPixelColor(0); - setPixelColor(i, color1); + //background + if (do_palette) + { + for(uint16_t i = 0; i < SEGLEN; i++) { + setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } - SEGENV.aux0 = 1; - } - setPixelColor(b, color2); - setPixelColor(c, color3); + } else fill(color1); - if (a != SEGENV.aux1) { // when speed is too fast, this catches the gaps - if (a > SEGENV.aux1) { - for (uint16_t i = SEGENV.aux1; i <= a; i++) { // sometimes the step-length varies from one to the next call - therefor "<= a" and not "< a" - setPixelColor(i, color1); - uint16_t b1 = (i + size) % SEGLEN; - uint16_t c1 = (b1 + size) % SEGLEN; - setPixelColor(b1, color2); - setPixelColor(c1, color3); - } - } else { - for (uint16_t i = SEGENV.aux1; i <= SEGLEN; i++) { // from last position to the end - setPixelColor(i, color1); - uint16_t b1 = (i + size) % SEGLEN; - uint16_t c1 = (b1 + size) % SEGLEN; - setPixelColor(b1, color2); - setPixelColor(c1, color3); - } - for (uint16_t i = 0; i < a; i++) { // from 0 to the actual position - setPixelColor(i, color1); - uint16_t b1 = (i + size) % SEGLEN; - uint16_t c1 = (b1 + size) % SEGLEN; - setPixelColor(b1, color2); - setPixelColor(c1, color3); - } - SEGENV.step = 0; - SEGENV.aux0 = 0; - } + //if random, fill old background between a and end + if (chase_random) + { + color1 = color_wheel(SEGENV.aux1); + for (uint16_t i = a; i < SEGLEN; i++) + setPixelColor(i, color1); + } + + //fill between points a and b with color2 + if (a < b) + { + for (uint16_t i = a; i < b; i++) + setPixelColor(i, color2); + } else { + for (uint16_t i = a; i < SEGLEN; i++) //fill until end + setPixelColor(i, color2); + for (uint16_t i = 0; i < b; i++) //fill from start until b + setPixelColor(i, color2); + } + + //fill between points b and c with color2 + if (b < c) + { + for (uint16_t i = b; i < c; i++) + setPixelColor(i, color3); + } else { + for (uint16_t i = b; i < SEGLEN; i++) //fill until end + setPixelColor(i, color3); + for (uint16_t i = 0; i < c; i++) //fill from start until c + setPixelColor(i, color3); } - SEGENV.aux1 = ++a; return FRAMETIME; } @@ -765,7 +802,7 @@ uint16_t WS2812FX::chase(uint32_t color1, uint32_t color2, uint32_t color3, bool * Bicolor chase, more primary color. */ uint16_t WS2812FX::mode_chase_color(void) { - return chase(SEGCOLOR(1), SEGCOLOR(0), SEGCOLOR(0), true); + return chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), true); } @@ -773,12 +810,20 @@ uint16_t WS2812FX::mode_chase_color(void) { * Primary running followed by random color. */ uint16_t WS2812FX::mode_chase_random(void) { - if (!SEGENV.allocateData(2)) return mode_static(); //allocation failed - if (SEGENV.call == 0) SEGENV.data[0] = 0; - if (SEGENV.step == 0) { - SEGENV.data[0] = get_random_wheel_index(SEGENV.data[0]); - } - return chase(color_wheel(SEGENV.data[0]), SEGCOLOR(0), SEGCOLOR(0), false); + return chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), false); +} + + +/* + * Primary, secondary running on rainbow. + */ +uint16_t WS2812FX::mode_chase_rainbow(void) { + uint8_t color_sep = 256 / SEGLEN; + if (color_sep == 0) color_sep = 1; // correction for segments longer than 256 LEDs + uint8_t color_index = SEGENV.call & 0xFF; + uint32_t color = color_wheel(((SEGENV.step * color_sep) + color_index) & 0xFF); + + return chase(color, SEGCOLOR(0), SEGCOLOR(1), false); } @@ -799,46 +844,40 @@ uint16_t WS2812FX::mode_chase_rainbow_white(void) { * Red - Amber - Green - Blue lights running */ uint16_t WS2812FX::mode_colorful(void) { - uint32_t cols[]{0x00FF0000,0x00EEBB00,0x0000EE00,0x000077CC,0x00FF0000,0x00EEBB00,0x0000EE00}; - if (SEGMENT.intensity < 127) //pastel (easter) colors + uint8_t numColors = 4; //3, 4, or 5 + uint32_t cols[9]{0x00FF0000,0x00EEBB00,0x0000EE00,0x000077CC}; + if (SEGMENT.intensity > 160 || SEGMENT.palette) { //palette or color + if (!SEGMENT.palette) { + numColors = 3; + for (uint8_t i = 0; i < 3; i++) cols[i] = SEGCOLOR(i); + } else { + uint16_t fac = 80; + if (SEGMENT.palette == 52) {numColors = 5; fac = 61;} //C9 2 has 5 colors + for (uint8_t i = 0; i < numColors; i++) { + cols[i] = color_from_palette(i*fac, false, true, 255); + } + } + } else if (SEGMENT.intensity < 80) //pastel (easter) colors { cols[0] = 0x00FF8040; cols[1] = 0x00E5D241; cols[2] = 0x0077FF77; cols[3] = 0x0077F0F0; - for (uint8_t i = 4; i < 7; i++) cols[i] = cols[i-4]; } + for (uint8_t i = numColors; i < numColors*2 -1; i++) cols[i] = cols[i-numColors]; - uint32_t cycleTime = 50 + (15 * (uint32_t)(255 - SEGMENT.speed)); + uint32_t cycleTime = 50 + (8 * (uint32_t)(255 - SEGMENT.speed)); uint32_t it = now / cycleTime; if (it != SEGENV.step) { if (SEGMENT.speed > 0) SEGENV.aux0++; - if (SEGENV.aux0 > 3) SEGENV.aux0 = 0; + if (SEGENV.aux0 >= numColors) SEGENV.aux0 = 0; SEGENV.step = it; } - uint16_t i = 0; - for (i; i < SEGLEN -3; i+=4) + for (uint16_t i = 0; i < SEGLEN; i+= numColors) { - setPixelColor(i, cols[SEGENV.aux0]); - setPixelColor(i+1, cols[SEGENV.aux0+1]); - setPixelColor(i+2, cols[SEGENV.aux0+2]); - setPixelColor(i+3, cols[SEGENV.aux0+3]); - } - if(i < SEGLEN) - { - setPixelColor(i, cols[SEGENV.aux0]); - - if(i+1 < SEGLEN) - { - setPixelColor(i+1, cols[SEGENV.aux0+1]); - - if(i+2 < SEGLEN) - { - setPixelColor(i+2, cols[SEGENV.aux0+2]); - } - } + for (uint16_t j = 0; j < numColors; j++) setPixelColor(i + j, cols[SEGENV.aux0 + j]); } return FRAMETIME; @@ -866,6 +905,7 @@ uint16_t WS2812FX::mode_traffic_light(void) { if (now - SEGENV.step > mdelay) { SEGENV.aux0++; + if (SEGENV.aux0 == 1 && SEGMENT.intensity > 140) SEGENV.aux0 = 2; //skip Red + Amber, to get US-style sequence if (SEGENV.aux0 > 3) SEGENV.aux0 = 0; SEGENV.step = now; } @@ -874,18 +914,6 @@ uint16_t WS2812FX::mode_traffic_light(void) { } -/* - * Primary, secondary running on rainbow. - */ -uint16_t WS2812FX::mode_chase_rainbow(void) { - uint8_t color_sep = 256 / SEGLEN; - uint8_t color_index = SEGENV.call & 0xFF; - uint32_t color = color_wheel(((SEGENV.step * color_sep) + color_index) & 0xFF); - - return chase(color, SEGCOLOR(0), SEGCOLOR(1), 0); -} - - /* * Sec flashes running on prim. */ @@ -987,23 +1015,13 @@ uint16_t WS2812FX::mode_running_color(void) { return running(SEGCOLOR(0), SEGCOLOR(1)); } - /* - * Alternating red/blue pixels running. + * Alternating red/white pixels running. */ -uint16_t WS2812FX::mode_running_red_blue(void) { - return running(RED, BLUE); +uint16_t WS2812FX::mode_candy_cane(void) { + return running(RED, WHITE); } - -/* - * Alternating red/green pixels running. - */ -uint16_t WS2812FX::mode_merry_christmas(void) { - return running(RED, GREEN); -} - - /* * Alternating orange/purple pixels running. */ @@ -1030,7 +1048,7 @@ uint16_t WS2812FX::mode_running_random(void) { } SEGENV.step++; - if (SEGENV.step > ((255-SEGMENT.intensity) >> 4)) + if (SEGENV.step > (uint8_t)((255-SEGMENT.intensity) >> 4)) { SEGENV.step = 0; } @@ -1124,7 +1142,7 @@ uint16_t WS2812FX::mode_fireworks() { if (valid1) setPixelColor(SEGENV.aux0 , sv1); if (valid2) setPixelColor(SEGENV.aux1, sv2); - for(uint16_t i=0; i> 1)) == 0) { uint16_t index = random(SEGLEN); setPixelColor(index, color_from_palette(random8(), false, false, 0)); @@ -1142,12 +1160,12 @@ uint16_t WS2812FX::mode_rain() SEGENV.step += FRAMETIME; if (SEGENV.step > SPEED_FORMULA_L) { SEGENV.step = 0; - //shift all leds right - uint32_t ctemp = getPixelColor(SEGLEN -1); - for(uint16_t i = SEGLEN -1; i > 0; i--) { - setPixelColor(i, getPixelColor(i-1)); + //shift all leds left + uint32_t ctemp = getPixelColor(0); + for(uint16_t i = 0; i < SEGLEN - 1; i++) { + setPixelColor(i, getPixelColor(i+1)); } - setPixelColor(0, ctemp); + setPixelColor(SEGLEN -1, ctemp); SEGENV.aux0++; SEGENV.aux1++; if (SEGENV.aux0 == 0) SEGENV.aux0 = UINT16_MAX; @@ -1171,12 +1189,12 @@ uint16_t WS2812FX::mode_fire_flicker(void) { byte r = (SEGCOLOR(0) >> 16) & 0xFF; byte g = (SEGCOLOR(0) >> 8) & 0xFF; byte b = (SEGCOLOR(0) & 0xFF); - byte lum = (SEGMENT.palette == 0) ? max(w, max(r, max(g, b))) : 255; + byte lum = (SEGMENT.palette == 0) ? MAX(w, MAX(r, MAX(g, b))) : 255; lum /= (((256-SEGMENT.intensity)/16)+1); for(uint16_t i = 0; i < SEGLEN; i++) { byte flicker = random8(lum); if (SEGMENT.palette == 0) { - setPixelColor(i, max(r - flicker, 0), max(g - flicker, 0), max(b - flicker, 0), max(w - flicker, 0)); + setPixelColor(i, MAX(r - flicker, 0), MAX(g - flicker, 0), MAX(b - flicker, 0), MAX(w - flicker, 0)); } else { setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, 255 - flicker)); } @@ -1206,7 +1224,7 @@ uint16_t WS2812FX::gradient_base(bool 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; setPixelColor(i, color_blend(SEGCOLOR(0), color_from_palette(i, true, PALETTE_SOLID_WRAP, 1), val)); @@ -1253,7 +1271,7 @@ uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, bool all) for (uint16_t i = idexB; i < idexR; i++) setPixelColor(i, color2); } } else { //regular dot-only mode - uint8_t size = 1 + SEGMENT.intensity >> 3; + uint8_t size = 1 + (SEGMENT.intensity >> 3); if (size > SEGLEN/2) size = 1+ SEGLEN/2; for (uint8_t i=0; i <= size; i++) { setPixelColor(idexR+i, color1); @@ -1564,9 +1582,9 @@ uint16_t WS2812FX::mode_oscillate(void) if (SEGENV.call == 0) { - oscillators[0] = {SEGLEN/4, SEGLEN/8, 1, 1}; - oscillators[1] = {SEGLEN/4*3, SEGLEN/8, 1, 2}; - oscillators[2] = {SEGLEN/4*2, SEGLEN/8, -1, 1}; + oscillators[0] = {(int16_t)(SEGLEN/4), (int8_t)(SEGLEN/8), 1, 1}; + oscillators[1] = {(int16_t)(SEGLEN/4*3), (int8_t)(SEGLEN/8), 1, 2}; + oscillators[2] = {(int16_t)(SEGLEN/4*2), (int8_t)(SEGLEN/8), -1, 1}; } uint32_t cycleTime = 20 + (2 * (uint32_t)(255 - SEGMENT.speed)); @@ -1607,40 +1625,42 @@ uint16_t WS2812FX::mode_oscillate(void) uint16_t WS2812FX::mode_lightning(void) { uint16_t ledstart = random16(SEGLEN); // Determine starting location of flash - uint16_t ledlen = random16(SEGLEN -1 -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) + uint16_t ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) uint8_t bri = 255/random8(1, 3); - if (SEGENV.step == 0) + if (SEGENV.aux1 == 0) //init, leader flash { - SEGENV.aux0 = random8(3, 3 + SEGMENT.intensity/20); //number of flashes - bri = 52; - SEGENV.aux1 = 1; + SEGENV.aux1 = random8(4, 4 + SEGMENT.intensity/20); //number of flashes + SEGENV.aux1 *= 2; + + bri = 52; //leader has lower brightness + SEGENV.aux0 = 200; //200ms delay after leader } fill(SEGCOLOR(1)); - if (SEGENV.aux1) { + if (SEGENV.aux1 > 3 && !(SEGENV.aux1 & 0x01)) { //flash on even number >2 for (int i = ledstart; i < ledstart + ledlen; i++) { - if (SEGMENT.palette == 0) - { - setPixelColor(i,bri,bri,bri,bri); - } else { - setPixelColor(i,color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, bri)); - } + setPixelColor(i,color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, bri)); + } + SEGENV.aux1--; + + SEGENV.step = millis(); + //return random8(4, 10); // each flash only lasts one frame/every 24ms... originally 4-10 milliseconds + } else { + if (millis() - SEGENV.step > SEGENV.aux0) { + SEGENV.aux1--; + if (SEGENV.aux1 < 2) SEGENV.aux1 = 0; + + SEGENV.aux0 = (50 + random8(100)); //delay between flashes + if (SEGENV.aux1 == 2) { + SEGENV.aux0 = (random8(255 - SEGMENT.speed) * 100); // delay between strikes + } + SEGENV.step = millis(); } - SEGENV.aux1 = 0; - SEGENV.step++; - return random8(4, 10); // each flash only lasts 4-10 milliseconds } - - SEGENV.aux1 = 1; - if (SEGENV.step == 1) return (200); // longer delay until next flash after the leader - - if (SEGENV.step <= SEGENV.aux0) return (50 + random8(100)); // shorter delay between strokes - - SEGENV.step = 0; - return (random8(255 - SEGMENT.speed) * 100); // delay between strikes + return FRAMETIME; } @@ -1767,19 +1787,22 @@ uint16_t WS2812FX::mode_fire_2012() if (it != SEGENV.step) { + uint8_t ignition = max(7,SEGLEN/10); // ignition area: 10% of segment length or minimum 7 pixels + // Step 1. Cool down every cell a little for (uint16_t i = 0; i < SEGLEN; i++) { - SEGENV.data[i] = qsub8(heat[i], random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2)); + uint8_t temp = qsub8(heat[i], random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2)); + heat[i] = (temp==0 && i 1; k--) { - heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3; + heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 } // Step 3. Randomly ignite new 'sparks' of heat near the bottom if (random8() <= SEGMENT.intensity) { - uint8_t y = random8(7); + uint8_t y = random8(ignition); if (y < SEGLEN) heat[y] = qadd8(heat[y], random8(160,255)); } SEGENV.step = it; @@ -1787,7 +1810,7 @@ uint16_t WS2812FX::mode_fire_2012() // Step 4. Map from heat cells to LED colors for (uint16_t j = 0; j < SEGLEN; j++) { - CRGB color = ColorFromPalette(currentPalette, min(heat[j],240), 255, LINEARBLEND); + CRGB color = ColorFromPalette(currentPalette, MIN(heat[j],240), 255, LINEARBLEND); setPixelColor(j, color.red, color.green, color.blue); } return FRAMETIME; @@ -1910,7 +1933,6 @@ uint16_t WS2812FX::mode_noise16_2() for (uint16_t i = 0; i < SEGLEN; i++) { uint16_t shift_x = SEGENV.step >> 6; // x as a function of time - uint16_t shift_y = SEGENV.step/42; uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field @@ -1974,7 +1996,7 @@ uint16_t WS2812FX::mode_colortwinkle() if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed CRGB fastled_col, prev; - fract8 fadeUpAmount = 8 + (SEGMENT.speed/4), fadeDownAmount = 5 + (SEGMENT.speed/7); + fract8 fadeUpAmount = _brightness>28 ? 8 + (SEGMENT.speed>>2) : 68-_brightness, fadeDownAmount = _brightness>28 ? 8 + (SEGMENT.speed>>3) : 68-_brightness; for (uint16_t i = 0; i < SEGLEN; i++) { fastled_col = col_to_crgb(getPixelColor(i)); prev = fastled_col; @@ -2255,7 +2277,7 @@ uint16_t WS2812FX::mode_ripple_rainbow(void) { CRGB WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) { // Overall twinkle speed (changed) - uint16_t ticks = ms / (32 - (SEGMENT.speed >> 3)); + uint16_t ticks = ms / SEGENV.aux0; uint8_t fastcycle8 = ticks; uint16_t slowcycle16 = (ticks >> 8) + salt; slowcycle16 += sin8(slowcycle16); @@ -2320,10 +2342,11 @@ uint16_t WS2812FX::twinklefox_base(bool cat) // numbers that it generates is (paradoxically) stable. uint16_t PRNG16 = 11337; + // Calculate speed + if (SEGMENT.speed > 100) SEGENV.aux0 = 3 + ((255 - SEGMENT.speed) >> 3); + else SEGENV.aux0 = 22 + ((100 - SEGMENT.speed) >> 1); + // Set up the background color, "bg". - // if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of - // the current palette are identical, then a deeply faded version of - // that color is used for the background color CRGB bg; bg = col_to_crgb(SEGCOLOR(1)); uint8_t bglight = bg.getAverageLight(); @@ -2582,7 +2605,7 @@ uint16_t WS2812FX::mode_bouncing_balls(void) { uint32_t color = SEGCOLOR(0); if (SEGMENT.palette) { - color = color_wheel(i*(256/max(numBalls, 8))); + color = color_wheel(i*(256/MAX(numBalls, 8))); } else if (hasCol2) { color = SEGCOLOR(i % NUM_COLORS); } @@ -2728,50 +2751,92 @@ uint16_t WS2812FX::mode_popcorn(void) { //Inspired by https://github.com/avanhanegem/ArduinoCandleEffectNeoPixel //and https://cpldcpu.wordpress.com/2016/01/05/reverse-engineering-a-real-candle/ -uint16_t WS2812FX::mode_candle() +uint16_t WS2812FX::candle(bool multi) { - if (SEGENV.call == 0) { - SEGENV.aux0 = 128; SEGENV.aux1 = 132; SEGENV.step = 1; - } - bool newTarget = false; - - uint8_t s = SEGENV.aux0, target = SEGENV.aux1, fadeStep = SEGENV.step; - - if (target > s) { //fade up - s = qadd8(s, fadeStep); - if (s >= target) newTarget = true; - } else { - s = qsub8(s, fadeStep); - if (s <= target) newTarget = true; - } - SEGENV.aux0 = s; - - for (uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255-s)); - } - - if (newTarget) + if (multi) { - uint8_t valrange = SEGMENT.intensity; - uint8_t rndval = valrange >> 1; - target = random8(rndval) + random8(rndval); - if (target < (rndval >> 1)) target = (rndval >> 1) + random8(rndval); - uint8_t offset = (255 - valrange) >> 1; - target += offset; + //allocate segment data + uint16_t dataSize = (SEGLEN -1) *3; + if (!SEGENV.allocateData(dataSize)) return candle(false); //allocation failed + } - uint8_t dif = (target > s) ? target - s : s - target; - - //how much to move closer to target per frame - fadeStep = dif >> 2; //mode called every ~25 ms, so 4 frames to have a new target every 100ms - if (fadeStep == 0) fadeStep = 1; + //max. flicker range controlled by intensity + uint8_t valrange = SEGMENT.intensity; + uint8_t rndval = valrange >> 1; //max 127 + + //step (how much to move closer to target per frame) coarsely set by speed + uint8_t speedFactor = 4; + if (SEGMENT.speed > 252) { //epilepsy + speedFactor = 1; + } else if (SEGMENT.speed > 99) { //regular candle (mode called every ~25 ms, so 4 frames to have a new target every 100ms) + speedFactor = 2; + } else if (SEGMENT.speed > 49) { //slower fade + speedFactor = 3; + } //else 4 (slowest) + + uint16_t numCandles = (multi) ? SEGLEN : 1; + + for (uint16_t i = 0; i < numCandles; i++) + { + uint16_t d = 0; //data location + + uint8_t s = SEGENV.aux0, s_target = SEGENV.aux1, fadeStep = SEGENV.step; + if (i > 0) { + d = (i-1) *3; + s = SEGENV.data[d]; s_target = SEGENV.data[d+1]; fadeStep = SEGENV.data[d+2]; + } + if (fadeStep == 0) { //init vals + s = 128; s_target = 130 + random8(4); fadeStep = 1; + } + + bool newTarget = false; + if (s_target > s) { //fade up + s = qadd8(s, fadeStep); + if (s >= s_target) newTarget = true; + } else { + s = qsub8(s, fadeStep); + if (s <= s_target) newTarget = true; + } + + if (newTarget) { + s_target = random8(rndval) + random8(rndval); //between 0 and rndval*2 -2 = 252 + if (s_target < (rndval >> 1)) s_target = (rndval >> 1) + random8(rndval); + uint8_t offset = (255 - valrange); + s_target += offset; + + uint8_t dif = (s_target > s) ? s_target - s : s - s_target; - SEGENV.step = fadeStep; - SEGENV.aux1 = target; + fadeStep = dif >> speedFactor; + if (fadeStep == 0) fadeStep = 1; + } + + if (i > 0) { + setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s)); + + SEGENV.data[d] = s; SEGENV.data[d+1] = s_target; SEGENV.data[d+2] = fadeStep; + } else { + for (uint16_t j = 0; j < SEGLEN; j++) { + setPixelColor(j, color_blend(SEGCOLOR(1), color_from_palette(j, true, PALETTE_SOLID_WRAP, 0), s)); + } + + SEGENV.aux0 = s; SEGENV.aux1 = s_target; SEGENV.step = fadeStep; + } } return FRAMETIME; } +uint16_t WS2812FX::mode_candle() +{ + return candle(false); +} + + +uint16_t WS2812FX::mode_candle_multi() +{ + return candle(true); +} + /* / Fireworks in starburst effect @@ -2910,9 +2975,9 @@ uint16_t WS2812FX::mode_exploding_fireworks(void) fill(BLACK); - bool actuallyReverse = SEGMENT.getOption(1); + bool actuallyReverse = SEGMENT.getOption(SEG_OPTION_REVERSED); //have fireworks start in either direction based on intensity - SEGMENT.setOption(1, SEGENV.step); + SEGMENT.setOption(SEG_OPTION_REVERSED, SEGENV.step); Spark* sparks = reinterpret_cast(SEGENV.data); Spark* flare = sparks; //first spark is flare data @@ -3003,7 +3068,7 @@ uint16_t WS2812FX::mode_exploding_fireworks(void) } } - SEGMENT.setOption(1, actuallyReverse); + SEGMENT.setOption(SEG_OPTION_REVERSED, actuallyReverse); return FRAMETIME; } @@ -3030,7 +3095,7 @@ uint16_t WS2812FX::mode_drip(void) gravity *= SEGLEN; int sourcedrop = 12; - for (int j=0;j255) drops[j].col=255; - setPixelColor(int(drops[j].pos),color_blend(BLACK,SEGCOLOR(0),drops[j].col)); + setPixelColor(uint16_t(drops[j].pos),color_blend(BLACK,SEGCOLOR(0),drops[j].col)); drops[j].col += map(SEGMENT.speed, 0, 255, 1, 6); // swelling @@ -3056,8 +3121,9 @@ uint16_t WS2812FX::mode_drip(void) if (drops[j].pos < 0) drops[j].pos = 0; drops[j].vel += gravity; - for (int i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets - setPixelColor(int(drops[j].pos)+i,color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling + for (uint16_t i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets + uint16_t pos = uint16_t(drops[j].pos) +i; //this is BAD, returns a pos >= SEGLEN occasionally + setPixelColor(pos,color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling } if (drops[j].colIndex > 2) { // during bounce, some water is on the floor @@ -3084,6 +3150,59 @@ uint16_t WS2812FX::mode_drip(void) } +/* + * Tetris or Stacking (falling bricks) Effect + * by Blaz Kristan (https://github.com/blazoncek, https://blaz.at/home) + */ +typedef struct Tetris { + float pos; + float speed; + uint32_t col; +} tetris; + +uint16_t WS2812FX::mode_tetrix(void) { + + uint16_t dataSize = sizeof(tetris); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Tetris* drop = reinterpret_cast(SEGENV.data); + + // initialize dropping on first call or segment full + if (SEGENV.call == 0 || SEGENV.aux1 >= SEGLEN) { + SEGENV.aux1 = 0; // reset brick stack size + SEGENV.step = 0; + fill(SEGCOLOR(1)); + return 250; // short wait + } + + if (SEGENV.step == 0) { //init + drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>3)+1 : random8(6,40)); // set speed + drop->pos = SEGLEN-1; // start at end of segment + drop->col = color_from_palette(random8(0,15)<<4,false,false,0); // limit color choices so there is enough HUE gap + SEGENV.step = 1; // drop state (0 init, 1 forming, 2 falling) + SEGENV.aux0 = (SEGMENT.intensity ? (SEGMENT.intensity>>5)+1 : random8(1,5)) * (1+(SEGLEN>>6)); // size of brick + } + + if (SEGENV.step == 1) { // forming + if (random8()>>6) { // random drop + SEGENV.step = 2; // fall + } + } + + if (SEGENV.step > 1) { // falling + if (drop->pos > SEGENV.aux1) { // fall until top of stack + drop->pos -= drop->speed; // may add gravity as: speed += gravity + if (int(drop->pos) < SEGENV.aux1) drop->pos = SEGENV.aux1; + for (uint16_t i=int(drop->pos); ipos)+SEGENV.aux0 ? drop->col : SEGCOLOR(1)); + } else { // we hit bottom + SEGENV.step = 0; // go back to init + SEGENV.aux1 += SEGENV.aux0; // increase the stack size + if (SEGENV.aux1 >= SEGLEN) return 1000; // wait for a second + } + } + return FRAMETIME; +} + + /* / Plasma Effect / adapted from https://github.com/atuline/FastLED-Demos/blob/master/plasma/plasma.ino @@ -3093,8 +3212,8 @@ uint16_t WS2812FX::mode_plasma(void) { uint8_t thatPhase = beatsin8(7,-64,64); for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set color & brightness based on a wave as follows: - uint8_t colorIndex = cubicwave8((i*(1+ 3*(SEGMENT.speed >> 5)))+(thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change. - + cos8((i*(1+ 2*(SEGMENT.speed >> 5)))+(thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. + uint8_t colorIndex = cubicwave8(((i*(1+ 3*(SEGMENT.speed >> 5)))+(thisPhase)) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change. + + cos8(((i*(1+ 2*(SEGMENT.speed >> 5)))+(thatPhase)) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. uint8_t thisBright = qsub8(colorIndex, beatsin8(6,0, (255 - SEGMENT.intensity)|0x01 )); CRGB color = ColorFromPalette(currentPalette, colorIndex, thisBright, LINEARBLEND); setPixelColor(i, color.red, color.green, color.blue); @@ -3103,13 +3222,14 @@ uint16_t WS2812FX::mode_plasma(void) { return FRAMETIME; } + /* * Percentage display * Intesity values from 0-100 turn on the leds. */ uint16_t WS2812FX::mode_percent(void) { - uint8_t percent = max(0, min(200, SEGMENT.intensity)); + uint8_t percent = MAX(0, MIN(200, SEGMENT.intensity)); uint16_t active_leds = (percent < 100) ? SEGLEN * percent / 100.0 : SEGLEN * (200 - percent) / 100.0; @@ -3174,5 +3294,789 @@ uint16_t WS2812FX::mode_heartbeat(void) { setPixelColor(i, color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255 - (SEGENV.aux1 >> 8))); } + return FRAMETIME; +} + + +// "Pacifica" +// Gentle, blue-green ocean waves. +// December 2019, Mark Kriegsman and Mary Corey March. +// For Dan. +// +// +// In this animation, there are four "layers" of waves of light. +// +// Each layer moves independently, and each is scaled separately. +// +// All four wave layers are added together on top of each other, and then +// another filter is applied that adds "whitecaps" of brightness where the +// waves line up with each other more. Finally, another pass is taken +// over the led array to 'deepen' (dim) the blues and greens. +// +// The speed and scale and motion each layer varies slowly within independent +// hand-chosen ranges, which is why the code has a lot of low-speed 'beatsin8' functions +// with a lot of oddly specific numeric ranges. +// +// These three custom blue-green color palettes were inspired by the colors found in +// the waters off the southern coast of California, https://goo.gl/maps/QQgd97jjHesHZVxQ7 +// +// Modified for WLED, based on https://github.com/FastLED/FastLED/blob/master/examples/Pacifica/Pacifica.ino +// +uint16_t WS2812FX::mode_pacifica() +{ + uint32_t nowOld = now; + + CRGBPalette16 pacifica_palette_1 = + { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117, + 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x14554B, 0x28AA50 }; + CRGBPalette16 pacifica_palette_2 = + { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117, + 0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x0C5F52, 0x19BE5F }; + CRGBPalette16 pacifica_palette_3 = + { 0x000208, 0x00030E, 0x000514, 0x00061A, 0x000820, 0x000927, 0x000B2D, 0x000C33, + 0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF }; + + if (SEGMENT.palette) { + pacifica_palette_1 = currentPalette; + pacifica_palette_2 = currentPalette; + pacifica_palette_3 = currentPalette; + } + + // Increment the four "color index start" counters, one for each wave layer. + // Each is incremented at a different speed, and the speeds vary over time. + uint16_t sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step, sCIStart4 = SEGENV.step >> 16; + //static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; + //uint32_t deltams = 26 + (SEGMENT.speed >> 3); + uint32_t deltams = (FRAMETIME >> 2) + ((FRAMETIME * SEGMENT.speed) >> 7); + uint64_t deltat = (now >> 2) + ((now * SEGMENT.speed) >> 7); + now = deltat; + + uint16_t speedfactor1 = beatsin16(3, 179, 269); + uint16_t speedfactor2 = beatsin16(4, 179, 269); + uint32_t deltams1 = (deltams * speedfactor1) / 256; + uint32_t deltams2 = (deltams * speedfactor2) / 256; + uint32_t deltams21 = (deltams1 + deltams2) / 2; + sCIStart1 += (deltams1 * beatsin88(1011,10,13)); + sCIStart2 -= (deltams21 * beatsin88(777,8,11)); + sCIStart3 -= (deltams1 * beatsin88(501,5,7)); + sCIStart4 -= (deltams2 * beatsin88(257,4,6)); + SEGENV.aux0 = sCIStart1; SEGENV.aux1 = sCIStart2; + SEGENV.step = sCIStart4; SEGENV.step = (SEGENV.step << 16) + sCIStart3; + + // Clear out the LED array to a dim background blue-green + //fill(132618); + + uint8_t basethreshold = beatsin8( 9, 55, 65); + uint8_t wave = beat8( 7 ); + + for( uint16_t i = 0; i < SEGLEN; i++) { + CRGB c = CRGB(2, 6, 10); + // Render each of four layers, with different scales and speeds, that vary over time + c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130), 0-beat16(301)); + c += pacifica_one_layer(i, pacifica_palette_2, sCIStart2, beatsin16(4, 6 * 256, 9 * 256), beatsin8(17, 40, 80), beat16(401)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart3, 6 * 256 , beatsin8(9, 10,38) , 0-beat16(503)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart4, 5 * 256 , beatsin8(8, 10,28) , beat16(601)); + + // Add extra 'white' to areas where the four layers of light have lined up brightly + uint8_t threshold = scale8( sin8( wave), 20) + basethreshold; + wave += 7; + uint8_t l = c.getAverageLight(); + if (l > threshold) { + uint8_t overage = l - threshold; + uint8_t overage2 = qadd8(overage, overage); + c += CRGB(overage, overage2, qadd8(overage2, overage2)); + } + + //deepen the blues and greens + c.blue = scale8(c.blue, 145); + c.green = scale8(c.green, 200); + c |= CRGB( 2, 5, 7); + + setPixelColor(i, c.red, c.green, c.blue); + } + + now = nowOld; + return FRAMETIME; +} + +// Add one layer of waves into the led array +CRGB WS2812FX::pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) +{ + uint16_t ci = cistart; + uint16_t waveangle = ioff; + uint16_t wavescale_half = (wavescale >> 1) + 20; + + waveangle += ((120 + SEGMENT.intensity) * i); //original 250 * i + uint16_t s16 = sin16(waveangle) + 32768; + uint16_t cs = scale16(s16, wavescale_half) + wavescale_half; + ci += (cs * i); + uint16_t sindex16 = sin16(ci) + 32768; + uint8_t sindex8 = scale16(sindex16, 240); + return ColorFromPalette(p, sindex8, bri, LINEARBLEND); +} + +//Solid colour background with glitter +uint16_t WS2812FX::mode_solid_glitter() +{ + fill(SEGCOLOR(0)); + + if (SEGMENT.intensity > random8()) + { + setPixelColor(random16(SEGLEN), ULTRAWHITE); + } + return FRAMETIME; +} + + +/* + * Mode simulates a gradual sunrise + */ +uint16_t WS2812FX::mode_sunrise() { + //speed 0 - static sun + //speed 1 - 60: sunrise time in minutes + //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.aux0 = SEGMENT.speed; + } + + fill(0); + uint16_t stage = 0xFFFF; + + uint32_t s10SinceStart = (millis() - SEGENV.step) /100; //tenths of seconds + + if (SEGMENT.speed > 120) { //quick sunrise and sunset + uint16_t counter = (now >> 1) * (((SEGMENT.speed -120) >> 1) +1); + stage = triwave16(counter); + } else if (SEGMENT.speed) { //sunrise + uint8_t durMins = SEGMENT.speed; + if (durMins > 60) durMins -= 60; + uint32_t s10Target = durMins * 600; + if (s10SinceStart > s10Target) s10SinceStart = s10Target; + stage = map(s10SinceStart, 0, s10Target, 0, 0xFFFF); + if (SEGMENT.speed > 60) stage = 0xFFFF - stage; //sunset + } + + for (uint16_t i = 0; i <= SEGLEN/2; i++) + { + //default palette is Fire + uint32_t c = color_from_palette(0, false, true, 255); //background + + uint16_t wave = triwave16((i * stage) / SEGLEN); + + wave = (wave >> 8) + ((wave * SEGMENT.intensity) >> 15); + + if (wave > 240) { //clipped, full white sun + c = color_from_palette( 240, false, true, 255); + } else { //transition + c = color_from_palette(wave, false, true, 255); + } + setPixelColor(i, c); + setPixelColor(SEGLEN - i - 1, c); + } + + return FRAMETIME; +} + + +/* + * Effects by Andrew Tuline + */ +uint16_t WS2812FX::phased_base(uint8_t moder) { // We're making sine waves here. By Andrew Tuline. + + uint8_t allfreq = 16; // Base frequency. + //float* phasePtr = reinterpret_cast(SEGENV.step); // Phase change value gets calculated. + static float phase = 0;//phasePtr[0]; + uint8_t cutOff = (255-SEGMENT.intensity); // You can change the number of pixels. AKA INTENSITY (was 192). + uint8_t modVal = 5;//SEGMENT.fft1/8+1; // You can change the modulus. AKA FFT1 (was 5). + + uint8_t index = now/64; // Set color rotation speed + phase += SEGMENT.speed/32.0; // You can change the speed of the wave. AKA SPEED (was .4) + //phasePtr[0] = phase; + + for (int i = 0; i < SEGLEN; i++) { + if (moder == 1) modVal = (inoise8(i*10 + i*10) /16); // Let's randomize our mod length with some Perlin noise. + uint16_t val = (i+1) * allfreq; // This sets the frequency of the waves. The +1 makes sure that leds[0] is used. + if (modVal == 0) modVal = 1; + val += phase * (i % modVal +1) /2; // This sets the varying phase change of the waves. By Andrew Tuline. + uint8_t b = cubicwave8(val); // Now we make an 8 bit sinewave. + b = (b > cutOff) ? (b - cutOff) : 0; // A ternary operator to cutoff the light. + setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(index, false, false, 0), b)); + index += 256 / SEGLEN; + if (SEGLEN > 256) index ++; // Correction for segments longer than 256 LEDs + } + + return FRAMETIME; +} + + + +uint16_t WS2812FX::mode_phased(void) { + return phased_base(0); +} + + + +uint16_t WS2812FX::mode_phased_noise(void) { + return phased_base(1); +} + + + +uint16_t WS2812FX::mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. + 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 SEGMENT.intensity) pixBri = 0; + setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(i*20, false, PALETTE_SOLID_WRAP, 0), pixBri)); + } + + return FRAMETIME; +} + + +// Peaceful noise that's slow and with gradually changing palettes. Does not support WLED palettes or default colours or controls. +uint16_t WS2812FX::mode_noisepal(void) { // Slow noise palette by Andrew Tuline. + uint16_t scale = 15 + (SEGMENT.intensity >> 2); //default was 30 + //#define scale 30 + + uint16_t dataSize = sizeof(CRGBPalette16) * 2; //allocate space for 2 Palettes + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + CRGBPalette16* palettes = reinterpret_cast(SEGENV.data); + + uint16_t changePaletteMs = 4000 + SEGMENT.speed *10; //between 4 - 6.5sec + if (millis() - SEGENV.step > changePaletteMs) + { + SEGENV.step = millis(); + + 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))); + } + + CRGB color; + + //EVERY_N_MILLIS(10) { //(don't have to time this, effect function is only called every 24ms) + nblendPaletteTowardPalette(palettes[0], palettes[1], 48); // Blend towards the target palette over 48 iterations. + + if (SEGMENT.palette > 0) palettes[0] = currentPalette; + + for(int i = 0; i < SEGLEN; i++) { + uint8_t index = inoise8(i*scale, SEGENV.aux0+i*scale); // Get a value from the noise function. I'm using both x and y axis. + color = ColorFromPalette(palettes[0], index, 255, LINEARBLEND); // Use the my own palette. + setPixelColor(i, color.red, color.green, color.blue); + } + + SEGENV.aux0 += beatsin8(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. + + return FRAMETIME; +} + + +// Sine waves that have controllable phase change speed, frequency and cutoff. By Andrew Tuline. +// SEGMENT.speed ->Speed, SEGMENT.intensity -> Frequency (SEGMENT.fft1 -> Color change, SEGMENT.fft2 -> PWM cutoff) +// +uint16_t WS2812FX::mode_sinewave(void) { // Adjustable sinewave. By Andrew Tuline + //#define qsuba(x, b) ((x>b)?x-b:0) // Analog Unsigned subtraction macro. if result <0, then => 0 + + uint16_t colorIndex = now /32;//(256 - SEGMENT.fft1); // Amount of colour change. + + SEGENV.step += SEGMENT.speed/16; // Speed of animation. + uint16_t freq = SEGMENT.intensity/4;//SEGMENT.fft2/8; // Frequency of the signal. + + for (int i=0; i> 2) +1); + counter = counter >> 8; + } + + uint16_t maxZones = SEGLEN / 6; //only looks good if each zone has at least 6 LEDs + uint16_t zones = (SEGMENT.intensity * maxZones) >> 8; + if (zones & 0x01) zones++; //zones must be even + if (zones < 2) zones = 2; + uint16_t zoneLen = SEGLEN / zones; + uint16_t offset = (SEGLEN - zones * zoneLen) >> 1; + + fill(color_from_palette(-counter, false, true, 255)); + + for (uint16_t z = 0; z < zones; z++) + { + uint16_t pos = offset + z * zoneLen; + for (uint16_t i = 0; i < zoneLen; i++) + { + uint8_t colorIndex = (i * 255 / zoneLen) - counter; + uint16_t led = (z & 0x01) ? i : (zoneLen -1) -i; + if (IS_REVERSE) led = (zoneLen -1) -led; + setPixelColor(pos + led, color_from_palette(colorIndex, false, true, 255)); + } + } + + return FRAMETIME; +} + + +/* + * Dots waving around in a sine/pendulum motion. + * Little pixel birds flying in a circle. By Aircoookie + */ +uint16_t WS2812FX::mode_chunchun(void) +{ + fill(SEGCOLOR(1)); + uint16_t counter = now*(6 + (SEGMENT.speed >> 4)); + uint16_t numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment + uint16_t span = (SEGMENT.intensity << 8) / numBirds; + + for (uint16_t i = 0; i < numBirds; i++) + { + counter -= span; + uint16_t megumin = sin16(counter) + 0x8000; + uint32_t bird = (megumin * SEGLEN) >> 16; + uint32_t c = color_from_palette((i * 255)/ numBirds, false, false, 0); // no palette wrapping + setPixelColor(bird, c); + } + return FRAMETIME; +} + + +typedef struct Spotlight { + float speed; + uint8_t colorIdx; + int16_t position; + unsigned long lastUpdateTime; + uint8_t width; + uint8_t type; +} spotlight; + +#define SPOT_TYPE_SOLID 0 +#define SPOT_TYPE_GRADIENT 1 +#define SPOT_TYPE_2X_GRADIENT 2 +#define SPOT_TYPE_2X_DOT 3 +#define SPOT_TYPE_3X_DOT 4 +#define SPOT_TYPE_4X_DOT 5 +#define SPOT_TYPES_COUNT 6 + +/* + * Spotlights moving back and forth that cast dancing shadows. + * Shine this through tree branches/leaves or other close-up objects that cast + * interesting shadows onto a ceiling or tarp. + * + * By Steve Pomeroy @xxv + */ +uint16_t WS2812FX::mode_dancing_shadows(void) +{ + uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, 50); + bool initialize = SEGENV.aux0 != numSpotlights; + SEGENV.aux0 = numSpotlights; + + uint16_t dataSize = sizeof(spotlight) * numSpotlights; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Spotlight* spotlights = reinterpret_cast(SEGENV.data); + + fill(BLACK); + + unsigned long time = millis(); + bool respawn = false; + + for (uint8_t i = 0; i < numSpotlights; i++) { + if (!initialize) { + // advance the position of the spotlight + int16_t delta = (float)(time - spotlights[i].lastUpdateTime) * + (spotlights[i].speed * ((1.0 + SEGMENT.speed)/100.0)); + + if (abs(delta) >= 1) { + spotlights[i].position += delta; + spotlights[i].lastUpdateTime = time; + } + + respawn = (spotlights[i].speed > 0.0 && spotlights[i].position > (SEGLEN + 2)) + || (spotlights[i].speed < 0.0 && spotlights[i].position < -(spotlights[i].width + 2)); + } + + if (initialize || respawn) { + spotlights[i].colorIdx = random8(); + spotlights[i].width = random8(1, 10); + + spotlights[i].speed = 1.0/random8(4, 50); + + if (initialize) { + spotlights[i].position = random16(SEGLEN); + spotlights[i].speed *= random8(2) ? 1.0 : -1.0; + } else { + if (random8(2)) { + spotlights[i].position = SEGLEN + spotlights[i].width; + spotlights[i].speed *= -1.0; + }else { + spotlights[i].position = -spotlights[i].width; + } + } + + spotlights[i].lastUpdateTime = time; + spotlights[i].type = random8(SPOT_TYPES_COUNT); + } + + uint32_t color = color_from_palette(spotlights[i].colorIdx, false, false, 0); + int start = spotlights[i].position; + + if (spotlights[i].width <= 1) { + if (start >= 0 && start < SEGLEN) { + blendPixelColor(start, color, 128); + } + } else { + switch (spotlights[i].type) { + case SPOT_TYPE_SOLID: + for (uint8_t j = 0; j < spotlights[i].width; j++) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, 128); + } + } + break; + + case SPOT_TYPE_GRADIENT: + for (uint8_t j = 0; j < spotlights[i].width; j++) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, + cubicwave8(map(j, 0, spotlights[i].width - 1, 0, 255))); + } + } + break; + + case SPOT_TYPE_2X_GRADIENT: + for (uint8_t j = 0; j < spotlights[i].width; j++) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, + cubicwave8(2 * map(j, 0, spotlights[i].width - 1, 0, 255))); + } + } + break; + + case SPOT_TYPE_2X_DOT: + for (uint8_t j = 0; j < spotlights[i].width; j += 2) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, 128); + } + } + break; + + case SPOT_TYPE_3X_DOT: + for (uint8_t j = 0; j < spotlights[i].width; j += 3) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, 128); + } + } + break; + + case SPOT_TYPE_4X_DOT: + for (uint8_t j = 0; j < spotlights[i].width; j += 4) { + if ((start + j) >= 0 && (start + j) < SEGLEN) { + blendPixelColor(start + j, color, 128); + } + } + break; + } + } + } + + return FRAMETIME; +} + +/* + Imitates a washing machine, rotating same waves forward, then pause, then backward. + By Stefan Seegel +*/ +uint16_t WS2812FX::mode_washing_machine(void) { + float speed = tristate_square8(now >> 7, 90, 15); + float quot = 32.0f - ((float)SEGMENT.speed / 16.0f); + speed /= quot; + + SEGENV.step += (speed * 128.0f); + + for (int i=0; i> 7)); + setPixelColor(i, color_from_palette(col, false, PALETTE_SOLID_WRAP, 3)); + } + + return FRAMETIME; +} + +/* + Blends random colors across palette + Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e +*/ +uint16_t WS2812FX::mode_blends(void) { + uint16_t dataSize = sizeof(uint32_t) * SEGLEN; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + uint32_t* pixels = reinterpret_cast(SEGENV.data); + uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); + uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; + + for (int i = 0; i < SEGLEN; i++) { + pixels[i] = color_blend(pixels[i], color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_SOLID_WRAP, 255), blendSpeed); + setPixelColor(i, pixels[i]); + shift += 3; + } + + return FRAMETIME; +} + +#ifndef WLED_DISABLE_FX_HIGH_FLASH_USE +typedef struct TvSim { + uint32_t totalTime = 0; + uint32_t fadeTime = 0; + uint32_t startTime = 0; + uint32_t elapsed = 0; + uint32_t pixelNum = 0; + uint16_t pr = 0; // Prev R, G, B + uint16_t pg = 0; + uint16_t pb = 0; +} tvSim; + +#define numTVPixels (sizeof(tv_colors) / 2) // 2 bytes per Pixel (5/6/5) +#endif + +/* + TV Simulator + Modified and adapted to WLED by Def3nder, based on "Fake TV Light for Engineers" by Phillip Burgess https://learn.adafruit.com/fake-tv-light-for-engineers/arduino-sketch +*/ +uint16_t WS2812FX::mode_tv_simulator(void) { + #ifdef WLED_DISABLE_FX_HIGH_FLASH_USE + return mode_static(); + #else + uint16_t nr, ng, nb, r, g, b, i; + uint8_t hi, lo, r8, g8, b8; + + if (!SEGENV.allocateData(sizeof(tvSim))) return mode_static(); //allocation failed + TvSim* tvSimulator = reinterpret_cast(SEGENV.data); + + // initialize start of the TV-Colors + if (SEGENV.call == 0) { + tvSimulator->pixelNum = ((uint8_t)random(18)) * numTVPixels / 18; // Begin at random movie (18 in total) + } + + // Read next 16-bit (5/6/5) color + hi = pgm_read_byte(&tv_colors[tvSimulator->pixelNum * 2 ]); + lo = pgm_read_byte(&tv_colors[tvSimulator->pixelNum * 2 + 1]); + + // Expand to 24-bit (8/8/8) + r8 = (hi & 0xF8) | (hi >> 5); + g8 = ((hi << 5) & 0xff) | ((lo & 0xE0) >> 3) | ((hi & 0x06) >> 1); + b8 = ((lo << 3) & 0xff) | ((lo & 0x1F) >> 2); + + // Apply gamma correction, further expand to 16/16/16 + nr = (uint8_t)gamma8(r8) * 257; // New R/G/B + ng = (uint8_t)gamma8(g8) * 257; + nb = (uint8_t)gamma8(b8) * 257; + + if (SEGENV.aux0 == 0) { // initialize next iteration + SEGENV.aux0 = 1; + + // increase color-index for next loop + tvSimulator->pixelNum++; + if (tvSimulator->pixelNum >= numTVPixels) tvSimulator->pixelNum = 0; + + // randomize total duration and fade duration for the actual color + tvSimulator->totalTime = random(250, 2500); // Semi-random pixel-to-pixel time + tvSimulator->fadeTime = random(0, tvSimulator->totalTime); // Pixel-to-pixel transition time + if (random(10) < 3) tvSimulator->fadeTime = 0; // Force scene cut 30% of time + + tvSimulator->startTime = millis(); + } // end of initialization + + // how much time is elapsed ? + tvSimulator->elapsed = millis() - tvSimulator->startTime; + + // fade from prev volor 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); + b = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pb, nb); + } else { // Avoid divide-by-zero in map() + r = nr; + g = ng; + b = nb; + } + + // set strip color + for (i = 0; i < SEGLEN; i++) { + setPixelColor(i, r >> 8, g >> 8, b >> 8); // Quantize to 8-bit + } + + // if total duration has passed, remember last color and restart the loop + if ( tvSimulator->elapsed >= tvSimulator->totalTime) { + tvSimulator->pr = nr; // Prev RGB = new RGB + tvSimulator->pg = ng; + tvSimulator->pb = nb; + SEGENV.aux0 = 0; + } + + return FRAMETIME; + #endif +} + +/* + Aurora effect +*/ + +//CONFIG +#define BACKLIGHT 5 +#define W_MAX_COUNT 20 //Number of simultaneous waves +#define W_MAX_SPEED 6 //Higher number, higher speed +#define W_WIDTH_FACTOR 6 //Higher number, smaller waves + +class AuroraWave { + private: + uint16_t ttl; + CRGB basecolor; + float basealpha; + uint16_t age; + uint16_t width; + float center; + bool goingleft; + float speed_factor; + bool alive = true; + + public: + void init(uint32_t segment_length, CRGB color) { + ttl = random(500, 1501); + basecolor = color; + basealpha = random(60, 101) / (float)100; + age = 0; + width = random(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); + alive = true; + } + + CRGB getColorForLED(int ledIndex) { + if(ledIndex < center - width || ledIndex > center + width) return 0; //Position out of range of this wave + + CRGB rgb; + + //Offset of this led from center of wave + //The further away from the center, the dimmer the LED + float offset = ledIndex - center; + if (offset < 0) offset = -offset; + float offsetFactor = offset / width; + + //The age of the wave determines it brightness. + //At half its maximum age it will be the brightest. + float ageFactor = 0.1; + if((float)age / ttl < 0.5) { + ageFactor = (float)age / (ttl / 2); + } else { + ageFactor = (float)(ttl - age) / ((float)ttl * 0.5); + } + + //Calculate color based on above factors and basealpha value + float factor = (1 - offsetFactor) * ageFactor * basealpha; + rgb.r = basecolor.r * factor; + rgb.g = basecolor.g * factor; + rgb.b = basecolor.b * factor; + + return rgb; + }; + + //Change position and age of wave + //Determine if its sill "alive" + void update(uint32_t segment_length, uint32_t speed) { + if(goingleft) { + center -= speed_factor * speed; + } else { + center += speed_factor * speed; + } + + age++; + + if(age > ttl) { + alive = false; + } else { + if(goingleft) { + if(center + width < 0) { + alive = false; + } + } else { + if(center - width > segment_length) { + alive = false; + } + } + } + }; + + bool stillAlive() { + return alive; + }; +}; + +uint16_t WS2812FX::mode_aurora(void) { + //aux1 = Wavecount + //aux2 = Intensity in last loop + + AuroraWave* waves; + + if(SEGENV.aux0 != SEGMENT.intensity || SEGENV.call == 0) { + //Intensity slider changed or first call + SEGENV.aux1 = ((float)SEGMENT.intensity / 255) * W_MAX_COUNT; + SEGENV.aux0 = SEGMENT.intensity; + + if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { + return mode_static(); //allocation failed + } + + waves = reinterpret_cast(SEGENV.data); + + for(int i = 0; i < SEGENV.aux1; i++) { + waves[i].init(SEGLEN, col_to_crgb(color_from_palette(random8(), false, false, random(0, 3)))); + } + } else { + waves = reinterpret_cast(SEGENV.data); + } + + for(int i = 0; i < SEGENV.aux1; i++) { + //Update values of wave + waves[i].update(SEGLEN, SEGMENT.speed); + + if(!(waves[i].stillAlive())) { + //If a wave dies, reinitialize it starts over. + waves[i].init(SEGLEN, col_to_crgb(color_from_palette(random8(), false, false, random(0, 3)))); + } + } + + //Loop through LEDs to determine color + for(int i = 0; i < SEGLEN; i++) { + CRGB mixedRgb = CRGB(BACKLIGHT, BACKLIGHT, BACKLIGHT); + + //For each LED we must check each wave if it is "active" at this position. + //If there are multiple waves active on a LED we multiply their values. + for(int j = 0; j < SEGENV.aux1; j++) { + CRGB rgb = waves[j].getColorForLED(i); + + if(rgb != CRGB(0)) { + mixedRgb += rgb; + } + } + + setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2], BACKLIGHT); + } + return FRAMETIME; } \ No newline at end of file diff --git a/wled00/FX.h b/wled00/FX.h index 1efbb59ba..6ea3a2097 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -24,21 +24,32 @@ Modified for WLED */ +#include "wled.h" + #ifndef WS2812FX_h #define WS2812FX_h -#include "NpbWrapper.h" +#include "const.h" #define FASTLED_INTERNAL //remove annoying pragma messages +#define USE_GET_MILLISECOND_TIMER #include "FastLED.h" #define DEFAULT_BRIGHTNESS (uint8_t)127 #define DEFAULT_MODE (uint8_t)0 #define DEFAULT_SPEED (uint8_t)128 +#define DEFAULT_INTENSITY (uint8_t)128 #define DEFAULT_COLOR (uint32_t)0xFFAA00 -#define min(a,b) ((a)<(b)?(a):(b)) -#define max(a,b) ((a)>(b)?(a):(b)) +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +/* Disable effects with high flash memory usage (currently TV simulator) - saves 18.5kB */ +//#define WLED_DISABLE_FX_HIGH_FLASH_USE /* Not used in all effects yet */ #define WLED_FPS 42 @@ -46,18 +57,24 @@ /* each segment uses 52 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ -#define MAX_NUM_SEGMENTS 10 - -/* How much data bytes all segments combined may allocate */ #ifdef ESP8266 -#define MAX_SEGMENT_DATA 2048 + #define MAX_NUM_SEGMENTS 12 + /* How many color transitions can run at once */ + #define MAX_NUM_TRANSITIONS 8 + /* How much data bytes all segments combined may allocate */ + #define MAX_SEGMENT_DATA 2048 #else -#define MAX_SEGMENT_DATA 8192 + #define MAX_NUM_SEGMENTS 16 + #define MAX_NUM_TRANSITIONS 16 + #define MAX_SEGMENT_DATA 8192 #endif +#define LED_SKIP_AMOUNT 1 +#define MIN_SHOW_DELAY 15 + #define NUM_COLORS 3 /* number of colors per segment */ #define SEGMENT _segments[_segment_index] -#define SEGCOLOR(x) gamma32(_segments[_segment_index].colors[x]) +#define SEGCOLOR(x) _colors_t[x] #define SEGENV _segment_runtimes[_segment_index] #define SEGLEN _virtualSegmentLength #define SEGACT SEGMENT.stop @@ -80,18 +97,24 @@ // options // bit 7: segment is in transition mode -// bits 2-6: TBD +// bits 4-6: TBD +// bit 3: mirror effect within segment +// bit 2: segment is on // bit 1: reverse segment // bit 0: segment is selected #define NO_OPTIONS (uint8_t)0x00 #define TRANSITIONAL (uint8_t)0x80 +#define MIRROR (uint8_t)0x08 +#define SEGMENT_ON (uint8_t)0x04 #define REVERSE (uint8_t)0x02 #define SELECTED (uint8_t)0x01 #define IS_TRANSITIONAL ((SEGMENT.options & TRANSITIONAL) == TRANSITIONAL) -#define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE ) -#define IS_SELECTED ((SEGMENT.options & SELECTED) == SELECTED ) +#define IS_MIRROR ((SEGMENT.options & MIRROR ) == MIRROR ) +#define IS_SEGMENT_ON ((SEGMENT.options & SEGMENT_ON ) == SEGMENT_ON ) +#define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE ) +#define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED ) -#define MODE_COUNT 102 +#define MODE_COUNT 119 #define FX_MODE_STATIC 0 #define FX_MODE_BLINK 1 @@ -131,13 +154,13 @@ #define FX_MODE_TRAFFIC_LIGHT 35 #define FX_MODE_COLOR_SWEEP_RANDOM 36 #define FX_MODE_RUNNING_COLOR 37 -#define FX_MODE_RUNNING_RED_BLUE 38 +#define FX_MODE_AURORA 38 #define FX_MODE_RUNNING_RANDOM 39 #define FX_MODE_LARSON_SCANNER 40 #define FX_MODE_COMET 41 #define FX_MODE_FIREWORKS 42 #define FX_MODE_RAIN 43 -#define FX_MODE_MERRY_CHRISTMAS 44 +#define FX_MODE_TETRIX 44 #define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 @@ -194,13 +217,33 @@ #define FX_MODE_PERCENT 98 #define FX_MODE_RIPPLE_RAINBOW 99 #define FX_MODE_HEARTBEAT 100 -#define FX_MODE_RUNNING_DUAL 101 +#define FX_MODE_PACIFICA 101 +#define FX_MODE_CANDLE_MULTI 102 +#define FX_MODE_SOLID_GLITTER 103 +#define FX_MODE_SUNRISE 104 +#define FX_MODE_PHASED 105 +#define FX_MODE_TWINKLEUP 106 +#define FX_MODE_NOISEPAL 107 +#define FX_MODE_SINEWAVE 108 +#define FX_MODE_PHASEDNOISE 109 +#define FX_MODE_FLOW 110 +#define FX_MODE_CHUNCHUN 111 +#define FX_MODE_DANCING_SHADOWS 112 +#define FX_MODE_WASHING_MACHINE 113 +#define FX_MODE_CANDY_CANE 114 +#define FX_MODE_BLENDS 115 +#define FX_MODE_TV_SIMULATOR 116 +#define FX_MODE_DYNAMIC_SMOOTH 117 +#define FX_MODE_RUNNING_DUAL 118 + class WS2812FX { typedef uint16_t (WS2812FX::*mode_ptr)(void); // pre show callback typedef void (*show_callback) (void); + + static WS2812FX* instance; // segment parameters public: @@ -211,18 +254,44 @@ class WS2812FX { uint8_t intensity; uint8_t palette; uint8_t mode; - uint8_t options; //bit pattern: msb first: transitional tbd tbd tbd tbd paused reverse selected + uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected uint8_t grouping, spacing; uint8_t opacity; uint32_t colors[NUM_COLORS]; - void setOption(uint8_t n, bool val) + bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed + if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false; + if (c == colors[slot]) return false; + ColorTransition::startTransition(opacity, colors[slot], instance->_transitionDur, segn, slot); + colors[slot] = c; return true; + } + void setOpacity(uint8_t o, uint8_t segn) { + if (segn >= MAX_NUM_SEGMENTS) return; + if (opacity == o) return; + ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0); + opacity = o; + } + /*uint8_t actualOpacity() { //respects On/Off state + if (!getOption(SEG_OPTION_ON)) return 0; + return opacity; + }*/ + void setOption(uint8_t n, bool val, uint8_t segn = 255) { + //bool prevOn = false; + //if (n == SEG_OPTION_ON) prevOn = getOption(SEG_OPTION_ON); if (val) { options |= 0x01 << n; } else { options &= ~(0x01 << n); } + //transitions on segment on/off don't work correctly at this point + /*if (n == SEG_OPTION_ON && segn < MAX_NUM_SEGMENTS && getOption(SEG_OPTION_ON) != prevOn) { + if (getOption(SEG_OPTION_ON)) { + ColorTransition::startTransition(0, colors[0], instance->_transitionDur, segn, 0); + } else { + ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0); + } + }*/ } bool getOption(uint8_t n) { @@ -247,7 +316,10 @@ class WS2812FX { uint16_t virtualLength() { uint16_t groupLen = groupLength(); - return (length() + groupLen -1) / groupLen; + uint16_t vLength = (length() + groupLen - 1) / groupLen; + if (options & MIRROR) + vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vLength; } } segment; @@ -262,10 +334,10 @@ class WS2812FX { bool allocateData(uint16_t len){ if (data && _dataLen == len) return true; //already allocated deallocateData(); - if (WS2812FX::_usedSegmentData + len > MAX_SEGMENT_DATA) return false; //not enough memory + if (WS2812FX::instance->_usedSegmentData + len > MAX_SEGMENT_DATA) return false; //not enough memory data = new (std::nothrow) byte[len]; if (!data) return false; //allocation failed - WS2812FX::_usedSegmentData += len; + WS2812FX::instance->_usedSegmentData += len; _dataLen = len; memset(data, 0, len); return true; @@ -273,15 +345,116 @@ class WS2812FX { void deallocateData(){ delete[] data; data = nullptr; - WS2812FX::_usedSegmentData -= _dataLen; + WS2812FX::instance->_usedSegmentData -= _dataLen; _dataLen = 0; } - void reset(){next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; deallocateData();} + + /** + * If reset of this segment was request, clears runtime + * settings of this segment. + * Must not be called while an effect mode function is running + * because it could access the data buffer and this method + * may free that data buffer. + */ + void resetIfRequired() { + if (_requiresReset) { + next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; + deallocateData(); + _requiresReset = false; + } + } + + /** + * Flags that before the next effect is calculated, + * the internal segment state should be reset. + * Call resetIfRequired before calling the next effect function. + */ + void reset() { _requiresReset = true; } private: uint16_t _dataLen = 0; + bool _requiresReset = false; } segment_runtime; + typedef struct ColorTransition { // 12 bytes + uint32_t colorOld = 0; + uint32_t transitionStart; + uint16_t transitionDur; + uint8_t segment = 0xFF; //lower 6 bits: the segment this transition is for (255 indicates transition not in use/available) upper 2 bits: color channel + uint8_t briOld = 0; + static void startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot) { + if (segn >= MAX_NUM_SEGMENTS || slot >= NUM_COLORS || dur == 0) return; + if (instance->_brightness == 0) return; //do not need transitions if master bri is off + uint8_t tIndex = 0xFF; //none found + uint16_t tProgression = 0; + uint8_t s = segn + (slot << 6); //merge slot and segment into one byte + + for (uint8_t i = 0; i < MAX_NUM_TRANSITIONS; i++) { + uint8_t tSeg = instance->transitions[i].segment; + //see if this segment + color already has a running transition + if (tSeg == s) { + tIndex = i; break; + } + if (tSeg == 0xFF) { //free transition + tIndex = i; tProgression = 0xFFFF; + } + } + + if (tIndex == 0xFF) { //no slot found yet + for (uint8_t i = 0; i < MAX_NUM_TRANSITIONS; i++) { + //find most progressed transition to overwrite + uint16_t prog = instance->transitions[i].progress(); + if (prog > tProgression) { + tIndex = i; tProgression = prog; + } + } + } + + ColorTransition& t = instance->transitions[tIndex]; + if (t.segment == s) //this is an active transition on the same segment+color + { + t.briOld = t.currentBri(); + t.colorOld = t.currentColor(oldCol); + } else { + t.briOld = oldBri; + t.colorOld = oldCol; + uint8_t prevSeg = t.segment & 0x3F; + if (prevSeg < MAX_NUM_SEGMENTS) instance->_segments[prevSeg].setOption(SEG_OPTION_TRANSITIONAL, false); + } + t.transitionDur = dur; + t.transitionStart = millis(); + t.segment = s; + instance->_segments[segn].setOption(SEG_OPTION_TRANSITIONAL, true); + //refresh immediately, required for Solid mode + if (instance->_segment_runtimes[segn].next_time > t.transitionStart + 22) instance->_segment_runtimes[segn].next_time = t.transitionStart; + } + uint16_t progress(bool allowEnd = false) { //transition progression between 0-65535 + uint32_t timeNow = millis(); + if (timeNow - transitionStart > transitionDur) { + if (allowEnd) { + uint8_t segn = segment & 0x3F; + if (segn < MAX_NUM_SEGMENTS) instance->_segments[segn].setOption(SEG_OPTION_TRANSITIONAL, false); + segment = 0xFF; + } + return 0xFFFF; + } + uint32_t elapsed = timeNow - transitionStart; + uint32_t prog = elapsed * 0xFFFF / transitionDur; + return (prog > 0xFFFF) ? 0xFFFF : prog; + } + uint32_t currentColor(uint32_t colorNew) { + return instance->color_blend(colorOld, colorNew, progress(true), true); + } + uint8_t currentBri() { + uint8_t segn = segment & 0x3F; + if (segn >= MAX_NUM_SEGMENTS) return 0; + uint8_t briNew = instance->_segments[segn].opacity; + uint32_t prog = progress() + 1; + return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16; + } + } color_transition; + WS2812FX() { + WS2812FX::instance = this; //assign each member of the _mode[] array to its respective function reference _mode[FX_MODE_STATIC] = &WS2812FX::mode_static; _mode[FX_MODE_BLINK] = &WS2812FX::mode_blink; @@ -319,13 +492,13 @@ class WS2812FX { _mode[FX_MODE_TRAFFIC_LIGHT] = &WS2812FX::mode_traffic_light; _mode[FX_MODE_COLOR_SWEEP_RANDOM] = &WS2812FX::mode_color_sweep_random; _mode[FX_MODE_RUNNING_COLOR] = &WS2812FX::mode_running_color; - _mode[FX_MODE_RUNNING_RED_BLUE] = &WS2812FX::mode_running_red_blue; + _mode[FX_MODE_AURORA] = &WS2812FX::mode_aurora; _mode[FX_MODE_RUNNING_RANDOM] = &WS2812FX::mode_running_random; _mode[FX_MODE_LARSON_SCANNER] = &WS2812FX::mode_larson_scanner; _mode[FX_MODE_COMET] = &WS2812FX::mode_comet; _mode[FX_MODE_FIREWORKS] = &WS2812FX::mode_fireworks; _mode[FX_MODE_RAIN] = &WS2812FX::mode_rain; - _mode[FX_MODE_MERRY_CHRISTMAS] = &WS2812FX::mode_merry_christmas; + _mode[FX_MODE_TETRIX] = &WS2812FX::mode_tetrix; _mode[FX_MODE_FIRE_FLICKER] = &WS2812FX::mode_fire_flicker; _mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient; _mode[FX_MODE_LOADING] = &WS2812FX::mode_loading; @@ -384,6 +557,23 @@ class WS2812FX { _mode[FX_MODE_PERCENT] = &WS2812FX::mode_percent; _mode[FX_MODE_RIPPLE_RAINBOW] = &WS2812FX::mode_ripple_rainbow; _mode[FX_MODE_HEARTBEAT] = &WS2812FX::mode_heartbeat; + _mode[FX_MODE_PACIFICA] = &WS2812FX::mode_pacifica; + _mode[FX_MODE_CANDLE_MULTI] = &WS2812FX::mode_candle_multi; + _mode[FX_MODE_SOLID_GLITTER] = &WS2812FX::mode_solid_glitter; + _mode[FX_MODE_SUNRISE] = &WS2812FX::mode_sunrise; + _mode[FX_MODE_PHASED] = &WS2812FX::mode_phased; + _mode[FX_MODE_TWINKLEUP] = &WS2812FX::mode_twinkleup; + _mode[FX_MODE_NOISEPAL] = &WS2812FX::mode_noisepal; + _mode[FX_MODE_SINEWAVE] = &WS2812FX::mode_sinewave; + _mode[FX_MODE_PHASEDNOISE] = &WS2812FX::mode_phased_noise; + _mode[FX_MODE_FLOW] = &WS2812FX::mode_flow; + _mode[FX_MODE_CHUNCHUN] = &WS2812FX::mode_chunchun; + _mode[FX_MODE_DANCING_SHADOWS] = &WS2812FX::mode_dancing_shadows; + _mode[FX_MODE_WASHING_MACHINE] = &WS2812FX::mode_washing_machine; + _mode[FX_MODE_CANDY_CANE] = &WS2812FX::mode_candy_cane; + _mode[FX_MODE_BLENDS] = &WS2812FX::mode_blends; + _mode[FX_MODE_TV_SIMULATOR] = &WS2812FX::mode_tv_simulator; + _mode[FX_MODE_DYNAMIC_SMOOTH] = &WS2812FX::mode_dynamic_smooth; _mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual; _brightness = DEFAULT_BRIGHTNESS; @@ -392,46 +582,51 @@ class WS2812FX { ablMilliampsMax = 850; currentMilliamps = 0; timebase = 0; - bus = new NeoPixelWrapper(); resetSegments(); } void - init(bool supportWhite, uint16_t countPixels, bool skipFirst), + finalizeInit(uint16_t countPixels, bool skipFirst), service(void), blur(uint8_t), + fill(uint32_t), fade_out(uint8_t r), setMode(uint8_t segid, uint8_t m), setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0), setColor(uint8_t slot, uint32_t c), setBrightness(uint8_t b), - driverModeCronixie(bool b), - setCronixieDigits(byte* d), - setCronixieBacklight(bool b), setRange(uint16_t i, uint16_t i2, uint32_t col), setShowCallback(show_callback cb), + setTransition(uint16_t t), setTransitionMode(bool t), + calcGammaTable(float), trigger(void), setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0), resetSegments(), setPixelColor(uint16_t n, uint32_t c), setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0), - show(void); + show(void), + setColorOrder(uint8_t co), + setPixelSegment(uint8_t n); bool - reverseMode = false, + isRgbw = false, gammaCorrectBri = false, gammaCorrectCol = true, applyToAllSelected = true, segmentsAreIdentical(Segment* a, Segment* b), - setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p); + setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p), + // return true if the strip is being sent pixel updates + isUpdating(void); uint8_t mainSegment = 0, + rgbwMode = RGBW_MODE_DUAL, paletteFade = 0, paletteBlend = 0, - colorOrder = 0, milliampsPerLed = 55, +// getStripType(uint8_t strip=0), +// setStripType(uint8_t type, uint8_t strip=0), getBrightness(void), getMode(void), getSpeed(void), @@ -440,19 +635,33 @@ class WS2812FX { getMaxSegments(void), //getFirstSelectedSegment(void), getMainSegmentId(void), + getColorOrder(void), gamma8(uint8_t), + gamma8_cal(uint8_t, float), get_random_wheel_index(uint8_t); + int8_t +// setStripPin(uint8_t strip, int8_t pin), +// getStripPin(uint8_t strip=0), +// setStripPinClk(uint8_t strip, int8_t pin), +// getStripPinClk(uint8_t strip=0), + tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec); + uint16_t ablMilliampsMax, currentMilliamps, - triwave16(uint16_t); +// setStripLen(uint8_t strip, uint16_t len), +// getStripLen(uint8_t strip=0), + triwave16(uint16_t), + getFps(); uint32_t + now, timebase, color_wheel(uint8_t), - color_from_palette(uint16_t, bool, bool, uint8_t, uint8_t pbri = 255), - color_blend(uint32_t,uint32_t,uint8_t), + color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255), + color_blend(uint32_t,uint32_t,uint16_t,bool b16=false), + currentColor(uint32_t colorNew, uint8_t tNr), gamma32(uint32_t), getLastShow(void), getPixelColor(uint16_t), @@ -507,13 +716,13 @@ class WS2812FX { mode_colorful(void), mode_traffic_light(void), mode_running_color(void), - mode_running_red_blue(void), + mode_aurora(void), mode_running_random(void), mode_larson_scanner(void), mode_comet(void), mode_fireworks(void), mode_rain(void), - mode_merry_christmas(void), + mode_tetrix(void), mode_halloween(void), mode_fire_flicker(void), mode_gradient(void), @@ -554,7 +763,7 @@ class WS2812FX { mode_twinklecat(void), mode_halloween_eyes(void), mode_static_pattern(void), - mode_tri_static_pattern(void), + mode_tri_static_pattern(void), mode_spots(void), mode_spots_fade(void), mode_glitter(void), @@ -571,35 +780,46 @@ class WS2812FX { mode_percent(void), mode_ripple_rainbow(void), mode_heartbeat(void), + mode_pacifica(void), + mode_candle_multi(void), + mode_solid_glitter(void), + mode_sunrise(void), + mode_phased(void), + mode_twinkleup(void), + mode_noisepal(void), + mode_sinewave(void), + mode_phased_noise(void), + mode_flow(void), + mode_chunchun(void), + mode_dancing_shadows(void), + mode_washing_machine(void), + mode_candy_cane(void), + mode_blends(void), + mode_tv_simulator(void), + mode_dynamic_smooth(void), mode_running_dual(void); - private: - NeoPixelWrapper *bus; - uint32_t crgb_to_col(CRGB fastled); CRGB col_to_crgb(uint32_t); CRGBPalette16 currentPalette; CRGBPalette16 targetPalette; - uint32_t now; uint16_t _length, _lengthRaw, _virtualSegmentLength; uint16_t _rand16seed; uint8_t _brightness; - static uint16_t _usedSegmentData; + uint16_t _usedSegmentData = 0; + uint16_t _transitionDur = 750; + uint16_t _cumulativeFps = 2; + + void load_gradient_palette(uint8_t); void handle_palette(void); - void fill(uint32_t); bool - _rgbwMode, - _cronixieMode, - _cronixieBacklightEnabled, _skipFirstMode, _triggered; - byte _cronixieDigits[6]; - mode_ptr _mode[MODE_COUNT]; // SRAM footprint: 4 bytes per element show_callback _callback = nullptr; @@ -607,7 +827,9 @@ class WS2812FX { // mode helper functions uint16_t blink(uint32_t, uint32_t, bool strobe, bool), + candle(bool), color_wipe(bool, bool), + dynamic(bool), scan(bool), theater_chase(uint32_t, uint32_t, bool), running_base(bool,bool), @@ -621,12 +843,25 @@ class WS2812FX { running(uint32_t, uint32_t), tricolor_chase(uint32_t, uint32_t), twinklefox_base(bool), - spots_base(uint16_t); + spots_base(uint16_t), + phased_base(uint8_t); CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat); + CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff); + + void + blendPixelColor(uint16_t n, uint32_t color, uint8_t blend), + startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot), + deserializeMap(void); + + uint16_t* customMappingTable = nullptr; + uint16_t customMappingSize = 0; uint32_t _lastPaletteChange = 0; uint32_t _lastShow = 0; + + uint32_t _colors_t[3]; + uint8_t _bri_t; uint8_t _segment_index = 0; uint8_t _segment_index_palette_last = 99; @@ -637,33 +872,38 @@ class WS2812FX { segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 28 bytes per element friend class Segment_runtime; - uint16_t realPixelIndex(uint16_t i); -}; + ColorTransition transitions[MAX_NUM_TRANSITIONS]; //12 bytes per element + friend class ColorTransition; + uint16_t + realPixelIndex(uint16_t i), + transitionProgress(uint8_t tNr); +}; //10 names per line const char JSON_mode_names[] PROGMEM = R"=====([ "Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow", "Scan","Scan Dual","Fade","Theater","Theater Rainbow","Running","Saw","Twinkle","Dissolve","Dissolve Rnd", "Sparkle","Sparkle Dark","Sparkle+","Strobe","Strobe Rainbow","Strobe Mega","Blink Rainbow","Android","Chase","Chase Random", -"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Red & Blue","Stream", -"Scanner","Lighthouse","Fireworks","Rain","Merry Christmas","Fire Flicker","Gradient","Loading","Police","Police All", +"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Aurora","Stream", +"Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Police All", "Two Dots","Two Areas","Circus","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet", "Scanner Dual","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise", "Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple", "Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst", "Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow", -"Heartbeat","Running Dual" +"Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise", +"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth","Running Dual" ])====="; const char JSON_palette_names[] PROGMEM = R"=====([ -"Default","Random Cycle","Primary Color","Based on Primary","Set Colors","Based on Set","Party","Cloud","Lava","Ocean", +"Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", "Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", "Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64", "Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", "Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", -"Aurora" +"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2" ])====="; #endif diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index c1ee11afa..f58bee232 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -27,30 +27,59 @@ #include "FX.h" #include "palettes.h" -#define LED_SKIP_AMOUNT 1 -#define MIN_SHOW_DELAY 15 +/* + Custom per-LED mapping has moved! -void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst) + Create a file "ledmap.json" using the edit page. + + this is just an example (30 LEDs). It will first set all even, then all uneven LEDs. + {"map":[ + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, + 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]} + + another example. Switches direction every 5 LEDs. + {"map":[ + 0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14, + 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25] +*/ + +//do not call this method from system context (network callback) +void WS2812FX::finalizeInit(uint16_t countPixels, bool skipFirst) { - if (supportWhite == _rgbwMode && countPixels == _length) return; RESET_RUNTIME; - _rgbwMode = supportWhite; - _skipFirstMode = skipFirst; _length = countPixels; + _skipFirstMode = skipFirst; - uint8_t ty = 1; - if (supportWhite) ty = 2; _lengthRaw = _length; if (_skipFirstMode) { _lengthRaw += LED_SKIP_AMOUNT; } - bus->Begin((NeoPixelType)ty, _lengthRaw); + //if busses failed to load, add default (FS issue...) + if (busses.getNumBusses() == 0) { + uint8_t defPin[] = {LEDPIN}; + BusConfig defCfg = BusConfig(TYPE_WS2812_RGB, defPin, 0, _lengthRaw, COL_ORDER_GRB); + busses.add(defCfg); + } + deserializeMap(); + + //make segment 0 cover the entire strip _segments[0].start = 0; _segments[0].stop = _length; setBrightness(_brightness); + + #ifdef ESP8266 + for (uint8_t i = 0; i < busses.getNumBusses(); i++) { + Bus* b = busses.getBus(i); + if ((!IS_DIGITAL(b->getType()) || IS_2PIN(b->getType()))) continue; + uint8_t pins[5]; + b->getPins(pins); + BusDigital* bd = static_cast(b); + if (pins[0] == 3) bd->reinit(); + } + #endif } void WS2812FX::service() { @@ -58,20 +87,40 @@ void WS2812FX::service() { now = nowUp + timebase; if (nowUp - _lastShow < MIN_SHOW_DELAY) return; bool doShow = false; + for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++) { _segment_index = i; - if (SEGMENT.isActive()) + + // reset the segment runtime data if needed, called before isActive to ensure deleted + // segment's buffers are cleared + SEGENV.resetIfRequired(); + + if (!SEGMENT.isActive()) continue; + + if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary { - if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary - { + if (SEGMENT.grouping == 0) SEGMENT.grouping = 1; //sanity check + doShow = true; + uint16_t delay = FRAMETIME; + + if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen _virtualSegmentLength = SEGMENT.virtualLength(); - doShow = true; + _bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2]; + if (!IS_SEGMENT_ON) _bri_t = 0; + for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) { + if ((transitions[t].segment & 0x3F) != i) continue; + uint8_t slot = transitions[t].segment >> 6; + if (slot == 0) _bri_t = transitions[t].currentBri(); + _colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]); + } + for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]); handle_palette(); - uint16_t delay = (this->*_mode[SEGMENT.mode])(); - SEGENV.next_time = nowUp + delay; + delay = (this->*_mode[SEGMENT.mode])(); //effect function if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++; } + + SEGENV.next_time = nowUp + delay; } } _virtualSegmentLength = 0; @@ -90,112 +139,79 @@ void WS2812FX::setPixelColor(uint16_t n, uint32_t c) { setPixelColor(n, r, g, b, w); } +//used to map from segment index to physical pixel, taking into account grouping, offsets, reverse and mirroring uint16_t WS2812FX::realPixelIndex(uint16_t i) { int16_t iGroup = i * SEGMENT.groupLength(); /* reverse just an individual segment */ int16_t realIndex = iGroup; - if (IS_REVERSE) realIndex = SEGMENT.length() -iGroup -1; + if (IS_REVERSE) { + if (IS_MIRROR) { + realIndex = (SEGMENT.length() -1) / 2 - iGroup; //only need to index half the pixels + } else { + realIndex = SEGMENT.length() - iGroup - 1; + } + } realIndex += SEGMENT.start; - /* Reverse the whole string */ - if (reverseMode) realIndex = _length - 1 - realIndex; return realIndex; } void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) { - RgbwColor col; - switch (colorOrder) - { - case 0: col.G = g; col.R = r; col.B = b; break; //0 = GRB, default - case 1: col.G = r; col.R = g; col.B = b; break; //1 = RGB, common for WS2811 - case 2: col.G = b; col.R = r; col.B = g; break; //2 = BRG - case 3: col.G = r; col.R = b; col.B = g; break; //3 = RBG - case 4: col.G = b; col.R = g; col.B = r; break; //4 = BGR - default: col.G = g; col.R = b; col.B = r; break; //5 = GBR + //auto calculate white channel value if enabled + if (isRgbw) { + if (rgbwMode == RGBW_MODE_AUTO_BRIGHTER || (w == 0 && (rgbwMode == RGBW_MODE_DUAL || rgbwMode == RGBW_MODE_LEGACY))) + { + //white value is set to lowest RGB channel + //thank you to @Def3nder! + w = r < g ? (r < b ? r : b) : (g < b ? g : b); + } else if (rgbwMode == RGBW_MODE_AUTO_ACCURATE && w == 0) + { + w = r < g ? (r < b ? r : b) : (g < b ? g : b); + r -= w; g -= w; b -= w; + } } - col.W = w; - if (!_cronixieMode) - { - uint16_t skip = _skipFirstMode ? LED_SKIP_AMOUNT : 0; - if (SEGLEN) {//from segment - /* Set all the pixels in the group, ensuring _skipFirstMode is honored */ - bool reversed = reverseMode ^ IS_REVERSE; - uint16_t realIndex = realPixelIndex(i); + uint16_t skip = _skipFirstMode ? LED_SKIP_AMOUNT : 0; + if (SEGLEN) {//from segment - for (uint16_t j = 0; j < SEGMENT.grouping; j++) { - int16_t indexSet = realIndex + (reversed ? -j : j); - int16_t indexSetRev = indexSet; - if (reverseMode) indexSetRev = _length - 1 - indexSet; - if (indexSetRev >= SEGMENT.start && indexSetRev < SEGMENT.stop) bus->SetPixelColor(indexSet + skip, col); - } - } else { //live data, etc. - if (reverseMode) i = _length - 1 - i; - bus->SetPixelColor(i + skip, col); + //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments) + if (_bri_t < 255) { + r = scale8(r, _bri_t); + g = scale8(g, _bri_t); + b = scale8(b, _bri_t); + w = scale8(w, _bri_t); } - if (skip && i == 0) { - for (uint16_t j = 0; j < skip; j++) { - bus->SetPixelColor(j, RgbwColor(0, 0, 0, 0)); + uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b)); + + /* Set all the pixels in the group, ensuring _skipFirstMode is honored */ + bool reversed = IS_REVERSE; + uint16_t realIndex = realPixelIndex(i); + + for (uint16_t j = 0; j < SEGMENT.grouping; j++) { + int indexSet = realIndex + (reversed ? -j : j); + if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; + if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) { + busses.setPixelColor(indexSet + skip, col); + if (IS_MIRROR) { //set the corresponding mirrored pixel + uint16_t indexMir = SEGMENT.stop - indexSet + SEGMENT.start - 1; + if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir]; + busses.setPixelColor(indexMir + skip, col); + } } } - return; + } else { //live data, etc. + if (i < customMappingSize) i = customMappingTable[i]; + + uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b)); + busses.setPixelColor(i + skip, col); } - - //CRONIXIE - if(i>6)return; - byte o = 10*i; - if (_cronixieBacklightEnabled && _cronixieDigits[i] <11) - { - byte r2 = _segments[0].colors[1] >>16; - byte g2 = _segments[0].colors[1] >> 8; - byte b2 = _segments[0].colors[1]; - byte w2 = _segments[0].colors[1] >>24; - for (int j=o; j< o+19; j++) - { - bus->SetPixelColor(j, RgbwColor(r2,g2,b2,w2)); + if (skip && i == 0) { + for (uint16_t j = 0; j < skip; j++) { + busses.setPixelColor(j, BLACK); } - } else - { - for (int j=o; j< o+19; j++) - { - bus->SetPixelColor(j, RgbwColor(0,0,0,0)); - } - } - if (_skipFirstMode) o += LED_SKIP_AMOUNT; - switch(_cronixieDigits[i]) - { - case 0: bus->SetPixelColor(o+5, col); break; - case 1: bus->SetPixelColor(o+0, col); break; - case 2: bus->SetPixelColor(o+6, col); break; - case 3: bus->SetPixelColor(o+1, col); break; - case 4: bus->SetPixelColor(o+7, col); break; - case 5: bus->SetPixelColor(o+2, col); break; - case 6: bus->SetPixelColor(o+8, col); break; - case 7: bus->SetPixelColor(o+3, col); break; - case 8: bus->SetPixelColor(o+9, col); break; - case 9: bus->SetPixelColor(o+4, col); break; - } -} - -void WS2812FX::driverModeCronixie(bool b) -{ - _cronixieMode = b; - _segments[0].stop = (b) ? 6 : _length; -} - -void WS2812FX::setCronixieBacklight(bool b) -{ - _cronixieBacklightEnabled = b; -} - -void WS2812FX::setCronixieDigits(byte d[]) -{ - for (int i = 0; i<6; i++) - { - _cronixieDigits[i] = d[i]; } } @@ -212,8 +228,11 @@ void WS2812FX::setCronixieDigits(byte d[]) //you can set it to 0 if the ESP is powered by USB and the LEDs by external void WS2812FX::show(void) { - if (_callback) _callback(); - + + // avoid race condition, caputre _callback value + show_callback callback = _callback; + if (callback) callback(); + //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 @@ -242,21 +261,22 @@ void WS2812FX::show(void) { for (uint16_t i = 0; i < _length; i++) //sum up the usage of each LED { - RgbwColor c = bus->GetPixelColorRgbw(i); + uint32_t c = busses.getPixelColor(i); + byte r = c >> 16, g = c >> 8, b = c, w = c >> 24; if(useWackyWS2815PowerModel) { // ignore white component on WS2815 power calculation - powerSum += (max(max(c.R,c.G),c.B)) * 3; + powerSum += (MAX(MAX(r,g),b)) * 3; } else { - powerSum += (c.R + c.G + c.B + c.W); + powerSum += (r + g + b + w); } } - if (_rgbwMode) //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less + if (isRgbw) //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less { powerSum *= 3; powerSum = powerSum >> 2; //same as /= 4 @@ -271,24 +291,52 @@ void WS2812FX::show(void) { uint16_t scaleI = scale * 255; uint8_t scaleB = (scaleI > 255) ? 255 : scaleI; uint8_t newBri = scale8(_brightness, scaleB); - bus->SetBrightness(newBri); + busses.setBrightness(newBri); currentMilliamps = (powerSum0 * newBri) / puPerMilliamp; } else { currentMilliamps = powerSum / puPerMilliamp; - bus->SetBrightness(_brightness); + busses.setBrightness(_brightness); } currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate currentMilliamps += _length; //add standby power back to estimate } else { currentMilliamps = 0; - bus->SetBrightness(_brightness); + busses.setBrightness(_brightness); } - bus->Show(); - _lastShow = millis(); + // 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; + if (diff > 0) fpsCurr = 1000 / diff; + _cumulativeFps = (3 * _cumulativeFps + fpsCurr) >> 2; + _lastShow = now; } +/** + * Returns a true value if any of the strips are still being updated. + * On some hardware (ESP32), strip updates are done asynchronously. + */ +bool WS2812FX::isUpdating() { + return !busses.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 + */ +uint16_t WS2812FX::getFps() { + if (millis() - _lastShow > 2000) return 0; + return _cumulativeFps +1; +} + +/** + * Forces the next frame to be computed on all active segments. + */ void WS2812FX::trigger() { _triggered = true; } @@ -312,17 +360,18 @@ uint8_t WS2812FX::getModeCount() uint8_t WS2812FX::getPaletteCount() { - return 13 + gGradientPaletteCount; + return 13 + GRADIENT_PALETTE_COUNT; } -//TODO transitions +//TODO effect transitions bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t p) { - uint8_t mainSeg = getMainSegmentId(); Segment& seg = _segments[getMainSegmentId()]; uint8_t modePrev = seg.mode, speedPrev = seg.speed, intensityPrev = seg.intensity, palettePrev = seg.palette; + bool applied = false; + if (applyToAllSelected) { for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { @@ -332,9 +381,12 @@ bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t p) { _segments[i].intensity = in; _segments[i].palette = p; setMode(i, m); + applied = true; } } - } else { + } + + if (!applyToAllSelected || !applied) { seg.speed = s; seg.intensity = in; seg.palette = p; @@ -351,21 +403,37 @@ void WS2812FX::setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w void WS2812FX::setColor(uint8_t slot, uint32_t c) { if (slot >= NUM_COLORS) return; + + bool applied = false; + if (applyToAllSelected) { for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { - if (_segments[i].isSelected()) _segments[i].colors[slot] = c; + if (_segments[i].isSelected()) { + _segments[i].setColor(slot, c, i); + applied = true; + } } - } else { - _segments[getMainSegmentId()].colors[slot] = c; + } + + if (!applyToAllSelected || !applied) { + uint8_t mainseg = getMainSegmentId(); + _segments[mainseg].setColor(slot, c, mainseg); } } void WS2812FX::setBrightness(uint8_t b) { + if (gammaCorrectBri) b = gamma8(b); if (_brightness == b) return; - _brightness = (gammaCorrectBri) ? gamma8(b) : b; + _brightness = b; _segment_index = 0; - if (SEGENV.next_time > millis() + 22) show();//apply brightness change immediately if no refresh soon + if (_brightness == 0) { //unfreeze all segments on power off + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + _segments[i].setOption(SEG_OPTION_FREEZE, false); + } + } + if (SEGENV.next_time > millis() + 22 && millis() - _lastShow > MIN_SHOW_DELAY) show();//apply brightness change immediately if no refresh soon } uint8_t WS2812FX::getMode(void) { @@ -399,7 +467,12 @@ uint8_t WS2812FX::getMaxSegments(void) { uint8_t WS2812FX::getMainSegmentId(void) { if (mainSegment >= MAX_NUM_SEGMENTS) return 0; - return mainSegment; + if (_segments[mainSegment].isActive()) return mainSegment; + for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) //get first active + { + if (_segments[i].isActive()) return i; + } + return 0; } uint32_t WS2812FX::getColor(void) { @@ -408,41 +481,15 @@ uint32_t WS2812FX::getColor(void) { uint32_t WS2812FX::getPixelColor(uint16_t i) { - i = realPixelIndex(i) + (_skipFirstMode ? LED_SKIP_AMOUNT : 0); + i = realPixelIndex(i); + + if (i < customMappingSize) i = customMappingTable[i]; + + if (_skipFirstMode) i += LED_SKIP_AMOUNT; - if (_cronixieMode) - { - if(i>6)return 0; - byte o = 10*i; - switch(_cronixieDigits[i]) - { - case 0: i=o+5; break; - case 1: i=o+0; break; - case 2: i=o+6; break; - case 3: i=o+1; break; - case 4: i=o+7; break; - case 5: i=o+2; break; - case 6: i=o+8; break; - case 7: i=o+3; break; - case 8: i=o+9; break; - case 9: i=o+4; break; - default: return 0; - } - } if (i >= _lengthRaw) return 0; - RgbwColor col = bus->GetPixelColorRgbw(i); - switch (colorOrder) - { - // W G R B - case 0: return ((col.W << 24) | (col.G << 8) | (col.R << 16) | (col.B)); //0 = GRB, default - case 1: return ((col.W << 24) | (col.R << 8) | (col.G << 16) | (col.B)); //1 = RGB, common for WS2811 - case 2: return ((col.W << 24) | (col.B << 8) | (col.R << 16) | (col.G)); //2 = BRG - case 3: return ((col.W << 24) | (col.R << 8) | (col.B << 16) | (col.G)); //3 = RBG - case 4: return ((col.W << 24) | (col.B << 8) | (col.G << 16) | (col.R)); //4 = BGR - case 5: return ((col.W << 24) | (col.G << 8) | (col.B << 16) | (col.R)); //5 = GBR - } - return 0; + return busses.getPixelColor(i); } WS2812FX::Segment& WS2812FX::getSegment(uint8_t id) { @@ -462,6 +509,15 @@ uint32_t WS2812FX::getLastShow(void) { return _lastShow; } +//TODO these need to be on a per-strip basis +uint8_t WS2812FX::getColorOrder(void) { + return COL_ORDER_GRB; +} + +void WS2812FX::setColorOrder(uint8_t co) { + //bus->SetColorOrder(co); +} + void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) { if (n >= MAX_NUM_SEGMENTS) return; Segment& seg = _segments[n]; @@ -505,18 +561,38 @@ void WS2812FX::resetSegments() { _segments[0].colors[0] = DEFAULT_COLOR; _segments[0].start = 0; _segments[0].speed = DEFAULT_SPEED; + _segments[0].intensity = DEFAULT_INTENSITY; _segments[0].stop = _length; _segments[0].grouping = 1; - _segments[0].setOption(0, 1); //select + _segments[0].setOption(SEG_OPTION_SELECTED, 1); + _segments[0].setOption(SEG_OPTION_ON, 1); + _segments[0].opacity = 255; + for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) { _segments[i].colors[0] = color_wheel(i*51); _segments[i].grouping = 1; + _segments[i].setOption(SEG_OPTION_ON, 1); + _segments[i].opacity = 255; + _segments[i].speed = DEFAULT_SPEED; + _segments[i].intensity = DEFAULT_INTENSITY; _segment_runtimes[i].reset(); } _segment_runtimes[0].reset(); } +//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply) +void WS2812FX::setPixelSegment(uint8_t n) +{ + if (n < MAX_NUM_SEGMENTS) { + _segment_index = n; + _virtualSegmentLength = SEGMENT.length(); + } else { + _segment_index = 0; + _virtualSegmentLength = 0; + } +} + void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { if (i2 >= i) @@ -533,36 +609,46 @@ void WS2812FX::setShowCallback(show_callback cb) _callback = cb; } +void WS2812FX::setTransition(uint16_t t) +{ + _transitionDur = t; +} + void WS2812FX::setTransitionMode(bool t) { - _segment_index = getMainSegmentId(); - SEGMENT.setOption(7,t); - if (!t) return; unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled - if (SEGMENT.mode == FX_MODE_STATIC && SEGENV.next_time > waitMax) SEGENV.next_time = waitMax; + for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++) + { + _segment_index = i; + SEGMENT.setOption(SEG_OPTION_TRANSITIONAL, t); + + if (t && SEGMENT.mode == FX_MODE_STATIC && SEGENV.next_time > waitMax) SEGENV.next_time = waitMax; + } } /* * color blend function */ -uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { +uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) { if(blend == 0) return color1; - if(blend == 255) return color2; + uint16_t blendmax = b16 ? 0xFFFF : 0xFF; + if(blend == blendmax) return color2; + uint8_t shift = b16 ? 16 : 8; - uint32_t w1 = (color1 >> 24) & 0xff; - uint32_t r1 = (color1 >> 16) & 0xff; - uint32_t g1 = (color1 >> 8) & 0xff; - uint32_t b1 = color1 & 0xff; + uint32_t w1 = (color1 >> 24) & 0xFF; + uint32_t r1 = (color1 >> 16) & 0xFF; + uint32_t g1 = (color1 >> 8) & 0xFF; + uint32_t b1 = color1 & 0xFF; - uint32_t w2 = (color2 >> 24) & 0xff; - uint32_t r2 = (color2 >> 16) & 0xff; - uint32_t g2 = (color2 >> 8) & 0xff; - uint32_t b2 = color2 & 0xff; + uint32_t w2 = (color2 >> 24) & 0xFF; + uint32_t r2 = (color2 >> 16) & 0xFF; + uint32_t g2 = (color2 >> 8) & 0xFF; + uint32_t b2 = color2 & 0xFF; - uint32_t w3 = ((w2 * blend) + (w1 * (255 - blend))) >> 8; - uint32_t r3 = ((r2 * blend) + (r1 * (255 - blend))) >> 8; - uint32_t g3 = ((g2 * blend) + (g1 * (255 - blend))) >> 8; - uint32_t b3 = ((b2 * blend) + (b1 * (255 - blend))) >> 8; + uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift; + uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift; + uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift; + uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift; return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3)); } @@ -576,6 +662,14 @@ void WS2812FX::fill(uint32_t c) { } } +/* + * Blends the specified color with the existing pixel color. + */ +void WS2812FX::blendPixelColor(uint16_t n, uint32_t color, uint8_t blend) +{ + setPixelColor(n, color_blend(getPixelColor(n), color, blend)); +} + /* * fade out function, higher rate = quicker fade */ @@ -644,6 +738,32 @@ uint16_t WS2812FX::triwave16(uint16_t in) return 0xFFFF - (in - 0x8000)*2; } +/* + * 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 + * @returns signed waveform value + */ +int8_t WS2812FX::tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { + int8_t a = 127; + if (x > 127) { + a = -127; + x -= 127; + } + + if (x < attdec) { //inc to max + return (int16_t) x * a / attdec; + } + else if (x < pulsewidth - attdec) { //max + return a; + } + else if (x < pulsewidth) { //dec to 0 + return (int16_t) (pulsewidth - x) * a / attdec; + } + return 0; +} + /* * Put a value 0 to 255 in to get a color value. * The colours are a transition r -> g -> b -> back to r @@ -673,7 +793,7 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { r = random8(); x = abs(pos - r); y = 255 - x; - d = min(x, y); + d = MIN(x, y); } return r; } @@ -695,6 +815,15 @@ CRGB WS2812FX::col_to_crgb(uint32_t color) } +void WS2812FX::load_gradient_palette(uint8_t index) +{ + byte i = constrain(index, 0, GRADIENT_PALETTE_COUNT -1); + byte tcp[72]; //support gradient palettes with up to 18 entries + memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i])), 72); + targetPalette.loadDynamicGradientPalette(tcp); +} + + /* * FastLED palette modes helper function. Limitation: Due to memory reasons, multiple active segments with FastLED will disable the Palette transitions */ @@ -704,26 +833,28 @@ void WS2812FX::handle_palette(void) _segment_index_palette_last = _segment_index; byte paletteIndex = SEGMENT.palette; - if (SEGMENT.mode == FX_MODE_GLITTER && paletteIndex == 0) paletteIndex = 11; + if (paletteIndex == 0) //default palette. Differs depending on effect + { + switch (SEGMENT.mode) + { + case FX_MODE_FIRE_2012 : paletteIndex = 35; break; //heat palette + case FX_MODE_COLORWAVES : paletteIndex = 26; break; //landscape 33 + case FX_MODE_FILLNOISE8 : paletteIndex = 9; break; //ocean colors + case FX_MODE_NOISE16_1 : paletteIndex = 20; break; //Drywet + case FX_MODE_NOISE16_2 : paletteIndex = 43; break; //Blue cyan yellow + case FX_MODE_NOISE16_3 : paletteIndex = 35; break; //heat palette + case FX_MODE_NOISE16_4 : paletteIndex = 26; break; //landscape 33 + case FX_MODE_GLITTER : paletteIndex = 11; break; //rainbow colors + case FX_MODE_SUNRISE : paletteIndex = 35; break; //heat palette + case FX_MODE_FLOW : paletteIndex = 6; break; //party + } + } if (SEGMENT.mode >= FX_MODE_METEOR && paletteIndex == 0) paletteIndex = 4; switch (paletteIndex) { - case 0: {//default palette. Differs depending on effect - switch (SEGMENT.mode) - { - case FX_MODE_FIRE_2012 : targetPalette = gGradientPalettes[22]; break;//heat palette - case FX_MODE_COLORWAVES : targetPalette = gGradientPalettes[13]; break;//landscape 33 - case FX_MODE_FILLNOISE8 : targetPalette = OceanColors_p; break; - case FX_MODE_NOISE16_1 : targetPalette = gGradientPalettes[17]; break;//Drywet - case FX_MODE_NOISE16_2 : targetPalette = gGradientPalettes[30]; break;//Blue cyan yellow - case FX_MODE_NOISE16_3 : targetPalette = gGradientPalettes[22]; break;//heat palette - case FX_MODE_NOISE16_4 : targetPalette = gGradientPalettes[13]; break;//landscape 33 - //case FX_MODE_GLITTER : targetPalette = RainbowColors_p; break; - - default: targetPalette = PartyColors_p; break;//palette, bpm - } - break;} + case 0: //default palette. Exceptions for specific effects above + targetPalette = PartyColors_p; break; case 1: {//periodically replace palette with a random one. Doesn't work with multiple FastLED segments if (!singleSegmentMode) { @@ -741,25 +872,25 @@ void WS2812FX::handle_palette(void) case 2: {//primary color only CRGB prim = col_to_crgb(SEGCOLOR(0)); targetPalette = CRGBPalette16(prim); break;} - case 3: {//based on primary - //considering performance implications - CRGB prim = col_to_crgb(SEGCOLOR(0)); - CHSV prim_hsv = rgb2hsv_approximate(prim); - targetPalette = CRGBPalette16( - CHSV(prim_hsv.h, prim_hsv.s, prim_hsv.v), //color itself - CHSV(prim_hsv.h, max(prim_hsv.s - 50,0), prim_hsv.v), //less saturated - CHSV(prim_hsv.h, prim_hsv.s, max(prim_hsv.v - 50,0)), //darker - CHSV(prim_hsv.h, prim_hsv.s, prim_hsv.v)); //color itself - break;} - case 4: {//primary + secondary + case 3: {//primary + secondary CRGB prim = col_to_crgb(SEGCOLOR(0)); CRGB sec = col_to_crgb(SEGCOLOR(1)); - targetPalette = CRGBPalette16(sec,prim); break;} - case 5: {//based on primary + secondary + targetPalette = CRGBPalette16(prim,prim,sec,sec); break;} + case 4: {//primary + secondary + tertiary CRGB prim = col_to_crgb(SEGCOLOR(0)); CRGB sec = col_to_crgb(SEGCOLOR(1)); CRGB ter = col_to_crgb(SEGCOLOR(2)); targetPalette = CRGBPalette16(ter,sec,prim); break;} + case 5: {//primary + secondary (+tert if not off), more distinct + CRGB prim = col_to_crgb(SEGCOLOR(0)); + CRGB sec = col_to_crgb(SEGCOLOR(1)); + if (SEGCOLOR(2)) { + CRGB ter = col_to_crgb(SEGCOLOR(2)); + targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim); + } else { + targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); + } + break;} case 6: //Party colors targetPalette = PartyColors_p; break; case 7: //Cloud colors @@ -775,10 +906,10 @@ void WS2812FX::handle_palette(void) case 12: //Rainbow stripe colors targetPalette = RainbowStripeColors_p; break; default: //progmem palettes - targetPalette = gGradientPalettes[constrain(SEGMENT.palette -13, 0, gGradientPaletteCount -1)]; + load_gradient_palette(paletteIndex -13); } - if (singleSegmentMode && paletteFade) //only blend if just one segment uses FastLED mode + if (singleSegmentMode && paletteFade && SEGENV.call > 0) //only blend if just one segment uses FastLED mode { nblendPaletteTowardPalette(currentPalette, targetPalette, 48); } else @@ -787,17 +918,39 @@ void WS2812FX::handle_palette(void) } } + +/* + * Gets a single color from the currently selected palette. + * @param i Palette Index (if mapping is true, the full palette will be SEGLEN long, if false, 255). Will wrap around automatically. + * @param mapping if true, LED position in segment is considered for color + * @param wrap FastLED palettes will usally wrap back to the start smoothly. Set false to get a hard edge + * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead + * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) + * @returns Single color from palette + */ uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) { - if (SEGMENT.palette == 0 && mcol < 3) return SEGCOLOR(mcol); //WS2812FX default + if (SEGMENT.palette == 0 && mcol < 3) { + uint32_t color = SEGCOLOR(mcol); + if (pbri != 255) { + CRGB crgb_color = col_to_crgb(color); + crgb_color.nscale8_video(pbri); + return crgb_to_col(crgb_color); + } else { + return color; + } + } + uint8_t paletteIndex = i; if (mapping) paletteIndex = (i*255)/(SEGLEN -1); if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" CRGB fastled_col; fastled_col = ColorFromPalette( currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND); - return fastled_col.r*65536 + fastled_col.g*256 + fastled_col.b; + + return crgb_to_col(fastled_col); } +//@returns `true` if color, mode, speed, intensity and palette match bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b) { //if (a->start != b->start) return false; @@ -810,12 +963,38 @@ bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b) if (a->speed != b->speed) return false; if (a->intensity != b->intensity) return false; if (a->palette != b->palette) return false; - //if (a->getOption(1) != b->getOption(1)) return false; //reverse + //if (a->getOption(SEG_OPTION_REVERSED) != b->getOption(SEG_OPTION_REVERSED)) return false; return true; } -//gamma 2.4 lookup table used for color correction -const byte gammaT[] = { + +//load custom mapping table from JSON file +void WS2812FX::deserializeMap(void) { + if (!WLED_FS.exists("/ledmap.json")) return; + DynamicJsonDocument doc(JSON_BUFFER_SIZE); // full sized buffer for larger maps + + DEBUG_PRINTLN(F("Reading LED map from /ledmap.json...")); + + if (!readObjectFromFile("/ledmap.json", nullptr, &doc)) return; //if file does not exist just exit + + if (customMappingTable != nullptr) { + delete[] customMappingTable; + customMappingTable = nullptr; + customMappingSize = 0; + } + + 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; i +#include + +#define NODE_TYPE_ID_UNDEFINED 0 +#define NODE_TYPE_ID_ESP8266 82 +#define NODE_TYPE_ID_ESP32 32 + +/*********************************************************************************************\ +* NodeStruct +\*********************************************************************************************/ +struct NodeStruct +{ + String nodeName; + IPAddress ip; + uint8_t unit; + uint8_t age; + uint8_t nodeType; + uint32_t build; + + NodeStruct() : age(0), nodeType(0), build(0) + { + for (uint8_t i = 0; i < 4; ++i) { ip[i] = 0; } + } +}; +typedef std::map NodesMap; + +#endif // WLED_NODESTRUCT_H diff --git a/wled00/NpbWrapper.h b/wled00/NpbWrapper.h deleted file mode 100644 index 54d21ac18..000000000 --- a/wled00/NpbWrapper.h +++ /dev/null @@ -1,315 +0,0 @@ -//this code is a modified version of https://github.com/Makuna/NeoPixelBus/issues/103 -#ifndef NpbWrapper_h -#define NpbWrapper_h - -//PIN CONFIGURATION -#define LEDPIN 2 //strip pin. Any for ESP32, gpio2 or 3 is recommended for ESP8266 (gpio2/3 are labeled D4/RX on NodeMCU and Wemos) -//#define USE_APA102 // Uncomment for using APA102 LEDs. -//#define USE_WS2801 // Uncomment for using WS2801 LEDs (make sure you have NeoPixelBus v2.5.6 or newer) -//#define USE_LPD8806 // Uncomment for using LPD8806 -//#define WLED_USE_ANALOG_LEDS //Uncomment for using "dumb" PWM controlled LEDs (see pins below, default R: gpio5, G: 12, B: 15, W: 13) -//#define WLED_USE_H801 //H801 controller. Please uncomment #define WLED_USE_ANALOG_LEDS as well -//#define WLED_USE_5CH_LEDS //5 Channel H801 for cold and warm white - -#define BTNPIN 0 //button pin. Needs to have pullup (gpio0 recommended) -#define IR_PIN 4 //infrared pin (-1 to disable) MagicHome: 4, H801 Wifi: 0 -#define RLYPIN 12 //pin for relay, will be set HIGH if LEDs are on (-1 to disable). Also usable for standby leds, triggers,... -#define AUXPIN -1 //debug auxiliary output pin (-1 to disable) - -#define RLYMDE 1 //mode for relay, 0: LOW if LEDs are on 1: HIGH if LEDs are on - -//END CONFIGURATION - -#if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) - #define CLKPIN 0 - #define DATAPIN 2 - #if BTNPIN == CLKPIN || BTNPIN == DATAPIN - #undef BTNPIN // Deactivate button pin if it conflicts with one of the APA102 pins. - #endif -#endif - -#ifdef WLED_USE_ANALOG_LEDS - //PWM pins - PINs 15,13,12,14 (W2 = 04)are used with H801 Wifi LED Controller - #ifdef WLED_USE_H801 - #define RPIN 15 //R pin for analog LED strip - #define GPIN 13 //G pin for analog LED strip - #define BPIN 12 //B pin for analog LED strip - #define WPIN 14 //W pin for analog LED strip - #define W2PIN 04 //W2 pin for analog LED strip - #undef BTNPIN - #undef IR_PIN - #define IR_PIN 0 //infrared pin (-1 to disable) MagicHome: 4, H801 Wifi: 0 - #else - //PWM pins - PINs 5,12,13,15 are used with Magic Home LED Controller - #define RPIN 5 //R pin for analog LED strip - #define GPIN 12 //G pin for analog LED strip - #define BPIN 15 //B pin for analog LED strip - #define WPIN 13 //W pin for analog LED strip - #endif - #undef RLYPIN - #define RLYPIN -1 //disable as pin 12 is used by analog LEDs -#endif - -//automatically uses the right driver method for each platform -#ifdef ARDUINO_ARCH_ESP32 - #ifdef USE_APA102 - #define PIXELMETHOD DotStarMethod - #elif defined(USE_WS2801) - #define PIXELMETHOD NeoWs2801Method - #elif defined(USE_LPD8806) - #define PIXELMETHOD Lpd8806Method - #else - #define PIXELMETHOD NeoEsp32Rmt0Ws2812xMethod - #endif -#else //esp8266 - //autoselect the right method depending on strip pin - #ifdef USE_APA102 - #define PIXELMETHOD DotStarMethod - #elif defined(USE_WS2801) - #define PIXELMETHOD NeoWs2801Method - #elif defined(USE_LPD8806) - #define PIXELMETHOD Lpd8806Method - #elif LEDPIN == 2 - #define PIXELMETHOD NeoEsp8266Uart1Ws2813Method //if you get an error here, try to change to NeoEsp8266UartWs2813Method or update Neopixelbus - #elif LEDPIN == 3 - #define PIXELMETHOD NeoEsp8266Dma800KbpsMethod - #else - #define PIXELMETHOD NeoEsp8266BitBang800KbpsMethod - #pragma message "Software BitBang will be used because of your selected LED pin. This may cause flicker. Use GPIO 2 or 3 for best results." - #endif -#endif - - -//you can now change the color order in the web settings -#ifdef USE_APA102 - #define PIXELFEATURE3 DotStarBgrFeature - #define PIXELFEATURE4 DotStarLbgrFeature -#elif defined(USE_LPD8806) - #define PIXELFEATURE3 Lpd8806GrbFeature - #define PIXELFEATURE4 Lpd8806GrbFeature -#else - #define PIXELFEATURE3 NeoGrbFeature - #define PIXELFEATURE4 NeoGrbwFeature -#endif - - -#include - -enum NeoPixelType -{ - NeoPixelType_None = 0, - NeoPixelType_Grb = 1, - NeoPixelType_Grbw = 2, - NeoPixelType_End = 3 -}; - -class NeoPixelWrapper -{ -public: - NeoPixelWrapper() : - // initialize each member to null - _pGrb(NULL), - _pGrbw(NULL), - _type(NeoPixelType_None) - { - - } - - ~NeoPixelWrapper() - { - cleanup(); - } - - void Begin(NeoPixelType type, uint16_t countPixels) - { - cleanup(); - _type = type; - - switch (_type) - { - case NeoPixelType_Grb: - #if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) - _pGrb = new NeoPixelBrightnessBus(countPixels, CLKPIN, DATAPIN); - #else - _pGrb = new NeoPixelBrightnessBus(countPixels, LEDPIN); - #endif - _pGrb->Begin(); - break; - - case NeoPixelType_Grbw: - #if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) - _pGrbw = new NeoPixelBrightnessBus(countPixels, CLKPIN, DATAPIN); - #else - _pGrbw = new NeoPixelBrightnessBus(countPixels, LEDPIN); - #endif - _pGrbw->Begin(); - break; - } - - #ifdef WLED_USE_ANALOG_LEDS - #ifdef ARDUINO_ARCH_ESP32 - ledcSetup(0, 5000, 8); - ledcAttachPin(RPIN, 0); - ledcSetup(1, 5000, 8); - ledcAttachPin(GPIN, 1); - ledcSetup(2, 5000, 8); - ledcAttachPin(BPIN, 2); - if(_type == NeoPixelType_Grbw) - { - ledcSetup(3, 5000, 8); - ledcAttachPin(WPIN, 3); - #ifdef WLED_USE_5CH_LEDS - ledcSetup(4, 5000, 8); - ledcAttachPin(W2PIN, 4); - #endif - } - #else // ESP8266 - //init PWM pins - PINs 5,12,13,15 are used with Magic Home LED Controller - pinMode(RPIN, OUTPUT); - pinMode(GPIN, OUTPUT); - pinMode(BPIN, OUTPUT); - if(_type == NeoPixelType_Grbw) - { - pinMode(WPIN, OUTPUT); - #ifdef WLED_USE_5CH_LEDS - pinMode(W2PIN, OUTPUT); - #endif - } - analogWriteRange(255); //same range as one RGB channel - analogWriteFreq(880); //PWM frequency proven as good for LEDs - #endif - #endif - } - -#ifdef WLED_USE_ANALOG_LEDS - void SetRgbwPwm(uint8_t r, uint8_t g, uint8_t b, uint8_t w, uint8_t w2=0) - { - #ifdef ARDUINO_ARCH_ESP32 - ledcWrite(0, r); //RPIN - ledcWrite(1, g); //GPIN - ledcWrite(2, b); //BPIN - switch (_type) { - case NeoPixelType_Grb: break; - #ifdef WLED_USE_5CH_LEDS - case NeoPixelType_Grbw: ledcWrite(3, w); ledcWrite(4, w2); break; - #else - case NeoPixelType_Grbw: ledcWrite(3, w); break; - #endif - } - #else - analogWrite(RPIN, r); - analogWrite(GPIN, g); - analogWrite(BPIN, b); - switch (_type) { - case NeoPixelType_Grb: break; - #ifdef WLED_USE_5CH_LEDS - case NeoPixelType_Grbw: analogWrite(WPIN, w); analogWrite(W2PIN, w2); break; - #else - case NeoPixelType_Grbw: analogWrite(WPIN, w); break; - #endif - } - #endif - } -#endif - - void Show() - { - byte b; - switch (_type) - { - case NeoPixelType_Grb: _pGrb->Show(); break; - case NeoPixelType_Grbw: _pGrbw->Show(); break; - } - } - - void SetPixelColor(uint16_t indexPixel, RgbwColor color) - { - switch (_type) { - case NeoPixelType_Grb: { - _pGrb->SetPixelColor(indexPixel, RgbColor(color.R,color.G,color.B)); - #ifdef WLED_USE_ANALOG_LEDS - if (indexPixel != 0) return; //set analog LEDs from first pixel - byte b = _pGrb->GetBrightness(); - SetRgbwPwm(color.R * b / 255, color.G * b / 255, color.B * b / 255, 0); - #endif - } - break; - case NeoPixelType_Grbw: { - #ifdef USE_LPD8806 - _pGrbw->SetPixelColor(indexPixel, RgbColor(color.R,color.G,color.B)); - #else - _pGrbw->SetPixelColor(indexPixel, color); - #endif - #ifdef WLED_USE_ANALOG_LEDS - if (indexPixel != 0) return; //set analog LEDs from first pixel - byte b = _pGrbw->GetBrightness(); - // check color values for Warm / Cold white mix (for RGBW) // EsplanexaDevice.cpp - #ifdef WLED_USE_5CH_LEDS - if (color.R == 255 & color.G == 255 && color.B == 255 && color.W == 255) { - SetRgbwPwm(0, 0, 0, 0, color.W * b / 255); - } else if (color.R == 127 & color.G == 127 && color.B == 127 && color.W == 255) { - SetRgbwPwm(0, 0, 0, color.W * b / 512, color.W * b / 255); - } else if (color.R == 0 & color.G == 0 && color.B == 0 && color.W == 255) { - SetRgbwPwm(0, 0, 0, color.W * b / 255, 0); - } else if (color.R == 130 & color.G == 90 && color.B == 0 && color.W == 255) { - SetRgbwPwm(0, 0, 0, color.W * b / 255, color.W * b / 512); - } else if (color.R == 255 & color.G == 153 && color.B == 0 && color.W == 255) { - SetRgbwPwm(0, 0, 0, color.W * b / 255, 0); - } else { // not only white colors - SetRgbwPwm(color.R * b / 255, color.G * b / 255, color.B * b / 255, color.W * b / 255); - } - #else - SetRgbwPwm(color.R * b / 255, color.G * b / 255, color.B * b / 255, color.W * b / 255); - #endif - #endif - } - break; - } - - } - - void SetBrightness(byte b) - { - switch (_type) { - case NeoPixelType_Grb: _pGrb->SetBrightness(b); break; - case NeoPixelType_Grbw:_pGrbw->SetBrightness(b); break; - } - } - - // NOTE: Due to feature differences, some support RGBW but the method name - // here needs to be unique, thus GetPixeColorRgbw - RgbwColor GetPixelColorRgbw(uint16_t indexPixel) const - { - switch (_type) { - case NeoPixelType_Grb: return _pGrb->GetPixelColor(indexPixel); break; - case NeoPixelType_Grbw: return _pGrbw->GetPixelColor(indexPixel); break; - } - return 0; - } - - uint8_t* GetPixels(void) - { - switch (_type) { - case NeoPixelType_Grb: return _pGrb->Pixels(); break; - case NeoPixelType_Grbw: return _pGrbw->Pixels(); break; - } - return 0; - } - - -private: - NeoPixelType _type; - - // have a member for every possible type - NeoPixelBrightnessBus* _pGrb; - NeoPixelBrightnessBus* _pGrbw; - - void cleanup() - { - switch (_type) { - case NeoPixelType_Grb: delete _pGrb ; _pGrb = NULL; break; - case NeoPixelType_Grbw: delete _pGrbw; _pGrbw = NULL; break; - } - } -}; -#endif diff --git a/wled00/__vm/.wled00.vsarduino.h b/wled00/__vm/.wled00.vsarduino.h index 722f8378f..2a9ef40a4 100644 --- a/wled00/__vm/.wled00.vsarduino.h +++ b/wled00/__vm/.wled00.vsarduino.h @@ -17,8 +17,10 @@ #define __ESP32_ESP32__ #define ESP_PLATFORM #define HAVE_CONFIG_H +#define GCC_NOT_5_2_0 0 +#define WITH_POSIX #define F_CPU 240000000L -#define ARDUINO 10809 +#define ARDUINO 108011 #define ARDUINO_ESP32_DEV #define ARDUINO_ARCH_ESP32 #define ESP32 @@ -92,18 +94,11 @@ typedef long pthread_cond_t; #include "arduino.h" #include -//#include "..\generic\Common.h" -//#include "..\generic\pins_arduino.h" - -//#undef F -//#define F(string_literal) ((const PROGMEM char *)(string_literal)) -//#undef PSTR -//#define PSTR(string_literal) ((const PROGMEM char *)(string_literal)) -//current vc++ does not understand this syntax so use older arduino example for intellisense -//todo:move to the new clang/gcc project types. #define interrupts() sei() #define noInterrupts() cli() +#define ESP_LOGI(tag, ...) + #include "wled00.ino" #include "wled01_eeprom.ino" #include "wled02_xml.ino" diff --git a/wled00/__vm/Compile.vmps.xml b/wled00/__vm/Compile.vmps.xml index a7819d208..6390398ed 100644 --- a/wled00/__vm/Compile.vmps.xml +++ b/wled00/__vm/Compile.vmps.xml @@ -1,8 +1,8 @@ - - + + - + diff --git a/wled00/wled12_alexa.ino b/wled00/alexa.cpp similarity index 50% rename from wled00/wled12_alexa.ino rename to wled00/alexa.cpp index c3bc01ab0..4f406a3f6 100644 --- a/wled00/wled12_alexa.ino +++ b/wled00/alexa.cpp @@ -1,10 +1,13 @@ +#include "wled.h" + /* - * Alexa Voice On/Off/Brightness Control. Emulates a Philips Hue bridge to Alexa. + * Alexa Voice On/Off/Brightness/Color Control. Emulates a Philips Hue bridge to Alexa. * * This was put together from these two excellent projects: * https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch * https://github.com/probonopd/ESP8266HueEmulator */ +#include "src/dependencies/espalexa/EspalexaDevice.h" #ifndef WLED_DISABLE_ALEXA void onAlexaChange(EspalexaDevice* dev); @@ -41,9 +44,9 @@ void onAlexaChange(EspalexaDevice* dev) if (bri == 0) { bri = briLast; - colorUpdated(10); + colorUpdated(NOTIFIER_CALL_MODE_ALEXA); } - } else applyMacro(macroAlexaOn); + } else applyPreset(macroAlexaOn); } else if (m == EspalexaDeviceProperty::off) { if (!macroAlexaOff) @@ -52,22 +55,39 @@ void onAlexaChange(EspalexaDevice* dev) { briLast = bri; bri = 0; - colorUpdated(10); + colorUpdated(NOTIFIER_CALL_MODE_ALEXA); } - } else applyMacro(macroAlexaOff); + } else applyPreset(macroAlexaOff); } else if (m == EspalexaDeviceProperty::bri) { bri = espalexaDevice->getValue(); - colorUpdated(10); + colorUpdated(NOTIFIER_CALL_MODE_ALEXA); } else //color { - uint32_t color = espalexaDevice->getRGB(); - col[3] = ((color >> 24) & 0xFF); // white color from Alexa is "pure white only" - col[0] = ((color >> 16) & 0xFF); - col[1] = ((color >> 8) & 0xFF); - col[2] = (color & 0xFF); - if (useRGBW && col[3] == 0) colorRGBtoRGBW(col); // do not touch white value if EspalexaDevice.cpp did set the white channel - colorUpdated(10); + if (espalexaDevice->getColorMode() == EspalexaColorMode::ct) //shade of white + { + uint16_t ct = espalexaDevice->getCt(); + if (strip.isRgbw) + { + switch (ct) { //these values empirically look good on RGBW + case 199: col[0]=255; col[1]=255; col[2]=255; col[3]=255; break; + case 234: col[0]=127; col[1]=127; col[2]=127; col[3]=255; break; + case 284: col[0]= 0; col[1]= 0; col[2]= 0; col[3]=255; break; + case 350: col[0]=130; col[1]= 90; col[2]= 0; col[3]=255; break; + case 383: col[0]=255; col[1]=153; col[2]= 0; col[3]=255; break; + } + } else { + colorCTtoRGB(ct, col); + } + } else { + uint32_t color = espalexaDevice->getRGB(); + + col[0] = ((color >> 16) & 0xFF); + col[1] = ((color >> 8) & 0xFF); + col[2] = ( color & 0xFF); + col[3] = 0; + } + colorUpdated(NOTIFIER_CALL_MODE_ALEXA); } } diff --git a/wled00/wled16_blynk.ino b/wled00/blynk.cpp similarity index 72% rename from wled00/wled16_blynk.ino rename to wled00/blynk.cpp index c09d0c185..ef53ca9b2 100644 --- a/wled00/wled16_blynk.ino +++ b/wled00/blynk.cpp @@ -1,3 +1,6 @@ +#include "wled.h" +#include "src/dependencies/blynk/Blynk/BlynkHandlers.h" + /* * Remote light control with the free Blynk app */ @@ -5,12 +8,12 @@ uint16_t blHue = 0; byte blSat = 255; -void initBlynk(const char* auth) +void initBlynk(const char *auth, const char *host, uint16_t port) { #ifndef WLED_DISABLE_BLYNK if (!WLED_CONNECTED) return; blynkEnabled = (auth[0] != 0); - if (blynkEnabled) Blynk.config(auth); + if (blynkEnabled) Blynk.config(auth, host, port); #endif } @@ -41,45 +44,45 @@ void updateBlynk() BLYNK_WRITE(V0) { bri = param.asInt();//bri - colorUpdated(9); + colorUpdated(NOTIFIER_CALL_MODE_BLYNK); } BLYNK_WRITE(V1) { blHue = param.asInt();//hue colorHStoRGB(blHue*10,blSat,(false)? colSec:col); - colorUpdated(9); + colorUpdated(NOTIFIER_CALL_MODE_BLYNK); } BLYNK_WRITE(V2) { blSat = param.asInt();//sat colorHStoRGB(blHue*10,blSat,(false)? colSec:col); - colorUpdated(9); + colorUpdated(NOTIFIER_CALL_MODE_BLYNK); } BLYNK_WRITE(V3) { bool on = (param.asInt()>0); - if (!on != !bri) {toggleOnOff(); colorUpdated(9);} + if (!on != !bri) {toggleOnOff(); colorUpdated(NOTIFIER_CALL_MODE_BLYNK);} } BLYNK_WRITE(V4) { effectCurrent = param.asInt()-1;//fx - colorUpdated(9); + colorUpdated(NOTIFIER_CALL_MODE_BLYNK); } BLYNK_WRITE(V5) { effectSpeed = param.asInt();//sx - colorUpdated(9); + colorUpdated(NOTIFIER_CALL_MODE_BLYNK); } BLYNK_WRITE(V6) { effectIntensity = param.asInt();//ix - colorUpdated(9); + colorUpdated(NOTIFIER_CALL_MODE_BLYNK); } BLYNK_WRITE(V7) diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h new file mode 100644 index 000000000..89f3e9079 --- /dev/null +++ b/wled00/bus_manager.h @@ -0,0 +1,406 @@ +#ifndef BusManager_h +#define BusManager_h + +/* + * Class for addressing various light types + */ + +#include "const.h" +#include "pin_manager.h" +#include "bus_wrapper.h" +#include + +//temporary struct for passing bus configuration to bus +struct BusConfig { + uint8_t type = TYPE_WS2812_RGB; + uint16_t count = 1; + uint16_t start = 0; + uint8_t colorOrder = COL_ORDER_GRB; + bool reversed = false; + uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false) { + type = busType; count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; + uint8_t nPins = 1; + if (type > 47) nPins = 2; + else if (type > 41 && type < 46) nPins = NUM_PWM_PINS(type); + for (uint8_t i = 0; i < nPins; i++) pins[i] = ppins[i]; + } +}; + +//parent class of BusDigital and BusPwm +class Bus { + public: + Bus(uint8_t type, uint16_t start) { + _type = type; + _start = start; + }; + + virtual void show() {} + virtual bool canShow() { return true; } + + virtual void setPixelColor(uint16_t pix, uint32_t c) {}; + + virtual void setBrightness(uint8_t b) {}; + + virtual uint32_t getPixelColor(uint16_t pix) { return 0; }; + + virtual void cleanup() {}; + + virtual ~Bus() { //throw the bus under the bus + } + + virtual uint8_t getPins(uint8_t* pinArray) { return 0; } + + uint16_t getStart() { + return _start; + } + + void setStart(uint16_t start) { + _start = start; + } + + virtual uint16_t getLength() { + return 1; + } + + virtual void setColorOrder() {} + + virtual uint8_t getColorOrder() { + return COL_ORDER_RGB; + } + + uint8_t getType() { + return _type; + } + + bool isOk() { + return _valid; + } + + bool reversed = false; + + protected: + uint8_t _type = TYPE_NONE; + uint8_t _bri = 255; + uint16_t _start = 0; + bool _valid = false; +}; + + +class BusDigital : public Bus { + public: + BusDigital(BusConfig &bc, uint8_t nr) : Bus(bc.type, bc.start) { + if (!IS_DIGITAL(bc.type) || !bc.count) return; + _pins[0] = bc.pins[0]; + if (!pinManager.allocatePin(_pins[0])) return; + if (IS_2PIN(bc.type)) { + _pins[1] = bc.pins[1]; + if (!pinManager.allocatePin(_pins[1])) { + cleanup(); return; + } + } + _len = bc.count; + reversed = bc.reversed; + _iType = PolyBus::getI(bc.type, _pins, nr); + if (_iType == I_NONE) return; + _busPtr = PolyBus::create(_iType, _pins, _len); + _valid = (_busPtr != nullptr); + _colorOrder = bc.colorOrder; + //Serial.printf("Successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n",nr, len, type, pins[0],pins[1],_iType); + }; + + void show() { + PolyBus::show(_busPtr, _iType); + } + + bool canShow() { + return PolyBus::canShow(_busPtr, _iType); + } + + void setBrightness(uint8_t b) { + //Fix for turning off onboard LED breaking bus + #ifdef LED_BUILTIN + if (_bri == 0 && b > 0) { + if (_pins[0] == LED_BUILTIN || _pins[1] == LED_BUILTIN) PolyBus::begin(_busPtr, _iType, _pins); + } + #endif + _bri = b; + PolyBus::setBrightness(_busPtr, _iType, b); + } + + void setPixelColor(uint16_t pix, uint32_t c) { + if (reversed) pix = _len - pix -1; + PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder); + } + + uint32_t getPixelColor(uint16_t pix) { + if (reversed) pix = _len - pix -1; + return PolyBus::getPixelColor(_busPtr, _iType, pix, _colorOrder); + } + + uint8_t getColorOrder() { + return _colorOrder; + } + + uint16_t getLength() { + return _len; + } + + uint8_t getPins(uint8_t* pinArray) { + uint8_t numPins = IS_2PIN(_type) ? 2 : 1; + for (uint8_t i = 0; i < numPins; i++) pinArray[i] = _pins[i]; + return numPins; + } + + void setColorOrder(uint8_t colorOrder) { + if (colorOrder > 5) return; + _colorOrder = colorOrder; + } + + void reinit() { + PolyBus::begin(_busPtr, _iType, _pins); + } + + void cleanup() { + //Serial.println("Digital Cleanup"); + PolyBus::cleanup(_busPtr, _iType); + _iType = I_NONE; + _valid = false; + _busPtr = nullptr; + pinManager.deallocatePin(_pins[0]); + pinManager.deallocatePin(_pins[1]); + } + + ~BusDigital() { + cleanup(); + } + + private: + uint8_t _colorOrder = COL_ORDER_GRB; + uint8_t _pins[2] = {255, 255}; + uint8_t _iType = I_NONE; + uint16_t _len = 0; + void * _busPtr = nullptr; +}; + + +class BusPwm : public Bus { + public: + BusPwm(BusConfig &bc) : Bus(bc.type, bc.start) { + if (!IS_PWM(bc.type)) return; + uint8_t numPins = NUM_PWM_PINS(bc.type); + + #ifdef ESP8266 + analogWriteRange(255); //same range as one RGB channel + analogWriteFreq(WLED_PWM_FREQ); + #else + _ledcStart = pinManager.allocateLedc(numPins); + if (_ledcStart == 255) { //no more free LEDC channels + deallocatePins(); return; + } + #endif + + for (uint8_t i = 0; i < numPins; i++) { + _pins[i] = bc.pins[i]; + if (!pinManager.allocatePin(_pins[i])) { + deallocatePins(); return; + } + #ifdef ESP8266 + pinMode(_pins[i], OUTPUT); + #else + ledcSetup(_ledcStart + i, WLED_PWM_FREQ, 8); + ledcAttachPin(_pins[i], _ledcStart + i); + #endif + } + reversed = bc.reversed; + _valid = true; + }; + + void setPixelColor(uint16_t pix, uint32_t c) { + if (pix != 0 || !_valid) return; //only react to first pixel + uint8_t r = c >> 16; + uint8_t g = c >> 8; + uint8_t b = c ; + uint8_t w = c >> 24; + + switch (_type) { + case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value + _data[0] = max(r, max(g, max(b, w))); break; + + case TYPE_ANALOG_2CH: //warm white + cold white, we'll need some nice handling here, for now just R+G channels + case TYPE_ANALOG_3CH: //standard dumb RGB + case TYPE_ANALOG_4CH: //RGBW + case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB + _data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = 0; break; + + default: return; + } + } + + //does no index check + uint32_t getPixelColor(uint16_t pix) { + return ((_data[3] << 24) | (_data[0] << 16) | (_data[1] << 8) | (_data[2])); + } + + void show() { + 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; + #ifdef ESP8266 + analogWrite(_pins[i], scaled); + #else + ledcWrite(_ledcStart + i, scaled); + #endif + } + } + + void setBrightness(uint8_t b) { + _bri = b; + } + + uint8_t getPins(uint8_t* pinArray) { + uint8_t numPins = NUM_PWM_PINS(_type); + for (uint8_t i = 0; i < numPins; i++) pinArray[i] = _pins[i]; + return numPins; + } + + void cleanup() { + deallocatePins(); + } + + ~BusPwm() { + cleanup(); + } + + private: + uint8_t _pins[5] = {255, 255, 255, 255, 255}; + uint8_t _data[5] = {255, 255, 255, 255, 255}; + #ifdef ARDUINO_ARCH_ESP32 + uint8_t _ledcStart = 255; + #endif + + void deallocatePins() { + uint8_t numPins = NUM_PWM_PINS(_type); + for (uint8_t i = 0; i < numPins; i++) { + if (!pinManager.isPinOk(_pins[i])) continue; + #ifdef ESP8266 + digitalWrite(_pins[i], LOW); //turn off PWM interrupt + #else + if (_ledcStart < 16) ledcDetachPin(_pins[i]); + #endif + pinManager.deallocatePin(_pins[i]); + } + #ifdef ARDUINO_ARCH_ESP32 + pinManager.deallocateLedc(_ledcStart, numPins); + #endif + } +}; + +class BusManager { + public: + BusManager() { + + }; + + //utility to get the approx. memory usage of a given BusConfig + uint32_t memUsage(BusConfig &bc) { + uint8_t type = bc.type; + uint16_t len = bc.count; + if (type < 32) { + #ifdef ESP8266 + if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem + if (type > 29) return len*20; //RGBW + return len*15; + } + if (type > 29) return len*4; //RGBW + return len*3; + #else //ESP32 RMT uses double buffer? + if (type > 29) return len*8; //RGBW + return len*6; + #endif + } + + if (type > 31 && type < 48) return 5; + if (type == 44 || type == 45) return len*4; //RGBW + return len*3; + } + + int add(BusConfig &bc) { + if (numBusses >= WLED_MAX_BUSSES) return -1; + if (IS_DIGITAL(bc.type)) { + busses[numBusses] = new BusDigital(bc, numBusses); + } else { + busses[numBusses] = new BusPwm(bc); + } + numBusses++; + return numBusses -1; + } + + //do not call this method from system context (network callback) + void removeAll() { + //Serial.println("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]; + numBusses = 0; + } + + void show() { + for (uint8_t i = 0; i < numBusses; i++) { + busses[i]->show(); + } + } + + void setPixelColor(uint16_t pix, uint32_t c) { + for (uint8_t i = 0; i < numBusses; i++) { + Bus* b = busses[i]; + uint16_t bstart = b->getStart(); + if (pix < bstart || pix >= bstart + b->getLength()) continue; + busses[i]->setPixelColor(pix - bstart, c); + } + } + + void setBrightness(uint8_t b) { + for (uint8_t i = 0; i < numBusses; i++) { + busses[i]->setBrightness(b); + } + } + + uint32_t getPixelColor(uint16_t pix) { + for (uint8_t i = 0; i < numBusses; i++) { + Bus* b = busses[i]; + uint16_t bstart = b->getStart(); + if (pix < bstart || pix >= bstart + b->getLength()) continue; + return b->getPixelColor(pix - bstart); + } + return 0; + } + + bool canAllShow() { + for (uint8_t i = 0; i < numBusses; i++) { + if (!busses[i]->canShow()) return false; + } + return true; + } + + Bus* getBus(uint8_t busNr) { + if (busNr >= numBusses) return nullptr; + return busses[busNr]; + } + + uint8_t getNumBusses() { + return numBusses; + } + + static bool isRgbw(uint8_t type) { + if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true; + if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; + return false; + } + + private: + uint8_t numBusses = 0; + Bus* busses[WLED_MAX_BUSSES]; +}; +#endif \ No newline at end of file diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h new file mode 100644 index 000000000..c96896c37 --- /dev/null +++ b/wled00/bus_wrapper.h @@ -0,0 +1,882 @@ +#ifndef BusWrapper_h +#define BusWrapper_h + +#include "NeoPixelBrightnessBus.h" + +//Hardware SPI Pins +#define P_8266_HS_MOSI 13 +#define P_8266_HS_CLK 14 +#define P_32_HS_MOSI 13 +#define P_32_HS_CLK 14 +#define P_32_VS_MOSI 23 +#define P_32_VS_CLK 18 + +//The dirty list of possible bus types. Quite a lot... +#define I_NONE 0 +//ESP8266 RGB +#define I_8266_U0_NEO_3 1 +#define I_8266_U1_NEO_3 2 +#define I_8266_DM_NEO_3 3 +#define I_8266_BB_NEO_3 4 +//RGBW +#define I_8266_U0_NEO_4 5 +#define I_8266_U1_NEO_4 6 +#define I_8266_DM_NEO_4 7 +#define I_8266_BB_NEO_4 8 +//400Kbps +#define I_8266_U0_400_3 9 +#define I_8266_U1_400_3 10 +#define I_8266_DM_400_3 11 +#define I_8266_BB_400_3 12 +//TM1418 (RGBW) +#define I_8266_U0_TM1_4 13 +#define I_8266_U1_TM1_4 14 +#define I_8266_DM_TM1_4 15 +#define I_8266_BB_TM1_4 16 + +/*** ESP32 Neopixel methods ***/ +//RGB +#define I_32_R0_NEO_3 17 +#define I_32_R1_NEO_3 18 +#define I_32_R2_NEO_3 19 +#define I_32_R3_NEO_3 20 +#define I_32_R4_NEO_3 21 +#define I_32_R5_NEO_3 22 +#define I_32_R6_NEO_3 23 +#define I_32_R7_NEO_3 24 +#define I_32_I0_NEO_3 25 +#define I_32_I1_NEO_3 26 +//RGBW +#define I_32_R0_NEO_4 27 +#define I_32_R1_NEO_4 28 +#define I_32_R2_NEO_4 29 +#define I_32_R3_NEO_4 30 +#define I_32_R4_NEO_4 31 +#define I_32_R5_NEO_4 32 +#define I_32_R6_NEO_4 33 +#define I_32_R7_NEO_4 34 +#define I_32_I0_NEO_4 35 +#define I_32_I1_NEO_4 36 +//400Kbps +#define I_32_R0_400_3 37 +#define I_32_R1_400_3 38 +#define I_32_R2_400_3 39 +#define I_32_R3_400_3 40 +#define I_32_R4_400_3 41 +#define I_32_R5_400_3 42 +#define I_32_R6_400_3 43 +#define I_32_R7_400_3 44 +#define I_32_I0_400_3 45 +#define I_32_I1_400_3 46 +//TM1418 (RGBW) +#define I_32_R0_TM1_4 47 +#define I_32_R1_TM1_4 48 +#define I_32_R2_TM1_4 49 +#define I_32_R3_TM1_4 50 +#define I_32_R4_TM1_4 51 +#define I_32_R5_TM1_4 52 +#define I_32_R6_TM1_4 53 +#define I_32_R7_TM1_4 54 +#define I_32_I0_TM1_4 55 +#define I_32_I1_TM1_4 56 +//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) + +//APA102 +#define I_HS_DOT_3 57 //hardware SPI +#define I_SS_DOT_3 58 //soft SPI + +//LPD8806 +#define I_HS_LPD_3 59 +#define I_SS_LPD_3 60 + +//WS2801 +#define I_HS_WS1_3 61 +#define I_SS_WS1_3 62 + +//P9813 +#define I_HS_P98_3 63 +#define I_SS_P98_3 64 + + +/*** ESP8266 Neopixel methods ***/ +#ifdef ESP8266 +//RGB +#define B_8266_U0_NEO_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio1 +#define B_8266_U1_NEO_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio2 +#define B_8266_DM_NEO_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio3 +#define B_8266_BB_NEO_3 NeoPixelBrightnessBus //3 chan, esp8266, bb (any pin but 16) +//RGBW +#define B_8266_U0_NEO_4 NeoPixelBrightnessBus //4 chan, esp8266, gpio1 +#define B_8266_U1_NEO_4 NeoPixelBrightnessBus //4 chan, esp8266, gpio2 +#define B_8266_DM_NEO_4 NeoPixelBrightnessBus //4 chan, esp8266, gpio3 +#define B_8266_BB_NEO_4 NeoPixelBrightnessBus //4 chan, esp8266, bb (any pin) +//400Kbps +#define B_8266_U0_400_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio1 +#define B_8266_U1_400_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio2 +#define B_8266_DM_400_3 NeoPixelBrightnessBus //3 chan, esp8266, gpio3 +#define B_8266_BB_400_3 NeoPixelBrightnessBus //3 chan, esp8266, bb (any pin) +//TM1418 (RGBW) +#define B_8266_U0_TM1_4 NeoPixelBrightnessBus +#define B_8266_U1_TM1_4 NeoPixelBrightnessBus +#define B_8266_DM_TM1_4 NeoPixelBrightnessBus +#define B_8266_BB_TM1_4 NeoPixelBrightnessBus +#endif + +/*** ESP32 Neopixel methods ***/ +#ifdef ARDUINO_ARCH_ESP32 +//RGB +#define B_32_R0_NEO_3 NeoPixelBrightnessBus +#define B_32_R1_NEO_3 NeoPixelBrightnessBus +#define B_32_R2_NEO_3 NeoPixelBrightnessBus +#define B_32_R3_NEO_3 NeoPixelBrightnessBus +#define B_32_R4_NEO_3 NeoPixelBrightnessBus +#define B_32_R5_NEO_3 NeoPixelBrightnessBus +#define B_32_R6_NEO_3 NeoPixelBrightnessBus +#define B_32_R7_NEO_3 NeoPixelBrightnessBus +#define B_32_I0_NEO_3 NeoPixelBrightnessBus +#define B_32_I1_NEO_3 NeoPixelBrightnessBus +//RGBW +#define B_32_R0_NEO_4 NeoPixelBrightnessBus +#define B_32_R1_NEO_4 NeoPixelBrightnessBus +#define B_32_R2_NEO_4 NeoPixelBrightnessBus +#define B_32_R3_NEO_4 NeoPixelBrightnessBus +#define B_32_R4_NEO_4 NeoPixelBrightnessBus +#define B_32_R5_NEO_4 NeoPixelBrightnessBus +#define B_32_R6_NEO_4 NeoPixelBrightnessBus +#define B_32_R7_NEO_4 NeoPixelBrightnessBus +#define B_32_I0_NEO_4 NeoPixelBrightnessBus +#define B_32_I1_NEO_4 NeoPixelBrightnessBus +//400Kbps +#define B_32_R0_400_3 NeoPixelBrightnessBus +#define B_32_R1_400_3 NeoPixelBrightnessBus +#define B_32_R2_400_3 NeoPixelBrightnessBus +#define B_32_R3_400_3 NeoPixelBrightnessBus +#define B_32_R4_400_3 NeoPixelBrightnessBus +#define B_32_R5_400_3 NeoPixelBrightnessBus +#define B_32_R6_400_3 NeoPixelBrightnessBus +#define B_32_R7_400_3 NeoPixelBrightnessBus +#define B_32_I0_400_3 NeoPixelBrightnessBus +#define B_32_I1_400_3 NeoPixelBrightnessBus +//TM1418 (RGBW) +#define B_32_R0_TM1_4 NeoPixelBrightnessBus +#define B_32_R1_TM1_4 NeoPixelBrightnessBus +#define B_32_R2_TM1_4 NeoPixelBrightnessBus +#define B_32_R3_TM1_4 NeoPixelBrightnessBus +#define B_32_R4_TM1_4 NeoPixelBrightnessBus +#define B_32_R5_TM1_4 NeoPixelBrightnessBus +#define B_32_R6_TM1_4 NeoPixelBrightnessBus +#define B_32_R7_TM1_4 NeoPixelBrightnessBus +#define B_32_I0_TM1_4 NeoPixelBrightnessBus +#define B_32_I1_TM1_4 NeoPixelBrightnessBus +//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) + +#endif + +//APA102 +#define B_HS_DOT_3 NeoPixelBrightnessBus //hardware SPI +#define B_SS_DOT_3 NeoPixelBrightnessBus //soft SPI + +//LPD8806 +#define B_HS_LPD_3 NeoPixelBrightnessBus +#define B_SS_LPD_3 NeoPixelBrightnessBus + +//WS2801 +#define B_HS_WS1_3 NeoPixelBrightnessBus +#define B_SS_WS1_3 NeoPixelBrightnessBus + +//P9813 +#define B_HS_P98_3 NeoPixelBrightnessBus +#define B_SS_P98_3 NeoPixelBrightnessBus + +//handles pointer type conversion for all possible bus types +class PolyBus { + public: + static void begin(void* busPtr, uint8_t busType, uint8_t* pins) { + switch (busType) { + case I_NONE: break; + #ifdef ESP8266 + case I_8266_U0_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_400_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_400_3: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_400_3: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_400_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U0_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_HS_DOT_3: (static_cast(busPtr))->Begin(); break; + case I_HS_LPD_3: (static_cast(busPtr))->Begin(); break; + case I_HS_WS1_3: (static_cast(busPtr))->Begin(); break; + case I_HS_P98_3: (static_cast(busPtr))->Begin(); break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + case I_32_R0_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_R1_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_R2_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_R3_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_R4_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_R5_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_R6_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_R7_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_I0_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_I1_NEO_3: (static_cast(busPtr))->Begin(); break; + case I_32_R0_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_R1_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_R2_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_R3_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_R4_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_R5_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_R6_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_R7_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_I1_NEO_4: (static_cast(busPtr))->Begin(); break; + case I_32_R0_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_R1_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_R2_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_R3_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_R4_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_R5_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_R6_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_R7_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_I1_400_3: (static_cast(busPtr))->Begin(); break; + case I_32_R0_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_R1_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_R2_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_R3_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_R4_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_R5_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_R6_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_R7_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_I0_TM1_4: (static_cast(busPtr))->Begin(); break; + case I_32_I1_TM1_4: (static_cast(busPtr))->Begin(); break; + // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() + case I_HS_DOT_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; + case I_HS_LPD_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; + case I_HS_WS1_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; + case I_HS_P98_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; + #endif + case I_SS_DOT_3: (static_cast(busPtr))->Begin(); break; + case I_SS_LPD_3: (static_cast(busPtr))->Begin(); break; + 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) { + void* busPtr = nullptr; + switch (busType) { + case I_NONE: break; + #ifdef ESP8266 + case I_8266_U0_NEO_3: busPtr = new B_8266_U0_NEO_3(len, pins[0]); break; + case I_8266_U1_NEO_3: busPtr = new B_8266_U1_NEO_3(len, pins[0]); break; + case I_8266_DM_NEO_3: busPtr = new B_8266_DM_NEO_3(len, pins[0]); break; + case I_8266_BB_NEO_3: busPtr = new B_8266_BB_NEO_3(len, pins[0]); break; + case I_8266_U0_NEO_4: busPtr = new B_8266_U0_NEO_4(len, pins[0]); break; + case I_8266_U1_NEO_4: busPtr = new B_8266_U1_NEO_4(len, pins[0]); break; + case I_8266_DM_NEO_4: busPtr = new B_8266_DM_NEO_4(len, pins[0]); break; + case I_8266_BB_NEO_4: busPtr = new B_8266_BB_NEO_4(len, pins[0]); break; + case I_8266_U0_400_3: busPtr = new B_8266_U0_400_3(len, pins[0]); break; + case I_8266_U1_400_3: busPtr = new B_8266_U1_400_3(len, pins[0]); break; + case I_8266_DM_400_3: busPtr = new B_8266_DM_400_3(len, pins[0]); break; + case I_8266_BB_400_3: busPtr = new B_8266_BB_400_3(len, pins[0]); break; + case I_8266_U0_TM1_4: busPtr = new B_8266_U0_TM1_4(len, pins[0]); break; + case I_8266_U1_TM1_4: busPtr = new B_8266_U1_TM1_4(len, pins[0]); break; + case I_8266_DM_TM1_4: busPtr = new B_8266_DM_TM1_4(len, pins[0]); break; + case I_8266_BB_TM1_4: busPtr = new B_8266_BB_TM1_4(len, pins[0]); break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + case I_32_R0_NEO_3: busPtr = new B_32_R0_NEO_3(len, pins[0]); break; + case I_32_R1_NEO_3: busPtr = new B_32_R1_NEO_3(len, pins[0]); break; + case I_32_R2_NEO_3: busPtr = new B_32_R2_NEO_3(len, pins[0]); break; + case I_32_R3_NEO_3: busPtr = new B_32_R3_NEO_3(len, pins[0]); break; + case I_32_R4_NEO_3: busPtr = new B_32_R4_NEO_3(len, pins[0]); break; + case I_32_R5_NEO_3: busPtr = new B_32_R5_NEO_3(len, pins[0]); break; + case I_32_R6_NEO_3: busPtr = new B_32_R6_NEO_3(len, pins[0]); break; + case I_32_R7_NEO_3: busPtr = new B_32_R7_NEO_3(len, pins[0]); break; + case I_32_I0_NEO_3: busPtr = new B_32_I0_NEO_3(len, pins[0]); break; + case I_32_I1_NEO_3: busPtr = new B_32_I1_NEO_3(len, pins[0]); break; + case I_32_R0_NEO_4: busPtr = new B_32_R0_NEO_4(len, pins[0]); break; + case I_32_R1_NEO_4: busPtr = new B_32_R1_NEO_4(len, pins[0]); break; + case I_32_R2_NEO_4: busPtr = new B_32_R2_NEO_4(len, pins[0]); break; + case I_32_R3_NEO_4: busPtr = new B_32_R3_NEO_4(len, pins[0]); break; + case I_32_R4_NEO_4: busPtr = new B_32_R4_NEO_4(len, pins[0]); break; + case I_32_R5_NEO_4: busPtr = new B_32_R5_NEO_4(len, pins[0]); break; + case I_32_R6_NEO_4: busPtr = new B_32_R6_NEO_4(len, pins[0]); break; + case I_32_R7_NEO_4: busPtr = new B_32_R7_NEO_4(len, pins[0]); break; + case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break; + case I_32_I1_NEO_4: busPtr = new B_32_I1_NEO_4(len, pins[0]); break; + case I_32_R0_400_3: busPtr = new B_32_R0_400_3(len, pins[0]); break; + case I_32_R1_400_3: busPtr = new B_32_R1_400_3(len, pins[0]); break; + case I_32_R2_400_3: busPtr = new B_32_R2_400_3(len, pins[0]); break; + case I_32_R3_400_3: busPtr = new B_32_R3_400_3(len, pins[0]); break; + case I_32_R4_400_3: busPtr = new B_32_R4_400_3(len, pins[0]); break; + case I_32_R5_400_3: busPtr = new B_32_R5_400_3(len, pins[0]); break; + case I_32_R6_400_3: busPtr = new B_32_R6_400_3(len, pins[0]); break; + case I_32_R7_400_3: busPtr = new B_32_R7_400_3(len, pins[0]); break; + case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break; + case I_32_I1_400_3: busPtr = new B_32_I1_400_3(len, pins[0]); break; + case I_32_R0_TM1_4: busPtr = new B_32_R0_TM1_4(len, pins[0]); break; + case I_32_R1_TM1_4: busPtr = new B_32_R1_TM1_4(len, pins[0]); break; + case I_32_R2_TM1_4: busPtr = new B_32_R2_TM1_4(len, pins[0]); break; + case I_32_R3_TM1_4: busPtr = new B_32_R3_TM1_4(len, pins[0]); break; + case I_32_R4_TM1_4: busPtr = new B_32_R4_TM1_4(len, pins[0]); break; + case I_32_R5_TM1_4: busPtr = new B_32_R5_TM1_4(len, pins[0]); break; + case I_32_R6_TM1_4: busPtr = new B_32_R6_TM1_4(len, pins[0]); break; + case I_32_R7_TM1_4: busPtr = new B_32_R7_TM1_4(len, pins[0]); break; + case I_32_I0_TM1_4: busPtr = new B_32_I0_TM1_4(len, pins[0]); break; + case I_32_I1_TM1_4: busPtr = new B_32_I1_TM1_4(len, pins[0]); break; + #endif + // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) + case I_HS_DOT_3: busPtr = new B_HS_DOT_3(len, pins[1], pins[0]); break; + case I_SS_DOT_3: busPtr = new B_SS_DOT_3(len, pins[1], pins[0]); break; + case I_HS_LPD_3: busPtr = new B_HS_LPD_3(len, pins[1], pins[0]); break; + case I_SS_LPD_3: busPtr = new B_SS_LPD_3(len, pins[1], pins[0]); break; + case I_HS_WS1_3: busPtr = new B_HS_WS1_3(len, pins[1], pins[0]); break; + case I_SS_WS1_3: busPtr = new B_SS_WS1_3(len, pins[1], pins[0]); break; + case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break; + case I_SS_P98_3: busPtr = new B_SS_P98_3(len, pins[1], pins[0]); break; + } + begin(busPtr, busType, pins); + return busPtr; + }; + static void show(void* busPtr, uint8_t busType) { + 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; + #endif + #ifdef ARDUINO_ARCH_ESP32 + case I_32_R0_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_R1_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_R2_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_R3_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_R4_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_R5_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_R6_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_R7_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_I0_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_I1_NEO_3: (static_cast(busPtr))->Show(); break; + case I_32_R0_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_R1_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_R2_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_R3_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_R4_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_R5_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_R6_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_R7_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_I1_NEO_4: (static_cast(busPtr))->Show(); break; + case I_32_R0_400_3: (static_cast(busPtr))->Show(); break; + case I_32_R1_400_3: (static_cast(busPtr))->Show(); break; + case I_32_R2_400_3: (static_cast(busPtr))->Show(); break; + case I_32_R3_400_3: (static_cast(busPtr))->Show(); break; + case I_32_R4_400_3: (static_cast(busPtr))->Show(); break; + case I_32_R5_400_3: (static_cast(busPtr))->Show(); break; + case I_32_R6_400_3: (static_cast(busPtr))->Show(); break; + case I_32_R7_400_3: (static_cast(busPtr))->Show(); break; + case I_32_I0_400_3: (static_cast(busPtr))->Show(); break; + case I_32_I1_400_3: (static_cast(busPtr))->Show(); break; + case I_32_R0_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_R1_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_R2_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_R3_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_R4_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_R5_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_R6_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_R7_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_I0_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_I1_TM1_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_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; + } + }; + static bool canShow(void* busPtr, uint8_t busType) { + switch (busType) { + case I_NONE: return true; + #ifdef ESP8266 + case I_8266_U0_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_TM1_4: return (static_cast(busPtr))->CanShow(); break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + case I_32_R0_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R1_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R2_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R3_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R4_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R5_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R6_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R7_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R0_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R1_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R2_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R3_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R4_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R5_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R6_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R7_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R0_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R1_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R2_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R3_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R4_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R5_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R6_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R7_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_R0_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R1_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R2_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R3_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R4_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R5_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R6_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_R7_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_TM1_4: return (static_cast(busPtr))->CanShow(); break; + #endif + case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; + case I_SS_DOT_3: return (static_cast(busPtr))->CanShow(); break; + case I_HS_LPD_3: return (static_cast(busPtr))->CanShow(); break; + case I_SS_LPD_3: return (static_cast(busPtr))->CanShow(); break; + case I_HS_WS1_3: return (static_cast(busPtr))->CanShow(); break; + case I_SS_WS1_3: return (static_cast(busPtr))->CanShow(); break; + case I_HS_P98_3: return (static_cast(busPtr))->CanShow(); break; + 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) { + uint8_t r = c >> 16; + uint8_t g = c >> 8; + uint8_t b = c >> 0; + uint8_t w = c >> 24; + RgbwColor col; + + //TODO make color order override possible on a per-strip basis + #ifdef COLOR_ORDER_OVERRIDE + if (pix >= COO_MIN && pix < COO_MAX) co = COO_ORDER; + #endif + + //reorder channels to selected order + switch (co) + { + case 0: col.G = g; col.R = r; col.B = b; break; //0 = GRB, default + case 1: col.G = r; col.R = g; col.B = b; break; //1 = RGB, common for WS2811 + case 2: col.G = b; col.R = r; col.B = g; break; //2 = BRG + case 3: col.G = r; col.R = b; col.B = g; break; //3 = RBG + case 4: col.G = b; col.R = g; col.B = r; break; //4 = BGR + default: col.G = g; col.R = b; col.B = r; break; //5 = GBR + } + col.W = w; + + switch (busType) { + case I_NONE: break; + #ifdef ESP8266 + case I_8266_U0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_U1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_DM_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_BB_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_U0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_8266_U1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_8266_DM_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_8266_BB_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_8266_U0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_U1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_DM_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_BB_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_U0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_8266_U1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_8266_DM_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_8266_BB_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + case I_32_R0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R2_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R3_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R4_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R5_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R6_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R7_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R2_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R3_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R4_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R5_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R6_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R7_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R2_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R3_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R4_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R5_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R6_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R7_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_I1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_32_R0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R2_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R3_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R4_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R5_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R6_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_R7_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + #endif + case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_SS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_HS_LPD_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_SS_LPD_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_HS_WS1_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_SS_WS1_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_HS_P98_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_SS_P98_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + } + }; + 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))->SetBrightness(b); break; + case I_8266_U1_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_DM_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_BB_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_U0_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_U1_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_DM_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_BB_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_U0_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_U1_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_DM_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_BB_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_U0_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_U1_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_DM_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_BB_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + case I_32_R0_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R1_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R2_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R3_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R4_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R5_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R6_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R7_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I0_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I1_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R0_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R1_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R2_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R3_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R4_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R5_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R6_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R7_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I0_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I1_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R0_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R1_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R2_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R3_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R4_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R5_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R6_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R7_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I0_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I1_400_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R0_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R1_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R2_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R3_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R4_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R5_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R6_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_R7_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I0_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I1_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + #endif + case I_HS_DOT_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_SS_DOT_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_HS_LPD_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_SS_LPD_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_HS_WS1_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_SS_WS1_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_HS_P98_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_SS_P98_3: (static_cast(busPtr))->SetBrightness(b); break; + } + }; + static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { + RgbwColor col(0,0,0,0); + switch (busType) { + case I_NONE: break; + #ifdef ESP8266 + case I_8266_U0_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U1_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_DM_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_BB_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U1_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_DM_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_BB_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U1_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_DM_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_BB_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U0_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U1_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_DM_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_BB_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + case I_32_R0_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R1_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R2_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R3_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R4_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R5_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R6_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R7_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I0_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R1_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R2_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R3_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R4_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R5_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R6_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R7_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R1_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R2_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R3_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R4_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R5_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R6_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R7_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R0_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R1_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R2_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R3_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R4_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R5_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R6_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_R7_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I0_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + #endif + case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_SS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_HS_LPD_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_SS_LPD_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_HS_WS1_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_SS_WS1_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_HS_P98_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_SS_P98_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + } + + #ifdef COLOR_ORDER_OVERRIDE + if (pix >= COO_MIN && pix < COO_MAX) co = COO_ORDER; + #endif + + switch (co) + { + // W G R B + case 0: return ((col.W << 24) | (col.G << 8) | (col.R << 16) | (col.B)); //0 = GRB, default + case 1: return ((col.W << 24) | (col.R << 8) | (col.G << 16) | (col.B)); //1 = RGB, common for WS2811 + case 2: return ((col.W << 24) | (col.B << 8) | (col.R << 16) | (col.G)); //2 = BRG + case 3: return ((col.W << 24) | (col.B << 8) | (col.G << 16) | (col.R)); //3 = RBG + case 4: return ((col.W << 24) | (col.R << 8) | (col.B << 16) | (col.G)); //4 = BGR + case 5: return ((col.W << 24) | (col.G << 8) | (col.B << 16) | (col.R)); //5 = GBR + } + return 0; + } + + static void cleanup(void* busPtr, uint8_t busType) { + if (busPtr == nullptr) return; + switch (busType) { + case I_NONE: break; + #ifdef ESP8266 + case I_8266_U0_NEO_3: delete (static_cast(busPtr)); break; + case I_8266_U1_NEO_3: delete (static_cast(busPtr)); break; + case I_8266_DM_NEO_3: delete (static_cast(busPtr)); break; + case I_8266_BB_NEO_3: delete (static_cast(busPtr)); break; + case I_8266_U0_NEO_4: delete (static_cast(busPtr)); break; + case I_8266_U1_NEO_4: delete (static_cast(busPtr)); break; + case I_8266_DM_NEO_4: delete (static_cast(busPtr)); break; + case I_8266_BB_NEO_4: delete (static_cast(busPtr)); break; + case I_8266_U0_400_3: delete (static_cast(busPtr)); break; + case I_8266_U1_400_3: delete (static_cast(busPtr)); break; + case I_8266_DM_400_3: delete (static_cast(busPtr)); break; + case I_8266_BB_400_3: delete (static_cast(busPtr)); break; + case I_8266_U0_TM1_4: delete (static_cast(busPtr)); break; + case I_8266_U1_TM1_4: delete (static_cast(busPtr)); break; + case I_8266_DM_TM1_4: delete (static_cast(busPtr)); break; + case I_8266_BB_TM1_4: delete (static_cast(busPtr)); break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + case I_32_R0_NEO_3: delete (static_cast(busPtr)); break; + case I_32_R1_NEO_3: delete (static_cast(busPtr)); break; + case I_32_R2_NEO_3: delete (static_cast(busPtr)); break; + case I_32_R3_NEO_3: delete (static_cast(busPtr)); break; + case I_32_R4_NEO_3: delete (static_cast(busPtr)); break; + case I_32_R5_NEO_3: delete (static_cast(busPtr)); break; + case I_32_R6_NEO_3: delete (static_cast(busPtr)); break; + case I_32_R7_NEO_3: delete (static_cast(busPtr)); break; + case I_32_I0_NEO_3: delete (static_cast(busPtr)); break; + case I_32_I1_NEO_3: delete (static_cast(busPtr)); break; + case I_32_R0_NEO_4: delete (static_cast(busPtr)); break; + case I_32_R1_NEO_4: delete (static_cast(busPtr)); break; + case I_32_R2_NEO_4: delete (static_cast(busPtr)); break; + case I_32_R3_NEO_4: delete (static_cast(busPtr)); break; + case I_32_R4_NEO_4: delete (static_cast(busPtr)); break; + case I_32_R5_NEO_4: delete (static_cast(busPtr)); break; + case I_32_R6_NEO_4: delete (static_cast(busPtr)); break; + case I_32_R7_NEO_4: delete (static_cast(busPtr)); break; + case I_32_I0_NEO_4: delete (static_cast(busPtr)); break; + case I_32_I1_NEO_4: delete (static_cast(busPtr)); break; + case I_32_R0_400_3: delete (static_cast(busPtr)); break; + case I_32_R1_400_3: delete (static_cast(busPtr)); break; + case I_32_R2_400_3: delete (static_cast(busPtr)); break; + case I_32_R3_400_3: delete (static_cast(busPtr)); break; + case I_32_R4_400_3: delete (static_cast(busPtr)); break; + case I_32_R5_400_3: delete (static_cast(busPtr)); break; + case I_32_R6_400_3: delete (static_cast(busPtr)); break; + case I_32_R7_400_3: delete (static_cast(busPtr)); break; + case I_32_I0_400_3: delete (static_cast(busPtr)); break; + case I_32_I1_400_3: delete (static_cast(busPtr)); break; + case I_32_R0_TM1_4: delete (static_cast(busPtr)); break; + case I_32_R1_TM1_4: delete (static_cast(busPtr)); break; + case I_32_R2_TM1_4: delete (static_cast(busPtr)); break; + case I_32_R3_TM1_4: delete (static_cast(busPtr)); break; + case I_32_R4_TM1_4: delete (static_cast(busPtr)); break; + case I_32_R5_TM1_4: delete (static_cast(busPtr)); break; + case I_32_R6_TM1_4: delete (static_cast(busPtr)); break; + case I_32_R7_TM1_4: delete (static_cast(busPtr)); break; + case I_32_I0_TM1_4: delete (static_cast(busPtr)); break; + case I_32_I1_TM1_4: delete (static_cast(busPtr)); break; + #endif + case I_HS_DOT_3: delete (static_cast(busPtr)); break; + case I_SS_DOT_3: delete (static_cast(busPtr)); break; + case I_HS_LPD_3: delete (static_cast(busPtr)); break; + case I_SS_LPD_3: delete (static_cast(busPtr)); break; + case I_HS_WS1_3: delete (static_cast(busPtr)); break; + case I_SS_WS1_3: delete (static_cast(busPtr)); break; + case I_HS_P98_3: delete (static_cast(busPtr)); break; + case I_SS_P98_3: delete (static_cast(busPtr)); break; + } + } + + //gives back the internal type index (I_XX_XXX_X above) for the input + static uint8_t getI(uint8_t busType, uint8_t* pins, uint8_t num = 0) { + if (!IS_DIGITAL(busType)) return I_NONE; + if (IS_2PIN(busType)) { //SPI LED chips + bool isHSPI = false; + #ifdef ESP8266 + if (pins[0] == P_8266_HS_MOSI && pins[1] == P_8266_HS_CLK) isHSPI = true; + #else + if(!num) isHSPI = true; // temporary hack to limit use of hardware SPI to a single SPI peripheral: only allow ESP32 hardware serial on segment 0 + #endif + uint8_t t = I_NONE; + switch (busType) { + case TYPE_APA102: t = I_SS_DOT_3; break; + case TYPE_LPD8806: t = I_SS_LPD_3; break; + case TYPE_WS2801: t = I_SS_WS1_3; break; + case TYPE_P9813: t = I_SS_P98_3; break; + default: t=I_NONE; + } + if (t > I_NONE && isHSPI) t--; //hardware SPI has one smaller ID than software + return t; + } else { + #ifdef ESP8266 + uint8_t offset = pins[0] -1; //for driver: 0 = uart0, 1 = uart1, 2 = dma, 3 = bitbang + if (offset > 3) offset = 3; + switch (busType) { + case TYPE_WS2812_RGB: + case TYPE_WS2812_WWA: + return I_8266_U0_NEO_3 + offset; + case TYPE_SK6812_RGBW: + return I_8266_U0_NEO_4 + offset; + case TYPE_WS2811_400KHZ: + return I_8266_U0_400_3 + offset; + } + #else //ESP32 + uint8_t offset = num; //RMT bus # == bus index in BusManager + if (offset > 9) return I_NONE; + switch (busType) { + case TYPE_WS2812_RGB: + case TYPE_WS2812_WWA: + return I_32_R0_NEO_3 + offset; + case TYPE_SK6812_RGBW: + return I_32_R0_NEO_4 + offset; + case TYPE_WS2811_400KHZ: + return I_32_R0_400_3 + offset; + } + #endif + } + return I_NONE; + } +}; + +#endif \ No newline at end of file diff --git a/wled00/wled09_button.ino b/wled00/button.cpp similarity index 51% rename from wled00/wled09_button.ino rename to wled00/button.cpp index 86227dc3f..22e71a69b 100644 --- a/wled00/wled09_button.ino +++ b/wled00/button.cpp @@ -1,3 +1,5 @@ +#include "wled.h" + /* * Physical IO */ @@ -7,19 +9,27 @@ void shortPressAction() if (!macroButton) { toggleOnOff(); - colorUpdated(2); + colorUpdated(NOTIFIER_CALL_MODE_BUTTON); } else { - applyMacro(macroButton); + applyPreset(macroButton); } } +bool isButtonPressed() +{ + if (btnPin>=0 && digitalRead(btnPin) == LOW) return true; + #ifdef TOUCHPIN + if (touchRead(TOUCHPIN) <= TOUCH_THRESHOLD) return true; + #endif + return false; +} + void handleButton() { -#ifdef BTNPIN - if (!buttonEnabled) return; - - if (digitalRead(BTNPIN) == LOW) //pressed + if (btnPin<0 || !buttonEnabled) return; + + if (isButtonPressed()) //pressed { if (!buttonPressedBefore) buttonPressedTime = millis(); buttonPressedBefore = true; @@ -28,14 +38,14 @@ void handleButton() { if (!buttonLongPressed) { - if (macroLongPress) {applyMacro(macroLongPress);} + if (macroLongPress) {applyPreset(macroLongPress);} else _setRandomColor(false,true); buttonLongPressed = true; } } } - else if (digitalRead(BTNPIN) == HIGH && buttonPressedBefore) //released + else if (!isButtonPressed() && buttonPressedBefore) //released { long dur = millis() - buttonPressedTime; if (dur < 50) {buttonPressedBefore = false; return;} //too short "press", debounce @@ -44,12 +54,12 @@ void handleButton() if (dur > 6000) //long press { - initAP(true); + WLED::instance().initAP(true); } else if (!buttonLongPressed) { //short press if (macroDoublePress) { - if (doublePress) applyMacro(macroDoublePress); + if (doublePress) applyPreset(macroDoublePress); else buttonWaitTime = millis(); } else shortPressAction(); } @@ -62,7 +72,6 @@ void handleButton() buttonWaitTime = 0; shortPressAction(); } -#endif } void handleIO() @@ -74,46 +83,26 @@ void handleIO() { lastOnTime = millis(); if (offMode) - { - #if RLYPIN >= 0 - digitalWrite(RLYPIN, RLYMDE); - #endif + { + if (rlyPin>=0) { + pinMode(rlyPin, OUTPUT); + digitalWrite(rlyPin, rlyMde); + } offMode = false; } } else if (millis() - lastOnTime > 600) { - #if RLYPIN >= 0 - if (!offMode) digitalWrite(RLYPIN, !RLYMDE); - #endif + if (!offMode) { + #ifdef ESP8266 + //turn off built-in LED if strip is turned off + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + #endif + if (rlyPin>=0) { + pinMode(rlyPin, OUTPUT); + digitalWrite(rlyPin, !rlyMde); + } + } offMode = true; } - - #if AUXPIN >= 0 - //output - if (auxActive || auxActiveBefore) - { - if (!auxActiveBefore) - { - auxActiveBefore = true; - switch (auxTriggeredState) - { - case 0: pinMode(AUXPIN, INPUT); break; - case 1: pinMode(AUXPIN, OUTPUT); digitalWrite(AUXPIN, HIGH); break; - case 2: pinMode(AUXPIN, OUTPUT); digitalWrite(AUXPIN, LOW); break; - } - auxStartTime = millis(); - } - if ((millis() - auxStartTime > auxTime*1000 && auxTime != 255) || !auxActive) - { - auxActive = false; - auxActiveBefore = false; - switch (auxDefaultState) - { - case 0: pinMode(AUXPIN, INPUT); break; - case 1: pinMode(AUXPIN, OUTPUT); digitalWrite(AUXPIN, HIGH); break; - case 2: pinMode(AUXPIN, OUTPUT); digitalWrite(AUXPIN, LOW); break; - } - } - } - #endif } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp new file mode 100644 index 000000000..12611409e --- /dev/null +++ b/wled00/cfg.cpp @@ -0,0 +1,746 @@ +#include "wled.h" + +/* + * Serializes and parses the cfg.json and wsec.json settings files, stored in internal FS. + * The structure of the JSON is not to be considered an official API and may change without notice. + */ + +//simple macro for ArduinoJSON's or syntax +#define CJSON(a,b) a = b | a + +void getStringFromJson(char* dest, const char* src, size_t len) { + if (src != nullptr) strlcpy(dest, src, len); +} + +void deserializeConfig() { + bool fromeep = false; + bool success = deserializeConfigSec(); + if (!success) { //if file does not exist, try reading from EEPROM + deEEPSettings(); + fromeep = true; + } + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); + + success = readObjectFromFile("/cfg.json", nullptr, &doc); + if (!success) { //if file does not exist, try reading from EEPROM + if (!fromeep) deEEPSettings(); + return; + } + + //int rev_major = doc["rev"][0]; // 1 + //int rev_minor = doc["rev"][1]; // 0 + + //long vid = doc[F("vid")]; // 2010020 + + JsonObject id = doc["id"]; + getStringFromJson(cmDNS, id[F("mdns")], 33); + getStringFromJson(serverDescription, id[F("name")], 33); + getStringFromJson(alexaInvocationName, id[F("inv")], 33); + + JsonObject nw_ins_0 = doc["nw"][F("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); + + 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]); + } + + JsonObject ap = doc["ap"]; + getStringFromJson(apSSID, ap[F("ssid")], 33); + getStringFromJson(apPass, ap["psk"] , 65); //normally not present due to security + //int ap_pskl = ap[F("pskl")]; + + CJSON(apChannel, ap[F("chan")]); + if (apChannel > 13 || apChannel < 1) apChannel = 1; + + CJSON(apHide, ap[F("hide")]); + if (apHide > 1) apHide = 1; + + CJSON(apBehavior, ap[F("behav")]); + + #ifdef WLED_USE_ETHERNET + JsonObject ethernet = doc[F("eth")]; + CJSON(ethernetType, ethernet["type"]); + #endif + + /* + JsonArray ap_ip = ap["ip"]; + for (byte i = 0; i < 4; i++) { + apIP[i] = ap_ip; + }*/ + + noWifiSleep = doc[F("wifi")][F("sleep")] | !noWifiSleep; // inverted + noWifiSleep = !noWifiSleep; + //int wifi_phy = doc[F("wifi")][F("phy")]; //force phy mode n? + + JsonObject hw = doc[F("hw")]; + + // initialize LED pins and lengths prior to other HW + JsonObject hw_led = hw[F("led")]; + + CJSON(ledCount, hw_led[F("total")]); + if (ledCount > MAX_LEDS) ledCount = MAX_LEDS; + + CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); + CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); + CJSON(strip.rgbwMode, hw_led[F("rgbwm")]); + + JsonArray ins = hw_led["ins"]; + uint8_t s = 0; //bus iterator + strip.isRgbw = false; + busses.removeAll(); + uint32_t mem = 0; + for (JsonObject elm : ins) { + if (s >= WLED_MAX_BUSSES) break; + uint8_t pins[5] = {255, 255, 255, 255, 255}; + JsonArray pinArr = elm[F("pin")]; + if (pinArr.size() == 0) continue; + pins[0] = pinArr[0]; + uint8_t i = 0; + for (int p : pinArr) { + pins[i] = p; + i++; + if (i>4) break; + } + + uint16_t length = elm[F("len")]; + if (length==0) continue; + uint8_t colorOrder = (int)elm[F("order")]; + //only use skip from the first strip (this shouldn't have been in ins obj. but remains here for compatibility) + if (s==0) skipFirstLed = elm[F("skip")]; + uint16_t start = elm[F("start")] | 0; + if (start >= ledCount) continue; + //limit length of strip if it would exceed total configured LEDs + if (start + length > ledCount) length = ledCount - start; + uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; + bool reversed = elm["rev"]; + //RGBW mode is enabled if at least one of the strips is RGBW + strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(ledType)); + s++; + BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed); + mem += busses.memUsage(bc); + if (mem <= MAX_LED_MEMORY) busses.add(bc); + } + strip.finalizeInit(ledCount, skipFirstLed); + if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus + + JsonObject hw_btn_ins_0 = hw[F("btn")][F("ins")][0]; + CJSON(buttonEnabled, hw_btn_ins_0["type"]); + int hw_btn_pin = hw_btn_ins_0[F("pin")][0]; + if (pinManager.allocatePin(hw_btn_pin,false)) { + btnPin = hw_btn_pin; + pinMode(btnPin, INPUT_PULLUP); + } else { + btnPin = -1; + } + + JsonArray hw_btn_ins_0_macros = hw_btn_ins_0[F("macros")]; + CJSON(macroButton, hw_btn_ins_0_macros[0]); + CJSON(macroLongPress,hw_btn_ins_0_macros[1]); + CJSON(macroDoublePress, hw_btn_ins_0_macros[2]); + + //int hw_btn_ins_0_type = hw_btn_ins_0["type"]; // 0 + + #ifndef WLED_DISABLE_INFRARED + int hw_ir_pin = hw["ir"]["pin"] | -1; // 4 + if (pinManager.allocatePin(hw_ir_pin,false)) { + irPin = hw_ir_pin; + } else { + irPin = -1; + } + #endif + CJSON(irEnabled, hw["ir"]["type"]); + + JsonObject relay = hw[F("relay")]; + + int hw_relay_pin = relay["pin"]; + if (pinManager.allocatePin(hw_relay_pin,true)) { + rlyPin = hw_relay_pin; + pinMode(rlyPin, OUTPUT); + } else { + rlyPin = -1; + } + if (relay.containsKey("rev")) { + rlyMde = !relay["rev"]; + } + + //int hw_status_pin = hw[F("status")][F("pin")]; // -1 + + JsonObject light = doc[F("light")]; + CJSON(briMultiplier, light[F("scale-bri")]); + CJSON(strip.paletteBlend, light[F("pal-mode")]); + + float light_gc_bri = light[F("gc")]["bri"]; + float light_gc_col = light[F("gc")]["col"]; // 2.8 + if (light_gc_bri > 1.5) strip.gammaCorrectBri = true; + else if (light_gc_bri > 0.5) strip.gammaCorrectBri = false; + if (light_gc_col > 1.5) strip.gammaCorrectCol = true; + else if (light_gc_col > 0.5) strip.gammaCorrectCol = false; + + JsonObject light_tr = light[F("tr")]; + CJSON(fadeTransition, light_tr[F("mode")]); + int tdd = light_tr[F("dur")] | -1; + if (tdd >= 0) transitionDelayDefault = tdd * 100; + CJSON(strip.paletteFade, light_tr[F("pal")]); + + JsonObject light_nl = light["nl"]; + CJSON(nightlightMode, light_nl[F("mode")]); + CJSON(nightlightDelayMinsDefault, light_nl[F("dur")]); + nightlightDelayMins = nightlightDelayMinsDefault; + + CJSON(nightlightTargetBri, light_nl[F("tbri")]); + CJSON(macroNl, light_nl[F("macro")]); + + JsonObject def = doc[F("def")]; + CJSON(bootPreset, def[F("ps")]); + CJSON(turnOnAtBoot, def["on"]); // true + CJSON(briS, def["bri"]); // 128 + + JsonObject def_cy = def[F("cy")]; + CJSON(presetCyclingEnabled, def_cy["on"]); + + CJSON(presetCycleMin, def_cy[F("range")][0]); + CJSON(presetCycleMax, def_cy[F("range")][1]); + + tdd = def_cy[F("dur")] | -1; + if (tdd > 0) presetCycleTime = tdd; + + JsonObject interfaces = doc["if"]; + + JsonObject if_sync = interfaces[F("sync")]; + CJSON(udpPort, if_sync[F("port0")]); // 21324 + CJSON(udpPort2, if_sync[F("port1")]); // 65506 + + JsonObject if_sync_recv = if_sync["recv"]; + CJSON(receiveNotificationBrightness, if_sync_recv["bri"]); + CJSON(receiveNotificationColor, if_sync_recv["col"]); + CJSON(receiveNotificationEffects, if_sync_recv[F("fx")]); + receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); + + JsonObject if_sync_send = if_sync["send"]; + CJSON(notifyDirectDefault, if_sync_send[F("dir")]); + notifyDirect = notifyDirectDefault; + CJSON(notifyButton, if_sync_send[F("btn")]); + CJSON(notifyAlexa, if_sync_send[F("va")]); + CJSON(notifyHue, if_sync_send[F("hue")]); + CJSON(notifyMacro, if_sync_send[F("macro")]); + CJSON(notifyTwice, if_sync_send[F("twice")]); + + JsonObject if_nodes = interfaces["nodes"]; + CJSON(nodeListEnabled, if_nodes[F("list")]); + CJSON(nodeBroadcastEnabled, if_nodes[F("bcast")]); + + JsonObject if_live = interfaces["live"]; + CJSON(receiveDirect, if_live["en"]); + CJSON(e131Port, if_live["port"]); // 5568 + CJSON(e131Multicast, if_live[F("mc")]); + + JsonObject if_live_dmx = if_live[F("dmx")]; + CJSON(e131Universe, if_live_dmx[F("uni")]); + CJSON(e131SkipOutOfSequence, if_live_dmx[F("seqskip")]); + CJSON(DMXAddress, if_live_dmx[F("addr")]); + CJSON(DMXMode, if_live_dmx[F("mode")]); + + tdd = if_live[F("timeout")] | -1; + if (tdd >= 0) realtimeTimeoutMs = tdd * 100; + CJSON(arlsForceMaxBri, if_live[F("maxbri")]); + CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false + CJSON(arlsOffset, if_live[F("offset")]); // 0 + + CJSON(alexaEnabled, interfaces[F("va")][F("alexa")]); // false + + CJSON(macroAlexaOn, interfaces[F("va")][F("macros")][0]); + CJSON(macroAlexaOff, interfaces[F("va")][F("macros")][1]); + + const char* apikey = interfaces["blynk"][F("token")] | "Hidden"; + tdd = strnlen(apikey, 36); + if (tdd > 20 || tdd == 0) + getStringFromJson(blynkApiKey, apikey, 36); //normally not present due to security + + JsonObject if_blynk = interfaces["blynk"]; + getStringFromJson(blynkHost, if_blynk[F("host")], 33); + CJSON(blynkPort, if_blynk["port"]); + + JsonObject if_mqtt = interfaces["mqtt"]; + CJSON(mqttEnabled, if_mqtt["en"]); + getStringFromJson(mqttServer, if_mqtt[F("broker")], 33); + CJSON(mqttPort, if_mqtt["port"]); // 1883 + getStringFromJson(mqttUser, if_mqtt[F("user")], 41); + getStringFromJson(mqttPass, if_mqtt["psk"], 41); //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); // "" + + JsonObject if_hue = interfaces[F("hue")]; + CJSON(huePollingEnabled, if_hue["en"]); + CJSON(huePollLightId, if_hue["id"]); + tdd = if_hue[F("iv")] | -1; + if (tdd >= 2) huePollIntervalMs = tdd * 100; + + JsonObject if_hue_recv = if_hue["recv"]; + CJSON(hueApplyOnOff, if_hue_recv["on"]); + CJSON(hueApplyBri, if_hue_recv["bri"]); + CJSON(hueApplyColor, if_hue_recv["col"]); + + JsonArray if_hue_ip = if_hue["ip"]; + + for (byte i = 0; i < 4; i++) + CJSON(hueIP[i], if_hue_ip[i]); + + JsonObject if_ntp = interfaces[F("ntp")]; + CJSON(ntpEnabled, if_ntp["en"]); + getStringFromJson(ntpServerName, if_ntp[F("host")], 33); // "1.wled.pool.ntp.org" + CJSON(currentTimezone, if_ntp[F("tz")]); + CJSON(utcOffsetSecs, if_ntp[F("offset")]); + CJSON(useAMPM, if_ntp[F("ampm")]); + + JsonObject ol = doc[F("ol")]; + CJSON(overlayDefault ,ol[F("clock")]); // 0 + CJSON(countdownMode, ol[F("cntdwn")]); + overlayCurrent = overlayDefault; + + CJSON(overlayMin, ol[F("min")]); + CJSON(overlayMax, ol[F("max")]); + CJSON(analogClock12pixel, ol[F("o12pix")]); + CJSON(analogClock5MinuteMarks, ol[F("o5m")]); + CJSON(analogClockSecondsTrail, ol[F("osec")]); + + //timed macro rules + JsonObject tm = doc[F("timers")]; + JsonObject cntdwn = tm[F("cntdwn")]; + JsonArray cntdwn_goal = cntdwn[F("goal")]; + CJSON(countdownYear, cntdwn_goal[0]); + CJSON(countdownMonth, cntdwn_goal[1]); + CJSON(countdownDay, cntdwn_goal[2]); + CJSON(countdownHour, cntdwn_goal[3]); + CJSON(countdownMin, cntdwn_goal[4]); + CJSON(countdownSec, cntdwn_goal[5]); + CJSON(macroCountdown, cntdwn[F("macro")]); + setCountdown(); + + JsonArray timers = tm[F("ins")]; + uint8_t it = 0; + for (JsonObject timer : timers) { + if (it > 7) break; + CJSON(timerHours[it], timer[F("hour")]); + CJSON(timerMinutes[it], timer[F("min")]); + CJSON(timerMacro[it], timer[F("macro")]); + + byte dowPrev = timerWeekday[it]; + //note: act is currently only 0 or 1. + //the reason we are not using bool is that the on-disk type in 0.11.0 was already int + int actPrev = timerWeekday[it] & 0x01; + CJSON(timerWeekday[it], timer[F("dow")]); + if (timerWeekday[it] != dowPrev) { //present in JSON + timerWeekday[it] <<= 1; //add active bit + int act = timer["en"] | actPrev; + if (act) timerWeekday[it]++; + } + + it++; + } + + JsonObject ota = doc["ota"]; + const char* pwd = ota["psk"]; //normally not present due to security + + bool pwdCorrect = !otaLock; //always allow access if ota not locked + if (pwd != nullptr && strncmp(otaPass, pwd, 33) == 0) pwdCorrect = true; + + if (pwdCorrect) { //only accept these values from cfg.json if ota is unlocked (else from wsec.json) + CJSON(otaLock, ota[F("lock")]); + CJSON(wifiLock, ota[F("lock-wifi")]); + CJSON(aOtaEnabled, ota[F("aota")]); + getStringFromJson(otaPass, pwd, 33); //normally not present due to security + } + + #ifdef WLED_ENABLE_DMX + JsonObject dmx = doc["dmx"]; + CJSON(DMXChannels, dmx[F("chan")]); + CJSON(DMXGap,dmx[F("gap")]); + CJSON(DMXStart, dmx[F("start")]); + CJSON(DMXStartLED,dmx[F("start-led")]); + + JsonArray dmx_fixmap = dmx[F("fixmap")]; + it = 0; + for (int i : dmx_fixmap) { + if (it > 14) break; + CJSON(DMXFixtureMap[i],dmx_fixmap[i]); + it++; + } + #endif + + JsonObject usermods_settings = doc["um"]; + usermods.readFromConfig(usermods_settings); +} + +void serializeConfig() { + serializeConfigSec(); + + DEBUG_PRINTLN(F("Writing settings to /cfg.json...")); + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + JsonArray rev = doc.createNestedArray("rev"); + rev.add(1); //major settings revision + rev.add(0); //minor settings revision + + doc[F("vid")] = VERSION; + + JsonObject id = doc.createNestedObject("id"); + id[F("mdns")] = cmDNS; + id[F("name")] = serverDescription; + id[F("inv")] = alexaInvocationName; + + 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]); + } + + JsonObject ap = doc.createNestedObject("ap"); + ap[F("ssid")] = apSSID; + ap[F("pskl")] = strlen(apPass); + ap[F("chan")] = apChannel; + ap[F("hide")] = apHide; + ap[F("behav")] = apBehavior; + + JsonArray ap_ip = ap.createNestedArray("ip"); + ap_ip.add(4); + ap_ip.add(3); + ap_ip.add(2); + ap_ip.add(1); + + JsonObject wifi = doc.createNestedObject("wifi"); + wifi[F("sleep")] = !noWifiSleep; + wifi[F("phy")] = 1; + + #ifdef WLED_USE_ETHERNET + JsonObject ethernet = doc.createNestedObject("eth"); + ethernet["type"] = ethernetType; + #endif + + JsonObject hw = doc.createNestedObject("hw"); + + JsonObject hw_led = hw.createNestedObject("led"); + hw_led[F("total")] = ledCount; + hw_led[F("maxpwr")] = strip.ablMilliampsMax; + hw_led[F("ledma")] = strip.milliampsPerLed; + hw_led[F("rgbwm")] = strip.rgbwMode; + + JsonArray hw_led_ins = hw_led.createNestedArray("ins"); + + for (uint8_t s = 0; s < busses.getNumBusses(); s++) { + Bus *bus = busses.getBus(s); + if (!bus || bus->getLength()==0) break; + JsonObject ins = hw_led_ins.createNestedObject(); + ins["en"] = true; + ins[F("start")] = bus->getStart(); + ins[F("len")] = bus->getLength(); + JsonArray ins_pin = ins.createNestedArray("pin"); + uint8_t pins[5]; + 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[F("skip")] = (skipFirstLed && s == 0) ? 1 : 0; + ins["type"] = bus->getType(); + } + + JsonObject hw_btn = hw.createNestedObject("btn"); + + JsonArray hw_btn_ins = hw_btn.createNestedArray("ins"); + + // button BTNPIN + JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject(); + hw_btn_ins_0["type"] = (buttonEnabled) ? BTN_TYPE_PUSH : BTN_TYPE_NONE; + + JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin"); + hw_btn_ins_0_pin.add(btnPin); + + JsonArray hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros"); + hw_btn_ins_0_macros.add(macroButton); + hw_btn_ins_0_macros.add(macroLongPress); + hw_btn_ins_0_macros.add(macroDoublePress); + + #ifndef WLED_DISABLE_INFRARED + JsonObject hw_ir = hw.createNestedObject("ir"); + hw_ir["pin"] = irPin; + hw_ir[F("type")] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled ) + #endif + + JsonObject hw_relay = hw.createNestedObject(F("relay")); + hw_relay["pin"] = rlyPin; + hw_relay["rev"] = !rlyMde; + + //JsonObject hw_status = hw.createNestedObject("status"); + //hw_status["pin"] = -1; + + JsonObject light = doc.createNestedObject(F("light")); + light[F("scale-bri")] = briMultiplier; + light[F("pal-mode")] = strip.paletteBlend; + + JsonObject light_gc = light.createNestedObject("gc"); + light_gc["bri"] = (strip.gammaCorrectBri) ? 2.8 : 1.0; + light_gc["col"] = (strip.gammaCorrectCol) ? 2.8 : 1.0; + + JsonObject light_tr = light.createNestedObject("tr"); + light_tr[F("mode")] = fadeTransition; + light_tr[F("dur")] = transitionDelayDefault / 100; + light_tr[F("pal")] = strip.paletteFade; + + JsonObject light_nl = light.createNestedObject("nl"); + light_nl[F("mode")] = nightlightMode; + light_nl[F("dur")] = nightlightDelayMinsDefault; + light_nl[F("tbri")] = nightlightTargetBri; + light_nl[F("macro")] = macroNl; + + JsonObject def = doc.createNestedObject("def"); + def[F("ps")] = bootPreset; + def["on"] = turnOnAtBoot; + def["bri"] = briS; + + //to be removed once preset cycles are presets + if (saveCurrPresetCycConf) { + JsonObject def_cy = def.createNestedObject("cy"); + def_cy["on"] = presetCyclingEnabled; + + JsonArray def_cy_range = def_cy.createNestedArray(F("range")); + def_cy_range.add(presetCycleMin); + def_cy_range.add(presetCycleMax); + def_cy[F("dur")] = presetCycleTime; + } + + JsonObject interfaces = doc.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"); + if_sync_recv["bri"] = receiveNotificationBrightness; + if_sync_recv["col"] = receiveNotificationColor; + if_sync_recv[F("fx")] = receiveNotificationEffects; + + JsonObject if_sync_send = if_sync.createNestedObject("send"); + if_sync_send[F("dir")] = notifyDirect; + if_sync_send[F("btn")] = notifyButton; + if_sync_send[F("va")] = notifyAlexa; + if_sync_send[F("hue")] = notifyHue; + if_sync_send[F("macro")] = notifyMacro; + if_sync_send[F("twice")] = notifyTwice; + + JsonObject if_nodes = interfaces.createNestedObject("nodes"); + if_nodes[F("list")] = nodeListEnabled; + if_nodes[F("bcast")] = nodeBroadcastEnabled; + + JsonObject if_live = interfaces.createNestedObject("live"); + if_live["en"] = receiveDirect; + if_live["port"] = e131Port; + if_live[F("mc")] = e131Multicast; + + JsonObject if_live_dmx = if_live.createNestedObject("dmx"); + if_live_dmx[F("uni")] = e131Universe; + if_live_dmx[F("seqskip")] = e131SkipOutOfSequence; + if_live_dmx[F("addr")] = DMXAddress; + if_live_dmx[F("mode")] = DMXMode; + if_live[F("timeout")] = realtimeTimeoutMs / 100; + if_live[F("maxbri")] = arlsForceMaxBri; + if_live[F("no-gc")] = arlsDisableGammaCorrection; + if_live[F("offset")] = arlsOffset; + + JsonObject if_va = interfaces.createNestedObject("va"); + if_va[F("alexa")] = alexaEnabled; + + JsonArray if_va_macros = if_va.createNestedArray("macros"); + if_va_macros.add(macroAlexaOn); + if_va_macros.add(macroAlexaOff); + JsonObject if_blynk = interfaces.createNestedObject("blynk"); + if_blynk[F("token")] = strlen(blynkApiKey) ? "Hidden":""; + if_blynk[F("host")] = blynkHost; + if_blynk["port"] = blynkPort; + + JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); + if_mqtt["en"] = mqttEnabled; + if_mqtt[F("broker")] = mqttServer; + if_mqtt["port"] = mqttPort; + if_mqtt[F("user")] = mqttUser; + if_mqtt[F("pskl")] = strlen(mqttPass); + if_mqtt[F("cid")] = mqttClientID; + + JsonObject if_mqtt_topics = if_mqtt.createNestedObject(F("topics")); + if_mqtt_topics[F("device")] = mqttDeviceTopic; + if_mqtt_topics[F("group")] = mqttGroupTopic; + + 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"); + if_hue_recv["on"] = hueApplyOnOff; + if_hue_recv["bri"] = hueApplyBri; + if_hue_recv["col"] = hueApplyColor; + + JsonArray if_hue_ip = if_hue.createNestedArray("ip"); + for (byte i = 0; i < 4; i++) { + if_hue_ip.add(hueIP[i]); + } + + JsonObject if_ntp = interfaces.createNestedObject("ntp"); + if_ntp["en"] = ntpEnabled; + if_ntp[F("host")] = ntpServerName; + if_ntp[F("tz")] = currentTimezone; + if_ntp[F("offset")] = utcOffsetSecs; + if_ntp[F("ampm")] = useAMPM; + + JsonObject ol = doc.createNestedObject("ol"); + ol[F("clock")] = overlayDefault; + ol[F("cntdwn")] = countdownMode; + + ol[F("min")] = overlayMin; + ol[F("max")] = overlayMax; + ol[F("o12pix")] = analogClock12pixel; + ol[F("o5m")] = analogClock5MinuteMarks; + ol[F("osec")] = analogClockSecondsTrail; + + JsonObject timers = doc.createNestedObject(F("timers")); + + JsonObject cntdwn = timers.createNestedObject(F("cntdwn")); + JsonArray goal = cntdwn.createNestedArray(F("goal")); + goal.add(countdownYear); goal.add(countdownMonth); goal.add(countdownDay); + goal.add(countdownHour); goal.add(countdownMin); goal.add(countdownSec); + cntdwn[F("macro")] = macroCountdown; + + JsonArray timers_ins = timers.createNestedArray("ins"); + + for (byte i = 0; i < 8; i++) { + if (timerMacro[i] == 0 && timerHours[i] == 0 && timerMinutes[i] == 0) continue; + JsonObject timers_ins0 = timers_ins.createNestedObject(); + timers_ins0["en"] = (timerWeekday[i] & 0x01); + timers_ins0[F("hour")] = timerHours[i]; + timers_ins0[F("min")] = timerMinutes[i]; + timers_ins0[F("macro")] = timerMacro[i]; + timers_ins0[F("dow")] = timerWeekday[i] >> 1; + } + + JsonObject ota = doc.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"); + dmx[F("chan")] = DMXChannels; + dmx[F("gap")] = DMXGap; + dmx[F("start")] = DMXStart; + dmx[F("start-led")] = DMXStartLED; + + JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap")); + for (byte i = 0; i < 15; i++) + dmx_fixmap.add(DMXFixtureMap[i]); + #endif + //} + + JsonObject usermods_settings = doc.createNestedObject("um"); + usermods.addToConfig(usermods_settings); + + File f = WLED_FS.open("/cfg.json", "w"); + if (f) serializeJson(doc, f); + f.close(); +} + +//settings in /wsec.json, not accessible via webserver, for passwords and tokens +bool deserializeConfigSec() { + DEBUG_PRINTLN(F("Reading settings from /wsec.json...")); + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + bool success = readObjectFromFile("/wsec.json", nullptr, &doc); + if (!success) return false; + + JsonObject nw_ins_0 = doc["nw"][F("ins")][0]; + getStringFromJson(clientPass, nw_ins_0["psk"], 65); + + JsonObject ap = doc["ap"]; + getStringFromJson(apPass, ap["psk"] , 65); + + JsonObject interfaces = doc["if"]; + + const char* apikey = interfaces["blynk"][F("token")] | "Hidden"; + int tdd = strnlen(apikey, 36); + if (tdd > 20 || tdd == 0) + getStringFromJson(blynkApiKey, apikey, 36); + + JsonObject if_mqtt = interfaces["mqtt"]; + getStringFromJson(mqttPass, if_mqtt["psk"], 41); + + getStringFromJson(hueApiKey, interfaces[F("hue")][F("key")], 47); + + JsonObject ota = doc["ota"]; + getStringFromJson(otaPass, ota[F("pwd")], 33); + CJSON(otaLock, ota[F("lock")]); + CJSON(wifiLock, ota[F("lock-wifi")]); + CJSON(aOtaEnabled, ota[F("aota")]); + + return true; +} + +void serializeConfigSec() { + DEBUG_PRINTLN(F("Writing settings to /wsec.json...")); + + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + JsonObject nw = doc.createNestedObject("nw"); + + JsonArray nw_ins = nw.createNestedArray("ins"); + + JsonObject nw_ins_0 = nw_ins.createNestedObject(); + nw_ins_0["psk"] = clientPass; + + JsonObject ap = doc.createNestedObject("ap"); + ap["psk"] = apPass; + + JsonObject interfaces = doc.createNestedObject("if"); + JsonObject if_blynk = interfaces.createNestedObject("blynk"); + if_blynk[F("token")] = blynkApiKey; + JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); + if_mqtt["psk"] = mqttPass; + JsonObject if_hue = interfaces.createNestedObject("hue"); + if_hue[F("key")] = hueApiKey; + + JsonObject ota = doc.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); + f.close(); +} diff --git a/wled00/wled14_colors.ino b/wled00/colors.cpp similarity index 71% rename from wled00/wled14_colors.ino rename to wled00/colors.cpp index 8b0414627..dfdd53e07 100644 --- a/wled00/wled14_colors.ino +++ b/wled00/colors.cpp @@ -1,3 +1,5 @@ +#include "wled.h" + /* * Color conversion methods */ @@ -18,7 +20,7 @@ void colorFromUint32(uint32_t in, bool secondary) } //load a color without affecting the white channel -void colorFromUint24(uint32_t in, bool secondary = false) +void colorFromUint24(uint32_t in, bool secondary) { if (secondary) { colSec[0] = in >> 16 & 0xFF; @@ -31,8 +33,13 @@ void colorFromUint24(uint32_t in, bool secondary = false) } } +//store color components in uint32_t +uint32_t colorFromRgbw(byte* rgbw) { + return (rgbw[0] << 16) + (rgbw[1] << 8) + rgbw[2] + (rgbw[3] << 24); +} + //relatively change white brightness, minumum A=5 -void relativeChangeWhite(int8_t amount, byte lowerBoundary =0) +void relativeChangeWhite(int8_t amount, byte lowerBoundary) { int16_t new_val = (int16_t) col[3] + amount; if (new_val > 0xFF) new_val = 0xFF; @@ -57,11 +64,34 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb case 4: rgb[0]=t,rgb[1]=p,rgb[2]=255;break; case 5: rgb[0]=255,rgb[1]=p,rgb[2]=q; } - if (useRGBW) colorRGBtoRGBW(col); + if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col); } -#ifndef WLED_DISABLE_HUESYNC -void colorCTtoRGB(uint16_t mired, byte* rgb) //white spectrum to rgb +void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc +{ + float r = 0, g = 0, b = 0; + float temp = kelvin / 100; + if (temp <= 66) { + r = 255; + g = round(99.4708025861 * log(temp) - 161.1195681661); + if (temp <= 19) { + b = 0; + } else { + b = round(138.5177312231 * log((temp - 10)) - 305.0447927307); + } + } else { + r = round(329.698727446 * pow((temp - 60), -0.1332047592)); + g = round(288.1221695283 * pow((temp - 60), -0.0755148492)); + b = 255; + } + g += 15; //mod by Aircoookie, a bit less accurate but visibly less pinkish + rgb[0] = (uint8_t) constrain(r, 0, 255); + rgb[1] = (uint8_t) constrain(g, 0, 255); + rgb[2] = (uint8_t) constrain(b, 0, 255); + rgb[3] = 0; +} + +void colorCTtoRGB(uint16_t mired, byte* rgb) //white spectrum to rgb, bins { //this is only an approximation using WS2812B with gamma correction enabled if (mired > 475) { @@ -81,9 +111,10 @@ void colorCTtoRGB(uint16_t mired, byte* rgb) //white spectrum to rgb } else { rgb[0]=237;rgb[1]=255;rgb[2]=239;//150 } - if (useRGBW) colorRGBtoRGBW(col); + if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col); } +#ifndef WLED_DISABLE_HUESYNC void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) { float z = 1.0f - x - y; @@ -138,7 +169,7 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www rgb[0] = 255.0*r; rgb[1] = 255.0*g; rgb[2] = 255.0*b; - if (useRGBW) colorRGBtoRGBW(col); + if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col); } void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) @@ -149,8 +180,9 @@ void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.develo xy[0] = X / (X + Y + Z); xy[1] = Y / (X + Y + Z); } -#endif +#endif // WLED_DISABLE_HUESYNC +//RRGGBB / WWRRGGBB order for hex void colorFromDecOrHexString(byte* rgb, char* in) { if (in[0] == 0) return; @@ -171,6 +203,27 @@ void colorFromDecOrHexString(byte* rgb, char* in) rgb[2] = c & 0xFF; } +//contrary to the colorFromDecOrHexString() function, this uses the more standard RRGGBB / RRGGBBWW order +bool colorFromHexString(byte* rgb, const char* in) { + if (in == nullptr) return false; + size_t inputSize = strnlen(in, 9); + if (inputSize != 6 && inputSize != 8) return false; + + uint32_t c = strtoul(in, NULL, 16); + + if (inputSize == 6) { + rgb[0] = (c >> 16) & 0xFF; + rgb[1] = (c >> 8) & 0xFF; + rgb[2] = c & 0xFF; + } else { + rgb[0] = (c >> 24) & 0xFF; + rgb[1] = (c >> 16) & 0xFF; + rgb[2] = (c >> 8) & 0xFF; + rgb[3] = c & 0xFF; + } + return true; +} + float minf (float v, float w) { if (w > v) return v; @@ -183,7 +236,7 @@ float maxf (float v, float w) return v; } -void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw) +void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) { float low = minf(rgb[0],minf(rgb[1],rgb[2])); float high = maxf(rgb[0],maxf(rgb[1],rgb[2])); diff --git a/wled00/const.h b/wled00/const.h new file mode 100644 index 000000000..8ce633726 --- /dev/null +++ b/wled00/const.h @@ -0,0 +1,254 @@ +#ifndef WLED_CONST_H +#define WLED_CONST_H + +/* + * Readability defines and their associated numerical values + compile-time constants + */ + +//Defaults +#define DEFAULT_CLIENT_SSID "Your_Network" +#define DEFAULT_AP_PASS "wled1234" +#define DEFAULT_OTA_PASS "wledota" + +//increase if you need more +#define WLED_MAX_USERMODS 4 + +#ifdef ESP8266 +#define WLED_MAX_BUSSES 3 +#else +#define WLED_MAX_BUSSES 10 +#endif + +//Usermod IDs +#define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present +#define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID +#define USERMOD_ID_EXAMPLE 2 //Usermod "usermod_v2_example.h" +#define USERMOD_ID_TEMPERATURE 3 //Usermod "usermod_temperature.h" +#define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h" +#define USERMOD_ID_PIRSWITCH 5 //Usermod "usermod_PIR_sensor_switch.h" +#define USERMOD_ID_IMU 6 //Usermod "usermod_mpu6050_imu.h" +#define USERMOD_ID_FOUR_LINE_DISP 7 //Usermod "usermod_v2_four_line_display.h +#define USERMOD_ID_ROTARY_ENC_UI 8 //Usermod "usermod_v2_rotary_encoder_ui.h" +#define USERMOD_ID_AUTO_SAVE 9 //Usermod "usermod_v2_auto_save.h" +#define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h" +#define USERMOD_ID_MODE_SORT 11 //Usermod "usermod_v2_mode_sort.h" + +//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 + +//Notifier callMode +#define NOTIFIER_CALL_MODE_INIT 0 //no updates on init, can be used to disable updates +#define NOTIFIER_CALL_MODE_DIRECT_CHANGE 1 +#define NOTIFIER_CALL_MODE_BUTTON 2 +#define NOTIFIER_CALL_MODE_NOTIFICATION 3 +#define NOTIFIER_CALL_MODE_NIGHTLIGHT 4 +#define NOTIFIER_CALL_MODE_NO_NOTIFY 5 +#define NOTIFIER_CALL_MODE_FX_CHANGED 6 //no longer used +#define NOTIFIER_CALL_MODE_HUE 7 +#define NOTIFIER_CALL_MODE_PRESET_CYCLE 8 +#define NOTIFIER_CALL_MODE_BLYNK 9 +#define NOTIFIER_CALL_MODE_ALEXA 10 + +//RGB to RGBW conversion mode +#define RGBW_MODE_MANUAL_ONLY 0 //No automatic white channel calculation. Manual white channel slider +#define RGBW_MODE_AUTO_BRIGHTER 1 //New algorithm. Adds as much white as the darkest RGBW channel +#define RGBW_MODE_AUTO_ACCURATE 2 //New algorithm. Adds as much white as the darkest RGBW channel and subtracts this amount from each RGB channel +#define RGBW_MODE_DUAL 3 //Manual slider + auto calculation. Automatically calculates only if manual slider is set to off (0) +#define RGBW_MODE_LEGACY 4 //Old floating algorithm. Too slow for realtime and palette support + +//realtime modes +#define REALTIME_MODE_INACTIVE 0 +#define REALTIME_MODE_GENERIC 1 +#define REALTIME_MODE_UDP 2 +#define REALTIME_MODE_HYPERION 3 +#define REALTIME_MODE_E131 4 +#define REALTIME_MODE_ADALIGHT 5 +#define REALTIME_MODE_ARTNET 6 +#define REALTIME_MODE_TPM2NET 7 +#define REALTIME_MODE_DDP 8 + +//realtime override modes +#define REALTIME_OVERRIDE_NONE 0 +#define REALTIME_OVERRIDE_ONCE 1 +#define REALTIME_OVERRIDE_ALWAYS 2 + +//E1.31 DMX modes +#define DMX_MODE_DISABLED 0 //not used +#define DMX_MODE_SINGLE_RGB 1 //all LEDs same RGB color (3 channels) +#define DMX_MODE_SINGLE_DRGB 2 //all LEDs same RGB color and master dimmer (4 channels) +#define DMX_MODE_EFFECT 3 //trigger standalone effects of WLED (11 channels) +#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) + +//Light capability byte (unused) 0bRRCCTTTT +//bits 0/1/2/3: specifies a type of LED driver. A single "driver" may have different chip models but must have the same protocol/behavior +//bits 4/5: specifies the class of LED driver - 0b00 (dec. 0-15) unconfigured/reserved +// - 0b01 (dec. 16-31) digital (data pin only) +// - 0b10 (dec. 32-47) analog (PWM) +// - 0b11 (dec. 48-63) digital (data + clock / SPI) +//bits 6/7 are reserved and set to 0b00 + +#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) +#define TYPE_WS2812_1CH 20 //white-only chips +#define TYPE_WS2812_WWA 21 //amber + warm + cold white +#define TYPE_WS2812_RGB 22 +#define TYPE_GS8608 23 //same driver as WS2812, but will require signal 2x per second (else displays test pattern) +#define TYPE_WS2811_400KHZ 24 //half-speed WS2812 protocol, used by very old WS2811 units +#define TYPE_SK6812_RGBW 30 +#define TYPE_TM1814 31 +//"Analog" types (PWM) (32-47) +#define TYPE_ONOFF 40 //binary output (relays etc.) +#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 +#define TYPE_ANALOG_4CH 44 //analog RGBW +#define TYPE_ANALOG_5CH 45 //analog RGB + WW + CW +//Digital types (data + clock / SPI) (48-63) +#define TYPE_WS2801 50 +#define TYPE_APA102 51 +#define TYPE_LPD8806 52 +#define TYPE_P9813 53 + +#define IS_DIGITAL(t) (t & 0x10) //digital are 16-31 and 48-63 +#define IS_PWM(t) (t > 40 && t < 46) +#define NUM_PWM_PINS(t) (t - 40) //for analog PWM 41-45 only +#define IS_2PIN(t) (t > 47) + +//Color orders +#define COL_ORDER_GRB 0 //GRB(w),defaut +#define COL_ORDER_RGB 1 //common for WS2811 +#define COL_ORDER_BRG 2 +#define COL_ORDER_RBG 3 +#define COL_ORDER_BGR 4 +#define COL_ORDER_GBR 5 + + +//Button type +#define BTN_TYPE_NONE 0 +#define BTN_TYPE_RESERVED 1 +#define BTN_TYPE_PUSH 2 +#define BTN_TYPE_PUSH_ACT_HIGH 3 //not implemented +#define BTN_TYPE_SWITCH 4 //not implemented +#define BTN_TYPE_SWITCH_ACT_HIGH 5 //not implemented + +//Ethernet board types +#define WLED_NUM_ETH_TYPES 5 + +#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 + +//Hue error codes +#define HUE_ERROR_INACTIVE 0 +#define HUE_ERROR_UNAUTHORIZED 1 +#define HUE_ERROR_LIGHTID 3 +#define HUE_ERROR_PUSHLINK 101 +#define HUE_ERROR_JSON_PARSING 250 +#define HUE_ERROR_TIMEOUT 251 +#define HUE_ERROR_ACTIVE 255 + +//Segment option byte bits +#define SEG_OPTION_SELECTED 0 +#define SEG_OPTION_REVERSED 1 +#define SEG_OPTION_ON 2 +#define SEG_OPTION_MIRROR 3 //Indicates that the effect will be mirrored within the segment +#define SEG_OPTION_NONUNITY 4 //Indicates that the effect does not use FRAMETIME or needs getPixelColor +#define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed +#define SEG_OPTION_TRANSITIONAL 7 + +// WLED Error modes +#define ERR_NONE 0 // All good :) +#define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?) +#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_GENERAL 19 // A general unspecified filesystem error occured +#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) + +//Timer mode types +#define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness +#define NL_MODE_FADE 1 //Fade to target brightness gradually +#define NL_MODE_COLORFADE 2 //Fade to target brightness and secondary color gradually +#define NL_MODE_SUN 3 //Sunrise/sunset. Target brightness is set immediately, then Sunrise effect is started. Max 60 min. + + +#define NTP_PACKET_SIZE 48 + +// maximum number of LEDs - more than 1500 LEDs (or 500 DMA "LEDPIN 3" driven ones) will cause a low memory condition on ESP8266 +#ifndef MAX_LEDS +#ifdef ESP8266 +#define MAX_LEDS 8192 //rely on memory limit to limit this to 1600 LEDs +#else +#define MAX_LEDS 8192 +#endif +#endif + +#ifndef MAX_LED_MEMORY +#ifdef ESP8266 +#define MAX_LED_MEMORY 5000 +#else +#define MAX_LED_MEMORY 64000 +#endif +#endif + +#ifndef MAX_LEDS_PER_BUS +#define MAX_LEDS_PER_BUS 4096 +#endif + +// string temp buffer (now stored in stack locally) +#define OMAX 2048 + +#define E131_MAX_UNIVERSE_COUNT 9 + +#define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit + +// PWM settings +#ifndef WLED_PWM_FREQ +#ifdef ESP8266 + #define WLED_PWM_FREQ 880 //PWM frequency proven as good for LEDs +#else + #define WLED_PWM_FREQ 19531 +#endif +#endif + +#define TOUCH_THRESHOLD 32 // limit to recognize a touch, higher value means more sensitive + +// Size of buffer for API JSON object (increase for more segments) +#ifdef ESP8266 + #define JSON_BUFFER_SIZE 9216 +#else + #define JSON_BUFFER_SIZE 16384 +#endif + +// Maximum size of node map (list of other WLED instances) +#ifdef ESP8266 + #define WLED_MAX_NODES 15 +#else + #define WLED_MAX_NODES 150 +#endif + +//this is merely a default now and can be changed at runtime +#ifndef LEDPIN +#define LEDPIN 2 +#endif + +#ifdef WLED_ENABLE_DMX +#if (LEDPIN == 2) + #undef LEDPIN + #define LEDPIN 3 + #warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 3." +#endif +#endif + +#endif diff --git a/wled00/data/404.htm b/wled00/data/404.htm new file mode 100644 index 000000000..1b7c95fc0 --- /dev/null +++ b/wled00/data/404.htm @@ -0,0 +1,47 @@ + + + + + + + Not found + + + + +

404 Not Found

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

+ + + \ No newline at end of file diff --git a/wled00/data/dmxmap.htm b/wled00/data/dmxmap.htm new file mode 100644 index 000000000..23e056cb1 --- /dev/null +++ b/wled00/data/dmxmap.htm @@ -0,0 +1,28 @@ + + +DMX Map + + +
...
\ No newline at end of file diff --git a/wled00/data/index.css b/wled00/data/index.css new file mode 100644 index 000000000..f4b4eaf15 --- /dev/null +++ b/wled00/data/index.css @@ -0,0 +1,1064 @@ +@font-face { + font-family: "WIcons"; + src: url(data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAABMkAAsAAAAAEtgAAQACAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgD50AIWNtYXAAAAFoAAABBAAAAQTVan0qZ2FzcAAAAmwAAAAIAAAACAAAABBnbHlmAAACdAAADewAAA3sm6svT2hlYWQAABBgAAAANgAAADYb/Mf8aGhlYQAAEJgAAAAkAAAAJAcYA1FobXR4AAAQvAAAAHAAAABwZAAMiWxvY2EAABEsAAAAOgAAADowHizsbWF4cAAAEWgAAAAgAAAAIAAmAF1uYW1lAAARiAAAAXoAAAF62zUFRXBvc3QAABMEAAAAIAAAACAAAwAAAAMEAAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA5BADM/80AMwDMwDMAAAAAQAAAAAAAAAAAAAAIAAAAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEAOgAAAA2ACAABAAWAAEAIOA34DzgTOBm4I/gouDo4RbhOeGK4i3iPeKi4qbis+Lj4yXjM+NL45DjleQJ5BD//f//AAAAAAAg4DfgPOBM4Gbgj+Ci4OjhFuE54YriLeI94qLipuKz4uPjJeMz40vjj+OV5AnkEP/9//8AAf/jH80fyR+6H6EfeR9nHyIe9R7THoMd4R3SHW4dax1fHTAc7xziHMsciByEHBEcCwADAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAACANX/wAMrAsAACQASAAAlESERFAYjISImARUhNTM3MxczAQACADIj/qojMgIr/aqWKtYqlhUCAP4AIzIyAqNVVSsrAAEAkQAVA4ACUQAFAAAlARcBJzcBgAHEPP4A7zyNAcQ8/gDvPAAAAAACAFX/sQOrAsAAJABBAAABMhceARcWFRQHDgEHBg8BJyYnLgEnJjU0Nz4BNzYzMhYXPgEzAzY3PgE3NjU0JiMiBgcjLgEjIgYVFBceARcWHwECwDErKz8SExobX0NEUj4+UkRDXxsaExI/KysxOGUjI2U4vEw/PlgYGFVAMVYRUBFWMUBVGBhYPj9MBALAEhJAKyoyPDk4dT9ASzg4Sz9AdTg5PDIqK0ASEjApKTD9aUQ7OmcvLy5AVjksLDlWQC4vL2c6O0QFAAMAVf+VA6sC6wAcACAAJAAAATIXHgEXFhUUBw4BBwYjIicuAScmNTQ3PgE3NjMTESMREzUjFQIAWE5OdCEiIiF0Tk5YWE5OdCEiIiF0Tk5YK1ZWVgLrIiF0Tk5YWE5OdCEiIiF0Tk5YWE5OdCEi/YABAP8AAVVVVQAAAAACAID/wAOAAsAABAA2AAABESMRMxcWFx4BFxYVFAcOAQcGIyInLgEnJjU0Nz4BNzY3Fw4BFRQXHgEXFjMyNz4BNzY1NCYnAitWVs4fGRkjCgkeHmlGRVBQRUZpHh4JCiMZGR88MjwYF1E3Nj4+NjdRFxg8MwLA/lUBq10aICFKKSksUEVGaR4eHh5pRkVQLCkpSiEgGjwpeEY+NjdRFxgYF1E3Nj5GeCkAAAAAAgB0/6YDjALaAE4AWgAAARceAQ8BDgEvAQ4BDwEOASsBIiYvAS4BJwcGJi8BJjY/AS4BNTQ2NycuAT8BPgEfAT4BPwE+ATsBMhYfAR4BFzc2Fh8BFgYPAR4BFRQGBwUyNjU0JiMiBhUUFgMxVQYDBFIDDwdmDyMTDwELCKQICwEQEyIQZgcOBFIDAwVXAgECAVYGAwRSAw8HZg8jEw8BCwikCAsBEBMiEGYHDgRSAwMFVwIBAQH+zz9bWz8/W1sBGEQEDweNBwUCKQwUCGwICgoIbAgUDCkCBQeNBw8ERAoUCgoUCkQEDweNBwUCKQwUCGwICgoIbAgUDCkCBQeNBw8ERAoUCgoUCnJbPz9bWz8/WwAAAwArAAAD1QKAABsANwBDAAABMhceARcWFwYHDgEHBiMiJy4BJyYnNjc+ATc2EzI3PgE3NjU0Jy4BJyYjIgcOAQcGFRQXHgEXFhMyFhUUBiMiJjU0NgIAUElJei8vGxsvL3pJSVBQSUl6Ly8bGy8veklJUCwnJzoREBAROicnLCwnJzoREBAROicnLDVLSzU1S0sCgBgXVTs7RkY7O1UXGBgXVTs7RkY7O1UXGP3rEBE6JycsLCcnOhEQEBE6JycsLCcnOhEQAVVLNTVLSzU1SwAAAAACAKv/awNVAxUAGQAyAAABMhceARcWFRQGByc+ATU0Jy4BJyYjFSc3FRE1Fwc1IicuAScmNTQ2NxcOARUUFx4BFxYCAEc+Pl0bGhwZPg8PFBRGLi81q6urq0c+Pl0bGhwZPg8PFBRGLi8ClRobXT4+RzJcKD8aPSA1Ly5GFBSAq6qA/auAq6qAGhtdPj5HMlwoPxo9IDUvLkYUFAAIAFf/lwOrAukAAwAHAAsAFAAcACUALgBNAAABFwURHwEFERcnESUDDgEHJz4BNxUHDgEHIz4BNwMeARcHLgEnMxM3HgEXFS4BJwEUBw4BBwYHNTY3PgE3NjU0Jy4BJyYnNRYXHgEXFhUCLX7/AIJ+/wCCggEA1i5VIz0wc0DiHCQFVwcxJwgFJBw9JzEHV0Q9I1UuQHMwArkeHWdGRlA/NjZQFxYWF1A2Nj9QRkZnHR4Bnl7AAYBiXsABgGJi/oDAAVIFJBw9JzEHV4EjVS5AczD+xy5UJD0wc0D+4T0cJAVXBzEnAUpTSUpxJSQJVwgeHVs5OkFBOjlbHR4IVwkkJXFKSVMAAAABANUAFQMrAmsACwAAASERIxEhNSERMxEhAyv/AFb/AAEAVgEAARX/AAEAVgEA/wAAAAAABgBV/+sDgAKVAAsAEQAcACEAJgArAAA3NTMVIzUzNSM1MzUDNSM1MxUHNTMVBzMVIzU3IxMhFSE1ETUhFSERNSEVIVWAgFYrKysrVlaATEyATU3WAlX9qwJV/asCVf2rayqqKhYqFgGAgCqqgComWiomWgEAVlb9qlZWAQBWVgAFAFX/lQOrAusAHAA4AEQAUABYAAABMhceARcWFRQHDgEHBiMiJy4BJyY1NDc+ATc2MxEyNz4BNzY1NCcuAScmIyIHDgEHBhUUFx4BFxYTIiY1NDYzMhYVFAYhIiY1NDYzMhYVFAYTIiYnIQ4BIwIAWE5OdCEiIiF0Tk5YWU1OdCEiIiF0Tk1ZRz4+XRsaGhtdPj5HRz4+XRsaGhtdPj7cGiYmGhslJf67GyUlGxomJntLdRoBtBp1SwLrIiF0Tk5YWE5OdCEiIiF0Tk5YWE5OdCEi/QAaG10+PkdHPj5dGxoaG10+PkdHPj5dGxoBgCUbGiYmGhslJRsaJiYaGyX+6lRCQlQAAAABAQD/lQMrAusAIgAAATIXHgEXFhUUBw4BBwYjIiYnNjc+ATc2NTQnLgEnJic+ATMBgFhOTnQhIiIhdE5OWCJAHkE3N08WFxcWTzc3QR5AIgLrIiF0Tk5YWE5OdCEiCgoUKCdqQUFISEFBaicoFAoKAAAAAAMAHf9dA+MDIwAPACsAOAAAARcHFSMHJyM1Jzc1MzcXMwEyNz4BNzY1NCcuAScmIyIHDgEHBhUUFx4BFxYTMhYVFAYjIiY1NDYzA1WOjsiNjciOjsiNjcj+qzUvLkYUFBQURi4vNTUvLkYUFBQURi4vNUdkZEdHZGRHAc2NjciOjsiNjciOjv2rFBRGLi81NS8uRhQUFBRGLi81NS8uRhQUAatkR0dkZEdHZAAFAID/wAOAAsAAKAA0AEAATABYAAABMhceARcWFRQHDgEHBisBIgYVFBYXHgEVFAYjIicuAScmNTQ3PgE3NgMyNjU0JiMiBhUUFjcyNjU0JiMiBhUUFjMyNjU0JiMiBhUUFhcyNjU0JiMiBhUUFgIAUEVGaR4eERE5JycsTBomCQcICSUbUEVGaR4eHh5pRkWbGyUlGxomJpobJSUbGiYm8BomJhobJSWbGiYmGhslJQLAGxtcPj9GLCcnOhERJRsMFggJFgwbJR4eaUZFUFBFRmkeHv6AJRsbJSUbGyWrJRsaJiYaGyUlGxomJhobJaslGxslJRsbJQAAAAABASv/lQLVAusABwAAASEDMwERIxEBKwGqqqr+1oAC6/6q/gABgAHWAAAAAAQAgP+VA4ADFQADAAcAJwBEAAABFSE1ExEzEQEeARUUBw4BBwYjIicuAScmNTQ3PgE3NjMyFhc3HgEXATI3PgE3NjU0Jy4BJyYjIgcOAQcGFRQXHgEXFjMCgP8AVVYBASctHh5oRkZQUEZGaB4eHh5pRkVQRHoyPBEeDv6XPjY3URcYGBdRNzY+PjY3URcYGBdRNzY+AxVVVf3WAQD/AAEaMnpET0ZGaB4fHx5oRkZPUEZGaB4eLCg8DR4R/aoXGFE2Nj4+NzZRGBcXGFE2Nz4+NjZRGBcAAAkAK/+CA9UDKQADAAcACwAPABMAFwAzADcAOwAAAQcnNwMVIzUBFSM1BQcnNwM3FwcTMxUjATIXHgEXFhUUBw4BBwYjIicuAScmNTQ3PgE3NhM1MxUlNxcHASA8TT0pgAIAVgGUTTxMTDtNPCmAgP6rNS8uRhQUFBRGLi81NS8uRhQUFBRGLi8KVv5sTTxMAnE8TTz+wlVVAal+fqdNPE39ezxMPQGUVQEqFBRFLy81NS4vRRUUFBVFLy41NS8vRRQU/S1+fqdNPE0AAAIAgP+9A4AC6wAFAAoAAC0BFwkBNwUJAgcCAAE6Rv6A/oBFATv+gAGAAYBGKfU2/tUBKzWIASsBK/7VNgAAAAACAFX/lQOrAusAHAAoAAABMhceARcWFRQHDgEHBiMiJy4BJyY1NDc+ATc2MxMnNycHJwcXBxc3FwIAWE5OdCEiIiF0Tk5YWE5OdCEiIiF0Tk5Y1ZmZPJmZPJmZPJmZAusiIXROTlhYTk50ISIiIXROTlhYTk50ISL9vJmZPJmZPJmZPJmZAAAAAQCRABUDgAJRAAUAACUBFwEnNwGAAcQ8/gDvPY4Bwzz+AO88AAAAAAEBAACVAwAB0QAFAAABFwkBNxcCxDz/AP8APMQB0Tz/AAEAPMMAAAACAKv/lQNVAyMAJgA5AAABFhceARcWFRQHDgEHBiMiJy4BJyY1NDc+ATc2NwcUFjMyNjU0JjEDMjc+ATc2NTQmJw4BBw4BFRQWAkA/MzNJExQaG10+PkdHPj5dGxoJCSQaGSABWUJCUCBMKiUmNxAQDA0gbDk4QFEDIzI+P5FRUVZHPj5dGxsbG10+Pkc2MzRgLCwmD0JeXkJEiPzyEBE3JSYqLVYqLDcMC0Y0N08AAAIAVf/AA6sC6wAJABMAAAEHEyUFEyclGwEDFyc3LwEPARcHA6vpRv74/vhG6QEzeHh4oSuOu0lJuo0qAbbK/tSfnwEsyhoBG/7l/t9htnsQrawQe7cAAAABAAAAATMzF648mV8PPPUACwQAAAAAANx9KKMAAAAA3H0oowAA/10D4wMpAAAACAACAAAAAAAAAAEAAAMz/zQAAAQAAAAAAAPjAAEAAAAAAAAAAAAAAAAAAAAcBAAAAAAAAAAAAAAAAAAAAAQAANUEAACRBAAAVQQAAFUEAACABAAAdAQAACsEAACrBAAAVwQAANUEAABVBAAAVQQAAQAEAAAdBAAAgAQAASsEAACABAAAKwQAAIAEAABVBAAAkQQAAQAEAACrBAAAVQAAAAAACgAUAB4AQABUALgA9gFMAdYCQAKOAxIDLANsA/AEKgSABP4FFAWABeYGBgZKBl4GcgbKBvYAAAABAAAAHABbAAkAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEABgAAAAEAAAAAAAIABwBXAAEAAAAAAAMABgAzAAEAAAAAAAQABgBsAAEAAAAAAAUACwASAAEAAAAAAAYABgBFAAEAAAAAAAoAGgB+AAMAAQQJAAEADAAGAAMAAQQJAAIADgBeAAMAAQQJAAMADAA5AAMAAQQJAAQADAByAAMAAQQJAAUAFgAdAAMAAQQJAAYADABLAAMAAQQJAAoANACYd2xlZDEyAHcAbABlAGQAMQAyVmVyc2lvbiAxLjIAVgBlAHIAcwBpAG8AbgAgADEALgAyd2xlZDEyAHcAbABlAGQAMQAyd2xlZDEyAHcAbABlAGQAMQAyUmVndWxhcgBSAGUAZwB1AGwAYQByd2xlZDEyAHcAbABlAGQAMQAyRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format('woff'); +} + +:root { + --c-1: #111; + --c-f: #fff; + --c-2: #222; + --c-3: #333; + --c-4: #444; + --c-5: #555; + --c-6: #666; + --c-8: #888; + --c-b: #bbb; + --c-c: #ccc; + --c-e: #eee; + --c-d: #ddd; + --c-r: #831; + --t-b: 0.5; + --c-o: rgba(34, 34, 34, 0.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)); + /*following are internal*/ + --th: 70px; + --tp: 70px; + --bh: 63px; + --tbp: 14px 14px 10px 14px; + --bbp: 9px 0 7px 0; + --bhd: none; + --bmt: 0px; +} + +html { + touch-action: manipulation; +} + +body { + margin: 0; + background-color: var(--c-1); + font-family: Helvetica, Verdana, sans-serif; + font-size: 17px; + color: var(--c-f); + text-align: center; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; + scrollbar-width: 6px; + scrollbar-color: var(--c-sb) transparent; +} + +html, +body { + height: 100%; + width: 100%; + position: fixed; + overscroll-behavior: none; +} + +#bg { + height: 100vh; + width: 100vw; + position: fixed; + z-index: -10; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + opacity: 0; + transition: opacity 2s; +} + +p { + margin: 10px 0 2px 0; + color: var(--c-d); +} + +button { + outline: none; + cursor: pointer; +} + +.labels { + margin: 0; + padding: 8px 0 2px 0; +} + +#namelabel { + position: fixed; + bottom: calc(var(--bh) + 6px); + right: 4px; + color: var(--c-6); + cursor: pointer; + writing-mode: vertical-rl; +} + +.bri { + padding: 4px; +} + +.wrapper { + position: fixed; + top: 0; + left: 0; + right: 0; + background: var(--c-tb); + z-index: 1; +} + +.icons { + font-family: 'WIcons'; + font-style: normal; + font-size: 24px; + line-height: 1; + display: inline-block; + margin: -2px 0 4px 0; +} + +.huge { + font-size: 42px; +} + +.infot { + table-layout: fixed; + width: 100%; +} + +.segt { + table-layout: fixed; + width: 76%; +} + +.segtd { + text-align: center; + text-transform: uppercase; + font-size: 14px; +} + +.keytd { + text-align: left; + padding-bottom: 8px; +} + +.valtd { + text-align: right; + padding-bottom: 8px; +} + +.slider-icon +{ + transform: translate(6px,3px); + color: var(--c-d); +} + +.e-icon +{ + transform: translateY(3px); + color: var(--c-d); +} + +.sel-icon { + transform: translateX(3px); + color: var(--c-d); +} + +.search-cancel-icon { + position: absolute; + right: 8px; + top: 9px; + cursor: pointer; + display: none; +} + +.flr { + float: right; + cursor: pointer; + margin: 0; + color: var(--c-f); + transform: rotate(0deg); + transition: transform 0.3s; +} + +.exp { + transform: rotate(180deg); +} + +.il { + display: inline-block; + vertical-align: middle; +} + +#liveview { + height: 4px; + display: none; + width: 100%; + border: 0px; +} + +.tab { + background-color: transparent; + color: var(--c-d); +} + +.bot { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + background-color: var(--c-tb); +} + +.tab button { + background-color: transparent; + float: left; + border: none; + transition: color 0.3s, background-color 0.3s; + font-size: 17px; + color: var(--c-c); +} + +.top button { + padding: var(--tbp); +} + +.bot button { + padding: var(--bbp); + width:25%; +} + +.tab button:hover { + background-color: var(--c-tbh); + color: var(--c-e); +} + +.tab button.active { + background-color: var(--c-tba) !important; + color: var(--c-f); +} + +.active { + background-color: var(--c-6) !important; + color: var(--c-f); +} + +.container { + --n: 1; + width: 100%; + width: calc(var(--n)*100%); + height: calc(100% - var(--tp) - var(--bh)); + margin-top: var(--tp); + transform: translate(calc(var(--i, 0)/var(--n)*-100%)); + overscroll-behavior: none; +} + +.tabcontent { + float: left; + position: relative; + width: 100%; + width: calc(100%/var(--n)); + padding: 11px 0; + box-sizing: border-box; + border: 0px; + overflow: auto; + height: 100%; + overscroll-behavior: none; +} + +#Effects { + padding-top: 0; + margin-top: 11px; + height: calc(100% - 11px); + -webkit-overflow-scrolling: touch; +} + +.smooth { transition: transform calc(var(--f, 1)*.5s) ease-out } + +.tab-label { + margin: 0 0 -5px 0; + padding-bottom: 4px; +} + +.overlay { + position: fixed; + height: 100%; + width: 100%; + top: 0; + left: 0; + background-color: var(--c-3); + font-size: 24px; + display: flex; + align-items: center; + justify-content: center; + z-index: 11; + opacity: 0.95; + transition: 0.7s; + pointer-events: none; +} + +.staytop { + display: block; + position: -webkit-sticky; + position: sticky; + background: var(--c-1); + top: -1px; + z-index: 1; + margin-top: 1px; + width: 272px; + margin: auto; + border-radius: 25px; +} + +#staytop1 { + top: 28px; +} + +#fxb0 { + margin-bottom: 2px; + filter: drop-shadow(0 0 1px #000); +} + +.first { + margin-top: 18px !important; +} + +#toast { + opacity: 0; + background-color: var(--c-5); + max-width: 90%; + color: var(--c-f); + text-align: center; + border-radius: 5px; + padding: 16px; + position: fixed; + z-index: 5; + left: 50%; + transform: translateX(-50%); + bottom: calc(var(--bh) + 22px); + font-size: 17px; + pointer-events: none; +} + +#toast.show { + opacity: 1; + animation: fadein 0.5s, fadein 0.5s 2.5s reverse; +} + +#toast.error { + opacity: 1; + background-color: #b21; + animation: fadein 0.5s; +} + +.modal { + position:fixed; + left: 0px; + bottom: 0px; + right: 0px; + top: calc(var(--th) - 1px); + background-color: var(--c-o); + transform: translateY(100%); + transition: transform 0.4s; + padding: 8px; + font-size: 20px; + overflow: auto; +} + +#info { + z-index: 3; +} + +#rover, #nodes { + z-index: 2; +} + +#ndlt { + margin: 12px 0; +} + +.valtd i { + font-size: 14px; +} + +#roverstar { + position: fixed; + top: calc(var(--th) + 5px); + left: 1px; + display: none; + cursor: pointer; +} + +#connind { + position: fixed; + bottom: calc(var(--bh) + 5px); + left: 4px; + padding: 5px; + border-radius: 5px; + background-color: #a90; + z-index: -2; +} + +#imgw { + width: 200px; + height: 55px; + display: inline-block; +} + +#kv, #kn { + max-width: 490px; + display: inline-block; +} + +#kn td { + padding-bottom: 12px; +} + +#lv { + max-width: 600px; + display: inline-block; +} + +#heart { + transition: color 0.9s; + font-size: 16px; + color: #f00; +} + +img { + max-width: 100%; + max-height: 100%; +} + +@keyframes fadein { + from {bottom: 0; opacity: 0;} + to {bottom: calc(var(--bh) + 22px); opacity: 1;} +} + +.sliderdisplay { + content:''; + position: absolute; + top: 13px; bottom: 13px; + left: 10px; right: 10px; + background: var(--c-4); + border-radius: 17px; + pointer-events: none; + z-index: -1; +} + +.sliderbubble { + width: 36px; + line-height: 24px; + background: var(--c-3); + position: absolute; + transform: translateX(-50%); + border-radius: 12px; + margin-left: 12px; + margin-top: 3px; + padding: 0px; + display: inline; +} + +.hidden { + display: none; +} + +input[type=range] { + -webkit-appearance: none; + width: 220px; + padding: 0px; + margin: 0px 10px 0px 10px; + background-color: transparent; + cursor: pointer; +} +input[type=range]:focus { + outline: none; +} +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 30px; + cursor: pointer; + background: transparent; +} +input[type=range]::-webkit-slider-thumb { + height: 16px; + width: 16px; + border-radius: 17px; + background: var(--c-f); + cursor: pointer; + -webkit-appearance: none; + margin-top: 7px; +} +input[type=range]::-moz-range-track { + width: 100%; + height: 30px; + background-color: rgba(0, 0, 0, 0); +} +input[type=range]::-moz-range-thumb { + border: 0px solid rgba(0, 0, 0, 0); + height: 16px; + width: 16px; + border-radius: 17px; + background: var(--c-f); + transform: translateY(7px); +} +input[type=range]:active + .sliderbubble { + display: inline; + transform: translateX(-50%); +} + +#wwrap { + display: none; +} + +.sliderwrap { + height: 30px; + width: 240px; + position: relative; +} + +.sws { + width: 212px; +} + +.sis { + width: 192px !important; +} + +.hd { + display: var(--bhd); +} + +#briwrap { + float: right; + margin-top: var(--bmt); +} + +#picker { + margin: 10px auto; + width: 260px; +} + +#rgbwrap { + display: none; +} + +.btn { + padding: 8px; + margin: 10px; + width: 230px; + font-size: 19px; + background-color: var(--c-3); + color: var(--c-f); + cursor: pointer; + border: 0px solid white; + border-radius: 25px; + transition-duration: 0.5s; + -webkit-backface-visibility: hidden; + -webkit-transform:translate3d(0,0,0); +} + +.btn-s { + padding: 9px; + width: 276px; + background-color: var(--c-2); +} +.btn-i { + padding-bottom: 3px; +} +.btn-icon { + margin: 0px 8px 4px 0; + vertical-align: middle; +} +.btna-icon { + margin: 0px; +} +.btn-p { + width: 216px; +} + +#qcs-w { + margin-top: 10px; +} +.qcs { + padding: 14px; + margin: 2px; + border-radius: 14px; + display: inline-block; +} +.qcsb { + padding: 13px; + border: 1px solid var(--c-f); +} +#hexw { + margin-top: 5px; + display: none; +} + +.cl { + width: 42px; +} + +select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: url("data:image/svg+xml;utf8,") no-repeat; + background-size: 12px; + background-position: 206px 16px; + padding-left: 12px !important; + background-repeat: no-repeat; + outline: none; +} +select:-moz-focusring { + transition-duration: 0s; + color: transparent; + text-shadow: 0 0 0 #fff; +} +option { + background-color: var(--c-3); + color: var(--c-f); +} + +input[type=number], input[type=text] { + background: var(--c-3); + color: var(--c-f); + border: 0px solid white; + border-radius: 5px; + padding: 8px; + margin: 6px 6px 6px 0; + font-size: 19px; + transition: background-color 0.2s; + outline: none; + width: 50px; + -webkit-appearance: textfield; + -moz-appearance: textfield; + appearance: textfield; +} + +textarea { + background: var(--c-2); + color: var(--c-f); + width: 236px; + height: 90px; + border-radius: 5px; + border: 2px solid #555; + outline: none; + resize: none; + font-size: 19px; +} + +::selection { + background: var(--c-b); +} + +input[type=text] { + width: 100px; + border-radius: 25px; + text-align: center; +} + +.ptxt { + width: 200px !important; + margin: 26px 0 6px 12px !important; +} + +.stxt { + width: 50px !important; +} + +input[type=number]:focus, input[type=text]:focus { + background: var(--c-6); +} + +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; +} + +.segn { + margin: 3px 0 6px 0 !important; +} + +.segname { + position: absolute; + top: 0px; + left: 50%; + padding: 9px 0; + transform: translateX(-50%); + white-space: nowrap; + cursor: pointer; +} + +.pname { + width: 208px; + padding: 8px 0; + text-align: center; + overflow: hidden; + text-overflow: clip; +} + +.pid { + position: absolute; + top: 0px; + left: 0px; + padding: 11px 0px 0px 11px; + font-size: 16px; + width: 20px; + text-align: center; + color: var(--c-b); +} + +.newseg { + cursor: default; +} + +.ic { + padding: 6px 0 0 0; +} + +.xxs { + width: 40px; + margin: 6px; +} + +.psts { + background-color: var(--c-3); + color: var(--c-f); + cursor: pointer; + padding: 2px 0 0 0; + height: 40px; +} + +.cnf { + color: var(--c-f); + cursor: pointer; + background: var(--c-3); + border-radius: 5px; +} + +.cnf-s { + position: absolute; + top: 66px; + right: 28px; + padding: 43px 6px; +} + +.pwr { + color: var(--c-6); + transform: translate(2px, 3px); + cursor: pointer; +} + +.act { + color: var(--c-f); +} + +.half { + padding: 7.5px; + top: 64px; +} + +.del { + position: absolute; + bottom: 8px; + right: 8px; + color: var(--c-f); + cursor: pointer; +} + +.check, .radio { + display: inline-block; + position: relative; + padding-bottom: 32px; + margin-bottom: 14px; + cursor: pointer; + text-align: center; +} + +.schkl { + padding: 2px 5px 0px 35px; + margin: 0 0 0 2px; +} + +.revchkl { + padding: 2px 0px 0px 35px; + margin-bottom: 0px; + margin-top: 8px; +} + +.fxchkl { + position: absolute; + top: 0px; + left: 8px; +} + +.check input, .radio input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +.checkmark, .radiomark { + position: absolute; + bottom: 0; + left: 0; + height: 25px; + width: 25px; + background-color: var(--c-3); + border-radius: 10px; +} + +.radiomark { + height: 24px; + width: 24px; + border-radius: 50%; + background-color: transparent; +} + +.schk { + top: 0; +} + +.psv { + left: initial; + bottom: initial; + top: 0; + right: 0; +} + +.psvl { + padding: 2px 35px 10px 0px; + margin-top: 10px; + margin-bottom: 0px; +} + + +.check:hover input ~ .checkmark { + background-color: var(--c-4); +} + +.check input:checked ~ .checkmark { + background-color: var(--c-6); +} + +.checkmark:after, .radiomark:after { + content: ""; + position: absolute; + display: none; +} + +.check input:checked ~ .checkmark:after, .radio input:checked ~ .radiomark:after { + display: block; +} + +.check .checkmark:after { + left: 9px; + top: 5px; + width: 5px; + height: 10px; + border: solid var(--c-f); + border-width: 0 3px 3px 0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} + +.radio .radiomark:after { + width: 12px; + height: 12px; + top: 50%; + left: 50%; + margin: -6px; + border-radius: 50%; + background: var(--c-f); +} + +.h { + font-size: 13px; + text-align: center; + color: var(--c-b); +} + +.bp { + margin-bottom: 5px; +} + +.seg { + position: relative; + display: inline-block; + padding: 8px; + margin: 10px; + width: 260px; + font-size: 19px; + background-color: var(--c-2); + color: var(--c-f); + border: 0px solid white; + border-radius: 20px; + text-align: left; + transition: background-color 0.5s; + filter: brightness(1); +} + +.list { + position: relative; + transition: background-color 0.5s; + margin: auto auto 10px; + padding-bottom: 10px; + width: 230px; +} + +.lstI { + position: sticky; + overflow: hidden; +} + +.fxbtn { + margin: 20px auto; + padding: 8px 0; +} + +.lstI:hover { + background: var(--c-4); +} + +.lstI:last-child { + border: none; +} + +.lstI.sticky, .lstI.selected { + z-index: 1; +} + +#selectPalette .lstI.selected { + top: 27px; + bottom: -11px; +} + +#selectPalette .lstI.sticky { + top: -11px; +} + +.lstI.selected { + background: var(--c-5); + top: 95px; + bottom: -11px; +} + +.lstI.sticky { + top: 57px; +} + +.lstIname { + margin: 3px 0; + white-space: nowrap; + cursor: pointer; + font-size: 19px; +} + +.lstIprev { + width: 220px; + height: 5px; + margin: auto; + position: absolute; + bottom: 0px; + left: 5px; +} + +input[type="text"].search { + display: block; + width: 230px; + box-sizing: border-box; + padding: 8px 8px 9px 38px; + margin: 6px auto 0 auto; + text-align: left; + background: url("data:image/svg+xml;utf8,") + no-repeat 10px 10px; + background-size: 20px; + background-color: var(--c-3); +} + +input[type="text"].search:focus { + background-color: var(--c-4); +} + +input[type="text"].search:not(:placeholder-shown) { + background-color: var(--c-5); +} + +.pres { + margin-bottom: 6px; +} + +.segin { + padding: 8px 8px 4px 8px; + display: none; +} + +.expanded { + display: block; +} + +.c { + text-align: center; +} + +.po2 { + display: none; + margin-top: 8px; +} + +.pwarn { + color: red; +} + +::-webkit-scrollbar { + width: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: var(--c-sb); + opacity: 0.2; + border-radius: 5px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--c-sbh); +} + +@media all and (max-width: 335px) { + .sliderbubble { + display: none; + } +} + +@media all and (max-width: 550px) and (min-width: 374px) { + .infobtn { + width: 155px; + } +} + +@media all and (max-width: 685px) { + .top button { + width: 16.6%; + padding: 8px 0 4px 0; + } + .hd { + display: none !important; + } + #briwrap { + margin-top: 0px !important; + float: none; + } +} + +@media all and (max-width: 1249px) { + #buttonPcm { + display: none; + } +} diff --git a/wled00/data/index.htm b/wled00/data/index.htm index d9d1ccc95..825832e4d 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -1,1486 +1,224 @@ - + - - - - - -WLED - - + + + + + + WLED + +
Loading WLED UI...
+
-
-
- - - - - - -
-
-

Brightness

-
- -
- -
-
-
-
- -
+
+
+ + + + + +
+ + +
+
+

Brightness

+
+ +
+ +
+
+
+
+ +
-
-
-
-

White channel

-
- -
-
-
-
-
-
-
-
-
-

-
-
-
-
-
R
-
-
- - - -
-

Color palette

-
- - -
-
+
+
+
+
+ +
+

+
+ +
+

+
+ +
+

+
+
+

White channel

+
+ +
+
+
+
+
+
+
+
+
+

+
+
+
+
+
R
+
+
+ + + +
+
+ + +
+

+ + Color palette +

+
+
+
+ +
+ + Default + +
+
+
+
+ + Loading... + +
+
-
-

Effect speed

-
- -
- -
-
-
-

Effect intensity

-
- -
- -
-
-
-

Effect mode

-
- -
-
-Loading... -
-
-
+
+
+
-
-
-Loading... -
-
+
+

Effect speed

+
+ +
+ + +
+
+
+

Effect intensity

+
+ +
+ + +
+
+
+

Effect mode

+
+ Loading... +
+
-
-
+
+
+ Loading... +
+
-
-

Load from slot

- - - -
- - - -
- - - -
- - - -
-
-Slot 16 can save all segments.

-
-First preset:
-Last preset:
-Time per preset: s
-Transition: s -
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ Loading... +

+
+ First preset:
+ Last preset:
+ Time per preset: s
+ Transition: s +
- - - - + + + +
+
-
- - +
+ + + - \ No newline at end of file + diff --git a/wled00/data/index.js b/wled00/data/index.js new file mode 100644 index 000000000..0b62fcfe3 --- /dev/null +++ b/wled00/data/index.js @@ -0,0 +1,1778 @@ +//page js +var loc = false, locip; +var noNewSegs = false; +var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true, isRgbw = false; +var whites = [0,0,0]; +var selColors; +var expanded = [false]; +var powered = [true]; +var nlDur = 60, nlTar = 0; +var nlFade = false; +var selectedFx = 0; +var csel = 0; +var currentPreset = -1; +var lastUpdate = 0; +var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0; +var pcMode = false, pcModeA = false, lastw = 0; +var d = document; +const ranges = RangeTouch.setup('input[type="range"]', {}); +var palettesData; +var pJson = {}; +var pN = "", pI = 0; +var pmt = 1, pmtLS = 0, pmtLast = 0; +var lastinfo = {}; +var cfg = { + theme:{base:"dark", bg:{url:""}, 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} +}; + +var cpick = new iro.ColorPicker("#picker", { + width: 260, + wheelLightness: false, + wheelAngle: 90, + layout: [ + { + component: iro.ui.Wheel, + options: {} + }, + { + component: iro.ui.Slider, + options: { + sliderType: 'value' + } + }, + { + component: iro.ui.Slider, + options: { + sliderType: 'kelvin', + minTemperature: 2100, + maxTemperature: 10000 + } + } + ] +}); + +function handleVisibilityChange() { + if (!d.hidden && new Date () - lastUpdate > 3000) { + requestJson(null); + } +} + +function sCol(na, col) { + d.documentElement.style.setProperty(na, col); +} + +function applyCfg() +{ + cTheme(cfg.theme.base === "light"); + var bg = cfg.theme.color.bg; + if (bg) sCol('--c-1', bg); + var ccfg = cfg.comp.colors; + d.getElementById('hexw').style.display = ccfg.hex ? "block":"none"; + d.getElementById('picker').style.display = ccfg.picker ? "block":"none"; + d.getElementById('rgbwrap').style.display = ccfg.rgb ? "block":"none"; + d.getElementById('qcs-w').style.display = ccfg.quick ? "block":"none"; + var l = cfg.comp.labels; + var e = d.querySelectorAll('.tab-label'); + for (var i=0; i 23 && today.getDate() < 28)) img.src = "https://aircoookie.github.io/xmas.png"; + else if (today.getMonth() == 3 && (today.getDate() > 3 && today.getDate() < 6)) img.src = "https://aircoookie.github.io/easter.png"; + } + img.addEventListener('load', (event) => { + var a = parseFloat(cfg.theme.alpha.bg); + if (isNaN(a)) a = 0.6; + bg.style.opacity = a; + bg.style.backgroundImage = `url(${img.src})`; + img = null; + }); +} + +function onLoad() { + if (window.location.protocol == "file:") { + loc = true; + locip = localStorage.getItem('locIp'); + if (!locip) + { + locip = prompt("File Mode. Please enter WLED IP!"); + localStorage.setItem('locIp', locip); + } + } + var sett = localStorage.getItem('wledUiCfg'); + if (sett) cfg = mergeDeep(cfg, JSON.parse(sett)); + + resetPUtil(); + + applyCfg(); + loadBg(cfg.theme.bg.url); + + var cd = d.getElementById('csl').children; + for (var i = 0; i < cd.length; i++) { + cd[i].style.backgroundColor = "rgb(0, 0, 0)"; + } + selectSlot(0); + updateTablinks(0); + resetUtil(); + cpick.on("input:end", function() { + setColor(1); + }); + pmtLS = localStorage.getItem('wledPmt'); + setTimeout(function(){requestJson(null, false);}, 25); + d.addEventListener("visibilitychange", handleVisibilityChange, false); + size(); + d.getElementById("cv").style.opacity=0; + if (localStorage.getItem('pcm') == "true") togglePcMode(true); + var sls = d.querySelectorAll('input[type="range"]'); + for (var sl of sls) { + sl.addEventListener('input', updateBubble, true); + sl.addEventListener('touchstart', toggleBubble); + sl.addEventListener('touchend', toggleBubble); + } +} + +function updateTablinks(tabI) +{ + var tablinks = d.getElementsByClassName("tablinks"); + for (var i of tablinks) { + i.className = i.className.replace(" active", ""); + } + if (pcMode) return; + tablinks[tabI].className += " active"; +} + +function openTab(tabI, force = false) { + if (pcMode && !force) return; + iSlide = tabI; + _C.classList.toggle('smooth', false); + _C.style.setProperty('--i', iSlide); + updateTablinks(tabI); +} + +var timeout; +function showToast(text, error = false) { + if (error) d.getElementById('connind').style.backgroundColor = "#831"; + var x = d.getElementById("toast"); + x.innerHTML = text; + x.className = error ? "error":"show"; + clearTimeout(timeout); + x.style.animation = 'none'; + x.style.animation = null; + timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900); +} + +function showErrorToast() { + showToast('Connection to light failed!', true); +} +function clearErrorToast() { + d.getElementById("toast").className = d.getElementById("toast").className.replace("error", ""); +} + +function getRuntimeStr(rt) +{ + var t = parseInt(rt); + var days = Math.floor(t/86400); + var hrs = Math.floor((t - days*86400)/3600); + var mins = Math.floor((t - days*86400 - hrs*3600)/60); + var str = days ? (days + " " + (days == 1 ? "day" : "days") + ", ") : ""; + str += (hrs || days) ? (hrs + " " + (hrs == 1 ? "hour" : "hours")) : ""; + if (!days && hrs) str += ", "; + if (t > 59 && !days) str += mins + " min"; + if (t < 3600 && t > 59) str += ", "; + if (t < 3600) str += (t - mins*60) + " sec"; + return str; +} + +function inforow(key, val, unit = "") +{ + return `${key}${val}${unit}`; +} + +function getLowestUnusedP() +{ + var l = 1; + for (var key in pJson) + { + if (key == l) l++; + } + if (l > 250) l = 250; + return l; +} + +function checkUsed(i) { + var id = d.getElementById(`p${i}id`).value; + if (pJson[id] && (i == 0 || id != i)) { + d.getElementById(`p${i}warn`).innerHTML = `⚠ Overwriting ${pName(id)}!`; + } else { + d.getElementById(`p${i}warn`).innerHTML = ""; + } +} + +function pName(i) { + var n = "Preset " + i; + if (pJson[i].n) n = pJson[i].n; + return n; +} + +function papiVal(i) { + if (!pJson[i]) return ""; + var o = Object.assign({},pJson[i]); + if (o.win) return o.win; + delete o.n; delete o.p; delete o.ql; + return JSON.stringify(o); +} + +function qlName(i) { + if (!pJson[i]) return ""; + if (!pJson[i].ql) return ""; + return pJson[i].ql; +} + +function cpBck() { + var copyText = d.getElementById("bck"); + + copyText.select(); + copyText.setSelectionRange(0, 999999); + d.execCommand("copy"); + + showToast("Copied to clipboard!"); +} + +function presetError(empty) +{ + var hasBackup = false; var bckstr = ""; + try { + bckstr = localStorage.getItem("wledP"); + if (bckstr.length > 10) hasBackup = true; + } catch (e) { + + } + var cn = `
`; + if (empty) + cn += `You have no presets yet!`; + else + cn += `Sorry, there was an issue loading your presets!`; + + 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)`; + else + cn += `Here is a backup of the last known good state:`; + cn += `
+ `; + } + cn += `
`; + d.getElementById('pcont').innerHTML = cn; + if (hasBackup) d.getElementById('bck').value = bckstr; +} + +function loadPresets() +{ + var url = '/presets.json'; + if (loc) { + url = `http://${locip}/presets.json`; + } + + fetch + (url, { + method: 'get' + }) + .then(res => { + if (!res.ok) { + showErrorToast(); + } + return res.json(); + }) + .then(json => { + pJson = json; + populatePresets(); + }) + .catch(function (error) { + showToast(error, true); + console.log(error); + presetError(false); + }); +} + +var pQL = []; + +function populateQL() +{ + var cn = ""; + if (pQL.length > 0) { + cn += `

Quick load

`; + + var it = 0; + for (var key of (pQL||[])) + { + cn += ``; + it++; + if (it > 4) { + it = 0; + cn += '
'; + } + } + if (it != 0) cn+= '
'; + + cn += `

All presets

`; + } + d.getElementById('pql').innerHTML = cn; +} + +function populatePresets(fromls) +{ + if (fromls) pJson = JSON.parse(localStorage.getItem("wledP")); + delete pJson["0"]; + var cn = ""; + var arr = Object.entries(pJson); + arr.sort(cmpP); + var added = false; + pQL = []; + var is = []; + + for (var key of (arr||[])) + { + if (!isObject(key[1])) continue; + let i = parseInt(key[0]); + var qll = key[1].ql; + if (qll) pQL.push([i, qll]); + is.push(i); + + cn += `
`; + if (cfg.comp.pid) cn += `
${i}
`; + cn += `
${pName(i)}
+ +
+

`; + added = true; + } + + d.getElementById('pcont').innerHTML = cn; + if (added) { + if (pmtLS != pmt && pmt != 0) { + localStorage.setItem("wledPmt", pmt); + pJson["0"] = {}; + localStorage.setItem("wledP", JSON.stringify(pJson)); + } + pmtLS = pmt; + for (var a = 0; a < is.length; a++) { + let i = is[a]; + if (expanded[i+100]) expand(i+100, true); + } + } else { presetError(true); } + updatePA(); + populateQL(); +} + +function populateInfo(i) +{ + var cn=""; + var heap = i.freeheap/1000; + 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";} + else if (pwr > 0) {pwr = 50 * Math.round(pwr/50); pwru = pwr + " mA";} + var urows=""; + if (i.u) { + for (const [k, val] of Object.entries(i.u)) + { + if (val[1]) { + urows += inforow(k,val[0],val[1]); + } else { + urows += inforow(k,val); + } + } + } + + var vcn = "Kuuhaku"; + if (i.ver.startsWith("0.12.")) vcn = "Hikari"; + if (i.cn) vcn = i.cn; + + cn += `v${i.ver} "${vcn}"

+ ${urows} + ${inforow("Build",i.vid)} + ${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")} + ${inforow("Uptime",getRuntimeStr(i.uptime))} + ${inforow("Free heap",heap," kB")} + ${inforow("Estimated current",pwru)} + ${inforow("Frames / second",i.leds.fps)} + ${inforow("MAC address",i.mac)} + ${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")} + ${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} +
`; + d.getElementById('kv').innerHTML = cn; +} + +function populateSegments(s) +{ + var cn = ""; + segCount = 0; lowestUnused = 0; lSeg = 0; + + for (var y = 0; y < (s.seg||[]).length; y++) + { + segCount++; + + var inst=s.seg[y]; + let i = parseInt(inst.id); + powered[i] = inst.on; + if (i == lowestUnused) lowestUnused = i+1; + if (i > lSeg) lSeg = i; + + cn += `
+ +
+ Segment ${i} +
+ +
+ + + + + + + + + +
Start LEDStop LED
+ + + + + + + + + +
GroupingSpacing
+
+ +
+ +
+
+ + + + +
+

`; + } + + d.getElementById('segcont').innerHTML = cn; + if (lowestUnused >= maxSeg) { + d.getElementById('segutil').innerHTML = 'Maximum number of segments reached.'; + noNewSegs = true; + } else if (noNewSegs) { + resetUtil(); + noNewSegs = false; + } + for (var i = 0; i <= lSeg; i++) { + updateLen(i); + updateTrail(d.getElementById(`seg${i}bri`)); + if (segCount < 2) d.getElementById(`segd${lSeg}`).style.display = "none"; + } + d.getElementById('rsbtn').style.display = (segCount > 1) ? "inline":"none"; +} + +function populateEffects(effects) +{ + var html = ``; + + effects.shift(); //remove solid + for (let i = 0; i < effects.length; i++) { + effects[i] = {id: parseInt(i)+1, name:effects[i]}; + } + effects.sort(compare); + + effects.unshift({ + "id": 0, + "name": "Solid", + "class": "sticky" + }); + + for (let i = 0; i < effects.length; i++) { + html += generateListItemHtml( + 'fx', + effects[i].id, + effects[i].name, + 'setX', + '', + effects[i].class, + ); + } + + d.getElementById('fxlist').innerHTML=html; +} + +function populatePalettes(palettes) +{ + palettes.shift(); //remove default + for (let i = 0; i < palettes.length; i++) { + palettes[i] = { + "id": parseInt(i)+1, + "name": palettes[i] + }; + } + palettes.sort(compare); + + palettes.unshift({ + "id": 0, + "name": "Default", + "class": "sticky" + }); + + var html = ``; + for (let i = 0; i < palettes.length; i++) { + let previewCss = genPalPrevCss(palettes[i].id); + html += generateListItemHtml( + 'palette', + palettes[i].id, + palettes[i].name, + 'setPalette', + `
`, + palettes[i].class, + ); + } + + d.getElementById('selectPalette').innerHTML=html; +} + +function redrawPalPrev() +{ + let palettes = d.querySelectorAll('#selectPalette .lstI'); + for (let i = 0; i < palettes.length; i++) { + let id = palettes[i].dataset.id; + let lstPrev = palettes[i].querySelector('.lstIprev'); + if (lstPrev) { + lstPrev.style = genPalPrevCss(id); + } + } +} + +function genPalPrevCss(id) +{ + if (!palettesData) { + return; + } + var paletteData = palettesData[id]; + var previewCss = ""; + + if (!paletteData) { + return 'display: none'; + } + + // We need at least two colors for a gradient + if (paletteData.length == 1) { + paletteData[1] = paletteData[0]; + if (Array.isArray(paletteData[1])) { + paletteData[1][0] = 255; + } + } + + var gradient = []; + for (let j = 0; j < paletteData.length; j++) { + const element = paletteData[j]; + let r; + let g; + let b; + let index = false; + if (Array.isArray(element)) { + index = element[0]/255*100; + r = element[1]; + g = element[2]; + b = element[3]; + } else if (element == 'r') { + r = Math.random() * 255; + g = Math.random() * 255; + b = Math.random() * 255; + } else { + if (selColors) { + let pos = element[1] - 1; + r = selColors[pos][0]; + g = selColors[pos][1]; + b = selColors[pos][2]; + } + } + if (index === false) { + index = j / paletteData.length * 100; + } + + gradient.push(`rgb(${r},${g},${b}) ${index}%`); + } + + return `background: linear-gradient(to right,${gradient.join()});`; +} + +function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', extraClass = '') +{ + return `
+ + + ${name} + + ${extraHtml} +
`; +} + +function btype(b){ + switch (b) { + case 32: return "ESP32"; + case 82: return "ESP8266"; + } + return "?"; +} +function bname(o){ + if (o.name=="WLED") return o.ip; + return o.name; +} + +function populateNodes(i,n) +{ + var cn=""; + var urows=""; + var nnodes = 0; + if (n.nodes) { + n.nodes.sort((a,b) => (a.name).localeCompare(b.name)); + for (var x=0;x${bname(o)}`; + urows += inforow(url,`${btype(o.type)}
${o.vid==0?"N/A":o.vid}`); + nnodes++; + } + } + } + if (i.ndc < 0) cn += `Instance List is disabled.`; + else if (nnodes == 0) cn += `No other instances found.`; + cn += ` + ${urows} + ${inforow("Current instance:",i.name)} +
`; + d.getElementById('kn').innerHTML = cn; +} + +function loadNodes() +{ + var url = '/json/nodes'; + if (loc) { + url = `http://${locip}/json/nodes`; + } + + fetch + (url, { + method: 'get' + }) + .then(res => { + if (!res.ok) { + showToast('Could not load Node list!', true); + } + return res.json(); + }) + .then(json => { + populateNodes(lastinfo, json); + }) + .catch(function (error) { + showToast(error, true); + console.log(error); + }); +} + +function updateTrail(e, slidercol) +{ + if (e==null) return; + var max = e.hasAttribute('max') ? e.attributes.max.value : 255; + var progress = e.value * 100 / max; + progress = parseInt(progress); + var scol; + switch (slidercol) { + case 1: scol = "#f00"; break; + case 2: scol = "#0f0"; break; + case 3: scol = "#00f"; break; + default: scol = "var(--c-f)"; + } + var val = `linear-gradient(90deg, ${scol} ${progress}%, var(--c-4) ${progress}%)`; + e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val; +} + +function updateBubble(e) +{ + var bubble = e.target.parentNode.getElementsByTagName('output')[0]; + + if (bubble) { + bubble.innerHTML = e.target.value; + } +} + +function toggleBubble(e) +{ + e.target.parentNode.querySelector('output').classList.toggle('hidden'); +} + +function updateLen(s) +{ + if (!d.getElementById(`seg${s}s`)) return; + var start = parseInt(d.getElementById(`seg${s}s`).value); + var stop = parseInt(d.getElementById(`seg${s}e`).value); + var len = stop - start; + var out = "(delete)"; + if (len > 1) { + out = `${len} LEDs`; + } else if (len == 1) { + out = "1 LED"; + } + + if (d.getElementById(`seg${s}grp`) != null) + { + var grp = parseInt(d.getElementById(`seg${s}grp`).value); + var spc = parseInt(d.getElementById(`seg${s}spc`).value); + if (grp == 0) grp = 1; + var virt = Math.ceil(len/(grp + spc)); + if (!isNaN(virt) && (grp > 1 || spc > 0)) out += ` (${virt} virtual)`; + } + + d.getElementById(`seg${s}len`).innerHTML = out; +} + +function updatePA() +{ + var ps = d.getElementsByClassName("seg"); + for (let i = 0; i < ps.length; i++) { + ps[i].style.backgroundColor = "var(--c-2)"; + } + ps = d.getElementsByClassName("psts"); + for (let i = 0; i < ps.length; i++) { + ps[i].style.backgroundColor = "var(--c-2)"; + } + if (currentPreset > 0) { + var acv = d.getElementById(`p${currentPreset}o`); + if (acv && !expanded[currentPreset+100]) + acv.style.background = "var(--c-6)"; + acv = d.getElementById(`p${currentPreset}qlb`); + if (acv) acv.style.background = "var(--c-6)"; + } +} + +function updateUI() +{ + d.getElementById('buttonPower').className = (isOn) ? "active":""; + d.getElementById('buttonNl').className = (nlA) ? "active":""; + d.getElementById('buttonSync').className = (syncSend) ? "active":""; + + updateTrail(d.getElementById('sliderBri')); + updateTrail(d.getElementById('sliderSpeed')); + updateTrail(d.getElementById('sliderIntensity')); + updateTrail(d.getElementById('sliderW')); + if (isRgbw) d.getElementById('wwrap').style.display = "block"; + + updatePA(); + updateHex(); + updateRgb(); +} + +function displayRover(i,s) +{ + d.getElementById('rover').style.transform = (i.live && s.lor == 0) ? "translateY(0px)":"translateY(100%)"; + var sour = i.lip ? i.lip:""; if (sour.length > 2) sour = " from " + sour; + d.getElementById('lv').innerHTML = `WLED is receiving live ${i.lm} data${sour}`; + d.getElementById('roverstar').style.display = (i.live && s.lor) ? "block":"none"; +} + +function compare(a, b) { + if (a.name < b.name) return -1; + return 1; +} +function cmpP(a, b) { + if (!a[1].n) return (a[0] > b[0]); + return a[1].n.localeCompare(b[1].n,undefined, {numeric: true}); +} + +var jsonTimeout; +function requestJson(command, rinfo = true, verbose = true) { + d.getElementById('connind').style.backgroundColor = "#a90"; + lastUpdate = new Date(); + if (!jsonTimeout) jsonTimeout = setTimeout(showErrorToast, 3000); + var req = null; + var e1 = d.getElementById('fxlist'); + var e2 = d.getElementById('selectPalette'); + + var url = rinfo ? '/json/si': (command ? '/json/state':'/json'); + if (loc) { + url = `http://${locip}${url}`; + } + + var type = command ? 'post':'get'; + if (command) + { + command.v = verbose; + command.time = Math.floor(Date.now() / 1000); + req = JSON.stringify(command); + //console.log(req); + } + fetch + (url, { + method: type, + headers: { + "Content-type": "application/json; charset=UTF-8" + }, + body: req + }) + .then(res => { + if (!res.ok) { + showErrorToast(); + } + return res.json(); + }) + .then(json => { + clearTimeout(jsonTimeout); + jsonTimeout = null; + clearErrorToast(); + d.getElementById('connind').style.backgroundColor = "#070"; + if (!json) { + showToast('Empty response', true); + } + if (json.success) { + return; + } + var s = json; + + if (!command || rinfo) { + if (!rinfo) { + pmt = json.info.fs.pmt; + if (pmt != pmtLS || pmt == 0) { + setTimeout(loadPresets,99); + } + else { + populatePresets(true); + } + pmtLast = pmt; + + populateEffects(json.effects); + populatePalettes(json.palettes); + } + + var info = json.info; + var name = info.name; + d.getElementById('namelabel').innerHTML = name; + if (name === "Dinnerbone") { + d.documentElement.style.transform = "rotate(180deg)"; + } + if (info.live) { + name = "(Live) " + name; + } + if (loc) { + name = "(L) " + name; + } + d.title = name; + isRgbw = info.leds.wv; + ledCount = info.leds.count; + syncTglRecv = info.str; + maxSeg = info.leds.maxseg; + pmt = info.fs.pmt; + + if (!command && pmt != pmtLast) { + setTimeout(loadPresets,99); + } + pmtLast = pmt; + d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none"; + lastinfo = info; + if (isInfo) { + populateInfo(info); + } + s = json.state; + displayRover(info, s); + + if (!rinfo) loadPalettesData(); + } + + isOn = s.on; + d.getElementById('sliderBri').value= s.bri; + nlA = s.nl.on; + nlDur = s.nl.dur; + nlTar = s.nl.tbri; + nlFade = s.nl.fade; + syncSend = s.udpn.send; + currentPreset = s.ps; + d.getElementById('cyToggle').checked = (s.pl >= 0); + d.getElementById('cycs').value = s.ccnf.min; + d.getElementById('cyce').value = s.ccnf.max; + d.getElementById('cyct').value = s.ccnf.time /10; + d.getElementById('cyctt').value = s.transition /10; + + var selc=0; var ind=0; + populateSegments(s); + for (let i = 0; i < (s.seg||[]).length; i++) + { + if(s.seg[i].sel) {selc = ind; break;} ind++; + } + var i=s.seg[selc]; + if (!i) { + showToast('No Segments!', true); + updateUI(); + return; + } + + selColors = i.col; + var cd = d.getElementById('csl').children; + for (let e = 2; e >= 0; e--) + { + cd[e].style.backgroundColor = "rgb(" + i.col[e][0] + "," + i.col[e][1] + "," + i.col[e][2] + ")"; + if (isRgbw) whites[e] = parseInt(i.col[e][3]); + selectSlot(csel); + } + d.getElementById('sliderSpeed').value = whites[csel]; + + d.getElementById('sliderSpeed').value = i.sx; + d.getElementById('sliderIntensity').value = i.ix; + + // Effects + e1.querySelector(`input[name="fx"][value="${i.fx}"]`).checked = true; + var selElement = e1.querySelector('.selected'); + if (selElement) { + selElement.classList.remove('selected') + } + var selectedEffect = e1.querySelector(`.lstI[data-id="${i.fx}"]`); + selectedEffect.classList.add('selected'); + selectedFx = i.fx; + + // Palettes + e2.querySelector(`input[name="palette"][value="${i.pal}"]`).checked = true; + selElement = e2.querySelector('.selected'); + if (selElement) { + selElement.classList.remove('selected') + } + e2.querySelector(`.lstI[data-id="${i.pal}"]`).classList.add('selected'); + + if (!command) { + selectedEffect.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); + } + + 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 = "The requested preset does not exist."; + break; + case 19: + errstr = "A filesystem error has occured."; + break; + } + showToast('Error ' + s.error + ": " + errstr, true); + } + updateUI(); + }) + .catch(function (error) { + showToast(error, true); + console.log(error); + }); +} + +function togglePower() { + isOn = !isOn; + var obj = {"on": isOn}; + obj.transition = parseInt(d.getElementById('cyctt').value*10); + requestJson(obj); +} + +function toggleNl() { + nlA = !nlA; + if (nlA) + { + showToast(`Timer active. Your light will turn ${nlTar > 0 ? "on":"off"} ${nlFade ? "over":"after"} ${nlDur} minutes.`); + } else { + showToast('Timer deactivated.'); + } + var obj = {"nl": {"on": nlA}}; + requestJson(obj); +} + +function toggleSync() { + syncSend = !syncSend; + 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; + requestJson(obj); +} + +function toggleLiveview() { + isLv = !isLv; + d.getElementById('liveview').style.display = (isLv) ? "block":"none"; + var url = loc ? `http://${locip}/liveview`:"/liveview"; + d.getElementById('liveview').src = (isLv) ? url:"about:blank"; + d.getElementById('buttonSr').className = (isLv) ? "active":""; + size(); +} + +function toggleInfo() { + if (isNodes) toggleNodes(); + isInfo = !isInfo; + if (isInfo) populateInfo(lastinfo); + d.getElementById('info').style.transform = (isInfo) ? "translateY(0px)":"translateY(100%)"; + d.getElementById('buttonI').className = (isInfo) ? "active":""; +} + +function toggleNodes() { + if (isInfo) toggleInfo(); + isNodes = !isNodes; + d.getElementById('nodes').style.transform = (isNodes) ? "translateY(0px)":"translateY(100%)"; + d.getElementById('buttonNodes').className = (isNodes) ? "active":""; + if (isNodes) loadNodes(); +} + +function makeSeg() { + var ns = 0; + if (lowestUnused > 0) { + var pend = d.getElementById(`seg${lowestUnused -1}e`).value; + if (pend < ledCount) ns = pend; + } + var cn = `
+
+ New segment ${lowestUnused} +
+
+
+ + + + + + + + + +
Start LEDStop LED
+
${ledCount - ns} LEDs
+ +
+
`; + d.getElementById('segutil').innerHTML = cn; +} + +function resetUtil() { + var cn = `
`; + d.getElementById('segutil').innerHTML = cn; +} + +function makeP(i) { + return ` +
+
Quick load label:
+
(leave empty for no Quick load button)
+
+
+ API command
+ +
+
+ + +
+
Save to ID 0)?i:getLowestUnusedP()}>
+
+ + ${(i>0)?'': + ''} +
+
+ +
+ ${(i>0)? ('
ID ' +i+ '
'):""}`; +} + +function makePUtil() { + d.getElementById('putil').innerHTML = `
+
+ New preset
+
+ ${makeP(0)}
`; + updateTrail(d.getElementById('p0p')); +} + +function resetPUtil() { + var cn = `
`; + d.getElementById('putil').innerHTML = cn; +} + +function tglCs(i){ + var pss = d.getElementById(`p${i}cstgl`).checked; + d.getElementById(`p${i}o1`).style.display = pss? "block" : "none"; + d.getElementById(`p${i}o2`).style.display = !pss? "block" : "none"; +} + +function selSegEx(s) +{ + var obj = {"seg":[]}; + for (let i=0; i<=lSeg; i++){ + obj.seg.push({"sel":(i==s)?true:false}); + } + requestJson(obj); +} + +function selSeg(s){ + var sel = d.getElementById(`seg${s}sel`).checked; + var obj = {"seg": {"id": s, "sel": sel}}; + requestJson(obj, false); +} + +function setSeg(s){ + var start = parseInt(d.getElementById(`seg${s}s`).value); + var stop = parseInt(d.getElementById(`seg${s}e`).value); + if (stop <= start) {delSeg(s); return;} + var obj = {"seg": {"id": s, "start": start, "stop": stop}}; + if (d.getElementById(`seg${s}grp`)) + { + var grp = parseInt(d.getElementById(`seg${s}grp`).value); + var spc = parseInt(d.getElementById(`seg${s}spc`).value); + obj.seg.grp = grp; + obj.seg.spc = spc; + } + requestJson(obj); +} + +function delSeg(s){ + if (segCount < 2) { + showToast("You need to have multiple segments to delete one!"); + return; + } + expanded[s] = false; + segCount--; + var obj = {"seg": {"id": s, "stop": 0}}; + requestJson(obj, false); +} + +function setRev(s){ + var rev = d.getElementById(`seg${s}rev`).checked; + var obj = {"seg": {"id": s, "rev": rev}}; + requestJson(obj, false); +} + +function setMi(s){ + var mi = d.getElementById(`seg${s}mi`).checked; + var obj = {"seg": {"id": s, "mi": mi}}; + requestJson(obj, false); +} + +function setSegPwr(s){ + var obj = {"seg": {"id": s, "on": !powered[s]}}; + requestJson(obj); +} + +function setSegBri(s){ + var obj = {"seg": {"id": s, "bri": parseInt(d.getElementById(`seg${s}bri`).value)}}; + requestJson(obj); +} + +function setX(ind = null) { + if (ind === null) { + ind = parseInt(d.querySelector('#fxlist input[name="fx"]:checked').value); + } else { + d.querySelector(`#fxlist input[name="fx"][value="${ind}`).checked = true; + } + var selElement = d.querySelector('#fxlist .selected'); + if (selElement) { + selElement.classList.remove('selected') + } + d.querySelector(`#fxlist .lstI[data-id="${ind}"]`).classList.add('selected'); + + var obj = {"seg": {"fx": parseInt(ind)}}; + requestJson(obj); +} + +function setPalette(paletteId = null) +{ + if (paletteId === null) { + paletteId = parseInt(d.querySelector('#selectPalette input[name="palette"]:checked').value); + } else { + d.querySelector(`#selectPalette input[name="palette"][value="${paletteId}`).checked = true; + } + var selElement = d.querySelector('#selectPalette .selected'); + if (selElement) { + selElement.classList.remove('selected') + } + d.querySelector(`#selectPalette .lstI[data-id="${paletteId}"]`).classList.add('selected'); + var obj = {"seg": {"pal": paletteId}}; + requestJson(obj); +} + +function setBri() { + var obj = {"bri": parseInt(d.getElementById('sliderBri').value)}; + obj.transition = parseInt(d.getElementById('cyctt').value*10); + requestJson(obj); +} + +function setSpeed() { + var obj = {"seg": {"sx": parseInt(d.getElementById('sliderSpeed').value)}}; + requestJson(obj, false); +} + +function setIntensity() { + var obj = {"seg": {"ix": parseInt(d.getElementById('sliderIntensity').value)}}; + requestJson(obj, false); +} + +function setLor(i) { + var obj = {"lor": i}; + requestJson(obj); +} + +function toggleCY() { + var obj = {"pl" : -1}; + if (d.getElementById('cyToggle').checked) + { + obj = {"pl": 0, "ccnf": {"min": parseInt(d.getElementById('cycs').value), "max": parseInt(d.getElementById('cyce').value), "time": parseInt(d.getElementById('cyct').value*10)}}; + obj.transition = parseInt(d.getElementById('cyctt').value*10); + } + + requestJson(obj); +} + +function setPreset(i) { + var obj = {"ps": i}; + + showToast("Loading preset " + pName(i) +" (" + i + ")"); + + requestJson(obj); +} + +function saveP(i) { + pI = parseInt(d.getElementById(`p${i}id`).value); + if (!pI || pI < 1) pI = (i>0) ? i : getLowestUnusedP(); + pN = d.getElementById(`p${i}txt`).value; + if (pN == "") pN = "Preset " + pI; + var obj = {}; + if (!d.getElementById(`p${i}cstgl`).checked) { + var raw = d.getElementById(`p${i}api`).value; + try { + obj = JSON.parse(raw); + } catch (e) { + obj.win = raw; + if (raw.length < 2) { + d.getElementById(`p${i}warn`).innerHTML = "⚠ Please enter your API command first"; + return; + } else if (raw.indexOf('{') > -1) { + d.getElementById(`p${i}warn`).innerHTML = "⚠ Syntax error in custom JSON API command"; + return; + } else if (raw.indexOf("Please") == 0) { + d.getElementById(`p${i}warn`).innerHTML = "⚠ Please refresh the page before modifying this preset"; + return; + } + } + obj.o = true; + } else { + obj.ib = d.getElementById(`p${i}ibtgl`).checked; + obj.sb = d.getElementById(`p${i}sbtgl`).checked; + } + obj.psave = pI; obj.n = pN; + var pQN = d.getElementById(`p${i}ql`).value; + if (pQN.length > 0) obj.ql = pQN; + + showToast("Saving " + pN +" (" + pI + ")"); + requestJson(obj); + if (obj.o) { + pJson[pI] = obj; + delete pJson[pI].psave; + delete pJson[pI].o; + delete pJson[pI].v; + delete pJson[pI].time; + } else { + pJson[pI] = {"n":pN, "win":"Please refresh the page to see this newly saved command."}; + if (obj.win) pJson[pI].win = obj.win; + if (obj.ql) pJson[pI].ql = obj.ql; + } + populatePresets(); + resetPUtil(); +} + +function delP(i) { + var obj = {"pdel": i}; + requestJson(obj); + delete pJson[i]; + populatePresets(); +} + +function selectSlot(b) { + csel = b; + var cd = d.getElementById('csl').children; + for (let i = 0; i < cd.length; i++) { + cd[i].style.border="2px solid white"; + cd[i].style.margin="5px"; + cd[i].style.width="42px"; + } + cd[csel].style.border="5px solid white"; + cd[csel].style.margin="2px"; + cd[csel].style.width="50px"; + cpick.color.set(cd[csel].style.backgroundColor); + d.getElementById('sliderW').value = whites[csel]; + updateTrail(d.getElementById('sliderW')); + updateHex(); + updateRgb(); + redrawPalPrev(); +} + +var lasth = 0; +function pC(col) +{ + if (col == "rnd") + { + col = {h: 0, s: 0, v: 100}; + col.s = Math.floor((Math.random() * 50) + 50); + do { + col.h = Math.floor(Math.random() * 360); + } while (Math.abs(col.h - lasth) < 50); + lasth = col.h; + } + cpick.color.set(col); + setColor(0); +} + +function updateRgb() +{ + var col = cpick.color.rgb; + var s = d.getElementById('sliderR'); + s.value = col.r; updateTrail(s,1); + s = d.getElementById('sliderG'); + s.value = col.g; updateTrail(s,2); + s = d.getElementById('sliderB'); + s.value = col.b; updateTrail(s,3); +} + +function updateHex() +{ + var str = cpick.color.hexString; + str = str.substring(1); + var w = whites[csel]; + if (w > 0) str += w.toString(16); + d.getElementById('hexc').value = str; + d.getElementById('hexcnf').style.backgroundColor = "var(--c-3)"; +} + +function hexEnter() { + d.getElementById('hexcnf').style.backgroundColor = "var(--c-6)"; + if(event.keyCode == 13) fromHex(); +} + +function fromHex() +{ + var str = d.getElementById('hexc').value; + whites[csel] = parseInt(str.substring(6), 16); + try { + cpick.color.set("#" + str.substring(0,6)); + } catch (e) { + cpick.color.set("#ffaa00"); + } + if (isNaN(whites[csel])) whites[csel] = 0; + setColor(2); +} + +function fromRgb() +{ + var r = d.getElementById('sliderR').value; + var g = d.getElementById('sliderG').value; + var b = d.getElementById('sliderB').value; + cpick.color.set(`rgb(${r},${g},${b})`); + setColor(0); +} + +function setColor(sr) { + var cd = d.getElementById('csl').children; + if (sr == 1 && cd[csel].style.backgroundColor == 'rgb(0, 0, 0)') cpick.color.setChannel('hsv', 'v', 100); + cd[csel].style.backgroundColor = cpick.color.rgbString; + if (sr != 2) whites[csel] = d.getElementById('sliderW').value; + var col = cpick.color.rgb; + var obj = {"seg": {"col": [[col.r, col.g, col.b, whites[csel]],[],[]]}}; + if (csel == 1) { + obj = {"seg": {"col": [[],[col.r, col.g, col.b, whites[csel]],[]]}}; + } else if (csel == 2) { + obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}}; + } + updateHex(); + updateRgb(); + obj.transition = parseInt(d.getElementById('cyctt').value*10); + requestJson(obj); +} + +var hc = 0; +setInterval(function(){if (!isInfo) return; hc+=18; if (hc>300) hc=0; if (hc>200)hc=306; if (hc==144) hc+=36; if (hc==108) hc+=18; +d.getElementById('heart').style.color = `hsl(${hc}, 100%, 50%)`;}, 910); + +function openGH() +{ + window.open("https://github.com/Aircoookie/WLED/wiki"); +} + +var cnfr = false; +function cnfReset() +{ + if (!cnfr) + { + var bt = d.getElementById('resetbtn'); + bt.style.color = "#f00"; + bt.innerHTML = "Confirm Reboot"; + cnfr = true; return; + } + window.location.href = "/reset"; +} + +var cnfrS = false; +function rSegs() +{ + var bt = d.getElementById('rsbtn'); + if (!cnfrS) + { + bt.style.color = "#f00"; + bt.innerHTML = "Confirm reset"; + cnfrS = true; return; + } + cnfrS = false; + bt.style.color = "#fff"; + bt.innerHTML = "Reset segments"; + var obj = {"seg":[{"start":0,"stop":ledCount,"sel":true}]}; + for (let i=1; i<=lSeg; i++){ + obj.seg.push({"stop":0}); + } + requestJson(obj); +} + +function loadPalettesData() +{ + if (palettesData) return; + const lsKey = "wledPalx"; + var palettesDataJson = localStorage.getItem(lsKey); + if (palettesDataJson) { + try { + palettesDataJson = JSON.parse(palettesDataJson); + var d = new Date(); + if (palettesDataJson && palettesDataJson.vid == lastinfo.vid) { + palettesData = palettesDataJson.p; + return; + } + } catch (e) {} + } + + palettesData = {}; + getPalettesData(0, function() { + localStorage.setItem(lsKey, JSON.stringify({ + p: palettesData, + vid: lastinfo.vid + })); + redrawPalPrev(); + }); +} + +function getPalettesData(page, callback) +{ + var url = `/json/palx?page=${page}`; + if (loc) { + url = `http://${locip}${url}`; + } + + fetch(url, { + method: 'get', + headers: { + "Content-type": "application/json; charset=UTF-8" + } + }) + .then(res => { + if (!res.ok) { + showErrorToast(); + } + return res.json(); + }) + .then(json => { + palettesData = Object.assign({}, palettesData, json.p); + if (page < json.m) { + getPalettesData(page + 1, callback); + } else { + callback(); + } + }) + .catch(function (error) { + showToast(error, true); + console.log(error); + presetError(false); + }); +} + +function search(searchField) { + var searchText = searchField.value.toUpperCase(); + searchField.parentElement.getElementsByClassName('search-cancel-icon')[0].style.display = (searchText.length < 1)?"none":"inline"; + var elements = searchField.parentElement.parentElement.querySelectorAll('.lstI'); + for (i = 0; i < elements.length; i++) { + var item = elements[i]; + var itemText = item.querySelector('.lstIname').innerText.toUpperCase(); + if (itemText.indexOf(searchText) > -1) { + item.style.display = ""; + } else { + item.style.display = "none"; + } + } +} + +function cancelSearch(ic) { + var searchField = ic.parentElement.getElementsByClassName('search')[0]; + searchField.value = ""; + search(searchField); + searchField.focus(); +} + +function expand(i,a) +{ + if (!a) expanded[i] = !expanded[i]; + d.getElementById('seg' +i).style.display = (expanded[i]) ? "block":"none"; + d.getElementById('sege' +i).style.transform = (expanded[i]) ? "rotate(180deg)":"rotate(0deg)"; + if (i > 100) { //presets + var p = i-100; + d.getElementById(`p${p}o`).style.background = (expanded[i] || p != currentPreset)?"var(--c-2)":"var(--c-6)"; + if (d.getElementById('seg' +i).innerHTML == "") { + d.getElementById('seg' +i).innerHTML = makeP(p); + var papi = papiVal(p); + d.getElementById(`p${p}api`).value = papi; + if (papi.indexOf("Please") == 0) d.getElementById(`p${p}cstgl`).checked = true; + tglCs(p); + } + } +} + +function unfocusSliders() { + d.getElementById("sliderBri").blur(); + d.getElementById("sliderSpeed").blur(); + d.getElementById("sliderIntensity").blur(); +} + +//sliding UI +const _C = d.querySelector('.container'), N = 4; + +let iSlide = 0, x0 = null, scrollS = 0, locked = false, w; + +function unify(e) { return e.changedTouches ? e.changedTouches[0] : e; } + +function hasIroClass(classList) { + for (var i = 0; i < classList.length; i++) { + var element = classList[i]; + if (element.startsWith('Iro')) return true; + } + + return false; +} + + +function lock(e) { + if (pcMode) return; + var l = e.target.classList; + var pl = e.target.parentElement.classList; + + if (l.contains('noslide') || hasIroClass(l) || hasIroClass(pl)) return; + + x0 = unify(e).clientX; + scrollS = d.getElementsByClassName("tabcontent")[iSlide].scrollTop; + + _C.classList.toggle('smooth', !(locked = true)); +} + +function move(e) { + if(!locked || pcMode) return; + var clientX = unify(e).clientX; + var dx = clientX - x0; + var s = Math.sign(dx); + var f = +(s*dx/w).toFixed(2); + + if((clientX != 0) && + (iSlide > 0 || s < 0) && (iSlide < N - 1 || s > 0) && + f > 0.12 && + d.getElementsByClassName("tabcontent")[iSlide].scrollTop == scrollS) { + _C.style.setProperty('--i', iSlide -= s); + f = 1 - f; + updateTablinks(iSlide); + } + _C.style.setProperty('--f', f); + _C.classList.toggle('smooth', !(locked = false)); + x0 = null; +} + +function size() { + w = window.innerWidth; + d.getElementById('buttonNodes').style.display = (lastinfo.ndc > 0 && w > 770) ? "block":"none"; + var h = d.getElementById('top').clientHeight; + sCol('--th', h + "px"); + sCol('--bh', d.getElementById('bot').clientHeight + "px"); + if (isLv) h -= 4; + sCol('--tp', h + "px"); + togglePcMode(); +} + +function togglePcMode(fromB = false) +{ + if (fromB) { + pcModeA = !pcModeA; + localStorage.setItem('pcm', pcModeA); + pcMode = pcModeA; + } + if (w < 1250 && !pcMode) return; + if (!fromB && ((w < 1250 && lastw < 1250) || (w >= 1250 && lastw >= 1250))) return; + openTab(0, true); + if (w < 1250) {pcMode = false;} + else if (pcModeA && !fromB) pcMode = pcModeA; + updateTablinks(0); + d.getElementById('buttonPcm').className = (pcMode) ? "active":""; + d.getElementById('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto"; + sCol('--bh', d.getElementById('bot').clientHeight + "px"); + _C.style.width = (pcMode)?'100%':'400%'; + lastw = w; +} + +function isObject(item) { + return (item && typeof item === 'object' && !Array.isArray(item)); +} + +function mergeDeep(target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + return mergeDeep(target, ...sources); +} + +size(); +_C.style.setProperty('--n', N); + +window.addEventListener('resize', size, false); + +_C.addEventListener('mousedown', lock, false); +_C.addEventListener('touchstart', lock, false); + +_C.addEventListener('mouseout', move, false); +_C.addEventListener('mouseup', move, false); +_C.addEventListener('touchend', move, false); diff --git a/wled00/data/iro.js b/wled00/data/iro.js new file mode 100644 index 000000000..f459e417c --- /dev/null +++ b/wled00/data/iro.js @@ -0,0 +1,7 @@ +/*! + * iro.js v5.3.1 + * 2016-2020 James Daniel + * Licensed under MPL 2.0 + * github.com/jaames/iro.js + */ +!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).iro=n()}(this,function(){"use strict";var k,s,n,i,o,m={},M=[],r=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i;function j(t,n){for(var i in n)t[i]=n[i];return t}function b(t){var n=t.parentNode;n&&n.removeChild(t)}function d(t,n,i){var r,e,u,o,l=arguments;if(n=j({},n),3=r/i?u=n:e=n}return n},function(t,n,i){n&&g(t.prototype,n),i&&g(t,i)}(l,[{key:"hsv",get:function(){var t=this.$;return{h:t.h,s:t.s,v:t.v}},set:function(t){var n=this.$;if(t=p({},n,t),this.onChange){var i={h:!1,v:!1,s:!1,a:!1};for(var r in n)i[r]=t[r]!=n[r];this.$=t,(i.h||i.s||i.v||i.a)&&this.onChange(this,i)}else this.$=t}},{key:"hsva",get:function(){return p({},this.$)},set:function(t){this.hsv=t}},{key:"hue",get:function(){return this.$.h},set:function(t){this.hsv={h:t}}},{key:"saturation",get:function(){return this.$.s},set:function(t){this.hsv={s:t}}},{key:"value",get:function(){return this.$.v},set:function(t){this.hsv={v:t}}},{key:"alpha",get:function(){return this.$.a},set:function(t){this.hsv=p({},this.hsv,{a:t})}},{key:"kelvin",get:function(){return l.rgbToKelvin(this.rgb)},set:function(t){this.rgb=l.kelvinToRgb(t)}},{key:"red",get:function(){return this.rgb.r},set:function(t){this.rgb=p({},this.rgb,{r:t})}},{key:"green",get:function(){return this.rgb.g},set:function(t){this.rgb=p({},this.rgb,{g:t})}},{key:"blue",get:function(){return this.rgb.b},set:function(t){this.rgb=p({},this.rgb,{b:t})}},{key:"rgb",get:function(){var t=l.hsvToRgb(this.$),n=t.r,i=t.g,r=t.b;return{r:U(n),g:U(i),b:U(r)}},set:function(t){this.hsv=p({},l.rgbToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"rgba",get:function(){return p({},this.rgb,{a:this.alpha})},set:function(t){this.rgb=t}},{key:"hsl",get:function(){var t=l.hsvToHsl(this.$),n=t.h,i=t.s,r=t.l;return{h:U(n),s:U(i),l:U(r)}},set:function(t){this.hsv=p({},l.hslToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"hsla",get:function(){return p({},this.hsl,{a:this.alpha})},set:function(t){this.hsl=t}},{key:"rgbString",get:function(){var t=this.rgb;return"rgb("+t.r+", "+t.g+", "+t.b+")"},set:function(t){var n,i,r,e,u=1;if((n=P.exec(t))?(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255)):(n=z.exec(t))&&(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255),u=K(n[4],1)),!n)throw new Error("Invalid rgb string");this.rgb={r:i,g:r,b:e,a:u}}},{key:"rgbaString",get:function(){var t=this.rgba;return"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a+")"},set:function(t){this.rgbString=t}},{key:"hexString",get:function(){var t=this.rgb;return"#"+V(t.r)+V(t.g)+V(t.b)},set:function(t){var n,i,r,e,u=255;if((n=C.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3])):(n=D.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3]),u=17*Q(n[4])):(n=F.exec(t))?(i=Q(n[1]),r=Q(n[2]),e=Q(n[3])):(n=G.exec(t))&&(i=Q(n[1]),r=Q(n[2]),e=Q(n[3]),u=Q(n[4])),!n)throw new Error("Invalid hex string");this.rgb={r:i,g:r,b:e,a:u/255}}},{key:"hex8String",get:function(){var t=this.rgba;return"#"+V(t.r)+V(t.g)+V(t.b)+V(q(255*t.a))},set:function(t){this.hexString=t}},{key:"hslString",get:function(){var t=this.hsl;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%)"},set:function(t){var n,i,r,e,u=1;if((n=H.exec(t))?(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100)):(n=$.exec(t))&&(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100),u=K(n[4],1)),!n)throw new Error("Invalid hsl string");this.hsl={h:i,s:r,l:e,a:u}}},{key:"hslaString",get:function(){var t=this.hsla;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%, "+t.a+")"},set:function(t){this.hslString=t}}]),l}();function Z(t){var n,i=t.width,r=t.sliderSize,e=t.borderWidth,u=t.handleRadius,o=t.padding,l=t.sliderShape,s="horizontal"===t.layoutDirection;return r=null!=(n=r)?n:2*o+2*u+2*e,"circle"===l?{handleStart:t.padding+t.handleRadius,handleRange:i-2*o-2*u-2*e,width:i,height:i,cx:i/2,cy:i/2,radius:i/2-e/2}:{handleStart:r/2,handleRange:i-r,radius:r/2,x:0,y:0,width:s?r:i,height:s?i:r}}function tt(t,n){var i=Z(t),r=i.width,e=i.height,u=i.handleRange,o=i.handleStart,l="horizontal"===t.layoutDirection,s=l?r/2:e/2,c=o+function(t,n){var i=n.hsva,r=n.rgb;switch(t.sliderType){case"red":return r.r/2.55;case"green":return r.g/2.55;case"blue":return r.b/2.55;case"alpha":return 100*i.a;case"kelvin":var e=t.minTemperature,u=t.maxTemperature-e,o=(n.kelvin-e)/u*100;return Math.max(0,Math.min(o,100));case"hue":return i.h/=3.6;case"saturation":return i.s;case"value":default:return i.v}}(t,n)/100*u;return l&&(c=-1*c+u+2*o),{x:l?s:c,y:l?c:s}}function nt(t){var n=t.width/2;return{width:t.width,radius:n-t.borderWidth,cx:n,cy:n}}function it(t,n,i){var r=t.wheelAngle,e=t.wheelDirection;return((n=!i&&"clockwise"===e||i&&"anticlockwise"===e?(i?180:360)-(r-n):r+n)%360+360)%360}function rt(t,n,i){var r=nt(t),e=r.cx,u=r.cy,o=t.width/2-t.padding-t.handleRadius-t.borderWidth;n=e-n,i=u-i;var l=it(t,Math.atan2(-i,-n)*(180/Math.PI)),s=Math.min(Math.sqrt(n*n+i*i),o);return{h:Math.round(l),s:Math.round(100/o*s)}}function et(t){var n=t.width,i=t.boxHeight;return{width:n,height:null!=i?i:n,radius:t.padding+t.handleRadius}}function ut(t,n,i){var r=et(t),e=r.width,u=r.height,o=r.radius,l=(n-o)/(e-2*o)*100,s=(i-o)/(u-2*o)*100;return{s:Math.max(0,Math.min(l,100)),v:Math.max(0,Math.min(100-s,100))}}function ot(t){X=X||document.getElementsByTagName("base");var n=window.navigator.userAgent,i=/^((?!chrome|android).)*safari/i.test(n),r=/iPhone|iPod|iPad/i.test(n),e=window.location;return(i||r)&&0 + + + + + + WLED Live Preview + + + +
+ + + \ No newline at end of file diff --git a/wled00/data/msg.htm b/wled00/data/msg.htm index 93179399e..e25aeda00 100644 --- a/wled00/data/msg.htm +++ b/wled00/data/msg.htm @@ -1,56 +1,36 @@ - + + + WLED Message - + + -

Sample message.

-Sample detail. +

Sample Message.

+ Sample Detail. - - \ No newline at end of file + + \ No newline at end of file diff --git a/wled00/data/rangetouch.js b/wled00/data/rangetouch.js new file mode 100644 index 000000000..ceaef5377 --- /dev/null +++ b/wled00/data/rangetouch.js @@ -0,0 +1,8 @@ +// ========================================================================== +// rangetouch.js v2.0.1 +// Making work on touch devices +// https://github.com/sampotts/rangetouch +// License: The MIT License (MIT) +// ========================================================================== +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define("RangeTouch",t):(e=e||self).RangeTouch=t()}(this,(function(){"use strict";function e(e,t){for(var n=0;nt){var n=function(e){var t="".concat(e).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return t?Math.max(0,(t[1]?t[1].length:0)-(t[2]?+t[2]:0)):0}(t);return parseFloat(e.toFixed(n))}return Math.round(e/t)*t}return function(){function t(e,n){(function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")})(this,t),m(e)?this.element=e:d(e)&&(this.element=document.querySelector(e)),m(this.element)&&p(this.element.rangeTouch)&&(this.config=r({},i,{},n),this.init())}return n=t,c=[{key:"setup",value:function(e){var n=1(n=100/l.width*(i.clientX-l.left))?n=0:100n?n-=(100-2*n)*a:50 - - + + WLED Settings + + +
+
+
+

Imma firin ma lazer (if it has DMX support)

+ +Proxy Universe from E1.31 to DMX (0=disabled)
+This will disable the LED data output to DMX configurable below

+Number of fixtures is taken from LED config page
+ +Channels per fixture (15 max):
+Start channel:
+Spacing between start channels: [ info ]
+ +
+DMX fixtures start LED: +

Channel functions

+
+
+
+ + diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 190d6b536..fd8308222 100644 Binary files a/wled00/data/settings_leds.htm and b/wled00/data/settings_leds.htm differ diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index 478b1bf03..f9d1a3a53 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -1,7 +1,8 @@ - + + Misc Settings @@ -88,12 +45,12 @@
Enable ArduinoOTA:

About

- WLED version 0.9.0

- Contributors, dependencies and special thanks
+ WLED version ##VERSION##

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

- (c) 2016-2019 Christian Schwinne
- Licensed under the MIT license

- Server message: Response error!
+ (c) 2016-2021 Christian Schwinne
+ Licensed under the MIT license

+ Server message: Response error!
diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index b4bcf0599..37b56e6ae 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -1,140 +1,116 @@ - - - - Sync Settings - - - - -
-
-
-

Sync setup

-

Button setup

- On/Off button enabled:
- Infrared receiver enabled:
- IR info -

WLED Sync UDP Broadcast

- UDP Port:
- Receive Brightness, Color, and Effects
- Send notifications on direct change:
- Send notifications on button press:
- Send Alexa notifications:
- Send Philips Hue change notifications:
- Send Macro notifications:
- Send notifications twice: -

Realtime

- Receive UDP realtime:

- E1.31 (sACN)
- Multicast mode:
- E1.31 start universe:
- Reboot required. Check out LedFx!

- DMX start address:
- DMX mode: disabled
- Single RGB (3 Channels for all LEDs: Red Green Blue)
- Single DRGB (4 Channels for all LEDs: Dimmer Red Green Blue)
- Effect (11 Channels parametrizing Effects: Dimmer Effect Speed Intensity Palette PriRed PriGreen PriBlue SecRed SecGreen SecBlue)
- Multiple RGB (3 Channels for each LED: Red Green Blue)
- Multiple DRGB (1+3 Channels for each LED: Dimmer Red1 Green1 Blue1 Red2 Green2 Blue2...)
- Reboot required. Check out LedFx!

- Timeout: ms
- Force max brightness:
- Disable realtime gamma correction:
- Realtime LED offset: -

Alexa Voice Assistant

- Emulate Alexa device:
- Alexa invocation name: -

Blynk

- Blynk, MQTT and Hue sync all connect to external hosts!
- This may impact the responsiveness of the ESP8266.

- For best results, only use one of these services at a time.
- (alternatively, connect a second ESP to them and use the UDP sync)

- Device Auth token:
- Clear the token field to disable. Setup info -

MQTT

- Enable MQTT:
- Broker:
- Port:
- The MQTT credentials are sent over an unsecured connection.
- Never use the MQTT password for another service!

- Username:
- Password:
- Client ID:
- Device Topic:
- Group Topic:
- MQTT info -

Philips Hue

- You can find the bridge IP and the light number in the 'About' section of the hue app.
- Poll Hue light every ms:
- Then, receive On/Off, Brightness, and Color
- Hue Bridge IP:
- . - . - . -
- Press the pushlink button on the bridge, after that save this page!
- (when first connecting)
- Hue status: Internal ESP Error!
- -
+Sync Settings + + + +
+
+
+

Sync setup

+

Button setup

+On/Off button enabled:
+Infrared remote: +
+IR info +

WLED Broadcast

+UDP Port:
+2nd Port:
+Receive Brightness, Color, and Effects
+Send notifications on direct change:
+Send notifications on button press or IR:
+Send Alexa notifications:
+Send Philips Hue change notifications:
+Send Macro notifications:
+Send notifications twice:
+Reboot required to apply changes. +

Instance List

+Enable instance list:
+Make this instance discoverable:
+

Realtime

+Receive UDP realtime:

+Network DMX input
+Type: +
+
Port:
+Multicast:
+Start universe:
+Reboot required. Check out LedFx!
+Skip out-of-sequence packets:
+DMX start address:
+DMX mode: +
+E1.31 info
+Timeout: ms
+Force max brightness:
+Disable realtime gamma correction:
+Realtime LED offset: +

Alexa Voice Assistant

+Emulate Alexa device:
+Alexa invocation name: +

Blynk

+Blynk, MQTT and Hue sync all connect to external hosts!
+This may impact the responsiveness of the ESP8266.

+For best results, only use one of these services at a time.
+(alternatively, connect a second ESP to them and use the UDP sync)

+Host: +Port:
+Device Auth token:
+Clear the token field to disable. Setup info +

MQTT

+Enable MQTT:
+Broker: +Port:
+The MQTT credentials are sent over an unsecured connection.
+Never use the MQTT password for another service!

+Username:
+Password:
+Client ID:
+Device Topic:
+Group Topic:
+Reboot required to apply changes. MQTT info +

Philips Hue

+You can find the bridge IP and the light number in the 'About' section of the hue app.
+Poll Hue light every ms:
+Then, receive On/Off, Brightness, and Color
+Hue Bridge IP:
+ . + . + . +
+Press the pushlink button on the bridge, after that save this page!
+(when first connecting)
+Hue status: Disabled in this build
+ +
\ No newline at end of file diff --git a/wled00/data/settings_time.htm b/wled00/data/settings_time.htm index ff2ce4807..9924530f3 100644 --- a/wled00/data/settings_time.htm +++ b/wled00/data/settings_time.htm @@ -1,7 +1,8 @@ - + + Time Settings @@ -159,7 +103,12 @@ - + + + + + +
UTC offset: seconds (max. 18 hours)
Current local time is unknown. @@ -186,31 +135,17 @@ Countdown Goal:
Year: 20 Month: Day:
Hour: Minute: Second:
-

Advanced Macros

- Define API macros here:
- 1:
- 2:
- 3:
- 4:
- 5:
- 6:
- 7:
- 8:
- 9:
- 10:
- 11:
- 12:
- 13:
- 14:
- 15:
- 16:

- Use 0 for the default action instead of a macro
- Boot Macro:
- Alexa On/Off Macros:
- Button Macro: Long Press:
- Countdown-Over Macro:
- Timed-Light-Over Macro:
- Time-Controlled Macros:
+

Macro presets

+ Macros have moved!
+ Presets now also can be used as macros to save both JSON and HTTP API commands.
+ Just enter the preset id below!
+ Use 0 for the default action instead of a preset
+ Alexa On/Off Preset:
+ Button short press Preset:
+ Long Press: Double press:
+ Countdown-Over Preset:
+ Timed-Light-Over Presets:
+

Time-controlled presets


diff --git a/wled00/data/settings_ui.htm b/wled00/data/settings_ui.htm index 372532647..a5fae3c66 100644 --- a/wled00/data/settings_ui.htm +++ b/wled00/data/settings_ui.htm @@ -1,16 +1,154 @@ - + + UI Settings
+
-
+
+ +
+

Web Setup

Server description:
- Sync button toggles both send and receive:

-
+ Sync button toggles both send and receive:
+ The following UI customization settings are unique both to the WLED device and this browser.
+ You will need to set them again if using a different browser, device or WLED IP address.
+ Refresh the main UI to apply changes.

+ +
Loading settings...
+ +

UI Appearance

+ :
+ :
+ :
+ I hate dark mode:
+ + :
+ :
+ :
+ BG image URL:
+ Random BG image:
+ +
\ No newline at end of file diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 62ef153de..31924526e 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -1,6 +1,7 @@ - + + WiFi Settings
-
+

WiFi setup

Connect to existing network

Network name (SSID, empty to not connect):

@@ -94,7 +51,7 @@

Configure Access Point

AP SSID (leave empty for no AP):

Hide AP name:
- AP password (leave empty for open):

+ AP password (leave empty for open):

Access Point WiFi channel:
AP opens:
- AP IP: Not active
- + AP IP: Not active
+

Experimental

+ Disable WiFi sleep:
+ Can help with connectivity issues.
+ Do not enable if WiFi is working correctly, increases power consumption.
+
+

Ethernet Type

+

+
+
\ No newline at end of file diff --git a/wled00/data/style.css b/wled00/data/style.css new file mode 100644 index 000000000..83404355f --- /dev/null +++ b/wled00/data/style.css @@ -0,0 +1,48 @@ +body { + font-family: Verdana, sans-serif; + text-align: center; + background: #222; + color: #fff; + line-height: 200%; + margin: 0; +} +hr { + border-color: #666; +} +button { + background: #333; + color: #fff; + font-family: Verdana, sans-serif; + border: 0.3ch solid #333; + display: inline-block; + font-size: 20px; + margin: 8px; + margin-top: 12px; + cursor: pointer; +} +.helpB { + text-align: left; + position: absolute; + width: 60px; +} +input { + background: #333; + color: #fff; + font-family: Verdana, sans-serif; + border: 0.5ch solid #333; +} +input[type="number"] { + width: 4em; +} +select { + background: #333; + color: #fff; + font-family: Verdana, sans-serif; + border: 0.5ch solid #333; +} +td { + padding: 2px; +} +.d5 { + width: 4.5em !important; +} diff --git a/wled00/data/update.htm b/wled00/data/update.htm index ed70eb168..f3a0a2211 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -1,4 +1,52 @@ -WLED Message - -

WLED Software Update

Installed version: 0.8.5-dev
Download the latest binary:

\ No newline at end of file + + + + + WLED Update + + + + + +

WLED Software Update

+
+ Installed version: ##VERSION##
+ Download the latest binary: +
+
+
+
+
Updating...
Please do not close or refresh the page :)
+ + + \ No newline at end of file diff --git a/wled00/data/usermod.htm b/wled00/data/usermod.htm new file mode 100644 index 000000000..3f7734a72 --- /dev/null +++ b/wled00/data/usermod.htm @@ -0,0 +1,6 @@ + + + +No usermod custom web page set. + + \ No newline at end of file diff --git a/wled00/data/welcome.htm b/wled00/data/welcome.htm index 0542aff54..f0e1b463d 100644 --- a/wled00/data/welcome.htm +++ b/wled00/data/welcome.htm @@ -3,13 +3,13 @@ - - WLED Setup + + Welcome! - - - - -

- + +

Welcome to WLED!

Thank you for installing my application!

-If you encounter a bug or have a question/feature suggestion, feel free to open a GitHub issue!

Next steps:

Connect the module to your local WiFi here!

Just trying this out in AP mode?
- - +
+
\ No newline at end of file diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp new file mode 100644 index 000000000..7fef5666a --- /dev/null +++ b/wled00/dmx.cpp @@ -0,0 +1,68 @@ +#include "wled.h" + +/* + * Support for DMX via MAX485. + * Change the output pin in src/dependencies/ESPDMX.cpp if needed. + * Library from: + * https://github.com/Rickgg/ESP-Dmx + */ + +#ifdef WLED_ENABLE_DMX + +void handleDMX() +{ + // don't act, when in DMX Proxy mode + if (e131ProxyUniverse != 0) return; + + // TODO: calculate brightness manually if no shutter channel is set + + uint8_t brightness = strip.getBrightness(); + + for (int i = DMXStartLED; i < ledCount; i++) { // uses the amount of LEDs as fixture count + + uint32_t in = strip.getPixelColor(i); // get the colors for the individual fixtures as suggested by Aircoookie in issue #462 + byte w = in >> 24 & 0xFF; + byte r = in >> 16 & 0xFF; + byte g = in >> 8 & 0xFF; + byte b = in & 0xFF; + + int DMXFixtureStart = DMXStart + (DMXGap * (i - DMXStartLED)); + for (int j = 0; j < DMXChannels; j++) { + int DMXAddr = DMXFixtureStart + j; + switch (DMXFixtureMap[j]) { + case 0: // Set this channel to 0. Good way to tell strobe- and fade-functions to fuck right off. + dmx.write(DMXAddr, 0); + break; + case 1: // Red + dmx.write(DMXAddr, r); + break; + case 2: // Green + dmx.write(DMXAddr, g); + break; + case 3: // Blue + dmx.write(DMXAddr, b); + break; + case 4: // White + dmx.write(DMXAddr, w); + break; + case 5: // Shutter channel. Controls the brightness. + dmx.write(DMXAddr, brightness); + break; + case 6: // Sets this channel to 255. Like 0, but more wholesome. + dmx.write(DMXAddr, 255); + break; + } + } + } + + dmx.update(); // update the DMX bus +} + +void initDMX() { + dmx.init(512); // initialize with bus length +} + +#else +void handleDMX() {} +void initDMX() {} +#endif diff --git a/wled00/e131.cpp b/wled00/e131.cpp new file mode 100644 index 000000000..9ca092a9a --- /dev/null +++ b/wled00/e131.cpp @@ -0,0 +1,209 @@ +#include "wled.h" + +#define MAX_3_CH_LEDS_PER_UNIVERSE 170 +#define MAX_4_CH_LEDS_PER_UNIVERSE 128 +#define MAX_CHANNELS_PER_UNIVERSE 512 + +/* + * E1.31 handler + */ + +//DDP protocol support, called by handleE131Packet +//handles RGB data only +void handleDDPPacket(e131_packet_t* p) { + int lastPushSeq = e131LastSequenceNumber[0]; + + //reject late packets belonging to previous frame (assuming 4 packets max. before push) + if (e131SkipOutOfSequence && lastPushSeq) { + int sn = p->sequenceNum & 0xF; + if (sn) { + if (lastPushSeq > 5) { + if (sn > (lastPushSeq -5) && sn < lastPushSeq) return; + } else { + if (sn > (10 + lastPushSeq) || sn < lastPushSeq) return; + } + } + } + + uint32_t start = htonl(p->channelOffset) /3; + start += DMXAddress /3; + uint16_t stop = start + htons(p->dataLen) /3; + uint8_t* data = p->data; + uint16_t c = 0; + if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later + + realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); + + for (uint16_t i = start; i < stop; i++) { + setRealtimePixel(i, data[c], data[c+1], data[c+2], 0); + c+=3; + } + + bool push = p->flags & DDP_PUSH_FLAG; + if (push) { + e131NewData = true; + byte sn = p->sequenceNum & 0xF; + if (sn) e131LastSequenceNumber[0] = sn; + } +} + +//E1.31 and Art-Net protocol support +void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ + + uint16_t uni = 0, dmxChannels = 0; + uint8_t* e131_data = nullptr; + uint8_t seq = 0, mde = REALTIME_MODE_E131; + + if (protocol == P_ARTNET) + { + uni = p->art_universe; + dmxChannels = htons(p->art_length); + e131_data = p->art_data; + seq = p->art_sequence_number; + mde = REALTIME_MODE_ARTNET; + } else if (protocol == P_E131) { + uni = htons(p->universe); + dmxChannels = htons(p->property_value_count) -1; + e131_data = p->property_values; + seq = p->sequence_number; + } else { //DDP + realtimeIP = clientIP; + handleDDPPacket(p); + return; + } + + #ifdef WLED_ENABLE_DMX + // does not act on out-of-order packets yet + if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) { + for (uint16_t i = 1; i <= dmxChannels; i++) + dmx.write(i, e131_data[i]); + dmx.update(); + } + #endif + + // only listen for universes we're handling & allocated memory + if (uni >= (e131Universe + E131_MAX_UNIVERSE_COUNT)) return; + + uint8_t previousUniverses = uni - e131Universe; + + if (e131SkipOutOfSequence) + if (seq < e131LastSequenceNumber[uni-e131Universe] && seq > 20 && e131LastSequenceNumber[uni-e131Universe] < 250){ + DEBUG_PRINT("skipping E1.31 frame (last seq="); + DEBUG_PRINT(e131LastSequenceNumber[uni-e131Universe]); + DEBUG_PRINT(", current seq="); + DEBUG_PRINT(seq); + DEBUG_PRINT(", universe="); + DEBUG_PRINT(uni); + DEBUG_PRINTLN(")"); + return; + } + e131LastSequenceNumber[uni-e131Universe] = seq; + + // update status info + realtimeIP = clientIP; + byte wChannel = 0; + + switch (DMXMode) { + case DMX_MODE_DISABLED: + return; // nothing to do + break; + + case DMX_MODE_SINGLE_RGB: + if (uni != e131Universe) return; + if (dmxChannels-DMXAddress+1 < 3) return; + realtimeLock(realtimeTimeoutMs, mde); + if (realtimeOverride) return; + wChannel = (dmxChannels-DMXAddress+1 > 3) ? e131_data[DMXAddress+3] : 0; + for (uint16_t i = 0; i < ledCount; i++) + setRealtimePixel(i, e131_data[DMXAddress+0], e131_data[DMXAddress+1], e131_data[DMXAddress+2], wChannel); + break; + + case DMX_MODE_SINGLE_DRGB: + if (uni != e131Universe) return; + if (dmxChannels-DMXAddress+1 < 4) return; + realtimeLock(realtimeTimeoutMs, mde); + if (realtimeOverride) return; + wChannel = (dmxChannels-DMXAddress+1 > 4) ? e131_data[DMXAddress+4] : 0; + if (DMXOldDimmer != e131_data[DMXAddress+0]) { + DMXOldDimmer = e131_data[DMXAddress+0]; + bri = e131_data[DMXAddress+0]; + strip.setBrightness(bri); + } + for (uint16_t i = 0; i < ledCount; i++) + setRealtimePixel(i, e131_data[DMXAddress+1], e131_data[DMXAddress+2], e131_data[DMXAddress+3], wChannel); + break; + + case DMX_MODE_EFFECT: + if (uni != e131Universe) return; + if (dmxChannels-DMXAddress+1 < 11) return; + if (DMXOldDimmer != e131_data[DMXAddress+0]) { + DMXOldDimmer = e131_data[DMXAddress+0]; + bri = e131_data[DMXAddress+0]; + } + if (e131_data[DMXAddress+1] < MODE_COUNT) + effectCurrent = e131_data[DMXAddress+ 1]; + effectSpeed = e131_data[DMXAddress+ 2]; // flickers + effectIntensity = e131_data[DMXAddress+ 3]; + effectPalette = e131_data[DMXAddress+ 4]; + col[0] = e131_data[DMXAddress+ 5]; + col[1] = e131_data[DMXAddress+ 6]; + col[2] = e131_data[DMXAddress+ 7]; + colSec[0] = e131_data[DMXAddress+ 8]; + colSec[1] = e131_data[DMXAddress+ 9]; + colSec[2] = e131_data[DMXAddress+10]; + if (dmxChannels-DMXAddress+1 > 11) + { + col[3] = e131_data[DMXAddress+11]; //white + colSec[3] = e131_data[DMXAddress+12]; + } + transitionDelayTemp = 0; // act fast + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); // don't send UDP + return; // don't activate realtime live mode + break; + + case DMX_MODE_MULTIPLE_DRGB: + case DMX_MODE_MULTIPLE_RGB: + case DMX_MODE_MULTIPLE_RGBW: + { + realtimeLock(realtimeTimeoutMs, mde); + bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); + const uint16_t dmxChannelsPerLed = is4Chan ? 4 : 3; + const uint16_t ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE; + if (realtimeOverride) return; + uint16_t previousLeds, dmxOffset; + if (previousUniverses == 0) { + if (dmxChannels-DMXAddress < 1) return; + dmxOffset = DMXAddress; + previousLeds = 0; + // First DMX address is dimmer in DMX_MODE_MULTIPLE_DRGB mode. + if (DMXMode == DMX_MODE_MULTIPLE_DRGB) { + strip.setBrightness(e131_data[dmxOffset++]); + } + } else { + // All subsequent universes start at the first channel. + dmxOffset = (protocol == P_ARTNET) ? 0 : 1; + uint16_t ledsInFirstUniverse = (MAX_CHANNELS_PER_UNIVERSE - DMXAddress) / dmxChannelsPerLed; + previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse; + } + uint16_t ledsTotal = previousLeds + (dmxChannels - dmxOffset +1) / dmxChannelsPerLed; + if (!is4Chan) { + for (uint16_t i = previousLeds; i < ledsTotal; i++) { + setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0); + dmxOffset+=3; + } + } else { + for (uint16_t i = previousLeds; i < ledsTotal; i++) { + setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], e131_data[dmxOffset+3]); + dmxOffset+=4; + } + } + break; + } + default: + DEBUG_PRINTLN(F("unknown E1.31 DMX mode")); + return; // nothing to do + break; + } + + e131NewData = true; +} diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h new file mode 100644 index 000000000..28621fd6f --- /dev/null +++ b/wled00/fcn_declare.h @@ -0,0 +1,255 @@ +#ifndef WLED_FCN_DECLARE_H +#define WLED_FCN_DECLARE_H +#include +#include "src/dependencies/espalexa/EspalexaDevice.h" +#include "src/dependencies/e131/ESPAsyncE131.h" + +/* + * All globally accessible functions are declared here + */ + +//alexa.cpp +void onAlexaChange(EspalexaDevice* dev); +void alexaInit(); +void handleAlexa(); +void onAlexaChange(EspalexaDevice* dev); + +//blynk.cpp +void initBlynk(const char* auth, const char* host, uint16_t port); +void handleBlynk(); +void updateBlynk(); + +//button.cpp +void shortPressAction(); +bool isButtonPressed(); +void handleButton(); +void handleIO(); + +//cfg.cpp +void deserializeConfig(); +bool deserializeConfigSec(); +void serializeConfig(); +void serializeConfigSec(); + +//colors.cpp +void colorFromUint32(uint32_t in, bool secondary = false); +void colorFromUint24(uint32_t in, bool secondary = false); +uint32_t colorFromRgbw(byte* rgbw); +void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0); +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb +void colorKtoRGB(uint16_t kelvin, byte* rgb); +void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb + +void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO +void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO + +void colorFromDecOrHexString(byte* rgb, char* in); +bool colorFromHexString(byte* rgb, const char* in); +void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) + +//dmx.cpp +void initDMX(); +void handleDMX(); + +//e131.cpp +void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); + +//file.cpp +bool handleFileRead(AsyncWebServerRequest*, String path); +bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content); +bool writeObjectToFile(const char* file, const char* key, JsonDocument* content); +bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest); +bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest); +void updateFSInfo(); +void closeFile(); + +//hue.cpp +void handleHue(); +void reconnectHue(); +void onHueError(void* arg, AsyncClient* client, int8_t error); +void onHueConnect(void* arg, AsyncClient* client); +void sendHuePoll(); +void onHueData(void* arg, AsyncClient* client, void *data, size_t len); + +//ir.cpp +bool decodeIRCustom(uint32_t code); +void applyRepeatActions(); +void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); +void changeEffectSpeed(int8_t amount); +void changeEffectIntensity(int8_t amount); +void decodeIR(uint32_t code); +void decodeIR24(uint32_t code); +void decodeIR24OLD(uint32_t code); +void decodeIR24CT(uint32_t code); +void decodeIR40(uint32_t code); +void decodeIR44(uint32_t code); +void decodeIR21(uint32_t code); +void decodeIR6(uint32_t code); +void decodeIR9(uint32_t code); + +void initIR(); +void handleIR(); + +//json.cpp +#include "ESPAsyncWebServer.h" +#include "src/dependencies/json/ArduinoJson-v6.h" +#include "src/dependencies/json/AsyncJson-v6.h" +#include "FX.h" + +void deserializeSegment(JsonObject elem, byte it); +bool deserializeState(JsonObject root); +void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); +void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true); +void serializeInfo(JsonObject root); +void serveJson(AsyncWebServerRequest* request); +bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); + +//led.cpp +void setValuesFromMainSeg(); +void resetTimebase(); +void toggleOnOff(); +void setAllLeds(); +void setLedsStandard(); +bool colorChanged(); +void colorUpdated(int callMode); +void updateInterfaces(uint8_t callMode); +void handleTransitions(); +void handleNightlight(); +byte scaledBri(byte in); + +//lx_parser.cpp +bool parseLx(int lxValue, byte* rgbw); +void parseLxJson(int lxValue, byte segId, bool secondary); + +//mqtt.cpp +bool initMqtt(); +void publishMqtt(); + +//ntp.cpp +void handleNetworkTime(); +void sendNTPPacket(); +bool checkNTPResponse(); +void updateLocalTime(); +void getTimeString(char* out); +bool checkCountdown(); +void setCountdown(); +byte weekdayMondayFirst(); +void checkTimers(); + +//overlay.cpp +void initCronixie(); +void handleOverlays(); +void handleOverlayDraw(); +void _overlayAnalogCountdown(); +void _overlayAnalogClock(); + +byte getSameCodeLength(char code, int index, char const cronixieDisplay[]); +void setCronixie(); +void _overlayCronixie(); +void _drawOverlayCronixie(); + +//playlist.cpp +void unloadPlaylist(); +void loadPlaylist(JsonObject playlistObject); +void handlePlaylist(); + +//presets.cpp +bool applyPreset(byte index); +void savePreset(byte index, bool persist = true, const char* pname = nullptr, JsonObject saveobj = JsonObject()); +void deletePreset(byte index); + +//set.cpp +void _setRandomColor(bool _sec,bool fromButton=false); +bool isAsterisksOnly(const char* str, byte maxLen); +void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); +bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=true); +int getNumVal(const String* req, uint16_t pos); +bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); + +//udp.cpp +void notify(byte callMode, bool followUp=false); +void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); +void handleNotifications(); +void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); +void refreshNodeList(); +void sendSysInfoUDP(); + +//um_manager.cpp +class Usermod { + public: + virtual void loop() {} + virtual void setup() {} + virtual void connected() {} + virtual void addToJsonState(JsonObject& obj) {} + virtual void addToJsonInfo(JsonObject& obj) {} + virtual void readFromJsonState(JsonObject& obj) {} + virtual void addToConfig(JsonObject& obj) {} + virtual void readFromConfig(JsonObject& obj) {} + virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} +}; + +class UsermodManager { + private: + Usermod* ums[WLED_MAX_USERMODS]; + byte numMods = 0; + + public: + void loop(); + + void setup(); + void connected(); + + void addToJsonState(JsonObject& obj); + void addToJsonInfo(JsonObject& obj); + void readFromJsonState(JsonObject& obj); + + void addToConfig(JsonObject& obj); + void readFromConfig(JsonObject& obj); + + bool add(Usermod* um); + Usermod* lookup(uint16_t mod_id); + byte getModCount(); +}; + +//usermods_list.cpp +void registerUsermods(); + +//usermod.cpp +void userSetup(); +void userConnected(); +void userLoop(); + +//wled_eeprom.cpp +void applyMacro(byte index); +void deEEP(); +void deEEPSettings(); +void clearEEPROM(); + +//wled_serial.cpp +void handleSerial(); + +//wled_server.cpp +bool isIp(String str); +bool captivePortal(AsyncWebServerRequest *request); +void initServer(); +void serveIndexOrWelcome(AsyncWebServerRequest *request); +void serveIndex(AsyncWebServerRequest* request); +String msgProcessor(const String& var); +void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255); +String settingsProcessor(const String& var); +String dmxProcessor(const String& var); +void serveSettings(AsyncWebServerRequest* request, bool post = false); + +//ws.cpp +void handleWs(); +void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); +void sendDataWs(AsyncWebSocketClient * client = nullptr); + +//xml.cpp +void XML_response(AsyncWebServerRequest *request, char* dest = nullptr); +void URL_response(AsyncWebServerRequest *request); +void sappend(char stype, const char* key, int val); +void sappends(char stype, const char* key, char* val); +void getSettingsJS(byte subPage, char* dest); + +#endif diff --git a/wled00/file.cpp b/wled00/file.cpp new file mode 100644 index 000000000..e3aac3ae0 --- /dev/null +++ b/wled00/file.cpp @@ -0,0 +1,414 @@ +#include "wled.h" + +/* + * Utility for SPIFFS filesystem + */ + +#ifdef ARDUINO_ARCH_ESP32 //FS info bare IDF function until FS wrapper is available for ESP32 +#if WLED_FS != LITTLEFS + #include "esp_spiffs.h" +#endif +#endif + +#define FS_BUFSIZE 256 + +/* + * Structural requirements for files managed by writeObjectToFile() and readObjectFromFile() utilities: + * 1. File must be a string representation of a valid JSON object + * 2. File must have '{' as first character + * 3. There must not be any additional characters between a root-level key and its value object (e.g. space, tab, newline) + * 4. There must not be any characters between an root object-separating ',' and the next object key string + * 5. There may be any number of spaces, tabs, and/or newlines before such object-separating ',' + * 6. There must not be more than 5 consecutive spaces at any point except for those permitted in condition 5 + * 7. If it is desired to delete the first usable object (e.g. preset file), a dummy object '"0":{}' is inserted at the beginning. + * It shall be disregarded by receiving software. + * The reason for it is that deleting the first preset would require special code to handle commas between it and the 2nd preset + */ + +// There are no consecutive spaces longer than this in the file, so if more space is required, findSpace() can return false immediately +// Actual space may be lower +uint16_t knownLargestSpace = UINT16_MAX; + +File f; + +//wrapper to find out how long closing takes +void closeFile() { + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINT(F("Close -> ")); + uint32_t s = millis(); + #endif + f.close(); + DEBUGFS_PRINTF("took %d ms\n", millis() - s); + doCloseFile = false; +} + +//find() that reads and buffers data from file stream in 256-byte blocks. +//Significantly faster, f.find(key) can take SECONDS for multi-kB files +bool bufferedFind(const char *target, bool fromStart = true) { + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINT("Find "); + DEBUGFS_PRINTLN(target); + uint32_t s = millis(); + #endif + + if (!f || !f.size()) return false; + size_t targetLen = strlen(target); + + size_t index = 0; + uint16_t bufsize = 0, count = 0; + byte buf[FS_BUFSIZE]; + if (fromStart) f.seek(0); + + while (f.position() < f.size() -1) { + bufsize = f.read(buf, FS_BUFSIZE); + count = 0; + while (count < bufsize) { + if(buf[count] != target[index]) + index = 0; // reset index if any char does not match + + if(buf[count] == target[index]) { + if(++index >= targetLen) { // return true if all chars in the target match + f.seek((f.position() - bufsize) + count +1); + DEBUGFS_PRINTF("Found at pos %d, took %d ms", f.position(), millis() - s); + return true; + } + } + count++; + } + } + DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); + return false; +} + +//find empty spots in file stream in 256-byte blocks. +bool bufferedFindSpace(uint16_t targetLen, bool fromStart = true) { + + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTF("Find %d spaces\n", targetLen); + uint32_t s = millis(); + #endif + + if (knownLargestSpace < targetLen) { + DEBUGFS_PRINT(F("No match, KLS ")); + DEBUGFS_PRINTLN(knownLargestSpace); + return false; + } + + if (!f || !f.size()) return false; + + uint16_t index = 0; + uint16_t bufsize = 0, count = 0; + byte buf[FS_BUFSIZE]; + if (fromStart) f.seek(0); + + while (f.position() < f.size() -1) { + bufsize = f.read(buf, FS_BUFSIZE); + count = 0; + + while (count < bufsize) { + if(buf[count] == ' ') { + if(++index >= targetLen) { // return true if space long enough + if (fromStart) { + f.seek((f.position() - bufsize) + count +1 - targetLen); + knownLargestSpace = UINT16_MAX; //there may be larger spaces after, so we don't know + } + DEBUGFS_PRINTF("Found at pos %d, took %d ms", f.position(), millis() - s); + return true; + } + } else { + if (!fromStart) return false; + if (index) { + if (knownLargestSpace < index || knownLargestSpace == UINT16_MAX) knownLargestSpace = index; + index = 0; // reset index if not space + } + } + + count++; + } + } + DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); + return false; +} + +//find the closing bracket corresponding to the opening bracket at the file pos when calling this function +bool bufferedFindObjectEnd() { + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTLN(F("Find obj end")); + uint32_t s = millis(); + #endif + + if (!f || !f.size()) return false; + + uint16_t objDepth = 0; //num of '{' minus num of '}'. return once 0 + uint16_t bufsize = 0, count = 0; + //size_t start = f.position(); + byte buf[FS_BUFSIZE]; + + while (f.position() < f.size() -1) { + bufsize = f.read(buf, FS_BUFSIZE); + count = 0; + + while (count < bufsize) { + if (buf[count] == '{') objDepth++; + if (buf[count] == '}') objDepth--; + if (objDepth == 0) { + f.seek((f.position() - bufsize) + count +1); + DEBUGFS_PRINTF("} at pos %d, took %d ms", f.position(), millis() - s); + return true; + } + count++; + } + } + DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); + return false; +} + +//fills n bytes from current file pos with ' ' characters +void writeSpace(uint16_t l) +{ + byte buf[FS_BUFSIZE]; + memset(buf, ' ', FS_BUFSIZE); + + while (l > 0) { + uint16_t block = (l>FS_BUFSIZE) ? FS_BUFSIZE : l; + f.write(buf, block); + l -= block; + } + + if (knownLargestSpace < l) knownLargestSpace = l; +} + +bool appendObjectToFile(const char* key, JsonDocument* content, uint32_t s, uint32_t contentLen = 0) +{ + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTLN(F("Append")); + uint32_t s1 = millis(); + #endif + uint32_t pos = 0; + if (!f) return false; + + if (f.size() < 3) { + char init[10]; + strcpy_P(init, PSTR("{\"0\":{}}")); + f.print(init); + } + + if (content->isNull()) { + doCloseFile = true; + return true; //nothing to append + } + + //if there is enough empty space in file, insert there instead of appending + if (!contentLen) contentLen = measureJson(*content); + DEBUGFS_PRINTF("CLen %d\n", contentLen); + if (bufferedFindSpace(contentLen + strlen(key) + 1)) { + if (f.position() > 2) f.write(','); //add comma if not first object + f.print(key); + serializeJson(*content, f); + DEBUGFS_PRINTF("Inserted, took %d ms (total %d)", millis() - s1, millis() - s); + doCloseFile = true; + return true; + } + + //not enough space, append at end + + //permitted space for presets exceeded + updateFSInfo(); + + if (f.size() + 9000 > (fsBytesTotal - fsBytesUsed)) { //make sure there is enough space to at least copy the file once + errorFlag = ERR_FS_QUOTA; + doCloseFile = true; + return false; + } + + //check if last character in file is '}' (typical) + uint32_t eof = f.size() -1; + f.seek(eof, SeekSet); + if (f.read() == '}') pos = eof; + + if (pos == 0) //not found + { + DEBUGFS_PRINTLN("not }"); + f.seek(0); + while (bufferedFind("}",false)) //find last closing bracket in JSON if not last char + { + pos = f.position(); + } + if (pos > 0) pos--; + } + DEBUGFS_PRINT("pos "); DEBUGFS_PRINTLN(pos); + if (pos > 2) + { + f.seek(pos, SeekSet); + f.write(','); + } else { //file content is not valid JSON object + f.seek(0, SeekSet); + f.print('{'); //start JSON + } + + f.print(key); + + //Append object + serializeJson(*content, f); + f.write('}'); + + doCloseFile = true; + DEBUGFS_PRINTF("Appended, took %d ms (total %d)", millis() - s1, millis() - s); + return true; +} + +bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content) +{ + char objKey[10]; + sprintf(objKey, "\"%d\":", id); + return writeObjectToFile(file, objKey, content); +} + +bool writeObjectToFile(const char* file, const char* key, JsonDocument* content) +{ + uint32_t s = 0; //timing + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTF("Write to %s with key %s >>>\n", file, (key==nullptr)?"nullptr":key); + serializeJson(*content, Serial); DEBUGFS_PRINTLN(); + s = millis(); + #endif + + uint32_t pos = 0; + f = WLED_FS.open(file, "r+"); + if (!f && !WLED_FS.exists(file)) f = WLED_FS.open(file, "w+"); + if (!f) { + DEBUGFS_PRINTLN(F("Failed to open!")); + return false; + } + + if (!bufferedFind(key)) //key does not exist in file + { + return appendObjectToFile(key, content, s); + } + + //an object with this key already exists, replace or delete it + pos = f.position(); + //measure out end of old object + bufferedFindObjectEnd(); + uint32_t pos2 = f.position(); + + uint32_t oldLen = pos2 - pos; + DEBUGFS_PRINTF("Old obj len %d\n", oldLen); + + //Three cases: + //1. The new content is null, overwrite old obj with spaces + //2. The new content is smaller than the old, overwrite and fill diff with spaces + //3. The new content is larger than the old, but smaller than old + trailing spaces, overwrite with new + //4. The new content is larger than old + trailing spaces, delete old and append + + uint32_t contentLen = 0; + if (!content->isNull()) contentLen = measureJson(*content); + + if (contentLen && contentLen <= oldLen) { //replace and fill diff with spaces + DEBUGFS_PRINTLN(F("replace")); + f.seek(pos); + serializeJson(*content, f); + writeSpace(pos2 - f.position()); + } else if (contentLen && bufferedFindSpace(contentLen - oldLen, false)) { //enough leading spaces to replace + DEBUGFS_PRINTLN(F("replace (trailing)")); + f.seek(pos); + serializeJson(*content, f); + } else { + DEBUGFS_PRINTLN(F("delete")); + pos -= strlen(key); + if (pos > 3) pos--; //also delete leading comma if not first object + f.seek(pos); + writeSpace(pos2 - pos); + if (contentLen) return appendObjectToFile(key, content, s, contentLen); + } + + doCloseFile = true; + DEBUGFS_PRINTF("Replaced/deleted, took %d ms\n", millis() - s); + return true; +} + +bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest) +{ + char objKey[10]; + sprintf(objKey, "\"%d\":", id); + return readObjectFromFile(file, objKey, dest); +} + +//if the key is a nullptr, deserialize entire object +bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest) +{ + if (doCloseFile) closeFile(); + #ifdef WLED_DEBUG_FS + DEBUGFS_PRINTF("Read from %s with key %s >>>\n", file, (key==nullptr)?"nullptr":key); + uint32_t s = millis(); + #endif + f = WLED_FS.open(file, "r"); + if (!f) return false; + + if (key != nullptr && !bufferedFind(key)) //key does not exist in file + { + f.close(); + dest->clear(); + DEBUGFS_PRINTLN(F("Obj not found.")); + return false; + } + + deserializeJson(*dest, f); + + f.close(); + DEBUGFS_PRINTF("Read, took %d ms\n", millis() - s); + return true; +} + +void updateFSInfo() { + #ifdef ARDUINO_ARCH_ESP32 + #if WLED_FS == LITTLEFS + fsBytesTotal = LITTLEFS.totalBytes(); + fsBytesUsed = LITTLEFS.usedBytes(); + #else + esp_spiffs_info(nullptr, &fsBytesTotal, &fsBytesUsed); + #endif + #else + FSInfo fsi; + WLED_FS.info(fsi); + fsBytesUsed = fsi.usedBytes; + fsBytesTotal = fsi.totalBytes; + #endif +} + + +//Un-comment any file types you need +String getContentType(AsyncWebServerRequest* request, String filename){ + if(request->hasArg("download")) return "application/octet-stream"; + else if(filename.endsWith(".htm")) return "text/html"; + else if(filename.endsWith(".html")) return "text/html"; +// else if(filename.endsWith(".css")) return "text/css"; +// else if(filename.endsWith(".js")) return "application/javascript"; + else if(filename.endsWith(".json")) return "application/json"; + else if(filename.endsWith(".png")) return "image/png"; +// else if(filename.endsWith(".gif")) return "image/gif"; + else if(filename.endsWith(".jpg")) return "image/jpeg"; + else if(filename.endsWith(".ico")) return "image/x-icon"; +// else if(filename.endsWith(".xml")) return "text/xml"; +// else if(filename.endsWith(".pdf")) return "application/x-pdf"; +// else if(filename.endsWith(".zip")) return "application/x-zip"; +// else if(filename.endsWith(".gz")) return "application/x-gzip"; + return "text/plain"; +} + +bool handleFileRead(AsyncWebServerRequest* request, String path){ + DEBUG_PRINTLN("FileRead: " + path); + if(path.endsWith("/")) path += "index.htm"; + if(path.indexOf("sec") > -1) return false; + String contentType = getContentType(request, path); + /*String pathWithGz = path + ".gz"; + if(WLED_FS.exists(pathWithGz)){ + request->send(WLED_FS, pathWithGz, contentType); + return true; + }*/ + if(WLED_FS.exists(path)) { + request->send(WLED_FS, path, contentType); + return true; + } + return false; +} diff --git a/wled00/html_other.h b/wled00/html_other.h index c7d3e855a..802e59e47 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -1,103 +1,118 @@ /* - * Various pages - */ + * More web UI HTML source arrays. + * This file is auto generated, please don't make any changes manually. + * Instead, see https://github.com/Aircoookie/WLED/wiki/Add-own-functionality#web-ui + * to find out how to easily modify the web UI source! + */ -//USER HTML HERE (/u subpage) -const char PAGE_usermod[] PROGMEM = R"=====( -No usermod custom web page set.)====="; +// Autogenerated from wled00/data/usermod.htm, do not edit!! +const char PAGE_usermod[] PROGMEM = R"=====(No usermod custom web page set.)====="; -//server message -const char PAGE_msg[] PROGMEM = R"=====( - -WLED Message - - -

%MSG%)====="; +// Autogenerated from wled00/data/msg.htm, do not edit!! +const char PAGE_msg[] PROGMEM = R"=====( +WLED Message

%MSG%)====="; -//firmware update page -const char PAGE_update[] PROGMEM = R"=====( -WLED Update - -

WLED Software Update

Installed version: 0.9.1
Download the latest binary:

)====="; +#ifdef WLED_ENABLE_DMX + +// Autogenerated from wled00/data/dmxmap.htm, do not edit!! +const char PAGE_dmxmap[] PROGMEM = R"=====( +DMX Map
...
)====="; -//new user welcome page -const char PAGE_welcome[] PROGMEM = R"=====(WLED Setup -

-

Welcome to WLED!

Thank you for installing my application!

If you encounter a bug or have a question/feature suggestion, feel free to open a GitHub issue!

Next steps:

Connect the module to your local WiFi here!

Just trying this out in AP mode?
)====="; +#else +const char PAGE_dmxmap[] PROGMEM = R"=====()====="; +#endif + +// Autogenerated from wled00/data/update.htm, do not edit!! +const char PAGE_update[] PROGMEM = R"=====( +WLED Update

WLED Software Update

+Installed version: 0.12.0
Download the latest binary: +

+
Updating... +
Please do not close or refresh the page :)
)====="; -//liveview -const char PAGE_liveview[] PROGMEM = R"=====( - - - - -WLED Live Preview - - -
- +// Autogenerated from wled00/data/welcome.htm, do not edit!! +const char PAGE_welcome[] PROGMEM = R"=====(Welcome! +

Welcome to WLED!

+Thank you for installing my application!

Next steps:

+Connect the module to your local WiFi here!

+Just trying this out in AP mode?

)====="; -/* - * favicon - */ +// Autogenerated from wled00/data/liveview.htm, do not edit!! +const char PAGE_liveview[] PROGMEM = R"=====( +WLED Live Preview
)====="; + + +// Autogenerated from wled00/data/liveviewws.htm, do not edit!! +const char PAGE_liveviewws[] PROGMEM = R"=====( +WLED Live Preview
)====="; + + +// Autogenerated from wled00/data/404.htm, do not edit!! +const char PAGE_404[] PROGMEM = R"=====(Not found +

404 Not Found

Akemi does not know where you are headed...

+ +)====="; + + +// Autogenerated from wled00/data/favicon.ico, do not edit!! +const uint16_t favicon_length = 954; const uint8_t favicon[] PROGMEM = { - 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x10, 0x10, 0x00, 0x00, 0x01, 0x00, - 0x18, 0x00, 0x86, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x89, 0x50, - 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, - 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, - 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x4D, 0x49, - 0x44, 0x41, 0x54, 0x38, 0x8D, 0x63, 0xFC, 0xFF, 0xFF, 0x3F, 0x03, 0xB1, - 0x80, 0xD1, 0x9E, 0x01, 0x43, 0x31, 0x13, 0xD1, 0xBA, 0x71, 0x00, 0x8A, - 0x0D, 0x60, 0x21, 0xA4, 0x00, 0xD9, 0xD9, 0xFF, 0x0F, 0x32, 0x30, 0x52, - 0xDD, 0x05, 0xB4, 0xF1, 0x02, 0xB6, 0xD0, 0xA6, 0x99, 0x0B, 0x68, 0x1F, - 0x0B, 0xD8, 0x42, 0x9E, 0xAA, 0x2E, 0xA0, 0xD8, 0x00, 0x46, 0x06, 0x3B, - 0xCC, 0xCC, 0x40, 0xC8, 0xD9, 0x54, 0x75, 0x01, 0xE5, 0x5E, 0x20, 0x25, - 0x3B, 0x63, 0x03, 0x00, 0x3E, 0xB7, 0x11, 0x5A, 0x8D, 0x1C, 0x07, 0xB4, - 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x10, 0x10, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x86, 0x00, + 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, + 0x00, 0x00, 0x00, 0x1f, 0xf3, 0xff, 0x61, 0x00, 0x00, 0x00, 0x4d, 0x49, 0x44, 0x41, 0x54, 0x38, + 0x8d, 0x63, 0xfc, 0xff, 0xff, 0x3f, 0x03, 0xb1, 0x80, 0xd1, 0x9e, 0x01, 0x43, 0x31, 0x13, 0xd1, + 0xba, 0x71, 0x00, 0x8a, 0x0d, 0x60, 0x21, 0xa4, 0x00, 0xd9, 0xd9, 0xff, 0x0f, 0x32, 0x30, 0x52, + 0xdd, 0x05, 0xb4, 0xf1, 0x02, 0xb6, 0xd0, 0xa6, 0x99, 0x0b, 0x68, 0x1f, 0x0b, 0xd8, 0x42, 0x9e, + 0xaa, 0x2e, 0xa0, 0xd8, 0x00, 0x46, 0x06, 0x3b, 0xcc, 0xcc, 0x40, 0xc8, 0xd9, 0x54, 0x75, 0x01, + 0xe5, 0x5e, 0x20, 0x25, 0x3b, 0x63, 0x03, 0x00, 0x3e, 0xb7, 0x11, 0x5a, 0x8d, 0x1c, 0x07, 0xb4, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 5fe0e919e..34e5a3180 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -1,402 +1,389 @@ /* - * Settings html - */ + * More web UI HTML source arrays. + * This file is auto generated, please don't make any changes manually. + * Instead, see https://github.com/Aircoookie/WLED/wiki/Add-own-functionality#web-ui + * to find out how to easily modify the web UI source! + */ -//common CSS of settings pages -const char PAGE_settingsCss[] PROGMEM = R"=====()====="; +// Autogenerated from wled00/data/style.css, do not edit!! +const char PAGE_settingsCss[] PROGMEM = R"=====()====="; -//settings menu -const char PAGE_settings[] PROGMEM = R"=====( -WLED Settings - - -
-
-
-
-
-
-
+// Autogenerated from wled00/data/settings.htm, do not edit!! +const char PAGE_settings[] PROGMEM = R"=====(WLED Settings +
%DMXMENU%
+
)====="; -//wifi settings -const char PAGE_settings_wifi[] PROGMEM = R"=====( - -WiFi Settings"); -} diff --git a/wled00/wled03_set.ino b/wled00/wled03_set.ino deleted file mode 100644 index 2e0e57306..000000000 --- a/wled00/wled03_set.ino +++ /dev/null @@ -1,655 +0,0 @@ -/* - * Receives client input - */ - -void _setRandomColor(bool _sec,bool fromButton=false) -{ - lastRandomIndex = strip.get_random_wheel_index(lastRandomIndex); - if (_sec){ - colorHStoRGB(lastRandomIndex*256,255,colSec); - } else { - colorHStoRGB(lastRandomIndex*256,255,col); - } - if (fromButton) colorUpdated(2); -} - - -bool isAsterisksOnly(const char* str, byte maxLen) -{ - for (byte i = 0; i < maxLen; i++) { - if (str[i] == 0) break; - if (str[i] != '*') return false; - } - return true; -} - - -//called upon POST settings form submit -void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) -{ - //0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec - if (subPage <1 || subPage >6) return; - - //WIFI SETTINGS - if (subPage == 1) - { - strlcpy(clientSSID,request->arg("CS").c_str(), 33); - - if (!isAsterisksOnly(request->arg("CP").c_str(), 65)) strlcpy(clientPass, request->arg("CP").c_str(), 65); - - strlcpy(cmDNS, request->arg("CM").c_str(), 33); - - apBehavior = request->arg("AB").toInt(); - strlcpy(apSSID, request->arg("AS").c_str(), 33); - apHide = request->hasArg("AH"); - int passlen = request->arg("AP").length(); - if (passlen == 0 || (passlen > 7 && !isAsterisksOnly(request->arg("AP").c_str(), 65))) strlcpy(apPass, request->arg("AP").c_str(), 65); - int t = request->arg("AC").toInt(); if (t > 0 && t < 14) apChannel = t; - - char k[3]; k[2] = 0; - for (int i = 0; i<4; i++) - { - k[1] = i+48;//ascii 0,1,2,3 - - k[0] = 'I'; //static IP - staticIP[i] = request->arg(k).toInt(); - - k[0] = 'G'; //gateway - staticGateway[i] = request->arg(k).toInt(); - - k[0] = 'S'; //subnet - staticSubnet[i] = request->arg(k).toInt(); - } - } - - //LED SETTINGS - if (subPage == 2) - { - int t = request->arg("LC").toInt(); - if (t > 0 && t <= MAX_LEDS) ledCount = t; - #ifdef ESP8266 - #if LEDPIN == 3 - if (ledCount > MAX_LEDS_DMA) ledCount = MAX_LEDS_DMA; //DMA method uses too much ram - #endif - #endif - strip.ablMilliampsMax = request->arg("MA").toInt(); - strip.milliampsPerLed = request->arg("LA").toInt(); - - useRGBW = request->hasArg("EW"); - strip.colorOrder = request->arg("CO").toInt(); - autoRGBtoRGBW = request->hasArg("AW"); - - briS = request->arg("CA").toInt(); - - saveCurrPresetCycConf = request->hasArg("PC"); - turnOnAtBoot = request->hasArg("BO"); - t = request->arg("BP").toInt(); - if (t <= 25) bootPreset = t; - strip.gammaCorrectBri = request->hasArg("GB"); - strip.gammaCorrectCol = request->hasArg("GC"); - - fadeTransition = request->hasArg("TF"); - t = request->arg("TD").toInt(); - if (t > 0) transitionDelay = t; - transitionDelayDefault = t; - strip.paletteFade = request->hasArg("PF"); - - nightlightTargetBri = request->arg("TB").toInt(); - t = request->arg("TL").toInt(); - if (t > 0) nightlightDelayMinsDefault = t; - nightlightDelayMins = nightlightDelayMinsDefault; - nightlightFade = request->hasArg("TW"); - - t = request->arg("PB").toInt(); - if (t >= 0 && t < 4) strip.paletteBlend = t; - strip.reverseMode = request->hasArg("RV"); - skipFirstLed = request->hasArg("SL"); - t = request->arg("BF").toInt(); - if (t > 0) briMultiplier = t; - } - - //UI - if (subPage == 3) - { - strlcpy(serverDescription, request->arg("DS").c_str(), 33); - syncToggleReceive = request->hasArg("ST"); - } - - //SYNC - if (subPage == 4) - { - buttonEnabled = request->hasArg("BT"); - irEnabled = request->arg("IR").toInt(); - int t = request->arg("UP").toInt(); - if (t > 0) udpPort = t; - receiveNotificationBrightness = request->hasArg("RB"); - receiveNotificationColor = request->hasArg("RC"); - receiveNotificationEffects = request->hasArg("RX"); - receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); - notifyDirectDefault = request->hasArg("SD"); - notifyDirect = notifyDirectDefault; - notifyButton = request->hasArg("SB"); - notifyAlexa = request->hasArg("SA"); - notifyHue = request->hasArg("SH"); - notifyMacro = request->hasArg("SM"); - notifyTwice = request->hasArg("S2"); - - receiveDirect = request->hasArg("RD"); - e131Multicast = request->hasArg("EM"); - t = request->arg("EU").toInt(); - if (t > 0 && t <= 63999) e131Universe = t; - t = request->arg("DA").toInt(); - if (t > 0 && t <= 510) DMXAddress = t; - t = request->arg("DM").toInt(); - if (t >= DMX_MODE_DISABLED && t <= DMX_MODE_MULTIPLE_DRGB) DMXMode = t; - t = request->arg("ET").toInt(); - if (t > 99 && t <= 65000) realtimeTimeoutMs = t; - arlsForceMaxBri = request->hasArg("FB"); - arlsDisableGammaCorrection = request->hasArg("RG"); - t = request->arg("WO").toInt(); - if (t >= -255 && t <= 255) arlsOffset = t; - - alexaEnabled = request->hasArg("AL"); - strlcpy(alexaInvocationName, request->arg("AI").c_str(), 33); - - if (request->hasArg("BK") && !request->arg("BK").equals("Hidden")) { - strlcpy(blynkApiKey, request->arg("BK").c_str(), 36); initBlynk(blynkApiKey); - } - - #ifdef WLED_ENABLE_MQTT - mqttEnabled = request->hasArg("MQ"); - strlcpy(mqttServer, request->arg("MS").c_str(), 33); - t = request->arg("MQPORT").toInt(); - if (t > 0) mqttPort = t; - strlcpy(mqttUser, request->arg("MQUSER").c_str(), 41); - if (!isAsterisksOnly(request->arg("MQPASS").c_str(), 41)) strlcpy(mqttPass, request->arg("MQPASS").c_str(), 41); - strlcpy(mqttClientID, request->arg("MQCID").c_str(), 41); - strlcpy(mqttDeviceTopic, request->arg("MD").c_str(), 33); - strlcpy(mqttGroupTopic, request->arg("MG").c_str(), 33); - #endif - - #ifndef WLED_DISABLE_HUESYNC - for (int i=0;i<4;i++){ - String a = "H"+String(i); - hueIP[i] = request->arg(a).toInt(); - } - - t = request->arg("HL").toInt(); - if (t > 0) huePollLightId = t; - - t = request->arg("HI").toInt(); - if (t > 50) huePollIntervalMs = t; - - hueApplyOnOff = request->hasArg("HO"); - hueApplyBri = request->hasArg("HB"); - hueApplyColor = request->hasArg("HC"); - huePollingEnabled = request->hasArg("HP"); - hueStoreAllowed = true; - reconnectHue(); - #endif - } - - //TIME - if (subPage == 5) - { - ntpEnabled = request->hasArg("NT"); - strlcpy(ntpServerName, request->arg("NS").c_str(), 33); - useAMPM = !request->hasArg("CF"); - currentTimezone = request->arg("TZ").toInt(); - utcOffsetSecs = request->arg("UO").toInt(); - - //start ntp if not already connected - if (ntpEnabled && WLED_CONNECTED && !ntpConnected) ntpConnected = ntpUdp.begin(ntpLocalPort); - - if (request->hasArg("OL")){ - overlayDefault = request->arg("OL").toInt(); - overlayCurrent = overlayDefault; - } - - overlayMin = request->arg("O1").toInt(); - overlayMax = request->arg("O2").toInt(); - analogClock12pixel = request->arg("OM").toInt(); - analogClock5MinuteMarks = request->hasArg("O5"); - analogClockSecondsTrail = request->hasArg("OS"); - - strcpy(cronixieDisplay,request->arg("CX").c_str()); - bool cbOld = cronixieBacklight; - cronixieBacklight = request->hasArg("CB"); - if (cbOld != cronixieBacklight && overlayCurrent == 3) - { - strip.setCronixieBacklight(cronixieBacklight); overlayRefreshedTime = 0; - } - countdownMode = request->hasArg("CE"); - countdownYear = request->arg("CY").toInt(); - countdownMonth = request->arg("CI").toInt(); - countdownDay = request->arg("CD").toInt(); - countdownHour = request->arg("CH").toInt(); - countdownMin = request->arg("CM").toInt(); - countdownSec = request->arg("CS").toInt(); - - for (int i=1;i<17;i++) - { - String a = "M"+String(i); - if (request->hasArg(a.c_str())) saveMacro(i,request->arg(a),false); - } - - macroBoot = request->arg("MB").toInt(); - macroAlexaOn = request->arg("A0").toInt(); - macroAlexaOff = request->arg("A1").toInt(); - macroButton = request->arg("MP").toInt(); - macroLongPress = request->arg("ML").toInt(); - macroCountdown = request->arg("MC").toInt(); - macroNl = request->arg("MN").toInt(); - macroDoublePress = request->arg("MD").toInt(); - - char k[3]; k[2] = 0; - for (int i = 0; i<8; i++) - { - k[1] = i+48;//ascii 0,1,2,3 - - k[0] = 'H'; //timer hours - timerHours[i] = request->arg(k).toInt(); - - k[0] = 'N'; //minutes - timerMinutes[i] = request->arg(k).toInt(); - - k[0] = 'T'; //macros - timerMacro[i] = request->arg(k).toInt(); - - k[0] = 'W'; //weekdays - timerWeekday[i] = request->arg(k).toInt(); - } - } - - //SECURITY - if (subPage == 6) - { - if (request->hasArg("RS")) //complete factory reset - { - clearEEPROM(); - serveMessage(request, 200, "All Settings erased.", "Connect to WLED-AP to setup again",255); - doReboot = true; - } - - bool pwdCorrect = !otaLock; //always allow access if ota not locked - if (request->hasArg("OP")) - { - if (otaLock && strcmp(otaPass,request->arg("OP").c_str()) == 0) - { - pwdCorrect = true; - } - if (!otaLock && request->arg("OP").length() > 0) - { - strlcpy(otaPass,request->arg("OP").c_str(), 33); - } - } - - if (pwdCorrect) //allow changes if correct pwd or no ota active - { - otaLock = request->hasArg("NO"); - wifiLock = request->hasArg("OW"); - aOtaEnabled = request->hasArg("AO"); - } - } - if (subPage != 6 || !doReboot) saveSettingsToEEPROM(); //do not save if factory reset - if (subPage == 2) { - strip.init(useRGBW,ledCount,skipFirstLed); - } - if (subPage == 4) alexaInit(); -} - - - -//helper to get int value at a position in string -int getNumVal(const String* req, uint16_t pos) -{ - return req->substring(pos+3).toInt(); -} - - -//helper to get int value at a position in string -bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255) -{ - int pos = req->indexOf(key); - if (pos < 1) return false; - - if (req->charAt(pos+3) == '~') { - int out = getNumVal(req, pos+1); - if (out == 0) - { - if (req->charAt(pos+4) == '-') - { - *val = (*val <= minv)? maxv : *val -1; - } else { - *val = (*val >= maxv)? minv : *val +1; - } - } else { - out += *val; - if (out > maxv) out = maxv; - if (out < minv) out = minv; - *val = out; - } - } else - { - *val = getNumVal(req, pos); - } - return true; -} - - -//HTTP API request parser -bool handleSet(AsyncWebServerRequest *request, const String& req) -{ - if (!(req.indexOf("win") >= 0)) return false; - - int pos = 0; - DEBUG_PRINT("API req: "); - DEBUG_PRINTLN(req); - - //save macro, requires &MS=() format - pos = req.indexOf("&MS="); - if (pos > 0) { - int i = req.substring(pos + 4).toInt(); - pos = req.indexOf('(') +1; - if (pos > 0) { - int en = req.indexOf(')'); - String mc = req.substring(pos); - if (en > 0) mc = req.substring(pos, en); - saveMacro(i, mc); - } - - pos = req.indexOf("IN"); - if (pos < 1) XML_response(request); - return true; - //if you save a macro in one request, other commands in that request are ignored due to unwanted behavior otherwise - } - - strip.applyToAllSelected = true; - - //segment select (sets main segment) - byte prevMain = strip.getMainSegmentId(); - pos = req.indexOf("SM="); - if (pos > 0) { - strip.mainSegment = getNumVal(&req, pos); - } - byte main = strip.getMainSegmentId(); - if (main != prevMain) setValuesFromMainSeg(); - - pos = req.indexOf("SS="); - if (pos > 0) { - byte t = getNumVal(&req, pos); - if (t < strip.getMaxSegments()) main = t; - } - - WS2812FX::Segment& mainseg = strip.getSegment(main); - pos = req.indexOf("SV="); //segment selected - if (pos > 0) mainseg.setOption(0, (req.charAt(pos+3) != '0')); - - uint16_t startI = mainseg.start; - uint16_t stopI = mainseg.stop; - uint8_t grpI = mainseg.grouping; - uint16_t spcI = mainseg.spacing; - pos = req.indexOf("&S="); //segment start - if (pos > 0) { - startI = getNumVal(&req, pos); - } - pos = req.indexOf("S2="); //segment stop - if (pos > 0) { - stopI = getNumVal(&req, pos); - } - pos = req.indexOf("GP="); //segment grouping - if (pos > 0) { - grpI = getNumVal(&req, pos); - if (grpI == 0) grpI = 1; - } - pos = req.indexOf("SP="); //segment spacing - if (pos > 0) { - spcI = getNumVal(&req, pos); - } - strip.setSegment(main, startI, stopI, grpI, spcI); - - main = strip.getMainSegmentId(); - - //set presets - pos = req.indexOf("P1="); //sets first preset for cycle - if (pos > 0) presetCycleMin = getNumVal(&req, pos); - - pos = req.indexOf("P2="); //sets last preset for cycle - if (pos > 0) presetCycleMax = getNumVal(&req, pos); - - //preset cycle - pos = req.indexOf("CY="); - if (pos > 0) - { - presetCyclingEnabled = (req.charAt(pos+3) != '0'); - presetCycCurr = presetCycleMin; - } - - pos = req.indexOf("PT="); //sets cycle time in ms - if (pos > 0) { - int v = getNumVal(&req, pos); - if (v > 49) presetCycleTime = v; - } - - pos = req.indexOf("PA="); //apply brightness from preset - if (pos > 0) presetApplyBri = (req.charAt(pos+3) != '0'); - - pos = req.indexOf("PC="); //apply color from preset - if (pos > 0) presetApplyCol = (req.charAt(pos+3) != '0'); - - pos = req.indexOf("PX="); //apply effects from preset - if (pos > 0) presetApplyFx = (req.charAt(pos+3) != '0'); - - pos = req.indexOf("PS="); //saves current in preset - if (pos > 0) savePreset(getNumVal(&req, pos)); - - //apply preset - if (updateVal(&req, "PL=", &presetCycCurr, presetCycleMin, presetCycleMax)) { - applyPreset(presetCycCurr, presetApplyBri, presetApplyCol, presetApplyFx); - } - - //set brightness - updateVal(&req, "&A=", &bri); - - //set colors - updateVal(&req, "&R=", &col[0]); - updateVal(&req, "&G=", &col[1]); - updateVal(&req, "&B=", &col[2]); - updateVal(&req, "&W=", &col[3]); - updateVal(&req, "R2=", &colSec[0]); - updateVal(&req, "G2=", &colSec[1]); - updateVal(&req, "B2=", &colSec[2]); - updateVal(&req, "W2=", &colSec[3]); - - //set hue - pos = req.indexOf("HU="); - if (pos > 0) { - uint16_t temphue = getNumVal(&req, pos); - byte tempsat = 255; - pos = req.indexOf("SA="); - if (pos > 0) { - tempsat = getNumVal(&req, pos); - } - colorHStoRGB(temphue,tempsat,(req.indexOf("H2")>0)? colSec:col); - } - - //set color from HEX or 32bit DEC - pos = req.indexOf("CL="); - if (pos > 0) { - colorFromDecOrHexString(col, (char*)req.substring(pos + 3).c_str()); - } - pos = req.indexOf("C2="); - if (pos > 0) { - colorFromDecOrHexString(colSec, (char*)req.substring(pos + 3).c_str()); - } - - //set to random hue SR=0->1st SR=1->2nd - pos = req.indexOf("SR"); - if (pos > 0) { - _setRandomColor(getNumVal(&req, pos)); - } - - //swap 2nd & 1st - pos = req.indexOf("SC"); - if (pos > 0) { - byte temp; - for (uint8_t i=0; i<4; i++) - { - temp = col[i]; - col[i] = colSec[i]; - colSec[i] = temp; - } - } - - //set effect parameters - if (updateVal(&req, "FX=", &effectCurrent, 0, strip.getModeCount()-1)) presetCyclingEnabled = false; - updateVal(&req, "SX=", &effectSpeed); - updateVal(&req, "IX=", &effectIntensity); - updateVal(&req, "FP=", &effectPalette, 0, strip.getPaletteCount()-1); - - //set advanced overlay - pos = req.indexOf("OL="); - if (pos > 0) { - overlayCurrent = getNumVal(&req, pos); - } - - //apply macro - pos = req.indexOf("&M="); - if (pos > 0) { - applyMacro(getNumVal(&req, pos)); - } - - //toggle send UDP direct notifications - pos = req.indexOf("SN="); - if (pos > 0) notifyDirect = (req.charAt(pos+3) != '0'); - - //toggle receive UDP direct notifications - pos = req.indexOf("RN="); - if (pos > 0) receiveNotifications = (req.charAt(pos+3) != '0'); - - //receive live data via UDP/Hyperion - pos = req.indexOf("RD="); - if (pos > 0) receiveDirect = (req.charAt(pos+3) != '0'); - - //toggle nightlight mode - bool aNlDef = false; - if (req.indexOf("&ND") > 0) aNlDef = true; - pos = req.indexOf("NL="); - if (pos > 0) - { - if (req.charAt(pos+3) == '0') - { - nightlightActive = false; - bri = briT; - } else { - nightlightActive = true; - if (!aNlDef) nightlightDelayMins = getNumVal(&req, pos); - nightlightStartTime = millis(); - } - } else if (aNlDef) - { - nightlightActive = true; - nightlightStartTime = millis(); - } - - //set nightlight target brightness - pos = req.indexOf("NT="); - if (pos > 0) { - nightlightTargetBri = getNumVal(&req, pos); - nightlightActiveOld = false; //re-init - } - - //toggle nightlight fade - pos = req.indexOf("NF="); - if (pos > 0) - { - nightlightFade = (req.charAt(pos+3) != '0'); - nightlightColorFade = (req.charAt(pos+3) == '2'); //NighLightColorFade can only be enabled via API or Macro with "NF=2" - nightlightActiveOld = false; //re-init - } - - #if AUXPIN >= 0 - //toggle general purpose output - pos = req.indexOf("AX="); - if (pos > 0) { - auxTime = getNumVal(&req, pos); - auxActive = true; - if (auxTime == 0) auxActive = false; - } - #endif - - pos = req.indexOf("TT="); - if (pos > 0) transitionDelay = getNumVal(&req, pos); - - //main toggle on/off - pos = req.indexOf("&T="); - if (pos > 0) { - nightlightActive = false; //always disable nightlight when toggling - switch (getNumVal(&req, pos)) - { - case 0: if (bri != 0){briLast = bri; bri = 0;} break; //off - case 1: bri = briLast; break; //on - default: toggleOnOff(); //toggle - } - } - - //Segment reverse - pos = req.indexOf("RV="); - if (pos > 0) strip.getSegment(main).setOption(1, req.charAt(pos+3) != '0'); - - //deactivate nightlight if target brightness is reached - if (bri == nightlightTargetBri) nightlightActive = false; - //set time (unix timestamp) - pos = req.indexOf("ST="); - if (pos > 0) { - setTime(getNumVal(&req, pos)); - } - - //set countdown goal (unix timestamp) - pos = req.indexOf("CT="); - if (pos > 0) { - countdownTime = getNumVal(&req, pos); - if (countdownTime - now() > 0) countdownOverTriggered = false; - } - - //cronixie - #ifndef WLED_DISABLE_CRONIXIE - //mode, 1 countdown - pos = req.indexOf("NM="); - if (pos > 0) countdownMode = (req.charAt(pos+3) != '0'); - - pos = req.indexOf("NX="); //sets digits to code - if (pos > 0) { - strlcpy(cronixieDisplay, req.substring(pos + 3, pos + 9).c_str(), 6); - setCronixie(); - } - - pos = req.indexOf("NB="); - if (pos > 0) //sets backlight - { - presetApplyFx = (req.charAt(pos+3) != '0'); - if (overlayCurrent == 3) strip.setCronixieBacklight(cronixieBacklight); - overlayRefreshedTime = 0; - } - #endif - - pos = req.indexOf("U0="); //user var 0 - if (pos > 0) { - userVar0 = getNumVal(&req, pos); - } - - pos = req.indexOf("U1="); //user var 1 - if (pos > 0) { - userVar1 = getNumVal(&req, pos); - } - //you can add more if you need - - //internal call, does not send XML response - pos = req.indexOf("IN"); - if (pos < 1) XML_response(request); - - pos = req.indexOf("&NN"); //do not send UDP notifications this time - colorUpdated((pos > 0) ? 5:1); - - return true; -} diff --git a/wled00/wled05_init.ino b/wled00/wled05_init.ino deleted file mode 100644 index 0cc6ac7ec..000000000 --- a/wled00/wled05_init.ino +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Setup code - */ - -void wledInit() -{ - EEPROM.begin(EEPSIZE); - ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); - if (ledCount > MAX_LEDS || ledCount == 0) ledCount = 30; - - #ifdef ESP8266 - #if LEDPIN == 3 - if (ledCount > MAX_LEDS_DMA) ledCount = MAX_LEDS_DMA; //DMA method uses too much ram - #endif - #endif - Serial.begin(115200); - Serial.setTimeout(50); - DEBUG_PRINTLN(); - DEBUG_PRINT("---WLED "); DEBUG_PRINT(versionString); DEBUG_PRINT(" "); DEBUG_PRINT(VERSION); DEBUG_PRINTLN(" INIT---"); - #ifdef ARDUINO_ARCH_ESP32 - DEBUG_PRINT("esp32 "); DEBUG_PRINTLN(ESP.getSdkVersion()); - #else - DEBUG_PRINT("esp8266 "); DEBUG_PRINTLN(ESP.getCoreVersion()); - #endif - int heapPreAlloc = ESP.getFreeHeap(); - DEBUG_PRINT("heap "); - DEBUG_PRINTLN(ESP.getFreeHeap()); - - strip.init(EEPROM.read(372),ledCount,EEPROM.read(2204)); //init LEDs quickly - strip.setBrightness(0); - - DEBUG_PRINT("LEDs inited. heap usage ~"); - DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); - - #ifndef WLED_DISABLE_FILESYSTEM - #ifdef ARDUINO_ARCH_ESP32 - SPIFFS.begin(true); - #endif - SPIFFS.begin(); - #endif - - DEBUG_PRINTLN("Load EEPROM"); - loadSettingsFromEEPROM(true); - beginStrip(); - userSetup(); - if (strcmp(clientSSID,"Your_Network") == 0) showWelcomePage = true; - WiFi.persistent(false); - - if (macroBoot>0) applyMacro(macroBoot); - Serial.println("Ada"); - - //generate module IDs - escapedMac = WiFi.macAddress(); - escapedMac.replace(":", ""); - escapedMac.toLowerCase(); - if (strcmp(cmDNS,"x") == 0) //fill in unique mdns default - { - strcpy(cmDNS, "wled-"); - sprintf(cmDNS+5, "%*s", 6, escapedMac.c_str()+6); - } - if (mqttDeviceTopic[0] == 0) - { - strcpy(mqttDeviceTopic, "wled/"); - sprintf(mqttDeviceTopic+5, "%*s", 6, escapedMac.c_str()+6); - } - if (mqttClientID[0] == 0) - { - strcpy(mqttClientID, "WLED-"); - sprintf(mqttClientID+5, "%*s", 6, escapedMac.c_str()+6); - } - - strip.service(); - - #ifndef WLED_DISABLE_OTA - if (aOtaEnabled) - { - ArduinoOTA.onStart([]() { - #ifdef ESP8266 - wifi_set_sleep_type(NONE_SLEEP_T); - #endif - DEBUG_PRINTLN("Start ArduinoOTA"); - }); - if (strlen(cmDNS) > 0) ArduinoOTA.setHostname(cmDNS); - } - #endif - - //HTTP server page init - initServer(); -} - - -void beginStrip() -{ - // Initialize NeoPixel Strip and button - strip.setShowCallback(handleOverlayDraw); - -#ifdef BTNPIN - pinMode(BTNPIN, INPUT_PULLUP); -#endif - - if (bootPreset>0) applyPreset(bootPreset, turnOnAtBoot, true, true); - colorUpdated(0); - - //init relay pin - #if RLYPIN >= 0 - pinMode(RLYPIN, OUTPUT); - #if RLYMDE - digitalWrite(RLYPIN, bri); - #else - digitalWrite(RLYPIN, !bri); - #endif - #endif - - //disable button if it is "pressed" unintentionally -#ifdef BTNPIN - if(digitalRead(BTNPIN) == LOW) buttonEnabled = false; -#else - buttonEnabled = false; -#endif -} - - -void initAP(bool resetAP=false){ - if (apBehavior == 3 && !resetAP) return; - - if (!apSSID[0] || resetAP) strcpy(apSSID, "WLED-AP"); - if (resetAP) strcpy(apPass,"wled1234"); - DEBUG_PRINT("Opening access point "); - DEBUG_PRINTLN(apSSID); - WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255,255,255,0)); - WiFi.softAP(apSSID, apPass, apChannel, apHide); - - if (!apActive) //start captive portal if AP active - { - DEBUG_PRINTLN("Init AP interfaces"); - server.begin(); - if (udpPort > 0 && udpPort != ntpLocalPort) - { - udpConnected = notifierUdp.begin(udpPort); - } - if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) - { - udpRgbConnected = rgbUdp.begin(udpRgbPort); - } - - dnsServer.setErrorReplyCode(DNSReplyCode::NoError); - dnsServer.start(53, "*", WiFi.softAPIP()); - } - apActive = true; -} - -void initConnection() -{ - WiFi.disconnect(); //close old connections - #ifdef ESP8266 - WiFi.setPhyMode(WIFI_PHY_MODE_11N); - #endif - - if (staticIP[0] != 0 && staticGateway[0] != 0) - { - WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8,8,8,8)); - } else - { - WiFi.config(0U, 0U, 0U); - } - - lastReconnectAttempt = millis(); - - if (!WLED_WIFI_CONFIGURED) - { - DEBUG_PRINT("No connection configured. "); - if (!apActive) initAP(); //instantly go to ap mode - return; - } else if (!apActive) { - if (apBehavior == 2) - { - initAP(); - } else - { - DEBUG_PRINTLN("Access point disabled."); - WiFi.softAPdisconnect(true); - } - } - showWelcomePage = false; - - DEBUG_PRINT("Connecting to "); - DEBUG_PRINT(clientSSID); - DEBUG_PRINTLN("..."); - - #ifdef ESP8266 - WiFi.hostname(serverDescription); - #endif - WiFi.begin(clientSSID, clientPass); - #ifdef ARDUINO_ARCH_ESP32 - WiFi.setHostname(serverDescription); - #endif -} - -void initInterfaces() { - DEBUG_PRINTLN("Init STA interfaces"); - - if (hueIP[0] == 0) - { - hueIP[0] = WiFi.localIP()[0]; - hueIP[1] = WiFi.localIP()[1]; - hueIP[2] = WiFi.localIP()[2]; - } - - //init Alexa hue emulation - if (alexaEnabled) alexaInit(); - - #ifndef WLED_DISABLE_OTA - if (aOtaEnabled) ArduinoOTA.begin(); - #endif - - strip.service(); - // Set up mDNS responder: - if (strlen(cmDNS) > 0) - { - if (!aOtaEnabled) MDNS.begin(cmDNS); - - DEBUG_PRINTLN("mDNS started"); - MDNS.addService("http", "tcp", 80); - MDNS.addService("wled", "tcp", 80); - MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); - } - server.begin(); - - if (udpPort > 0 && udpPort != ntpLocalPort) - { - udpConnected = notifierUdp.begin(udpPort); - if (udpConnected && udpRgbPort != udpPort) udpRgbConnected = rgbUdp.begin(udpRgbPort); - } - if (ntpEnabled) ntpConnected = ntpUdp.begin(ntpLocalPort); - - initBlynk(blynkApiKey); - e131.begin((e131Multicast) ? E131_MULTICAST : E131_UNICAST , e131Universe, E131_MAX_UNIVERSE_COUNT); - reconnectHue(); - initMqtt(); - interfacesInited = true; - wasConnected = true; -} - -byte stacO = 0; -uint32_t lastHeap; -unsigned long heapTime = 0; - -void handleConnection() { - if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == 2)) return; - if (lastReconnectAttempt == 0) initConnection(); - - //reconnect WiFi to clear stale allocations if heap gets too low - if (millis() - heapTime > 5000) - { - uint32_t heap = ESP.getFreeHeap(); - if (heap < 9000 && lastHeap < 9000) { - DEBUG_PRINT("Heap too low! "); - DEBUG_PRINTLN(heap); - forceReconnect = true; - } - lastHeap = heap; - heapTime = millis(); - } - - byte stac = 0; - if (apActive) { - #ifdef ESP8266 - stac = wifi_softap_get_station_num(); - #else - wifi_sta_list_t stationList; - esp_wifi_ap_get_sta_list(&stationList); - stac = stationList.num; - #endif - if (stac != stacO) - { - stacO = stac; - DEBUG_PRINT("Connected AP clients: "); - DEBUG_PRINTLN(stac); - if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { //trying to connect, but not connected - if (stac) WiFi.disconnect(); //disable search so that AP can work - else initConnection(); //restart search - } - } - } - if (forceReconnect) { - DEBUG_PRINTLN("Forcing reconnect."); - initConnection(); - interfacesInited = false; - forceReconnect = false; - wasConnected = false; - return; - } - if (!WLED_CONNECTED) { - if (interfacesInited) { - DEBUG_PRINTLN("Disconnected!"); - interfacesInited = false; - initConnection(); - } - if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) initConnection(); - if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == 1)) initAP(); - } else if (!interfacesInited) { //newly connected - DEBUG_PRINTLN(""); - DEBUG_PRINT("Connected! IP address: "); - DEBUG_PRINTLN(WiFi.localIP()); - initInterfaces(); - userConnected(); - - //shut down AP - if (apBehavior != 2 && apActive) - { - dnsServer.stop(); - WiFi.softAPdisconnect(true); - apActive = false; - DEBUG_PRINTLN("Access point disabled."); - } - } -} - -//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp -int getSignalQuality(int rssi) -{ - int quality = 0; - - if (rssi <= -100) { - quality = 0; - } else if (rssi >= -50) { - quality = 100; - } else { - quality = 2 * (rssi + 100); - } - return quality; -} diff --git a/wled00/wled07_notify.ino b/wled00/wled07_notify.ino deleted file mode 100644 index ab55df28d..000000000 --- a/wled00/wled07_notify.ino +++ /dev/null @@ -1,399 +0,0 @@ -/* - * UDP notifier - */ - -#define WLEDPACKETSIZE 29 -#define UDP_IN_MAXSIZE 1472 - - -void notify(byte callMode, bool followUp=false) -{ - if (!udpConnected) return; - switch (callMode) - { - case 0: return; - case 1: if (!notifyDirect) return; break; - case 2: if (!notifyButton) return; break; - case 4: if (!notifyDirect) return; break; - case 6: if (!notifyDirect) return; break; //fx change - case 7: if (!notifyHue) return; break; - case 8: if (!notifyDirect) return; break; - case 9: if (!notifyDirect) return; break; - case 10: if (!notifyAlexa) return; break; - default: return; - } - byte udpOut[WLEDPACKETSIZE]; - udpOut[0] = 0; //0: wled notifier protocol 1: WARLS protocol - udpOut[1] = callMode; - udpOut[2] = bri; - udpOut[3] = col[0]; - udpOut[4] = col[1]; - udpOut[5] = col[2]; - udpOut[6] = nightlightActive; - udpOut[7] = nightlightDelayMins; - udpOut[8] = effectCurrent; - udpOut[9] = effectSpeed; - udpOut[10] = col[3]; - //compatibilityVersionByte: - //0: old 1: supports white 2: supports secondary color - //3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette - //6: supports timebase syncing, 29 byte packet 7: supports tertiary color - udpOut[11] = 7; - udpOut[12] = colSec[0]; - udpOut[13] = colSec[1]; - udpOut[14] = colSec[2]; - udpOut[15] = colSec[3]; - udpOut[16] = effectIntensity; - udpOut[17] = (transitionDelay >> 0) & 0xFF; - udpOut[18] = (transitionDelay >> 8) & 0xFF; - udpOut[19] = effectPalette; - uint32_t colTer = strip.getSegment(strip.getMainSegmentId()).colors[2]; - udpOut[20] = (colTer >> 16) & 0xFF; - udpOut[21] = (colTer >> 8) & 0xFF; - udpOut[22] = (colTer >> 0) & 0xFF; - udpOut[23] = (colTer >> 24) & 0xFF; - - udpOut[24] = followUp; - uint32_t t = millis() + strip.timebase; - udpOut[25] = (t >> 24) & 0xFF; - udpOut[26] = (t >> 16) & 0xFF; - udpOut[27] = (t >> 8) & 0xFF; - udpOut[28] = (t >> 0) & 0xFF; - - IPAddress broadcastIp; - broadcastIp = ~uint32_t(WiFi.subnetMask()) | uint32_t(WiFi.gatewayIP()); - - notifierUdp.beginPacket(broadcastIp, udpPort); - notifierUdp.write(udpOut, WLEDPACKETSIZE); - notifierUdp.endPacket(); - notificationSentCallMode = callMode; - notificationSentTime = millis(); - notificationTwoRequired = (followUp)? false:notifyTwice; -} - - -void arlsLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC) -{ - if (!realtimeMode){ - for (uint16_t i = 0; i < ledCount; i++) - { - strip.setPixelColor(i,0,0,0,0); - } - realtimeMode = md; - } - realtimeTimeout = millis() + timeoutMs; - if (timeoutMs == 255001 || timeoutMs == 65000) realtimeTimeout = UINT32_MAX; - if (arlsForceMaxBri) strip.setBrightness(255); -} - - -void handleE131Packet(e131_packet_t* p, IPAddress clientIP){ - //E1.31 protocol support - - // skip out-of-sequence packets - if (p->sequence_number < e131LastSequenceNumber && p->sequence_number - e131LastSequenceNumber > -20){ - DEBUG_PRINT("skipping E1.31 frame (last seq="); - DEBUG_PRINT(e131LastSequenceNumber); - DEBUG_PRINT(", current seq="); - DEBUG_PRINT(p->sequence_number); - DEBUG_PRINTLN(")"); - e131LastSequenceNumber = p->sequence_number; - return; - } - e131LastSequenceNumber = p->sequence_number; - - // update status info - realtimeIP = clientIP; - - uint16_t uni = htons(p->universe); - uint8_t previousUniverses = uni - e131Universe; - uint16_t possibleLEDsInCurrentUniverse; - uint16_t dmxChannels = htons(p->property_value_count) -1; - - switch (DMXMode) { - case DMX_MODE_DISABLED: - return; // nothing to do - break; - - case DMX_MODE_SINGLE_RGB: - if (uni != e131Universe) return; - if (dmxChannels-DMXAddress+1 < 3) return; - for (uint16_t i = 0; i < ledCount; i++) - setRealtimePixel(i, p->property_values[DMXAddress+0], p->property_values[DMXAddress+1], p->property_values[DMXAddress+2], 0); - break; - - case DMX_MODE_SINGLE_DRGB: - if (uni != e131Universe) return; - if (dmxChannels-DMXAddress+1 < 4) return; - if (DMXOldDimmer != p->property_values[DMXAddress+0]) { - DMXOldDimmer = p->property_values[DMXAddress+0]; - bri = p->property_values[DMXAddress+0]; - strip.setBrightness(bri); - } - for (uint16_t i = 0; i < ledCount; i++) - setRealtimePixel(i, p->property_values[DMXAddress+1], p->property_values[DMXAddress+2], p->property_values[DMXAddress+3], 0); - break; - - case DMX_MODE_EFFECT: - if (uni != e131Universe) return; - if (dmxChannels-DMXAddress+1 < 11) return; - if (DMXOldDimmer != p->property_values[DMXAddress+0]) { - DMXOldDimmer = p->property_values[DMXAddress+0]; - bri = p->property_values[DMXAddress+0]; - } - if (p->property_values[DMXAddress+1] < MODE_COUNT) - effectCurrent = p->property_values[DMXAddress+ 1]; - effectSpeed = p->property_values[DMXAddress+ 2]; // flickers - effectIntensity = p->property_values[DMXAddress+ 3]; - effectPalette = p->property_values[DMXAddress+ 4]; - col[0] = p->property_values[DMXAddress+ 5]; - col[1] = p->property_values[DMXAddress+ 6]; - col[2] = p->property_values[DMXAddress+ 7]; - colSec[0] = p->property_values[DMXAddress+ 8]; - colSec[1] = p->property_values[DMXAddress+ 9]; - colSec[2] = p->property_values[DMXAddress+10]; - if (dmxChannels-DMXAddress+1 > 11) - { - col[3] = p->property_values[DMXAddress+11]; //white - colSec[3] = p->property_values[DMXAddress+12]; - } - transitionDelayTemp = 0; // act fast - colorUpdated(3); // don't send UDP - return; // don't activate realtime live mode - break; - - case DMX_MODE_MULTIPLE_RGB: - if (previousUniverses == 0) { - // first universe of this fixture - possibleLEDsInCurrentUniverse = (dmxChannels - DMXAddress + 1) / 3; - for (uint16_t i = 0; i < ledCount; i++) { - if (i >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) - setRealtimePixel(i, p->property_values[DMXAddress+i*3+0], p->property_values[DMXAddress+i*3+1], p->property_values[DMXAddress+i*3+2], 0); - } - } else if (previousUniverses > 0 && uni < (e131Universe + E131_MAX_UNIVERSE_COUNT)) { - // additional universe(s) of this fixture - uint16_t numberOfLEDsInPreviousUniverses = ((512 - DMXAddress + 1) / 3); // first universe - if (previousUniverses > 1) numberOfLEDsInPreviousUniverses += (512 / 3) * (previousUniverses - 1); // extended universe(s) before current - possibleLEDsInCurrentUniverse = dmxChannels / 3; - for (uint16_t i = numberOfLEDsInPreviousUniverses; i < ledCount; i++) { - uint8_t j = i - numberOfLEDsInPreviousUniverses; - if (j >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) - setRealtimePixel(i, p->property_values[j*3+1], p->property_values[j*3+2], p->property_values[j*3+3], 0); - } - } - break; - - case DMX_MODE_MULTIPLE_DRGB: - if (previousUniverses == 0) { - // first universe of this fixture - if (DMXOldDimmer != p->property_values[DMXAddress+0]) { - DMXOldDimmer = p->property_values[DMXAddress+0]; - bri = p->property_values[DMXAddress+0]; - strip.setBrightness(bri); - } - possibleLEDsInCurrentUniverse = (dmxChannels - DMXAddress) / 3; - for (uint16_t i = 0; i < ledCount; i++) { - if (i >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) - setRealtimePixel(i, p->property_values[DMXAddress+i*3+1], p->property_values[DMXAddress+i*3+2], p->property_values[DMXAddress+i*3+3], 0); - } - } else if (previousUniverses > 0 && uni < (e131Universe + E131_MAX_UNIVERSE_COUNT)) { - // additional universe(s) of this fixture - uint16_t numberOfLEDsInPreviousUniverses = ((512 - DMXAddress + 1) / 3); // first universe - if (previousUniverses > 1) numberOfLEDsInPreviousUniverses += (512 / 3) * (previousUniverses - 1); // extended universe(s) before current - possibleLEDsInCurrentUniverse = dmxChannels / 3; - for (uint16_t i = numberOfLEDsInPreviousUniverses; i < ledCount; i++) { - uint8_t j = i - numberOfLEDsInPreviousUniverses; - if (j >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) - setRealtimePixel(i, p->property_values[j*3+1], p->property_values[j*3+2], p->property_values[j*3+3], 0); - } - } - break; - - default: - DEBUG_PRINTLN("unknown E1.31 DMX mode"); - return; // nothing to do - break; - } - - arlsLock(realtimeTimeoutMs, REALTIME_MODE_E131); - e131NewData = true; -} - - -void handleNotifications() -{ - //send second notification if enabled - if(udpConnected && notificationTwoRequired && millis()-notificationSentTime > 250){ - notify(notificationSentCallMode,true); - } - - if (e131NewData && millis() - strip.getLastShow() > 15) - { - e131NewData = false; - strip.show(); - } - - //unlock strip when realtime UDP times out - if (realtimeMode && millis() > realtimeTimeout) - { - strip.setBrightness(bri); - realtimeMode = REALTIME_MODE_INACTIVE; - } - - //receive UDP notifications - if (!udpConnected || !(receiveNotifications || receiveDirect)) return; - - uint16_t packetSize = notifierUdp.parsePacket(); - - //hyperion / raw RGB - if (!packetSize && udpRgbConnected) { - packetSize = rgbUdp.parsePacket(); - if (!receiveDirect) return; - if (packetSize > UDP_IN_MAXSIZE || packetSize < 3) return; - realtimeIP = rgbUdp.remoteIP(); - DEBUG_PRINTLN(rgbUdp.remoteIP()); - uint8_t lbuf[packetSize]; - rgbUdp.read(lbuf, packetSize); - arlsLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); - uint16_t id = 0; - for (uint16_t i = 0; i < packetSize -2; i += 3) - { - setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0); - - id++; if (id >= ledCount) break; - } - strip.show(); - return; - } - - //notifier and UDP realtime - if (packetSize > UDP_IN_MAXSIZE) return; - if(packetSize && notifierUdp.remoteIP() != WiFi.localIP()) //don't process broadcasts we send ourselves - { - uint8_t udpIn[packetSize]; - notifierUdp.read(udpIn, packetSize); - - //wled notifier, block if realtime packets active - if (udpIn[0] == 0 && !realtimeMode && receiveNotifications) - { - //ignore notification if received within a second after sending a notification ourselves - if (millis() - notificationSentTime < 1000) return; - if (udpIn[1] > 199) return; //do not receive custom versions - - bool someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); - //apply colors from notification - if (receiveNotificationColor || !someSel) - { - col[0] = udpIn[3]; - col[1] = udpIn[4]; - col[2] = udpIn[5]; - if (udpIn[11] > 0) //sending module's white val is intended - { - col[3] = udpIn[10]; - if (udpIn[11] > 1) - { - colSec[0] = udpIn[12]; - colSec[1] = udpIn[13]; - colSec[2] = udpIn[14]; - colSec[3] = udpIn[15]; - } - if (udpIn[11] > 5) - { - uint32_t t = (udpIn[25] << 24) | (udpIn[26] << 16) | (udpIn[27] << 8) | (udpIn[28]); - t += 2; - t -= millis(); - strip.timebase = t; - } - if (udpIn[11] > 6) - { - strip.setColor(2, udpIn[20], udpIn[21], udpIn[22], udpIn[23]); //tertiary color - } - } - } - - //apply effects from notification - if (udpIn[11] < 200 && (receiveNotificationEffects || !someSel)) - { - if (udpIn[8] < strip.getModeCount()) effectCurrent = udpIn[8]; - effectSpeed = udpIn[9]; - if (udpIn[11] > 2) effectIntensity = udpIn[16]; - if (udpIn[11] > 4 && udpIn[19] < strip.getPaletteCount()) effectPalette = udpIn[19]; - } - - if (udpIn[11] > 3) - { - transitionDelayTemp = ((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00); - } - - nightlightActive = udpIn[6]; - if (nightlightActive) nightlightDelayMins = udpIn[7]; - - if (receiveNotificationBrightness || !someSel) bri = udpIn[2]; - colorUpdated(3); - - } else if (udpIn[0] > 0 && udpIn[0] < 5 && receiveDirect) //1 warls //2 drgb //3 drgbw - { - realtimeIP = notifierUdp.remoteIP(); - DEBUG_PRINTLN(notifierUdp.remoteIP()); - if (packetSize > 1) { - if (udpIn[1] == 0) - { - realtimeTimeout = 0; - return; - } else { - arlsLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); - } - if (udpIn[0] == 1) //warls - { - for (uint16_t i = 2; i < packetSize -3; i += 4) - { - setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0); - } - } else if (udpIn[0] == 2) //drgb - { - uint16_t id = 0; - for (uint16_t i = 2; i < packetSize -2; i += 3) - { - setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); - - id++; if (id >= ledCount) break; - } - } else if (udpIn[0] == 3) //drgbw - { - uint16_t id = 0; - for (uint16_t i = 2; i < packetSize -3; i += 4) - { - setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); - - id++; if (id >= ledCount) break; - } - } else if (udpIn[0] == 4) //dnrgb - { - uint16_t id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); - for (uint16_t i = 4; i < packetSize -2; i += 3) - { - if (id >= ledCount) break; - setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); - id++; - } - } - strip.show(); - } - } - } -} - - -void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w) -{ - uint16_t pix = i + arlsOffset; - if (pix < ledCount) - { - if (!arlsDisableGammaCorrection && strip.gammaCorrectCol) - { - strip.setPixelColor(pix, strip.gamma8(r), strip.gamma8(g), strip.gamma8(b), strip.gamma8(w)); - } else { - strip.setPixelColor(pix, r, g, b, w); - } - } -} diff --git a/wled00/wled08_led.ino b/wled00/wled08_led.ino deleted file mode 100644 index 883baa06a..000000000 --- a/wled00/wled08_led.ino +++ /dev/null @@ -1,263 +0,0 @@ -/* - * LED methods - */ -void setValuesFromMainSeg() -{ - WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId()); - colorFromUint32(seg.colors[0]); - colorFromUint32(seg.colors[1], true); - effectCurrent = seg.mode; - effectSpeed = seg.speed; - effectIntensity = seg.intensity; - effectPalette = seg.palette; -} - - -void resetTimebase() -{ - strip.timebase = 0 - millis(); -} - - -void toggleOnOff() -{ - if (bri == 0) - { - bri = briLast; - } else - { - briLast = bri; - bri = 0; - } -} - - -void setAllLeds() { - if (!realtimeMode || !arlsForceMaxBri) - { - double d = briT*briMultiplier; - int val = d/100; - if (val > 255) val = 255; - strip.setBrightness(val); - } - if (!enableSecTransition) - { - for (byte i = 0; i<4; i++) - { - colSecT[i] = colSec[i]; - } - } - if (useRGBW && autoRGBtoRGBW) - { - colorRGBtoRGBW(colT); - colorRGBtoRGBW(colSecT); - } - strip.setColor(0, colT[0], colT[1], colT[2], colT[3]); - strip.setColor(1, colSecT[0], colSecT[1], colSecT[2], colSecT[3]); -} - - -void setLedsStandard(bool justColors = false) -{ - for (byte i=0; i<4; i++) - { - colOld[i] = col[i]; - colT[i] = col[i]; - colSecOld[i] = colSec[i]; - colSecT[i] = colSec[i]; - } - if (justColors) return; - briOld = bri; - briT = bri; - setAllLeds(); -} - - -bool colorChanged() -{ - for (byte i=0; i<4; i++) - { - if (col[i] != colIT[i]) return true; - if (colSec[i] != colSecIT[i]) return true; - } - if (bri != briIT) return true; - return false; -} - - -void colorUpdated(int callMode) -{ - //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) - // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa - if (callMode != 0 && callMode != 1 && callMode != 5) strip.applyToAllSelected = true; //if not from JSON api, which directly sets segments - - bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette); - if (!colorChanged()) - { - if (nightlightActive && !nightlightActiveOld && callMode != 3 && callMode != 5) - { - notify(4); interfaceUpdateCallMode = 4; return; - } - else if (fxChanged) { - notify(6); - if (callMode != 8) interfaceUpdateCallMode = 6; - if (realtimeTimeout == UINT32_MAX) realtimeTimeout = 0; - if (isPreset) {isPreset = false;} - else {currentPreset = -1;} - } - return; //no change - } - if (realtimeTimeout == UINT32_MAX) realtimeTimeout = 0; - if (isPreset) {isPreset = false;} - else {currentPreset = -1;} - if (callMode != 5 && nightlightActive && nightlightFade) - { - briNlT = bri; - nightlightDelayMs -= (millis() - nightlightStartTime); - nightlightStartTime = millis(); - } - for (byte i=0; i<4; i++) - { - colIT[i] = col[i]; - colSecIT[i] = colSec[i]; - } - if (briT == 0) - { - setLedsStandard(true); //do not color transition if starting from off - if (callMode != 3) resetTimebase(); //effect start from beginning - } - - briIT = bri; - if (bri > 0) briLast = bri; - - notify(callMode); - - if (fadeTransition) - { - //set correct delay if not using notification delay - if (callMode != 3 && !jsonTransitionOnce) transitionDelayTemp = transitionDelay; - jsonTransitionOnce = false; - if (transitionDelayTemp == 0) {setLedsStandard(); strip.trigger(); return;} - - if (transitionActive) - { - for (byte i=0; i<4; i++) - { - colOld[i] = colT[i]; - colSecOld[i] = colSecT[i]; - } - briOld = briT; - tperLast = 0; - } - strip.setTransitionMode(true); - transitionActive = true; - transitionStartTime = millis(); - } else - { - setLedsStandard(); - strip.trigger(); - } - - if (callMode == 8) return; - //set flag to update blynk and mqtt - interfaceUpdateCallMode = callMode; -} - - -void updateInterfaces(uint8_t callMode) -{ - #ifndef WLED_DISABLE_ALEXA - if (espalexaDevice != nullptr && callMode != 10) { - espalexaDevice->setValue(bri); - espalexaDevice->setColor(col[0], col[1], col[2]); - } - #endif - if (callMode != 9 && callMode != 5) updateBlynk(); - doPublishMqtt = true; - lastInterfaceUpdate = millis(); -} - - -void handleTransitions() -{ - //handle still pending interface update - if (interfaceUpdateCallMode && millis() - lastInterfaceUpdate > 2000) - { - updateInterfaces(interfaceUpdateCallMode); - interfaceUpdateCallMode = 0; //disable - } - if (doPublishMqtt) publishMqtt(); - - if (transitionActive && transitionDelayTemp > 0) - { - float tper = (millis() - transitionStartTime)/(float)transitionDelayTemp; - if (tper >= 1.0) - { - strip.setTransitionMode(false); - transitionActive = false; - tperLast = 0; - setLedsStandard(); - return; - } - if (tper - tperLast < 0.004) return; - tperLast = tper; - for (byte i=0; i<4; i++) - { - colT[i] = colOld[i]+((col[i] - colOld[i])*tper); - colSecT[i] = colSecOld[i]+((colSec[i] - colSecOld[i])*tper); - } - briT = briOld +((bri - briOld )*tper); - - setAllLeds(); - } -} - - -void handleNightlight() -{ - if (nightlightActive) - { - if (!nightlightActiveOld) //init - { - nightlightStartTime = millis(); - nightlightDelayMs = (int)(nightlightDelayMins*60000); - nightlightActiveOld = true; - briNlT = bri; - for (byte i=0; i<4; i++) colNlT[i] = col[i]; // remember starting color - } - float nper = (millis() - nightlightStartTime)/((float)nightlightDelayMs); - if (nightlightFade) - { - bri = briNlT + ((nightlightTargetBri - briNlT)*nper); - if (nightlightColorFade) // color fading only is enabled with "NF=2" - { - for (byte i=0; i<4; i++) col[i] = colNlT[i]+ ((colSec[i] - colNlT[i])*nper); // fading from actual color to secondary color - } - colorUpdated(5); - } - if (nper >= 1) - { - nightlightActive = false; - if (!nightlightFade) - { - bri = nightlightTargetBri; - colorUpdated(5); - } - updateBlynk(); - if (bri == 0) briLast = briNlT; - } - } else if (nightlightActiveOld) //early de-init - { - nightlightActiveOld = false; - } - - //also handle preset cycle here - if (presetCyclingEnabled && (millis() - presetCycledTime > presetCycleTime)) - { - applyPreset(presetCycCurr,presetApplyBri,presetApplyCol,presetApplyFx); - presetCycCurr++; if (presetCycCurr > presetCycleMax) presetCycCurr = presetCycleMin; - if (presetCycCurr > 25) presetCycCurr = 1; - colorUpdated(8); - presetCycledTime = millis(); - } -} diff --git a/wled00/wled10_ntp.ino b/wled00/wled10_ntp.ino deleted file mode 100644 index bf8df064d..000000000 --- a/wled00/wled10_ntp.ino +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Acquires time from NTP server - */ - -TimeChangeRule UTCr = {Last, Sun, Mar, 1, 0}; // UTC -Timezone tzUTC(UTCr, UTCr); - -TimeChangeRule BST = {Last, Sun, Mar, 1, 60}; // British Summer Time -TimeChangeRule GMT = {Last, Sun, Oct, 2, 0}; // Standard Time -Timezone tzUK(BST, GMT); - -TimeChangeRule CEST = {Last, Sun, Mar, 2, 120}; //Central European Summer Time -TimeChangeRule CET = {Last, Sun, Oct, 3, 60}; //Central European Standard Time -Timezone tzEUCentral(CEST, CET); - -TimeChangeRule EEST = {Last, Sun, Mar, 3, 180}; //Central European Summer Time -TimeChangeRule EET = {Last, Sun, Oct, 4, 120}; //Central European Standard Time -Timezone tzEUEastern(EEST, EET); - -TimeChangeRule EDT = {Second, Sun, Mar, 2, -240 }; //Daylight time = UTC - 4 hours -TimeChangeRule EST = {First, Sun, Nov, 2, -300 }; //Standard time = UTC - 5 hours -Timezone tzUSEastern(EDT, EST); - -TimeChangeRule CDT = {Second, Sun, Mar, 2, -300 }; //Daylight time = UTC - 5 hours -TimeChangeRule CST = {First, Sun, Nov, 2, -360 }; //Standard time = UTC - 6 hours -Timezone tzUSCentral(CDT, CST); - -Timezone tzCASaskatchewan(CST, CST); //Central without DST - -TimeChangeRule MDT = {Second, Sun, Mar, 2, -360 }; //Daylight time = UTC - 6 hours -TimeChangeRule MST = {First, Sun, Nov, 2, -420 }; //Standard time = UTC - 7 hours -Timezone tzUSMountain(MDT, MST); - -Timezone tzUSArizona(MST, MST); //Mountain without DST - -TimeChangeRule PDT = {Second, Sun, Mar, 2, -420 }; //Daylight time = UTC - 7 hours -TimeChangeRule PST = {First, Sun, Nov, 2, -480 }; //Standard time = UTC - 8 hours -Timezone tzUSPacific(PDT, PST); - -TimeChangeRule ChST = {Last, Sun, Mar, 1, 480}; // China Standard Time = UTC + 8 hours -Timezone tzChina(ChST, ChST); - -TimeChangeRule JST = {Last, Sun, Mar, 1, 540}; // Japan Standard Time = UTC + 9 hours -Timezone tzJapan(JST, JST); - -TimeChangeRule AEDT = {Second, Sun, Oct, 2, 660 }; //Daylight time = UTC + 11 hours -TimeChangeRule AEST = {First, Sun, Apr, 3, 600 }; //Standard time = UTC + 10 hours -Timezone tzAUEastern(AEDT, AEST); - -TimeChangeRule NZDT = {Second, Sun, Sep, 2, 780 }; //Daylight time = UTC + 13 hours -TimeChangeRule NZST = {First, Sun, Apr, 3, 720 }; //Standard time = UTC + 12 hours -Timezone tzNZ(NZDT, NZST); - -TimeChangeRule NKST = {Last, Sun, Mar, 1, 510}; //Pyongyang Time = UTC + 8.5 hours -Timezone tzNK(NKST, NKST); - -TimeChangeRule IST = {Last, Sun, Mar, 1, 330}; // India Standard Time = UTC + 5.5 hours -Timezone tzIndia(IST, IST); - -Timezone* timezones[] = {&tzUTC, &tzUK, &tzEUCentral, &tzEUEastern, &tzUSEastern, &tzUSCentral, &tzUSMountain, &tzUSArizona, &tzUSPacific, &tzChina, &tzJapan, &tzAUEastern, &tzNZ, &tzNK, &tzIndia, &tzCASaskatchewan}; - -void handleNetworkTime() -{ - if (ntpEnabled && ntpConnected && millis() - ntpLastSyncTime > 50000000L && WLED_CONNECTED) - { - if (millis() - ntpPacketSentTime > 10000) - { - sendNTPPacket(); - ntpPacketSentTime = millis(); - } - if (checkNTPResponse()) - { - ntpLastSyncTime = millis(); - } - } -} - -void sendNTPPacket() -{ - if (!ntpServerIP.fromString(ntpServerName)) //see if server is IP or domain - { - #ifdef ESP8266 - WiFi.hostByName(ntpServerName, ntpServerIP, 750); - #else - WiFi.hostByName(ntpServerName, ntpServerIP); - #endif - } - - DEBUG_PRINTLN("send NTP"); - byte pbuf[NTP_PACKET_SIZE]; - memset(pbuf, 0, NTP_PACKET_SIZE); - - pbuf[0] = 0b11100011; // LI, Version, Mode - pbuf[1] = 0; // Stratum, or type of clock - pbuf[2] = 6; // Polling Interval - pbuf[3] = 0xEC; // Peer Clock Precision - // 8 bytes of zero for Root Delay & Root Dispersion - pbuf[12] = 49; - pbuf[13] = 0x4E; - pbuf[14] = 49; - pbuf[15] = 52; - - ntpUdp.beginPacket(ntpServerIP, 123); //NTP requests are to port 123 - ntpUdp.write(pbuf, NTP_PACKET_SIZE); - ntpUdp.endPacket(); -} - -bool checkNTPResponse() -{ - int cb = ntpUdp.parsePacket(); - if (cb) { - DEBUG_PRINT("NTP recv, l="); - DEBUG_PRINTLN(cb); - byte pbuf[NTP_PACKET_SIZE]; - ntpUdp.read(pbuf, NTP_PACKET_SIZE); // read the packet into the buffer - - unsigned long highWord = word(pbuf[40], pbuf[41]); - unsigned long lowWord = word(pbuf[42], pbuf[43]); - if (highWord == 0 && lowWord == 0) return false; - - unsigned long secsSince1900 = highWord << 16 | lowWord; - - DEBUG_PRINT("Unix time = "); - unsigned long epoch = secsSince1900 - 2208988799UL; //subtract 70 years -1sec (on avg. more precision) - setTime(epoch); - DEBUG_PRINTLN(epoch); - if (countdownTime - now() > 0) countdownOverTriggered = false; - return true; - } - return false; -} - -void updateLocalTime() -{ - unsigned long tmc = now()+ utcOffsetSecs; - local = timezones[currentTimezone]->toLocal(tmc); -} - -void getTimeString(char* out) -{ - updateLocalTime(); - byte hr = hour(local); - if (useAMPM) - { - if (hr > 11) hr -= 12; - if (hr == 0) hr = 12; - } - sprintf(out,"%i-%i-%i, %i:%s%i:%s%i",year(local), month(local), day(local), - hr,(minute(local)<10)?"0":"",minute(local), - (second(local)<10)?"0":"",second(local)); - if (useAMPM) - { - strcat(out,(hour(local) > 11)? " PM":" AM"); - } -} - -void setCountdown() -{ - countdownTime = timezones[currentTimezone]->toUTC(getUnixTime(countdownHour, countdownMin, countdownSec, countdownDay, countdownMonth, countdownYear)); - if (countdownTime - now() > 0) countdownOverTriggered = false; -} - -//returns true if countdown just over -bool checkCountdown() -{ - unsigned long n = now(); - local = countdownTime - n; - if (n > countdownTime) { - local = n - countdownTime; - if (!countdownOverTriggered) - { - if (macroCountdown != 0) applyMacro(macroCountdown); - countdownOverTriggered = true; - return true; - } - } - return false; -} - -byte weekdayMondayFirst() -{ - byte wd = weekday(local) -1; - if (wd == 0) wd = 7; - return wd; -} - -void checkTimers() -{ - if (lastTimerMinute != minute(local)) //only check once a new minute begins - { - lastTimerMinute = minute(local); - for (uint8_t i = 0; i < 8; i++) - { - if (timerMacro[i] != 0 - && (timerHours[i] == hour(local) || timerHours[i] == 24) //if hour is set to 24, activate every hour - && timerMinutes[i] == minute(local) - && (timerWeekday[i] & 0x01) //timer is enabled - && timerWeekday[i] >> weekdayMondayFirst() & 0x01) //timer should activate at current day of week - { - applyMacro(timerMacro[i]); - } - } - } -} diff --git a/wled00/wled11_ol.ino b/wled00/wled11_ol.ino deleted file mode 100644 index cfe8a1f9f..000000000 --- a/wled00/wled11_ol.ino +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Used to draw clock overlays over the strip - */ - -void initCronixie() -{ - if (overlayCurrent == 3 && !cronixieInit) - { - strip.driverModeCronixie(true); - strip.setCronixieBacklight(cronixieBacklight); - setCronixie(); - cronixieInit = true; - } else if (cronixieInit && overlayCurrent != 3) - { - strip.driverModeCronixie(false); - cronixieInit = false; - } -} - - -void handleOverlays() -{ - if (millis() - overlayRefreshedTime > overlayRefreshMs) - { - initCronixie(); - updateLocalTime(); - checkTimers(); - checkCountdown(); - if (overlayCurrent == 3) _overlayCronixie();//Diamex cronixie clock kit - overlayRefreshedTime = millis(); - } -} - - -void _overlayAnalogClock() -{ - int overlaySize = overlayMax - overlayMin +1; - if (countdownMode) - { - _overlayAnalogCountdown(); return; - } - double hourP = ((double)(hour(local)%12))/12; - double minuteP = ((double)minute(local))/60; - hourP = hourP + minuteP/12; - double secondP = ((double)second(local))/60; - int hourPixel = floor(analogClock12pixel + overlaySize*hourP); - if (hourPixel > overlayMax) hourPixel = overlayMin -1 + hourPixel - overlayMax; - int minutePixel = floor(analogClock12pixel + overlaySize*minuteP); - if (minutePixel > overlayMax) minutePixel = overlayMin -1 + minutePixel - overlayMax; - int secondPixel = floor(analogClock12pixel + overlaySize*secondP); - if (secondPixel > overlayMax) secondPixel = overlayMin -1 + secondPixel - overlayMax; - if (analogClockSecondsTrail) - { - if (secondPixel < analogClock12pixel) - { - strip.setRange(analogClock12pixel, overlayMax, 0xFF0000); - strip.setRange(overlayMin, secondPixel, 0xFF0000); - } else - { - strip.setRange(analogClock12pixel, secondPixel, 0xFF0000); - } - } - if (analogClock5MinuteMarks) - { - int pix; - for (int i = 0; i <= 12; i++) - { - pix = analogClock12pixel + round((overlaySize / 12.0) *i); - if (pix > overlayMax) pix -= overlaySize; - strip.setPixelColor(pix, 0x00FFAA); - } - } - if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, 0xFF0000); - strip.setPixelColor(minutePixel, 0x00FF00); - strip.setPixelColor(hourPixel, 0x0000FF); - overlayRefreshMs = 998; -} - - -void _overlayAnalogCountdown() -{ - if (now() < countdownTime) - { - long diff = countdownTime - now(); - double pval = 60; - if (diff > 31557600L) //display in years if more than 365 days - { - pval = 315576000L; //10 years - } else if (diff > 2592000L) //display in months if more than a month - { - pval = 31557600L; //1 year - } else if (diff > 604800) //display in weeks if more than a week - { - pval = 2592000L; //1 month - } else if (diff > 86400) //display in days if more than 24 hours - { - pval = 604800; //1 week - } else if (diff > 3600) //display in hours if more than 60 minutes - { - pval = 86400; //1 day - } else if (diff > 60) //display in minutes if more than 60 seconds - { - pval = 3600; //1 hour - } - int overlaySize = overlayMax - overlayMin +1; - double perc = (pval-(double)diff)/pval; - if (perc > 1.0) perc = 1.0; - byte pixelCnt = perc*overlaySize; - if (analogClock12pixel + pixelCnt > overlayMax) - { - strip.setRange(analogClock12pixel, overlayMax, ((uint32_t)colSec[3] << 24)| ((uint32_t)colSec[0] << 16) | ((uint32_t)colSec[1] << 8) | colSec[2]); - strip.setRange(overlayMin, overlayMin +pixelCnt -(1+ overlayMax -analogClock12pixel), ((uint32_t)colSec[3] << 24)| ((uint32_t)colSec[0] << 16) | ((uint32_t)colSec[1] << 8) | colSec[2]); - } else - { - strip.setRange(analogClock12pixel, analogClock12pixel + pixelCnt, ((uint32_t)colSec[3] << 24)| ((uint32_t)colSec[0] << 16) | ((uint32_t)colSec[1] << 8) | colSec[2]); - } - } - overlayRefreshMs = 998; -} - - -void handleOverlayDraw() { - if (overlayCurrent != 1) return; //only analog clock - _overlayAnalogClock(); -} diff --git a/wled00/wled19_json.ino b/wled00/wled19_json.ino deleted file mode 100644 index ae3e95a05..000000000 --- a/wled00/wled19_json.ino +++ /dev/null @@ -1,381 +0,0 @@ -/* - * JSON API (De)serialization - */ - -void deserializeSegment(JsonObject elem, byte it) -{ - byte id = elem["id"] | it; - if (id < strip.getMaxSegments()) - { - WS2812FX::Segment& seg = strip.getSegment(id); - uint16_t start = elem["start"] | seg.start; - int stop = elem["stop"] | -1; - - if (stop < 0) { - uint16_t len = elem["len"]; - stop = (len > 0) ? start + len : seg.stop; - } - uint16_t grp = elem["grp"] | seg.grouping; - uint16_t spc = elem["spc"] | seg.spacing; - strip.setSegment(id, start, stop, grp, spc); - - JsonArray colarr = elem["col"]; - if (!colarr.isNull()) - { - for (uint8_t i = 0; i < 3; i++) - { - JsonArray colX = colarr[i]; - if (colX.isNull()) break; - byte sz = colX.size(); - if (sz > 0 && sz < 5) - { - int rgbw[] = {0,0,0,0}; - byte cp = copyArray(colX, rgbw); - seg.colors[i] = ((rgbw[3] << 24) | ((rgbw[0]&0xFF) << 16) | ((rgbw[1]&0xFF) << 8) | ((rgbw[2]&0xFF))); - if (cp == 1 && rgbw[0] == 0) seg.colors[i] = 0; - if (id == strip.getMainSegmentId()) //temporary - { - if (i == 0) {col[0] = rgbw[0]; col[1] = rgbw[1]; col[2] = rgbw[2]; col[3] = rgbw[3];} - if (i == 1) {colSec[0] = rgbw[0]; colSec[1] = rgbw[1]; colSec[2] = rgbw[2]; colSec[3] = rgbw[3];} - } - } - } - } - - //if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal); - seg.setOption(0, elem["sel"] | seg.getOption(0)); //selected - seg.setOption(1, elem["rev"] | seg.getOption(1)); //reverse - //int cln = seg_0["cln"]; - //temporary, strip object gets updated via colorUpdated() - if (id == strip.getMainSegmentId()) { - effectCurrent = elem["fx"] | effectCurrent; - effectSpeed = elem["sx"] | effectSpeed; - effectIntensity = elem["ix"] | effectIntensity; - effectPalette = elem["pal"] | effectPalette; - } else { //permanent - byte fx = elem["fx"] | seg.mode; - if (fx != seg.mode && fx < strip.getModeCount()) strip.setMode(id, fx); - seg.speed = elem["sx"] | seg.speed; - seg.intensity = elem["ix"] | seg.intensity; - seg.palette = elem["pal"] | seg.palette; - } - } -} - -bool deserializeState(JsonObject root) -{ - strip.applyToAllSelected = false; - bool stateResponse = root["v"] | false; - - int ps = root["ps"] | -1; - if (ps >= 0) applyPreset(ps); - - bri = root["bri"] | bri; - - bool on = root["on"] | (bri > 0); - if (!on != !bri) toggleOnOff(); - - int tr = root["transition"] | -1; - if (tr >= 0) - { - transitionDelay = tr; - transitionDelay *= 100; - } - - tr = root["tt"] | -1; - if (tr >= 0) - { - transitionDelayTemp = tr; - transitionDelayTemp *= 100; - jsonTransitionOnce = true; - } - - int cy = root["pl"] | -2; - if (cy > -2) presetCyclingEnabled = (cy >= 0); - JsonObject ccnf = root["ccnf"]; - presetCycleMin = ccnf["min"] | presetCycleMin; - presetCycleMax = ccnf["max"] | presetCycleMax; - tr = ccnf["time"] | -1; - if (tr >= 2) - { - presetCycleTime = tr; - presetCycleTime *= 100; - } - - JsonObject nl = root["nl"]; - nightlightActive = nl["on"] | nightlightActive; - nightlightDelayMins = nl["dur"] | nightlightDelayMins; - nightlightFade = nl["fade"] | nightlightFade; - nightlightTargetBri = nl["tbri"] | nightlightTargetBri; - - JsonObject udpn = root["udpn"]; - notifyDirect = udpn["send"] | notifyDirect; - receiveNotifications = udpn["recv"] | receiveNotifications; - bool noNotification = udpn["nn"]; //send no notification just for this request - - int timein = root["time"] | -1; - if (timein != -1) setTime(timein); - - byte prevMain = strip.getMainSegmentId(); - strip.mainSegment = root["mainseg"] | prevMain; - if (strip.getMainSegmentId() != prevMain) setValuesFromMainSeg(); - - int it = 0; - JsonVariant segVar = root["seg"]; - if (segVar.is()) - { - int id = segVar["id"] | -1; - - if (id < 0) { //set all selected segments - bool didSet = false; - byte lowestActive = 99; - for (byte s = 0; s < strip.getMaxSegments(); s++) - { - WS2812FX::Segment sg = strip.getSegment(s); - if (sg.isActive()) - { - if (lowestActive == 99) lowestActive = s; - if (sg.isSelected()) { - deserializeSegment(segVar, s); - didSet = true; - } - } - } - if (!didSet && lowestActive < strip.getMaxSegments()) deserializeSegment(segVar, lowestActive); - } else { //set only the segment with the specified ID - deserializeSegment(segVar, it); - } - } else { - JsonArray segs = segVar.as(); - for (JsonObject elem : segs) - { - deserializeSegment(elem, it); - it++; - } - } - - colorUpdated(noNotification ? 5:1); - - ps = root["psave"] | -1; - if (ps >= 0) savePreset(ps); - - return stateResponse; -} - -void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id) -{ - root["id"] = id; - root["start"] = seg.start; - root["stop"] = seg.stop; - root["len"] = seg.stop - seg.start; - root["grp"] = seg.grouping; - root["spc"] = seg.spacing; - - JsonArray colarr = root.createNestedArray("col"); - - for (uint8_t i = 0; i < 3; i++) - { - JsonArray colX = colarr.createNestedArray(); - colX.add((seg.colors[i] >> 16) & 0xFF); - colX.add((seg.colors[i] >> 8) & 0xFF); - colX.add((seg.colors[i]) & 0xFF); - if (useRGBW) - colX.add((seg.colors[i] >> 24) & 0xFF); - } - - root["fx"] = seg.mode; - root["sx"] = seg.speed; - root["ix"] = seg.intensity; - root["pal"] = seg.palette; - root["sel"] = seg.isSelected(); - root["rev"] = seg.getOption(1); -} - - -void serializeState(JsonObject root) -{ - if (errorFlag) root["error"] = errorFlag; - - root["on"] = (bri > 0); - root["bri"] = briLast; - root["transition"] = transitionDelay/100; //in 100ms - - root["ps"] = currentPreset; - root["pss"] = savedPresets; - root["pl"] = (presetCyclingEnabled) ? 0: -1; - - //temporary for preser cycle - JsonObject ccnf = root.createNestedObject("ccnf"); - ccnf["min"] = presetCycleMin; - ccnf["max"] = presetCycleMax; - ccnf["time"] = presetCycleTime/100; - - JsonObject nl = root.createNestedObject("nl"); - nl["on"] = nightlightActive; - nl["dur"] = nightlightDelayMins; - nl["fade"] = nightlightFade; - nl["tbri"] = nightlightTargetBri; - - JsonObject udpn = root.createNestedObject("udpn"); - udpn["send"] = notifyDirect; - udpn["recv"] = receiveNotifications; - - root["mainseg"] = strip.getMainSegmentId(); - - JsonArray seg = root.createNestedArray("seg"); - for (byte s = 0; s < strip.getMaxSegments(); s++) - { - WS2812FX::Segment sg = strip.getSegment(s); - if (sg.isActive()) - { - JsonObject seg0 = seg.createNestedObject(); - serializeSegment(seg0, sg, s); - } - } -} - -void serializeInfo(JsonObject root) -{ - root["ver"] = versionString; - root["vid"] = VERSION; - - JsonObject leds = root.createNestedObject("leds"); - leds["count"] = ledCount; - leds["rgbw"] = useRGBW; - leds["wv"] = useRGBW && !autoRGBtoRGBW; //should a white channel slider be displayed? - JsonArray leds_pin = leds.createNestedArray("pin"); - leds_pin.add(LEDPIN); - - leds["pwr"] = strip.currentMilliamps; - leds["maxpwr"] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0; - leds["maxseg"] = strip.getMaxSegments(); - leds["seglock"] = false; //will be used in the future to prevent modifications to segment config - - root["str"] = syncToggleReceive; - - root["name"] = serverDescription; - root["udpport"] = udpPort; - root["live"] = (bool)realtimeMode; - root["fxcount"] = strip.getModeCount(); - root["palcount"] = strip.getPaletteCount(); - - JsonObject wifi_info = root.createNestedObject("wifi"); - wifi_info["bssid"] = WiFi.BSSIDstr(); - int qrssi = WiFi.RSSI(); - wifi_info["rssi"] = qrssi; - wifi_info["signal"] = getSignalQuality(qrssi); - wifi_info["channel"] = WiFi.channel(); - - #ifdef ARDUINO_ARCH_ESP32 - root["arch"] = "esp32"; - root["core"] = ESP.getSdkVersion(); - //root["maxalloc"] = ESP.getMaxAllocHeap(); - root["lwip"] = 0; - #else - root["arch"] = "esp8266"; - root["core"] = ESP.getCoreVersion(); - //root["maxalloc"] = ESP.getMaxFreeBlockSize(); - root["lwip"] = LWIP_VERSION_MAJOR; - #endif - - root["freeheap"] = ESP.getFreeHeap(); - root["uptime"] = millis()/1000; - - byte os = 0; - #ifdef WLED_DEBUG - os = 0x80; - #endif - #ifndef WLED_DISABLE_ALEXA - os += 0x40; - #endif - #ifndef WLED_DISABLE_BLYNK - os += 0x20; - #endif - #ifndef WLED_DISABLE_CRONIXIE - os += 0x10; - #endif - #ifndef WLED_DISABLE_FILESYSTEM - os += 0x08; - #endif - #ifndef WLED_DISABLE_HUESYNC - os += 0x04; - #endif - #ifdef WLED_ENABLE_ADALIGHT - os += 0x02; - #endif - #ifndef WLED_DISABLE_OTA - os += 0x01; - #endif - root["opt"] = os; - - root["brand"] = "WLED"; - root["product"] = "DIY light"; - root["btype"] = "src"; - root["mac"] = escapedMac; -} - -void serveJson(AsyncWebServerRequest* request) -{ - byte subJson = 0; - const String& url = request->url(); - if (url.indexOf("state") > 0) subJson = 1; - else if (url.indexOf("info") > 0) subJson = 2; - else if (url.indexOf("live") > 0) { - serveLiveLeds(request); - return; - } - else if (url.indexOf("eff") > 0) { - request->send_P(200, "application/json", JSON_mode_names); - return; - } - else if (url.indexOf("pal") > 0) { - request->send_P(200, "application/json", JSON_palette_names); - return; - } - else if (url.length() > 6) { //not just /json - request->send( 501, "application/json", "{\"error\":\"Not implemented\"}"); - return; - } - - AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject doc = response->getRoot(); - - switch (subJson) - { - case 1: //state - serializeState(doc); break; - case 2: //info - serializeInfo(doc); break; - default: //all - JsonObject state = doc.createNestedObject("state"); - serializeState(state); - JsonObject info = doc.createNestedObject("info"); - serializeInfo(info); - doc["effects"] = serialized((const __FlashStringHelper*)JSON_mode_names); - doc["palettes"] = serialized((const __FlashStringHelper*)JSON_palette_names); - } - - response->setLength(); - request->send(response); -} - -#define MAX_LIVE_LEDS 180 - -void serveLiveLeds(AsyncWebServerRequest* request) -{ - byte used = ledCount; - byte n = (used -1) /MAX_LIVE_LEDS +1; //only serve every n'th LED if count over MAX_LIVE_LEDS - char buffer[2000] = "{\"leds\":["; - olen = 9; - obuf = buffer; - - for (uint16_t i= 0; i < used; i += n) - { - olen += sprintf(buffer + olen, "\"%06X\",", strip.getPixelColor(i)); - } - olen -= 1; - oappend("],\"n\":"); - oappendi(n); - oappend("}"); - request->send(200, "application/json", buffer); -} diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp new file mode 100644 index 000000000..cc72fd1de --- /dev/null +++ b/wled00/wled_eeprom.cpp @@ -0,0 +1,477 @@ +#include +#include "wled.h" + +/* + * DEPRECATED, do not use for new settings + * Only used to restore config from pre-0.11 installations using the deEEP() methods + * + * Methods to handle saving and loading to non-volatile memory + * EEPROM Map: https://github.com/Aircoookie/WLED/wiki/EEPROM-Map + */ + +//eeprom Version code, enables default settings instead of 0 init on update +#define EEPVER 22 +#define EEPSIZE 2560 //Maximum is 4096 +//0 -> old version, default +//1 -> 0.4p 1711272 and up +//2 -> 0.4p 1711302 and up +//3 -> 0.4 1712121 and up +//4 -> 0.5.0 and up +//5 -> 0.5.1 and up +//6 -> 0.6.0 and up +//7 -> 0.7.1 and up +//8 -> 0.8.0-a and up +//9 -> 0.8.0 +//10-> 0.8.2 +//11-> 0.8.5-dev #mqttauth @TimothyBrown +//12-> 0.8.7-dev +//13-> 0.9.0-dev +//14-> 0.9.0-b1 +//15-> 0.9.0-b3 +//16-> 0.9.1 +//17-> 0.9.1-dmx +//18-> 0.9.1-e131 +//19-> 0.9.1n +//20-> 0.9.1p +//21-> 0.10.1p +//22-> 2009260 + +/* + * Erase all (pre 0.11) configuration data on factory reset + */ +void clearEEPROM() +{ + EEPROM.begin(EEPSIZE); + for (int i = 0; i < EEPSIZE; i++) + { + EEPROM.write(i, 0); + } + EEPROM.end(); +} + + +void readStringFromEEPROM(uint16_t pos, char* str, uint16_t len) +{ + for (int i = 0; i < len; ++i) + { + str[i] = EEPROM.read(pos + i); + if (str[i] == 0) return; + } + str[len] = 0; //make sure every string is properly terminated. str must be at least len +1 big. +} + +/* + * Read all configuration from flash + */ +void loadSettingsFromEEPROM() +{ + if (EEPROM.read(233) != 233) //first boot/reset to default + { + DEBUG_PRINTLN(F("EEPROM settings invalid, using defaults...")); + return; + } + int lastEEPROMversion = EEPROM.read(377); //last EEPROM version before update + + + readStringFromEEPROM( 0, clientSSID, 32); + readStringFromEEPROM( 32, clientPass, 64); + readStringFromEEPROM( 96, cmDNS, 32); + readStringFromEEPROM(128, apSSID, 32); + readStringFromEEPROM(160, apPass, 64); + + nightlightDelayMinsDefault = EEPROM.read(224); + nightlightDelayMins = nightlightDelayMinsDefault; + nightlightMode = EEPROM.read(225); + notifyDirectDefault = EEPROM.read(226); + notifyDirect = notifyDirectDefault; + + apChannel = EEPROM.read(227); + if (apChannel > 13 || apChannel < 1) apChannel = 1; + apHide = EEPROM.read(228); + if (apHide > 1) apHide = 1; + ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); if (ledCount > MAX_LEDS || ledCount == 0) ledCount = 30; + + notifyButton = EEPROM.read(230); + notifyTwice = EEPROM.read(231); + buttonEnabled = EEPROM.read(232); + + staticIP[0] = EEPROM.read(234); + staticIP[1] = EEPROM.read(235); + staticIP[2] = EEPROM.read(236); + staticIP[3] = EEPROM.read(237); + staticGateway[0] = EEPROM.read(238); + staticGateway[1] = EEPROM.read(239); + staticGateway[2] = EEPROM.read(240); + staticGateway[3] = EEPROM.read(241); + staticSubnet[0] = EEPROM.read(242); + staticSubnet[1] = EEPROM.read(243); + staticSubnet[2] = EEPROM.read(244); + staticSubnet[3] = EEPROM.read(245); + + briS = EEPROM.read(249); bri = briS; + if (!EEPROM.read(369)) + { + bri = 0; briLast = briS; + } + receiveNotificationBrightness = EEPROM.read(250); + fadeTransition = EEPROM.read(251); + transitionDelayDefault = EEPROM.read(253) + ((EEPROM.read(254) << 8) & 0xFF00); + transitionDelay = transitionDelayDefault; + briMultiplier = EEPROM.read(255); + + readStringFromEEPROM(256, otaPass, 32); + + nightlightTargetBri = EEPROM.read(288); + otaLock = EEPROM.read(289); + udpPort = EEPROM.read(290) + ((EEPROM.read(291) << 8) & 0xFF00); + + readStringFromEEPROM(292, serverDescription, 32); + + ntpEnabled = EEPROM.read(327); + currentTimezone = EEPROM.read(328); + useAMPM = EEPROM.read(329); + strip.gammaCorrectBri = EEPROM.read(330); + strip.gammaCorrectCol = EEPROM.read(331); + overlayDefault = EEPROM.read(332); + if (lastEEPROMversion < 8 && overlayDefault > 0) overlayDefault--; //overlay mode 1 (solid) was removed + + alexaEnabled = EEPROM.read(333); + + readStringFromEEPROM(334, alexaInvocationName, 32); + + notifyAlexa = EEPROM.read(366); + arlsOffset = EEPROM.read(368); + if (!EEPROM.read(367)) arlsOffset = -arlsOffset; + turnOnAtBoot = EEPROM.read(369); + strip.isRgbw = EEPROM.read(372); + //374 - strip.paletteFade + + apBehavior = EEPROM.read(376); + + //377 = lastEEPROMversion + if (lastEEPROMversion > 3) { + aOtaEnabled = EEPROM.read(390); + receiveNotificationColor = EEPROM.read(391); + receiveNotificationEffects = EEPROM.read(392); + } + receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); + + if (lastEEPROMversion > 4) { + huePollingEnabled = EEPROM.read(2048); + //hueUpdatingEnabled = EEPROM.read(2049); + for (int i = 2050; i < 2054; ++i) + { + hueIP[i-2050] = EEPROM.read(i); + } + + readStringFromEEPROM(2054, hueApiKey, 46); + + huePollIntervalMs = EEPROM.read(2100) + ((EEPROM.read(2101) << 8) & 0xFF00); + notifyHue = EEPROM.read(2102); + hueApplyOnOff = EEPROM.read(2103); + hueApplyBri = EEPROM.read(2104); + hueApplyColor = EEPROM.read(2105); + huePollLightId = EEPROM.read(2106); + } + if (lastEEPROMversion > 5) { + overlayMin = EEPROM.read(2150); + overlayMax = EEPROM.read(2151); + analogClock12pixel = EEPROM.read(2152); + analogClock5MinuteMarks = EEPROM.read(2153); + analogClockSecondsTrail = EEPROM.read(2154); + countdownMode = EEPROM.read(2155); + countdownYear = EEPROM.read(2156); + countdownMonth = EEPROM.read(2157); + countdownDay = EEPROM.read(2158); + countdownHour = EEPROM.read(2159); + countdownMin = EEPROM.read(2160); + countdownSec = EEPROM.read(2161); + setCountdown(); + + readStringFromEEPROM(2165, cronixieDisplay, 6); + cronixieBacklight = EEPROM.read(2171); + + //macroBoot = EEPROM.read(2175); + macroAlexaOn = EEPROM.read(2176); + macroAlexaOff = EEPROM.read(2177); + macroButton = EEPROM.read(2178); + macroLongPress = EEPROM.read(2179); + macroCountdown = EEPROM.read(2180); + macroNl = EEPROM.read(2181); + macroDoublePress = EEPROM.read(2182); + if (macroDoublePress > 16) macroDoublePress = 0; + } + + if (lastEEPROMversion > 6) + { + e131Universe = EEPROM.read(2190) + ((EEPROM.read(2191) << 8) & 0xFF00); + e131Multicast = EEPROM.read(2192); + realtimeTimeoutMs = EEPROM.read(2193) + ((EEPROM.read(2194) << 8) & 0xFF00); + arlsForceMaxBri = EEPROM.read(2195); + arlsDisableGammaCorrection = EEPROM.read(2196); + } + + if (lastEEPROMversion > 7) + { + strip.paletteFade = EEPROM.read(374); + strip.paletteBlend = EEPROM.read(382); + + for (int i = 0; i < 8; ++i) + { + timerHours[i] = EEPROM.read(2260 + i); + timerMinutes[i] = EEPROM.read(2270 + i); + timerWeekday[i] = EEPROM.read(2280 + i); + timerMacro[i] = EEPROM.read(2290 + i); + if (timerMacro[i] > 0) timerMacro[i] += 16; //add 16 to work with macro --> preset mapping + if (timerWeekday[i] == 0) timerWeekday[i] = 255; + if (timerMacro[i] == 0) timerWeekday[i] = timerWeekday[i] & 0b11111110; + } + } + + if (lastEEPROMversion > 8) + { + readStringFromEEPROM(2300, mqttServer, 32); + readStringFromEEPROM(2333, mqttDeviceTopic, 32); + readStringFromEEPROM(2366, mqttGroupTopic, 32); + } + + if (lastEEPROMversion > 9) + { + strip.setColorOrder(EEPROM.read(383)); + irEnabled = EEPROM.read(385); + strip.ablMilliampsMax = EEPROM.read(387) + ((EEPROM.read(388) << 8) & 0xFF00); + } else if (lastEEPROMversion > 1) //ABL is off by default when updating from version older than 0.8.2 + { + strip.ablMilliampsMax = 65000; + } else { + strip.ablMilliampsMax = ABL_MILLIAMPS_DEFAULT; + } + + if (lastEEPROMversion > 10) + { + readStringFromEEPROM(2399, mqttUser, 40); + readStringFromEEPROM(2440, mqttPass, 40); + readStringFromEEPROM(2481, mqttClientID, 40); + mqttPort = EEPROM.read(2522) + ((EEPROM.read(2523) << 8) & 0xFF00); + } + + if (lastEEPROMversion > 11) + { + strip.milliampsPerLed = EEPROM.read(375); + } else if (strip.ablMilliampsMax == 65000) //65000 indicates disabled ABL in <0.8.7 + { + strip.ablMilliampsMax = ABL_MILLIAMPS_DEFAULT; + strip.milliampsPerLed = 0; //disable ABL + } + if (lastEEPROMversion > 12) + { + readStringFromEEPROM(990, ntpServerName, 32); + } + if (lastEEPROMversion > 13) + { + mqttEnabled = EEPROM.read(2299); + syncToggleReceive = EEPROM.read(397); + } else { + mqttEnabled = true; + syncToggleReceive = false; + } + + if (lastEEPROMversion > 14) + { + DMXAddress = EEPROM.read(2197) + ((EEPROM.read(2198) << 8) & 0xFF00); + DMXMode = EEPROM.read(2199); + } else { + DMXAddress = 1; + DMXMode = DMX_MODE_MULTIPLE_RGB; + } + + //if (lastEEPROMversion > 15) + //{ + noWifiSleep = EEPROM.read(370); + //} + + if (lastEEPROMversion > 17) + { + e131SkipOutOfSequence = EEPROM.read(2189); + } else { + e131SkipOutOfSequence = true; + } + + if (lastEEPROMversion > 18) + { + e131Port = EEPROM.read(2187) + ((EEPROM.read(2188) << 8) & 0xFF00); + } + + #ifdef WLED_ENABLE_DMX + if (lastEEPROMversion > 19) + { + e131ProxyUniverse = EEPROM.read(2185) + ((EEPROM.read(2186) << 8) & 0xFF00); + } + #endif + + if (lastEEPROMversion > 21) { + udpPort2 = EEPROM.read(378) + ((EEPROM.read(379) << 8) & 0xFF00); + } + + receiveDirect = !EEPROM.read(2200); + notifyMacro = EEPROM.read(2201); + + strip.rgbwMode = EEPROM.read(2203); + skipFirstLed = EEPROM.read(2204); + + if (EEPROM.read(2210) || EEPROM.read(2211) || EEPROM.read(2212)) + { + presetCyclingEnabled = EEPROM.read(2205); + presetCycleTime = EEPROM.read(2206) + ((EEPROM.read(2207) << 8) & 0xFF00); + if (lastEEPROMversion < 21) presetCycleTime /= 100; //was stored in ms, now is in tenths of a second + presetCycleMin = EEPROM.read(2208); + presetCycleMax = EEPROM.read(2209); + //was presetApplyBri = EEPROM.read(2210); + //was presetApplyCol = EEPROM.read(2211); + //was presetApplyFx = EEPROM.read(2212); + } + + bootPreset = EEPROM.read(389); + wifiLock = EEPROM.read(393); + utcOffsetSecs = EEPROM.read(394) + ((EEPROM.read(395) << 8) & 0xFF00); + if (EEPROM.read(396)) utcOffsetSecs = -utcOffsetSecs; //negative + //!EEPROM.read(399); was enableSecTransition + + //favorite setting (preset) memory (25 slots/ each 20byte) + //400 - 899 reserved + + //custom macro memory (16 slots/ each 64byte) + //1024-2047 reserved + + readStringFromEEPROM(2220, blynkApiKey, 35); + if (strlen(blynkApiKey) < 25) blynkApiKey[0] = 0; + + #ifdef WLED_ENABLE_DMX + // DMX (2530 - 2549)2535 + DMXChannels = EEPROM.read(2530); + DMXGap = EEPROM.read(2531) + ((EEPROM.read(2532) << 8) & 0xFF00); + DMXStart = EEPROM.read(2533) + ((EEPROM.read(2534) << 8) & 0xFF00); + + for (int i=0;i<15;i++) { + DMXFixtureMap[i] = EEPROM.read(2535+i); + } //last used: 2549 + DMXStartLED = EEPROM.read(2550); + #endif + + //Usermod memory + //2551 - 2559 reserved for Usermods, usable by default + //2560 - 2943 usable, NOT reserved (need to increase EEPSIZE accordingly, new WLED core features may override this section) + //2944 - 3071 reserved for Usermods (need to increase EEPSIZE to 3072 in const.h) + + overlayCurrent = overlayDefault; +} + + +//provided for increased compatibility with usermods written for v0.10 +void applyMacro(byte index) { + applyPreset(index+16); +} + + +// De-EEPROM routine, upgrade from previous versions to v0.11 +void deEEP() { + if (WLED_FS.exists("/presets.json")) return; + + DEBUG_PRINTLN(F("Preset file not found, attempting to load from EEPROM")); + DEBUGFS_PRINTLN(F("Allocating saving buffer for dEEP")); + DynamicJsonDocument dDoc(JSON_BUFFER_SIZE *2); + JsonObject sObj = dDoc.to(); + sObj.createNestedObject("0"); + + EEPROM.begin(EEPSIZE); + if (EEPROM.read(233) == 233) { //valid EEPROM save + for (uint16_t index = 1; index <= 16; index++) { //copy presets to presets.json + uint16_t i = 380 + index*20; + byte ver = EEPROM.read(i); + + if ((index < 16 && ver != 1) || (index == 16 && (ver < 2 || ver > 3))) continue; + + char nbuf[16]; + sprintf(nbuf, "%d", index); + + JsonObject pObj = sObj.createNestedObject(nbuf); + + sprintf_P(nbuf, (char*)F("Preset %d"), index); + pObj["n"] = nbuf; + + pObj["bri"] = EEPROM.read(i+1); + + if (index < 16) { + JsonObject segObj = pObj.createNestedObject("seg"); + + JsonArray colarr = segObj.createNestedArray("col"); + + byte numChannels = (strip.isRgbw)? 4:3; + + for (uint8_t k = 0; k < 3; k++) //k=0 primary (i+2) k=1 secondary (i+6) k=2 tertiary color (i+12) + { + JsonArray colX = colarr.createNestedArray(); + uint16_t memloc = i + 6*k; + if (k == 0) memloc += 2; + + for (byte j = 0; j < numChannels; j++) colX.add(EEPROM.read(memloc + j)); + } + + segObj[F("fx")] = EEPROM.read(i+10); + segObj[F("sx")] = EEPROM.read(i+11); + segObj[F("ix")] = EEPROM.read(i+16); + segObj[F("pal")] = EEPROM.read(i+17); + } else { + WS2812FX::Segment* seg = strip.getSegments(); + memcpy(seg, EEPROM.getDataPtr() +i+2, 240); + if (ver == 2) { //versions before 2004230 did not have opacity + for (byte j = 0; j < strip.getMaxSegments(); j++) + { + strip.getSegment(j).opacity = 255; + strip.getSegment(j).setOption(SEG_OPTION_ON, 1); + } + } + setValuesFromMainSeg(); + serializeState(pObj, true, false, true); + + strip.resetSegments(); + setValuesFromMainSeg(); + } + } + + + + for (uint16_t index = 1; index <= 16; index++) { //copy macros to presets.json + char m[65]; + readStringFromEEPROM(1024+64*(index-1), m, 64); + if (m[0]) { //macro exists + char nbuf[16]; + sprintf(nbuf, "%d", index + 16); + JsonObject pObj = sObj.createNestedObject(nbuf); + sprintf_P(nbuf, "Z Macro %d", index); + pObj["n"] = nbuf; + pObj["win"] = m; + } + } + } + + EEPROM.end(); + + File f = WLED_FS.open("/presets.json", "w"); + if (!f) { + errorFlag = ERR_FS_GENERAL; + return; + } + serializeJson(dDoc, f); + f.close(); + DEBUG_PRINTLN(F("deEEP complete!")); +} + +void deEEPSettings() { + DEBUG_PRINTLN(F("Restore settings from EEPROM")); + EEPROM.begin(EEPSIZE); + loadSettingsFromEEPROM(); + EEPROM.end(); + + serializeConfig(); +} \ No newline at end of file diff --git a/wled00/wled04_file.ino b/wled00/wled_serial.cpp similarity index 50% rename from wled00/wled04_file.ino rename to wled00/wled_serial.cpp index de7523281..de4c3697b 100644 --- a/wled00/wled04_file.ino +++ b/wled00/wled_serial.cpp @@ -1,6 +1,9 @@ +#include "wled.h" + /* - * Utility for SPIFFS filesystem & Serial console + * Adalight and TPM2 handler */ + enum class AdaState { Header_A, Header_d, @@ -10,7 +13,10 @@ enum class AdaState { Header_CountCheck, Data_Red, Data_Green, - Data_Blue + Data_Blue, + TPM2_Header_Type, + TPM2_Header_CountHi, + TPM2_Header_CountLo }; void handleSerial() @@ -30,6 +36,9 @@ void handleSerial() switch (state) { case AdaState::Header_A: if (next == 'A') state = AdaState::Header_d; + else if (next == 0xC9) { //TPM2 start byte + state = AdaState::TPM2_Header_Type; + } break; case AdaState::Header_d: if (next == 'd') state = AdaState::Header_a; @@ -54,6 +63,20 @@ void handleSerial() if (check == next) state = AdaState::Data_Red; else state = AdaState::Header_A; break; + case AdaState::TPM2_Header_Type: + state = AdaState::Header_A; //(unsupported) TPM2 command or invalid type + if (next == 0xDA) state = AdaState::TPM2_Header_CountHi; //TPM2 data + else if (next == 0xAA) Serial.write(0xAC); //TPM2 ping + break; + case AdaState::TPM2_Header_CountHi: + pixel = 0; + count = (next * 0x100) /3; + state = AdaState::TPM2_Header_CountLo; + break; + case AdaState::TPM2_Header_CountLo: + count += next /3; + state = AdaState::Data_Red; + break; case AdaState::Data_Red: red = next; state = AdaState::Data_Green; @@ -64,13 +87,13 @@ void handleSerial() break; case AdaState::Data_Blue: byte blue = next; - setRealtimePixel(pixel++, red, green, blue, 0); + if (!realtimeOverride) setRealtimePixel(pixel++, red, green, blue, 0); if (--count > 0) state = AdaState::Data_Red; else { if (!realtimeMode && bri == 0) strip.setBrightness(briLast); - arlsLock(realtimeTimeoutMs, REALTIME_MODE_ADALIGHT); + realtimeLock(realtimeTimeoutMs, REALTIME_MODE_ADALIGHT); - strip.show(); + if (!realtimeOverride) strip.show(); state = AdaState::Header_A; } break; @@ -78,44 +101,3 @@ void handleSerial() } #endif } - - -#if !defined WLED_DISABLE_FILESYSTEM && defined WLED_ENABLE_FS_SERVING -//Un-comment any file types you need -String getContentType(AsyncWebServerRequest* request, String filename){ - if(request->hasArg("download")) return "application/octet-stream"; - else if(filename.endsWith(".htm")) return "text/html"; - else if(filename.endsWith(".html")) return "text/html"; -// else if(filename.endsWith(".css")) return "text/css"; -// else if(filename.endsWith(".js")) return "application/javascript"; - else if(filename.endsWith(".json")) return "application/json"; - else if(filename.endsWith(".png")) return "image/png"; -// else if(filename.endsWith(".gif")) return "image/gif"; - else if(filename.endsWith(".jpg")) return "image/jpeg"; - else if(filename.endsWith(".ico")) return "image/x-icon"; -// else if(filename.endsWith(".xml")) return "text/xml"; -// else if(filename.endsWith(".pdf")) return "application/x-pdf"; -// else if(filename.endsWith(".zip")) return "application/x-zip"; -// else if(filename.endsWith(".gz")) return "application/x-gzip"; - return "text/plain"; -} - -bool handleFileRead(AsyncWebServerRequest* request, String path){ - DEBUG_PRINTLN("FileRead: " + path); - if(path.endsWith("/")) path += "index.htm"; - String contentType = getContentType(request, path); - String pathWithGz = path + ".gz"; - if(SPIFFS.exists(pathWithGz)){ - request->send(SPIFFS, pathWithGz, contentType); - return true; - } - if(SPIFFS.exists(path)) { - request->send(SPIFFS, path, contentType); - return true; - } - return false; -} - -#else -bool handleFileRead(AsyncWebServerRequest*, String path){return false;} -#endif diff --git a/wled00/wled18_server.ino b/wled00/wled_server.cpp similarity index 55% rename from wled00/wled18_server.ino rename to wled00/wled_server.cpp index f7162d3e5..b03c4a362 100644 --- a/wled00/wled18_server.ino +++ b/wled00/wled_server.cpp @@ -1,5 +1,7 @@ +#include "wled.h" + /* - * Server page definitions + * Integrated HTTP web server page declarations */ //Is this an IP? @@ -23,7 +25,7 @@ bool captivePortal(AsyncWebServerRequest *request) if (!isIp(hostH) && hostH.indexOf("wled.me") < 0 && hostH.indexOf(cmDNS) < 0) { DEBUG_PRINTLN("Captive portal"); AsyncWebServerResponse *response = request->beginResponse(302); - response->addHeader("Location", "http://4.3.2.1"); + response->addHeader(F("Location"), F("http://4.3.2.1")); request->send(response); return true; } @@ -33,13 +35,19 @@ bool captivePortal(AsyncWebServerRequest *request) void initServer() { //CORS compatiblity - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "*"); - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "*"); + DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Origin"), "*"); + DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Methods"), "*"); + DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Headers"), "*"); - server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", PAGE_liveview); - }); + #ifdef WLED_ENABLE_WEBSOCKETS + server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", PAGE_liveviewws); + }); + #else + server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", PAGE_liveview); + }); + #endif //settings page server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request){ @@ -62,40 +70,12 @@ void initServer() }); server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 200,"Rebooting now...","Please wait ~10 seconds...",129); + serveMessage(request, 200,F("Rebooting now..."),F("Please wait ~10 seconds..."),129); doReboot = true; }); - server.on("/settings/wifi", HTTP_POST, [](AsyncWebServerRequest *request){ - if (!(wifiLock && otaLock)) handleSettingsSet(request, 1); - serveMessage(request, 200,"WiFi settings saved.","Please connect to the new IP (if changed)",129); - forceReconnect = true; - }); - - server.on("/settings/leds", HTTP_POST, [](AsyncWebServerRequest *request){ - handleSettingsSet(request, 2); - serveMessage(request, 200,"LED settings saved.","Redirecting...",1); - }); - - server.on("/settings/ui", HTTP_POST, [](AsyncWebServerRequest *request){ - handleSettingsSet(request, 3); - serveMessage(request, 200,"UI settings saved.","Redirecting...",1); - }); - - server.on("/settings/sync", HTTP_POST, [](AsyncWebServerRequest *request){ - handleSettingsSet(request, 4); - serveMessage(request, 200,"Sync settings saved.","Redirecting...",1); - }); - - server.on("/settings/time", HTTP_POST, [](AsyncWebServerRequest *request){ - handleSettingsSet(request, 5); - serveMessage(request, 200,"Time settings saved.","Redirecting...",1); - }); - - server.on("/settings/sec", HTTP_POST, [](AsyncWebServerRequest *request){ - handleSettingsSet(request, 6); - if (!doReboot) serveMessage(request, 200,"Security settings saved.","Rebooting, please wait ~10 seconds...",129); - doReboot = true; + server.on("/settings", HTTP_POST, [](AsyncWebServerRequest *request){ + serveSettings(request, true); }); server.on("/json", HTTP_GET, [](AsyncWebServerRequest *request){ @@ -105,22 +85,23 @@ void initServer() AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request) { bool verboseResponse = false; { //scope JsonDocument so it releases its buffer - DynamicJsonDocument jsonBuffer(8192); + DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE); DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); JsonObject root = jsonBuffer.as(); if (error || root.isNull()) { - request->send(400, "application/json", "{\"error\":10}"); return; + request->send(400, "application/json", F("{\"error\":9}")); return; } + fileDoc = &jsonBuffer; verboseResponse = deserializeState(root); + fileDoc = nullptr; } if (verboseResponse) { //if JSON contains "v" serveJson(request); return; } - request->send(200, "application/json", "{\"success\":true}"); + request->send(200, "application/json", F("{\"success\":true}")); }); server.addHandler(handler); - //*******DEPRECATED******* server.on("/version", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, "text/plain", (String)VERSION); }); @@ -132,27 +113,30 @@ void initServer() server.on("/freeheap", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, "text/plain", (String)ESP.getFreeHeap()); }); - //*******END*******/ server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", PAGE_usermod); }); + server.on("/url", HTTP_GET, [](AsyncWebServerRequest *request){ + URL_response(request); + }); + server.on("/teapot", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 418, "418. I'm a teapot.", "(Tangible Embedded Advanced Project Of Twinkling)", 254); + serveMessage(request, 418, F("418. I'm a teapot."), F("(Tangible Embedded Advanced Project Of Twinkling)"), 254); }); //if OTA is allowed if (!otaLock){ - #if !defined WLED_DISABLE_FILESYSTEM && defined WLED_ENABLE_FS_EDITOR + #ifdef WLED_ENABLE_FS_EDITOR #ifdef ARDUINO_ARCH_ESP32 - server.addHandler(new SPIFFSEditor(SPIFFS));//http_username,http_password)); + server.addHandler(new SPIFFSEditor(WLED_FS));//http_username,http_password)); #else - server.addHandler(new SPIFFSEditor());//http_username,http_password)); + server.addHandler(new SPIFFSEditor("","",WLED_FS));//http_username,http_password)); #endif #else server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, "Not implemented", "The SPIFFS editor is disabled in this build.", 254); + serveMessage(request, 501, "Not implemented", F("The FS editor is disabled in this build."), 254); }); #endif //init ota page @@ -164,13 +148,13 @@ void initServer() server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ if (Update.hasError()) { - serveMessage(request, 500, "Failed updating firmware!", "Please check your file and retry!", 254); return; + serveMessage(request, 500, F("Failed updating firmware!"), F("Please check your file and retry!"), 254); return; } - serveMessage(request, 200, "Successfully updated firmware!", "Please wait while the module reboots...", 131); + serveMessage(request, 200, F("Successfully updated firmware!"), F("Please wait while the module reboots..."), 131); doReboot = true; },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ if(!index){ - DEBUG_PRINTLN("OTA Update Start"); + DEBUG_PRINTLN(F("OTA Update Start")); #ifdef ESP8266 Update.runAsync(true); #endif @@ -179,32 +163,46 @@ void initServer() if(!Update.hasError()) Update.write(data, len); if(final){ if(Update.end(true)){ - DEBUG_PRINTLN("Update Success"); + DEBUG_PRINTLN(F("Update Success")); } else { - DEBUG_PRINTLN("Update Failed"); + DEBUG_PRINTLN(F("Update Failed")); } } }); #else server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, "Not implemented", "OTA updates are disabled in this build.", 254); + serveMessage(request, 501, "Not implemented", F("OTA updates are disabled in this build."), 254); }); #endif } else { server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 500, "Access Denied", "Please unlock OTA in security settings!", 254); + serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254); }); server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 500, "Access Denied", "Please unlock OTA in security settings!", 254); + serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254); }); } + + #ifdef WLED_ENABLE_DMX + server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send_P(200, "text/html", PAGE_dmxmap , dmxProcessor); + }); + #else + server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){ + serveMessage(request, 501, "Not implemented", F("DMX support is not enabled in this build."), 254); + }); + #endif server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ if (captivePortal(request)) return; serveIndexOrWelcome(request); }); + + #ifdef WLED_ENABLE_WEBSOCKETS + server.addHandler(&ws); + #endif //called when the url is not defined here, ajax-in; get-settings server.onNotFound([](AsyncWebServerRequest *request){ @@ -222,10 +220,8 @@ void initServer() #ifndef WLED_DISABLE_ALEXA if(espalexa.handleAlexaApiCall(request)) return; #endif - #ifdef WLED_ENABLE_FS_SERVING if(handleFileRead(request, request->url())) return; - #endif - request->send(404, "text/plain", "Not Found"); + request->send_P(404, "text/html", PAGE_404); }); } @@ -239,16 +235,32 @@ void serveIndexOrWelcome(AsyncWebServerRequest *request) } } +bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest* request) +{ + AsyncWebHeader* header = request->getHeader("If-None-Match"); + if (header && header->value() == String(VERSION)) { + request->send(304); + return true; + } + return false; +} + +void setStaticContentCacheHeaders(AsyncWebServerResponse *response) +{ + response->addHeader(F("Cache-Control"),"no-cache"); + response->addHeader(F("ETag"), String(VERSION)); +} void serveIndex(AsyncWebServerRequest* request) { - #ifdef WLED_ENABLE_FS_SERVING if (handleFileRead(request, "/index.htm")) return; - #endif + + if (handleIfNoneMatchCacheHeader(request)) return; AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_index, PAGE_index_L); - response->addHeader("Content-Encoding","gzip"); + response->addHeader(F("Content-Encoding"),"gzip"); + setStaticContentCacheHeaders(response); request->send(response); } @@ -258,25 +270,29 @@ String msgProcessor(const String& var) { if (var == "MSG") { String messageBody = messageHead; - messageBody += "

"; + messageBody += F(""); messageBody += messageSub; uint32_t optt = optionType; if (optt < 60) //redirect to settings after optionType seconds { - messageBody += ""; + messageBody += F(""); } else if (optt < 120) //redirect back after optionType-60 seconds, unused { //messageBody += ""; } else if (optt < 180) //reload parent after optionType-120 seconds { - messageBody += ""; + messageBody += F(""); } else if (optt == 253) { - messageBody += "

"; //button to settings + messageBody += F("

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

"; + messageBody += F("

"); } return messageBody; } @@ -284,7 +300,7 @@ String msgProcessor(const String& var) } -void serveMessage(AsyncWebServerRequest* request, uint16_t code, String headl, String subl="", byte optionT=255) +void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl, byte optionT) { messageHead = headl; messageSub = subl; @@ -301,12 +317,40 @@ String settingsProcessor(const String& var) getSettingsJS(optionType, buf); return String(buf); } + + #ifdef WLED_ENABLE_DMX + + if (var == "DMXMENU") { + return String(F("
")); + } + + #endif if (var == "SCSS") return String(FPSTR(PAGE_settingsCss)); return String(); } +String dmxProcessor(const String& var) +{ + String mapJS; + #ifdef WLED_ENABLE_DMX + if (var == "DMXVARS") { + mapJS += "\nCN=" + String(DMXChannels) + ";\n"; + mapJS += "CS=" + String(DMXStart) + ";\n"; + mapJS += "CG=" + String(DMXGap) + ";\n"; + mapJS += "LC=" + String(ledCount) + ";\n"; + mapJS += "var CH=["; + for (int i=0;i<15;i++) { + mapJS += String(DMXFixtureMap[i]) + ","; + } + mapJS += "0];"; + } + #endif + + return mapJS; +} -void serveSettings(AsyncWebServerRequest* request) + +void serveSettings(AsyncWebServerRequest* request, bool post) { byte subPage = 0; const String& url = request->url(); @@ -318,11 +362,39 @@ void serveSettings(AsyncWebServerRequest* request) else if (url.indexOf("sync") > 0) subPage = 4; else if (url.indexOf("time") > 0) subPage = 5; else if (url.indexOf("sec") > 0) subPage = 6; + #ifdef WLED_ENABLE_DMX // include only if DMX is enabled + else if (url.indexOf("dmx") > 0) subPage = 7; + #endif } else subPage = 255; //welcome page if (subPage == 1 && wifiLock && otaLock) { - serveMessage(request, 500, "Access Denied", "Please unlock OTA in security settings!", 254); return; + serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254); return; + } + + if (post) { //settings/set POST request, saving + if (subPage != 1 || !(wifiLock && otaLock)) handleSettingsSet(request, subPage); + + char s[32]; + char s2[45] = ""; + + switch (subPage) { + case 1: strcpy_P(s, PSTR("WiFi")); strcpy_P(s2, PSTR("Please connect to the new IP (if changed)")); forceReconnect = true; break; + case 2: strcpy_P(s, PSTR("LED")); break; + case 3: strcpy_P(s, PSTR("UI")); break; + case 4: strcpy_P(s, PSTR("Sync")); break; + case 5: strcpy_P(s, PSTR("Time")); break; + case 6: strcpy_P(s, PSTR("Security")); strcpy_P(s2, PSTR("Rebooting, please wait ~10 seconds...")); break; + case 7: strcpy_P(s, PSTR("DMX")); break; + } + + strcat_P(s, PSTR(" settings saved.")); + if (!s2[0]) strcpy_P(s2, PSTR("Redirecting...")); + + if (!doReboot) serveMessage(request, 200, s, s2, (subPage == 1 || subPage == 6) ? 129 : 1); + if (subPage == 6) doReboot = true; + + return; } #ifdef WLED_DISABLE_MOBILE_UI //disable welcome page if not enough storage @@ -339,7 +411,8 @@ void serveSettings(AsyncWebServerRequest* request) case 4: request->send_P(200, "text/html", PAGE_settings_sync, settingsProcessor); break; case 5: request->send_P(200, "text/html", PAGE_settings_time, settingsProcessor); break; case 6: request->send_P(200, "text/html", PAGE_settings_sec , settingsProcessor); break; + case 7: request->send_P(200, "text/html", PAGE_settings_dmx , settingsProcessor); break; case 255: request->send_P(200, "text/html", PAGE_welcome); break; - default: request->send_P(200, "text/html", PAGE_settings); + default: request->send_P(200, "text/html", PAGE_settings , settingsProcessor); } } diff --git a/wled00/ws.cpp b/wled00/ws.cpp new file mode 100644 index 000000000..2d1747559 --- /dev/null +++ b/wled00/ws.cpp @@ -0,0 +1,114 @@ +#include "wled.h" + +/* + * WebSockets server for bidirectional communication + */ +#ifdef WLED_ENABLE_WEBSOCKETS + +uint16_t wsLiveClientId = 0; +unsigned long wsLastLiveTime = 0; +//uint8_t* wsFrameBuffer = nullptr; + +#define WS_LIVE_INTERVAL 40 + +void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) +{ + if(type == WS_EVT_CONNECT){ + //client connected + sendDataWs(client); + //client->ping(); + } else if(type == WS_EVT_DISCONNECT){ + //client disconnected + if (client->id() == wsLiveClientId) wsLiveClientId = 0; + } else if(type == WS_EVT_DATA){ + //data packet + AwsFrameInfo * info = (AwsFrameInfo*)arg; + if(info->final && info->index == 0 && info->len == len){ + //the whole message is in a single frame and we got all of it's data (max. 1450byte) + if(info->opcode == WS_TEXT) + { + bool verboseResponse = false; + { //scope JsonDocument so it releases its buffer + DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE); + DeserializationError error = deserializeJson(jsonBuffer, data, len); + JsonObject root = jsonBuffer.as(); + if (error || root.isNull()) return; + + if (root.containsKey("lv")) + { + wsLiveClientId = root["lv"] ? client->id() : 0; + } + + verboseResponse = deserializeState(root); + } + if (verboseResponse || millis() - lastInterfaceUpdate < 1900) sendDataWs(client); //update if it takes longer than 100ms until next "broadcast" + } + } else { + //message is comprised of multiple frames or the frame is split into multiple packets + //if(info->index == 0){ + //if (!wsFrameBuffer && len < 4096) wsFrameBuffer = new uint8_t[4096]; + //} + + //if (wsFrameBuffer && len < 4096 && info->index + info->) + //{ + + //} + + if((info->index + len) == info->len){ + if(info->final){ + if(info->message_opcode == WS_TEXT) { + client->text(F("{\"error\":9}")); //we do not handle split packets right now + } + } + } + } + } else if(type == WS_EVT_ERROR){ + //error was received from the other end + + } else if(type == WS_EVT_PONG){ + //pong message was received (in response to a ping request maybe) + + } +} + +void sendDataWs(AsyncWebSocketClient * client) +{ + if (!ws.count()) return; + AsyncWebSocketMessageBuffer * buffer; + + { //scope JsonDocument so it releases its buffer + DynamicJsonDocument doc(JSON_BUFFER_SIZE); + JsonObject state = doc.createNestedObject("state"); + serializeState(state); + JsonObject info = doc.createNestedObject("info"); + serializeInfo(info); + size_t len = measureJson(doc); + buffer = ws.makeBuffer(len); + if (!buffer) return; //out of memory + + serializeJson(doc, (char *)buffer->get(), len +1); + } + if (client) { + client->text(buffer); + } else { + ws.textAll(buffer); + } +} + +void handleWs() +{ + if (millis() - wsLastLiveTime > WS_LIVE_INTERVAL) + { + ws.cleanupClients(); + bool success = true; + if (wsLiveClientId) + success = serveLiveLeds(nullptr, wsLiveClientId); + wsLastLiveTime = millis(); + if (!success) wsLastLiveTime -= 20; //try again in 20ms if failed due to non-empty WS queue + } +} + +#else +void handleWs() {} +void sendDataWs(AsyncWebSocketClient * client) {} +#endif \ No newline at end of file diff --git a/wled00/xml.cpp b/wled00/xml.cpp new file mode 100644 index 000000000..dc572a069 --- /dev/null +++ b/wled00/xml.cpp @@ -0,0 +1,516 @@ +#include "wled.h" + +/* + * Sending XML status files to client + */ + +//macro to convert F to const +#define SET_F(x) (const char*)F(x) + +//build XML response to HTTP /win API request +void XML_response(AsyncWebServerRequest *request, char* dest) +{ + char sbuf[(dest == nullptr)?1024:1]; //allocate local buffer if none passed + obuf = (dest == nullptr)? sbuf:dest; + + olen = 0; + oappend(SET_F("")); + oappendi((nightlightActive && nightlightMode > NL_MODE_SET) ? briT : bri); + oappend(SET_F("")); + + for (int i = 0; i < 3; i++) + { + oappend(""); + oappendi(col[i]); + oappend(""); + } + for (int i = 0; i < 3; i++) + { + oappend(""); + oappendi(colSec[i]); + oappend(""); + } + oappend(SET_F("")); + oappendi(notifyDirect); + oappend(SET_F("")); + oappendi(receiveNotifications); + oappend(SET_F("")); + oappendi(nightlightActive); + oappend(SET_F("")); + oappendi(nightlightMode > NL_MODE_SET); + oappend(SET_F("")); + oappendi(nightlightDelayMins); + oappend(SET_F("")); + oappendi(nightlightTargetBri); + oappend(SET_F("")); + oappendi(effectCurrent); + oappend(SET_F("")); + oappendi(effectSpeed); + oappend(SET_F("")); + oappendi(effectIntensity); + oappend(SET_F("")); + oappendi(effectPalette); + oappend(SET_F("")); + if (strip.rgbwMode) { + oappendi(col[3]); + } else { + oappend("-1"); + } + oappend(SET_F("")); + oappendi(colSec[3]); + oappend(SET_F("")); + oappendi((currentPreset < 1) ? 0:currentPreset); + oappend(SET_F("")); + oappendi(presetCyclingEnabled); + oappend(SET_F("")); + oappend(serverDescription); + if (realtimeMode) + { + oappend(SET_F(" (live)")); + } + oappend(SET_F("")); + oappendi(strip.getMainSegmentId()); + oappend(SET_F("")); + if (request != nullptr) request->send(200, "text/xml", obuf); +} + +void URL_response(AsyncWebServerRequest *request) +{ + char sbuf[256]; + char s2buf[100]; + obuf = s2buf; + olen = 0; + + char s[16]; + oappend(SET_F("http://")); + IPAddress localIP = Network.localIP(); + sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); + oappend(s); + + oappend(SET_F("/win&A=")); + oappendi(bri); + oappend(SET_F("&CL=h")); + for (int i = 0; i < 3; i++) + { + sprintf(s,"%02X", col[i]); + oappend(s); + } + oappend(SET_F("&C2=h")); + for (int i = 0; i < 3; i++) + { + sprintf(s,"%02X", colSec[i]); + oappend(s); + } + oappend(SET_F("&FX=")); + oappendi(effectCurrent); + oappend(SET_F("&SX=")); + oappendi(effectSpeed); + oappend(SET_F("&IX=")); + oappendi(effectIntensity); + oappend(SET_F("&FP=")); + oappendi(effectPalette); + + obuf = sbuf; + olen = 0; + + oappend(SET_F("")); + oappend(s2buf); + oappend(SET_F("")); + + if (request != nullptr) request->send(200, "text/html", obuf); +} + +//append a numeric setting to string buffer +void sappend(char stype, const char* key, int val) +{ + char ds[] = "d.Sf."; + + switch(stype) + { + case 'c': //checkbox + oappend(ds); + oappend(key); + oappend(".checked="); + oappendi(val); + oappend(";"); + break; + case 'v': //numeric + oappend(ds); + oappend(key); + oappend(".value="); + oappendi(val); + oappend(";"); + break; + case 'i': //selectedIndex + oappend(ds); + oappend(key); + oappend(SET_F(".selectedIndex=")); + oappendi(val); + oappend(";"); + break; + } +} + +//append a string setting to buffer +void sappends(char stype, const char* key, char* val) +{ + switch(stype) + { + case 's': //string (we can interpret val as char*) + oappend("d.Sf."); + oappend(key); + oappend(".value=\""); + oappend(val); + oappend("\";"); + break; + case 'm': //message + oappend(SET_F("d.getElementsByClassName")); + oappend(key); + oappend(SET_F(".innerHTML=\"")); + oappend(val); + oappend("\";"); + break; + } +} + + +//get values for settings form in javascript +void getSettingsJS(byte subPage, char* dest) +{ + //0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec + DEBUG_PRINT(F("settings resp")); + DEBUG_PRINTLN(subPage); + obuf = dest; + olen = 0; + + if (subPage <1 || subPage >7) return; + + if (subPage == 1) { + sappends('s',SET_F("CS"),clientSSID); + + byte l = strlen(clientPass); + char fpass[l+1]; //fill password field with *** + fpass[l] = 0; + memset(fpass,'*',l); + sappends('s',SET_F("CP"),fpass); + + char k[3]; k[2] = 0; //IP addresses + for (int i = 0; i<4; i++) + { + k[1] = 48+i; //ascii 0,1,2,3 + k[0] = 'I'; sappend('v',k,staticIP[i]); + k[0] = 'G'; sappend('v',k,staticGateway[i]); + k[0] = 'S'; sappend('v',k,staticSubnet[i]); + } + + sappends('s',SET_F("CM"),cmDNS); + sappend('i',SET_F("AB"),apBehavior); + sappends('s',SET_F("AS"),apSSID); + sappend('c',SET_F("AH"),apHide); + + l = strlen(apPass); + char fapass[l+1]; //fill password field with *** + fapass[l] = 0; + memset(fapass,'*',l); + sappends('s',SET_F("AP"),fapass); + + sappend('v',SET_F("AC"),apChannel); + sappend('c',SET_F("WS"),noWifiSleep); + + #ifdef WLED_USE_ETHERNET + sappend('v',SET_F("ETH"),ethernetType); + #else + //hide ethernet setting if not compiled in + oappend(SET_F("document.getElementById('ethd').style.display='none';")); + #endif + + if (Network.isConnected()) //is connected + { + char s[32]; + IPAddress localIP = Network.localIP(); + sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); + + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) + if (Network.isEthernet()) strcat_P(s ,SET_F(" (Ethernet)")); + #endif + sappends('m',SET_F("(\"sip\")[0]"),s); + } else + { + sappends('m',SET_F("(\"sip\")[0]"),(char*)F("Not connected")); + } + + if (WiFi.softAPIP()[0] != 0) //is active + { + char s[16]; + IPAddress apIP = WiFi.softAPIP(); + sprintf(s, "%d.%d.%d.%d", apIP[0], apIP[1], apIP[2], apIP[3]); + sappends('m',SET_F("(\"sip\")[1]"),s); + } else + { + sappends('m',SET_F("(\"sip\")[1]"),(char*)F("Not active")); + } + } + + if (subPage == 2) { + char nS[8]; + + // add usermod pins as d.um_p array (TODO: usermod config shouldn't use state. instead we should load "um" object from cfg.json) + /*DynamicJsonDocument doc(JSON_BUFFER_SIZE); + JsonObject mods = doc.createNestedObject(F("mods")); + usermods.addToJsonState(mods); + if (!mods.isNull()) { + uint8_t i=0; + oappend(SET_F("d.um_p=[")); + for (JsonPair kv : mods) { + if (strncmp_P(kv.key().c_str(),PSTR("pin_"),4) == 0) { + if (i++) oappend(SET_F(",")); + oappend(itoa((int)kv.value(),nS,10)); + } + } + oappend(SET_F("];")); + }*/ + + oappend(SET_F("bLimits(")); + oappend(itoa(WLED_MAX_BUSSES,nS,10)); + oappend(","); + oappend(itoa(MAX_LEDS_PER_BUS,nS,10)); + oappend(","); + oappend(itoa(MAX_LED_MEMORY,nS,10)); + oappend(SET_F(");")); + + oappend(SET_F("d.Sf.LC.max=")); //TODO Formula for max LEDs on ESP8266 depending on types. 500 DMA or 1500 UART (about 4kB mem usage) + oappendi(MAX_LEDS); + oappend(";"); + + sappend('v',SET_F("LC"),ledCount); + + for (uint8_t s=0; s < busses.getNumBusses(); s++){ + Bus* bus = busses.getBus(s); + char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin + char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length + char co[4] = "CO"; co[2] = 48+s; co[3] = 0; //strip color order + char lt[4] = "LT"; lt[2] = 48+s; lt[3] = 0; //strip type + char ls[4] = "LS"; ls[2] = 48+s; ls[3] = 0; //strip start LED + char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse + oappend(SET_F("addLEDs(1);")); + uint8_t pins[5]; + uint8_t nPins = bus->getPins(pins); + for (uint8_t i = 0; i < nPins; i++) { + lp[1] = 48+i; + if (pinManager.isPinOk(pins[i])) sappend('v', lp, pins[i]); + } + sappend('v', lc, bus->getLength()); + sappend('v',lt,bus->getType()); + sappend('v',co,bus->getColorOrder()); + sappend('v',ls,bus->getStart()); + sappend('c',cv,bus->reversed); + } + sappend('v',SET_F("MA"),strip.ablMilliampsMax); + sappend('v',SET_F("LA"),strip.milliampsPerLed); + if (strip.currentMilliamps) + { + sappends('m',SET_F("(\"pow\")[0]"),(char*)""); + olen -= 2; //delete "; + oappendi(strip.currentMilliamps); + oappend(SET_F("mA\";")); + } + + sappend('v',SET_F("CA"),briS); + + sappend('v',SET_F("AW"),strip.rgbwMode); + + sappend('c',SET_F("BO"),turnOnAtBoot); + sappend('v',SET_F("BP"),bootPreset); + + sappend('c',SET_F("GB"),strip.gammaCorrectBri); + sappend('c',SET_F("GC"),strip.gammaCorrectCol); + sappend('c',SET_F("TF"),fadeTransition); + sappend('v',SET_F("TD"),transitionDelayDefault); + sappend('c',SET_F("PF"),strip.paletteFade); + sappend('v',SET_F("BF"),briMultiplier); + sappend('v',SET_F("TB"),nightlightTargetBri); + sappend('v',SET_F("TL"),nightlightDelayMinsDefault); + sappend('v',SET_F("TW"),nightlightMode); + sappend('i',SET_F("PB"),strip.paletteBlend); + sappend('c',SET_F("SL"),skipFirstLed); + sappend('v',SET_F("RL"),rlyPin); + sappend('c',SET_F("RM"),rlyMde); + sappend('v',SET_F("BT"),btnPin); + sappend('v',SET_F("IR"),irPin); + } + + if (subPage == 3) + { + sappends('s',SET_F("DS"),serverDescription); + sappend('c',SET_F("ST"),syncToggleReceive); + } + + if (subPage == 4) + { + sappend('c',SET_F("BT"),buttonEnabled); + sappend('v',SET_F("IR"),irEnabled); + sappend('v',SET_F("UP"),udpPort); + sappend('v',SET_F("U2"),udpPort2); + sappend('c',SET_F("RB"),receiveNotificationBrightness); + sappend('c',SET_F("RC"),receiveNotificationColor); + sappend('c',SET_F("RX"),receiveNotificationEffects); + sappend('c',SET_F("SD"),notifyDirectDefault); + sappend('c',SET_F("SB"),notifyButton); + sappend('c',SET_F("SH"),notifyHue); + sappend('c',SET_F("SM"),notifyMacro); + sappend('c',SET_F("S2"),notifyTwice); + + sappend('c',SET_F("NL"),nodeListEnabled); + sappend('c',SET_F("NB"),nodeBroadcastEnabled); + + sappend('c',SET_F("RD"),receiveDirect); + sappend('v',SET_F("EP"),e131Port); + sappend('c',SET_F("ES"),e131SkipOutOfSequence); + sappend('c',SET_F("EM"),e131Multicast); + sappend('v',SET_F("EU"),e131Universe); + sappend('v',SET_F("DA"),DMXAddress); + sappend('v',SET_F("DM"),DMXMode); + sappend('v',SET_F("ET"),realtimeTimeoutMs); + sappend('c',SET_F("FB"),arlsForceMaxBri); + sappend('c',SET_F("RG"),arlsDisableGammaCorrection); + sappend('v',SET_F("WO"),arlsOffset); + sappend('c',SET_F("AL"),alexaEnabled); + sappends('s',SET_F("AI"),alexaInvocationName); + sappend('c',SET_F("SA"),notifyAlexa); + sappends('s',SET_F("BK"),(char*)((blynkEnabled)?SET_F("Hidden"):"")); + sappends('s',SET_F("BH"),blynkHost); + sappend('v',SET_F("BP"),blynkPort); + + #ifdef WLED_ENABLE_MQTT + sappend('c',SET_F("MQ"),mqttEnabled); + sappends('s',SET_F("MS"),mqttServer); + sappend('v',SET_F("MQPORT"),mqttPort); + sappends('s',SET_F("MQUSER"),mqttUser); + byte l = strlen(mqttPass); + char fpass[l+1]; //fill password field with *** + fpass[l] = 0; + memset(fpass,'*',l); + sappends('s',SET_F("MQPASS"),fpass); + sappends('s',SET_F("MQCID"),mqttClientID); + sappends('s',SET_F("MD"),mqttDeviceTopic); + sappends('s',SET_F("MG"),mqttGroupTopic); + #endif + + #ifndef WLED_DISABLE_HUESYNC + sappend('v',SET_F("H0"),hueIP[0]); + sappend('v',SET_F("H1"),hueIP[1]); + sappend('v',SET_F("H2"),hueIP[2]); + sappend('v',SET_F("H3"),hueIP[3]); + sappend('v',SET_F("HL"),huePollLightId); + sappend('v',SET_F("HI"),huePollIntervalMs); + sappend('c',SET_F("HP"),huePollingEnabled); + sappend('c',SET_F("HO"),hueApplyOnOff); + sappend('c',SET_F("HB"),hueApplyBri); + sappend('c',SET_F("HC"),hueApplyColor); + char hueErrorString[25]; + switch (hueError) + { + case HUE_ERROR_INACTIVE : strcpy(hueErrorString,(char*)F("Inactive")); break; + case HUE_ERROR_ACTIVE : strcpy(hueErrorString,(char*)F("Active")); break; + case HUE_ERROR_UNAUTHORIZED : strcpy(hueErrorString,(char*)F("Unauthorized")); break; + case HUE_ERROR_LIGHTID : strcpy(hueErrorString,(char*)F("Invalid light ID")); break; + case HUE_ERROR_PUSHLINK : strcpy(hueErrorString,(char*)F("Link button not pressed")); break; + case HUE_ERROR_JSON_PARSING : strcpy(hueErrorString,(char*)F("JSON parsing error")); break; + case HUE_ERROR_TIMEOUT : strcpy(hueErrorString,(char*)F("Timeout")); break; + default: sprintf(hueErrorString,(char*)F("Bridge Error %i"),hueError); + } + + sappends('m',SET_F("(\"sip\")[0]"),hueErrorString); + #endif + } + + if (subPage == 5) + { + sappend('c',SET_F("NT"),ntpEnabled); + sappends('s',SET_F("NS"),ntpServerName); + sappend('c',SET_F("CF"),!useAMPM); + sappend('i',SET_F("TZ"),currentTimezone); + sappend('v',SET_F("UO"),utcOffsetSecs); + char tm[32]; + getTimeString(tm); + sappends('m',SET_F("(\"times\")[0]"),tm); + sappend('i',SET_F("OL"),overlayCurrent); + sappend('v',SET_F("O1"),overlayMin); + sappend('v',SET_F("O2"),overlayMax); + sappend('v',SET_F("OM"),analogClock12pixel); + sappend('c',SET_F("OS"),analogClockSecondsTrail); + sappend('c',SET_F("O5"),analogClock5MinuteMarks); + sappends('s',SET_F("CX"),cronixieDisplay); + sappend('c',SET_F("CB"),cronixieBacklight); + sappend('c',SET_F("CE"),countdownMode); + sappend('v',SET_F("CY"),countdownYear); + sappend('v',SET_F("CI"),countdownMonth); + sappend('v',SET_F("CD"),countdownDay); + sappend('v',SET_F("CH"),countdownHour); + sappend('v',SET_F("CM"),countdownMin); + sappend('v',SET_F("CS"),countdownSec); + + sappend('v',SET_F("A0"),macroAlexaOn); + sappend('v',SET_F("A1"),macroAlexaOff); + sappend('v',SET_F("MP"),macroButton); + sappend('v',SET_F("ML"),macroLongPress); + sappend('v',SET_F("MC"),macroCountdown); + sappend('v',SET_F("MN"),macroNl); + sappend('v',SET_F("MD"),macroDoublePress); + + char k[4]; + k[2] = 0; //Time macros + for (int i = 0; i<8; i++) + { + k[1] = 48+i; //ascii 0,1,2,3 + k[0] = 'H'; sappend('v',k,timerHours[i]); + k[0] = 'N'; sappend('v',k,timerMinutes[i]); + k[0] = 'T'; sappend('v',k,timerMacro[i]); + k[0] = 'W'; sappend('v',k,timerWeekday[i]); + } + } + + if (subPage == 6) + { + sappend('c',SET_F("NO"),otaLock); + sappend('c',SET_F("OW"),wifiLock); + sappend('c',SET_F("AO"),aOtaEnabled); + sappends('m',SET_F("(\"sip\")[0]"),(char*)F("WLED ")); + olen -= 2; //delete "; + oappend(versionString); + oappend(SET_F(" (build ")); + oappendi(VERSION); + oappend(SET_F(")\";")); + } + + #ifdef WLED_ENABLE_DMX // include only if DMX is enabled + if (subPage == 7) + { + sappend('v',SET_F("PU"),e131ProxyUniverse); + + sappend('v',SET_F("CN"),DMXChannels); + sappend('v',SET_F("CG"),DMXGap); + sappend('v',SET_F("CS"),DMXStart); + sappend('v',SET_F("SL"),DMXStartLED); + + sappend('i',SET_F("CH1"),DMXFixtureMap[0]); + sappend('i',SET_F("CH2"),DMXFixtureMap[1]); + sappend('i',SET_F("CH3"),DMXFixtureMap[2]); + sappend('i',SET_F("CH4"),DMXFixtureMap[3]); + sappend('i',SET_F("CH5"),DMXFixtureMap[4]); + sappend('i',SET_F("CH6"),DMXFixtureMap[5]); + sappend('i',SET_F("CH7"),DMXFixtureMap[6]); + sappend('i',SET_F("CH8"),DMXFixtureMap[7]); + sappend('i',SET_F("CH9"),DMXFixtureMap[8]); + sappend('i',SET_F("CH10"),DMXFixtureMap[9]); + sappend('i',SET_F("CH11"),DMXFixtureMap[10]); + sappend('i',SET_F("CH12"),DMXFixtureMap[11]); + sappend('i',SET_F("CH13"),DMXFixtureMap[12]); + sappend('i',SET_F("CH14"),DMXFixtureMap[13]); + sappend('i',SET_F("CH15"),DMXFixtureMap[14]); + } + #endif + oappend(SET_F("}")); +} diff --git a/wled_logo.png b/wled_logo.png deleted file mode 100644 index 905c793f7..000000000 Binary files a/wled_logo.png and /dev/null differ