diff --git a/CHANGELOG.md b/CHANGELOG.md index b2801b8664..c570ac1f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,68 @@ ## WLED changelog +#### Build 2410270 +- WLED 0.15.0-b7 release +- Re-license the WLED project from MIT to EUPL (#4194 by @Aircoookie) +- Fix alexa devices invisible/uncontrollable (#4214 by @Svennte) +- Add visual expand button on hover (#4172) +- Usermod: Audioreactive tuning and performance enhancements (by @softhack007) +- `/json/live` (JSON live data/peek) only enabled when WebSockets are disabled +- Various bugfixes and optimisations: #4179, #4215, #4219, #4222, #4223, #4224, #4228, #4230 + +#### Build 2410140 +- WLED 0.15.0-b6 release +- Added BRT timezone (#4188 by @LuisFadini) +- Fixed the positioning of the "Download the latest binary" button (#4184 by @maxi4329) +- Add WLED_AUTOSEGMENTS compile flag (#4183 by @PaoloTK) +- New 512kB FS parition map for 4MB devices +- Internal API change: Static PinManager & UsermodManager +- Change in Improv chip ID and version generation +- Various optimisations, bugfixes and enhancements (#4005, #4174 & #4175 by @Xevel, #4180, #4168, #4154, #4189 by @dosipod) + +#### Build 2409170 +- UI: Introduce common.js in settings pages (size optimisation) +- Add the ability to toggle the reception of palette synchronizations (#4137 by @felddy) +- Usermod/FX: Temperature usermod added Temperature effect (example usermod effect by @blazoncek) +- Fix AsyncWebServer version pin + +#### Build 2409140 +- Configure different kinds of busses at compile (#4107 by @PaoloTK) + - BREAKING: removes LEDPIN and DEFAULT_LED_TYPE compile overrides +- Fetch LED types from Bus classes (dynamic UI) (#4129 by @netmindz, @blazoncek, @dedehai) +- Temperature usermod: update OneWire to 2.3.8 (#4131 by @iammattcoleman) + +#### Build 2409100 +- WLED 0.15.0-b5 release +- Audioreactive usermod included by default in all compatible builds (including ESP8266) +- Demystified some byte definitions of WiZmote ESP-NOW message (#4114 by @ChuckMash) +- Update usermod "Battery" improved MQTT support (#4110 by @itCarl) +- Added a usermod for interacting with BLE Pixels Dice (#4093 by @axlan) +- Allow lower values for touch threshold (#4081 by @RobinMeis) +- Added POV image effect usermod (#3539 by @Liliputech) +- Remove repeating code to fetch audio data (#4103 by @netmindz) +- Loxone JSON parser doesn't handle lx=0 correctly (#4104 by @FreakyJ, fixes #3809) +- Rename wled00.ino to wled_main.cpp (#4090 by @willmmiles) +- SM16825 chip support including WW & CW channel swap (#4092) +- Add stress testing scripts (#4088 by @willmmiles) +- Improve jsonBufferLock management (#4089 by @willmmiles) +- Fix incorrect PWM bit depth on Esp32 with XTAL clock (#4082 by @PaoloTK) +- Devcontainer args (#4073 by @axlan) +- Effect: Fire2012 optional blur amount (#4078 by @apanteleev) +- Effect: GEQ fix bands (#4077 by @adrianschroeter) +- Boot delay option (#4060 by @DedeHai) +- ESP8266 Audioreactive sync (#3962 by @gaaat98, @netmindz, @softhack007) +- ESP8266 PWM crash fix (#4035 by @willmmiles) +- Usermod: Battery fix (#4051 by @Nickbert7) +- Usermod: Mpu6050 usermod crash fix (#4048 by @willmmiles) +- Usermod: Internal Temperature V2 (#4033 by @adamsthws) +- Various fixes and improvements (including build environments to emulate 0.14.0 for ESP8266) + +#### Build 2407070 +- Various fixes and improvements (mainly LED settings fix) + #### Build 2406290 - WLED 0.15.0-b4 release -- LED settings bus management update (WARNING only allow available outputs) +- LED settings bus management update (WARNING: only allows available outputs) - Add ETH support for LILYGO-POE-Pro (#4030 by @rorosaurus) - Update usermod_sn_photoresistor (#4017 by @xkvmoto) - Several internal fixes and optimisations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cd50b133d6..06c221fca6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,20 @@ A good description helps us to review and understand your proposed changes. For Please make all PRs against the `0_15` branch. +### Updating your code +While the PR is open - and under review by maintainers - you may be asked to modify your PR source code. +You can simply update your own branch, and push changes in response to reviewer recommendations. +Github will pick up the changes so your PR stays up-to-date. + +> [!CAUTION] +> Do not use "force-push" while your PR is open! +> It has many subtle and unexpected consequences on our github reposistory. +> For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push. + + +You can find a collection of very useful tips and tricks here: https://github.com/Aircoookie/WLED/wiki/How-to-properly-submit-a-PR + + ### Code style When in doubt, it is easiest to replicate the code style you find in the files you want to edit :) @@ -37,6 +51,11 @@ if (a == b) { } ``` +```cpp +if (a == b) doStuff(a); +``` + +Acceptable - however the first variant is usually easier to read: ```cpp if (a == b) { @@ -44,9 +63,6 @@ if (a == b) } ``` -```cpp -if (a == b) doStuff(a); -``` There should always be a space between a keyword and its condition and between the condition and brace. Within the condition, no space should be between the parenthesis and variables. diff --git a/LICENSE b/LICENSE index 69325d21c8..cca21c008b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,294 @@ -MIT License - -Copyright (c) 2016 Christian Schwinne - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +Copyright (c) 2016-present Christian Schwinne and individual WLED contributors +Licensed under the EUPL v. 1.2 or later + + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as +defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This + Licence does not define the extent of modification or dependence on the + Original Work required in order to classify a work as a Derivative Work; + this extent is determined by copyright law applicable in the country + mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which + is meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under + the Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright +vested in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case + may be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make +effective the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights +to any patents held by the Licensor, to the extent necessary to make use of +the rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, +in a notice following the copyright notice attached to the Work, a repository +where the Source Code is easily and freely accessible for as long as the +Licensor continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits +from any exception or limitation to the exclusive rights of the rights owners +in the Work, of the exhaustion of those rights or of other applicable +limitations thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and +a copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of +the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions +on the Work or Derivative Work that alter or restrict the terms of the +Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed +under a Compatible Licence, this Distribution or Communication can be done +under the terms of this Compatible Licence. For the sake of this clause, +‘Compatible Licence’ refers to the licences listed in the appendix attached to +this Licence. Should the Licensee's obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available +for as long as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade +names, trademarks, service marks, or names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she +brings to the Work are owned by him/her or licensed to him/her and that he/she +has the power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ +basis and without warranties of any kind concerning the Work, including +without limitation merchantability, fitness for a particular purpose, absence +of defects or errors, accuracy, non-infringement of intellectual property +rights other than copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a +condition for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the +use of the Work, including without limitation, damages for loss of goodwill, +work stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional +agreement, defining obligations or services consistent with this Licence. +However, if accepting obligations, You may act only on your own behalf and on +your sole responsibility, not on behalf of the original Licensor or any other +Contributor, and only if You agree to indemnify, defend, and hold each +Contributor harmless for any liability incurred by, or claims asserted against +such Contributor by the fact You have accepted any warranty or additional +liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I +agree’ placed under the bottom of a window displaying the text of this Licence +or by affirming consent in any other similar way, in accordance with the rules +of applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this +Licence, such as the use of the Work, the creation by You of a Derivative Work +or the Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions +of this Licence or updated versions of the Appendix, so far this is required +and reasonable, without reducing the scope of the rights granted by the +Licence. New versions of the Licence will be published with a unique version +number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty + on the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive + jurisdiction of the competent court where the Licensor resides or conducts + its primary business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the +above licences without producing a new version of the EUPL, as long as they +provide the rights granted in Article 2 of this Licence and protect the +covered Source Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a +new EUPL version. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 38f2099d62..0f73bff0c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.15.0-b4", + "version": "0.15.0-b7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-b4", + "version": "0.15.0-b7", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", @@ -54,9 +54,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -67,15 +67,10 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "bin": { "acorn": "bin/acorn" }, @@ -190,9 +185,9 @@ } }, "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -233,11 +228,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -701,9 +696,9 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1281,17 +1276,6 @@ "node": ">=0.10.0" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -1375,9 +1359,9 @@ } }, "node_modules/nodemon": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", - "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", @@ -1402,11 +1386,11 @@ } }, "node_modules/nodemon/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1418,9 +1402,9 @@ } }, "node_modules/nodemon/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nodemon/node_modules/supports-color": { "version": "5.5.0", @@ -1433,20 +1417,6 @@ "node": ">=4" } }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1507,6 +1477,7 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "deprecated": "This package is no longer supported.", "dependencies": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -1620,6 +1591,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", "engines": { "node": ">=0.6.0", "teleport": ">=0.2.0" @@ -1826,12 +1798,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -1993,9 +1962,9 @@ } }, "node_modules/terser": { - "version": "5.29.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.2.tgz", - "integrity": "sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw==", + "version": "5.34.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.0.tgz", + "integrity": "sha512-y5NUX+U9HhVsK/zihZwoq4r9dICLyV2jXGOriDAVOeKhq3LKVjgJbGO90FisozXLlJfvjHqgckGmJFBb9KYoWQ==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -2042,12 +2011,9 @@ } }, "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dependencies": { - "nopt": "~1.0.10" - }, + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "bin": { "nodetouch": "bin/nodetouch.js" } @@ -2065,9 +2031,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/tunnel-agent": { "version": "0.6.0", @@ -2229,11 +2195,6 @@ "node": ">=0.10.0" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", diff --git a/package.json b/package.json index e3c12629ca..9d095c82cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-b4", + "version": "0.15.0-b7", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { @@ -26,6 +26,6 @@ "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", - "nodemon": "^3.0.2" + "nodemon": "^3.1.7" } } diff --git a/platformio.ini b/platformio.ini index 5ebc43f559..6d4aa1dc13 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -140,7 +140,7 @@ lib_deps = IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.8.0 #https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta - https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 # for I2C interface ;Wire # ESP-NOW library @@ -197,6 +197,7 @@ build_flags = ; decrease code cache size and increase IRAM to fit all pixel functions -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'" ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown + -D NON32XFER_HANDLER ;; ask forgiveness for PROGMEM misuse lib_deps = #https://github.com/lorol/LITTLEFS.git @@ -205,6 +206,38 @@ lib_deps = ESP8266PWM ${env.lib_deps} +;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 +build_flags_compat = + -DESP8266 + -DFP_IN_IROM + ;;-Wno-deprecated-declarations + -Wno-misleading-indentation + ;;-Wno-attributes ;; silence warnings about unknown attribute 'maybe_unused' in NeoPixelBus + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH + -DVTABLES_IN_FLASH + -DMIMETYPE_MINIMAL + -DWLED_SAVE_IRAM ;; needed to prevent linker error + +;; this platform version was used for WLED 0.14.0 +platform_compat = espressif8266@4.2.0 +platform_packages_compat = + platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 + platformio/tool-esptool #@ ~1.413.0 + platformio/tool-esptoolpy #@ ~1.30000.0 + +;; experimental - for using older NeoPixelBus 2.7.9 +lib_deps_compat = + ESPAsyncTCP @ 1.2.2 + ESPAsyncUDP + ESP8266PWM + fastled/FastLED @ 3.6.0 + IRremoteESP8266 @ 2.8.2 + makuna/NeoPixelBus @ 2.7.9 + https://github.com/blazoncek/QuickESPNow.git#optional-debug + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 + + [esp32] #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip platform = espressif32@3.5.0 @@ -227,7 +260,8 @@ lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} # additional build flags for audioreactive -AR_build_flags = -D USERMOD_AUDIOREACTIVE +AR_build_flags = -D USERMOD_AUDIOREACTIVE + -D sqrt_internal=sqrtf ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster) AR_lib_deps = kosme/arduinoFFT @ 2.0.1 [esp32_idf_V4] @@ -315,10 +349,19 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME= lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder +[env:nodemcuv2_compat] +extends = env:nodemcuv2 +;; using platform version and build options from WLED 0.14.0 +platform = ${esp8266.platform_compat} +platform_packages = ${esp8266.platform_packages_compat} +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP8266_compat #-DWLED_DISABLE_2D +;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 + [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D + -D USERMOD_AUDIOREACTIVE [env:esp8266_2m] board = esp_wroom_02 @@ -329,10 +372,18 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02 lib_deps = ${esp8266.lib_deps} +[env:esp8266_2m_compat] +extends = env:esp8266_2m +;; using platform version and build options from WLED 0.14.0 +platform = ${esp8266.platform_compat} +platform_packages = ${esp8266.platform_packages_compat} +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP02_compat #-DWLED_DISABLE_2D + [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160 + -D USERMOD_AUDIOREACTIVE [env:esp01_1m_full] board = esp01_1m @@ -344,10 +395,18 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME= ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} +[env:esp01_1m_full_compat] +extends = env:esp01_1m_full +;; using platform version and build options from WLED 0.14.0 +platform = ${esp8266.platform_compat} +platform_packages = ${esp8266.platform_packages_compat} +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP01_compat -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D + [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA + -D USERMOD_AUDIOREACTIVE ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM [env:esp32dev] @@ -356,7 +415,9 @@ platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET + ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} + ${esp32.AR_lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} @@ -371,21 +432,40 @@ lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.large_partitions} +board_upload.flash_size = 8MB +board_upload.maximum_size = 8388608 ; board_build.f_flash = 80000000L +; board_build.flash_mode = qio -[env:esp32dev_audioreactive] +[env:esp32dev_16M] board = esp32dev -platform = ${esp32.platform} -platform_packages = ${esp32.platform_packages} +platform = ${esp32_idf_V4.platform} +platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_16M #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} -lib_deps = ${esp32.lib_deps} +lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} monitor_filters = esp32_exception_decoder -board_build.partitions = ${esp32.default_partitions} -; board_build.f_flash = 80000000L -; board_build.flash_mode = dio +board_build.partitions = ${esp32.extreme_partitions} +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +board_build.f_flash = 80000000L +board_build.flash_mode = dio + +;[env:esp32dev_audioreactive] +;board = esp32dev +;platform = ${esp32.platform} +;platform_packages = ${esp32.platform_packages} +;build_unflags = ${common.build_unflags} +;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET +; ${esp32.AR_build_flags} +;lib_deps = ${esp32.lib_deps} +; ${esp32.AR_lib_deps} +;monitor_filters = esp32_exception_decoder +;board_build.partitions = ${esp32.default_partitions} +;; board_build.f_flash = 80000000L +;; board_build.flash_mode = dio [env:esp32_eth] board = esp32-poe @@ -394,8 +474,10 @@ platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 - -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only +; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only + ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} + ${esp32.AR_lib_deps} board_build.partitions = ${esp32.default_partitions} [env:esp32_wrover] @@ -405,14 +487,14 @@ platform_packages = ${esp32_idf_V4.platform_packages} board = ttgo-t7-v14-mini32 board_build.f_flash = 80000000L board_build.flash_mode = qio -board_build.partitions = ${esp32.default_partitions} +board_build.partitions = ${esp32.extended_partitions} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html - -D LEDPIN=25 - ; ${esp32.AR_build_flags} + -D DATA_PINS=25 + ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} - ; ${esp32.AR_lib_deps} + ${esp32.AR_lib_deps} [env:esp32c3dev] extends = esp32c3 @@ -447,6 +529,8 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= lib_deps = ${esp32s3.lib_deps} ${esp32.AR_lib_deps} board_build.partitions = ${esp32.extreme_partitions} +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder @@ -472,6 +556,33 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder +[env:esp32S3_wroom2] +;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 +;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +board = esp32s3camlcd ;; this is the only standard board with "opi_opi" +board_build.arduino.memory_type = opi_opi +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 + -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 + -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -D LEDPIN=38 -D DATA_PINS=38 ;; buildin WS2812b LED + -D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1 + -D WLED_DEBUG + ${esp32.AR_build_flags} + -D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4 ;; I2S mic +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + +board_build.partitions = ${esp32.extreme_partitions} +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +monitor_filters = esp32_exception_decoder + [env:esp32s3_4M_qspi] ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM @@ -508,7 +619,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -D LEDPIN=16 + -D DATA_PINS=16 -D HW_PIN_SCL=35 -D HW_PIN_SDA=33 -D HW_PIN_CLOCKSPI=7 diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 7f6524922a..8e5fdf0030 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -59,7 +59,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; -D WLED_ENABLE_DMX ; ; PIN defines - uncomment and change, if needed: -; -D LEDPIN=2 +; -D DATA_PINS=2 ; or use this for multiple outputs ; -D DATA_PINS=1,3 ; -D BTNPIN=0 @@ -233,7 +233,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=1 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=1 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:esp32dev_qio80] @@ -339,7 +339,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=12 -D IRPIN=-1 -D RLYPIN=2 lib_deps = ${esp8266.lib_deps} [env:esp32c3dev_2MB] @@ -359,6 +359,8 @@ upload_speed = 115200 lib_deps = ${esp32c3.lib_deps} board_build.partitions = tools/WLED_ESP32_2MB_noOTA.csv board_build.flash_mode = dio +board_upload.flash_size = 2MB +board_upload.maximum_size = 2097152 [env:wemos_shield_esp32] board = esp32dev @@ -367,7 +369,7 @@ platform_packages = ${esp32.platform_packages} upload_speed = 460800 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32.build_flags} - -D LEDPIN=16 + -D DATA_PINS=16 -D RLYPIN=19 -D BTNPIN=17 -D IRPIN=18 @@ -386,7 +388,7 @@ board_build.partitions = ${esp32.default_partitions} [env:m5atom] board = esp32dev build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D LEDPIN=27 -D BTNPIN=39 +build_flags = ${common.build_flags} ${esp32.build_flags} -D DATA_PINS=27 -D BTNPIN=39 lib_deps = ${esp32.lib_deps} platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} @@ -396,14 +398,14 @@ board_build.partitions = ${esp32.default_partitions} board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=1 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=3 -D BTNPIN=1 lib_deps = ${esp8266.lib_deps} [env:sp511e] board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 lib_deps = ${esp8266.lib_deps} [env:Athom_RGBCW] ;7w and 5w(GU10) bulbs @@ -432,7 +434,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 -D LEDPIN=1 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 -D DATA_PINS=1 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:Athom_4Pin_Controller] ; With clock and data interface @@ -441,7 +443,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=12 -D LEDPIN=1 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=12 -D DATA_PINS=1 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:Athom_5Pin_Controller] ;Analog light strip controller @@ -496,7 +498,7 @@ upload_speed = 921600 build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED -D USERMOD_RTC -D USERMOD_ELEKSTUBE_IPS - -D LEDPIN=12 + -D DATA_PINS=12 -D RLYPIN=27 -D BTNPIN=34 -D DEFAULT_LED_COUNT=6 diff --git a/readme.md b/readme.md index 11c1733f87..80256560af 100644 --- a/readme.md +++ b/readme.md @@ -61,7 +61,7 @@ See [here](https://kno.wled.ge/basics/compatible-hardware)! ## ✌️ Other -Licensed under the MIT license +Licensed under the EUPL v1.2 license Credits [here](https://kno.wled.ge/about/contributors/)! Join the Discord server to discuss everything about WLED! @@ -80,5 +80,5 @@ If WLED really brightens up your day, you can [![](https://img.shields.io/badge/ If you are prone to photosensitive epilepsy, we recommended you do **not** use this software. If you still want to try, don't use strobe, lighting or noise modes or high effect speed settings. -As per the MIT license, I assume no liability for any damage to you or any other person or equipment. +As per the EUPL license, I assume no liability for any damage to you or any other person or equipment. diff --git a/requirements.txt b/requirements.txt index c4ce9445fd..666122aa28 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,19 +4,17 @@ # # pip-compile # -aiofiles==22.1.0 - # via platformio ajsonrpc==1.2.0 # via platformio -anyio==3.6.2 +anyio==4.6.0 # via starlette -bottle==0.12.25 +bottle==0.13.1 # via platformio -certifi==2023.7.22 +certifi==2024.8.30 # via requests -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests -click==8.1.3 +click==8.1.7 # via # platformio # uvicorn @@ -28,35 +26,33 @@ h11==0.14.0 # via # uvicorn # wsproto -idna==3.7 +idna==3.10 # via # anyio # requests -marshmallow==3.19.0 +marshmallow==3.22.0 # via platformio -packaging==23.1 +packaging==24.1 # via marshmallow -platformio==6.1.14 +platformio==6.1.16 # via -r requirements.in -pyelftools==0.29 +pyelftools==0.31 # via platformio pyserial==3.5 # via platformio -requests==2.32.0 +requests==2.32.3 # via platformio semantic-version==2.10.0 # via platformio -sniffio==1.3.0 +sniffio==1.3.1 # via anyio -starlette==0.23.1 +starlette==0.39.1 # via platformio tabulate==0.9.0 # via platformio -typing-extensions==4.11.0 - # via starlette -urllib3==1.26.19 +urllib3==2.2.3 # via requests -uvicorn==0.20.0 +uvicorn==0.30.6 # via platformio wsproto==1.2.0 # via platformio diff --git a/tools/WLED_ESP32_4MB_512KB_FS.csv b/tools/WLED_ESP32_4MB_512KB_FS.csv new file mode 100644 index 0000000000..5281a61244 --- /dev/null +++ b/tools/WLED_ESP32_4MB_512KB_FS.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x1B0000, +app1, app, ota_1, 0x1C0000,0x1B0000, +spiffs, data, spiffs, 0x370000,0x80000, +coredump, data, coredump,,64K \ No newline at end of file diff --git a/tools/all_xml.sh b/tools/all_xml.sh new file mode 100644 index 0000000000..68ed07bbda --- /dev/null +++ b/tools/all_xml.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Pull all settings pages for comparison +HOST=$1 +TGT_PATH=$2 +CURL_ARGS="--compressed" + +# Replicate one target many times +function replicate() { + for i in {0..10} + do + echo -n " http://${HOST}/settings.js?p=$i -o ${TGT_PATH}/$i.xml" + done +} +read -a TARGETS <<< $(replicate) + +mkdir -p ${TGT_PATH} +curl ${CURL_ARGS} ${TARGETS[@]} diff --git a/tools/cdata.js b/tools/cdata.js index 12dda1cbe0..d65573a8ea 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -116,7 +116,8 @@ async function minify(str, type = "plain") { } else if (type == "css-minify") { return new CleanCSS({}).minify(str).styles; } else if (type == "js-minify") { - return await minifyHtml('', options).replace(/<[\/]*script>/g, ''); + let js = await minifyHtml('', options); + return js.replace(/<[\/]*script>/g, ''); } else if (type == "html-minify") { return await minifyHtml(str, options); } @@ -252,6 +253,12 @@ writeChunks( str .replace("%%", "%") }, + { + file: "common.js", + name: "JS_common", + method: "gzip", + filter: "js-minify", + }, { file: "settings.htm", name: "PAGE_settings", diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 8953756d35..54a9b3331e 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -332,7 +332,7 @@ class Animated_Staircase : public Usermod { }; // NOTE: this *WILL* return TRUE if all the pins are set to -1. // this is *BY DESIGN*. - if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_AnimatedStaircase)) { + if (!PinManager::allocateMultiplePins(pins, 4, PinOwner::UM_AnimatedStaircase)) { topPIRorTriggerPin = -1; topEchoPin = -1; bottomPIRorTriggerPin = -1; @@ -425,10 +425,10 @@ class Animated_Staircase : public Usermod { } void appendConfigData() { - //oappend(SET_F("dd=addDropdown('staircase','selectfield');")); - //oappend(SET_F("addOption(dd,'1st value',0);")); - //oappend(SET_F("addOption(dd,'2nd value',1);")); - //oappend(SET_F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field + //oappend(F("dd=addDropdown('staircase','selectfield');")); + //oappend(F("addOption(dd,'1st value',0);")); + //oappend(F("addOption(dd,'2nd value',1);")); + //oappend(F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field } @@ -513,10 +513,10 @@ class Animated_Staircase : public Usermod { (oldBottomAPin != bottomPIRorTriggerPin) || (oldBottomBPin != bottomEchoPin)) { changed = true; - pinManager.deallocatePin(oldTopAPin, PinOwner::UM_AnimatedStaircase); - pinManager.deallocatePin(oldTopBPin, PinOwner::UM_AnimatedStaircase); - pinManager.deallocatePin(oldBottomAPin, PinOwner::UM_AnimatedStaircase); - pinManager.deallocatePin(oldBottomBPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldTopAPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldTopBPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldBottomAPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldBottomBPin, PinOwner::UM_AnimatedStaircase); } if (changed) setup(); } diff --git a/usermods/Animated_Staircase/README.md b/usermods/Animated_Staircase/README.md index 320b744a55..2ad66b5aef 100644 --- a/usermods/Animated_Staircase/README.md +++ b/usermods/Animated_Staircase/README.md @@ -18,7 +18,7 @@ Before compiling, you have to make the following modifications: Edit `usermods_list.cpp`: 1. Open `wled00/usermods_list.cpp` 2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file -3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function. +3. add `UsermodManager::add(new Animated_Staircase());` to the end of the `void registerUsermods()` function. You can configure usermod using the Usermods settings page. Please enter GPIO pins for PIR or ultrasonic sensors (trigger and echo). diff --git a/usermods/BME68X_v2/usermod_bme68x.h b/usermods/BME68X_v2/usermod_bme68x.h index 8e360515a2..aca24d0a29 100644 --- a/usermods/BME68X_v2/usermod_bme68x.h +++ b/usermods/BME68X_v2/usermod_bme68x.h @@ -767,22 +767,22 @@ void UsermodBME68X::appendConfigData() { // snprintf_P(charbuffer, 127, PSTR("addInfo('%s:%s',1,'*) Set to minus to deactivate (all sensors)');"), UMOD_NAME, _nameTemp); oappend(charbuffer); /* Dropdown for Celsius/Fahrenheit*/ - oappend(SET_F("dd=addDropdown('")); + oappend(F("dd=addDropdown('")); oappend(UMOD_NAME); - oappend(SET_F("','")); + oappend(F("','")); oappend(_nameTempScale); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'Celsius',0);")); - oappend(SET_F("addOption(dd,'Fahrenheit',1);")); + oappend(F("');")); + oappend(F("addOption(dd,'Celsius',0);")); + oappend(F("addOption(dd,'Fahrenheit',1);")); /* i²C Address*/ - oappend(SET_F("dd=addDropdown('")); + oappend(F("dd=addDropdown('")); oappend(UMOD_NAME); - oappend(SET_F("','")); + oappend(F("','")); oappend(_nameI2CAdr); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'0x76',0x76);")); - oappend(SET_F("addOption(dd,'0x77',0x77);")); + oappend(F("');")); + oappend(F("addOption(dd,'0x76',0x76);")); + oappend(F("addOption(dd,'0x77',0x77);")); } /** diff --git a/usermods/Battery/readme.md b/usermods/Battery/readme.md index 84a6f50542..c3d3d8bf47 100644 --- a/usermods/Battery/readme.md +++ b/usermods/Battery/readme.md @@ -131,6 +131,11 @@ Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6 ## 📝 Change Log +2024-08-19 + +- Improved MQTT support +- Added battery percentage & battery voltage as MQTT topic + 2024-05-11 - Documentation updated diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index f240d55f57..b36c5f4d60 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -50,6 +50,7 @@ class UsermodBattery : public Usermod // bool initDone = false; bool initializing = true; + bool HomeAssistantDiscovery = false; // strings to reduce flash memory usage (used more than twice) static const char _name[]; @@ -59,6 +60,7 @@ class UsermodBattery : public Usermod static const char _preset[]; static const char _duration[]; static const char _init[]; + static const char _haDiscovery[]; /** * Helper for rounding floating point values @@ -69,6 +71,17 @@ class UsermodBattery : public Usermod return (float)(nx / 100); } + /** + * Helper for converting a string to lowercase + */ + String stringToLower(String str) + { + for(int i = 0; i < str.length(); i++) + if(str[i] >= 'A' && str[i] <= 'Z') + str[i] += 32; + return str; + } + /** * Turn off all leds */ @@ -115,6 +128,55 @@ class UsermodBattery : public Usermod #endif } +#ifndef WLED_DISABLE_MQTT + void addMqttSensor(const String &name, const String &type, const String &topic, const String &deviceClass, const String &unitOfMeasurement = "", const bool &isDiagnostic = false) + { + StaticJsonDocument<600> doc; + char uid[128], json_str[1024], buf[128]; + + doc[F("name")] = name; + doc[F("stat_t")] = topic; + sprintf_P(uid, PSTR("%s_%s_%s"), escapedMac.c_str(), stringToLower(name).c_str(), type); + doc[F("uniq_id")] = uid; + doc[F("dev_cla")] = deviceClass; + doc[F("exp_aft")] = 1800; + + if(type == "binary_sensor") { + doc[F("pl_on")] = "on"; + doc[F("pl_off")] = "off"; + } + + if(unitOfMeasurement != "") + doc[F("unit_of_measurement")] = unitOfMeasurement; + + if(isDiagnostic) + doc[F("entity_category")] = "diagnostic"; + + JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device + device[F("name")] = serverDescription; + device[F("ids")] = String(F("wled-sensor-")) + mqttClientID; + device[F("mf")] = F(WLED_BRAND); + device[F("mdl")] = F(WLED_PRODUCT_NAME); + device[F("sw")] = versionString; + + sprintf_P(buf, PSTR("homeassistant/%s/%s/%s/config"), type, mqttClientID, uid); + DEBUG_PRINTLN(buf); + size_t payload_size = serializeJson(doc, json_str); + DEBUG_PRINTLN(json_str); + + mqtt->publish(buf, 0, true, json_str, payload_size); + } + + void publishMqtt(const char* topic, const char* state) + { + if (WLED_MQTT_CONNECTED) { + char buf[128]; + snprintf_P(buf, 127, PSTR("%s/%s"), mqttDeviceTopic, topic); + mqtt->publish(buf, 0, false, state); + } + } +#endif + public: //Functions called by WLED @@ -138,7 +200,7 @@ class UsermodBattery : public Usermod bool success = false; DEBUG_PRINTLN(F("Allocating battery pin...")); if (batteryPin >= 0 && digitalPinToAnalogChannel(batteryPin) >= 0) - if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { + if (PinManager::allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; } @@ -223,13 +285,8 @@ class UsermodBattery : public Usermod turnOff(); #ifndef WLED_DISABLE_MQTT - // SmartHome stuff - // still don't know much about MQTT and/or HA - if (WLED_MQTT_CONNECTED) { - char buf[64]; // buffer for snprintf() - snprintf_P(buf, 63, PSTR("%s/voltage"), mqttDeviceTopic); - mqtt->publish(buf, 0, false, String(bat->getVoltage()).c_str()); - } + publishMqtt("battery", String(bat->getLevel(), 0).c_str()); + publishMqtt("voltage", String(bat->getVoltage()).c_str()); #endif } @@ -288,6 +345,7 @@ class UsermodBattery : public Usermod battery[F("calibration")] = bat->getCalibration(); battery[F("voltage-multiplier")] = bat->getVoltageMultiplier(); battery[FPSTR(_readInterval)] = readingInterval; + battery[FPSTR(_haDiscovery)] = HomeAssistantDiscovery; JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section ao[FPSTR(_enabled)] = autoOffEnabled; @@ -307,8 +365,8 @@ class UsermodBattery : public Usermod getJsonValue(battery[F("max-voltage")], cfg.maxVoltage); getJsonValue(battery[F("calibration")], cfg.calibration); getJsonValue(battery[F("voltage-multiplier")], cfg.voltageMultiplier); - setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); + setHomeAssistantDiscovery(battery[FPSTR(_haDiscovery)] | HomeAssistantDiscovery); JsonObject ao = battery[F("auto-off")]; setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled); @@ -420,28 +478,29 @@ class UsermodBattery : public Usermod void appendConfigData() { // Total: 462 Bytes - oappend(SET_F("td=addDropdown('Battery', 'type');")); // 35 Bytes - oappend(SET_F("addOption(td, 'Unkown', '0');")); // 30 Bytes - oappend(SET_F("addOption(td, 'LiPo', '1');")); // 28 Bytes - oappend(SET_F("addOption(td, 'LiOn', '2');")); // 28 Bytes - oappend(SET_F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes - oappend(SET_F("addInfo('Battery:min-voltage', 1, 'v');")); // 40 Bytes - oappend(SET_F("addInfo('Battery:max-voltage', 1, 'v');")); // 40 Bytes - oappend(SET_F("addInfo('Battery:interval', 1, 'ms');")); // 38 Bytes - oappend(SET_F("addInfo('Battery:auto-off:threshold', 1, '%');")); // 47 Bytes - oappend(SET_F("addInfo('Battery:indicator:threshold', 1, '%');")); // 48 Bytes - oappend(SET_F("addInfo('Battery:indicator:duration', 1, 's');")); // 47 Bytes + oappend(F("td=addDropdown('Battery','type');")); // 34 Bytes + oappend(F("addOption(td,'Unkown','0');")); // 28 Bytes + oappend(F("addOption(td,'LiPo','1');")); // 26 Bytes + oappend(F("addOption(td,'LiOn','2');")); // 26 Bytes + oappend(F("addInfo('Battery:type',1,'requires reboot');")); // 81 Bytes + oappend(F("addInfo('Battery:min-voltage',1,'v');")); // 38 Bytes + oappend(F("addInfo('Battery:max-voltage',1,'v');")); // 38 Bytes + oappend(F("addInfo('Battery:interval',1,'ms');")); // 36 Bytes + oappend(F("addInfo('Battery:HA-discovery',1,'');")); // 38 Bytes + oappend(F("addInfo('Battery:auto-off:threshold',1,'%');")); // 45 Bytes + oappend(F("addInfo('Battery:indicator:threshold',1,'%');")); // 46 Bytes + oappend(F("addInfo('Battery:indicator:duration',1,'s');")); // 45 Bytes // this option list would exeed the oappend() buffer // a list of all presets to select one from - // oappend(SET_F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); - // the loop generates: oappend(SET_F("addOption(bd, 'preset name', preset id);")); + // oappend(F("bd=addDropdown('Battery:low-power-indicator', 'preset');")); + // the loop generates: oappend(F("addOption(bd, 'preset name', preset id);")); // for(int8_t i=1; i < 42; i++) { - // oappend(SET_F("addOption(bd, 'Preset#")); + // oappend(F("addOption(bd, 'Preset#")); // oappendi(i); - // oappend(SET_F("',")); + // oappend(F("',")); // oappendi(i); - // oappend(SET_F(");")); + // oappend(F(");")); // } } @@ -478,12 +537,12 @@ class UsermodBattery : public Usermod #ifdef ARDUINO_ARCH_ESP32 newBatteryPin = battery[F("pin")] | newBatteryPin; #endif - // calculateTimeLeftEnabled = battery[F("time-left")] | calculateTimeLeftEnabled; setMinBatteryVoltage(battery[F("min-voltage")] | bat->getMinVoltage()); setMaxBatteryVoltage(battery[F("max-voltage")] | bat->getMaxVoltage()); setCalibration(battery[F("calibration")] | bat->getCalibration()); setVoltageMultiplier(battery[F("voltage-multiplier")] | bat->getVoltageMultiplier()); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); + setHomeAssistantDiscovery(battery[FPSTR(_haDiscovery)] | HomeAssistantDiscovery); getUsermodConfigFromJsonObject(battery); @@ -502,7 +561,7 @@ class UsermodBattery : public Usermod if (newBatteryPin != batteryPin) { // deallocate pin - pinManager.deallocatePin(batteryPin, PinOwner::UM_Battery); + PinManager::deallocatePin(batteryPin, PinOwner::UM_Battery); batteryPin = newBatteryPin; // initialise setup(); @@ -513,38 +572,24 @@ class UsermodBattery : public Usermod return !battery[FPSTR(_readInterval)].isNull(); } - /** - * TBD: Generate a preset sample for low power indication - * a button on the config page would be cool, currently not possible - */ - void generateExamplePreset() +#ifndef WLED_DISABLE_MQTT + void onMqttConnect(bool sessionPresent) { - // StaticJsonDocument<300> j; - // JsonObject preset = j.createNestedObject(); - // preset["mainseg"] = 0; - // JsonArray seg = preset.createNestedArray("seg"); - // JsonObject seg0 = seg.createNestedObject(); - // seg0["id"] = 0; - // seg0["start"] = 0; - // seg0["stop"] = 60; - // seg0["grp"] = 0; - // seg0["spc"] = 0; - // seg0["on"] = true; - // seg0["bri"] = 255; - - // JsonArray col0 = seg0.createNestedArray("col"); - // JsonArray col00 = col0.createNestedArray(); - // col00.add(255); - // col00.add(0); - // col00.add(0); + // Home Assistant Autodiscovery + if (!HomeAssistantDiscovery) + return; - // seg0["fx"] = 1; - // seg0["sx"] = 128; - // seg0["ix"] = 128; + // battery percentage + char mqttBatteryTopic[128]; + snprintf_P(mqttBatteryTopic, 127, PSTR("%s/battery"), mqttDeviceTopic); + this->addMqttSensor(F("Battery"), "sensor", mqttBatteryTopic, "battery", "%", true); - // savePreset(199, "Low power Indicator", preset); + // voltage + char mqttVoltageTopic[128]; + snprintf_P(mqttVoltageTopic, 127, PSTR("%s/voltage"), mqttDeviceTopic); + this->addMqttSensor(F("Voltage"), "sensor", mqttVoltageTopic, "voltage", "V", true); } - +#endif /* * @@ -785,6 +830,22 @@ class UsermodBattery : public Usermod { return lowPowerIndicationDone; } + + /** + * Set Home Assistant auto discovery + */ + void setHomeAssistantDiscovery(bool enable) + { + HomeAssistantDiscovery = enable; + } + + /** + * Get Home Assistant auto discovery + */ + bool getHomeAssistantDiscovery() + { + return HomeAssistantDiscovery; + } }; // strings to reduce flash memory usage (used more than twice) @@ -795,3 +856,4 @@ const char UsermodBattery::_threshold[] PROGMEM = "threshold"; const char UsermodBattery::_preset[] PROGMEM = "preset"; const char UsermodBattery::_duration[] PROGMEM = "duration"; const char UsermodBattery::_init[] PROGMEM = "init"; +const char UsermodBattery::_haDiscovery[] PROGMEM = "HA-discovery"; diff --git a/usermods/EXAMPLE_v2/usermod_v2_example.h b/usermods/EXAMPLE_v2/usermod_v2_example.h index 32374fde2a..df05f3e3dc 100644 --- a/usermods/EXAMPLE_v2/usermod_v2_example.h +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -71,7 +71,7 @@ class MyExampleUsermod : public Usermod { // #endif // in setup() // #ifdef USERMOD_EXAMPLE - // UM = (MyExampleUsermod*) usermods.lookup(USERMOD_ID_EXAMPLE); + // UM = (MyExampleUsermod*) UsermodManager::lookup(USERMOD_ID_EXAMPLE); // #endif // somewhere in loop() or other member method // #ifdef USERMOD_EXAMPLE @@ -287,11 +287,11 @@ class MyExampleUsermod : public Usermod { */ void appendConfigData() override { - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":great")); oappend(SET_F("',1,'(this is a great config value)');")); - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":testString")); oappend(SET_F("',1,'enter any string you want');")); - oappend(SET_F("dd=addDropdown('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F("','testInt');")); - oappend(SET_F("addOption(dd,'Nothing',0);")); - oappend(SET_F("addOption(dd,'Everything',42);")); + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":great")); oappend(F("',1,'(this is a great config value)');")); + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":testString")); oappend(F("',1,'enter any string you want');")); + oappend(F("dd=addDropdown('")); oappend(String(FPSTR(_name)).c_str()); oappend(F("','testInt');")); + oappend(F("addOption(dd,'Nothing',0);")); + oappend(F("addOption(dd,'Everything',42);")); } diff --git a/usermods/Fix_unreachable_netservices_v2/readme.md b/usermods/Fix_unreachable_netservices_v2/readme.md index 006eaf9f94..07d64bc673 100644 --- a/usermods/Fix_unreachable_netservices_v2/readme.md +++ b/usermods/Fix_unreachable_netservices_v2/readme.md @@ -59,10 +59,10 @@ void registerUsermods() * || || || * \/ \/ \/ */ - //usermods.add(new MyExampleUsermod()); - //usermods.add(new UsermodTemperature()); - //usermods.add(new UsermodRenameMe()); - usermods.add(new FixUnreachableNetServices()); + //UsermodManager::add(new MyExampleUsermod()); + //UsermodManager::add(new UsermodTemperature()); + //UsermodManager::add(new UsermodRenameMe()); + UsermodManager::add(new FixUnreachableNetServices()); } ``` diff --git a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h index 2236bfeaba..c24b4c6288 100644 --- a/usermods/Internal_Temperature_v2/usermod_internal_temperature.h +++ b/usermods/Internal_Temperature_v2/usermod_internal_temperature.h @@ -149,11 +149,11 @@ class InternalTemperatureUsermod : public Usermod void appendConfigData() { // Display 'ms' next to the 'Loop Interval' setting - oappend(SET_F("addInfo('Internal Temperature:Loop Interval', 1, 'ms');")); + oappend(F("addInfo('Internal Temperature:Loop Interval', 1, 'ms');")); // Display '°C' next to the 'Activation Threshold' setting - oappend(SET_F("addInfo('Internal Temperature:Activation Threshold', 1, '°C');")); + oappend(F("addInfo('Internal Temperature:Activation Threshold', 1, '°C');")); // Display '0 = Disabled' next to the 'Preset To Activate' setting - oappend(SET_F("addInfo('Internal Temperature:Preset To Activate', 1, '0 = unused');")); + oappend(F("addInfo('Internal Temperature:Preset To Activate', 1, '0 = unused');")); } bool readFromConfig(JsonObject &root) diff --git a/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h b/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h index 393fc22327..03f4c078a4 100644 --- a/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h +++ b/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h @@ -30,7 +30,7 @@ class LDR_Dusk_Dawn_v2 : public Usermod { void setup() { // register ldrPin if ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)) { - if(!pinManager.allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod + if(!PinManager::allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod else pinMode(ldrPin, INPUT); // alloc success -> configure pin for input } else ldrEnabled = false; // invalid pin -> disable usermod initDone = true; @@ -110,7 +110,7 @@ class LDR_Dusk_Dawn_v2 : public Usermod { if (initDone && (ldrPin != oldLdrPin)) { // pin changed - un-register previous pin, register new pin - if (oldLdrPin >= 0) pinManager.deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN); + if (oldLdrPin >= 0) PinManager::deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN); setup(); // setup new pin } return configComplete; @@ -139,7 +139,7 @@ class LDR_Dusk_Dawn_v2 : public Usermod { //LDR_Off_Count.add(ldrOffCount); //bool pinValid = ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)); - //if (pinManager.getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false; + //if (PinManager::getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false; //JsonArray LDR_valid = user.createNestedArray(F("LDR pin")); //LDR_valid.add(ldrPin); //LDR_valid.add(pinValid ? F(" OK"): F(" invalid")); diff --git a/usermods/PIR_sensor_switch/readme.md b/usermods/PIR_sensor_switch/readme.md index 4dfdb07bd3..fac5419f00 100644 --- a/usermods/PIR_sensor_switch/readme.md +++ b/usermods/PIR_sensor_switch/readme.md @@ -52,7 +52,7 @@ class MyUsermod : public Usermod { void togglePIRSensor() { #ifdef USERMOD_PIR_SENSOR_SWITCH - PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) usermods.lookup(USERMOD_ID_PIRSWITCH); + PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) UsermodManager::lookup(USERMOD_ID_PIRSWITCH); if (PIRsensor != nullptr) { PIRsensor->EnablePIRsensor(!PIRsensor->PIRsensorEnabled()); } diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 7a67dd7497..0deda181c2 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -375,7 +375,7 @@ void PIRsensorSwitch::setup() sensorPinState[i] = LOW; if (PIRsensorPin[i] < 0) continue; // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (pinManager.allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) { + if (PinManager::allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) { // PIR Sensor mode INPUT_PULLDOWN #ifdef ESP8266 pinMode(PIRsensorPin[i], PIRsensorPin[i]==16 ? INPUT_PULLDOWN_16 : INPUT_PULLUP); // ESP8266 has INPUT_PULLDOWN on GPIO16 only @@ -511,8 +511,8 @@ void PIRsensorSwitch::addToConfig(JsonObject &root) void PIRsensorSwitch::appendConfigData() { - oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) { char str[128]; sprintf_P(str, PSTR("addInfo('PIRsensorSwitch:pin[]',%d,'','#%d');"), i, i); @@ -564,7 +564,7 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) DEBUG_PRINTLN(F(" config loaded.")); } else { for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) - if (oldPin[i] >= 0) pinManager.deallocatePin(oldPin[i], PinOwner::UM_PIR); + if (oldPin[i] >= 0) PinManager::deallocatePin(oldPin[i], PinOwner::UM_PIR); setup(); DEBUG_PRINTLN(F(" config (re)loaded.")); } diff --git a/usermods/PWM_fan/usermod_PWM_fan.h b/usermods/PWM_fan/usermod_PWM_fan.h index 1b78cfd4cc..c3ef24fe41 100644 --- a/usermods/PWM_fan/usermod_PWM_fan.h +++ b/usermods/PWM_fan/usermod_PWM_fan.h @@ -75,7 +75,7 @@ class PWMFanUsermod : public Usermod { static const char _lock[]; void initTacho(void) { - if (tachoPin < 0 || !pinManager.allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){ + if (tachoPin < 0 || !PinManager::allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){ tachoPin = -1; return; } @@ -88,7 +88,7 @@ class PWMFanUsermod : public Usermod { void deinitTacho(void) { if (tachoPin < 0) return; detachInterrupt(digitalPinToInterrupt(tachoPin)); - pinManager.deallocatePin(tachoPin, PinOwner::UM_Unspecified); + PinManager::deallocatePin(tachoPin, PinOwner::UM_Unspecified); tachoPin = -1; } @@ -111,7 +111,7 @@ class PWMFanUsermod : public Usermod { // https://randomnerdtutorials.com/esp32-pwm-arduino-ide/ void initPWMfan(void) { - if (pwmPin < 0 || !pinManager.allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) { + if (pwmPin < 0 || !PinManager::allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) { enabled = false; pwmPin = -1; return; @@ -121,7 +121,7 @@ class PWMFanUsermod : public Usermod { analogWriteRange(255); analogWriteFreq(WLED_PWM_FREQ); #else - pwmChannel = pinManager.allocateLedc(1); + pwmChannel = PinManager::allocateLedc(1); if (pwmChannel == 255) { //no more free LEDC channels deinitPWMfan(); return; } @@ -136,9 +136,9 @@ class PWMFanUsermod : public Usermod { void deinitPWMfan(void) { if (pwmPin < 0) return; - pinManager.deallocatePin(pwmPin, PinOwner::UM_Unspecified); + PinManager::deallocatePin(pwmPin, PinOwner::UM_Unspecified); #ifdef ARDUINO_ARCH_ESP32 - pinManager.deallocateLedc(pwmChannel, 1); + PinManager::deallocateLedc(pwmChannel, 1); #endif pwmPin = -1; } @@ -191,9 +191,9 @@ class PWMFanUsermod : public Usermod { void setup() override { #ifdef USERMOD_DALLASTEMPERATURE // This Usermod requires Temperature usermod - tempUM = (UsermodTemperature*) usermods.lookup(USERMOD_ID_TEMPERATURE); + tempUM = (UsermodTemperature*) UsermodManager::lookup(USERMOD_ID_TEMPERATURE); #elif defined(USERMOD_SHT) - tempUM = (ShtUsermod*) usermods.lookup(USERMOD_ID_SHT); + tempUM = (ShtUsermod*) UsermodManager::lookup(USERMOD_ID_SHT); #endif initTacho(); initPWMfan(); diff --git a/usermods/Power_Measurement/Power_Measurement.h b/usermods/Power_Measurement/Power_Measurement.h new file mode 100644 index 0000000000..297d007c75 --- /dev/null +++ b/usermods/Power_Measurement/Power_Measurement.h @@ -0,0 +1,583 @@ +// Filename: Power_Measurement.h +// This code was cocreated by github copilot and created by Tomáš Kuchta +#pragma once + +#include "wled.h" +#include "esp_adc_cal.h" + +#ifndef CURRENT_PIN + #define CURRENT_PIN 1 +#endif + +#ifndef VOLTAGE_PIN + #define VOLTAGE_PIN 0 +#endif + +#define NUM_READINGS 10 +#define NUM_READINGS_CAL 100 +#define ADC_MAX_VALUE (pow(2, ADCResolution) - 1) // For 12-bit ADC, the max value is 4095 +#define UPDATE_INTERVAL_MAIN 100 +#define UPDATE_INTERVAL_MQTT 60000 + +class UsermodPower_Measurement : public Usermod { + private: + bool initDone = false; + unsigned long lastTime_slow = 0; + unsigned long lastTime_main = 0; + unsigned long lastTime_energy = 0; + unsigned long lastTime_mqtt = 0; + boolean enabled = true; + boolean calibration_enable = false; + boolean cal_adavnced = false; + + int Voltage_raw = 0; + float AverageVoltage_raw = 0; + int Voltage_raw_adj = 0; + int Voltage_calc = 0; + + int Current_raw = 0; + float AverageCurrent_raw = 0; + int Current_calc = 0; + + float voltageReadings_raw[NUM_READINGS]; + float currentReadings_raw[NUM_READINGS]; + int readIndex = 0; + float totalVoltage_raw = 0; + float totalCurrent_raw = 0; + + // Low-pass filter variables + float alpha = 0.1; + float filtered_Voltage_raw = 0; + float filtered_Current_raw = 0; + + unsigned long long wattmiliseconds = 0; //energy counter in watt milliseconds + + + // calibration variables + int Num_Readings_Cal = NUM_READINGS_CAL; + bool Cal_In_Progress = false; + bool Cal_Zero_Points = false; + bool Cal_calibrate_Measured_Voltage = false; + bool Cal_calibrate_Measured_Current = false; + float Cal_Measured_Voltage = 0; + float Cal_Measured_Current = 0; + + float Cal_min_Voltage_raw = 17; + float Cal_min_Current_calc = 718; + + float Cal_Voltage_raw_averaged = 0; + float Cal_Voltage_calc_averaged = 0; + float Cal_Current_calc_averaged = 0; + + int Cal_Current_at_x = 1000; + int Cal_Current_calc_at_x = 775; + float Cal_Voltage_Coefficient = 22.97; + + // averiging variables + float Cal_Voltage_raw_Readings_Avg[NUM_READINGS_CAL]; + float Cal_Voltage_calc_Readings_Avg[NUM_READINGS_CAL]; + float Cal_Current_calc_Readings_Avg[NUM_READINGS_CAL]; + int Cal_Read_Index = 0; + float Cal_Total_Voltage_raw = 0; + float Cal_Total_Voltage_calc = 0; + float Cal_Total_Current_calc = 0; + + int8_t VoltagePin = VOLTAGE_PIN; + int8_t CurrentPin = CURRENT_PIN; + + int Update_Interval_Mqtt = UPDATE_INTERVAL_MQTT; + int Update_Interval_Main = UPDATE_INTERVAL_MAIN; + + // String used more than once + static const char _name[] PROGMEM; + static const char _no_data[] PROGMEM; + + public: + int ADCResolution = 12; + int ADCAttenuation = ADC_6db; + + //For usage in other parts of the main code + float Voltage = 0; + float Current = 0; + float Power = 0; + unsigned long watthours = 0; + + void setup() { + analogReadResolution(ADCResolution); + analogSetAttenuation(static_cast(ADCAttenuation)); // Set the ADC attenuation (ADC_ATTEN_DB_6 = 0 mV ~ 1300 mV) + + // Initialize all readings to 0: + for (int i = 0; i < NUM_READINGS; i++) { + voltageReadings_raw[i] = 0; + currentReadings_raw[i] = 0; + } + + Current_raw = 1800; + filtered_Current_raw = 1800; + + Cal_Zero_Points = false; + Cal_calibrate_Measured_Voltage = false; + Cal_calibrate_Measured_Current = false; + Cal_In_Progress = false; + Num_Readings_Cal = NUM_READINGS_CAL; + + + #ifdef WLED_DEBUG + esp_adc_cal_characteristics_t adc_chars; + esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, ESP_ADC_CAL_VAL_DEFAULT_VREF, &adc_chars); + + if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { + // eFuse Vref is available + DEBUG_PRINTLN(F("PM: Using eFuse Vref for ADC calibration_enable")); + } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { + // Two Point calibration_enable is available + DEBUG_PRINTLN(F("PM: Using Two Point calibration_enable for ADC calibration_enable")); + } else { + // Default Vref is used + DEBUG_PRINTLN(F("PM: Using default Vref for ADC calibration_enable")); + } + #endif + + + if (enabled) { + pinAlocation(); + } + + initDone = true; + + } + + void loop() { + if (!enabled || strip.isUpdating()) return; + + unsigned long currentTime = millis(); + + #ifdef WLED_DEBUG + if (currentTime - lastTime_slow >= 1000) { + printDebugInfo(); + lastTime_slow = currentTime; + } + #endif + + #ifndef WLED_DISABLE_MQTT + if (currentTime - lastTime_mqtt >= Update_Interval_Mqtt) { + publishPowerMeasurements(); + lastTime_mqtt = currentTime; + } + #endif + + if (currentTime - lastTime_main >= Update_Interval_Main) { + updateReadings(); + + if (Cal_Zero_Points || Cal_calibrate_Measured_Voltage || Cal_calibrate_Measured_Current) calibration(); + + lastTime_main = currentTime; + } + + + } + + + + void pinAlocation() { + DEBUG_PRINTLN(F("Allocating power pins...")); + if (VoltagePin >= 0 && pinManager.allocatePin(VoltagePin, true, PinOwner::UM_Power_Measurement)) { + DEBUG_PRINT(F("Voltage pin allocated: ")); + DEBUG_PRINTLN(VoltagePin); + } else { + if (VoltagePin >= 0) { + DEBUG_PRINTLN(F("Voltage pin allocation failed.")); + } + VoltagePin = -1; // allocation failed, disable + } + + if (CurrentPin >= 0 && pinManager.allocatePin(CurrentPin, true, PinOwner::UM_Power_Measurement)) { + DEBUG_PRINT(F("Current pin allocated: ")); + DEBUG_PRINTLN(CurrentPin); + } else { + if (CurrentPin >= 0) { + DEBUG_PRINTLN(F("Current pin allocation failed.")); + } + CurrentPin = -1; // allocation failed, disable + } + } + + + void printDebugInfo() { + DEBUG_PRINT(F("Voltage raw: ")); + DEBUG_PRINTLN(Voltage_raw); + DEBUG_PRINTLN(AverageVoltage_raw); + DEBUG_PRINT(F("Calc: ")); + DEBUG_PRINTLN(Voltage_calc); + DEBUG_PRINT(F("Voltage: ")); + DEBUG_PRINTLN(Voltage); + + DEBUG_PRINT(F("Current raw: ")); + DEBUG_PRINTLN(Current_raw); + DEBUG_PRINTLN(AverageCurrent_raw); + DEBUG_PRINT(F("Calc: ")); + DEBUG_PRINTLN(Current_calc); + DEBUG_PRINT("Current: "); + DEBUG_PRINTLN(Current); + + DEBUG_PRINT("Power: "); + DEBUG_PRINTLN(Power); + + DEBUG_PRINT("Energy: "); + DEBUG_PRINT(watthours); + DEBUG_PRINTLN(" Wh"); + DEBUG_PRINT("Energy Wms: "); + DEBUG_PRINTLN(wattmiliseconds); + } + + void updateReadings() { + // Measure the voltage and current and store them in the arrays for the moving average and convert via map function: + totalVoltage_raw -= voltageReadings_raw[readIndex]; + totalCurrent_raw -= currentReadings_raw[readIndex]; + + if (VoltagePin == -1) { + Voltage_raw = 0; + DEBUG_PRINTLN("Voltage pin not allocated"); + } else { + Voltage_raw = analogRead(VoltagePin); + } + + if (CurrentPin == -1) { + Current_raw = 0; + DEBUG_PRINTLN("Current pin not allocated"); + } else { + Current_raw = analogRead(CurrentPin); + } + + if (millis() > 1000) { // To avoid the initial spike in readings + filtered_Voltage_raw = (alpha * Voltage_raw) + ((1 - alpha) * filtered_Voltage_raw); + filtered_Current_raw = (alpha * Current_raw) + ((1 - alpha) * filtered_Current_raw); + } else { + filtered_Voltage_raw = Voltage_raw; + filtered_Current_raw = Current_raw; + } + + voltageReadings_raw[readIndex] = filtered_Voltage_raw; + currentReadings_raw[readIndex] = filtered_Current_raw; + + totalVoltage_raw += filtered_Voltage_raw; + totalCurrent_raw += filtered_Current_raw; + + AverageVoltage_raw = totalVoltage_raw / NUM_READINGS; + AverageCurrent_raw = totalCurrent_raw / NUM_READINGS; + + readIndex = (readIndex + 1) % NUM_READINGS; + + Voltage_raw_adj = map(AverageVoltage_raw, Cal_min_Voltage_raw, ADC_MAX_VALUE, 0, ADC_MAX_VALUE); + if (Voltage_raw_adj < 0) Voltage_raw_adj = 0; + Voltage_calc = readADC_Cal(Voltage_raw_adj); + Voltage = (Voltage_calc / 1000.0) * Cal_Voltage_Coefficient; + if (Voltage < 0.05) Voltage = 0; + Voltage = round(Voltage * 100.0) / 100.0; // Round to 2 decimal places + if (VoltagePin == -1) Voltage = 0; + + Current_calc = readADC_Cal(AverageCurrent_raw); + Current = (map(Current_calc, Cal_min_Current_calc, Cal_Current_calc_at_x, 0, Cal_Current_at_x)) / 1000.0; + if (Current > -0.1 && Current < 0.05) { + Current = 0; + } + Current = round(Current * 100.0) / 100.0; + if (CurrentPin == -1) Current = 0; + + // Calculate power + Power = Voltage * Current; + Power = round(Power * 100.0) / 100.0; + + // Calculate energy - dont do it when led is off + if (Power > 0) { + unsigned long currentTime = millis(); + unsigned long elapsedTime = currentTime - lastTime_energy; + lastTime_energy = currentTime; + + unsigned long long wattMilliseconds = static_cast(Power) * elapsedTime; + wattmiliseconds += wattMilliseconds; + + // Convert watt-milliseconds to kilowatt-hours + if (wattmiliseconds >= 3600000ULL) { // 3,600,000 milliseconds = 1 hour + watthours += static_cast(wattmiliseconds) / 3600000.0; + wattmiliseconds = 0; + } + } + } + + void calibration() { + if (Num_Readings_Cal == NUM_READINGS_CAL) { + DEBUG_PRINTLN("calibration_enable started"); + Cal_In_Progress = true; + serializeConfig(); // To update the checkboxes in the config + } + if (Num_Readings_Cal > 0) { + Num_Readings_Cal--; + // Average the readings + Cal_Total_Voltage_raw -= Cal_Voltage_raw_Readings_Avg[Cal_Read_Index]; + Cal_Total_Voltage_calc -= Cal_Voltage_calc_Readings_Avg[Cal_Read_Index]; + Cal_Total_Current_calc -= Cal_Current_calc_Readings_Avg[Cal_Read_Index]; + + Cal_Voltage_raw_Readings_Avg[Cal_Read_Index] = Voltage_raw; + Cal_Voltage_calc_Readings_Avg[Cal_Read_Index] = Voltage_calc; + Cal_Current_calc_Readings_Avg[Cal_Read_Index] = Current_calc; + + Cal_Total_Voltage_raw += Voltage_raw; + Cal_Total_Voltage_calc += Voltage_calc; + Cal_Total_Current_calc += Current_calc; + + Cal_Read_Index = (Cal_Read_Index + 1) % NUM_READINGS_CAL; + + Cal_Voltage_raw_averaged = Cal_Total_Voltage_raw / NUM_READINGS_CAL; + Cal_Voltage_calc_averaged = Cal_Total_Voltage_calc / NUM_READINGS_CAL; + Cal_Current_calc_averaged = Cal_Total_Current_calc / NUM_READINGS_CAL; + } else { + + DEBUG_PRINTLN("calibration_enable Flags:"); + DEBUG_PRINTLN(Cal_In_Progress); + DEBUG_PRINTLN(Num_Readings_Cal); + DEBUG_PRINTLN(Cal_Zero_Points); + DEBUG_PRINTLN(Cal_calibrate_Measured_Voltage); + DEBUG_PRINTLN(Cal_calibrate_Measured_Current); + DEBUG_PRINTLN("the averaged values are:"); + DEBUG_PRINTLN(Cal_Voltage_raw_averaged); + DEBUG_PRINTLN(Cal_Voltage_calc_averaged); + DEBUG_PRINTLN(Cal_Current_calc_averaged); + DEBUG_PRINTLN("Inputed values are:"); + DEBUG_PRINTLN(Cal_Measured_Voltage); + DEBUG_PRINTLN(Cal_Measured_Current); + + Calibration_calculation(); + + Cal_In_Progress = false; + Cal_Zero_Points = false; + Cal_calibrate_Measured_Voltage = false; + Cal_calibrate_Measured_Current = false; + Num_Readings_Cal = NUM_READINGS_CAL; + serializeConfig(); // To update the checkboxes in the config + + DEBUG_PRINTLN("calibration_enable finished"); + } + } + + void Calibration_calculation() { + DEBUG_PRINTLN("Calculating calibration_enable values"); + + if (Cal_calibrate_Measured_Current) { + Cal_Current_at_x = Cal_Measured_Current * 1000; + Cal_Current_calc_at_x = Cal_Current_calc_averaged; + + } else if (Cal_calibrate_Measured_Voltage) { + Cal_Voltage_Coefficient = (Cal_Measured_Voltage * 1000) / Cal_Voltage_calc_averaged; + + } else if (Cal_Zero_Points) { + Cal_min_Voltage_raw = Cal_Voltage_raw_averaged; + Cal_min_Current_calc = Cal_Current_calc_averaged; + } else { + DEBUG_PRINTLN("No calibration_enable values selected - but that should not happen"); + } + + } + + void addToJsonInfo(JsonObject& root) { + if (!enabled)return; + + JsonObject user = root["u"]; + if (user.isNull())user = root.createNestedObject("u"); + + JsonArray Current_json = user.createNestedArray(FPSTR("Current")); + if (Current_raw == 0 || CurrentPin == -1) { + Current_json.add(F(_no_data)); + } else if (Current_raw >= (ADC_MAX_VALUE - 3)) { + Current_json.add(F("Overrange")); + } else { + Current_json.add(Current); + Current_json.add(F(" A")); + } + + JsonArray Voltage_json = user.createNestedArray(FPSTR("Voltage")); + if (Voltage_raw == 0 || VoltagePin == -1) { + Voltage_json.add(F(_no_data)); + } else if (Voltage_raw >= (ADC_MAX_VALUE - 3)) { + Voltage_json.add(F(_no_data)); + } else if (Voltage_raw >= (ADC_MAX_VALUE - 3)) { + Voltage_json.add(F("Overrange")); + } else { + Voltage_json.add(Voltage); + Voltage_json.add(F(" V")); + } + + if (calibration_enable) { + JsonArray Current_raw_json = user.createNestedArray(FPSTR("Current raw")); + Current_raw_json.add(Current_raw); + Current_raw_json.add(" -> " + String(Current_calc)); + + JsonArray Voltage_raw_json = user.createNestedArray(FPSTR("Voltage raw")); + Voltage_raw_json.add(Voltage_raw); + Voltage_raw_json.add(" -> " + String(Voltage_calc)); + } + + JsonArray Power_json = user.createNestedArray(FPSTR("Power")); + Power_json.add(Power); + Power_json.add(F(" W")); + + JsonArray Energy_json = user.createNestedArray(FPSTR("Energy")); + Energy_json.add(watthours); + Energy_json.add(F(" Wh")); + } + + void addToConfig(JsonObject& root) { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR("enabled")] = enabled; + + JsonObject power_pins = top.createNestedObject(FPSTR("power_pins")); + power_pins[FPSTR("Voltage Pin")] = VoltagePin; + power_pins[FPSTR("Current Pin")] = CurrentPin; + + JsonObject update = top.createNestedObject(FPSTR("update rate in ms")); + update[FPSTR("update rate of mqtt")] = Update_Interval_Mqtt; + update[FPSTR("update rate of main")] = Update_Interval_Main; + + JsonObject cal = top.createNestedObject(FPSTR("calibration")); + cal[FPSTR("calibration Mode")] = calibration_enable; + if (calibration_enable && !Cal_In_Progress) { + cal[FPSTR("Advanced")] = cal_adavnced; + + cal["Zero Points"] = Cal_Zero_Points; + cal["Measured Voltage"] = Cal_Measured_Voltage; + cal["Calibrate Voltage?"] = Cal_calibrate_Measured_Voltage; + cal["Measured Current"] = Cal_Measured_Current; + cal["Calibrate Current?"] = Cal_calibrate_Measured_Current; + } else if (Cal_In_Progress) { + cal[FPSTR("calibration_enable is in progress please wait")] = "Non-Essential Data Entry Zone: Just for Kicks and Giggles"; + } + + if (calibration_enable && cal_adavnced && !Cal_In_Progress) { + cal[FPSTR("Number of samples")] = Num_Readings_Cal; + cal[FPSTR("Zero Point of Voltage")] = Cal_min_Voltage_raw; + cal[FPSTR("Zero Point of Current")] = Cal_min_Current_calc; + cal[FPSTR("Voltage Coefficient")] = Cal_Voltage_Coefficient; + cal[FPSTR("Current at X (mV at ADC)")] = Cal_Current_calc_at_x; + cal[FPSTR("Current at X (mA)")] = Cal_Current_at_x; + } + } + + bool readFromConfig(JsonObject& root) { + int8_t tmpVoltagePin = VoltagePin; + int8_t tmpCurrentPin = CurrentPin; + + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + enabled = top[FPSTR("Enabled")] | enabled; + + tmpVoltagePin = top[FPSTR("power_pins")][FPSTR("Voltage Pin")] | tmpVoltagePin; + tmpCurrentPin = top[FPSTR("power_pins")][FPSTR("Current Pin")] | tmpCurrentPin; + + Update_Interval_Mqtt = top[FPSTR("update rate in ms")][FPSTR("update rate of mqtt")] | Update_Interval_Mqtt; + Update_Interval_Main = top[FPSTR("update rate in ms")][FPSTR("update rate of main")] | Update_Interval_Main; + + JsonObject cal = top[FPSTR("calibration")]; + calibration_enable = cal[FPSTR("calibration Mode")] | calibration_enable; + + if (calibration_enable && !Cal_In_Progress) { + cal_adavnced = cal[FPSTR("Advanced")] | cal_adavnced; + + Cal_Zero_Points = cal["Zero Points"] | Cal_Zero_Points; + Cal_Measured_Voltage = cal["Measured Voltage"] | Cal_Measured_Voltage; + Cal_calibrate_Measured_Voltage = cal["Calibrate Voltage?"] | Cal_calibrate_Measured_Voltage; + Cal_Measured_Current = cal["Measured Current"] | Cal_Measured_Current; + Cal_calibrate_Measured_Current = cal["Calibrate Current?"] | Cal_calibrate_Measured_Current; + } + + if (calibration_enable && cal_adavnced && !Cal_In_Progress) { + Num_Readings_Cal = cal[FPSTR("Number of samples")] | Num_Readings_Cal; + Cal_min_Voltage_raw = cal[FPSTR("Zero Point of Voltage")] | Cal_min_Voltage_raw; + Cal_min_Current_calc = cal[FPSTR("Zero Point of Current")] | Cal_min_Current_calc; + Cal_Voltage_Coefficient = cal[FPSTR("Voltage Coefficient")] | Cal_Voltage_Coefficient; + Cal_Current_calc_at_x = cal[FPSTR("Current at X (mV at ADC)")] | Cal_Current_calc_at_x; + Cal_Current_at_x = cal[FPSTR("Current at X (mA)")] | Cal_Current_at_x; + } + + if (!initDone) { + // first run: reading from cfg.json + VoltagePin = tmpVoltagePin; + CurrentPin = tmpCurrentPin; + DEBUG_PRINTLN(F(" config loaded.")); + } else { + DEBUG_PRINTLN(F(" config (re)loaded.")); + // changing paramters from settings page + if (tmpVoltagePin != VoltagePin || tmpCurrentPin != CurrentPin) { + DEBUG_PRINTLN(F("Re-init Power pins.")); + // deallocate pin and release memory + pinManager.deallocatePin(VoltagePin, PinOwner::UM_Power_Measurement); + VoltagePin = tmpVoltagePin; + pinManager.deallocatePin(CurrentPin, PinOwner::UM_Power_Measurement); + CurrentPin = tmpCurrentPin; + // initialise + pinAlocation(); + } + } + + return true; + } + + #ifndef WLED_DISABLE_MQTT + void onMqttConnect(bool sessionPresent) { + publishPowerMeasurements(); + } + + void publishPowerMeasurements() { + if (WLED_MQTT_CONNECTED) { + char subuf[64]; + char payload[32]; + + // Publish Voltage + strcpy(subuf, mqttDeviceTopic); + strcat_P(subuf, PSTR("/power_measurement/voltage")); + dtostrf(Voltage, 6, 2, payload); // Convert float to string + mqtt->publish(subuf, 0, true, payload); + + // Publish Current + strcpy(subuf, mqttDeviceTopic); + strcat_P(subuf, PSTR("/power_measurement/current")); + dtostrf(Current, 6, 2, payload); // Convert float to string + mqtt->publish(subuf, 0, true, payload); + + // Publish Power + strcpy(subuf, mqttDeviceTopic); + strcat_P(subuf, PSTR("/power_measurement/power")); + dtostrf(Power, 6, 2, payload); // Convert float to string + mqtt->publish(subuf, 0, true, payload); + + // Publish watthours + strcpy(subuf, mqttDeviceTopic); + strcat_P(subuf, PSTR("/power_measurement/watthours")); + ultoa(watthours, payload, 10); // Convert unsigned long to string + mqtt->publish(subuf, 0, true, payload); + } + } + #endif + + uint16_t getId() override { + return USERMOD_ID_POWER_MEASUREMENT; + } + + uint32_t readADC_Cal(int ADC_Raw) { + esp_adc_cal_characteristics_t adc_chars; + esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, ESP_ADC_CAL_VAL_DEFAULT_VREF, &adc_chars); + if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { + // Handle error if calibration_enable value is not available + DEBUG_PRINTF("Error: eFuse Vref not available"); + return 0; + } + return (esp_adc_cal_raw_to_voltage(ADC_Raw, &adc_chars)); + } +}; + +// String used more than once +const char UsermodPower_Measurement::_name[] PROGMEM = "Power Measurement"; +const char UsermodPower_Measurement::_no_data[] PROGMEM = "No data"; \ No newline at end of file diff --git a/usermods/Power_Measurement/assets/example_schematic.kicad_sch b/usermods/Power_Measurement/assets/example_schematic.kicad_sch new file mode 100644 index 0000000000..7b0c9bb933 --- /dev/null +++ b/usermods/Power_Measurement/assets/example_schematic.kicad_sch @@ -0,0 +1,3266 @@ +(kicad_sch + (version 20231120) + (generator "eeschema") + (generator_version "8.0") + (uuid "2360a543-140e-4488-b7ed-7d45263c2314") + (paper "A4") + (lib_symbols + (symbol "Device:C" + (pin_numbers hide) + (pin_names + (offset 0.254) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "C" + (at 0.635 2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "C" + (at 0.635 -2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 0.9652 -3.81 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Unpolarized capacitor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "cap capacitor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "C_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "C_0_1" + (polyline + (pts + (xy -2.032 -0.762) (xy 2.032 -0.762) + ) + (stroke + (width 0.508) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -2.032 0.762) (xy 2.032 0.762) + ) + (stroke + (width 0.508) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "C_1_1" + (pin passive line + (at 0 3.81 270) + (length 2.794) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 2.794) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + (symbol "Device:C_Polarized" + (pin_numbers hide) + (pin_names + (offset 0.254) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "C" + (at 0.635 2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "C_Polarized" + (at 0.635 -2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 0.9652 -3.81 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Polarized capacitor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "cap capacitor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "CP_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "C_Polarized_0_1" + (rectangle + (start -2.286 0.508) + (end 2.286 1.016) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -1.778 2.286) (xy -0.762 2.286) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -1.27 2.794) (xy -1.27 1.778) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (rectangle + (start 2.286 -0.508) + (end -2.286 -1.016) + (stroke + (width 0) + (type default) + ) + (fill + (type outline) + ) + ) + ) + (symbol "C_Polarized_1_1" + (pin passive line + (at 0 3.81 270) + (length 2.794) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 2.794) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + (symbol "Device:Fuse" + (pin_numbers hide) + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "F" + (at 2.032 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "Fuse" + (at -1.905 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at -1.778 0 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Fuse" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "fuse" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "*Fuse*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "Fuse_0_1" + (rectangle + (start -0.762 -2.54) + (end 0.762 2.54) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 2.54) (xy 0 -2.54) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "Fuse_1_1" + (pin passive line + (at 0 3.81 270) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + (symbol "Device:R_Small" + (pin_numbers hide) + (pin_names + (offset 0.254) hide) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "R" + (at 0.762 0.508 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "R_Small" + (at 0.762 -1.016 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor, small symbol" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "R resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "R_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "R_Small_0_1" + (rectangle + (start -0.762 1.778) + (end 0.762 -1.778) + (stroke + (width 0.2032) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "R_Small_1_1" + (pin passive line + (at 0 2.54 270) + (length 0.762) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -2.54 90) + (length 0.762) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + (symbol "Sensor_Current:ACS722xLCTR-10AB" + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "U" + (at 2.54 11.43 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "ACS722xLCTR-10AB" + (at 2.54 8.89 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm" + (at 2.54 -8.89 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + (hide yes) + ) + ) + (property "Datasheet" "http://www.allegromicro.com/~/media/Files/Datasheets/ACS722-Datasheet.ashx?la=en" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "±10A Bidirectional Hall-Effect Current Sensor, +3.3V supply, 132mV/A, SOIC-8" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "hall effect current monitor sensor isolated" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "SOIC*3.9x4.9mm*P1.27mm*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "ACS722xLCTR-10AB_0_1" + (rectangle + (start -7.62 7.62) + (end 7.62 -7.62) + (stroke + (width 0.254) + (type default) + ) + (fill + (type background) + ) + ) + ) + (symbol "ACS722xLCTR-10AB_1_1" + (pin passive line + (at -10.16 5.08 0) + (length 2.54) + (name "IP+" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at -10.16 5.08 0) + (length 2.54) hide + (name "IP+" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at -10.16 -2.54 0) + (length 2.54) + (name "IP-" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at -10.16 -2.54 0) + (length 2.54) hide + (name "IP-" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 -10.16 90) + (length 2.54) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at 10.16 -2.54 180) + (length 2.54) + (name "BW_SEL" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "6" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 10.16 5.08 180) + (length 2.54) + (name "VIOUT" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "7" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 10.16 270) + (length 2.54) + (name "VCC" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "8" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + (symbol "power:+12V" + (power) + (pin_numbers hide) + (pin_names + (offset 0) hide) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "#PWR" + (at 0 -3.81 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "+12V" + (at 0 3.556 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"+12V\"" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "global power" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "+12V_0_1" + (polyline + (pts + (xy -0.762 1.27) (xy 0 2.54) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 0) (xy 0 2.54) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 2.54) (xy 0.762 1.27) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "+12V_1_1" + (pin power_in line + (at 0 0 90) + (length 0) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + (symbol "power:+3.3V" + (power) + (pin_numbers hide) + (pin_names + (offset 0) hide) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "#PWR" + (at 0 -3.81 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "+3.3V" + (at 0 3.556 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"+3.3V\"" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "global power" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "+3.3V_0_1" + (polyline + (pts + (xy -0.762 1.27) (xy 0 2.54) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 0) (xy 0 2.54) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 2.54) (xy 0.762 1.27) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "+3.3V_1_1" + (pin power_in line + (at 0 0 90) + (length 0) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + (symbol "power:GND" + (power) + (pin_numbers hide) + (pin_names + (offset 0) hide) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "#PWR" + (at 0 -6.35 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 0 -3.81 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "global power" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "GND_0_1" + (polyline + (pts + (xy 0 0) (xy 0 -1.27) (xy 1.27 -1.27) (xy 0 -2.54) (xy -1.27 -1.27) (xy 0 -1.27) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "GND_1_1" + (pin power_in line + (at 0 0 270) + (length 0) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + ) + (junction + (at 153.67 107.95) + (diameter 0) + (color 0 0 0 0) + (uuid "1b07dc7b-9356-4ae8-9e9e-b57b58329148") + ) + (junction + (at 113.03 76.2) + (diameter 0) + (color 0 0 0 0) + (uuid "2d154cf4-f5b7-4323-b3e9-9eac8537ff79") + ) + (junction + (at 193.04 76.2) + (diameter 0) + (color 0 0 0 0) + (uuid "2db59dc4-ddf7-4b94-a277-fb8c5b964aea") + ) + (junction + (at 125.73 76.2) + (diameter 0) + (color 0 0 0 0) + (uuid "3c475897-b6ee-4b26-aabb-a108d9c0d018") + ) + (junction + (at 113.03 87.63) + (diameter 0) + (color 0 0 0 0) + (uuid "c840daac-a559-40e2-bec1-819a61bd16f0") + ) + (junction + (at 139.7 107.95) + (diameter 0) + (color 0 0 0 0) + (uuid "cab8a5f1-5511-4c6e-a5cc-7be5a16b3808") + ) + (junction + (at 99.06 87.63) + (diameter 0) + (color 0 0 0 0) + (uuid "ec34c1bf-1f80-4e09-b75a-207e2d51eee0") + ) + (wire + (pts + (xy 91.44 76.2) (xy 95.25 76.2) + ) + (stroke + (width 0) + (type default) + ) + (uuid "09e68bce-a76f-468d-bd1f-3498704202a5") + ) + (wire + (pts + (xy 193.04 76.2) (xy 193.04 78.74) + ) + (stroke + (width 0) + (type default) + ) + (uuid "0e106dab-edbd-4402-baed-7cfb8c61d0fb") + ) + (wire + (pts + (xy 125.73 76.2) (xy 153.67 76.2) + ) + (stroke + (width 0) + (type default) + ) + (uuid "382618b9-c8aa-4d10-94fb-aa43deac4ea5") + ) + (wire + (pts + (xy 113.03 93.98) (xy 113.03 95.25) + ) + (stroke + (width 0) + (type default) + ) + (uuid "4bcdf674-c19f-4d9b-a412-c93407b26cea") + ) + (wire + (pts + (xy 139.7 111.76) (xy 139.7 107.95) + ) + (stroke + (width 0) + (type default) + ) + (uuid "4c39f15a-8351-403a-9d89-fe42188e85a2") + ) + (wire + (pts + (xy 125.73 87.63) (xy 125.73 86.36) + ) + (stroke + (width 0) + (type default) + ) + (uuid "4e53bfda-b832-4d7f-9baf-b0b01d5fdcb0") + ) + (wire + (pts + (xy 113.03 87.63) (xy 113.03 88.9) + ) + (stroke + (width 0) + (type default) + ) + (uuid "5241d529-f06d-413f-b170-da5727d7a0cf") + ) + (wire + (pts + (xy 193.04 76.2) (xy 200.66 76.2) + ) + (stroke + (width 0) + (type default) + ) + (uuid "548cd7f1-1ecc-42d5-9753-2b0cd4503ddb") + ) + (wire + (pts + (xy 193.04 87.63) (xy 193.04 86.36) + ) + (stroke + (width 0) + (type default) + ) + (uuid "633d110a-0f0e-41e8-b4ff-02cc1efb32dc") + ) + (wire + (pts + (xy 99.06 88.9) (xy 99.06 87.63) + ) + (stroke + (width 0) + (type default) + ) + (uuid "65593ed6-3469-4ce3-bb4d-c23dc0bac992") + ) + (wire + (pts + (xy 168.91 86.36) (xy 172.72 86.36) + ) + (stroke + (width 0) + (type default) + ) + (uuid "6e8528fd-746e-42ef-944b-bba142fa3f7f") + ) + (wire + (pts + (xy 144.78 86.36) (xy 144.78 83.82) + ) + (stroke + (width 0) + (type default) + ) + (uuid "6f12ed2d-ab77-4e6f-aa71-48b62d210b29") + ) + (wire + (pts + (xy 91.44 69.85) (xy 91.44 76.2) + ) + (stroke + (width 0) + (type default) + ) + (uuid "73e3453d-01b8-4011-ba4e-cbee25c77cd8") + ) + (wire + (pts + (xy 161.29 76.2) (xy 193.04 76.2) + ) + (stroke + (width 0) + (type default) + ) + (uuid "8fd82abc-2ff7-4ef7-b5f5-a611d22054ec") + ) + (wire + (pts + (xy 139.7 119.38) (xy 139.7 120.65) + ) + (stroke + (width 0) + (type default) + ) + (uuid "90109a20-1e33-4e88-b4cd-9b90e3d7275a") + ) + (wire + (pts + (xy 144.78 86.36) (xy 148.59 86.36) + ) + (stroke + (width 0) + (type default) + ) + (uuid "9068dd4f-f645-4baf-9b81-194e52f99ac6") + ) + (wire + (pts + (xy 125.73 76.2) (xy 125.73 78.74) + ) + (stroke + (width 0) + (type default) + ) + (uuid "970ce8ae-e94c-4e2f-bccc-11ad86d40bd2") + ) + (wire + (pts + (xy 138.43 107.95) (xy 139.7 107.95) + ) + (stroke + (width 0) + (type default) + ) + (uuid "973de61e-2303-4b5d-a962-023dd5f0e210") + ) + (wire + (pts + (xy 139.7 107.95) (xy 153.67 107.95) + ) + (stroke + (width 0) + (type default) + ) + (uuid "a43648d4-9f86-4160-9f51-0bdc2feee276") + ) + (wire + (pts + (xy 153.67 120.65) (xy 153.67 119.38) + ) + (stroke + (width 0) + (type default) + ) + (uuid "a46f7b49-f088-4aaf-8f04-e7699744187b") + ) + (wire + (pts + (xy 113.03 76.2) (xy 125.73 76.2) + ) + (stroke + (width 0) + (type default) + ) + (uuid "acb41acf-8594-442e-9d0a-dac74b40272f") + ) + (wire + (pts + (xy 99.06 87.63) (xy 113.03 87.63) + ) + (stroke + (width 0) + (type default) + ) + (uuid "c31c1aea-00f9-48f8-813e-f98f2c48866f") + ) + (wire + (pts + (xy 153.67 114.3) (xy 153.67 107.95) + ) + (stroke + (width 0) + (type default) + ) + (uuid "d703656b-3d51-44da-8626-4f4187e70bef") + ) + (wire + (pts + (xy 113.03 76.2) (xy 113.03 81.28) + ) + (stroke + (width 0) + (type default) + ) + (uuid "de954c79-1743-46bc-8ed2-bf76ba626004") + ) + (wire + (pts + (xy 99.06 96.52) (xy 99.06 97.79) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e2283c3f-7214-4471-9452-f46ed59fad2d") + ) + (wire + (pts + (xy 95.25 87.63) (xy 99.06 87.63) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e2dab855-68c4-4b49-a858-2d5e5b0d7830") + ) + (wire + (pts + (xy 113.03 86.36) (xy 113.03 87.63) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e527980f-78d5-4f87-9f2f-cefb29677c5f") + ) + (wire + (pts + (xy 153.67 96.52) (xy 153.67 99.06) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e68c62bd-c95a-4c05-bb15-b7259fbb2f1b") + ) + (wire + (pts + (xy 153.67 104.14) (xy 153.67 107.95) + ) + (stroke + (width 0) + (type default) + ) + (uuid "f2d91e7a-679e-42c3-9e11-69d10d26f58f") + ) + (wire + (pts + (xy 102.87 76.2) (xy 113.03 76.2) + ) + (stroke + (width 0) + (type default) + ) + (uuid "f370ca2a-777b-4e89-b9fc-7a1dbf365c8b") + ) + (wire + (pts + (xy 172.72 86.36) (xy 172.72 90.17) + ) + (stroke + (width 0) + (type default) + ) + (uuid "f540e002-e775-4d10-b433-96e7a62da590") + ) + (wire + (pts + (xy 161.29 96.52) (xy 161.29 99.06) + ) + (stroke + (width 0) + (type default) + ) + (uuid "f67391a0-35e9-4361-b34d-fe2d8426bffd") + ) + (text "0.33V - 2.97V\nZero Current Output Voltage = 1.65\n1.32V 10A swing" + (exclude_from_sim no) + (at 157.48 72.136 0) + (effects + (font + (size 1.27 1.27) + ) + ) + (uuid "67a60731-3184-4129-a037-edab08d612f7") + ) + (global_label "IO0X-Voltage" + (shape input) + (at 95.25 87.63 180) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + (uuid "4cd9524b-ddbd-409c-b13a-b8f411a39577") + (property "Intersheetrefs" "${INTERSHEET_REFS}" + (at 79.323 87.63 0) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + (hide yes) + ) + ) + ) + (global_label "VIN_Measured" + (shape output) + (at 200.66 76.2 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + (uuid "534fa94a-2362-42d7-a892-5ec26944b9f7") + (property "Intersheetrefs" "${INTERSHEET_REFS}" + (at 216.5266 76.2 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + (hide yes) + ) + ) + ) + (global_label "IO0Y-Current" + (shape input) + (at 138.43 107.95 180) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + (uuid "551f3e47-8f86-4a64-81ac-10348a5ca32d") + (property "Intersheetrefs" "${INTERSHEET_REFS}" + (at 122.6843 107.95 0) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + (hide yes) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 139.7 120.65 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "025dd409-0965-4b1b-b4b7-a70978a388b5") + (property "Reference" "#PWR05" + (at 139.7 127 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 139.7 124.714 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 139.7 120.65 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 139.7 120.65 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 139.7 120.65 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "b7b23158-8ed1-4860-b766-6b49aea9a474") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR05") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 125.73 87.63 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "0dd6af27-53f3-4b81-b860-78e0e2a7f4c6") + (property "Reference" "#PWR04" + (at 125.73 93.98 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 125.73 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 125.73 87.63 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 125.73 87.63 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 125.73 87.63 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "2cb6c609-62a5-4196-9689-ca66eed822b5") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR04") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 193.04 87.63 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "15b563e7-f8f8-4f8b-a72e-7a81c4e86445") + (property "Reference" "#PWR010" + (at 193.04 93.98 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 193.04 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 193.04 87.63 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 193.04 87.63 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 193.04 87.63 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "e5bd921a-8aac-4bcc-b564-5c8680cfd9f7") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR010") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:+12V") + (at 91.44 69.85 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "407df456-dee1-42cc-a267-d98fa7533233") + (property "Reference" "#PWR01" + (at 91.44 73.66 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "+VIN" + (at 91.186 64.77 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 91.44 69.85 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 91.44 69.85 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"+12V\"" + (at 91.44 69.85 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "aaaa8249-1eb5-42b2-8084-a2c6a598cc0c") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR01") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Sensor_Current:ACS722xLCTR-10AB") + (at 158.75 86.36 90) + (mirror x) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "56afd411-3977-437b-bb3a-8f28c710835e") + (property "Reference" "U1" + (at 177.8 80.0414 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "ACS722xLCTR-10AB" + (at 177.8 82.5814 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm" + (at 167.64 88.9 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + (hide yes) + ) + ) + (property "Datasheet" "http://www.allegromicro.com/~/media/Files/Datasheets/ACS722-Datasheet.ashx?la=en" + (at 158.75 86.36 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "±10A Bidirectional Hall-Effect Current Sensor, +3.3V supply, 132mV/A, SOIC-8" + (at 158.75 86.36 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "2" + (uuid "72f645c4-4abf-4ed6-8444-41711e1da047") + ) + (pin "5" + (uuid "b2582dca-89d3-426c-abbb-c3fc19df7eab") + ) + (pin "8" + (uuid "2a6be634-5f04-46c7-8438-214e2044f43d") + ) + (pin "6" + (uuid "e8f38fe2-ecef-4bd0-8f8c-99f4b004dbb6") + ) + (pin "4" + (uuid "97ba3573-4081-4ca9-96e6-5cdb0ea88803") + ) + (pin "7" + (uuid "142838f0-c367-4e18-bf7b-ee3e7e9d7db5") + ) + (pin "3" + (uuid "b75ee556-6002-41b0-96db-16a7d7beea40") + ) + (pin "1" + (uuid "cde11bc5-dfa3-4dd4-9658-9eb7c8fed8a7") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "U1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:C") + (at 125.73 82.55 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "5fad63ef-f4d6-456d-b079-2b3217b553f2") + (property "Reference" "C2" + (at 127 80.01 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "100nF" + (at 127.254 85.09 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Capacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolder" + (at 126.6952 86.36 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 125.73 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Unpolarized capacitor" + (at 125.73 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MANUFACTURER" "" + (at 125.73 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MAXIMUM_PACKAGE_HEIGHT" "" + (at 125.73 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "PARTREV" "" + (at 125.73 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "STANDARD" "" + (at 125.73 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "2" + (uuid "2cbac835-c714-454e-a097-f07e36ea31d4") + ) + (pin "1" + (uuid "d6ff9025-85f6-4ead-83e6-35d7daee59a5") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "C2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:+3.3V") + (at 144.78 83.82 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "6017f11f-a4ef-46fa-bf62-b72b94c8bdfc") + (property "Reference" "#PWR06" + (at 144.78 87.63 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "+3.3V" + (at 144.78 80.01 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 144.78 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 144.78 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"+3.3V\"" + (at 144.78 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "0147def6-8cae-4008-9c8a-fb5fa5d131c6") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR06") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:C_Polarized") + (at 193.04 82.55 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "82226e50-0f54-4abb-ad36-90f7a425fbd1") + (property "Reference" "C4" + (at 196.85 80.3909 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "1000uF" + (at 196.85 82.9309 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Capacitor_THT:CP_Radial_D10.0mm_P5.00mm" + (at 194.0052 86.36 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 193.04 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Polarized capacitor" + (at 193.04 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MANUFACTURER" "" + (at 193.04 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MAXIMUM_PACKAGE_HEIGHT" "" + (at 193.04 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "PARTREV" "" + (at 193.04 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "STANDARD" "" + (at 193.04 82.55 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "2" + (uuid "7ba46b09-0b39-4f1d-8c36-abf2cdc780df") + ) + (pin "1" + (uuid "683c3db6-7c90-44ef-ab5f-0091b7cee3da") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "C4") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:Fuse") + (at 99.06 76.2 90) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "89446d9b-f672-45df-9d3a-68373b75dc56") + (property "Reference" "F1" + (at 99.06 74.168 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "10A" + (at 99.06 78.486 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Fuse:Fuseholder_Littelfuse_Nano2_154x" + (at 99.06 77.978 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 99.06 76.2 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Fuse" + (at 99.06 76.2 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Can be found at" "https://www.digikey.cz/cs/products/detail/littelfuse-inc/0154010-DR/552684" + (at 99.06 76.2 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MANUFACTURER" "" + (at 99.06 76.2 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MAXIMUM_PACKAGE_HEIGHT" "" + (at 99.06 76.2 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "PARTREV" "" + (at 99.06 76.2 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "STANDARD" "" + (at 99.06 76.2 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "81156747-e392-4f5d-891c-de789d1cc7df") + ) + (pin "2" + (uuid "94ced624-93d2-41f8-ba92-b05615e69e68") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "F1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 99.06 97.79 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "90a71c55-8ccd-4b79-ad25-d3ab6fc7ddc7") + (property "Reference" "#PWR02" + (at 99.06 104.14 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 99.06 101.854 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 99.06 97.79 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 99.06 97.79 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 99.06 97.79 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "d04aa806-3c19-4632-afa5-705e3b63bf07") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR02") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R_Small") + (at 113.03 91.44 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "916b1f8d-6642-488e-af1d-84205ae4c834") + (property "Reference" "R2" + (at 115.57 90.1699 0) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Value" "10k" + (at 115.57 92.7099 0) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Footprint" "Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder" + (at 113.03 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 113.03 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor, small symbol" + (at 113.03 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MANUFACTURER" "" + (at 113.03 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MAXIMUM_PACKAGE_HEIGHT" "" + (at 113.03 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "PARTREV" "" + (at 113.03 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "STANDARD" "" + (at 113.03 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "80da57cb-1acf-41ff-accf-438dbaa07ce8") + ) + (pin "2" + (uuid "d76cf884-9331-435b-a0fb-7c592b71d183") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "R2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R_Small") + (at 153.67 116.84 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "a2f78b9f-6eea-4b57-9350-7a56c8bf81fc") + (property "Reference" "R4" + (at 151.638 115.316 90) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Value" "39k" + (at 155.702 114.808 90) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Footprint" "Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder" + (at 153.67 116.84 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 153.67 116.84 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor, small symbol" + (at 153.67 116.84 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MANUFACTURER" "" + (at 153.67 116.84 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MAXIMUM_PACKAGE_HEIGHT" "" + (at 153.67 116.84 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "PARTREV" "" + (at 153.67 116.84 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "STANDARD" "" + (at 153.67 116.84 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "4663be56-5d7b-4dde-981e-26590e75cb77") + ) + (pin "2" + (uuid "5ebf0633-3a93-4867-9db1-76976ac19143") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "R4") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R_Small") + (at 113.03 83.82 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "b3f1d506-940d-4f50-933d-aeebc3a5b136") + (property "Reference" "R1" + (at 115.57 82.5499 0) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Value" "220k" + (at 115.57 85.0899 0) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Footprint" "Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder" + (at 113.03 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 113.03 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor, small symbol" + (at 113.03 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MANUFACTURER" "" + (at 113.03 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MAXIMUM_PACKAGE_HEIGHT" "" + (at 113.03 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "PARTREV" "" + (at 113.03 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "STANDARD" "" + (at 113.03 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "70c05b2c-4e80-403a-9e92-8542d45d1206") + ) + (pin "2" + (uuid "c3cfea87-c544-4c8e-9eab-e26c8713801d") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "R1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 172.72 90.17 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "b404e6ac-da9a-4b38-9fcc-469610d9c102") + (property "Reference" "#PWR09" + (at 172.72 96.52 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 172.974 93.98 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 172.72 90.17 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 172.72 90.17 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 172.72 90.17 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "3c7ac4e1-3263-4e11-b848-2ee2b98850b3") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR09") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:+3.3V") + (at 161.29 99.06 180) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "d6678757-2ebb-4128-8030-6d8841bb6c75") + (property "Reference" "#PWR08" + (at 161.29 95.25 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "+3.3V" + (at 161.29 102.87 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 161.29 99.06 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 161.29 99.06 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"+3.3V\"" + (at 161.29 99.06 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "b2fbe9f3-3222-4ae1-b583-970abdfc56ca") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR08") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R_Small") + (at 153.67 101.6 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "db6a3718-e8ff-44ae-aa13-b57f45e4bd09") + (property "Reference" "R3" + (at 151.384 100.076 90) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Value" "51k" + (at 155.956 99.568 90) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Footprint" "Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder" + (at 153.67 101.6 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 153.67 101.6 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor, small symbol" + (at 153.67 101.6 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MANUFACTURER" "" + (at 153.67 101.6 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MAXIMUM_PACKAGE_HEIGHT" "" + (at 153.67 101.6 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "PARTREV" "" + (at 153.67 101.6 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "STANDARD" "" + (at 153.67 101.6 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "6829eee2-48c3-428c-b395-aded09ed3da1") + ) + (pin "2" + (uuid "3f91b68a-7552-416e-b2ab-0e04c521fffd") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "R3") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 113.03 95.25 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "dc1fb61f-7794-44b1-9c3f-9d8160f73445") + (property "Reference" "#PWR03" + (at 113.03 101.6 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 113.03 99.314 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 113.03 95.25 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 113.03 95.25 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 113.03 95.25 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "7a1a9ab3-e4f2-4afb-ade6-51fe3c60c5d4") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR03") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:C") + (at 139.7 115.57 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "df3697c9-ae53-41d5-a282-976508338da5") + (property "Reference" "C3" + (at 143.764 113.03 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "100nF" + (at 146.812 118.364 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Capacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolder" + (at 138.7348 119.38 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 139.7 115.57 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Unpolarized capacitor" + (at 139.7 115.57 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MANUFACTURER" "" + (at 139.7 115.57 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MAXIMUM_PACKAGE_HEIGHT" "" + (at 139.7 115.57 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "PARTREV" "" + (at 139.7 115.57 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "STANDARD" "" + (at 139.7 115.57 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "2" + (uuid "234d1c0f-c5de-4dc7-b16b-da5dc27fab1c") + ) + (pin "1" + (uuid "2b679a4b-9e23-47bd-a24d-6eded824d69c") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "C3") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:C") + (at 99.06 92.71 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "e78fc402-ce31-495a-81f4-a4788b0342b0") + (property "Reference" "C1" + (at 100.33 90.17 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "100nF" + (at 100.584 95.25 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Capacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolder" + (at 100.0252 96.52 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 99.06 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Unpolarized capacitor" + (at 99.06 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MANUFACTURER" "" + (at 99.06 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "MAXIMUM_PACKAGE_HEIGHT" "" + (at 99.06 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "PARTREV" "" + (at 99.06 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "STANDARD" "" + (at 99.06 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "2" + (uuid "30c9c460-abed-45f7-a241-ed63555d80fc") + ) + (pin "1" + (uuid "f986288e-0ac9-4da5-9bcb-ea41a75dd84f") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "C1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 153.67 120.65 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "eb2dd32b-7175-4175-be49-b823cf64d88f") + (property "Reference" "#PWR07" + (at 153.67 127 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 153.67 124.714 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 153.67 120.65 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 153.67 120.65 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 153.67 120.65 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "ea5a2d61-5227-4a80-af48-dc2dd1529c05") + ) + (instances + (project "example_schematic" + (path "/2360a543-140e-4488-b7ed-7d45263c2314" + (reference "#PWR07") + (unit 1) + ) + ) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/usermods/Power_Measurement/assets/img/example schematic.png b/usermods/Power_Measurement/assets/img/example schematic.png new file mode 100644 index 0000000000..2a25116fbd Binary files /dev/null and b/usermods/Power_Measurement/assets/img/example schematic.png differ diff --git a/usermods/Power_Measurement/assets/img/screenshot 1 - info.jpg b/usermods/Power_Measurement/assets/img/screenshot 1 - info.jpg new file mode 100644 index 0000000000..278d572463 Binary files /dev/null and b/usermods/Power_Measurement/assets/img/screenshot 1 - info.jpg differ diff --git a/usermods/Power_Measurement/assets/img/screenshot 2 - settings.png b/usermods/Power_Measurement/assets/img/screenshot 2 - settings.png new file mode 100644 index 0000000000..bb9a59d08d Binary files /dev/null and b/usermods/Power_Measurement/assets/img/screenshot 2 - settings.png differ diff --git a/usermods/Power_Measurement/assets/img/screenshot 3 - settings.png b/usermods/Power_Measurement/assets/img/screenshot 3 - settings.png new file mode 100644 index 0000000000..96c05e067e Binary files /dev/null and b/usermods/Power_Measurement/assets/img/screenshot 3 - settings.png differ diff --git a/usermods/Power_Measurement/readme.md b/usermods/Power_Measurement/readme.md new file mode 100644 index 0000000000..edbceb193d --- /dev/null +++ b/usermods/Power_Measurement/readme.md @@ -0,0 +1,94 @@ +# Voltage and Current Measurement using ESP's Internal ADC + +This usermod is a proof of concept that measures current and voltage using a voltage divider and a current sensor. It leverages the ESP's internal ADC to read the voltage and current, calculate power and energy consumption, and optionally publish these measurements via MQTT. + +## Features + +- **Voltage and Current Measurement**: Reads voltage and current using ADC pins of the ESP. +- **Power and Energy Calculation**: Calculates power (in watts) and energy consumption (in kilowatt-hours). +- **Calibration Support**: Offers calibration for more accurate measurements. +- **MQTT Publishing**: Publishes voltage, current, power, and energy measurements to an MQTT broker. +- **Debug Information**: Provides debug output via serial for monitoring raw and processed data. + +## Dependencies + +- **ESP32 ADC Calibration Library**: Requires `esp_adc_cal.h` for ADC calibration, which is a standard ESP-IDF library. + +## Configuration + +### Pins + +- `VOLTAGE_PIN`: ADC pin for voltage measurement (default: `0`) +- `CURRENT_PIN`: ADC pin for current measurement (default: `1`) + +### Constants + +- `NUM_READINGS`: Number of readings for moving average (default: `10`) +- `NUM_READINGS_CAL`: Number of readings for calibration (default: `100`) +- `UPDATE_INTERVAL_MAIN`: Main update interval in milliseconds (default: `100`) +- `UPDATE_INTERVAL_MQTT`: MQTT update interval in milliseconds (default: `60000`) + +## Installation + +Add `-D USERMOD_CURRENT_MEASUREMENT` to `build_flags` in `platformio_override.ini`. + +Or copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`. + +## Hardware Example + +![Example Schematic](./assets/img/example%20schematic.png "Example Schematic") + +## Define Your Options + +- `USERMOD_POWER_MEASUREMENT`: Enable the usermod + +All parameters and calibration variables can be configured at runtime via the Usermods settings page. + +## Calibration + +### Calibration Steps + +1. Enable the `Calibration mode` checkbox. +2. Connect the controller via USB. +3. Disconnect the power supply (Vin) from the LED strip. +4. Select the option to `Calibrate Zero Points`. +5. Reconnect the power supply to the LED strip and set it to white and full brightness. +6. Measure the voltage and current and enter the values into the `Measured Voltage` and `Measured Current` fields. +7. Check the checkboxes for `Calibrate Voltage` and `Calibrate Current`. + +### Advanced + +![Advanced Calibration](./assets/img/screenshot%203%20-%20settings.png "Advanced Calibration") + +## MQTT + +If MQTT is enabled, the module will periodically publish the voltage, current, power, and energy measurements to the configured MQTT broker. + +## Debugging + +Enable `WLED_DEBUG` to print detailed debug information to the serial output, including raw and calculated values for voltage, current, power, and energy. + +## Screenshots + +Info screen | Settings page +:-----------------------------------------------------------------------:|:-------------------------------------------------------------------------------: +![Info screen](./assets/img/screenshot%201%20-%20info.jpg "Info screen") | ![Settings page](./assets/img/screenshot%202%20-%20settings.png "Settings page") + +## To-Do + +- [ ] Pin manager doesn't work properly. +- [ ] Implement a brightness limiter based on current. +- [ ] Make the code use less flash memory. + +## Changelog + +19.8.2024 +- Initial PR + +## License + +This code was created by Tomáš Kuchta. + +## Contributions + +- Tomáš Kuchta (Initial idea and code) \ No newline at end of file diff --git a/usermods/SN_Photoresistor/usermods_list.cpp b/usermods/SN_Photoresistor/usermods_list.cpp index 649e197392..a2c6ca165f 100644 --- a/usermods/SN_Photoresistor/usermods_list.cpp +++ b/usermods/SN_Photoresistor/usermods_list.cpp @@ -9,6 +9,6 @@ void registerUsermods() { #ifdef USERMOD_SN_PHOTORESISTOR - usermods.add(new Usermod_SN_Photoresistor()); + UsermodManager::add(new Usermod_SN_Photoresistor()); #endif } \ No newline at end of file diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 281fba25de..65f4cae5d3 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -23,6 +23,9 @@ #ifndef TFT_RST #error Please define TFT_RST #endif + #ifndef TFT_CS + #error Please define TFT_CS + #endif #ifndef LOAD_GLCD #error Please define LOAD_GLCD #endif @@ -135,10 +138,10 @@ class St7789DisplayUsermod : public Usermod { void setup() override { PinManagerPinType spiPins[] = { { spi_mosi, true }, { spi_miso, false}, { spi_sclk, true } }; - if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; } + if (!PinManager::allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; } PinManagerPinType displayPins[] = { { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; - if (!pinManager.allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { - pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); + if (!PinManager::allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { + PinManager::deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); enabled = false; return; } @@ -374,10 +377,10 @@ class St7789DisplayUsermod : public Usermod { void appendConfigData() override { - oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');")); - oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');")); - oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');")); - oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI BL');")); + oappend(F("addInfo('ST7789:pin[]',0,'','SPI CS');")); + oappend(F("addInfo('ST7789:pin[]',1,'','SPI DC');")); + oappend(F("addInfo('ST7789:pin[]',2,'','SPI RST');")); + oappend(F("addInfo('ST7789:pin[]',3,'','SPI BL');")); } /* diff --git a/usermods/Temperature/platformio_override.ini b/usermods/Temperature/platformio_override.ini index 0e354da9ef..cc86367fde 100644 --- a/usermods/Temperature/platformio_override.ini +++ b/usermods/Temperature/platformio_override.ini @@ -7,6 +7,4 @@ extends = env:d1_mini build_flags = ${common.build_flags_esp8266} -D USERMOD_DALLASTEMPERATURE lib_deps = ${env.lib_deps} - paulstoffregen/OneWire@~2.3.7 -# you may want to use following with ESP32 -; https://github.com/blazoncek/OneWire.git # fixes Sensor error on ESP32 \ No newline at end of file + paulstoffregen/OneWire@~2.3.8 \ No newline at end of file diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md index b41e3e1199..3d65be7eb2 100644 --- a/usermods/Temperature/readme.md +++ b/usermods/Temperature/readme.md @@ -43,10 +43,8 @@ default_envs = d1_mini ... lib_deps = ... - #For Dallas sensor uncomment following line - OneWire@~2.3.7 - # ... or you may want to use following with ESP32 -; https://github.com/blazoncek/OneWire.git # fixes Sensor error on ESP32... + #For Dallas sensor uncomment following + paulstoffregen/OneWire @ ~2.3.8 ``` ## Change Log @@ -56,8 +54,14 @@ lib_deps = * Do not report erroneous low temperatures to MQTT * Disable plugin if temperature sensor not detected * Report the number of seconds until the first read in the info screen instead of sensor error + 2021-04 * Adaptation for runtime configuration. + 2023-05 * Rewrite to conform to newer recommendations. -* Recommended @blazoncek fork of OneWire for ESP32 to avoid Sensor error \ No newline at end of file +* Recommended @blazoncek fork of OneWire for ESP32 to avoid Sensor error + +2024-09 +* Update OneWire to version 2.3.8, which includes stickbreaker's and garyd9's ESP32 fixes: + blazoncek's fork is no longer needed \ No newline at end of file diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 5ac992f952..178bc05a0d 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -17,6 +17,8 @@ #define USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL 60000 #endif +static uint16_t mode_temperature(); + class UsermodTemperature : public Usermod { private: @@ -60,6 +62,7 @@ class UsermodTemperature : public Usermod { static const char _sensor[]; static const char _temperature[]; static const char _Temperature[]; + static const char _data_fx[]; //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 float readDallas(); @@ -70,8 +73,13 @@ class UsermodTemperature : public Usermod { void publishHomeAssistantAutodiscovery(); #endif + static UsermodTemperature* _instance; // to overcome nonstatic getTemperatureC() method and avoid UsermodManager::lookup(USERMOD_ID_TEMPERATURE); + public: + UsermodTemperature() { _instance = this; } + static UsermodTemperature *getInstance() { return UsermodTemperature::_instance; } + /* * API calls te enable data exchange between WLED modules */ @@ -215,14 +223,14 @@ void UsermodTemperature::setup() { // config says we are enabled DEBUG_PRINTLN(F("Allocating temperature pin...")); // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { + if (temperaturePin >= 0 && PinManager::allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { oneWire = new OneWire(temperaturePin); if (oneWire->reset()) { while (!findSensor() && retries--) { delay(25); // try to find sensor } } - if (parasite && pinManager.allocatePin(parasitePin, true, PinOwner::UM_Temperature)) { + if (parasite && PinManager::allocatePin(parasitePin, true, PinOwner::UM_Temperature)) { pinMode(parasitePin, OUTPUT); digitalWrite(parasitePin, LOW); // deactivate power (close MOSFET) } else { @@ -234,6 +242,7 @@ void UsermodTemperature::setup() { } temperaturePin = -1; // allocation failed } + if (sensorFound && !initDone) strip.addEffect(255, &mode_temperature, _data_fx); } lastMeasurement = millis() - readingInterval + 10000; initDone = true; @@ -414,9 +423,9 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { DEBUG_PRINTLN(F("Re-init temperature.")); // deallocate pin and release memory delete oneWire; - pinManager.deallocatePin(temperaturePin, PinOwner::UM_Temperature); + PinManager::deallocatePin(temperaturePin, PinOwner::UM_Temperature); temperaturePin = newTemperaturePin; - pinManager.deallocatePin(parasitePin, PinOwner::UM_Temperature); + PinManager::deallocatePin(parasitePin, PinOwner::UM_Temperature); // initialise setup(); } @@ -426,10 +435,10 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { } void UsermodTemperature::appendConfigData() { - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasite)).c_str()); - oappend(SET_F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); - oappend(SET_F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_parasite)).c_str()); + oappend(F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); + oappend(F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field } float UsermodTemperature::getTemperature() { @@ -440,6 +449,8 @@ const char *UsermodTemperature::getTemperatureUnit() { return degC ? "°C" : "°F"; } +UsermodTemperature* UsermodTemperature::_instance = nullptr; + // strings to reduce flash memory usage (used more than twice) const char UsermodTemperature::_name[] PROGMEM = "Temperature"; const char UsermodTemperature::_enabled[] PROGMEM = "enabled"; @@ -449,4 +460,14 @@ const char UsermodTemperature::_parasitePin[] PROGMEM = "parasite-pwr-pin"; const char UsermodTemperature::_domoticzIDX[] PROGMEM = "domoticz-idx"; const char UsermodTemperature::_sensor[] PROGMEM = "sensor"; const char UsermodTemperature::_temperature[] PROGMEM = "temperature"; -const char UsermodTemperature::_Temperature[] PROGMEM = "/temperature"; \ No newline at end of file +const char UsermodTemperature::_Temperature[] PROGMEM = "/temperature"; +const char UsermodTemperature::_data_fx[] PROGMEM = "Temperature@Min,Max;;!;01;pal=54,sx=255,ix=0"; + +static uint16_t mode_temperature() { + float low = roundf(mapf((float)SEGMENT.speed, 0.f, 255.f, -150.f, 150.f)); // default: 15°C, range: -15°C to 15°C + float high = roundf(mapf((float)SEGMENT.intensity, 0.f, 255.f, 300.f, 600.f)); // default: 30°C, range 30°C to 60°C + float temp = constrain(UsermodTemperature::getInstance()->getTemperatureC()*10.f, low, high); // get a little better resolution (*10) + unsigned i = map(roundf(temp), (unsigned)low, (unsigned)high, 0, 248); + SEGMENT.fill(SEGMENT.color_from_palette(i, false, false, 255)); + return FRAMETIME; +} diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 088ac880b8..ad449fc83a 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -149,7 +149,6 @@ static bool useBandPassFilter = false; // if true, enables a //////////////////// // some prototypes, to ensure consistent interfaces -static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float static float fftAddAvg(int from, int to); // average of several FFT result bins void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) @@ -192,8 +191,8 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT resul #define LOG_256 5.54517744f // log(256) // These are the input and output vectors. Input vectors receive computed results from FFT. -static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins -static float vImag[samplesFFT] = {0.0f}; // imaginary parts +static float* vReal = nullptr; // FFT sample inputs / freq output - these are our raw result bins +static float* vImag = nullptr; // imaginary parts // Create FFT object // lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 @@ -201,21 +200,11 @@ static float vImag[samplesFFT] = {0.0f}; // imaginary parts // #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 // #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 // Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt() -#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 -#define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 - -#include - -/* Create FFT object with weighing factor storage */ -static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); +// #define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 - since v2.0.0 this must be done in build_flags +#include // FFT object is created in FFTcode // Helper functions -// float version of map() -static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -} - // compute average of several FFT result bins static float fftAddAvg(int from, int to) { float result = 0.0f; @@ -232,6 +221,18 @@ void FFTcode(void * parameter) { DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); + // allocate FFT buffers on first call + if (vReal == nullptr) vReal = (float*) calloc(sizeof(float), samplesFFT); + if (vImag == nullptr) vImag = (float*) calloc(sizeof(float), samplesFFT); + if ((vReal == nullptr) || (vImag == nullptr)) { + // something went wrong + if (vReal) free(vReal); vReal = nullptr; + if (vImag) free(vImag); vImag = nullptr; + return; + } + // Create FFT object with weighing factor storage + ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); + // see https://www.freertos.org/vtaskdelayuntil.html const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; @@ -253,6 +254,7 @@ void FFTcode(void * parameter) // get a fresh batch of samples from I2S if (audioSource) audioSource->getSamples(vReal, samplesFFT); + memset(vImag, 0, samplesFFT * sizeof(float)); // set imaginary parts to 0 #if defined(WLED_DEBUG) || defined(SR_DEBUG) if (start < esp_timer_get_time()) { // filter out overflows @@ -271,8 +273,6 @@ void FFTcode(void * parameter) // find highest sample in the batch float maxSample = 0.0f; // max sample from FFT batch for (int i=0; i < samplesFFT; i++) { - // set imaginary parts to 0 - vImag[i] = 0; // pick our our current mic sample - we take the max value from all samples that go into FFT if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); @@ -303,7 +303,7 @@ void FFTcode(void * parameter) #endif } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. - memset(vReal, 0, sizeof(vReal)); + memset(vReal, 0, samplesFFT * sizeof(float)); FFT_MajorPeak = 1; FFT_Magnitude = 0.001; } @@ -1885,57 +1885,59 @@ class AudioReactive : public Usermod { } - void appendConfigData() override + void appendConfigData(Print& uiScript) override { -#ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); + uiScript.print(F("ux='AudioReactive';")); // ux = shortcut for Audioreactive - fingers crossed that "ux" isn't already used as JS var, html post parameter or css style +#ifdef ARDUINO_ARCH_ESP32 + uiScript.print(F("uxp=ux+':digitalmic:pin[]';")); // uxp = shortcut for AudioReactive:digitalmic:pin[] + uiScript.print(F("dd=addDropdown(ux,'digitalmic:type');")); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("addOption(dd,'Generic Analog',0);")); + uiScript.print(F("addOption(dd,'Generic Analog',0);")); #endif - oappend(SET_F("addOption(dd,'Generic I2S',1);")); - oappend(SET_F("addOption(dd,'ES7243',2);")); - oappend(SET_F("addOption(dd,'SPH0654',3);")); - oappend(SET_F("addOption(dd,'Generic I2S with Mclk',4);")); + uiScript.print(F("addOption(dd,'Generic I2S',1);")); + uiScript.print(F("addOption(dd,'ES7243',2);")); + uiScript.print(F("addOption(dd,'SPH0654',3);")); + uiScript.print(F("addOption(dd,'Generic I2S with Mclk',4);")); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) - oappend(SET_F("addOption(dd,'Generic I2S PDM',5);")); + uiScript.print(F("addOption(dd,'Generic I2S PDM',5);")); #endif - oappend(SET_F("addOption(dd,'ES8388',6);")); + uiScript.print(F("addOption(dd,'ES8388',6);")); - oappend(SET_F("dd=addDropdown('AudioReactive','config:AGC');")); - oappend(SET_F("addOption(dd,'Off',0);")); - oappend(SET_F("addOption(dd,'Normal',1);")); - oappend(SET_F("addOption(dd,'Vivid',2);")); - oappend(SET_F("addOption(dd,'Lazy',3);")); - - oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');")); - oappend(SET_F("addOption(dd,'Off',0);")); - oappend(SET_F("addOption(dd,'On',1);")); - oappend(SET_F("addInfo('AudioReactive:dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('AudioReactive:dynamics:rise',1,'ms (♪ effects only)');")); - oappend(SET_F("addInfo('AudioReactive:dynamics:fall',1,'ms (♪ effects only)');")); - - oappend(SET_F("dd=addDropdown('AudioReactive','frequency:scale');")); - oappend(SET_F("addOption(dd,'None',0);")); - oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); - oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); - oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); + uiScript.print(F("dd=addDropdown(ux,'config:AGC');")); + uiScript.print(F("addOption(dd,'Off',0);")); + uiScript.print(F("addOption(dd,'Normal',1);")); + uiScript.print(F("addOption(dd,'Vivid',2);")); + uiScript.print(F("addOption(dd,'Lazy',3);")); + + uiScript.print(F("dd=addDropdown(ux,'dynamics:limiter');")); + uiScript.print(F("addOption(dd,'Off',0);")); + uiScript.print(F("addOption(dd,'On',1);")); + uiScript.print(F("addInfo(ux+':dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field + uiScript.print(F("addInfo(ux+':dynamics:rise',1,'ms (♪ effects only)');")); + uiScript.print(F("addInfo(ux+':dynamics:fall',1,'ms (♪ effects only)');")); + + uiScript.print(F("dd=addDropdown(ux,'frequency:scale');")); + uiScript.print(F("addOption(dd,'None',0);")); + uiScript.print(F("addOption(dd,'Linear (Amplitude)',2);")); + uiScript.print(F("addOption(dd,'Square Root (Energy)',3);")); + uiScript.print(F("addOption(dd,'Logarithmic (Loudness)',1);")); #endif - oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); - oappend(SET_F("addOption(dd,'Off',0);")); + uiScript.print(F("dd=addDropdown(ux,'sync:mode');")); + uiScript.print(F("addOption(dd,'Off',0);")); #ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addOption(dd,'Send',1);")); + uiScript.print(F("addOption(dd,'Send',1);")); #endif - oappend(SET_F("addOption(dd,'Receive',2);")); + uiScript.print(F("addOption(dd,'Receive',2);")); #ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',2,'sck/bclk','I2S SCK');")); + uiScript.print(F("addInfo(ux+':digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field + uiScript.print(F("addInfo(uxp,0,'sd/data/dout','I2S SD');")); + uiScript.print(F("addInfo(uxp,1,'ws/clk/lrck','I2S WS');")); + uiScript.print(F("addInfo(uxp,2,'sck/bclk','I2S SCK');")); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'only use -1, 0, 1 or 3','I2S MCLK');")); + uiScript.print(F("addInfo(uxp,3,'only use -1, 0, 1 or 3','I2S MCLK');")); #else - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); + uiScript.print(F("addInfo(uxp,3,'master clock','I2S MCLK');")); #endif #endif } diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index a7337eaf99..a14f8def0b 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -194,8 +194,8 @@ class I2SSource : public AudioSource { virtual void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE, int8_t mclkPin = I2S_PIN_NO_CHANGE) { DEBUGSR_PRINTLN(F("I2SSource:: initialize().")); if (i2swsPin != I2S_PIN_NO_CHANGE && i2ssdPin != I2S_PIN_NO_CHANGE) { - if (!pinManager.allocatePin(i2swsPin, true, PinOwner::UM_Audioreactive) || - !pinManager.allocatePin(i2ssdPin, false, PinOwner::UM_Audioreactive)) { // #206 + if (!PinManager::allocatePin(i2swsPin, true, PinOwner::UM_Audioreactive) || + !PinManager::allocatePin(i2ssdPin, false, PinOwner::UM_Audioreactive)) { // #206 DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pins: ws=%d, sd=%d\n", i2swsPin, i2ssdPin); return; } @@ -203,7 +203,7 @@ class I2SSource : public AudioSource { // i2ssckPin needs special treatment, since it might be unused on PDM mics if (i2sckPin != I2S_PIN_NO_CHANGE) { - if (!pinManager.allocatePin(i2sckPin, true, PinOwner::UM_Audioreactive)) { + if (!PinManager::allocatePin(i2sckPin, true, PinOwner::UM_Audioreactive)) { DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pins: sck=%d\n", i2sckPin); return; } @@ -249,7 +249,7 @@ class I2SSource : public AudioSource { // Reserve the master clock pin if provided _mclkPin = mclkPin; if (mclkPin != I2S_PIN_NO_CHANGE) { - if(!pinManager.allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) { + if(!PinManager::allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) { DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pin: MCLK=%d\n", mclkPin); return; } else @@ -307,11 +307,11 @@ class I2SSource : public AudioSource { DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); return; } - if (_pinConfig.ws_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.ws_io_num, PinOwner::UM_Audioreactive); - if (_pinConfig.data_in_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.data_in_num, PinOwner::UM_Audioreactive); - if (_pinConfig.bck_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.bck_io_num, PinOwner::UM_Audioreactive); + if (_pinConfig.ws_io_num != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_pinConfig.ws_io_num, PinOwner::UM_Audioreactive); + if (_pinConfig.data_in_num != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_pinConfig.data_in_num, PinOwner::UM_Audioreactive); + if (_pinConfig.bck_io_num != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_pinConfig.bck_io_num, PinOwner::UM_Audioreactive); // Release the master clock pin - if (_mclkPin != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_mclkPin, PinOwner::UM_Audioreactive); + if (_mclkPin != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_mclkPin, PinOwner::UM_Audioreactive); } virtual void getSamples(float *buffer, uint16_t num_samples) { @@ -589,7 +589,7 @@ class I2SAdcSource : public I2SSource { void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { DEBUGSR_PRINTLN(F("I2SAdcSource:: initialize().")); _myADCchannel = 0x0F; - if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { + if(!PinManager::allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { DEBUGSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin); return; } @@ -717,7 +717,7 @@ class I2SAdcSource : public I2SSource { } void deinitialize() { - pinManager.deallocatePin(_audioPin, PinOwner::UM_Audioreactive); + PinManager::deallocatePin(_audioPin, PinOwner::UM_Audioreactive); _initialized = false; _myADCchannel = 0x0F; diff --git a/usermods/audioreactive/readme.md b/usermods/audioreactive/readme.md index 4668ca8814..aad269c675 100644 --- a/usermods/audioreactive/readme.md +++ b/usermods/audioreactive/readme.md @@ -30,7 +30,7 @@ There are however plans to create a lightweight audioreactive for the 8266, with ### using latest _arduinoFFT_ library version 2.x The latest arduinoFFT release version should be used for audioreactive. -* `build_flags` = `-D USERMOD_AUDIOREACTIVE` +* `build_flags` = `-D USERMOD_AUDIOREACTIVE -D sqrt_internal=sqrtf` * `lib_deps`= `kosme/arduinoFFT @ 2.0.1` ## Configuration diff --git a/usermods/boblight/boblight.h b/usermods/boblight/boblight.h index 32208a4fa2..b04b78fac7 100644 --- a/usermods/boblight/boblight.h +++ b/usermods/boblight/boblight.h @@ -1,459 +1,459 @@ -#pragma once - -#include "wled.h" - -/* - * Usermod that implements BobLight "ambilight" protocol - * - * See the accompanying README.md file for more info. - */ - -#ifndef BOB_PORT - #define BOB_PORT 19333 // Default boblightd port -#endif - -class BobLightUsermod : public Usermod { - typedef struct _LIGHT { - char lightname[5]; - float hscan[2]; - float vscan[2]; - } light_t; - - private: - unsigned long lastTime = 0; - bool enabled = false; - bool initDone = false; - - light_t *lights = nullptr; - uint16_t numLights = 0; // 16 + 9 + 16 + 9 - uint16_t top, bottom, left, right; // will be filled in readFromConfig() - uint16_t pct; - - WiFiClient bobClient; - WiFiServer *bob; - uint16_t bobPort = BOB_PORT; - - static const char _name[]; - static const char _enabled[]; - - /* - # boblight - # Copyright (C) Bob 2009 - # - # makeboblight.sh created by Adam Boeglin - # - # boblight is free software: you can redistribute it and/or modify it - # under the terms of the GNU General Public License as published by the - # Free Software Foundation, either version 3 of the License, or - # (at your option) any later version. - # - # boblight is distributed in the hope that it will be useful, but - # WITHOUT ANY WARRANTY; without even the implied warranty of - # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - # See the GNU General Public License for more details. - # - # You should have received a copy of the GNU General Public License along - # with this program. If not, see . - */ - - // fills the lights[] array with position & depth of scan for each LED - void fillBobLights(int bottom, int left, int top, int right, float pct_scan) { - - int lightcount = 0; - int total = top+left+right+bottom; - int bcount; - - if (total > strip.getLengthTotal()) { - DEBUG_PRINTLN(F("BobLight: Too many lights.")); - return; - } - - // start left part of bottom strip (clockwise direction, 1st half) - if (bottom > 0) { - bcount = 1; - float brange = 100.0/bottom; - float bcurrent = 50.0; - if (bottom < top) { - int diff = top - bottom; - brange = 100.0/top; - bcurrent -= (diff/2)*brange; - } - while (bcount <= bottom/2) { - float btop = bcurrent - brange; - String name = "b"+String(bcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = btop; - lights[lightcount].hscan[1] = bcurrent; - lights[lightcount].vscan[0] = 100 - pct_scan; - lights[lightcount].vscan[1] = 100; - lightcount+=1; - bcurrent = btop; - bcount+=1; - } - } - - // left side - if (left > 0) { - int lcount = 1; - float lrange = 100.0/left; - float lcurrent = 100.0; - while (lcount <= left) { - float ltop = lcurrent - lrange; - String name = "l"+String(lcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = 0; - lights[lightcount].hscan[1] = pct_scan; - lights[lightcount].vscan[0] = ltop; - lights[lightcount].vscan[1] = lcurrent; - lightcount+=1; - lcurrent = ltop; - lcount+=1; - } - } - - // top side - if (top > 0) { - int tcount = 1; - float trange = 100.0/top; - float tcurrent = 0; - while (tcount <= top) { - float ttop = tcurrent + trange; - String name = "t"+String(tcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = tcurrent; - lights[lightcount].hscan[1] = ttop; - lights[lightcount].vscan[0] = 0; - lights[lightcount].vscan[1] = pct_scan; - lightcount+=1; - tcurrent = ttop; - tcount+=1; - } - } - - // right side - if (right > 0) { - int rcount = 1; - float rrange = 100.0/right; - float rcurrent = 0; - while (rcount <= right) { - float rtop = rcurrent + rrange; - String name = "r"+String(rcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = 100-pct_scan; - lights[lightcount].hscan[1] = 100; - lights[lightcount].vscan[0] = rcurrent; - lights[lightcount].vscan[1] = rtop; - lightcount+=1; - rcurrent = rtop; - rcount+=1; - } - } - - // right side of bottom strip (2nd half) - if (bottom > 0) { - float brange = 100.0/bottom; - float bcurrent = 100; - if (bottom < top) { - brange = 100.0/top; - } - while (bcount <= bottom) { - float btop = bcurrent - brange; - String name = "b"+String(bcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = btop; - lights[lightcount].hscan[1] = bcurrent; - lights[lightcount].vscan[0] = 100 - pct_scan; - lights[lightcount].vscan[1] = 100; - lightcount+=1; - bcurrent = btop; - bcount+=1; - } - } - - numLights = lightcount; - - #if WLED_DEBUG - DEBUG_PRINTLN(F("Fill light data: ")); - DEBUG_PRINTF_P(PSTR(" lights %d\n"), numLights); - for (int i=0; i strip.getLengthTotal() ) { - DEBUG_PRINTLN(F("BobLight: Too many lights.")); - DEBUG_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal()); - totalLights = strip.getLengthTotal(); - top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f); - left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f); - } - lights = new light_t[totalLights]; - if (lights) fillBobLights(bottom, left, top, right, float(pct)); // will fill numLights - else enable(false); - initDone = true; - } - - void connected() override { - // we can only start server when WiFi is connected - if (!bob) bob = new WiFiServer(bobPort, 1); - bob->begin(); - bob->setNoDelay(true); - } - - void loop() override { - if (!enabled || strip.isUpdating()) return; - if (millis() - lastTime > 10) { - lastTime = millis(); - pollBob(); - } - } - - void enable(bool en) { enabled = en; } - -#ifndef WLED_DISABLE_MQTT - /** - * handling of MQTT message - * topic only contains stripped topic (part after /wled/MAC) - * topic should look like: /swipe with amessage of [up|down] - */ - bool onMqttMessage(char* topic, char* payload) override { - //if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/subtopic"), 6) == 0) { - // String action = payload; - // if (action == "on") { - // enable(true); - // return true; - // } else if (action == "off") { - // enable(false); - // return true; - // } - //} - return false; - } - - /** - * subscribe to MQTT topic for controlling usermod - */ - void onMqttConnect(bool sessionPresent) override { - //char subuf[64]; - //if (mqttDeviceTopic[0] != 0) { - // strcpy(subuf, mqttDeviceTopic); - // strcat_P(subuf, PSTR("/subtopic")); - // mqtt->subscribe(subuf, 0); - //} - } -#endif - - void addToJsonInfo(JsonObject& root) override - { - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - - JsonArray infoArr = user.createNestedArray(FPSTR(_name)); - String uiDomString = F(""); - infoArr.add(uiDomString); - } - - /* - * 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) override - { - } - - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void readFromJsonState(JsonObject& root) override { - if (!initDone) return; // prevent crash on boot applyPreset() - bool en = enabled; - JsonObject um = root[FPSTR(_name)]; - if (!um.isNull()) { - if (um[FPSTR(_enabled)].is()) { - en = um[FPSTR(_enabled)].as(); - } else { - String str = um[FPSTR(_enabled)]; // checkbox -> off or on - en = (bool)(str!="off"); // off is guaranteed to be present - } - if (en != enabled && lights) { - enable(en); - if (!enabled && bob && bob->hasClient()) { - if (bobClient) bobClient.stop(); - bobClient = bob->available(); - BobClear(); - exitRealtime(); - } - } - } - } - - void appendConfigData() override { - //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); - //oappend(SET_F("addOption(dd,'1st value',0);")); - //oappend(SET_F("addOption(dd,'2nd value',1);")); - oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field - } - - void addToConfig(JsonObject& root) override { - JsonObject umData = root.createNestedObject(FPSTR(_name)); - umData[FPSTR(_enabled)] = enabled; - umData[ "port" ] = bobPort; - umData[F("top")] = top; - umData[F("bottom")] = bottom; - umData[F("left")] = left; - umData[F("right")] = right; - umData[F("pct")] = pct; - } - - bool readFromConfig(JsonObject& root) override { - JsonObject umData = root[FPSTR(_name)]; - bool configComplete = !umData.isNull(); - - bool en = enabled; - configComplete &= getJsonValue(umData[FPSTR(_enabled)], en); - enable(en); - - configComplete &= getJsonValue(umData[ "port" ], bobPort); - configComplete &= getJsonValue(umData[F("bottom")], bottom, 16); - configComplete &= getJsonValue(umData[F("top")], top, 16); - configComplete &= getJsonValue(umData[F("left")], left, 9); - configComplete &= getJsonValue(umData[F("right")], right, 9); - configComplete &= getJsonValue(umData[F("pct")], pct, 5); // Depth of scan [%] - pct = MIN(50,MAX(1,pct)); - - uint16_t totalLights = bottom + left + top + right; - if (initDone && numLights != totalLights) { - if (lights) delete[] lights; - setup(); - } - return configComplete; - } - - /* - * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. - * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. - * Commonly used for custom clocks (Cronixie, 7 segment) - */ - void handleOverlayDraw() override { - //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black - } - - uint16_t getId() override { return USERMOD_ID_BOBLIGHT; } - -}; - -// strings to reduce flash memory usage (used more than twice) -const char BobLightUsermod::_name[] PROGMEM = "BobLight"; -const char BobLightUsermod::_enabled[] PROGMEM = "enabled"; - -// main boblight handling (definition here prevents inlining) -void BobLightUsermod::pollBob() { - - //check if there are any new clients - if (bob && bob->hasClient()) { - //find free/disconnected spot - if (!bobClient || !bobClient.connected()) { - if (bobClient) bobClient.stop(); - bobClient = bob->available(); - DEBUG_PRINTLN(F("Boblight: Client connected.")); - } - //no free/disconnected spot so reject - WiFiClient bobClientTmp = bob->available(); - bobClientTmp.stop(); - BobClear(); - exitRealtime(); - } - - //check clients for data - if (bobClient && bobClient.connected()) { - realtimeLock(realtimeTimeoutMs); // lock strip as we have a client connected - - //get data from the client - while (bobClient.available()) { - String input = bobClient.readStringUntil('\n'); - // DEBUG_PRINT(F("Client: ")); DEBUG_PRINTLN(input); // may be to stressful on Serial - if (input.startsWith(F("hello"))) { - DEBUG_PRINTLN(F("hello")); - bobClient.print(F("hello\n")); - } else if (input.startsWith(F("ping"))) { - DEBUG_PRINTLN(F("ping 1")); - bobClient.print(F("ping 1\n")); - } else if (input.startsWith(F("get version"))) { - DEBUG_PRINTLN(F("version 5")); - bobClient.print(F("version 5\n")); - } else if (input.startsWith(F("get lights"))) { - char tmp[64]; - String answer = ""; - sprintf_P(tmp, PSTR("lights %d\n"), numLights); - DEBUG_PRINT(tmp); - answer.concat(tmp); - for (int i=0; i ... - input.remove(0,10); - String tmp = input.substring(0,input.indexOf(' ')); - - int light_id = -1; - for (uint16_t i=0; iavailable(); - BobClear(); - } - } - } -} +#pragma once + +#include "wled.h" + +/* + * Usermod that implements BobLight "ambilight" protocol + * + * See the accompanying README.md file for more info. + */ + +#ifndef BOB_PORT + #define BOB_PORT 19333 // Default boblightd port +#endif + +class BobLightUsermod : public Usermod { + typedef struct _LIGHT { + char lightname[5]; + float hscan[2]; + float vscan[2]; + } light_t; + + private: + unsigned long lastTime = 0; + bool enabled = false; + bool initDone = false; + + light_t *lights = nullptr; + uint16_t numLights = 0; // 16 + 9 + 16 + 9 + uint16_t top, bottom, left, right; // will be filled in readFromConfig() + uint16_t pct; + + WiFiClient bobClient; + WiFiServer *bob; + uint16_t bobPort = BOB_PORT; + + static const char _name[]; + static const char _enabled[]; + + /* + # boblight + # Copyright (C) Bob 2009 + # + # makeboblight.sh created by Adam Boeglin + # + # boblight is free software: you can redistribute it and/or modify it + # under the terms of the GNU General Public License as published by the + # Free Software Foundation, either version 3 of the License, or + # (at your option) any later version. + # + # boblight is distributed in the hope that it will be useful, but + # WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + # See the GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License along + # with this program. If not, see . + */ + + // fills the lights[] array with position & depth of scan for each LED + void fillBobLights(int bottom, int left, int top, int right, float pct_scan) { + + int lightcount = 0; + int total = top+left+right+bottom; + int bcount; + + if (total > strip.getLengthTotal()) { + DEBUG_PRINTLN(F("BobLight: Too many lights.")); + return; + } + + // start left part of bottom strip (clockwise direction, 1st half) + if (bottom > 0) { + bcount = 1; + float brange = 100.0/bottom; + float bcurrent = 50.0; + if (bottom < top) { + int diff = top - bottom; + brange = 100.0/top; + bcurrent -= (diff/2)*brange; + } + while (bcount <= bottom/2) { + float btop = bcurrent - brange; + String name = "b"+String(bcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = btop; + lights[lightcount].hscan[1] = bcurrent; + lights[lightcount].vscan[0] = 100 - pct_scan; + lights[lightcount].vscan[1] = 100; + lightcount+=1; + bcurrent = btop; + bcount+=1; + } + } + + // left side + if (left > 0) { + int lcount = 1; + float lrange = 100.0/left; + float lcurrent = 100.0; + while (lcount <= left) { + float ltop = lcurrent - lrange; + String name = "l"+String(lcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = 0; + lights[lightcount].hscan[1] = pct_scan; + lights[lightcount].vscan[0] = ltop; + lights[lightcount].vscan[1] = lcurrent; + lightcount+=1; + lcurrent = ltop; + lcount+=1; + } + } + + // top side + if (top > 0) { + int tcount = 1; + float trange = 100.0/top; + float tcurrent = 0; + while (tcount <= top) { + float ttop = tcurrent + trange; + String name = "t"+String(tcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = tcurrent; + lights[lightcount].hscan[1] = ttop; + lights[lightcount].vscan[0] = 0; + lights[lightcount].vscan[1] = pct_scan; + lightcount+=1; + tcurrent = ttop; + tcount+=1; + } + } + + // right side + if (right > 0) { + int rcount = 1; + float rrange = 100.0/right; + float rcurrent = 0; + while (rcount <= right) { + float rtop = rcurrent + rrange; + String name = "r"+String(rcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = 100-pct_scan; + lights[lightcount].hscan[1] = 100; + lights[lightcount].vscan[0] = rcurrent; + lights[lightcount].vscan[1] = rtop; + lightcount+=1; + rcurrent = rtop; + rcount+=1; + } + } + + // right side of bottom strip (2nd half) + if (bottom > 0) { + float brange = 100.0/bottom; + float bcurrent = 100; + if (bottom < top) { + brange = 100.0/top; + } + while (bcount <= bottom) { + float btop = bcurrent - brange; + String name = "b"+String(bcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = btop; + lights[lightcount].hscan[1] = bcurrent; + lights[lightcount].vscan[0] = 100 - pct_scan; + lights[lightcount].vscan[1] = 100; + lightcount+=1; + bcurrent = btop; + bcount+=1; + } + } + + numLights = lightcount; + + #if WLED_DEBUG + DEBUG_PRINTLN(F("Fill light data: ")); + DEBUG_PRINTF_P(PSTR(" lights %d\n"), numLights); + for (int i=0; i strip.getLengthTotal() ) { + DEBUG_PRINTLN(F("BobLight: Too many lights.")); + DEBUG_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal()); + totalLights = strip.getLengthTotal(); + top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f); + left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f); + } + lights = new light_t[totalLights]; + if (lights) fillBobLights(bottom, left, top, right, float(pct)); // will fill numLights + else enable(false); + initDone = true; + } + + void connected() override { + // we can only start server when WiFi is connected + if (!bob) bob = new WiFiServer(bobPort, 1); + bob->begin(); + bob->setNoDelay(true); + } + + void loop() override { + if (!enabled || strip.isUpdating()) return; + if (millis() - lastTime > 10) { + lastTime = millis(); + pollBob(); + } + } + + void enable(bool en) { enabled = en; } + +#ifndef WLED_DISABLE_MQTT + /** + * handling of MQTT message + * topic only contains stripped topic (part after /wled/MAC) + * topic should look like: /swipe with amessage of [up|down] + */ + bool onMqttMessage(char* topic, char* payload) override { + //if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/subtopic"), 6) == 0) { + // String action = payload; + // if (action == "on") { + // enable(true); + // return true; + // } else if (action == "off") { + // enable(false); + // return true; + // } + //} + return false; + } + + /** + * subscribe to MQTT topic for controlling usermod + */ + void onMqttConnect(bool sessionPresent) override { + //char subuf[64]; + //if (mqttDeviceTopic[0] != 0) { + // strcpy(subuf, mqttDeviceTopic); + // strcat_P(subuf, PSTR("/subtopic")); + // mqtt->subscribe(subuf, 0); + //} + } +#endif + + void addToJsonInfo(JsonObject& root) override + { + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + String uiDomString = F(""); + infoArr.add(uiDomString); + } + + /* + * 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) override + { + } + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void readFromJsonState(JsonObject& root) override { + if (!initDone) return; // prevent crash on boot applyPreset() + bool en = enabled; + JsonObject um = root[FPSTR(_name)]; + if (!um.isNull()) { + if (um[FPSTR(_enabled)].is()) { + en = um[FPSTR(_enabled)].as(); + } else { + String str = um[FPSTR(_enabled)]; // checkbox -> off or on + en = (bool)(str!="off"); // off is guaranteed to be present + } + if (en != enabled && lights) { + enable(en); + if (!enabled && bob && bob->hasClient()) { + if (bobClient) bobClient.stop(); + bobClient = bob->available(); + BobClear(); + exitRealtime(); + } + } + } + } + + void appendConfigData() override { + //oappend(F("dd=addDropdown('usermod','selectfield');")); + //oappend(F("addOption(dd,'1st value',0);")); + //oappend(F("addOption(dd,'2nd value',1);")); + oappend(F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field + } + + void addToConfig(JsonObject& root) override { + JsonObject umData = root.createNestedObject(FPSTR(_name)); + umData[FPSTR(_enabled)] = enabled; + umData[ "port" ] = bobPort; + umData[F("top")] = top; + umData[F("bottom")] = bottom; + umData[F("left")] = left; + umData[F("right")] = right; + umData[F("pct")] = pct; + } + + bool readFromConfig(JsonObject& root) override { + JsonObject umData = root[FPSTR(_name)]; + bool configComplete = !umData.isNull(); + + bool en = enabled; + configComplete &= getJsonValue(umData[FPSTR(_enabled)], en); + enable(en); + + configComplete &= getJsonValue(umData[ "port" ], bobPort); + configComplete &= getJsonValue(umData[F("bottom")], bottom, 16); + configComplete &= getJsonValue(umData[F("top")], top, 16); + configComplete &= getJsonValue(umData[F("left")], left, 9); + configComplete &= getJsonValue(umData[F("right")], right, 9); + configComplete &= getJsonValue(umData[F("pct")], pct, 5); // Depth of scan [%] + pct = MIN(50,MAX(1,pct)); + + uint16_t totalLights = bottom + left + top + right; + if (initDone && numLights != totalLights) { + if (lights) delete[] lights; + setup(); + } + return configComplete; + } + + /* + * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. + * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. + * Commonly used for custom clocks (Cronixie, 7 segment) + */ + void handleOverlayDraw() override { + //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black + } + + uint16_t getId() override { return USERMOD_ID_BOBLIGHT; } + +}; + +// strings to reduce flash memory usage (used more than twice) +const char BobLightUsermod::_name[] PROGMEM = "BobLight"; +const char BobLightUsermod::_enabled[] PROGMEM = "enabled"; + +// main boblight handling (definition here prevents inlining) +void BobLightUsermod::pollBob() { + + //check if there are any new clients + if (bob && bob->hasClient()) { + //find free/disconnected spot + if (!bobClient || !bobClient.connected()) { + if (bobClient) bobClient.stop(); + bobClient = bob->available(); + DEBUG_PRINTLN(F("Boblight: Client connected.")); + } + //no free/disconnected spot so reject + WiFiClient bobClientTmp = bob->available(); + bobClientTmp.stop(); + BobClear(); + exitRealtime(); + } + + //check clients for data + if (bobClient && bobClient.connected()) { + realtimeLock(realtimeTimeoutMs); // lock strip as we have a client connected + + //get data from the client + while (bobClient.available()) { + String input = bobClient.readStringUntil('\n'); + // DEBUG_PRINT(F("Client: ")); DEBUG_PRINTLN(input); // may be to stressful on Serial + if (input.startsWith(F("hello"))) { + DEBUG_PRINTLN(F("hello")); + bobClient.print(F("hello\n")); + } else if (input.startsWith(F("ping"))) { + DEBUG_PRINTLN(F("ping 1")); + bobClient.print(F("ping 1\n")); + } else if (input.startsWith(F("get version"))) { + DEBUG_PRINTLN(F("version 5")); + bobClient.print(F("version 5\n")); + } else if (input.startsWith(F("get lights"))) { + char tmp[64]; + String answer = ""; + sprintf_P(tmp, PSTR("lights %d\n"), numLights); + DEBUG_PRINT(tmp); + answer.concat(tmp); + for (int i=0; i ... + input.remove(0,10); + String tmp = input.substring(0,input.indexOf(' ')); + + int light_id = -1; + for (uint16_t i=0; iavailable(); + BobClear(); + } + } + } +} diff --git a/usermods/mpu6050_imu/readme.md b/usermods/mpu6050_imu/readme.md index b804ba6029..c324738d6b 100644 --- a/usermods/mpu6050_imu/readme.md +++ b/usermods/mpu6050_imu/readme.md @@ -86,6 +86,6 @@ Example **usermods_list.cpp**: void registerUsermods() { - usermods.add(new MPU6050Driver()); + UsermodManager::add(new MPU6050Driver()); } ``` diff --git a/usermods/mpu6050_imu/usermod_gyro_surge.h b/usermods/mpu6050_imu/usermod_gyro_surge.h index c49d930ffe..c19358de74 100644 --- a/usermods/mpu6050_imu/usermod_gyro_surge.h +++ b/usermods/mpu6050_imu/usermod_gyro_surge.h @@ -163,7 +163,7 @@ class GyroSurge : public Usermod { void loop() { // get IMU data um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_IMU)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_IMU)) { // Apply max strip.getSegment(0).fadeToBlackBy(max); return; diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h index 436d03601d..f04578fe3a 100644 --- a/usermods/mpu6050_imu/usermod_mpu6050_imu.h +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -164,7 +164,7 @@ class MPU6050Driver : public Usermod { if (i2c_scl<0 || i2c_sda<0) { DEBUG_PRINTLN(F("MPU6050: I2C is no good.")); return; } // Check the interrupt pin if (config.interruptPin >= 0) { - irqBound = pinManager.allocatePin(config.interruptPin, false, PinOwner::UM_IMU); + irqBound = PinManager::allocatePin(config.interruptPin, false, PinOwner::UM_IMU); if (!irqBound) { DEBUG_PRINTLN(F("MPU6050: IRQ pin already in use.")); return; } pinMode(config.interruptPin, INPUT); }; @@ -408,7 +408,7 @@ class MPU6050Driver : public Usermod { // Previously loaded and config changed if (irqBound && ((old_cfg.interruptPin != config.interruptPin) || !config.enabled)) { detachInterrupt(old_cfg.interruptPin); - pinManager.deallocatePin(old_cfg.interruptPin, PinOwner::UM_IMU); + PinManager::deallocatePin(old_cfg.interruptPin, PinOwner::UM_IMU); irqBound = false; } diff --git a/usermods/mqtt_switch_v2/README.md b/usermods/mqtt_switch_v2/README.md index 744d7fe3c3..382f72d0e8 100644 --- a/usermods/mqtt_switch_v2/README.md +++ b/usermods/mqtt_switch_v2/README.md @@ -19,7 +19,7 @@ Example `usermods_list.cpp`: void registerUsermods() { - usermods.add(new UsermodMqttSwitch()); + UsermodManager::add(new UsermodMqttSwitch()); } ``` @@ -33,7 +33,7 @@ board = esp12e platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} - -D LEDPIN=3 + -D DATA_PINS=3 -D BTNPIN=4 -D RLYPIN=12 -D RLYMDE=1 diff --git a/usermods/multi_relay/readme.md b/usermods/multi_relay/readme.md index 24dd394b82..eaa069ae76 100644 --- a/usermods/multi_relay/readme.md +++ b/usermods/multi_relay/readme.md @@ -41,7 +41,7 @@ When a relay is switched, a message is published: ## Usermod installation -1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `usermods.add(new MultiRelay());` at the bottom of `usermods_list.cpp`. +1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `UsermodManager::add(new MultiRelay());` at the bottom of `usermods_list.cpp`. or 2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY` in your platformio.ini @@ -90,9 +90,9 @@ void registerUsermods() * || || || * \/ \/ \/ */ - //usermods.add(new MyExampleUsermod()); - //usermods.add(new UsermodTemperature()); - usermods.add(new MultiRelay()); + //UsermodManager::add(new MyExampleUsermod()); + //UsermodManager::add(new UsermodTemperature()); + UsermodManager::add(new MultiRelay()); } ``` diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index efb3c8ae19..c4446c7a20 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -264,7 +264,7 @@ void MultiRelay::handleOffTimer() { void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer DEBUG_PRINTLN(F("Relays: Initialize HTML API")); - server.on(SET_F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { + server.on(F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) { DEBUG_PRINTLN(F("Relays: HTML API")); String janswer; String error = ""; @@ -516,7 +516,7 @@ void MultiRelay::setup() { if (!_relay[i].external) _relay[i].state = !offMode; state |= (uint8_t)(_relay[i].invert ? !_relay[i].state : _relay[i].state) << pin; } else if (_relay[i].pin<100 && _relay[i].pin>=0) { - if (pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) { + if (PinManager::allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) { if (!_relay[i].external) _relay[i].state = !offMode; switchRelay(i, _relay[i].state); _relay[i].active = false; @@ -765,10 +765,10 @@ void MultiRelay::addToConfig(JsonObject &root) { } void MultiRelay::appendConfigData() { - oappend(SET_F("addInfo('MultiRelay:PCF8574-address',1,'(not hex!)');")); - oappend(SET_F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');")); - //oappend(SET_F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');")); - oappend(SET_F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); + oappend(F("addInfo('MultiRelay:PCF8574-address',1,'(not hex!)');")); + oappend(F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');")); + //oappend(F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');")); + oappend(F("d.extra.push({'MultiRelay':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); } /** @@ -817,7 +817,7 @@ bool MultiRelay::readFromConfig(JsonObject &root) { // deallocate all pins 1st for (int i=0; i=0 && oldPin[i]<100) { - pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay); + PinManager::deallocatePin(oldPin[i], PinOwner::UM_MultiRelay); } // allocate new pins setup(); diff --git a/usermods/pixels_dice_tray/pixels_dice_tray.h b/usermods/pixels_dice_tray/pixels_dice_tray.h index 238af314ea..61348ebb8e 100644 --- a/usermods/pixels_dice_tray/pixels_dice_tray.h +++ b/usermods/pixels_dice_tray/pixels_dice_tray.h @@ -112,15 +112,15 @@ class PixelsDiceTrayUsermod : public Usermod { SetSPIPinsFromMacros(); PinManagerPinType spiPins[] = { {spi_mosi, true}, {spi_miso, false}, {spi_sclk, true}}; - if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { + if (!PinManager::allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; } else { PinManagerPinType displayPins[] = { {TFT_CS, true}, {TFT_DC, true}, {TFT_RST, true}, {TFT_BL, true}}; - if (!pinManager.allocateMultiplePins( + if (!PinManager::allocateMultiplePins( displayPins, sizeof(displayPins) / sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { - pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); + PinManager::deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); enabled = false; } } @@ -387,23 +387,23 @@ class PixelsDiceTrayUsermod : public Usermod { // To work around this, add info text to the end of the preceding item. // // See addInfo in wled00/data/settings_um.htm for details on what this function does. - oappend(SET_F( + oappend(F( "addInfo('DiceTray:ble_scan_duration',1,'

