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/.gitignore b/.gitignore index 444745143..db3138f50 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ .DS_Store .gitignore .clang-format +node_modules diff --git a/.travis.yml b/.travis.yml index 882fe54aa..16b4f2c1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,4 +40,4 @@ install: - platformio update script: # - platformio ci --project-conf=./platformio.ini - - platformio run \ No newline at end of file + - platformio run \ No newline at end of file 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/CHANGELOG.md b/CHANGELOG.md index dadc3c20a..e12a25f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,128 @@ ## WLED changelog +### Development versions after the 0.10.2 release + +#### 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 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/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..64f645b3d 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/wled_logo.png b/images/wled_logo.png similarity index 100% rename from wled_logo.png rename to images/wled_logo.png diff --git a/images/wled_logo_akemi.png b/images/wled_logo_akemi.png new file mode 100644 index 000000000..c0bf93db6 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..c553f1186 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..11973fa92 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2233 @@ +{ + "name": "wled", + "version": "0.10.2", + "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" + } + }, + "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==" + } + } + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "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-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": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", + "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", + "requires": { + "camel-case": "^3.0.0", + "clean-css": "^4.2.1", + "commander": "^2.19.0", + "he": "^1.2.0", + "param-case": "^2.1.1", + "relateurl": "^0.2.7", + "uglify-js": "^3.5.1" + }, + "dependencies": { + "uglify-js": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.3.tgz", + "integrity": "sha512-r5ImcL6QyzQGVimQoov3aL2ZScywrOgBXGndbWrdehKoSvGe/RmiE5Jpw/v+GvxODt6l2tpBXwA7n+qZVlHBMA==", + "requires": { + "commander": "~2.20.3" + } + } + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "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=" + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" + }, + "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" + } + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "requires": { + "lower-case": "^1.1.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" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "requires": { + "no-case": "^2.2.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=" + }, + "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==" + }, + "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" + } + }, + "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" + } + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" + }, + "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..11613d8fb --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "wled", + "version": "0.10.2", + "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": "^4.0.0", + "inliner": "^1.13.1", + "nodemon": "^2.0.4", + "zlib": "^1.0.5" + } +} diff --git a/platformio.ini b/platformio.ini index f37f16158..b96a5fec6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,11 +15,11 @@ extra_configs = # Please uncomment one of the lines below to select your board(s) # ------------------------------------------------------------------------------ -# Travis CI binaries (comment this out when building for single board) -;default_envs = d1_mini, esp01, esp01_1m_ota, esp32dev +# Travis CI binaries +default_envs = travis_esp8266, travis_esp32 # Release binaries -; default_envs = nodemcuv2, esp01, esp01_1m_ota, esp01_1m_full, esp32dev, custom_WS2801, custom_APA102, custom_LEDPIN_16, custom_LEDPIN_4, custom32_LEDPIN_16 +; default_envs = nodemcuv2, esp01_1m_full, esp32dev, custom_WS2801, custom_APA102, custom_LEDPIN_16, custom_LEDPIN_4, custom_LEDPIN_3, custom32_LEDPIN_16 # Single binaries (uncomment your board) ; default_envs = nodemcuv2 @@ -27,7 +27,7 @@ extra_configs = ; default_envs = esp01_1m_ota ; default_envs = esp01_1m_full ; default_envs = esp07 -default_envs = d1_mini +; default_envs = d1_mini ; default_envs = heltec_wifi_kit_8 ; default_envs = h803wf ; default_envs = d1_mini_debug @@ -38,6 +38,7 @@ default_envs = d1_mini ; default_envs = esp8285_5CH_H801 ; default_envs = d1_mini_5CH_Shojo_PCB ; default_envs = wemos_shield_esp32 +; default_envs = m5atom [common] # ------------------------------------------------------------------------------ @@ -66,14 +67,19 @@ 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_2_6_3 = espressif8266@2.3.3 -arduino_core_2_7_0 = espressif8266@2.5.0 +arduino_core_2_7_1 = espressif8266@2.5.1 +arduino_core_2_7_2 = espressif8266@2.6.0 +arduino_core_2_7_3 = espressif8266@2.6.1 +arduino_core_2_7_4 = espressif8266@2.6.2 # Development platforms arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage # Platform to use for ESP8266 -platform_latest = ${common.arduino_core_2_6_3} +platform_wled_default = ${common.arduino_core_2_7_4} +# We use 2.7.0+ on analog boards because of PWM flicker fix +platform_latest = ${common.arduino_core_2_7_4} # ------------------------------------------------------------------------------ # FLAGS: DEBUG @@ -120,6 +126,16 @@ build_flags = -g -w -DMQTT_MAX_PACKET_SIZE=1024 -DPIO_FRAMEWORK_ARDUINO_LWIP_HIG build_flags_esp8266 = ${common.build_flags} -DESP8266 build_flags_esp32 = ${common.build_flags} -DARDUINO_ARCH_ESP32 +# 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 + ldscript_512k = eagle.flash.512k.ld ;for older versions change this to eagle.flash.512k0.ld ldscript_1m0m = eagle.flash.1m.ld ;for older versions change this to eagle.flash.1m0.ld ldscript_2m1m = eagle.flash.2m1m.ld @@ -151,10 +167,12 @@ lib_deps = FastLED@3.3.2 NeoPixelBus@2.5.7 ESPAsyncTCP@1.2.0 - ESPAsyncUDP@697c75a025 + ESPAsyncUDP AsyncTCP@1.0.3 - Esp Async WebServer@1.2.0 + https://github.com/Aircoookie/ESPAsyncWebServer IRremoteESP8266@2.7.3 + #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 @@ -170,56 +188,60 @@ lib_ignore = [env:nodemcuv2] board = nodemcuv2 -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} +# Unsupported environment due to insufficient flash [env:esp01] board = esp01 -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_512k} build_flags = ${common.build_flags_esp8266} -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_CRONIXIE -D WLED_DISABLE_HUESYNC -D WLED_DISABLE_INFRARED -D WLED_DISABLE_MQTT -D WLED_DISABLE_WEBSOCKETS +# Unsupported environment due to insufficient flash [env:esp01_1m_ota] board = esp01_1m -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_1m0m} -build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_ALEXA -D WLED_DISABLE_BLYNK -D WLED_DISABLE_CRONIXIE -D WLED_DISABLE_HUESYNC -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_ALEXA -D WLED_DISABLE_BLYNK -D WLED_DISABLE_CRONIXIE + -D WLED_DISABLE_HUESYNC -D WLED_DISABLE_INFRARED -D WLED_DISABLE_MQTT -D WLED_DISABLE_WEBSOCKETS [env:esp01_1m_full] board = esp01_1m -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_1m0m} build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA [env:esp07] board = esp07 -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} [env:d1_mini] board = d1_mini -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} +upload_speed = 921600 board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} [env:heltec_wifi_kit_8] board = d1_mini -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} [env:h803wf] board = d1_mini -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} -D LEDPIN=1 -D WLED_DISABLE_INFRARED [env:esp32dev] board = esp32dev -platform = espressif32@1.11.2 +platform = espressif32@1.12.4 build_flags = ${common.build_flags_esp32} lib_ignore = ESPAsyncTCP @@ -256,7 +278,7 @@ build_flags = ${common.build_flags_esp8266} -D WLED_USE_ANALOG_LEDS -D WLED_USE_ [env:d1_mini_debug] board = d1_mini build_type = debug -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} ${common.debug_flags} @@ -265,7 +287,7 @@ board = d1_mini upload_protocol = espota # exchange for your WLED IP upload_port = "10.10.1.27" -platform = ${common.platform_latest} +platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} @@ -285,6 +307,13 @@ platform = ${common.platform_latest} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} -D LEDPIN=16 + +[env:custom_LEDPIN_3] +board = d1_mini +platform = ${common.platform_latest} +board_build.ldscript = ${common.ldscript_4m1m} +build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 + [env:custom_APA102] board = d1_mini platform = ${common.platform_latest} @@ -299,7 +328,7 @@ build_flags = ${common.build_flags_esp8266} -D USE_WS2801 [env:custom32_LEDPIN_16] board = esp32dev -platform = espressif32@1.11.2 +platform = espressif32@1.12.4 build_flags = ${common.build_flags_esp32} -D LEDPIN=16 lib_ignore = ESPAsyncTCP @@ -307,7 +336,7 @@ lib_ignore = [env:wemos_shield_esp32] board = esp32dev -platform = espressif32@1.11.2 +platform = espressif32@1.12.4 upload_port = /dev/cu.SLAB_USBtoUART monitor_port = /dev/cu.SLAB_USBtoUART upload_speed = 460800 @@ -315,3 +344,25 @@ build_flags = ${common.build_flags_esp32} -D LEDPIN=16 -D RLYPIN=19 -D BTNPIN=17 lib_ignore = ESPAsyncTCP ESPAsyncUDP + +[env:m5atom] +board = esp32dev +build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39 +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP +platform = espressif32@1.12.4 + +# ------------------------------------------------------------------------------ +# travis test board configurations +# ------------------------------------------------------------------------------ + +[env:travis_esp8266] +extends = env:d1_mini +build_type = debug +build_flags = ${common.build_flags_esp8266} ${common.debug_flags} ${common.build_flags_all_features} + +[env:travis_esp32] +extends = env:esp32dev +build_type = debug +build_flags = ${common.build_flags_esp32} ${common.debug_flags} ${common.build_flags_all_features} diff --git a/platformio_override.ini.example b/platformio_override.ini.example index ba829aa1d..da024970b 100644 --- a/platformio_override.ini.example +++ b/platformio_override.ini.example @@ -4,6 +4,9 @@ # ------------------------------------------------------------------------------ # Please visit documentation: https://docs.platformio.org/page/projectconf.html +[platformio] +default_envs = esp8266_1m_custom + [env:esp8266_1m_custom] board = esp01_1m platform = ${common.arduino_core_2_4_2} @@ -25,6 +28,9 @@ build_flags = ${common.build_flags_esp8266} ; -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 diff --git a/readme.md b/readme.md index 53671c6b9..dd860131b 100644 --- a/readme.md +++ b/readme.md @@ -1,16 +1,17 @@ -![WLED logo](https://raw.githubusercontent.com/Aircoookie/WLED/master/wled_logo.png) +

+ + + + + + +

+ +# Welcome to my project WLED! ✨ -[![](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) +A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812, APA102) LEDs or also SPI based chipsets like the WS2801! -## Welcome to my project WLED! - -A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812, APA102) LEDs! - -### Features: +## ⚙️ Features - WS2812FX library integrated for over 100 special effects - FastLED noise effects and 50 palettes - Modern UI with color, effect and segment controls @@ -25,22 +26,22 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control - Configurable analog clock + support for the Cronixie kit by Diamex - Configurable Auto Brightness limit for safer operation -### 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 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)! @@ -54,18 +55,47 @@ Russian speakers, check out the videos by Room31: [WLED Firmware Overview: Interface and Settings](https://youtu.be/h7lKsczEI7E) [ESP8266 based LED controller for WS2812b strip. WLED Firmware + OpenHAB](https://youtu.be/K4ioTt3XvGc) -### Other +## 🖼️ Images + + +## 💾 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 | + +## 🧊 Compatibe 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. diff --git a/tools/cdata.js b/tools/cdata.js new file mode 100644 index 000000000..00055e50a --- /dev/null +++ b/tools/cdata.js @@ -0,0 +1,396 @@ +/** + * 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"); + + 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").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 { + 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("ascii"); + 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: "favicon.ico", + name: "favicon", + method: "binary", + }, + ], + "wled00/html_other.h" +); 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..e67ef8da2 --- /dev/null +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -0,0 +1,119 @@ +#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!")); + } + + + /* + * 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/Fix_unreachable_netservices_v2/readme.md b/usermods/Fix_unreachable_netservices_v2/readme.md new file mode 100644 index 000000000..f7d2aed6d --- /dev/null +++ b/usermods/Fix_unreachable_netservices_v2/readme.md @@ -0,0 +1,54 @@ +# Fix unreachable net services V2 + +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. + +**Webinterface**: The number of pings and reconnects is displayed on the info page in the web interface. + +_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. + +## 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..8ffc821ed --- /dev/null +++ b/usermods/Fix_unreachable_netservices_v2/usermod_Fix_unreachable_netservices.h @@ -0,0 +1,138 @@ +#pragma once + +#include "wled.h" +#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; + + // desclare required variables + const unsigned int PingDelayMs = 60000; + unsigned long m_connectedWiFi = 0; + ping_option m_pingOpt; + unsigned int m_pingCount = 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!"); + + ++m_connectedWiFi; + + // initialize ping_options structure + memset(&m_pingOpt, 0, sizeof(struct ping_option)); + m_pingOpt.count = 1; + m_pingOpt.ip = WiFi.localIP(); + + } + + + /* + * 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 (m_connectedWiFi > 0 && millis()-m_lastTime > PingDelayMs) + { + ping_start(&m_pingOpt); + m_lastTime = millis(); + ++m_pingCount; + } + } + + + /* + * 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"); + + JsonArray infoArr = user.createNestedArray("⚡ Ping fix pings"); //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["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_FIXNETSERVICES; + } + + //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/Fix_unreachable_webserver/readme.md b/usermods/Fix_unreachable_webserver/readme.md new file mode 100644 index 000000000..5ed17b87c --- /dev/null +++ b/usermods/Fix_unreachable_webserver/readme.md @@ -0,0 +1,17 @@ +# Fix unreachable Webserver + +This modification performs a ping request to the local IP address every 60 seconds. By this procedure the web server 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. + +## Installation + +Copy and replace the file `usermod.cpp` in wled00 directory. + + diff --git a/usermods/Fix_unreachable_webserver/usermod.cpp b/usermods/Fix_unreachable_webserver/usermod.cpp new file mode 100644 index 000000000..f1957da26 --- /dev/null +++ b/usermods/Fix_unreachable_webserver/usermod.cpp @@ -0,0 +1,43 @@ +#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 + */ + +#include + +const int PingDelayMs = 60000; +long lastCheckTime = 0; +bool connectedWiFi = false; +ping_option pingOpt; + +//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() +{ + +} + + +//gets called every time WiFi is (re-)connected. Initialize own network interfaces here +void userConnected() +{ + connectedWiFi = true; + // initialize ping_options structure + memset(&pingOpt, 0, sizeof(struct ping_option)); + pingOpt.count = 1; + pingOpt.ip = WiFi.localIP(); +} + +//loop. You can use "if (WLED_CONNECTED)" to check for successful connection +void userLoop() +{ + if (connectedWiFi && millis()-lastCheckTime > PingDelayMs) + { + ping_start(&pingOpt); + lastCheckTime = millis(); + } +} 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/readme.md b/usermods/PIR_sensor_switch/readme.md new file mode 100644 index 000000000..447f541cb --- /dev/null +++ b/usermods/PIR_sensor_switch/readme.md @@ -0,0 +1,75 @@ +# 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. +**I recommend to deactivate the sensor before installing an OTA update**. +- 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 | + +## 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()); + +} +``` + +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..6d71a426e --- /dev/null +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -0,0 +1,249 @@ +#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 { + 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; + + /* + * 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 + */ + 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) { + 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); + } + + + /* + * 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/TTGO-T-Display/README.md b/usermods/TTGO-T-Display/README.md new file mode 100644 index 000000000..5674c466e --- /dev/null +++ b/usermods/TTGO-T-Display/README.md @@ -0,0 +1,77 @@ +# 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 + * in AP mode and turned off lightning AP password is shown +* Current effect +* Current palette + +Usermod based on a rework of the ssd1306_i2c_oled_u8g2 usermod from the WLED repo. + +## Hardware +![Hardware](assets/ttgo_hardware1.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 full string of 68 LEDs. + +## 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_hardware1.png b/usermods/TTGO-T-Display/assets/ttgo_hardware1.png new file mode 100644 index 000000000..42d338db1 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..4b1760964 --- /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 IR_PIN=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..a4bb28c83 --- /dev/null +++ b/usermods/TTGO-T-Display/usermod.cpp @@ -0,0 +1,214 @@ + +/* + * 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(2); + 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, 10); + 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 IP or Psssword + tft.setCursor(1, 40); + // Print password in AP mode and if led is OFF. + if (apActive && bri == 0) + tft.print(apPass); + else + tft.print(knownIp); + + // Third row with mode name + tft.setCursor(1, 70); + 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, 100); + 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; + } + +} \ No newline at end of file diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md new file mode 100644 index 000000000..f6ce85329 --- /dev/null +++ b/usermods/Temperature/readme.md @@ -0,0 +1,40 @@ +# 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. + +## Installation + +Copy `usermod_temperature.h` to the wled00 directory. +Uncomment the corresponding lines in `usermods_list.cpp` and compile! +If this is the only v2 usermod you plan to use, you can alternatively replace `usermods_list.h` in wled00 with the one in this folder. + +## Project link + +* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link + +### PlatformIO requirements + +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_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/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h new file mode 100644 index 000000000..eb123df01 --- /dev/null +++ b/usermods/Temperature/usermod_temperature.h @@ -0,0 +1,79 @@ +#pragma once + +#include "wled.h" + +#include //DS18B20 + +//Pin defaults for QuinLed Dig-Uno +#ifdef ARDUINO_ARCH_ESP32 +#define TEMPERATURE_PIN 18 +#else //ESP8266 boards +#define TEMPERATURE_PIN 14 +#endif + +#define TEMP_CELSIUS // Comment out for Fahrenheit + +#define MEASUREMENT_INTERVAL 60000 //1 Minute + +OneWire oneWire(TEMPERATURE_PIN); +DallasTemperature sensor(&oneWire); + +class UsermodTemperature : public Usermod { + private: + //set last reading as "40 sec before boot", so first reading is taken after 20 sec + unsigned long lastMeasurement = UINT32_MAX - 40000; + float temperature = 0.0f; + public: + void getReading() { + sensor.requestTemperatures(); + #ifdef TEMP_CELSIUS + temperature = sensor.getTempCByIndex(0); + #else + temperature = sensor.getTempFByIndex(0); + #endif + } + + void setup() { + sensor.begin(); + sensor.setResolution(9); + } + + void loop() { + if (millis() - lastMeasurement > MEASUREMENT_INTERVAL) + { + getReading(); + + if (WLED_MQTT_CONNECTED) { + char subuf[38]; + strcpy(subuf, mqttDeviceTopic); + strcat(subuf, "/temperature"); + mqtt->publish(subuf, 0, true, String(temperature).c_str()); + } + lastMeasurement = millis(); + } + } + + void addToJsonInfo(JsonObject& root) { + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray temp = user.createNestedArray("Temperature"); + if (temperature == DEVICE_DISCONNECTED_C) { + temp.add(0); + temp.add(" Sensor Error!"); + return; + } + + temp.add(temperature); + #ifdef TEMP_CELSIUS + temp.add("°C"); + #else + temp.add("°F"); + #endif + } + + uint16_t getId() + { + return USERMOD_ID_TEMPERATURE; + } +}; \ No newline at end of file diff --git a/usermods/Temperature/usermods_list.cpp b/usermods/Temperature/usermods_list.cpp new file mode 100644 index 000000000..1a1efdd70 --- /dev/null +++ b/usermods/Temperature/usermods_list.cpp @@ -0,0 +1,25 @@ +#include "wled.h" +/* + * Register your v2 usermods here! + */ + +/* + * Add/uncomment your usermod filename here (and once more below) + * || || || + * \/ \/ \/ + */ +//#include "usermod_v2_example.h" +#include "usermod_temperature.h" +//#include "usermod_v2_empty.h" + +void registerUsermods() +{ + /* + * Add your usermod class name here + * || || || + * \/ \/ \/ + */ + //usermods.add(new MyExampleUsermod()); + usermods.add(new UsermodTemperature()); + //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/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/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/wled06_usermod.ino b/usermods/project_cars_shiftlight/wled06_usermod.ino index b00c2946a..0edad7ddd 100644 --- a/usermods/project_cars_shiftlight/wled06_usermod.ino +++ b/usermods/project_cars_shiftlight/wled06_usermod.ino @@ -18,25 +18,6 @@ u16 PCARS_maxRPM; long PCARS_lastRead = millis() - 2001; float PCARS_rpmRatio; - -void userSetup() -{ - UDP.begin(PCARS_localUdpPort); -} - -void userConnected() -{ - // new wifi, who dis? -} - -void userLoop() -{ - PCARS_readValues(); - if (PCARS_lastRead > millis() - 2000) { - PCARS_buildcolorbars(); - } -} - void PCARS_readValues() { int PCARS_packetSize = UDP.parsePacket(); @@ -48,7 +29,7 @@ void PCARS_readValues() { if (len == 1367) { // Telemetry packet. Ignoring everything else. PCARS_lastRead = millis(); - arlsLock(realtimeTimeoutMs, REALTIME_MODE_GENERIC); + realtimeLock(realtimeTimeoutMs, REALTIME_MODE_GENERIC); // current RPM memcpy(&PCARS_tempChar, &PCARS_packet[124], 2); PCARS_RPM = (PCARS_tempChar[1] << 8) + PCARS_tempChar[0]; @@ -93,4 +74,22 @@ void PCARS_buildcolorbars() { } 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/readme.md b/usermods/readme.md index 34ddef0ff..0c56efaed 100644 --- a/usermods/readme.md +++ b/usermods/readme.md @@ -7,7 +7,7 @@ If you have created an usermod that you believe is useful (for example to suppor 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 `usermod.cpp` file +- 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! @@ -15,4 +15,7 @@ In order for other people to be able to have fun with your usermod, please keep 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. +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/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/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 1b8624844..d8265c297 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -869,6 +869,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; } @@ -2246,7 +2247,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); @@ -2311,10 +2312,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(); @@ -3238,6 +3240,8 @@ uint16_t WS2812FX::mode_heartbeat(void) { // 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 }; @@ -3258,8 +3262,11 @@ uint16_t WS2812FX::mode_pacifica() // 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 = 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; @@ -3304,6 +3311,7 @@ uint16_t WS2812FX::mode_pacifica() setPixelColor(i, c.red, c.green, c.blue); } + now = nowOld; return FRAMETIME; } @@ -3322,3 +3330,385 @@ CRGB WS2812FX::pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart 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; + } + + 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 = SEGLEN >> 2; + uint16_t span = SEGMENT.intensity << 8; + + for (uint16_t i = 0; i < numBirds; i++) + { + counter -= span/numBirds; + int megumin = sin16(counter) + 0x8000; + uint32_t bird = (megumin * SEGLEN) >> 16; + uint32_t c = color_from_palette((i * 255)/ numBirds, false, true, 0); + 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; +} diff --git a/wled00/FX.h b/wled00/FX.h index c690a4e50..05802b8fc 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -31,6 +31,7 @@ #include "const.h" #define FASTLED_INTERNAL //remove annoying pragma messages +#define USE_GET_MILLISECOND_TIMER #include "FastLED.h" #define DEFAULT_BRIGHTNESS (uint8_t)127 @@ -84,21 +85,24 @@ // options // bit 7: segment is in transition mode -// bits 3-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_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 103 +#define MODE_COUNT 113 #define FX_MODE_STATIC 0 #define FX_MODE_BLINK 1 @@ -203,6 +207,16 @@ #define FX_MODE_HEARTBEAT 100 #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 class WS2812FX { typedef uint16_t (WS2812FX::*mode_ptr)(void); @@ -219,7 +233,7 @@ 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]; @@ -255,7 +269,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; @@ -394,6 +411,16 @@ class WS2812FX { _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; _brightness = DEFAULT_BRIGHTNESS; currentPalette = CRGBPalette16(CRGB::Black); @@ -409,6 +436,7 @@ class WS2812FX { init(bool supportWhite, 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), @@ -423,10 +451,11 @@ class WS2812FX { 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), - setRgbwPwm(void); + setRgbwPwm(void), + setPixelSegment(uint8_t n); bool - reverseMode = false, + reverseMode = false, //is the entire LED strip reversed? gammaCorrectBri = false, gammaCorrectCol = true, applyToAllSelected = true, @@ -457,9 +486,10 @@ class WS2812FX { triwave16(uint16_t); uint32_t + now, timebase, color_wheel(uint8_t), - color_from_palette(uint16_t, bool, bool, uint8_t, uint8_t pbri = 255), + color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255), color_blend(uint32_t,uint32_t,uint8_t), gamma32(uint32_t), getLastShow(void), @@ -562,7 +592,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), @@ -580,8 +610,17 @@ class WS2812FX { mode_ripple_rainbow(void), mode_heartbeat(void), mode_pacifica(void), - mode_candle_multi(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); private: NeoPixelWrapper *bus; @@ -591,7 +630,6 @@ class WS2812FX { CRGBPalette16 currentPalette; CRGBPalette16 targetPalette; - uint32_t now; uint16_t _length, _lengthRaw, _virtualSegmentLength; uint16_t _rand16seed; uint8_t _brightness; @@ -599,7 +637,6 @@ class WS2812FX { void load_gradient_palette(uint8_t); void handle_palette(void); - void fill(uint32_t); bool _useRgbw = false, @@ -628,10 +665,13 @@ 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); uint32_t _lastPaletteChange = 0; uint32_t _lastShow = 0; @@ -654,7 +694,6 @@ class WS2812FX { uint16_t realPixelIndex(uint16_t i); }; - //10 names per line const char JSON_mode_names[] PROGMEM = R"=====([ "Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow", @@ -667,7 +706,8 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "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","Pacifica","Candle Multi" +"Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise", +"Flow","Chunchun","Dancing Shadows" ])====="; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 25e460e2a..d979dfcf6 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -72,6 +72,7 @@ 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; @@ -80,12 +81,17 @@ void WS2812FX::service() { if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary { if (SEGMENT.grouping == 0) SEGMENT.grouping = 1; //sanity check - _virtualSegmentLength = SEGMENT.virtualLength(); doShow = true; - handle_palette(); - uint16_t delay = (this->*_mode[SEGMENT.mode])(); + uint16_t delay = FRAMETIME; + + if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen + _virtualSegmentLength = SEGMENT.virtualLength(); + handle_palette(); + delay = (this->*_mode[SEGMENT.mode])(); //effect function + if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++; + } + SEGENV.next_time = nowUp + delay; - if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++; } } } @@ -105,16 +111,25 @@ void WS2812FX::setPixelColor(uint16_t n, uint32_t c) { setPixelColor(n, r, g, b, w); } +#define REV(i) (_length - 1 - (i)) + +//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; + if (reverseMode) realIndex = REV(realIndex); return realIndex; } @@ -135,6 +150,7 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) } } + //reorder channels to selected order RgbwColor col; switch (colorOrder) { @@ -170,14 +186,23 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) 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 (reverseMode) indexSetRev = REV(indexSet); #ifdef WLED_CUSTOM_LED_MAPPING if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; #endif - if (indexSetRev >= SEGMENT.start && indexSetRev < SEGMENT.stop) bus->SetPixelColor(indexSet + skip, col); + if (indexSetRev >= SEGMENT.start && indexSetRev < SEGMENT.stop) { + bus->SetPixelColor(indexSet + skip, col); + if (IS_MIRROR) { //set the corresponding mirrored pixel + if (reverseMode) { + bus->SetPixelColor(REV(SEGMENT.start) - indexSet + skip + REV(SEGMENT.stop) + 1, col); + } else { + bus->SetPixelColor(SEGMENT.stop - indexSet + skip + SEGMENT.start - 1, col); + } + } + } } } else { //live data, etc. - if (reverseMode) i = _length - 1 - i; + if (reverseMode) i = REV(i); #ifdef WLED_CUSTOM_LED_MAPPING if (i < customMappingSize) i = customMappingTable[i]; #endif @@ -366,6 +391,12 @@ void WS2812FX::setBrightness(uint8_t b) { if (_brightness == b) return; _brightness = (gammaCorrectBri) ? gamma8(b) : b; _segment_index = 0; + if (b == 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 } @@ -400,7 +431,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) { @@ -510,6 +546,18 @@ void WS2812FX::resetSegments() { _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) @@ -528,11 +576,14 @@ void WS2812FX::setShowCallback(show_callback cb) void WS2812FX::setTransitionMode(bool t) { - _segment_index = getMainSegmentId(); - SEGMENT.setOption(SEG_OPTION_TRANSITIONAL, 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; + } } /* @@ -569,6 +620,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 */ @@ -706,26 +765,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 : load_gradient_palette(22); break;//heat palette - case FX_MODE_COLORWAVES : load_gradient_palette(13); break;//landscape 33 - case FX_MODE_FILLNOISE8 : targetPalette = OceanColors_p; break; - case FX_MODE_NOISE16_1 : load_gradient_palette(17); break;//Drywet - case FX_MODE_NOISE16_2 : load_gradient_palette(30); break;//Blue cyan yellow - case FX_MODE_NOISE16_3 : load_gradient_palette(22); break;//heat palette - case FX_MODE_NOISE16_4 : load_gradient_palette(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) { @@ -777,7 +838,7 @@ void WS2812FX::handle_palette(void) case 12: //Rainbow stripe colors targetPalette = RainbowStripeColors_p; break; default: //progmem palettes - load_gradient_palette(SEGMENT.palette -13); + load_gradient_palette(paletteIndex -13); } if (singleSegmentMode && paletteFade) //only blend if just one segment uses FastLED mode @@ -789,6 +850,16 @@ 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 @@ -800,6 +871,7 @@ uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8 return fastled_col.r*65536 + fastled_col.g*256 + fastled_col.b; } +//@returns `true` if color, mode, speed, intensity and palette match bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b) { //if (a->start != b->start) return false; diff --git a/wled00/NpbWrapper.h b/wled00/NpbWrapper.h index 8a3f95448..2fa6ab406 100644 --- a/wled00/NpbWrapper.h +++ b/wled00/NpbWrapper.h @@ -40,8 +40,12 @@ //END CONFIGURATION #if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) || defined(USE_P9813) - #define CLKPIN 0 - #define DATAPIN 2 + #ifndef CLKPIN + #define CLKPIN 0 + #endif + #ifndef DATAPIN + #define DATAPIN 2 + #endif #if BTNPIN == CLKPIN || BTNPIN == DATAPIN #undef BTNPIN // Deactivate button pin if it conflicts with one of the APA102 pins. #endif @@ -71,6 +75,14 @@ #define BPIN 5 //B pin for analog LED strip #define WPIN 15 //W pin for analog LED strip #define W2PIN 12 //W2 pin for analog LED strip + #elif defined(WLED_USE_PLJAKOBS_PCB) + // PWM pins - to use with esp_rgbww_controller from patrickjahns/pljakobs (https://github.com/pljakobs/esp_rgbww_controller) + #define RPIN 12 //R pin for analog LED strip + #define GPIN 13 //G pin for analog LED strip + #define BPIN 14 //B pin for analog LED strip + #define WPIN 4 //W pin for analog LED strip + #define W2PIN 5 //W2 pin for analog LED strip + #undef IR_PIN #else //PWM pins - PINs 5,12,13,15 are used with Magic Home LED Controller #define RPIN 5 //R pin for analog LED strip @@ -126,7 +138,9 @@ #define PIXELFEATURE4 DotStarLbgrFeature #elif defined(USE_LPD8806) #define PIXELFEATURE3 Lpd8806GrbFeature - #define PIXELFEATURE4 Lpd8806GrbFeature +#elif defined(USE_WS2801) + #define PIXELFEATURE3 NeoRbgFeature + #define PIXELFEATURE4 NeoRbgFeature #elif defined(USE_TM1814) #define PIXELFEATURE3 NeoWrgbTm1814Feature #define PIXELFEATURE4 NeoWrgbTm1814Feature @@ -276,7 +290,7 @@ public: } break; case NeoPixelType_Grbw: { - #ifdef USE_LPD8806 + #if defined(USE_LPD8806) || defined(USE_WS2801) _pGrbw->SetPixelColor(indexPixel, RgbColor(color.R,color.G,color.B)); #else _pGrbw->SetPixelColor(indexPixel, color); diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index c9f61ff23..05e460931 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -85,6 +85,7 @@ void onAlexaChange(EspalexaDevice* dev) 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/const.h b/wled00/const.h index 533a5f270..9fac43396 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -10,6 +10,18 @@ #define DEFAULT_AP_PASS "wled1234" #define DEFAULT_OTA_PASS "wledota" +//increase if you need more +#define WLED_MAX_USERMODS 4 + +//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" + //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) @@ -44,6 +56,7 @@ #define REALTIME_MODE_E131 4 #define REALTIME_MODE_ADALIGHT 5 #define REALTIME_MODE_ARTNET 6 +#define REALTIME_MODE_TPM2NET 7 //realtime override modes #define REALTIME_OVERRIDE_NONE 0 @@ -86,8 +99,17 @@ #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 +//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. + //EEPROM size #define EEPSIZE 2560 //Maximum is 4096 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.htm b/wled00/data/index.htm index 7248c076d..71292e4b1 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -1,626 +1,685 @@ - - - - - - WLED - + + + + + + WLED + @@ -796,171 +871,194 @@ input[type=number]::-webkit-outer-spin-button {
Loading WLED UI...
+
-
-
- - - - - - - -
-
-

Brightness

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

Brightness

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

White channel

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

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

Color palette

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

+
+ +
+

+
+ +
+

+
+
+

White channel

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

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

Color palette

+
+ + +
+
-
-

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 -
+
+

Load from slot

+ + + +
+ + + +
+ + + +
+ + + +
+
+ Slot 16 can save all segments.

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

+ + -

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/settings.htm b/wled00/data/settings.htm index b4f981e5a..425c3295b 100644 --- a/wled00/data/settings.htm +++ b/wled00/data/settings.htm @@ -3,34 +3,23 @@ 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..e57b09a49 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 0bff54b86..08d9427f6 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -2,6 +2,7 @@ + Misc Settings @@ -88,7 +45,7 @@
Enable ArduinoOTA:

About

- WLED version 0.10.0

+ WLED version ##VERSION##

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

(c) 2016-2019 Christian Schwinne
diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 91fe89924..7c13650e9 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -1,14 +1,15 @@ Sync Settings - +
@@ -25,6 +26,7 @@ Infrared remote: +
IR info

WLED Broadcast

@@ -39,7 +41,7 @@ Send notifications twice:

Realtime

Receive UDP realtime:

Network DMX input
-Type: +Type:
E1.31 info
Timeout: ms
@@ -98,7 +100,7 @@ Hue Bridge IP:

Press the pushlink button on the bridge, after that save this page!
(when first connecting)
-Hue status: Disabled in this build
+Hue status: Disabled in this build
diff --git a/wled00/data/settings_time.htm b/wled00/data/settings_time.htm index ff2ce4807..c0231277d 100644 --- a/wled00/data/settings_time.htm +++ b/wled00/data/settings_time.htm @@ -2,6 +2,7 @@ + Time Settings @@ -207,7 +151,8 @@ Use 0 for the default action instead of a macro
Boot Macro:
Alexa On/Off Macros:
- Button Macro: Long Press:
+ Button short press macro: Macro:
+ Long Press: Double press:
Countdown-Over Macro:
Timed-Light-Over Macro:
Time-Controlled Macros:
diff --git a/wled00/data/settings_ui.htm b/wled00/data/settings_ui.htm index 372532647..8e84bce5c 100644 --- a/wled00/data/settings_ui.htm +++ b/wled00/data/settings_ui.htm @@ -1,16 +1,152 @@ + 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: + +
\ No newline at end of file diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 62ef153de..a72c43fd0 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):

@@ -102,8 +59,13 @@
- 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.
+
+
\ No newline at end of file diff --git a/wled00/data/style.css b/wled00/data/style.css new file mode 100644 index 000000000..41e4c1fe8 --- /dev/null +++ b/wled00/data/style.css @@ -0,0 +1,47 @@ +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; +} +.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..be5949897 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -1,4 +1,43 @@ -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:
+

+ + + \ 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/dmx.cpp b/wled00/dmx.cpp index ccd3b0643..33aeb5748 100644 --- a/wled00/dmx.cpp +++ b/wled00/dmx.cpp @@ -8,11 +8,12 @@ */ #ifdef WLED_ENABLE_DMX -#include "src/dependencies/dmx/ESPDMX.h" -DMXESPSerial 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(); @@ -61,6 +62,10 @@ void initDMX() { dmx.init(512); // initialize with bus length } +#if (LEDPIN == 2) + #pragma message "Pin conflict compiling with DMX and LEDs on pin 2. Please set a different LEDPIN." +#endif + #else void handleDMX() {} void initDMX() {} diff --git a/wled00/e131.cpp b/wled00/e131.cpp index ddbfabe0e..7f67aadbe 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -1,5 +1,8 @@ #include "wled.h" +#define MAX_LEDS_PER_UNIVERSE 170 +#define MAX_CHANNELS_PER_UNIVERSE 512 + /* * E1.31 handler */ @@ -25,11 +28,19 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, bool isArtnet){ seq = p->sequence_number; } + #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; - uint16_t possibleLEDsInCurrentUniverse; if (e131SkipOutOfSequence) if (seq < e131LastSequenceNumber[uni-e131Universe] && seq > 20 && e131LastSequenceNumber[uni-e131Universe] < 250){ @@ -47,7 +58,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, bool isArtnet){ // update status info realtimeIP = clientIP; byte wChannel = 0; - + switch (DMXMode) { case DMX_MODE_DISABLED: return; // nothing to do @@ -98,65 +109,40 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, bool isArtnet){ colSec[2] = e131_data[DMXAddress+10]; if (dmxChannels-DMXAddress+1 > 11) { - col[3] = e131_data[DMXAddress+11]; //white - colSec[3] = e131_data[DMXAddress+12]; + 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_RGB: - realtimeLock(realtimeTimeoutMs, mde); - if (realtimeOverride) return; - 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, e131_data[DMXAddress+i*3+0], e131_data[DMXAddress+i*3+1], e131_data[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, e131_data[j*3+1], e131_data[j*3+2], e131_data[j*3+3], 0); - } - } - break; - case DMX_MODE_MULTIPLE_DRGB: - realtimeLock(realtimeTimeoutMs, mde); - if (realtimeOverride) return; - if (previousUniverses == 0) { - // first universe of this fixture - if (DMXOldDimmer != e131_data[DMXAddress+0]) { - DMXOldDimmer = e131_data[DMXAddress+0]; - bri = e131_data[DMXAddress+0]; - strip.setBrightness(bri); + case DMX_MODE_MULTIPLE_RGB: + { + realtimeLock(realtimeTimeoutMs, mde); + 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 = isArtnet ? 0 : 1; + uint16_t ledsInFirstUniverse = (MAX_CHANNELS_PER_UNIVERSE - DMXAddress) / 3; + previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * MAX_LEDS_PER_UNIVERSE; } - 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, e131_data[DMXAddress+i*3+1], e131_data[DMXAddress+i*3+2], e131_data[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, e131_data[j*3+1], e131_data[j*3+2], e131_data[j*3+3], 0); + uint16_t ledsTotal = previousLeds + (dmxChannels - dmxOffset) / 3; + for (uint16_t i = previousLeds; i < ledsTotal; i++) { + setRealtimePixel(i, e131_data[dmxOffset++], e131_data[dmxOffset++], e131_data[dmxOffset++], 0); } + break; } - break; - default: DEBUG_PRINTLN("unknown E1.31 DMX mode"); return; // nothing to do diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 5d5ab2b5c..f95a3a2a2 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -62,8 +62,10 @@ 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 changeBrightness(int8_t amount); void changeEffectIntensity(int8_t amount); void decodeIR(uint32_t code); void decodeIR24(uint32_t code); @@ -73,6 +75,7 @@ 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(); @@ -82,7 +85,6 @@ void handleIR(); #include "src/dependencies/json/ArduinoJson-v6.h" #include "src/dependencies/json/AsyncJson-v6.h" #include "FX.h" -// TODO: AsynicWebServerRequest conflict? void deserializeSegment(JsonObject elem, byte it); bool deserializeState(JsonObject root); @@ -90,7 +92,7 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id); void serializeState(JsonObject root); void serializeInfo(JsonObject root); void serveJson(AsyncWebServerRequest* request); -void serveLiveLeds(AsyncWebServerRequest* request); +bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); //led.cpp void setValuesFromMainSeg(); @@ -145,6 +147,40 @@ 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); +//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 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); + + bool add(Usermod* um); + byte getModCount(); +}; + +//usermods_list.cpp +void registerUsermods(); + //usermod.cpp void userSetup(); void userConnected(); @@ -179,9 +215,14 @@ String settingsProcessor(const String& var); String dmxProcessor(const String& var); void serveSettings(AsyncWebServerRequest* request); +//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 -char* XML_response(AsyncWebServerRequest *request, char* dest = nullptr); -char* URL_response(AsyncWebServerRequest *request); +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); diff --git a/wled00/html_other.h b/wled00/html_other.h index 348efc411..5a363497d 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -1,133 +1,138 @@ /* - * Various web 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%)====="; - -//DMX channel map -const char PAGE_dmxmap[] PROGMEM = R"=====( - -DMX Map - - -
...
)====="; +// 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.10.0
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 - -//liveview -const char PAGE_liveview[] PROGMEM = R"=====( - - - - -WLED Live Preview +// Autogenerated from wled00/data/update.htm, do not edit!! +const char PAGE_update[] PROGMEM = R"=====( +WLED Update - -
- -)====="; +.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%} +

WLED Software Update

Installed version: 0.10.2
+Download the latest binary: +
+
)====="; -/* - * favicon - */ +// Autogenerated from wled00/data/welcome.htm, do not edit!! +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?
+)====="; + + +// Autogenerated from wled00/data/liveview.htm, do not edit!! +const char PAGE_liveview[] PROGMEM = R"=====( +WLED Live Preview
)====="; + + +// 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 a2d8c9c48..22ed90ca7 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -1,484 +1,526 @@ /* - * 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 - - -
-
-
-
%DMXMENU% -
-
-
+// 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