Set to \"*\" to " "connect to any die.
Leave Blank to disable.

Saving will replace \"*\" with die names.','');")); #if USING_TFT_DISPLAY - oappend(SET_F("ddr=addDropdown('DiceTray','rotation');")); - oappend(SET_F("addOption(ddr,'0 deg',0);")); - oappend(SET_F("addOption(ddr,'90 deg',1);")); - oappend(SET_F("addOption(ddr,'180 deg',2);")); - oappend(SET_F("addOption(ddr,'270 deg',3);")); - oappend(SET_F( + oappend(F("ddr=addDropdown('DiceTray','rotation');")); + oappend(F("addOption(ddr,'0 deg',0);")); + oappend(F("addOption(ddr,'90 deg',1);")); + oappend(F("addOption(ddr,'180 deg',2);")); + oappend(F("addOption(ddr,'270 deg',3);")); + oappend(F( "addInfo('DiceTray:rotation',1,'
DO NOT CHANGE " "SPI PINS.
CHANGES ARE IGNORED.','');")); - oappend(SET_F("addInfo('TFT:pin[]',0,'','SPI CS');")); - oappend(SET_F("addInfo('TFT:pin[]',1,'','SPI DC');")); - oappend(SET_F("addInfo('TFT:pin[]',2,'','SPI RST');")); - oappend(SET_F("addInfo('TFT:pin[]',3,'','SPI BL');")); + oappend(F("addInfo('TFT:pin[]',0,'','SPI CS');")); + oappend(F("addInfo('TFT:pin[]',1,'','SPI DC');")); + oappend(F("addInfo('TFT:pin[]',2,'','SPI RST');")); + oappend(F("addInfo('TFT:pin[]',3,'','SPI BL');")); #endif } diff --git a/usermods/pwm_outputs/usermod_pwm_outputs.h b/usermods/pwm_outputs/usermod_pwm_outputs.h index 1880308c45..09232f043a 100644 --- a/usermods/pwm_outputs/usermod_pwm_outputs.h +++ b/usermods/pwm_outputs/usermod_pwm_outputs.h @@ -29,13 +29,13 @@ class PwmOutput { return; DEBUG_PRINTF("pwm_output[%d]: setup to freq %d\n", pin_, freq_); - if (!pinManager.allocatePin(pin_, true, PinOwner::UM_PWM_OUTPUTS)) + if (!PinManager::allocatePin(pin_, true, PinOwner::UM_PWM_OUTPUTS)) return; - channel_ = pinManager.allocateLedc(1); + channel_ = PinManager::allocateLedc(1); if (channel_ == 255) { DEBUG_PRINTF("pwm_output[%d]: failed to quire ledc\n", pin_); - pinManager.deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); + PinManager::deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); return; } @@ -49,9 +49,9 @@ class PwmOutput { DEBUG_PRINTF("pwm_output[%d]: close\n", pin_); if (!enabled_) return; - pinManager.deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); + PinManager::deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); if (channel_ != 255) - pinManager.deallocateLedc(channel_, 1); + PinManager::deallocateLedc(channel_, 1); channel_ = 255; duty_ = 0.0f; enabled_ = false; diff --git a/usermods/quinled-an-penta/quinled-an-penta.h b/usermods/quinled-an-penta/quinled-an-penta.h index 10b7843344..e446720398 100644 --- a/usermods/quinled-an-penta/quinled-an-penta.h +++ b/usermods/quinled-an-penta/quinled-an-penta.h @@ -129,7 +129,7 @@ class QuinLEDAnPentaUsermod : public Usermod void initOledDisplay() { PinManagerPinType pins[5] = { { oledSpiClk, true }, { oledSpiData, true }, { oledSpiCs, true }, { oledSpiDc, true }, { oledSpiRst, true } }; - if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) { + if (!PinManager::allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) { DEBUG_PRINTF("[%s] OLED pin allocation failed!\n", _name); oledEnabled = oledInitDone = false; return; @@ -164,11 +164,11 @@ class QuinLEDAnPentaUsermod : public Usermod oledDisplay->clear(); } - pinManager.deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta); delete oledDisplay; @@ -184,7 +184,7 @@ class QuinLEDAnPentaUsermod : public Usermod void initSht30TempHumiditySensor() { PinManagerPinType pins[2] = { { shtSda, true }, { shtScl, true } }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) { + if (!PinManager::allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) { DEBUG_PRINTF("[%s] SHT30 pin allocation failed!\n", _name); shtEnabled = shtInitDone = false; return; @@ -212,8 +212,8 @@ class QuinLEDAnPentaUsermod : public Usermod sht30TempHumidSensor->reset(); } - pinManager.deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta); delete sht30TempHumidSensor; diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h index e57641bf9b..00fc227252 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h @@ -40,7 +40,7 @@ class RgbRotaryEncoderUsermod : public Usermod void initRotaryEncoder() { PinManagerPinType pins[2] = { { eaIo, false }, { ebIo, false } }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) { + if (!PinManager::allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) { eaIo = -1; ebIo = -1; cleanup(); @@ -108,11 +108,11 @@ class RgbRotaryEncoderUsermod : public Usermod { // Only deallocate pins if we allocated them ;) if (eaIo != -1) { - pinManager.deallocatePin(eaIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(eaIo, PinOwner::UM_RGBRotaryEncoder); eaIo = -1; } if (ebIo != -1) { - pinManager.deallocatePin(ebIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(ebIo, PinOwner::UM_RGBRotaryEncoder); ebIo = -1; } @@ -303,8 +303,8 @@ class RgbRotaryEncoderUsermod : public Usermod } if (eaIo != oldEaIo || ebIo != oldEbIo || stepsPerClick != oldStepsPerClick || incrementPerClick != oldIncrementPerClick) { - pinManager.deallocatePin(oldEaIo, PinOwner::UM_RGBRotaryEncoder); - pinManager.deallocatePin(oldEbIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(oldEaIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(oldEbIo, PinOwner::UM_RGBRotaryEncoder); delete rotaryEncoder; initRotaryEncoder(); diff --git a/usermods/sd_card/usermod_sd_card.h b/usermods/sd_card/usermod_sd_card.h index 5dac79159c..da1999d9b5 100644 --- a/usermods/sd_card/usermod_sd_card.h +++ b/usermods/sd_card/usermod_sd_card.h @@ -45,7 +45,7 @@ class UsermodSdCard : public Usermod { { configPinPico, true } }; - if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) { + if (!PinManager::allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) { DEBUG_PRINTF("[%s] SD (SPI) pin allocation failed!\n", _name); sdInitDone = false; return; @@ -75,10 +75,10 @@ class UsermodSdCard : public Usermod { SD_ADAPTER.end(); DEBUG_PRINTF("[%s] deallocate pins!\n", _name); - pinManager.deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard); - pinManager.deallocatePin(configPinSourceClock, PinOwner::UM_SdCard); - pinManager.deallocatePin(configPinPoci, PinOwner::UM_SdCard); - pinManager.deallocatePin(configPinPico, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinSourceClock, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinPoci, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinPico, PinOwner::UM_SdCard); sdInitDone = false; } diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h index 5c2fac0d4d..1436f8fc4c 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -165,7 +165,7 @@ class UsermodSSDR : public Usermod { void _showElements(String *map, int timevar, bool isColon, bool removeZero ) { - if (!(*map).equals("") && !(*map) == NULL) { + if ((map != nullptr) && (*map != nullptr) && !(*map).equals("")) { int length = String(timevar).length(); bool addZero = false; if (length == 1) { @@ -236,11 +236,13 @@ class UsermodSSDR : public Usermod { } void _setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon) { + if ((lednr < 0) || (lednr >= umSSDRLength)) return; // prevent array bounds violation + if (!(colon && umSSDRColonblink) && ((number < 0) || (countSegments < 0))) return; if ((colon && umSSDRColonblink) || umSSDRNumbers[number][countSegments]) { if (range) { - for(int i = lastSeenLedNr; i <= lednr; i++) { + for(int i = max(0, lastSeenLedNr); i <= lednr; i++) { umSSDRMask[i] = true; } } else { @@ -383,7 +385,7 @@ class UsermodSSDR : public Usermod { _setAllFalse(); #ifdef USERMOD_SN_PHOTORESISTOR - ptr = (Usermod_SN_Photoresistor*) usermods.lookup(USERMOD_ID_SN_PHOTORESISTOR); + ptr = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR); #endif DEBUG_PRINTLN(F("Setup done")); } diff --git a/usermods/sht/usermod_sht.h b/usermods/sht/usermod_sht.h index c6e17221be..f10c78a251 100644 --- a/usermods/sht/usermod_sht.h +++ b/usermods/sht/usermod_sht.h @@ -310,22 +310,22 @@ void ShtUsermod::onMqttConnect(bool sessionPresent) { * @return void */ void ShtUsermod::appendConfigData() { - oappend(SET_F("dd=addDropdown('")); + oappend(F("dd=addDropdown('")); oappend(_name); - oappend(SET_F("','")); + oappend(F("','")); oappend(_shtType); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'SHT30',0);")); - oappend(SET_F("addOption(dd,'SHT31',1);")); - oappend(SET_F("addOption(dd,'SHT35',2);")); - oappend(SET_F("addOption(dd,'SHT85',3);")); - oappend(SET_F("dd=addDropdown('")); + oappend(F("');")); + oappend(F("addOption(dd,'SHT30',0);")); + oappend(F("addOption(dd,'SHT31',1);")); + oappend(F("addOption(dd,'SHT35',2);")); + oappend(F("addOption(dd,'SHT85',3);")); + oappend(F("dd=addDropdown('")); oappend(_name); - oappend(SET_F("','")); + oappend(F("','")); oappend(_unitOfTemp); - oappend(SET_F("');")); - oappend(SET_F("addOption(dd,'Celsius',0);")); - oappend(SET_F("addOption(dd,'Fahrenheit',1);")); + oappend(F("');")); + oappend(F("addOption(dd,'Celsius',0);")); + oappend(F("addOption(dd,'Fahrenheit',1);")); } /** diff --git a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h index f712316b86..707479df17 100644 --- a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h +++ b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h @@ -96,7 +96,7 @@ void setup() { jsonTransitionOnce = true; strip.setTransition(0); //no transition effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning + strip.resetTimebase(); //make sure wipe starts from beginning //set wipe direction Segment& seg = strip.getSegment(0); diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino index c1264ebfb2..dc2159ee9d 100644 --- a/usermods/stairway_wipe_basic/wled06_usermod.ino +++ b/usermods/stairway_wipe_basic/wled06_usermod.ino @@ -86,7 +86,7 @@ void startWipe() bri = briLast; //turn on transitionDelayTemp = 0; //no transition effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning + strip.resetTimebase(); //make sure wipe starts from beginning //set wipe direction Segment& seg = strip.getSegment(0); diff --git a/usermods/usermod_v2_HttpPullLightControl/readme.md b/usermods/usermod_v2_HttpPullLightControl/readme.md index cf7f971f79..eb56d505d2 100644 --- a/usermods/usermod_v2_HttpPullLightControl/readme.md +++ b/usermods/usermod_v2_HttpPullLightControl/readme.md @@ -93,7 +93,7 @@ After getting the URL (it can be a static file like static.json or a mylogic.php - -D ABL_MILLIAMPS_DEFAULT=450 - -D DEFAULT_LED_COUNT=60 ; For a LED Ring of 60 LEDs - -D BTNPIN=41 ; The M5Stack Atom S3 Lite has a button on GPIO41 -- -D LEDPIN=2 ; The M5Stack Atom S3 Lite has a Grove connector on the front, we use this GPIO2 +- -D DATA_PINS=2 ; The M5Stack Atom S3 Lite has a Grove connector on the front, we use this GPIO2 - -D STATUSLED=35 ; The M5Stack Atom S3 Lite has a Multi-Color LED on GPIO35, although I didnt managed to control it - -D IRPIN=4 ; The M5Stack Atom S3 Lite has a IR LED on GPIO4 diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index 52ff3cc1db..a257413b42 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -103,7 +103,7 @@ class AutoSaveUsermod : public Usermod { #ifdef USERMOD_FOUR_LINE_DISPLAY // This Usermod has enhanced functionality if // FourLineDisplayUsermod is available. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + display = (FourLineDisplayUsermod*) UsermodManager::lookup(USERMOD_ID_FOUR_LINE_DISP); #endif initDone = true; if (enabled && applyAutoSaveOnBoot) applyPreset(autoSavePreset); diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 008647fa7b..684dd86e46 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -543,7 +543,7 @@ void FourLineDisplayUsermod::setup() { type = NONE; } else { PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; - if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type = NONE; } + if (!PinManager::allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type = NONE; } } } else { if (i2c_scl<0 || i2c_sda<0) { type=NONE; } @@ -569,7 +569,7 @@ void FourLineDisplayUsermod::setup() { if (nullptr == u8x8) { DEBUG_PRINTLN(F("Display init failed.")); if (isSPI) { - pinManager.deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay); + PinManager::deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay); } type = NONE; return; @@ -1202,21 +1202,21 @@ void FourLineDisplayUsermod::onUpdateBegin(bool init) { //} void FourLineDisplayUsermod::appendConfigData() { - oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); - oappend(SET_F("addOption(dd,'None',0);")); - oappend(SET_F("addOption(dd,'SSD1306',1);")); - oappend(SET_F("addOption(dd,'SH1106',2);")); - oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); - oappend(SET_F("addOption(dd,'SSD1305',4);")); - oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); - oappend(SET_F("addOption(dd,'SSD1309 128x64',9);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); - oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);")); - oappend(SET_F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); + oappend(F("dd=addDropdown('4LineDisplay','type');")); + oappend(F("addOption(dd,'None',0);")); + oappend(F("addOption(dd,'SSD1306',1);")); + oappend(F("addOption(dd,'SH1106',2);")); + oappend(F("addOption(dd,'SSD1306 128x64',3);")); + oappend(F("addOption(dd,'SSD1305',4);")); + oappend(F("addOption(dd,'SSD1305 128x64',5);")); + oappend(F("addOption(dd,'SSD1309 128x64',9);")); + oappend(F("addOption(dd,'SSD1306 SPI',6);")); + oappend(F("addOption(dd,'SSD1306 SPI 128x64',7);")); + oappend(F("addOption(dd,'SSD1309 SPI 128x64',8);")); + oappend(F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); + oappend(F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); + oappend(F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); + oappend(F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); } /* @@ -1307,7 +1307,7 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type == SSD1309_SPI64); bool newSPI = (newType == SSD1306_SPI || newType == SSD1306_SPI64 || newType == SSD1309_SPI64); if (isSPI) { - if (pinsChanged || !newSPI) pinManager.deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay); + if (pinsChanged || !newSPI) PinManager::deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay); if (!newSPI) { // was SPI but is no longer SPI if (i2c_scl<0 || i2c_sda<0) { newType=NONE; } @@ -1315,7 +1315,7 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { // still SPI but pins changed PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; if (ioPin[0]<0 || ioPin[1]<0 || ioPin[1]<0) { newType=NONE; } - else if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + else if (!PinManager::allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } } } else if (newSPI) { // was I2C but is now SPI @@ -1324,7 +1324,7 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { } else { PinManagerPinType pins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; if (ioPin[0]<0 || ioPin[1]<0 || ioPin[1]<0) { newType=NONE; } - else if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + else if (!PinManager::allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } } } else { // just I2C type changed diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 5756fbb695..383c1193eb 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -489,7 +489,7 @@ void RotaryEncoderUIUsermod::setup() enabled = false; return; } else { - if (pinIRQ >= 0 && pinManager.allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) { + if (pinIRQ >= 0 && PinManager::allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) { pinMode(pinIRQ, INPUT_PULLUP); attachInterrupt(pinIRQ, i2cReadingISR, FALLING); // RISING, FALLING, CHANGE, ONLOW, ONHIGH DEBUG_PRINTLN(F("Interrupt attached.")); @@ -502,7 +502,7 @@ void RotaryEncoderUIUsermod::setup() } } else { PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; - if (pinA<0 || pinB<0 || !pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { + if (pinA<0 || pinB<0 || !PinManager::allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { pinA = pinB = pinC = -1; enabled = false; return; @@ -525,7 +525,7 @@ void RotaryEncoderUIUsermod::setup() #ifdef USERMOD_FOUR_LINE_DISPLAY // This Usermod uses FourLineDisplayUsermod for the best experience. // But it's optional. But you want it. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + display = (FourLineDisplayUsermod*) UsermodManager::lookup(USERMOD_ID_FOUR_LINE_DISP); if (display != nullptr) { display->setMarkLine(1, 0); } @@ -1090,8 +1090,8 @@ void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) { } void RotaryEncoderUIUsermod::appendConfigData() { - oappend(SET_F("addInfo('Rotary-Encoder:PCF8574-address',1,'(not hex!)');")); - oappend(SET_F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); + oappend(F("addInfo('Rotary-Encoder:PCF8574-address',1,'(not hex!)');")); + oappend(F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});")); } /** @@ -1138,14 +1138,14 @@ bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) { if (oldPcf8574) { if (pinIRQ >= 0) { detachInterrupt(pinIRQ); - pinManager.deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI); DEBUG_PRINTLN(F("Deallocated old IRQ pin.")); } pinIRQ = newIRQpin<100 ? newIRQpin : -1; // ignore PCF8574 pins } else { - pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); - pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); - pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); DEBUG_PRINTLN(F("Deallocated old pins.")); } pinA = newDTpin; diff --git a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h index b66be290a5..7ecec08e59 100644 --- a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h +++ b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h @@ -433,8 +433,8 @@ class WordClockUsermod : public Usermod void appendConfigData() { - oappend(SET_F("addInfo('WordClockUsermod:ledOffset', 1, 'Number of LEDs before the letters');")); - oappend(SET_F("addInfo('WordClockUsermod:Norddeutsch', 1, 'Viertel vor instead of Dreiviertel');")); + oappend(F("addInfo('WordClockUsermod:ledOffset', 1, 'Number of LEDs before the letters');")); + oappend(F("addInfo('WordClockUsermod:Norddeutsch', 1, 'Viertel vor instead of Dreiviertel');")); } /* diff --git a/usermods/wireguard/wireguard.h b/usermods/wireguard/wireguard.h index 8c88d00018..8656a704af 100644 --- a/usermods/wireguard/wireguard.h +++ b/usermods/wireguard/wireguard.h @@ -54,13 +54,13 @@ class WireguardUsermod : public Usermod { } void appendConfigData() { - oappend(SET_F("addInfo('WireGuard:host',1,'Server Hostname');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:port',1,'Server Port');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:ip',1,'Device IP');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:psk',1,'Pre Shared Key (optional)');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:pem',1,'Private Key');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:pub',1,'Public Key');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('WireGuard:tz',1,'POSIX timezone string');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:host',1,'Server Hostname');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:port',1,'Server Port');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:ip',1,'Device IP');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:psk',1,'Pre Shared Key (optional)');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:pem',1,'Private Key');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:pub',1,'Public Key');")); // 0 is field type, 1 is actual field + oappend(F("addInfo('WireGuard:tz',1,'POSIX timezone string');")); // 0 is field type, 1 is actual field } void addToConfig(JsonObject& root) { diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 807594e430..2f24f745a7 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2,24 +2,10 @@ WS2812FX.cpp contains all effect methods Harm Aldick - 2016 www.aldick.org - LICENSE - The MIT License (MIT) + Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. + Licensed under the EUPL v. 1.2 or later + Adapted from code originally licensed under the MIT license Modified heavily for WLED */ @@ -75,7 +61,7 @@ int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { static um_data_t* getAudioData() { um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } @@ -2520,9 +2506,9 @@ static uint16_t ripple_base() { #endif { int left = rippleorigin - propI -1; - int right = rippleorigin + propI +3; + int right = rippleorigin + propI +2; for (int v = 0; v < 4; v++) { - unsigned mag = scale8(cubicwave8((propF>>2)+(v-left)*64), amp); + unsigned mag = scale8(cubicwave8((propF>>2) + v * 64), amp); SEGMENT.setPixelColor(left + v, color_blend(SEGMENT.getPixelColor(left + v), col, mag)); // TODO SEGMENT.setPixelColor(right - v, color_blend(SEGMENT.getPixelColor(right - v), col, mag)); // TODO } @@ -4031,7 +4017,7 @@ uint16_t mode_pacifica() // Increment the four "color index start" counters, one for each wave layer. // Each is incremented at a different speed, and the speeds vary over time. - unsigned sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step, sCIStart4 = SEGENV.step >> 16; + unsigned sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step & 0xFFFF, sCIStart4 = (SEGENV.step >> 16); uint32_t deltams = (FRAMETIME >> 2) + ((FRAMETIME * SEGMENT.speed) >> 7); uint64_t deltat = (strip.now >> 2) + ((strip.now * SEGMENT.speed) >> 7); strip.now = deltat; @@ -4046,7 +4032,7 @@ uint16_t mode_pacifica() sCIStart3 -= (deltams1 * beatsin88(501,5,7)); sCIStart4 -= (deltams2 * beatsin88(257,4,6)); SEGENV.aux0 = sCIStart1; SEGENV.aux1 = sCIStart2; - SEGENV.step = sCIStart4; SEGENV.step = (SEGENV.step << 16) + sCIStart3; + SEGENV.step = (sCIStart4 << 16) | (sCIStart3 & 0xFFFF); // Clear out the LED array to a dim background blue-green //SEGMENT.fill(132618); @@ -4077,7 +4063,7 @@ uint16_t mode_pacifica() c.green = scale8(c.green, 200); c |= CRGB( 2, 5, 7); - SEGMENT.setPixelColor(i, c.red, c.green, c.blue); + SEGMENT.setPixelColor(i, c); } strip.now = nowOld; @@ -4931,8 +4917,8 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so SEGMENT.fadeToBlackBy(40); for (size_t i = 0; i < numLines; i++) { byte x1 = beatsin8(2 + SEGMENT.speed/16, 0, (cols - 1)); - byte x2 = beatsin8(1 + SEGMENT.speed/16, 0, (cols - 1)); - byte y1 = beatsin8(5 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 24); + byte x2 = beatsin8(1 + SEGMENT.speed/16, 0, (rows - 1)); + byte y1 = beatsin8(5 + SEGMENT.speed/16, 0, (cols - 1), 0, i * 24); byte y2 = beatsin8(3 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 48 + 64); CRGB color = ColorFromPalette(SEGPALETTE, i * 255 / numLines + (SEGENV.aux0&0xFF), 255, LINEARBLEND); @@ -5010,9 +4996,11 @@ uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulma // draw a gradient line between x and x1 x = x / 2; x1 = x1 / 2; unsigned steps = abs8(x - x1) + 1; + bool positive = (x1 >= x); // direction of drawing for (size_t k = 1; k <= steps; k++) { unsigned rate = k * 255 / steps; - unsigned dx = lerp8by8(x, x1, rate); + //unsigned dx = lerp8by8(x, x1, rate); + unsigned dx = positive? (x + k-1) : (x - k+1); // behaves the same as "lerp8by8" but does not create holes //SEGMENT.setPixelColorXY(dx, i, ColorFromPalette(SEGPALETTE, hue, 255, LINEARBLEND).nscale8_video(rate)); SEGMENT.addPixelColorXY(dx, i, ColorFromPalette(SEGPALETTE, hue, 255, LINEARBLEND)); // use setPixelColorXY for different look SEGMENT.fadePixelColorXY(dx, i, rate); @@ -6298,7 +6286,7 @@ static const char _data_FX_MODE_2DPLASMAROTOZOOM[] PROGMEM = "Rotozoomer@!,Scale uint8_t *fftResult = nullptr; float *fftBin = nullptr; um_data_t *um_data; - if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { volumeSmth = *(float*) um_data->u_data[0]; volumeRaw = *(float*) um_data->u_data[1]; fftResult = (uint8_t*) um_data->u_data[2]; @@ -6483,11 +6471,6 @@ static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly@Amplification,Sen #endif // WLED_DISABLE_2D -// float version of map() -static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -} - // Gravity struct requited for GRAV* effects typedef struct Gravity { int topLED; @@ -6916,7 +6899,7 @@ uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline. uint8_t *myVals = reinterpret_cast(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low. um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { um_data = simulateSound(SEGMENT.soundSim); } float volumeSmth = *(float*) um_data->u_data[0]; @@ -7499,7 +7482,7 @@ uint16_t mode_2DAkemi(void) { const float normalFactor = 0.4f; um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { um_data = simulateSound(SEGMENT.soundSim); } uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; @@ -7533,8 +7516,9 @@ uint16_t mode_2DAkemi(void) { //add geq left and right if (um_data && fftResult) { - for (int x=0; x < cols/8; x++) { - unsigned band = x * cols/8; + int xMax = cols/8; + for (int x=0; x < xMax; x++) { + unsigned band = map(x, 0, max(xMax,4), 0, 15); // map 0..cols/8 to 16 GEQ bands band = constrain(band, 0, 15); int barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); CRGB color = CRGB(SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0)); @@ -7751,7 +7735,7 @@ uint16_t mode_2Doctopus() { const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - rMap[XY(x, y)].angle = 40.7436f * atan2f((y - C_Y), (x - C_X)); // avoid 128*atan2()/PI + rMap[XY(x, y)].angle = int(40.7436f * atan2f((y - C_Y), (x - C_X))); // avoid 128*atan2()/PI rMap[XY(x, y)].radius = hypotf((x - C_X), (y - C_Y)) * mapp; //thanks Sutaburosu } } diff --git a/wled00/FX.h b/wled00/FX.h index b1b1fbcb78..5451615464 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -2,24 +2,10 @@ WS2812FX.h - Library for WS2812 LED effects. Harm Aldick - 2016 www.aldick.org - LICENSE - The MIT License (MIT) + Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. + Licensed under the EUPL v. 1.2 or later + Adapted from code originally licensed under the MIT license Modified for WLED */ @@ -61,6 +47,15 @@ #define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() +// FPS calculation (can be defined as compile flag for debugging) +#ifndef FPS_CALC_AVG +#define FPS_CALC_AVG 7 // average FPS calculation over this many frames (moving average) +#endif +#ifndef FPS_MULTIPLIER +#define FPS_MULTIPLIER 1 // dev option: multiplier to get sub-frame FPS without floats +#endif +#define FPS_CALC_SHIFT 7 // bit shift for fixed point math + /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ #ifdef ESP8266 @@ -517,68 +512,68 @@ typedef struct Segment { #endif inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); } - inline bool isSelected(void) const { return selected; } - inline bool isInTransition(void) const { return _t != nullptr; } - inline bool isActive(void) const { return stop > start; } - inline bool is2D(void) const { return (width()>1 && height()>1); } - inline bool hasRGB(void) const { return _isRGB; } - inline bool hasWhite(void) const { return _hasW; } - inline bool isCCT(void) const { return _isCCT; } - inline uint16_t width(void) const { return isActive() ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) - inline uint16_t height(void) const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1) - inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels - inline uint16_t groupLength(void) const { return grouping + spacing; } - inline uint8_t getLightCapabilities(void) const { return _capabilities; } - - static uint16_t getUsedSegmentData(void) { return _usedSegmentData; } - static void addUsedSegmentData(int len) { _usedSegmentData += len; } + inline bool isSelected() const { return selected; } + inline bool isInTransition() const { return _t != nullptr; } + inline bool isActive() const { return stop > start; } + inline bool is2D() const { return (width()>1 && height()>1); } + inline bool hasRGB() const { return _isRGB; } + inline bool hasWhite() const { return _hasW; } + inline bool isCCT() const { return _isCCT; } + inline uint16_t width() const { return isActive() ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) + inline uint16_t height() const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1) + inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels + inline uint16_t groupLength() const { return grouping + spacing; } + inline uint8_t getLightCapabilities() const { return _capabilities; } + + inline static uint16_t getUsedSegmentData() { return _usedSegmentData; } + inline static void addUsedSegmentData(int len) { _usedSegmentData += len; } #ifndef WLED_DISABLE_MODE_BLEND - static void modeBlend(bool blend) { _modeBlend = blend; } + inline static void modeBlend(bool blend) { _modeBlend = blend; } #endif static void handleRandomPalette(); - inline static const CRGBPalette16 &getCurrentPalette(void) { return Segment::_currentPalette; } + inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; } void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); - bool setColor(uint8_t slot, uint32_t c); //returns true if changed - void setCCT(uint16_t k); - void setOpacity(uint8_t o); - void setOption(uint8_t n, bool val); - void setMode(uint8_t fx, bool loadDefaults = false); - void setPalette(uint8_t pal); + Segment &setColor(uint8_t slot, uint32_t c); + Segment &setCCT(uint16_t k); + Segment &setOpacity(uint8_t o); + Segment &setOption(uint8_t n, bool val); + Segment &setMode(uint8_t fx, bool loadDefaults = false); + Segment &setPalette(uint8_t pal); uint8_t differs(Segment& b) const; - void refreshLightCapabilities(void); + void refreshLightCapabilities(); // runtime data functions - inline uint16_t dataSize(void) const { return _dataLen; } + inline uint16_t dataSize() const { return _dataLen; } bool allocateData(size_t len); // allocates effect data buffer in heap and clears it - void deallocateData(void); // deallocates (frees) effect data buffer from heap - void resetIfRequired(void); // sets all SEGENV variables to 0 and clears data buffer + void deallocateData(); // deallocates (frees) effect data buffer from heap + void resetIfRequired(); // sets all SEGENV variables to 0 and clears data buffer /** * Flags that before the next effect is calculated, * the internal segment state should be reset. * Call resetIfRequired before calling the next effect function. * Safe to call from interrupts and network requests. */ - inline void markForReset(void) { reset = true; } // setOption(SEG_OPTION_RESET, true) + inline Segment &markForReset() { reset = true; return *this; } // setOption(SEG_OPTION_RESET, true) // transition functions void startTransition(uint16_t dur); // transition has to start before actual segment values change - void stopTransition(void); // ends transition mode by destroying transition structure (does nothing if not in transition) - inline void handleTransition(void) { if (progress() == 0xFFFFU) stopTransition(); } + void stopTransition(); // ends transition mode by destroying transition structure (does nothing if not in transition) + inline void handleTransition() { if (progress() == 0xFFFFU) stopTransition(); } #ifndef WLED_DISABLE_MODE_BLEND void swapSegenv(tmpsegd_t &tmpSegD); // copies segment data into specifed buffer, if buffer is not a transition buffer, segment data is overwritten from transition buffer void restoreSegenv(tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer #endif - uint16_t progress(void) const; // transition progression between 0-65535 - uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition) - uint8_t currentMode(void) const; // currently active effect/mode (while in transition) - uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) + [[gnu::hot]] uint16_t progress() const; // transition progression between 0-65535 + [[gnu::hot]] uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition) + uint8_t currentMode() const; // currently active effect/mode (while in transition) + [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); - void setCurrentPalette(void); + void setCurrentPalette(); // 1D strip - uint16_t virtualLength(void) const; - void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color + [[gnu::hot]] uint16_t virtualLength() const; + [[gnu::hot]] void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } @@ -587,7 +582,7 @@ typedef struct Segment { inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #endif - uint32_t getPixelColor(int i) const; + [[gnu::hot]] uint32_t getPixelColor(int i) const; // 1D support functions (some implement 2D as well) void blur(uint8_t, bool smear = false); void fill(uint32_t c); @@ -599,8 +594,8 @@ typedef struct Segment { inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } - uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; - uint32_t color_wheel(uint8_t pos) const; + [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; + [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; // 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur) inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns @@ -613,12 +608,18 @@ typedef struct Segment { } // 2D matrix - uint16_t virtualWidth(void) const; // segment width in virtual pixels (accounts for groupping and spacing) - uint16_t virtualHeight(void) const; // segment height in virtual pixels (accounts for groupping and spacing) - uint16_t nrOfVStrips(void) const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) + [[gnu::hot]] unsigned virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) + [[gnu::hot]] unsigned virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) + inline unsigned nrOfVStrips() const { // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) + #ifndef WLED_DISABLE_2D + return (is2D() && map1D2D == M12_pBar) ? virtualWidth() : 1; + #else + return 1; + #endif + } #ifndef WLED_DISABLE_2D - uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment - void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color + [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment + [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } @@ -628,7 +629,7 @@ typedef struct Segment { inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } #endif - uint32_t getPixelColorXY(int x, int y) const; + [[gnu::hot]] uint32_t getPixelColorXY(int x, int y) const; // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } @@ -697,8 +698,8 @@ typedef struct Segment { // main "strip" class class WS2812FX { // 96 bytes - typedef uint16_t (*mode_ptr)(void); // pointer to mode function - typedef void (*show_callback)(void); // pre show callback + typedef uint16_t (*mode_ptr)(); // pointer to mode function + typedef void (*show_callback)(); // pre show callback typedef struct ModeData { uint8_t _id; // mode (effect) id mode_ptr _fcn; // mode (effect) function @@ -720,7 +721,11 @@ class WS2812FX { // 96 bytes #ifndef WLED_DISABLE_2D panels(1), #endif +#ifdef WLED_AUTOSEGMENTS + autoSegments(true), +#else autoSegments(false), +#endif correctWB(false), cctFromRgb(false), // semi-private (just obscured) used in effect functions through macros @@ -733,7 +738,7 @@ class WS2812FX { // 96 bytes _transitionDur(750), _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), - _cumulativeFps(2), + _cumulativeFps(50 << FPS_CALC_SHIFT), _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), @@ -764,104 +769,103 @@ class WS2812FX { // 96 bytes customPalettes.clear(); } - static WS2812FX* getInstance(void) { return instance; } + static WS2812FX* getInstance() { return instance; } void #ifdef WLED_DEBUG printSize(), // prints memory usage for strip components #endif finalizeInit(), // initialises strip components - service(void), // executes effect functions when due and calls strip.show() + service(), // executes effect functions when due and calls strip.show() setMode(uint8_t segid, uint8_t m), // sets effect/mode for given segment (high level API) setColor(uint8_t slot, uint32_t c), // sets color (in slot) for given segment (high level API) setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K) setBrightness(uint8_t b, bool direct = false), // sets strip brightness setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay - purgeSegments(void), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) + purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(uint8_t n), resetSegments(), // marks all segments for reset makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs fixInvalidSegments(), // fixes incorrect segment configuration setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c - show(void), // initiates LED output + show(), // initiates LED output setTargetFps(uint8_t fps), - setupEffectData(void); // add default effects to the list; defined in FX.cpp + setupEffectData(); // add default effects to the list; defined in FX.cpp - inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); } + inline void resetTimebase() { timebase = 0UL - millis(); } + inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } } inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); } inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) - inline void trigger(void) { _triggered = true; } // Forces the next frame to be computed on all active segments. + inline void trigger() { _triggered = true; } // Forces the next frame to be computed on all active segments. inline void setShowCallback(show_callback cb) { _callback = cb; } inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms) inline void appendSegment(const Segment &seg = Segment()) { if (_segments.size() < getMaxSegments()) _segments.push_back(seg); } - inline void suspend(void) { _suspend = true; } // will suspend (and canacel) strip.service() execution - inline void resume(void) { _suspend = false; } // will resume strip.service() execution + inline void suspend() { _suspend = true; } // will suspend (and canacel) strip.service() execution + inline void resume() { _suspend = false; } // will resume strip.service() execution bool paletteFade, - checkSegmentAlignment(void), - hasRGBWBus(void) const, - hasCCTBus(void) const, - isUpdating(void) const, // return true if the strip is being sent pixel updates + checkSegmentAlignment(), + hasRGBWBus() const, + hasCCTBus() const, + isUpdating() const, // return true if the strip is being sent pixel updates deserializeMap(uint8_t n=0); - inline bool isServicing(void) const { return _isServicing; } // returns true if strip.service() is executing - inline bool hasWhiteChannel(void) const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel - inline bool isOffRefreshRequired(void) const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) - inline bool isSuspended(void) const { return _suspend; } // returns true if strip.service() execution is suspended - inline bool needsUpdate(void) const { return _triggered; } // returns true if strip received a trigger() request + inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing + inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel + inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) + inline bool isSuspended() const { return _suspend; } // returns true if strip.service() execution is suspended + inline bool needsUpdate() const { return _triggered; } // returns true if strip received a trigger() request uint8_t paletteBlend, cctBlending, - getActiveSegmentsNum(void) const, - getFirstSelectedSegId(void) const, - getLastActiveSegmentId(void) const, + getActiveSegmentsNum() const, + getFirstSelectedSegId() const, + getLastActiveSegmentId() const, getActiveSegsLightCapabilities(bool selectedOnly = false) const, addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp; - inline uint8_t getBrightness(void) const { return _brightness; } // returns current strip brightness - inline uint8_t getMaxSegments(void) const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) - inline uint8_t getSegmentsNum(void) const { return _segments.size(); } // returns currently present segments - inline uint8_t getCurrSegmentId(void) const { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) - inline uint8_t getMainSegmentId(void) const { return _mainSegment; } // returns main segment index - inline uint8_t getPaletteCount() const { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } - inline uint8_t getTargetFps() const { return _targetFps; } // returns rough FPS value for las 2s interval - inline uint8_t getModeCount() const { return _modeCount; } // returns number of registered modes/effects + inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness + inline uint8_t getMaxSegments() const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) + inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments + inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) + inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index + inline uint8_t getPaletteCount() const { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } + inline uint8_t getTargetFps() const { return _targetFps; } // returns rough FPS value for las 2s interval + inline uint8_t getModeCount() const { return _modeCount; } // returns number of registered modes/effects uint16_t - getLengthPhysical(void) const, - getLengthTotal(void) const, // will include virtual/nonexistent pixels in matrix + getLengthPhysical() const, + getLengthTotal() const, // will include virtual/nonexistent pixels in matrix getFps() const, getMappedPixelIndex(uint16_t index) const; - inline uint16_t getFrameTime(void) const { return _frametime; } // returns amount of time a frame should take (in ms) - inline uint16_t getMinShowDelay(void) const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) - inline uint16_t getLength(void) const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) - inline uint16_t getTransition(void) const { return _transitionDur; } // returns currently set transition time (in ms) + inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms) + inline uint16_t getMinShowDelay() const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) + inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) + inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms) - uint32_t - now, - timebase, - getPixelColor(uint16_t) const; + unsigned long now, timebase; + uint32_t getPixelColor(unsigned) const; - inline uint32_t getLastShow(void) const { return _lastShow; } // returns millis() timestamp of last strip.show() call + inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition const char * getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } const char ** - getModeDataSrc(void) { return &(_modeData[0]); } // vectors use arrays for underlying data + getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data Segment& getSegment(uint8_t id); - inline Segment& getFirstSelectedSeg(void) { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" - inline Segment& getMainSegment(void) { return _segments[getMainSegmentId()]; } // returns reference to main segment - inline Segment* getSegments(void) { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) + inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" + inline Segment& getMainSegment() { return _segments[getMainSegmentId()]; } // returns reference to main segment + inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) // 2D support (panels) bool @@ -908,7 +912,7 @@ class WS2812FX { // 96 bytes // end 2D support - void loadCustomPalettes(void); // loads custom palettes from JSON + void loadCustomPalettes(); // loads custom palettes from JSON std::vector customPalettes; // TODO: move custom palettes out of WS2812FX class struct { diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 44919f925c..7c1ae366b7 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -1,24 +1,9 @@ /* FX_2Dfcn.cpp contains all 2D utility functions - LICENSE - The MIT License (MIT) Copyright (c) 2022 Blaz Kristan (https://blaz.at/home) - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. + Licensed under the EUPL v. 1.2 or later + Adapted from code originally licensed under the MIT license Parts of the code adapted from WLED Sound Reactive */ @@ -161,17 +146,17 @@ void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) +uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) { unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) return isActive() ? (x%width) + (y%height) * width : 0; } -void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit + if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit uint8_t _bri_t = currentBri(); if (_bri_t < 255) { @@ -211,7 +196,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, tmpCol); + strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, tmpCol); } } } @@ -264,9 +249,9 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) #endif // returns RGBW values of pixel -uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) const { +uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (!isActive()) return 0; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit + if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed @@ -704,11 +689,14 @@ void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)}; // multiply the intensities by the colour, and saturating-add them to the pixels for (int i = 0; i < 4; i++) { - CRGB led = getPixelColorXY((x >> 8) + (i & 1), (y >> 8) + ((i >> 1) & 1)); + int wu_x = (x >> 8) + (i & 1); // precalculate x + int wu_y = (y >> 8) + ((i >> 1) & 1); // precalculate y + CRGB led = getPixelColorXY(wu_x, wu_y); + CRGB oldLed = led; led.r = qadd8(led.r, c.r * wu[i] >> 8); led.g = qadd8(led.g, c.g * wu[i] >> 8); led.b = qadd8(led.b, c.b * wu[i] >> 8); - setPixelColorXY(int((x >> 8) + (i & 1)), int((y >> 8) + ((i >> 1) & 1)), led); + if (led != oldLed) setPixelColorXY(wu_x, wu_y, led); // don't repaint if same color } } #undef WU_WEIGHT diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index bc5c8b0519..e706f2b431 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -2,24 +2,10 @@ WS2812FX_fcn.cpp contains all utility functions Harm Aldick - 2016 www.aldick.org - LICENSE - The MIT License (MIT) + Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. + Licensed under the EUPL v. 1.2 or later + Adapted from code originally licensed under the MIT license Modified heavily for WLED */ @@ -43,21 +29,16 @@ 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]} */ -//factory defaults LED setup -//#define PIXEL_COUNTS 30, 30, 30, 30 -//#define DATA_PINS 16, 1, 3, 4 -//#define DEFAULT_LED_TYPE TYPE_WS2812_RGB - #ifndef PIXEL_COUNTS #define PIXEL_COUNTS DEFAULT_LED_COUNT #endif #ifndef DATA_PINS - #define DATA_PINS LEDPIN + #define DATA_PINS DEFAULT_LED_PIN #endif -#ifndef DEFAULT_LED_TYPE - #define DEFAULT_LED_TYPE TYPE_WS2812_RGB +#ifndef LED_TYPES + #define LED_TYPES DEFAULT_LED_TYPE #endif #ifndef DEFAULT_LED_COLOR_ORDER @@ -69,6 +50,18 @@ #error "Max segments must be at least max number of busses!" #endif +static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) { + return (count > 0) ? (Bus::getNumberOfPins(*current) + sumPinsRequired(current+1,count-1)) : 0; +} + +static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) { + // Pins provided < pins required -> always invalid + // Pins provided = pins required -> always valid + // Pins provided > pins required -> valid if excess pins are a product of last type pins since it will be repeated + return (sumPinsRequired(types, numTypes) > numPins) ? false : + (numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0; +} + /////////////////////////////////////////////////////////////////////////////// // Segment class implementation @@ -146,7 +139,7 @@ Segment& Segment::operator= (Segment &&orig) noexcept { } // allocates effect data buffer on heap and initialises (erases) it -bool IRAM_ATTR Segment::allocateData(size_t len) { +bool IRAM_ATTR_YN Segment::allocateData(size_t len) { if (len == 0) return false; // nothing to do if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation) if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation @@ -170,17 +163,13 @@ bool IRAM_ATTR Segment::allocateData(size_t len) { return true; } -void IRAM_ATTR Segment::deallocateData() { +void IRAM_ATTR_YN Segment::deallocateData() { if (!data) { _dataLen = 0; return; } //DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data); if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer free(data); } else { - DEBUG_PRINT(F("---- Released data ")); - DEBUG_PRINTF_P(PSTR("(%p): "), this); - DEBUG_PRINT(F("inconsistent UsedSegmentData ")); - DEBUG_PRINTF_P(PSTR("(%d/%d)"), _dataLen, Segment::getUsedSegmentData()); - DEBUG_PRINTLN(F(", cowardly refusing to free nothing.")); + DEBUG_PRINTF_P(PSTR("---- Released data (%p): inconsistent UsedSegmentData (%d/%d), cowardly refusing to free nothing.\n"), this, _dataLen, Segment::getUsedSegmentData()); } data = nullptr; Segment::addUsedSegmentData(_dataLen <= Segment::getUsedSegmentData() ? -_dataLen : -Segment::getUsedSegmentData()); @@ -202,7 +191,7 @@ void Segment::resetIfRequired() { reset = false; } -CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { +CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip //default palette. Differs depending on effect @@ -417,7 +406,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) const { return (useCct ? cct : (on ? opacity : 0)); } -uint8_t IRAM_ATTR Segment::currentMode() const { +uint8_t Segment::currentMode() const { #ifndef WLED_DISABLE_MODE_BLEND unsigned prog = progress(); if (modeBlending && prog < 0xFFFFU) return _t->_modeT; @@ -425,7 +414,7 @@ uint8_t IRAM_ATTR Segment::currentMode() const { return mode; } -uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) const { +uint32_t IRAM_ATTR_YN Segment::currentColor(uint8_t slot) const { if (slot >= NUM_COLORS) slot = 0; #ifndef WLED_DISABLE_MODE_BLEND return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; @@ -520,46 +509,53 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t } -bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed - if (slot >= NUM_COLORS || c == colors[slot]) return false; +Segment &Segment::setColor(uint8_t slot, uint32_t c) { + if (slot >= NUM_COLORS || c == colors[slot]) return *this; if (!_isRGB && !_hasW) { - if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black - if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black + if (slot == 0 && c == BLACK) return *this; // on/off segment cannot have primary color black + if (slot == 1 && c != BLACK) return *this; // on/off segment cannot have secondary color non black } if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast - return true; + return *this; } -void Segment::setCCT(uint16_t k) { +Segment &Segment::setCCT(uint16_t k) { if (k > 255) { //kelvin value, convert to 0-255 if (k < 1900) k = 1900; if (k > 10091) k = 10091; k = (k - 1900) >> 5; } - if (cct == k) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change - cct = k; - stateChanged = true; // send UDP/WS broadcast + if (cct != k) { + //DEBUGFX_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k); + startTransition(strip.getTransition()); // start transition prior to change + cct = k; + stateChanged = true; // send UDP/WS broadcast + } + return *this; } -void Segment::setOpacity(uint8_t o) { - if (opacity == o) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change - opacity = o; - stateChanged = true; // send UDP/WS broadcast +Segment &Segment::setOpacity(uint8_t o) { + if (opacity != o) { + //DEBUGFX_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o); + startTransition(strip.getTransition()); // start transition prior to change + opacity = o; + stateChanged = true; // send UDP/WS broadcast + } + return *this; } -void Segment::setOption(uint8_t n, bool val) { +Segment &Segment::setOption(uint8_t n, bool val) { bool prevOn = on; if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change if (val) options |= 0x01 << n; else options &= ~(0x01 << n); if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast + return *this; } -void Segment::setMode(uint8_t fx, bool loadDefaults) { +Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { // skip reserved while (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4) == 0) fx++; if (fx >= strip.getModeCount()) fx = 0; // set solid mode @@ -591,9 +587,10 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { markForReset(); stateChanged = true; // send UDP/WS broadcast } + return *this; } -void Segment::setPalette(uint8_t pal) { +Segment &Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { @@ -601,37 +598,24 @@ void Segment::setPalette(uint8_t pal) { palette = pal; stateChanged = true; // send UDP/WS broadcast } + return *this; } // 2D matrix -uint16_t IRAM_ATTR Segment::virtualWidth() const { +unsigned IRAM_ATTR Segment::virtualWidth() const { unsigned groupLen = groupLength(); unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED return vWidth; } -uint16_t IRAM_ATTR Segment::virtualHeight() const { +unsigned IRAM_ATTR Segment::virtualHeight() const { unsigned groupLen = groupLength(); unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED return vHeight; } -uint16_t IRAM_ATTR Segment::nrOfVStrips() const { - unsigned vLen = 1; -#ifndef WLED_DISABLE_2D - if (is2D()) { - switch (map1D2D) { - case M12_pBar: - vLen = virtualWidth(); - break; - } - } -#endif - return vLen; -} - // Constants for mapping mode "Pinwheel" #ifndef WLED_DISABLE_2D constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16 @@ -701,7 +685,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { return vLength; } -void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D @@ -895,7 +879,7 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) } #endif -uint32_t IRAM_ATTR Segment::getPixelColor(int i) const +uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const { if (!isActive()) return 0; // not active #ifndef WLED_DISABLE_2D @@ -1027,7 +1011,6 @@ void Segment::refreshLightCapabilities() { if (bus->getStart() >= segStopIdx) continue; if (bus->getStart() + bus->getLength() <= segStartIdx) continue; - //uint8_t type = bus->getType(); if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; if (!strip.cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; if (strip.correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) @@ -1197,12 +1180,9 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ /////////////////////////////////////////////////////////////////////////////// //do not call this method from system context (network callback) -void WS2812FX::finalizeInit(void) { +void WS2812FX::finalizeInit() { //reset segment runtimes - for (segment &seg : _segments) { - seg.markForReset(); - seg.resetIfRequired(); - } + restartRuntime(); // for the lack of better place enumerate ledmaps here // if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs @@ -1215,28 +1195,82 @@ void WS2812FX::finalizeInit(void) { //if busses failed to load, add default (fresh install, FS issue, ...) if (BusManager::getNumBusses() == 0) { DEBUG_PRINTLN(F("No busses, init default")); - const unsigned defDataPins[] = {DATA_PINS}; - const unsigned defCounts[] = {PIXEL_COUNTS}; - const unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); - const unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); - // if number of pins is divisible by counts, use number of counts to determine number of buses, otherwise use pins - const unsigned defNumBusses = defNumPins > defNumCounts && defNumPins%defNumCounts == 0 ? defNumCounts : defNumPins; - const unsigned pinsPerBus = defNumPins / defNumBusses; + constexpr unsigned defDataTypes[] = {LED_TYPES}; + constexpr unsigned defDataPins[] = {DATA_PINS}; + constexpr unsigned defCounts[] = {PIXEL_COUNTS}; + constexpr unsigned defNumTypes = ((sizeof defDataTypes) / (sizeof defDataTypes[0])); + constexpr unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); + constexpr unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); + + static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins), + "The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES"); + unsigned prevLen = 0; - for (unsigned i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { - uint8_t defPin[5]; // max 5 pins - for (unsigned j = 0; j < pinsPerBus; j++) defPin[j] = defDataPins[i*pinsPerBus + j]; - // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware - // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc - if (pinManager.isPinAllocated(defPin[0])) { - defPin[0] = 1; // start with GPIO1 and work upwards - while (pinManager.isPinAllocated(defPin[0]) && defPin[0] < WLED_NUM_PINS) defPin[0]++; + unsigned pinsIndex = 0; + for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { + uint8_t defPin[OUTPUT_MAX_PINS]; + // if we have less types than requested outputs and they do not align, use last known type to set current type + unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1]; + unsigned busPins = Bus::getNumberOfPins(dataType); + + // if we need more pins than available all outputs have been configured + if (pinsIndex + busPins > defNumPins) break; + + // Assign all pins first so we can check for conflicts on this bus + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j]; + + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) { + bool validPin = true; + // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware + // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. + // Pin should not be already allocated, read/only or defined for current bus + while (PinManager::isPinAllocated(defPin[j]) || !PinManager::isPinOk(defPin[j],true)) { + if (validPin) { + DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); + defPin[j] = 1; // start with GPIO1 and work upwards + validPin = false; + } else if (defPin[j] < WLED_NUM_PINS) { + defPin[j]++; + } else { + DEBUG_PRINTLN(F("No available pins left! Can't configure output.")); + return; + } + // is the newly assigned pin already defined or used previously? + // try next in line until there are no clashes or we run out of pins + bool clash; + do { + clash = false; + // check for conflicts on current bus + for (const auto &pin : defPin) { + if (&pin != &defPin[j] && pin == defPin[j]) { + clash = true; + break; + } + } + // We already have a clash on current bus, no point checking next buses + if (!clash) { + // check for conflicts in defined pins + for (const auto &pin : defDataPins) { + if (pin == defPin[j]) { + clash = true; + break; + } + } + } + if (clash) defPin[j]++; + if (defPin[j] >= WLED_NUM_PINS) break; + } while (clash); + } } + pinsIndex += busPins; + unsigned start = prevLen; // if we have less counts than pins and they do not align, use last known count to set current count unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; + // analog always has length 1 + if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1; prevLen += count; - BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); + BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); if (BusManager::add(defCfg) == -1) break; } } @@ -1249,12 +1283,12 @@ void WS2812FX::finalizeInit(void) { //RGBW mode is enabled if at least one of the strips is RGBW _hasWhiteChannel |= bus->hasWhite(); //refresh is required to remain off if at least one of the strips requires the refresh. - _isOffRefreshRequired |= bus->isOffRefreshRequired(); + _isOffRefreshRequired |= bus->isOffRefreshRequired() && !bus->isPWM(); // use refresh bit for phase shift with analog unsigned busEnd = bus->getStart() + bus->getLength(); if (busEnd > _length) _length = busEnd; #ifdef ESP8266 // why do we need to reinitialise GPIO3??? - //if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue; + //if (!bus->isDigital() || bus->is2Pin()) continue; //uint8_t pins[5]; //if (!bus->getPins(pins)) continue; //BusDigital* bd = static_cast(bus); @@ -1295,7 +1329,7 @@ void WS2812FX::service() { if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { doShow = true; - unsigned delay = FRAMETIME; + unsigned frameDelay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) @@ -1315,7 +1349,7 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - delay = (*_mode[seg.mode])(); // run new/current mode + frameDelay = (*_mode[seg.mode])(); // run new/current mode #ifndef WLED_DISABLE_MODE_BLEND if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; @@ -1324,16 +1358,16 @@ void WS2812FX::service() { _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) - delay = MIN(delay,d2); // use shortest delay + frameDelay = min(frameDelay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore } #endif seg.call++; - if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } - seg.next_time = nowUp + delay; + seg.next_time = nowUp + frameDelay; } _segment_index++; } @@ -1360,13 +1394,13 @@ void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) { BusManager::setPixelColor(i, col); } -uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) const { +uint32_t IRAM_ATTR WS2812FX::getPixelColor(unsigned i) const { i = getMappedPixelIndex(i); if (i >= _length) return 0; return BusManager::getPixelColor(i); } -void WS2812FX::show(void) { +void WS2812FX::show() { // avoid race condition, capture _callback value show_callback callback = _callback; if (callback) callback(); @@ -1378,10 +1412,12 @@ void WS2812FX::show(void) { unsigned long showNow = millis(); size_t diff = showNow - _lastShow; - size_t fpsCurr = 200; - if (diff > 0) fpsCurr = 1000 / diff; - _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) - _lastShow = showNow; + + if (diff > 0) { // skip calculation if no time has passed + size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math + _cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding + _lastShow = showNow; + } } /** @@ -1398,7 +1434,7 @@ bool WS2812FX::isUpdating() const { */ uint16_t WS2812FX::getFps() const { if (millis() - _lastShow > 2000) return 0; - return _cumulativeFps +1; + return (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; // _cumulativeFps is stored in fixed point } void WS2812FX::setTargetFps(uint8_t fps) { @@ -1463,7 +1499,7 @@ uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) const { return totalLC; } -uint8_t WS2812FX::getFirstSelectedSegId(void) const { +uint8_t WS2812FX::getFirstSelectedSegId() const { size_t i = 0; for (const segment &seg : _segments) { if (seg.isActive() && seg.isSelected()) return i; @@ -1481,14 +1517,14 @@ void WS2812FX::setMainSegmentId(uint8_t n) { return; } -uint8_t WS2812FX::getLastActiveSegmentId(void) const { +uint8_t WS2812FX::getLastActiveSegmentId() const { for (size_t i = _segments.size() -1; i > 0; i--) { if (_segments[i].isActive()) return i; } return 0; } -uint8_t WS2812FX::getActiveSegmentsNum(void) const { +uint8_t WS2812FX::getActiveSegmentsNum() const { uint8_t c = 0; for (size_t i = 0; i < _segments.size(); i++) { if (_segments[i].isActive()) c++; @@ -1496,17 +1532,17 @@ uint8_t WS2812FX::getActiveSegmentsNum(void) const { return c; } -uint16_t WS2812FX::getLengthTotal(void) const { +uint16_t WS2812FX::getLengthTotal() const { unsigned len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D if (isMatrix && _length > len) len = _length; // for 2D with trailing strip return len; } -uint16_t WS2812FX::getLengthPhysical(void) const { +uint16_t WS2812FX::getLengthPhysical() const { unsigned len = 0; for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); - if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses + if (bus->isVirtual()) continue; //exclude non-physical network busses len += bus->getLength(); } return len; @@ -1515,7 +1551,7 @@ uint16_t WS2812FX::getLengthPhysical(void) const { //used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. //returns if there is an RGBW bus (supports RGB and White, not only white) //not influenced by auto-white mode, also true if white slider does not affect output white channel -bool WS2812FX::hasRGBWBus(void) const { +bool WS2812FX::hasRGBWBus() const { for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); if (bus == nullptr || bus->getLength()==0) break; @@ -1524,7 +1560,7 @@ bool WS2812FX::hasRGBWBus(void) const { return false; } -bool WS2812FX::hasCCTBus(void) const { +bool WS2812FX::hasCCTBus() const { if (cctFromRgb && !correctWB) return false; for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index d0e32b2116..5b948b9c41 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -4,6 +4,18 @@ #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "driver/ledc.h" +#include "soc/ledc_struct.h" + #if !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)) + #define LEDC_MUTEX_LOCK() do {} while (xSemaphoreTake(_ledc_sys_lock, portMAX_DELAY) != pdPASS) + #define LEDC_MUTEX_UNLOCK() xSemaphoreGive(_ledc_sys_lock) + extern xSemaphoreHandle _ledc_sys_lock; + #else + #define LEDC_MUTEX_LOCK() + #define LEDC_MUTEX_UNLOCK() + #endif +#endif #include "const.h" #include "pin_manager.h" #include "bus_wrapper.h" @@ -48,38 +60,46 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte #define W(c) (byte((c) >> 24)) -void ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) { - if (_count >= WLED_MAX_COLOR_ORDER_MAPPINGS) { - return; - } - if (len == 0) { - return; - } - // upper nibble contains W swap information - if ((colorOrder & 0x0F) > COL_ORDER_MAX) { - return; - } - _mappings[_count].start = start; - _mappings[_count].len = len; - _mappings[_count].colorOrder = colorOrder; - _count++; +bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) { + if (count() >= WLED_MAX_COLOR_ORDER_MAPPINGS || len == 0 || (colorOrder & 0x0F) > COL_ORDER_MAX) return false; // upper nibble contains W swap information + _mappings.push_back({start,len,colorOrder}); + return true; } uint8_t IRAM_ATTR ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const { - if (_count > 0) { - // upper nibble contains W swap information - // when ColorOrderMap's upper nibble contains value >0 then swap information is used from it, otherwise global swap is used - for (unsigned i = 0; i < _count; i++) { - if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) { - return _mappings[i].colorOrder | ((_mappings[i].colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0)); - } + // upper nibble contains W swap information + // when ColorOrderMap's upper nibble contains value >0 then swap information is used from it, otherwise global swap is used + for (unsigned i = 0; i < count(); i++) { + if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) { + return _mappings[i].colorOrder | ((_mappings[i].colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0)); } } return defaultColorOrder; } -uint32_t Bus::autoWhiteCalc(uint32_t c) { +void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { + unsigned cct = 0; //0 - full warm white, 255 - full cold white + unsigned w = W(c); + + if (_cct > -1) { // using RGB? + if (_cct >= 1900) cct = (_cct - 1900) >> 5; // convert K in relative format + else if (_cct < 256) cct = _cct; // already relative + } else { + cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format + } + + //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) + if (cct < _cctBlend) ww = 255; + else ww = ((255-cct) * 255) / (255 - _cctBlend); + if ((255-cct) < _cctBlend) cw = 255; + else cw = (cct * 255) / (255 - _cctBlend); + + ww = (w * ww) / 255; //brightness scaling + cw = (w * cw) / 255; +} + +uint32_t Bus::autoWhiteCalc(uint32_t c) const { unsigned aWM = _autoWhiteMode; if (_gAWM < AW_GLOBAL_DISABLED) aWM = _gAWM; if (aWM == RGBW_MODE_MANUAL_ONLY) return c; @@ -95,7 +115,7 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) { return RGBW32(r, g, b, w); } -uint8_t *Bus::allocData(size_t size) { +uint8_t *Bus::allocateData(size_t size) { if (_data) free(_data); // should not happen, but for safety return _data = (uint8_t *)(size>0 ? calloc(size, sizeof(uint8_t)) : nullptr); } @@ -109,12 +129,12 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) , _milliAmpsMax(bc.milliAmpsMax) , _colorOrderMap(com) { - if (!IS_DIGITAL(bc.type) || !bc.count) return; - if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; + if (!isDigital(bc.type) || !bc.count) return; + if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; _frequencykHz = 0U; _pins[0] = bc.pins[0]; - if (IS_2PIN(bc.type)) { - if (!pinManager.allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { + if (is2Pin(bc.type)) { + if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { cleanup(); return; } @@ -123,13 +143,16 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) } _iType = PolyBus::getI(bc.type, _pins, nr); if (_iType == I_NONE) return; - if (bc.doubleBuffer && !allocData(bc.count * Bus::getNumberOfChannels(bc.type))) return; + _hasRgb = hasRGB(bc.type); + _hasWhite = hasWhite(bc.type); + _hasCCT = hasCCT(bc.type); + if (bc.doubleBuffer && !allocateData(bc.count * Bus::getNumberOfChannels(bc.type))) return; //_buffering = bc.doubleBuffer; uint16_t lenToCreate = bc.count; if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr, _frequencykHz); _valid = (_busPtr != nullptr); - DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], IS_2PIN(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); + DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); } //fine tune power estimation constants for your setup @@ -263,7 +286,7 @@ void BusDigital::show() { if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, _bri); } -bool BusDigital::canShow() { +bool BusDigital::canShow() const { if (!_valid) return true; return PolyBus::canShow(_busPtr, _iType); } @@ -319,7 +342,7 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { } // returns original color if global buffering is enabled, else returns lossly restored color from bus -uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) { +uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const { if (!_valid) return 0; if (_data) { size_t offset = pix * getNumberOfChannels(); @@ -349,9 +372,9 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) { } } -uint8_t BusDigital::getPins(uint8_t* pinArray) { - unsigned numPins = IS_2PIN(_type) ? 2 : 1; - for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; +uint8_t BusDigital::getPins(uint8_t* pinArray) const { + unsigned numPins = is2Pin(_type) + 1; + if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; return numPins; } @@ -361,6 +384,32 @@ void BusDigital::setColorOrder(uint8_t colorOrder) { _colorOrder = colorOrder; } +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +std::vector BusDigital::getLEDTypes() { + return { + {TYPE_WS2812_RGB, "D", PSTR("WS281x")}, + {TYPE_SK6812_RGBW, "D", PSTR("SK6812/WS2814 RGBW")}, + {TYPE_TM1814, "D", PSTR("TM1814")}, + {TYPE_WS2811_400KHZ, "D", PSTR("400kHz")}, + {TYPE_TM1829, "D", PSTR("TM1829")}, + {TYPE_UCS8903, "D", PSTR("UCS8903")}, + {TYPE_APA106, "D", PSTR("APA106/PL9823")}, + {TYPE_TM1914, "D", PSTR("TM1914")}, + {TYPE_FW1906, "D", PSTR("FW1906 GRBCW")}, + {TYPE_UCS8904, "D", PSTR("UCS8904 RGBW")}, + {TYPE_WS2805, "D", PSTR("WS2805 RGBCW")}, + {TYPE_SM16825, "D", PSTR("SM16825 RGBCW")}, + {TYPE_WS2812_1CH_X3, "D", PSTR("WS2811 White")}, + //{TYPE_WS2812_2CH_X3, "D", PSTR("WS2811 CCT")}, // not implemented + //{TYPE_WS2812_WWA, "D", PSTR("WS2811 WWA")}, // not implemented + {TYPE_WS2801, "2P", PSTR("WS2801")}, + {TYPE_APA102, "2P", PSTR("APA102")}, + {TYPE_LPD8806, "2P", PSTR("LPD8806")}, + {TYPE_LPD6803, "2P", PSTR("LPD6803")}, + {TYPE_P9813, "2P", PSTR("PP9813")}, + }; +} + void BusDigital::reinit() { if (!_valid) return; PolyBus::begin(_busPtr, _iType, _pins); @@ -373,8 +422,8 @@ void BusDigital::cleanup() { _valid = false; _busPtr = nullptr; if (_data != nullptr) freeData(); - pinManager.deallocatePin(_pins[1], PinOwner::BusDigital); - pinManager.deallocatePin(_pins[0], PinOwner::BusDigital); + PinManager::deallocatePin(_pins[1], PinOwner::BusDigital); + PinManager::deallocatePin(_pins[0], PinOwner::BusDigital); } @@ -399,42 +448,54 @@ void BusDigital::cleanup() { #define MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM #else // ESP32: 20 bit (but in reality we would never go beyond 16 bit as the frequency would be to low) - #define MAX_BIT_WIDTH 20 + #define MAX_BIT_WIDTH 14 #endif #endif BusPwm::BusPwm(BusConfig &bc) -: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed) +: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering { - if (!IS_PWM(bc.type)) return; - unsigned numPins = NUM_PWM_PINS(bc.type); + if (!isPWM(bc.type)) return; + unsigned numPins = numPWMPins(bc.type); + [[maybe_unused]] const bool dithering = _needsRefresh; _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; // duty cycle resolution (_depth) can be extracted from this formula: CLOCK_FREQUENCY > _frequency * 2^_depth for (_depth = MAX_BIT_WIDTH; _depth > 8; _depth--) if (((CLOCK_FREQUENCY/_frequency) >> _depth) > 0) break; + managed_pin_type pins[numPins]; + for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true}; + if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; + #ifdef ESP8266 analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); #else - _ledcStart = pinManager.allocateLedc(numPins); + // for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer + _ledcStart = PinManager::allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels - deallocatePins(); return; + PinManager::deallocateMultiplePins(pins, numPins, PinOwner::BusPwm); + return; } + // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) + if (dithering) _depth = 12; // fixed 8 bit depth PWM with 4 bit dithering (ESP8266 has no hardware to support dithering) #endif for (unsigned i = 0; i < numPins; i++) { - uint8_t currentPin = bc.pins[i]; - if (!pinManager.allocatePin(currentPin, true, PinOwner::BusPwm)) { - deallocatePins(); return; - } - _pins[i] = currentPin; //store only after allocatePin() succeeds + _pins[i] = bc.pins[i]; // store only after allocateMultiplePins() succeeded #ifdef ESP8266 pinMode(_pins[i], OUTPUT); #else - ledcSetup(_ledcStart + i, _frequency, _depth); - ledcAttachPin(_pins[i], _ledcStart + i); + unsigned channel = _ledcStart + i; + ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit + ledcAttachPin(_pins[i], channel); + // LEDC timer reset credit @dedehai + uint8_t group = (channel / 8), timer = ((channel / 2) % 4); // same fromula as in ledcSetup() + ledc_timer_rst((ledc_mode_t)group, (ledc_timer_t)timer); // reset timer so all timers are almost in sync (for phase shift) #endif } + _hasRgb = hasRGB(bc.type); + _hasWhite = hasWhite(bc.type); + _hasCCT = hasCCT(bc.type); _data = _pwmdata; // avoid malloc() and use stack _valid = true; DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]); @@ -477,7 +538,7 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { } //does no index check -uint32_t BusPwm::getPixelColor(uint16_t pix) { +uint32_t BusPwm::getPixelColor(uint16_t pix) const { if (!_valid) return 0; // TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented) switch (_type) { @@ -497,79 +558,98 @@ uint32_t BusPwm::getPixelColor(uint16_t pix) { return RGBW32(_data[0], _data[0], _data[0], _data[0]); } -#ifndef ESP8266 -static const uint16_t cieLUT[256] = { - 0, 2, 4, 5, 7, 9, 11, 13, 15, 16, - 18, 20, 22, 24, 26, 27, 29, 31, 33, 35, - 34, 36, 37, 39, 41, 43, 45, 47, 49, 52, - 54, 56, 59, 61, 64, 67, 69, 72, 75, 78, - 81, 84, 87, 90, 94, 97, 100, 104, 108, 111, - 115, 119, 123, 127, 131, 136, 140, 144, 149, 154, - 158, 163, 168, 173, 178, 183, 189, 194, 200, 205, - 211, 217, 223, 229, 235, 241, 247, 254, 261, 267, - 274, 281, 288, 295, 302, 310, 317, 325, 333, 341, - 349, 357, 365, 373, 382, 391, 399, 408, 417, 426, - 436, 445, 455, 464, 474, 484, 494, 505, 515, 526, - 536, 547, 558, 569, 580, 592, 603, 615, 627, 639, - 651, 663, 676, 689, 701, 714, 727, 741, 754, 768, - 781, 795, 809, 824, 838, 853, 867, 882, 897, 913, - 928, 943, 959, 975, 991, 1008, 1024, 1041, 1058, 1075, - 1092, 1109, 1127, 1144, 1162, 1180, 1199, 1217, 1236, 1255, - 1274, 1293, 1312, 1332, 1352, 1372, 1392, 1412, 1433, 1454, - 1475, 1496, 1517, 1539, 1561, 1583, 1605, 1628, 1650, 1673, - 1696, 1719, 1743, 1767, 1791, 1815, 1839, 1864, 1888, 1913, - 1939, 1964, 1990, 2016, 2042, 2068, 2095, 2121, 2148, 2176, - 2203, 2231, 2259, 2287, 2315, 2344, 2373, 2402, 2431, 2461, - 2491, 2521, 2551, 2581, 2612, 2643, 2675, 2706, 2738, 2770, - 2802, 2835, 2867, 2900, 2934, 2967, 3001, 3035, 3069, 3104, - 3138, 3174, 3209, 3244, 3280, 3316, 3353, 3389, 3426, 3463, - 3501, 3539, 3576, 3615, 3653, 3692, 3731, 3770, 3810, 3850, - 3890, 3930, 3971, 4012, 4053, 4095 -}; -#endif - void BusPwm::show() { if (!_valid) return; - unsigned numPins = NUM_PWM_PINS(_type); - unsigned maxBri = (1<<_depth) - 1; - #ifdef ESP8266 - unsigned pwmBri = (unsigned)(roundf(powf((float)_bri / 255.0f, 1.7f) * (float)maxBri)); // using gamma 1.7 to extrapolate PWM duty cycle - #else - unsigned pwmBri = cieLUT[_bri] >> (12 - _depth); // use CIE LUT - #endif + // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) + // https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) + const bool dithering = _needsRefresh; // avoid working with bitfield + const unsigned numPins = getPins(); + const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8) + [[maybe_unused]] const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits) + + // use CIE brightness formula (cubic) to fit (or approximate linearity of) human eye perceived brightness + // the formula is based on 12 bit resolution as there is no need for greater precision + // see: https://en.wikipedia.org/wiki/Lightness + unsigned pwmBri = (unsigned)_bri * 100; // enlarge to use integer math for linear response + if (pwmBri < 2040) { + // linear response for values [0-20] + pwmBri = ((pwmBri << 12) + 115043) / 230087; //adding '0.5' before division for correct rounding + } else { + // cubic response for values [21-255] + pwmBri += 4080; + float temp = (float)pwmBri / 29580.0f; + temp = temp * temp * temp * (float)maxBri; + pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] + } + + [[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri) + // we will be phase shifting every channel by previous pulse length (plus dead time if required) + // phase shifting is only mandatory when using H-bridge to drive reverse-polarity PWM CCT (2 wire) LED type + // CCT additive blending must be 0 (WW & CW will not overlap) otherwise signals *will* overlap + // for all other cases it will just try to "spread" the load on PSU + // Phase shifting requires that LEDC timers are synchronised (see setup()). For PWM CCT (and H-bridge) it is + // also mandatory that both channels use the same timer (pinManager takes care of that). for (unsigned i = 0; i < numPins; i++) { - unsigned scaled = (_data[i] * pwmBri) / 255; - if (_reversed) scaled = maxBri - scaled; + unsigned duty = (_data[i] * pwmBri) / 255; #ifdef ESP8266 - analogWrite(_pins[i], scaled); + if (_reversed) duty = maxBri - duty; + analogWrite(_pins[i], duty); #else - ledcWrite(_ledcStart + i, scaled); + int deadTime = 0; + if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) { + // add dead time between signals (when using dithering, two full 8bit pulses are required) + deadTime = (1+dithering) << bitShift; + // we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap + if (_bri >= 254 && duty >= maxBri / 2 && duty < maxBri) duty -= deadTime << 1; // shorten duty of larger signal except if full on + if (_reversed) deadTime = -deadTime; // need to invert dead time to make phaseshift go the opposite way so low signals dont overlap + } + if (_reversed) duty = maxBri - duty; + unsigned channel = _ledcStart + i; + unsigned gr = channel/8; // high/low speed group + unsigned ch = channel%8; // group channel + // directly write to LEDC struct as there is no HAL exposed function for dithering + // duty has 20 bit resolution with 4 fractional bits (24 bits in total) + LEDC.channel_group[gr].channel[ch].duty.duty = duty << ((!dithering)*4); // lowest 4 bits are used for dithering, shift by 4 bits if not using dithering + LEDC.channel_group[gr].channel[ch].hpoint.hpoint = hPoint >> bitShift; // hPoint is at _depth resolution (needs shifting if dithering) + ledc_update_duty((ledc_mode_t)gr, (ledc_channel_t)ch); + hPoint += duty + deadTime; // offset to cascade the signals + if (hPoint >= maxBri) hPoint = 0; // offset it out of bounds, reset #endif } } -uint8_t BusPwm::getPins(uint8_t* pinArray) { +uint8_t BusPwm::getPins(uint8_t* pinArray) const { if (!_valid) return 0; - unsigned numPins = NUM_PWM_PINS(_type); - for (unsigned i = 0; i < numPins; i++) { - pinArray[i] = _pins[i]; - } + unsigned numPins = numPWMPins(_type); + if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; return numPins; } +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +std::vector BusPwm::getLEDTypes() { + return { + {TYPE_ANALOG_1CH, "A", PSTR("PWM White")}, + {TYPE_ANALOG_2CH, "AA", PSTR("PWM CCT")}, + {TYPE_ANALOG_3CH, "AAA", PSTR("PWM RGB")}, + {TYPE_ANALOG_4CH, "AAAA", PSTR("PWM RGBW")}, + {TYPE_ANALOG_5CH, "AAAAA", PSTR("PWM RGB+CCT")}, + //{TYPE_ANALOG_6CH, "AAAAAA", PSTR("PWM RGB+DCCT")}, // unimplementable ATM + }; +} + void BusPwm::deallocatePins() { - unsigned numPins = NUM_PWM_PINS(_type); + unsigned numPins = getPins(); for (unsigned i = 0; i < numPins; i++) { - pinManager.deallocatePin(_pins[i], PinOwner::BusPwm); - if (!pinManager.isPinOk(_pins[i])) continue; + PinManager::deallocatePin(_pins[i], PinOwner::BusPwm); + if (!PinManager::isPinOk(_pins[i])) continue; #ifdef ESP8266 digitalWrite(_pins[i], LOW); //turn off PWM interrupt #else - if (_ledcStart < 16) ledcDetachPin(_pins[i]); + if (_ledcStart < WLED_MAX_ANALOG_CHANNELS) ledcDetachPin(_pins[i]); #endif } #ifdef ARDUINO_ARCH_ESP32 - pinManager.deallocateLedc(_ledcStart, numPins); + PinManager::deallocateLedc(_ledcStart, numPins); #endif } @@ -578,14 +658,17 @@ BusOnOff::BusOnOff(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed) , _onoffdata(0) { - if (bc.type != TYPE_ONOFF) return; + if (!Bus::isOnOff(bc.type)) return; uint8_t currentPin = bc.pins[0]; - if (!pinManager.allocatePin(currentPin, true, PinOwner::BusOnOff)) { + if (!PinManager::allocatePin(currentPin, true, PinOwner::BusOnOff)) { return; } _pin = currentPin; //store only after allocatePin() succeeds pinMode(_pin, OUTPUT); + _hasRgb = false; + _hasWhite = false; + _hasCCT = false; _data = &_onoffdata; // avoid malloc() and use stack _valid = true; DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin); @@ -601,7 +684,7 @@ void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { _data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; } -uint32_t BusOnOff::getPixelColor(uint16_t pix) { +uint32_t BusOnOff::getPixelColor(uint16_t pix) const { if (!_valid) return 0; return RGBW32(_data[0], _data[0], _data[0], _data[0]); } @@ -611,12 +694,18 @@ void BusOnOff::show() { digitalWrite(_pin, _reversed ? !(bool)_data[0] : (bool)_data[0]); } -uint8_t BusOnOff::getPins(uint8_t* pinArray) { +uint8_t BusOnOff::getPins(uint8_t* pinArray) const { if (!_valid) return 0; - pinArray[0] = _pin; + if (pinArray) pinArray[0] = _pin; return 1; } +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +std::vector BusOnOff::getLEDTypes() { + return { + {TYPE_ONOFF, "", PSTR("On/Off")}, + }; +} BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, bc.count) @@ -624,59 +713,71 @@ BusNetwork::BusNetwork(BusConfig &bc) { switch (bc.type) { case TYPE_NET_ARTNET_RGB: - _rgbw = false; _UDPtype = 2; break; case TYPE_NET_ARTNET_RGBW: - _rgbw = true; _UDPtype = 2; break; case TYPE_NET_E131_RGB: - _rgbw = false; _UDPtype = 1; break; default: // TYPE_NET_DDP_RGB / TYPE_NET_DDP_RGBW - _rgbw = bc.type == TYPE_NET_DDP_RGBW; _UDPtype = 0; break; } - _UDPchannels = _rgbw ? 4 : 3; + _hasRgb = hasRGB(bc.type); + _hasWhite = hasWhite(bc.type); + _hasCCT = false; + _UDPchannels = _hasWhite + 3; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); - _valid = (allocData(_len * _UDPchannels) != nullptr); + _valid = (allocateData(_len * _UDPchannels) != nullptr); DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]); } void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; - if (_rgbw) c = autoWhiteCalc(c); + if (_hasWhite) c = autoWhiteCalc(c); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT unsigned offset = pix * _UDPchannels; _data[offset] = R(c); _data[offset+1] = G(c); _data[offset+2] = B(c); - if (_rgbw) _data[offset+3] = W(c); + if (_hasWhite) _data[offset+3] = W(c); } -uint32_t BusNetwork::getPixelColor(uint16_t pix) { +uint32_t BusNetwork::getPixelColor(uint16_t pix) const { if (!_valid || pix >= _len) return 0; unsigned offset = pix * _UDPchannels; - return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (_rgbw ? _data[offset+3] : 0)); + return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0)); } void BusNetwork::show() { if (!_valid || !canShow()) return; _broadcastLock = true; - realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw); + realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, hasWhite()); _broadcastLock = false; } -uint8_t BusNetwork::getPins(uint8_t* pinArray) { - for (unsigned i = 0; i < 4; i++) { - pinArray[i] = _client[i]; - } +uint8_t BusNetwork::getPins(uint8_t* pinArray) const { + if (pinArray) for (unsigned i = 0; i < 4; i++) pinArray[i] = _client[i]; return 4; } +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +std::vector BusNetwork::getLEDTypes() { + return { + {TYPE_NET_DDP_RGB, "N", PSTR("DDP RGB (network)")}, // should be "NNNN" to determine 4 "pin" fields + {TYPE_NET_ARTNET_RGB, "N", PSTR("Art-Net RGB (network)")}, + {TYPE_NET_DDP_RGBW, "N", PSTR("DDP RGBW (network)")}, + {TYPE_NET_ARTNET_RGBW, "N", PSTR("Art-Net RGBW (network)")}, + // hypothetical extensions + //{TYPE_VIRTUAL_I2C_W, "V", PSTR("I2C White (virtual)")}, // allows setting I2C address in _pin[0] + //{TYPE_VIRTUAL_I2C_CCT, "V", PSTR("I2C CCT (virtual)")}, // allows setting I2C address in _pin[0] + //{TYPE_VIRTUAL_I2C_RGB, "VVV", PSTR("I2C RGB (virtual)")}, // allows setting I2C address in _pin[0] and 2 additional values in _pin[1] & _pin[2] + //{TYPE_USERMOD, "VVVVV", PSTR("Usermod (virtual)")}, // 5 data fields (see https://github.com/Aircoookie/WLED/pull/4123) + }; +} + void BusNetwork::cleanup() { _type = I_NONE; _valid = false; @@ -686,13 +787,13 @@ void BusNetwork::cleanup() { //utility to get the approx. memory usage of a given BusConfig uint32_t BusManager::memUsage(BusConfig &bc) { - if (bc.type == TYPE_ONOFF || IS_PWM(bc.type)) return 5; + if (Bus::isOnOff(bc.type) || Bus::isPWM(bc.type)) return OUTPUT_MAX_PINS; unsigned len = bc.count + bc.skipAmount; unsigned channels = Bus::getNumberOfChannels(bc.type); unsigned multiplier = 1; - if (IS_DIGITAL(bc.type)) { // digital types - if (IS_16BIT(bc.type)) len *= 2; // 16-bit LEDs + if (Bus::isDigital(bc.type)) { // digital types + if (Bus::is16bit(bc.type)) len *= 2; // 16-bit LEDs #ifdef ESP8266 if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem multiplier = 5; @@ -712,11 +813,11 @@ uint32_t BusManager::memUsage(unsigned maxChannels, unsigned maxCount, unsigned int BusManager::add(BusConfig &bc) { if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; - if (IS_VIRTUAL(bc.type)) { + if (Bus::isVirtual(bc.type)) { busses[numBusses] = new BusNetwork(bc); - } else if (IS_DIGITAL(bc.type)) { + } else if (Bus::isDigital(bc.type)) { busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap); - } else if (bc.type == TYPE_ONOFF) { + } else if (Bus::isOnOff(bc.type)) { busses[numBusses] = new BusOnOff(bc); } else { busses[numBusses] = new BusPwm(bc); @@ -724,7 +825,32 @@ int BusManager::add(BusConfig &bc) { return numBusses++; } -void BusManager::useParallelOutput(void) { +// credit @willmmiles +static String LEDTypesToJson(const std::vector& types) { + String json; + for (const auto &type : types) { + // capabilities follows similar pattern as JSON API + int capabilities = Bus::hasRGB(type.id) | Bus::hasWhite(type.id)<<1 | Bus::hasCCT(type.id)<<2 | Bus::is16bit(type.id)<<4 | Bus::mustRefresh(type.id)<<5; + char str[256]; + sprintf_P(str, PSTR("{i:%d,c:%d,t:\"%s\",n:\"%s\"},"), type.id, capabilities, type.type, type.name); + json += str; + } + return json; +} + +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +String BusManager::getLEDTypesJSONString() { + String json = "["; + json += LEDTypesToJson(BusDigital::getLEDTypes()); + json += LEDTypesToJson(BusOnOff::getLEDTypes()); + json += LEDTypesToJson(BusPwm::getLEDTypes()); + json += LEDTypesToJson(BusNetwork::getLEDTypes()); + //json += LEDTypesToJson(BusVirtual::getLEDTypes()); + json.setCharAt(json.length()-1, ']'); // replace last comma with bracket + return json; +} + +void BusManager::useParallelOutput() { _parallelOutputs = 8; // hardcoded since we use NPB I2S x8 methods PolyBus::setParallelI2S1Output(); } @@ -762,7 +888,7 @@ void BusManager::esp32RMTInvertIdle() { if (u >= _parallelOutputs + 8) return; // only 8 RMT channels rmt = u - _parallelOutputs; #endif - if (busses[u]->getLength()==0 || !IS_DIGITAL(busses[u]->getType()) || IS_2PIN(busses[u]->getType())) continue; + if (busses[u]->getLength()==0 || !busses[u]->isDigital() || busses[u]->is2Pin()) continue; //assumes that bus number to rmt channel mapping stays 1:1 rmt_channel_t ch = static_cast(rmt); rmt_idle_level_t lvl; @@ -778,10 +904,10 @@ void BusManager::esp32RMTInvertIdle() { void BusManager::on() { #ifdef ESP8266 //Fix for turning off onboard LED breaking bus - if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { + if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { for (unsigned i = 0; i < numBusses; i++) { uint8_t pins[2] = {255,255}; - if (IS_DIGITAL(busses[i]->getType()) && busses[i]->getPins(pins)) { + if (busses[i]->isDigital() && busses[i]->getPins(pins)) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { BusDigital *bus = static_cast(busses[i]); bus->reinit(); @@ -800,7 +926,7 @@ void BusManager::off() { #ifdef ESP8266 // turn off built-in LED if strip is turned off // this will break digital bus so will need to be re-initialised on On - if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { + if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { for (unsigned i = 0; i < numBusses; i++) if (busses[i]->isOffRefreshRequired()) return; pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); @@ -852,7 +978,7 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { uint32_t BusManager::getPixelColor(uint16_t pix) { for (unsigned i = 0; i < numBusses; i++) { unsigned bstart = busses[i]->getStart(); - if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue; + if (!busses[i]->containsPixel(pix)) continue; return busses[i]->getPixelColor(pix - bstart); } return 0; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 5e516d2e16..e96b9de714 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -6,6 +6,7 @@ */ #include "const.h" +#include //colors.cpp uint16_t approximateKelvinFromRGB(uint32_t rgb); @@ -21,89 +22,45 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb); #define IC_INDEX_WS2812_2CH_3X(i) ((i)*2/3) #define WS2812_2CH_3X_SPANS_2_ICS(i) ((i)&0x01) // every other LED zone is on two different ICs -//temporary struct for passing bus configuration to bus -struct BusConfig { - uint8_t type; - uint16_t count; - uint16_t start; - uint8_t colorOrder; - bool reversed; - uint8_t skipAmount; - bool refreshReq; - uint8_t autoWhite; - uint8_t pins[5] = {255, 255, 255, 255, 255}; - uint16_t frequency; - bool doubleBuffer; - uint8_t milliAmpsPerLed; - uint16_t milliAmpsMax; - - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) - : count(len) - , start(pstart) - , colorOrder(pcolorOrder) - , reversed(rev) - , skipAmount(skip) - , autoWhite(aw) - , frequency(clock_kHz) - , doubleBuffer(dblBfr) - , milliAmpsPerLed(maPerLed) - , milliAmpsMax(maMax) - { - refreshReq = (bool) GET_BIT(busType,7); - type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) - size_t nPins = 1; - if (IS_VIRTUAL(type)) nPins = 4; //virtual network bus. 4 "pins" store IP address - else if (IS_2PIN(type)) nPins = 2; - else if (IS_PWM(type)) nPins = NUM_PWM_PINS(type); - for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; - } - - //validates start and length and extends total if needed - bool adjustBounds(uint16_t& total) { - if (!count) count = 1; - if (count > MAX_LEDS_PER_BUS) count = MAX_LEDS_PER_BUS; - if (start >= MAX_LEDS) return false; - //limit length of strip if it would exceed total permissible LEDs - if (start + count > MAX_LEDS) count = MAX_LEDS - start; - //extend total count accordingly - if (start + count > total) total = start + count; - return true; - } -}; - +struct BusConfig; // forward declaration // Defines an LED Strip and its color ordering. -struct ColorOrderMapEntry { +typedef struct { uint16_t start; uint16_t len; uint8_t colorOrder; -}; +} ColorOrderMapEntry; struct ColorOrderMap { - void add(uint16_t start, uint16_t len, uint8_t colorOrder); + bool add(uint16_t start, uint16_t len, uint8_t colorOrder); - uint8_t count() const { return _count; } + inline uint8_t count() const { return _mappings.size(); } + inline void reserve(size_t num) { _mappings.reserve(num); } void reset() { - _count = 0; - memset(_mappings, 0, sizeof(_mappings)); + _mappings.clear(); + _mappings.shrink_to_fit(); } const ColorOrderMapEntry* get(uint8_t n) const { - if (n > _count) { - return nullptr; - } + if (n >= count()) return nullptr; return &(_mappings[n]); } - uint8_t getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const; + [[gnu::hot]] uint8_t getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const; private: - uint8_t _count; - ColorOrderMapEntry _mappings[WLED_MAX_COLOR_ORDER_MAPPINGS]; + std::vector _mappings; }; +typedef struct { + uint8_t id; + const char *type; + const char *name; +} LEDType; + + //parent class of BusDigital, BusPwm, and BusNetwork class Bus { public: @@ -123,99 +80,99 @@ class Bus { virtual ~Bus() {} //throw the bus under the bus virtual void show() = 0; - virtual bool canShow() { return true; } - virtual void setStatusPixel(uint32_t c) {} + virtual bool canShow() const { return true; } + virtual void setStatusPixel(uint32_t c) {} virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; - virtual uint32_t getPixelColor(uint16_t pix) { return 0; } - virtual void setBrightness(uint8_t b) { _bri = b; }; - virtual uint8_t getPins(uint8_t* pinArray) { return 0; } - virtual uint16_t getLength() { return isOk() ? _len : 0; } - virtual void setColorOrder(uint8_t co) {} - virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } - virtual uint8_t skippedLeds() { return 0; } - virtual uint16_t getFrequency() { return 0U; } - virtual uint16_t getLEDCurrent() { return 0; } - virtual uint16_t getUsedCurrent() { return 0; } - virtual uint16_t getMaxCurrent() { return 0; } - virtual uint8_t getNumberOfChannels() { return hasWhite(_type) + 3*hasRGB(_type) + hasCCT(_type); } - static inline uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } - inline void setReversed(bool reversed) { _reversed = reversed; } - inline uint16_t getStart() { return _start; } - inline void setStart(uint16_t start) { _start = start; } - inline uint8_t getType() { return _type; } - inline bool isOk() { return _valid; } - inline bool isReversed() { return _reversed; } - inline bool isOffRefreshRequired() { return _needsRefresh; } - bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; } - - virtual bool hasRGB(void) { return Bus::hasRGB(_type); } - static bool hasRGB(uint8_t type) { - if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF) return false; - return true; - } - virtual bool hasWhite(void) { return Bus::hasWhite(_type); } - static bool hasWhite(uint8_t type) { - if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || - type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || - type == TYPE_FW1906 || type == TYPE_WS2805 || type == TYPE_SM16825) return true; // digital types with white channel - if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; // analog types with white channel - if (type == TYPE_NET_DDP_RGBW || type == TYPE_NET_ARTNET_RGBW) return true; // network types with white channel - return false; + virtual void setBrightness(uint8_t b) { _bri = b; }; + virtual void setColorOrder(uint8_t co) {} + virtual uint32_t getPixelColor(uint16_t pix) const { return 0; } + virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; } + virtual uint16_t getLength() const { return isOk() ? _len : 0; } + virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } + virtual uint8_t skippedLeds() const { return 0; } + virtual uint16_t getFrequency() const { return 0U; } + virtual uint16_t getLEDCurrent() const { return 0; } + virtual uint16_t getUsedCurrent() const { return 0; } + virtual uint16_t getMaxCurrent() const { return 0; } + + inline bool hasRGB() const { return _hasRgb; } + inline bool hasWhite() const { return _hasWhite; } + inline bool hasCCT() const { return _hasCCT; } + inline bool isDigital() const { return isDigital(_type); } + inline bool is2Pin() const { return is2Pin(_type); } + inline bool isOnOff() const { return isOnOff(_type); } + inline bool isPWM() const { return isPWM(_type); } + inline bool isVirtual() const { return isVirtual(_type); } + inline bool is16bit() const { return is16bit(_type); } + inline bool mustRefresh() const { return mustRefresh(_type); } + inline void setReversed(bool reversed) { _reversed = reversed; } + inline void setStart(uint16_t start) { _start = start; } + inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } + inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } + inline uint8_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } + inline uint16_t getStart() const { return _start; } + inline uint8_t getType() const { return _type; } + inline bool isOk() const { return _valid; } + inline bool isReversed() const { return _reversed; } + inline bool isOffRefreshRequired() const { return _needsRefresh; } + inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } + + static inline std::vector getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes + static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK + static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } + static constexpr bool hasRGB(uint8_t type) { + return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF); } - virtual bool hasCCT(void) { return Bus::hasCCT(_type); } - static bool hasCCT(uint8_t type) { - if (type == TYPE_WS2812_2CH_X3 || type == TYPE_WS2812_WWA || - type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || - type == TYPE_FW1906 || type == TYPE_WS2805 || - type == TYPE_SM16825) return true; - return false; + static constexpr bool hasWhite(uint8_t type) { + return (type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || + type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || + type == TYPE_FW1906 || type == TYPE_WS2805 || type == TYPE_SM16825 || // digital types with white channel + (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) || // analog types with white channel + type == TYPE_NET_DDP_RGBW || type == TYPE_NET_ARTNET_RGBW; // network types with white channel } - static inline int16_t getCCT() { return _cct; } - static void setCCT(int16_t cct) { - _cct = cct; + static constexpr bool hasCCT(uint8_t type) { + return type == TYPE_WS2812_2CH_X3 || type == TYPE_WS2812_WWA || + type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || + type == TYPE_FW1906 || type == TYPE_WS2805 || + type == TYPE_SM16825; } - static inline uint8_t getCCTBlend() { return _cctBlend; } - static void setCCTBlend(uint8_t b) { - if (b > 100) b = 100; - _cctBlend = (b * 127) / 100; + static constexpr bool isTypeValid(uint8_t type) { return (type > 15 && type < 128); } + static constexpr bool isDigital(uint8_t type) { return (type >= TYPE_DIGITAL_MIN && type <= TYPE_DIGITAL_MAX) || is2Pin(type); } + static constexpr bool is2Pin(uint8_t type) { return (type >= TYPE_2PIN_MIN && type <= TYPE_2PIN_MAX); } + static constexpr bool isOnOff(uint8_t type) { return (type == TYPE_ONOFF); } + static constexpr bool isPWM(uint8_t type) { return (type >= TYPE_ANALOG_MIN && type <= TYPE_ANALOG_MAX); } + static constexpr bool isVirtual(uint8_t type) { return (type >= TYPE_VIRTUAL_MIN && type <= TYPE_VIRTUAL_MAX); } + static constexpr bool is16bit(uint8_t type) { return type == TYPE_UCS8903 || type == TYPE_UCS8904 || type == TYPE_SM16825; } + static constexpr bool mustRefresh(uint8_t type) { return type == TYPE_TM1814; } + static constexpr int numPWMPins(uint8_t type) { return (type - 40); } + + static inline int16_t getCCT() { return _cct; } + static inline void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } + static inline uint8_t getGlobalAWMode() { return _gAWM; } + static inline void setCCT(int16_t cct) { _cct = cct; } + static inline uint8_t getCCTBlend() { return _cctBlend; } + static inline void setCCTBlend(uint8_t b) { + _cctBlend = (std::min((int)b,100) * 127) / 100; //compile-time limiter for hardware that can't power both white channels at max #ifdef WLED_MAX_CCT_BLEND if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; #endif } - static void calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { - uint8_t cct = 0; //0 - full warm white, 255 - full cold white - uint8_t w = byte(c >> 24); - - if (_cct > -1) { - if (_cct >= 1900) cct = (_cct - 1900) >> 5; - else if (_cct < 256) cct = _cct; - } else { - cct = (approximateKelvinFromRGB(c) - 1900) >> 5; - } - - //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) - if (cct < _cctBlend) ww = 255; - else ww = ((255-cct) * 255) / (255 - _cctBlend); - if ((255-cct) < _cctBlend) cw = 255; - else cw = (cct * 255) / (255 - _cctBlend); - - ww = (w * ww) / 255; //brightness scaling - cw = (w * cw) / 255; - } - inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } - inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; } - inline static void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } - inline static uint8_t getGlobalAWMode() { return _gAWM; } + static void calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw); protected: uint8_t _type; uint8_t _bri; uint16_t _start; uint16_t _len; - bool _reversed; - bool _valid; - bool _needsRefresh; + //struct { //using bitfield struct adds abour 250 bytes to binary size + bool _reversed;// : 1; + bool _valid;// : 1; + bool _needsRefresh;// : 1; + bool _hasRgb;// : 1; + bool _hasWhite;// : 1; + bool _hasCCT;// : 1; + //} __attribute__ ((packed)); uint8_t _autoWhiteMode; uint8_t *_data; // global Auto White Calculation override @@ -231,8 +188,8 @@ class Bus { // 127 - additive CCT blending (CCT 127 => 100% warm, 100% cold) static uint8_t _cctBlend; - uint32_t autoWhiteCalc(uint32_t c); - uint8_t *allocData(size_t size = 1); + uint32_t autoWhiteCalc(uint32_t c) const; + uint8_t *allocateData(size_t size = 1); void freeData() { if (_data != nullptr) free(_data); _data = nullptr; } }; @@ -243,23 +200,24 @@ class BusDigital : public Bus { ~BusDigital() { cleanup(); } void show() override; - bool canShow() override; + bool canShow() const override; void setBrightness(uint8_t b) override; void setStatusPixel(uint32_t c) override; - void setPixelColor(uint16_t pix, uint32_t c) override; + [[gnu::hot]] void setPixelColor(uint16_t pix, uint32_t c) override; void setColorOrder(uint8_t colorOrder) override; - uint32_t getPixelColor(uint16_t pix) override; - uint8_t getColorOrder() override { return _colorOrder; } - uint8_t getPins(uint8_t* pinArray) override; - uint8_t skippedLeds() override { return _skip; } - uint16_t getFrequency() override { return _frequencykHz; } - uint8_t estimateCurrentAndLimitBri(); - uint16_t getLEDCurrent() override { return _milliAmpsPerLed; } - uint16_t getUsedCurrent() override { return _milliAmpsTotal; } - uint16_t getMaxCurrent() override { return _milliAmpsMax; } + [[gnu::hot]] uint32_t getPixelColor(uint16_t pix) const override; + uint8_t getColorOrder() const override { return _colorOrder; } + uint8_t getPins(uint8_t* pinArray = nullptr) const override; + uint8_t skippedLeds() const override { return _skip; } + uint16_t getFrequency() const override { return _frequencykHz; } + uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } + uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } + uint16_t getMaxCurrent() const override { return _milliAmpsMax; } void reinit(); void cleanup(); + static std::vector getLEDTypes(); + private: uint8_t _skip; uint8_t _colorOrder; @@ -273,7 +231,7 @@ class BusDigital : public Bus { static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() - inline uint32_t restoreColorLossy(uint32_t c, uint8_t restoreBri) { + inline uint32_t restoreColorLossy(uint32_t c, uint8_t restoreBri) const { if (restoreBri < 255) { uint8_t* chan = (uint8_t*) &c; for (uint_fast8_t i=0; i<4; i++) { @@ -283,6 +241,8 @@ class BusDigital : public Bus { } return c; } + + uint8_t estimateCurrentAndLimitBri(); }; @@ -292,15 +252,17 @@ class BusPwm : public Bus { ~BusPwm() { cleanup(); } void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) override; //does no index check - uint8_t getPins(uint8_t* pinArray) override; - uint16_t getFrequency() override { return _frequency; } + uint32_t getPixelColor(uint16_t pix) const override; //does no index check + uint8_t getPins(uint8_t* pinArray = nullptr) const override; + uint16_t getFrequency() const override { return _frequency; } void show() override; void cleanup() { deallocatePins(); } + static std::vector getLEDTypes(); + private: - uint8_t _pins[5]; - uint8_t _pwmdata[5]; + uint8_t _pins[OUTPUT_MAX_PINS]; + uint8_t _pwmdata[OUTPUT_MAX_PINS]; #ifdef ARDUINO_ARCH_ESP32 uint8_t _ledcStart; #endif @@ -317,10 +279,12 @@ class BusOnOff : public Bus { ~BusOnOff() { cleanup(); } void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) override; - uint8_t getPins(uint8_t* pinArray) override; + uint32_t getPixelColor(uint16_t pix) const override; + uint8_t getPins(uint8_t* pinArray) const override; void show() override; - void cleanup() { pinManager.deallocatePin(_pin, PinOwner::BusOnOff); } + void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } + + static std::vector getLEDTypes(); private: uint8_t _pin; @@ -333,24 +297,71 @@ class BusNetwork : public Bus { BusNetwork(BusConfig &bc); ~BusNetwork() { cleanup(); } - bool hasRGB() override { return true; } - bool hasWhite() override { return _rgbw; } - bool canShow() override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out + bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) override; - uint8_t getPins(uint8_t* pinArray) override; + uint32_t getPixelColor(uint16_t pix) const override; + uint8_t getPins(uint8_t* pinArray = nullptr) const override; void show() override; void cleanup(); + static std::vector getLEDTypes(); + private: IPAddress _client; uint8_t _UDPtype; uint8_t _UDPchannels; - bool _rgbw; bool _broadcastLock; }; +//temporary struct for passing bus configuration to bus +struct BusConfig { + uint8_t type; + uint16_t count; + uint16_t start; + uint8_t colorOrder; + bool reversed; + uint8_t skipAmount; + bool refreshReq; + uint8_t autoWhite; + uint8_t pins[5] = {255, 255, 255, 255, 255}; + uint16_t frequency; + bool doubleBuffer; + uint8_t milliAmpsPerLed; + uint16_t milliAmpsMax; + + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) + : count(len) + , start(pstart) + , colorOrder(pcolorOrder) + , reversed(rev) + , skipAmount(skip) + , autoWhite(aw) + , frequency(clock_kHz) + , doubleBuffer(dblBfr) + , milliAmpsPerLed(maPerLed) + , milliAmpsMax(maMax) + { + refreshReq = (bool) GET_BIT(busType,7); + type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) + size_t nPins = Bus::getNumberOfPins(type); + for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; + } + + //validates start and length and extends total if needed + bool adjustBounds(uint16_t& total) { + if (!count) count = 1; + if (count > MAX_LEDS_PER_BUS) count = MAX_LEDS_PER_BUS; + if (start >= MAX_LEDS) return false; + //limit length of strip if it would exceed total permissible LEDs + if (start + count > MAX_LEDS) count = MAX_LEDS - start; + //extend total count accordingly + if (start + count > total) total = start + count; + return true; + } +}; + + class BusManager { public: BusManager() {}; @@ -358,27 +369,27 @@ class BusManager { //utility to get the approx. memory usage of a given BusConfig static uint32_t memUsage(BusConfig &bc); static uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1); - static uint16_t currentMilliamps(void) { return _milliAmpsUsed; } - static uint16_t ablMilliampsMax(void) { return _milliAmpsMax; } + static uint16_t currentMilliamps() { return _milliAmpsUsed; } + static uint16_t ablMilliampsMax() { return _milliAmpsMax; } static int add(BusConfig &bc); - static void useParallelOutput(void); // workaround for inaccessible PolyBus + static void useParallelOutput(); // workaround for inaccessible PolyBus //do not call this method from system context (network callback) static void removeAll(); - static void on(void); - static void off(void); + static void on(); + static void off(); static void show(); static bool canAllShow(); static void setStatusPixel(uint32_t c); - static void setPixelColor(uint16_t pix, uint32_t c); + [[gnu::hot]] static void setPixelColor(uint16_t pix, uint32_t c); static void setBrightness(uint8_t b); // for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K // WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT() static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); - static void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} + static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} static uint32_t getPixelColor(uint16_t pix); static inline int16_t getSegmentCCT() { return Bus::getCCT(); } @@ -386,10 +397,10 @@ class BusManager { //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) static uint16_t getTotalLength(); - static uint8_t getNumBusses() { return numBusses; } + static inline uint8_t getNumBusses() { return numBusses; } + static String getLEDTypesJSONString(); - static void updateColorOrderMap(const ColorOrderMap &com) { memcpy(&colorOrderMap, &com, sizeof(ColorOrderMap)); } - static const ColorOrderMap& getColorOrderMap() { return colorOrderMap; } + static inline ColorOrderMap& getColorOrderMap() { return colorOrderMap; } private: static uint8_t numBusses; @@ -400,11 +411,11 @@ class BusManager { static uint8_t _parallelOutputs; #ifdef ESP32_DATA_IDLE_HIGH - static void esp32RMTInvertIdle(); + static void esp32RMTInvertIdle() ; #endif static uint8_t getNumVirtualBusses() { int j = 0; - for (int i=0; igetType() >= TYPE_NET_DDP_RGB && busses[i]->getType() < 96) j++; + for (int i=0; iisVirtual()) j++; return j; } }; diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index ae39adc146..84c32f46bc 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -766,47 +766,47 @@ class PolyBus { #endif #ifdef ARDUINO_ARCH_ESP32 // RMT buses - case I_32_RN_NEO_3: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_NEO_4: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_400_3: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_TM1_4: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_TM2_3: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_UCS_3: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_UCS_4: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_APA106_3: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_FW6_5: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_2805_5: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_TM1914_3: (static_cast(busPtr))->CanShow(); break; - case I_32_RN_SM16825_5: (static_cast(busPtr))->CanShow(); break; + case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_UCS_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_UCS_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_APA106_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_2805_5: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_TM1914_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_RN_SM16825_5: return (static_cast(busPtr))->CanShow(); break; // I2S1 bus or paralell buses #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; - case I_32_I1_SM16825_5: if (useParallelI2S) (static_cast(busPtr))->CanShow(); else (static_cast(busPtr))->CanShow(); break; + case I_32_I1_NEO_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_NEO_4: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_400_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_TM1_4: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_TM2_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_UCS_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_UCS_4: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_APA106_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_FW6_5: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_2805_5: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_TM1914_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_SM16825_5: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; #endif // I2S0 bus #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_NEO_4: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_400_3: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM1_4: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_UCS_3: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_UCS_4: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_APA106_3: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_FW6_5: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_2805_5: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM1914_3: (static_cast(busPtr))->CanShow(); break; - case I_32_I0_SM16825_5: (static_cast(busPtr))->CanShow(); break; + case I_32_I0_NEO_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_NEO_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_400_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_UCS_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_UCS_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_APA106_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_FW6_5: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_2805_5: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_TM1914_3: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_SM16825_5: return (static_cast(busPtr))->CanShow(); break; #endif #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -1314,8 +1314,8 @@ class PolyBus { //gives back the internal type index (I_XX_XXX_X above) for the input static uint8_t getI(uint8_t busType, uint8_t* pins, uint8_t num = 0) { - if (!IS_DIGITAL(busType)) return I_NONE; - if (IS_2PIN(busType)) { //SPI LED chips + if (!Bus::isDigital(busType)) return I_NONE; + if (Bus::is2Pin(busType)) { //SPI LED chips bool isHSPI = false; #ifdef ESP8266 if (pins[0] == P_8266_HS_MOSI && pins[1] == P_8266_HS_CLK) isHSPI = true; diff --git a/wled00/button.cpp b/wled00/button.cpp index 23d7b8a90f..4d6f954f60 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -125,7 +125,7 @@ void handleSwitch(uint8_t b) { // isButtonPressed() handles inverted/noninverted logic if (buttonPressedBefore[b] != isButtonPressed(b)) { - DEBUG_PRINT(F("Switch: State changed ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Switch: State changed %u\n"), b); buttonPressedTime[b] = millis(); buttonPressedBefore[b] = !buttonPressedBefore[b]; } @@ -133,15 +133,15 @@ void handleSwitch(uint8_t b) if (buttonLongPressed[b] == buttonPressedBefore[b]) return; if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) - DEBUG_PRINT(F("Switch: Activating ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Switch: Activating %u\n"), b); if (!buttonPressedBefore[b]) { // on -> off - DEBUG_PRINT(F("Switch: On -> Off ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Switch: On -> Off (%u)\n"), b); if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET); else { //turn on if (!bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);} } } else { // off -> on - DEBUG_PRINT(F("Switch: Off -> On ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Switch: Off -> On (%u)\n"), b); if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET); else { //turn off if (bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);} @@ -173,7 +173,7 @@ void handleAnalog(uint8_t b) static float filteredReading[WLED_MAX_BUTTONS] = {0.0f}; unsigned rawReading; // raw value from analogRead, scaled to 12bit - DEBUG_PRINT(F("Analog: Reading button ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Analog: Reading button %u\n"), b); #ifdef ESP8266 rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit @@ -193,8 +193,8 @@ void handleAnalog(uint8_t b) // remove noise & reduce frequency of UI updates if (abs(int(aRead) - int(oldRead[b])) <= POT_SENSITIVITY) return; // no significant change in reading - DEBUG_PRINT(F("Analog: Raw = ")); DEBUG_PRINT(rawReading); - DEBUG_PRINT(F(" Filtered = ")); DEBUG_PRINTLN(aRead); + DEBUG_PRINTF_P(PSTR("Analog: Raw = %u\n"), rawReading); + DEBUG_PRINTF_P(PSTR(" Filtered = %u\n"), aRead); // Unomment the next lines if you still see flickering related to potentiometer // This waits until strip finishes updating (why: strip was not updating at the start of handleButton() but may have started during analogRead()?) @@ -207,7 +207,7 @@ void handleAnalog(uint8_t b) // if no macro for "short press" and "long press" is defined use brightness control if (!macroButton[b] && !macroLongPress[b]) { - DEBUG_PRINT(F("Analog: Action = ")); DEBUG_PRINTLN(macroDoublePress[b]); + DEBUG_PRINTF_P(PSTR("Analog: Action = %u\n"), macroDoublePress[b]); // if "double press" macro defines which option to change if (macroDoublePress[b] >= 250) { // global brightness @@ -215,6 +215,7 @@ void handleAnalog(uint8_t b) briLast = bri; bri = 0; } else { + if (bri == 0) strip.restartRuntime(); bri = aRead; } } else if (macroDoublePress[b] == 249) { @@ -267,7 +268,7 @@ void handleButton() if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue; #endif - if (usermods.handleButton(b)) continue; // did usermod handle buttons + if (UsermodManager::handleButton(b)) continue; // did usermod handle buttons if (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { // button is not a button but a potentiometer if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) { @@ -308,7 +309,7 @@ void handleButton() buttonLongPressed[b] = true; } - } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released + } else if (buttonPressedBefore[b]) { //released long dur = now - buttonPressedTime[b]; // released after rising-edge short press action diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 89076efab6..3f6cfbacb6 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -173,8 +173,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { for (JsonObject elm : ins) { unsigned type = elm["type"] | TYPE_WS2812_RGB; unsigned len = elm["len"] | DEFAULT_LED_COUNT; - if (!IS_DIGITAL(type)) continue; - if (!IS_2PIN(type)) { + if (!Bus::isDigital(type)) continue; + if (!Bus::is2Pin(type)) { digitalCount++; unsigned channels = Bus::getNumberOfChannels(type); if (len > maxLedsOnBus) maxLedsOnBus = len; @@ -215,7 +215,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint8_t maPerLed = elm[F("ledma")] | LED_MILLIAMPS_DEFAULT; uint16_t maMax = elm[F("maxpwr")] | (ablMilliampsMax * length) / total; // rough (incorrect?) per strip ABL calculation when no config exists // To disable brightness limiter we either set output max current to 0 or single LED current to 0 (we choose output max current) - if (IS_PWM(ledType) || IS_ONOFF(ledType) || IS_VIRTUAL(ledType)) { // analog and virtual + if (Bus::isPWM(ledType) || Bus::isOnOff(ledType) || Bus::isVirtual(ledType)) { // analog and virtual maPerLed = 0; maMax = 0; } @@ -244,17 +244,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // read color order map configuration JsonArray hw_com = hw[F("com")]; if (!hw_com.isNull()) { - ColorOrderMap com = {}; - unsigned s = 0; + BusManager::getColorOrderMap().reserve(std::min(hw_com.size(), (size_t)WLED_MAX_COLOR_ORDER_MAPPINGS)); for (JsonObject entry : hw_com) { - if (s > WLED_MAX_COLOR_ORDER_MAPPINGS) break; uint16_t start = entry["start"] | 0; uint16_t len = entry["len"] | 0; uint8_t colorOrder = (int)entry[F("order")]; - com.add(start, len, colorOrder); - s++; + if (!BusManager::getColorOrderMap().add(start, len, colorOrder)) break; } - BusManager::updateColorOrderMap(com); } // read multiple button configuration @@ -265,23 +261,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonArray hw_btn_ins = btn_obj["ins"]; if (!hw_btn_ins.isNull()) { // deallocate existing button pins - for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button + for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) PinManager::deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button unsigned s = 0; for (JsonObject btn : hw_btn_ins) { CJSON(buttonType[s], btn["type"]); int8_t pin = btn["pin"][0] | -1; - if (pin > -1 && pinManager.allocatePin(pin, false, PinOwner::Button)) { + if (pin > -1 && PinManager::allocatePin(pin, false, PinOwner::Button)) { btnPin[s] = pin; #ifdef ARDUINO_ARCH_ESP32 // ESP32 only: check that analog button pin is a valid ADC gpio if ((buttonType[s] == BTN_TYPE_ANALOG) || (buttonType[s] == BTN_TYPE_ANALOG_INVERTED)) { if (digitalPinToAnalogChannel(btnPin[s]) < 0) { // not an ADC analog pin - DEBUG_PRINT(F("PIN ALLOC error: GPIO")); DEBUG_PRINT(btnPin[s]); - DEBUG_PRINT(F("for analog button #")); DEBUG_PRINT(s); - DEBUG_PRINTLN(F(" is not an analog pin!")); + DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[s], s); btnPin[s] = -1; - pinManager.deallocatePin(pin,PinOwner::Button); + PinManager::deallocatePin(pin,PinOwner::Button); } else { analogReadResolution(12); // see #4040 } @@ -290,9 +284,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { { if (digitalPinToTouchChannel(btnPin[s]) < 0) { // not a touch pin - DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), btnPin[s], s); + DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), btnPin[s], s); btnPin[s] = -1; - pinManager.deallocatePin(pin,PinOwner::Button); + PinManager::deallocatePin(pin,PinOwner::Button); } //if touch pin, enable the touch interrupt on ESP32 S2 & S3 #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so @@ -337,7 +331,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (fromFS) { // relies upon only being called once with fromFS == true, which is currently true. for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) { - if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !pinManager.allocatePin(btnPin[s], false, PinOwner::Button)) { + if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !PinManager::allocatePin(btnPin[s], false, PinOwner::Button)) { btnPin[s] = -1; buttonType[s] = BTN_TYPE_NONE; } @@ -364,8 +358,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { #ifndef WLED_DISABLE_INFRARED int hw_ir_pin = hw["ir"]["pin"] | -2; // 4 if (hw_ir_pin > -2) { - pinManager.deallocatePin(irPin, PinOwner::IR); - if (pinManager.allocatePin(hw_ir_pin, false, PinOwner::IR)) { + PinManager::deallocatePin(irPin, PinOwner::IR); + if (PinManager::allocatePin(hw_ir_pin, false, PinOwner::IR)) { irPin = hw_ir_pin; } else { irPin = -1; @@ -380,8 +374,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { rlyOpenDrain = relay[F("odrain")] | rlyOpenDrain; int hw_relay_pin = relay["pin"] | -2; if (hw_relay_pin > -2) { - pinManager.deallocatePin(rlyPin, PinOwner::Relay); - if (pinManager.allocatePin(hw_relay_pin,true, PinOwner::Relay)) { + PinManager::deallocatePin(rlyPin, PinOwner::Relay); + if (PinManager::allocatePin(hw_relay_pin,true, PinOwner::Relay)) { rlyPin = hw_relay_pin; pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); } else { @@ -400,7 +394,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(i2c_sda, hw_if_i2c[0]); CJSON(i2c_scl, hw_if_i2c[1]); PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } }; - if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { + if (i2c_scl >= 0 && i2c_sda >= 0 && PinManager::allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { #ifdef ESP32 if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initialised (Wire.begin() called prior) else Wire.begin(); @@ -416,7 +410,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(spi_sclk, hw_if_spi[1]); CJSON(spi_miso, hw_if_spi[2]); PinManagerPinType spi[3] = { { spi_mosi, true }, { spi_miso, true }, { spi_sclk, true } }; - if (spi_mosi >= 0 && spi_sclk >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { + if (spi_mosi >= 0 && spi_sclk >= 0 && PinManager::allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { #ifdef ESP32 SPI.begin(spi_sclk, spi_miso, spi_mosi); // SPI global uses VSPI on ESP32 and FSPI on C3, S3 #else @@ -488,6 +482,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(receiveNotificationBrightness, if_sync_recv["bri"]); CJSON(receiveNotificationColor, if_sync_recv["col"]); CJSON(receiveNotificationEffects, if_sync_recv["fx"]); + CJSON(receiveNotificationPalette, if_sync_recv["pal"]); CJSON(receiveGroups, if_sync_recv["grp"]); CJSON(receiveSegmentOptions, if_sync_recv["seg"]); CJSON(receiveSegmentBounds, if_sync_recv["sb"]); @@ -669,7 +664,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { DEBUG_PRINTLN(F("Starting usermod config.")); JsonObject usermods_settings = doc["um"]; if (!usermods_settings.isNull()) { - needsSave = !usermods.readFromConfig(usermods_settings); + needsSave = !UsermodManager::readFromConfig(usermods_settings); } if (fromFS) return needsSave; @@ -705,7 +700,7 @@ void deserializeConfigFromFS() { // save default values to /cfg.json // call readFromConfig() with an empty object so that usermods can initialize to defaults prior to saving JsonObject empty = JsonObject(); - usermods.readFromConfig(empty); + UsermodManager::readFromConfig(empty); serializeConfig(); // init Ethernet (in case default type is set at compile time) #ifdef WLED_USE_ETHERNET @@ -975,6 +970,7 @@ void serializeConfig() { if_sync_recv["bri"] = receiveNotificationBrightness; if_sync_recv["col"] = receiveNotificationColor; if_sync_recv["fx"] = receiveNotificationEffects; + if_sync_recv["pal"] = receiveNotificationPalette; if_sync_recv["grp"] = receiveGroups; if_sync_recv["seg"] = receiveSegmentOptions; if_sync_recv["sb"] = receiveSegmentBounds; @@ -1125,7 +1121,7 @@ void serializeConfig() { #endif JsonObject usermods_settings = root.createNestedObject("um"); - usermods.addToConfig(usermods_settings); + UsermodManager::addToConfig(usermods_settings); File f = WLED_FS.open(FPSTR(s_cfg_json), "w"); if (f) serializeJson(root, f); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index ebea7ea055..478a0a277d 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -37,6 +37,8 @@ uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) */ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) { + if (c1 == BLACK) return c2; + if (c2 == BLACK) return c1; if (fast) { uint8_t r = R(c1); uint8_t g = G(c1); @@ -68,17 +70,18 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { + if (c1 == BLACK || amount + video == 0) return BLACK; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t r = R(c1); uint32_t g = G(c1); uint32_t b = B(c1); uint32_t w = W(c1); - uint32_t scale = amount + !video; // 32bit for faster calculation + uint32_t scale = amount; // 32bit for faster calculation if (video) { - scaledcolor = (((r * scale) >> 8) << 16) + ((r && scale) ? 1 : 0); - scaledcolor |= (((g * scale) >> 8) << 8) + ((g && scale) ? 1 : 0); - scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); - scaledcolor |= (((w * scale) >> 8) << 24) + ((w && scale) ? 1 : 0); + scaledcolor = (((r * scale) >> 8) + ((r && scale) ? 1 : 0)) << 16; + scaledcolor |= (((g * scale) >> 8) + ((g && scale) ? 1 : 0)) << 8; + scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); + scaledcolor |= (((w * scale) >> 8) + ((w && scale) ? 1 : 0)) << 24; } else { scaledcolor = ((r * scale) >> 8) << 16; scaledcolor |= ((g * scale) >> 8) << 8; @@ -195,7 +198,7 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) RGBpalettecolors[3]); } -CRGBPalette16 generateRandomPalette(void) //generate fully random palette +CRGBPalette16 generateRandomPalette() //generate fully random palette { return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)), CHSV(random8(), random8(160, 255), random8(128, 255)), @@ -476,14 +479,14 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) } } -uint8_t NeoGammaWLEDMethod::Correct(uint8_t value) +uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value) { if (!gammaCorrectCol) return value; return gammaT[value]; } // used for color gamma correction -uint32_t NeoGammaWLEDMethod::Correct32(uint32_t color) +uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct32(uint32_t color) { if (!gammaCorrectCol) return color; uint8_t w = W(color); diff --git a/wled00/const.h b/wled00/const.h index d5cd049d5e..e3bf0247c6 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -51,27 +51,28 @@ #define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB #define WLED_MIN_VIRTUAL_BUSSES 2 #else + #define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX) #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM - #define WLED_MAX_BUSSES 4 // will allow 2 digital & 2 analog RGB + #define WLED_MAX_BUSSES 6 // will allow 2 digital & 2 analog RGB or 6 PWM white #define WLED_MAX_DIGITAL_CHANNELS 2 - #define WLED_MAX_ANALOG_CHANNELS 6 + //#define WLED_MAX_ANALOG_CHANNELS 6 #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB #define WLED_MAX_DIGITAL_CHANNELS 5 - #define WLED_MAX_ANALOG_CHANNELS 8 + //#define WLED_MAX_ANALOG_CHANNELS 8 #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog RGB #define WLED_MAX_DIGITAL_CHANNELS 4 - #define WLED_MAX_ANALOG_CHANNELS 8 + //#define WLED_MAX_ANALOG_CHANNELS 8 #define WLED_MIN_VIRTUAL_BUSSES 4 #else // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning #define WLED_MAX_BUSSES 20 // will allow 17 digital & 3 analog RGB #define WLED_MAX_DIGITAL_CHANNELS 17 - #define WLED_MAX_ANALOG_CHANNELS 10 + //#define WLED_MAX_ANALOG_CHANNELS 16 #define WLED_MIN_VIRTUAL_BUSSES 4 #endif #endif @@ -202,6 +203,7 @@ #define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h" #define USERMOD_ID_POV_DISPLAY 53 //Usermod "usermod_pov_display.h" #define USERMOD_ID_PIXELS_DICE_TRAY 54 //Usermod "pixels_dice_tray.h" +#define USERMOD_ID_POWER_MEASUREMENT 55 //Usermod "Power_measurement.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -281,6 +283,7 @@ #define TYPE_NONE 0 //light is not configured #define TYPE_RESERVED 1 //unused. Might indicate a "virtual" light //Digital types (data pin only) (16-39) +#define TYPE_DIGITAL_MIN 16 // first usable digital type #define TYPE_WS2812_1CH 18 //white-only chips (1 channel per IC) (unused) #define TYPE_WS2812_1CH_X3 19 //white-only chips (3 channels per IC) #define TYPE_WS2812_2CH_X3 20 //CCT chips (1st IC controls WW + CW of 1st zone and CW of 2nd zone, 2nd IC controls WW of 2nd zone and WW + CW of 3rd zone) @@ -298,26 +301,36 @@ #define TYPE_WS2805 32 //RGB + WW + CW #define TYPE_TM1914 33 //RGB #define TYPE_SM16825 34 //RGB + WW + CW +#define TYPE_DIGITAL_MAX 39 // last usable digital type //"Analog" types (40-47) #define TYPE_ONOFF 40 //binary output (relays etc.; NOT PWM) +#define TYPE_ANALOG_MIN 41 // first usable analog type #define TYPE_ANALOG_1CH 41 //single channel PWM. Uses value of brightest RGBW channel #define TYPE_ANALOG_2CH 42 //analog WW + CW #define TYPE_ANALOG_3CH 43 //analog RGB #define TYPE_ANALOG_4CH 44 //analog RGBW #define TYPE_ANALOG_5CH 45 //analog RGB + WW + CW +#define TYPE_ANALOG_6CH 46 //analog RGB + A + WW + CW +#define TYPE_ANALOG_MAX 47 // last usable analog type //Digital types (data + clock / SPI) (48-63) +#define TYPE_2PIN_MIN 48 #define TYPE_WS2801 50 #define TYPE_APA102 51 #define TYPE_LPD8806 52 #define TYPE_P9813 53 #define TYPE_LPD6803 54 +#define TYPE_2PIN_MAX 63 //Network types (master broadcast) (80-95) +#define TYPE_VIRTUAL_MIN 80 #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) #define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus, unused) #define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus, unused) #define TYPE_NET_DDP_RGBW 88 //network DDP RGBW bus (master broadcast bus) #define TYPE_NET_ARTNET_RGBW 89 //network ArtNet RGB bus (master broadcast bus, unused) +#define TYPE_VIRTUAL_MAX 95 +/* +// old macros that have been moved to Bus class #define IS_TYPE_VALID(t) ((t) > 15 && (t) < 128) #define IS_DIGITAL(t) (((t) > 15 && (t) < 40) || ((t) > 47 && (t) < 64)) //digital are 16-39 and 48-63 #define IS_2PIN(t) ((t) > 47 && (t) < 64) @@ -326,6 +339,7 @@ #define IS_PWM(t) ((t) > 40 && (t) < 46) //does not include on/Off type #define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only #define IS_VIRTUAL(t) ((t) >= 80 && (t) < 96) //this was a poor choice a better would be 96-111 +*/ //Color orders #define COL_ORDER_GRB 0 //GRB(w),defaut @@ -453,6 +467,9 @@ #define NTP_PACKET_SIZE 48 // size of NTP receive buffer #define NTP_MIN_PACKET_SIZE 48 // min expected size - NTP v4 allows for "extended information" appended to the standard fields +// Maximum number of pins per output. 5 for RGBCCT analog LEDs. +#define OUTPUT_MAX_PINS 5 + //maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses #ifndef MAX_LEDS #ifdef ESP8266 @@ -480,7 +497,7 @@ // string temp buffer (now stored in stack locally) #ifdef ESP8266 -#define SETTINGS_STACK_BUF_SIZE 2048 +#define SETTINGS_STACK_BUF_SIZE 2560 #else #define SETTINGS_STACK_BUF_SIZE 3840 // warning: quite a large value for stack (640 * WLED_MAX_USERMODS) #endif @@ -520,7 +537,11 @@ #ifdef ESP8266 #define WLED_PWM_FREQ 880 //PWM frequency proven as good for LEDs #else - #define WLED_PWM_FREQ 19531 + #ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK + #define WLED_PWM_FREQ 9765 // XTAL clock is 40MHz (this will allow 12 bit resolution) + #else + #define WLED_PWM_FREQ 19531 // APB clock is 80MHz + #endif #endif #endif @@ -547,26 +568,19 @@ #define WLED_MAX_NODES 150 #endif -//this is merely a default now and can be changed at runtime -#ifndef LEDPIN -#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) //|| (defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(ARDUINO_ESP32_PICO) - #define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board +// Defaults pins, type and counts to configure LED output +#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) + #ifdef WLED_ENABLE_DMX + #define DEFAULT_LED_PIN 1 + #warning "Compiling with DMX. The default LED pin has been changed to pin 1." + #else + #define DEFAULT_LED_PIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board + #endif #else - #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) -#endif -#endif - -#ifdef WLED_ENABLE_DMX -#if (LEDPIN == 2) - #undef LEDPIN - #define LEDPIN 1 - #warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 1." -#endif -#endif - -#ifndef DEFAULT_LED_COUNT - #define DEFAULT_LED_COUNT 30 + #define DEFAULT_LED_PIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) #endif +#define DEFAULT_LED_TYPE TYPE_WS2812_RGB +#define DEFAULT_LED_COUNT 30 #define INTERFACE_UPDATE_COOLDOWN 1000 // time in ms to wait between websockets, alexa, and MQTT updates @@ -628,4 +642,12 @@ #define HW_PIN_MISOSPI MISO #endif +// IRAM_ATTR for 8266 with 32Kb IRAM causes error: section `.text1' will not fit in region `iram1_0_seg' +// this hack removes the IRAM flag for some 1D/2D functions - somewhat slower, but it solves problems with some older 8266 chips +#ifdef WLED_SAVE_IRAM + #define IRAM_ATTR_YN +#else + #define IRAM_ATTR_YN IRAM_ATTR +#endif + #endif diff --git a/wled00/data/common.js b/wled00/data/common.js new file mode 100644 index 0000000000..9378ef07a8 --- /dev/null +++ b/wled00/data/common.js @@ -0,0 +1,118 @@ +var d=document; +var loc = false, locip, locproto = "http:"; + +function H(pg="") { window.open("https://kno.wled.ge/"+pg); } +function GH() { window.open("https://github.com/Aircoookie/WLED"); } +function gId(c) { return d.getElementById(c); } // getElementById +function cE(e) { return d.createElement(e); } // createElement +function gEBCN(c) { return d.getElementsByClassName(c); } // getElementsByClassName +function gN(s) { return d.getElementsByName(s)[0]; } // getElementsByName +function isE(o) { return Object.keys(o).length === 0; } // isEmpty +function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); } // isObject +function isN(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // isNumber +// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer +function isF(n) { return n === +n && n !== (n|0); } // isFloat +function isI(n) { return n === +n && n === (n|0); } // isInteger +function toggle(el) { gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide"); } +function tooltip(cont=null) { + d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{ + element.addEventListener("mouseover", ()=>{ + // save title + element.setAttribute("data-title", element.getAttribute("title")); + const tooltip = d.createElement("span"); + tooltip.className = "tooltip"; + tooltip.textContent = element.getAttribute("title"); + + // prevent default title popup + element.removeAttribute("title"); + + let { top, left, width } = element.getBoundingClientRect(); + + d.body.appendChild(tooltip); + + const { offsetHeight, offsetWidth } = tooltip; + + const offset = element.classList.contains("sliderwrap") ? 4 : 10; + top -= offsetHeight + offset; + left += (width - offsetWidth) / 2; + + tooltip.style.top = top + "px"; + tooltip.style.left = left + "px"; + tooltip.classList.add("visible"); + }); + + element.addEventListener("mouseout", ()=>{ + d.querySelectorAll('.tooltip').forEach((tooltip)=>{ + tooltip.classList.remove("visible"); + d.body.removeChild(tooltip); + }); + // restore title + element.setAttribute("title", element.getAttribute("data-title")); + }); + }); +}; +// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript +function loadJS(FILE_URL, async = true, preGetV = undefined, postGetV = undefined) { + let scE = d.createElement("script"); + scE.setAttribute("src", FILE_URL); + scE.setAttribute("type", "text/javascript"); + scE.setAttribute("async", async); + d.body.appendChild(scE); + // success event + scE.addEventListener("load", () => { + //console.log("File loaded"); + if (preGetV) preGetV(); + GetV(); + if (postGetV) postGetV(); + }); + // error event + scE.addEventListener("error", (ev) => { + console.log("Error on loading file", ev); + alert("Loading of configuration script failed.\nIncomplete page data!"); + }); +} +function getLoc() { + let l = window.location; + if (l.protocol == "file:") { + loc = true; + locip = localStorage.getItem('locIp'); + if (!locip) { + locip = prompt("File Mode. Please enter WLED IP!"); + localStorage.setItem('locIp', locip); + } + } else { + // detect reverse proxy + let path = l.pathname; + let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/"); + if (paths.length > 1) paths.pop(); // remove subpage (or "settings") + if (paths.length > 0 && paths[paths.length-1]=="settings") paths.pop(); // remove "settings" + if (paths.length > 1) { + locproto = l.protocol; + loc = true; + locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths.join('/'); + } + } +} +function getURL(path) { return (loc ? locproto + "//" + locip : "") + path; } +function B() { window.open(getURL("/settings"),"_self"); } +var timeout; +function showToast(text, error = false) { + var x = gId("toast"); + if (!x) return; + x.innerHTML = text; + x.className = error ? "error":"show"; + clearTimeout(timeout); + x.style.animation = 'none'; + timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900); +} +function uploadFile(fileObj, name) { + var req = new XMLHttpRequest(); + req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)}); + req.addEventListener('error', function(e){showToast(e.stack,true);}); + req.open("POST", "/upload"); + var formData = new FormData(); + formData.append("data", fileObj.files[0], name); + req.send(formData); + fileObj.value = ''; + return false; +} diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index a4b9135924..b58c0987ab 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -608,8 +608,8 @@

} function generatePaletteDivs() { - const palettesDiv = d.getElementById("palettes"); - const staticPalettesDiv = d.getElementById("staticPalettes"); + const palettesDiv = gId("palettes"); + const staticPalettesDiv = gId("staticPalettes"); const paletteDivs = Array.from(palettesDiv.children).filter((child) => { return child.id.match(/^palette\d$/); // match only elements with id starting with "palette" followed by a single digit }); @@ -620,25 +620,25 @@

for (let i = 0; i < paletteArray.length; i++) { const palette = paletteArray[i]; - const paletteDiv = d.createElement("div"); + const paletteDiv = cE("div"); paletteDiv.id = `palette${i}`; paletteDiv.classList.add("palette"); const thisKey = Object.keys(palette)[0]; paletteDiv.dataset.colarray = JSON.stringify(palette[thisKey]); - const gradientDiv = d.createElement("div"); + const gradientDiv = cE("div"); gradientDiv.id = `paletteGradient${i}` - const buttonsDiv = d.createElement("div"); + const buttonsDiv = cE("div"); buttonsDiv.id = `buttonsDiv${i}`; buttonsDiv.classList.add("buttonsDiv") - const sendSpan = d.createElement("span"); + const sendSpan = cE("span"); sendSpan.id = `sendSpan${i}`; sendSpan.onclick = function() {initiateUpload(i)}; sendSpan.setAttribute('title', `Send current editor to slot ${i}`); // perhaps Save instead of Send? sendSpan.innerHTML = svgSave; sendSpan.classList.add("sendSpan") - const editSpan = d.createElement("span"); + const editSpan = cE("span"); editSpan.id = `editSpan${i}`; editSpan.onclick = function() {loadForEdit(i)}; editSpan.setAttribute('title', `Copy slot ${i} palette to editor`); diff --git a/wled00/data/index.css b/wled00/data/index.css index 0952cca210..0e36ff08c9 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -35,6 +35,7 @@ --sgp: "block"; --bmt: 0; --sti: 42px; + --stp: 42px; } html { @@ -143,7 +144,7 @@ button { } .huge { - font-size: 42px; + font-size: 60px !important; } .segt, .plentry TABLE { @@ -468,7 +469,7 @@ button { padding: 4px 2px; position: relative; opacity: 1; - transition: opacity .5s linear, height .25s, transform .25s; + transition: opacity .25s linear, height .2s, transform .2s; } .filter { @@ -583,6 +584,10 @@ button { z-index: 3; } +#rover .ibtn { + margin: 5px; +} + #ndlt { margin: 12px 0; } @@ -623,7 +628,7 @@ button { padding-bottom: 8px; } -.infobtn { +#info .ibtn { margin: 5px; } @@ -847,7 +852,7 @@ input[type=range]::-moz-range-thumb { width: 135px; } -#nodes .infobtn { +#nodes .ibtn { margin: 0; } @@ -1035,7 +1040,7 @@ textarea { .segname .flr, .pname .flr { transform: rotate(0deg); - right: -6px; + /*right: -6px;*/ } /* segment power wrapper */ @@ -1330,15 +1335,22 @@ TD .checkmark, TD .radiomark { box-shadow: 0 0 10px 4px var(--c-1); } +.lstI .flr:hover { + background: var(--c-6); + border-radius: 100%; +} + #pcont .selected:not([class*="expanded"]) { bottom: 52px; top: 42px; } -#fxlist .lstI.selected, -#pallist .lstI.selected { +#fxlist .lstI.selected { top: calc(var(--sti) + 42px); } +#pallist .lstI.selected { + top: calc(var(--stp) + 42px); +} dialog::backdrop { backdrop-filter: blur(10px); @@ -1353,10 +1365,12 @@ dialog { color: var(--c-f); } -#fxlist .lstI.sticky, -#pallist .lstI.sticky { +#fxlist .lstI.sticky { top: var(--sti); } +#pallist .lstI.sticky { + top: var(--stp); +} /* list item content */ .lstIcontent { @@ -1519,7 +1533,7 @@ dialog { #info table .btn, #nodes table .btn { width: 200px; } - #info .infobtn, #nodes .infobtn { + #info .ibtn, #nodes .ibtn { width: 145px; } #info div, #nodes div, #nodes a.btn { diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 86b3b18796..e74ea00764 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -304,10 +304,10 @@
Loading...

- - - - + + + +

Made with ❤︎ by Aircoookie and the WLED community @@ -318,7 +318,7 @@
WLED instances
Loading...
- +
@@ -331,8 +331,8 @@
?


To use built-in effects, use an override button below.
You can return to realtime mode by pressing the star in the top left corner.
- -
+ +
For best performance, it is recommended to turn off the streaming source when not in use. diff --git a/wled00/data/index.js b/wled00/data/index.js index 447bf03a66..d9c64bdfbf 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2828,7 +2828,12 @@ function search(field, listId = null) { // restore default preset sorting if no search term is entered if (!search) { if (listId === 'pcont') { populatePresets(); return; } - if (listId === 'pallist') { populatePalettes(); return; } + if (listId === 'pallist') { + let id = parseInt(d.querySelector('#pallist input[name="palette"]:checked').value); // preserve selected palette + populatePalettes(); + updateSelectedPalette(id); + return; + } } // clear filter if searching in fxlist @@ -2887,18 +2892,25 @@ function initFilters() { function filterFocus(e) { const f = gId("filters"); - if (e.type === "focus") f.classList.remove('fade'); // immediately show (still has transition) - // compute sticky top (with delay for transition) - setTimeout(() => { - const sti = parseInt(getComputedStyle(d.documentElement).getPropertyValue('--sti')) + (e.type === "focus" ? 1 : -1) * f.offsetHeight; - sCol('--sti', sti + "px"); - }, 252); + const c = !!f.querySelectorAll("input[type=checkbox]:checked").length; + const h = f.offsetHeight; + const sti = parseInt(getComputedStyle(d.documentElement).getPropertyValue('--sti')); + if (e.type === "focus") { + // compute sticky top (with delay for transition) + if (!h) setTimeout(() => { + sCol('--sti', (sti+f.offsetHeight) + "px"); // has an unpleasant consequence on palette offset + }, 255); + f.classList.remove('fade'); // immediately show (still has transition) + } if (e.type === "blur") { setTimeout(() => { if (e.target === document.activeElement && document.hasFocus()) return; // do not hide if filter is active - if (gId("filters").querySelectorAll("input[type=checkbox]:checked").length) return; - f.classList.add('fade'); + if (!c) { + // compute sticky top + sCol('--sti', (sti-h) + "px"); // has an unpleasant consequence on palette offset + f.classList.add('fade'); + } }, 255); // wait with hiding } } @@ -2911,7 +2923,7 @@ function filterFx() { gId("fxlist").querySelectorAll('.lstI').forEach((listItem,i) => { const listItemName = listItem.querySelector('.lstIname').innerText; let hide = false; - gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = true; }); + gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i>0 /*true*/; }); listItem.style.display = hide ? 'none' : ''; }); } @@ -3201,7 +3213,7 @@ function simplifyUI() { createDropdown("palw", "Change palette"); createDropdown("fx", "Change effect", [gId("fxFind"), gId("fxlist")]); - // Hide pallete label + // Hide palette label gId("pall").style.display = "none"; gId("Colors").insertBefore(document.createElement("br"), gId("pall")); // Hide effect label diff --git a/wled00/data/pxmagic/pxmagic.htm b/wled00/data/pxmagic/pxmagic.htm index d59f924cf2..8ec11f454a 100644 --- a/wled00/data/pxmagic/pxmagic.htm +++ b/wled00/data/pxmagic/pxmagic.htm @@ -882,10 +882,8 @@ hostnameLabel(); })(); - function gId(id) { - return d.getElementById(id); - } - + function gId(e) {return d.getElementById(e);} + function cE(e) {return d.createElement(e);} function hostnameLabel() { const link = gId("wledEdit"); link.href = WLED_URL + "/edit"; @@ -1675,7 +1673,7 @@ } function createCanvas(width, height) { - const canvas = d.createElement("canvas"); + const canvas = cE("canvas"); canvas.width = width; canvas.height = height; @@ -1719,7 +1717,7 @@ const blob = new Blob([text], { type: mimeType }); const url = URL.createObjectURL(blob); - const anchorElement = d.createElement("a"); + const anchorElement = cE("a"); anchorElement.href = url; anchorElement.download = `${filename}.${fileExtension}`; @@ -1790,7 +1788,7 @@ hideElement = "preview" ) { const hide = gId(hideElement); - const toast = d.createElement("div"); + const toast = cE("div"); const wait = 100; toast.style.animation = "fadeIn"; @@ -1799,14 +1797,14 @@ toast.classList.add("toast", type); - const body = d.createElement("span"); + const body = cE("span"); body.classList.add("toast-body"); body.textContent = message; toast.appendChild(body); - const progress = d.createElement("div"); + const progress = cE("div"); progress.classList.add("toast-progress"); progress.style.animation = "progress"; @@ -1831,7 +1829,7 @@ function carousel(id, images, delay = 3000) { let index = 0; - const carousel = d.createElement("div"); + const carousel = cE("div"); carousel.classList.add("carousel"); images.forEach((canvas, i) => { @@ -1959,7 +1957,7 @@ let errorElement = parent.querySelector(".error-message"); if (!errorElement) { - errorElement = d.createElement("div"); + errorElement = cE("div"); errorElement.classList.add("error-message"); parent.appendChild(errorElement); } diff --git a/wled00/data/settings.htm b/wled00/data/settings.htm index 52b64006b5..82c7782140 100644 --- a/wled00/data/settings.htm +++ b/wled00/data/settings.htm @@ -4,53 +4,12 @@ WLED Settings +
-
+

Imma firin ma lazer (if it has DMX support)

diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index b7d2d18a7d..6be5becd10 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -4,59 +4,42 @@ LED Settings +
-
+

LED & Hardware setup

@@ -805,12 +756,13 @@

LED & Hardware setup

Automatically limits brightness to stay close to the limit.
Keep at <1A if poweing LEDs directly from the ESP 5V pin!
+ If using multiple outputs it is recommended to use per-output limiter.
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.
Maximum PSU Current: mA
Use per-output limiter: