diff --git a/.gitignore b/.gitignore index cdfd81d0f1..5a9283257a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,10 @@ esp01-update.sh platformio_override.ini replace_fs.py wled-update.sh +qodana.yaml +compile_commands.json +/build/ /build_output/ /node_modules/ diff --git a/Aircoookie_LICENSE b/Aircoookie_LICENSE deleted file mode 100644 index 30fd7534ba..0000000000 --- a/Aircoookie_LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -The license below is valid for the code that is common with upstream "AC WLED" from https://github.com/Aircoookie/WLED. -Additions and code changes that are exclusive to this fork ("MoonModules WLED") are under GPLv3 license, see LICENSE. - -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 diff --git a/LICENSE b/LICENSE index f288702d2f..22953c3cf2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,291 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program 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. - - This program 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 . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. + 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. diff --git a/package-lock.json b/package-lock.json index aa060721c0..59697131a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "wled", "version": "0.14.1-b32.41.dev", - "license": "GPL-3.0-or-later", + "license": "EUPL-1.2", "dependencies": { "clean-css": "^4.2.3", "html-minifier-terser": "^5.1.1", diff --git a/package.json b/package.json index c73dd26f34..b6763b3347 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "url": "git+https://github.com/MoonModules/WLED.git" }, "author": "", - "license": "GPL-3.0-or-later", + "license": "EUPL-1.2", "bugs": { "url": "https://github.com/MoonModules/WLED/issues" }, diff --git a/platformio.ini b/platformio.ini index fe92dbf459..76fc4c041a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,8 +78,11 @@ default_envs = esp32S3_4MB_PSRAM_S ;; for lolin s3 mini, S3 zero, S3 super mini - optimized for speed esp32S3_4MB_PSRAM_M ;; for lolin s3 mini, S3 zero, S3 super mini esp32S3_8MB_PSRAM_M ;; experiemental + esp32S3_16MB_PSRAM_M_HUB75 ;; for S3 with 16MB flash, and MOONHUB HUB75 adapter board + esp32S3_WROOM-2_M ;; for S3 WROOM-2 ;; esp32s2_tinyUF2_PSRAM_S ;; experimental - only for adafruit -S2 boards with tinyUF2 bootloader !!! - esp32s2_PSRAM_M ;; experimental + esp32s2_PSRAM_S ;; OTA-compatible with upstream + esp32s2_PSRAM_M ;; for lolin S2 mini esp32c3dev_4MB_M ;; experimental esp32c3dev_2MB_M ;; experimental - 2MB Flash, no OTA esp32c3mini_dio_4MB_M ;; for boards that need "dio" flash mode (instead of qio) @@ -731,6 +734,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap lib_deps = ${esp32s3.lib_deps} ${esp32.AR_lib_deps} board_build.partitions = ${esp32.large_partitions} @@ -827,6 +831,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -DARDUINO_USB_DFU_ON_BOOT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 6792 bytes FLASH -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 @@ -1053,7 +1058,7 @@ build_disable_sync_interfaces = -D WLED_DISABLE_ADALIGHT ;; WLEDMM this board does not have a serial-to-USB chip. Better to disable serial protocols, to avoid crashes (see upstream #3128) -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only -AR_build_flags = -D USERMOD_AUDIOREACTIVE -D UM_AUDIOREACTIVE_USE_NEW_FFT ;; WLEDMM audioreactive usermod, licensed under GPLv3 +AR_build_flags = -D USERMOD_AUDIOREACTIVE -D UM_AUDIOREACTIVE_USE_NEW_FFT ;; WLEDMM audioreactive usermod, licensed under EUPL-1.2 AR_lib_deps = https://github.com/softhack007/arduinoFFT.git#develop @ 1.9.2 ;; used for USERMOD_AUDIOREACTIVE - optimized version, 10% faster on -S2/-C3 animartrix_build_flags = -D USERMOD_ANIMARTRIX ;; WLEDMM usermod: CC BY-NC 3.0 licensed effects by Stefan Petrick @@ -1072,7 +1077,8 @@ HUB75_build_flags = -D NO_CIE1931 ;; Do not use LED brightness compensation described in CIE 1931. We use FastLED dimming already -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips -D WLEDMM_SLOWPATH ;; WLEDMM: do not use I2S for driving ws2812 LEDs (HUB75 driver needs I2S#1) -HUB75_lib_deps = https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a9 ;; S3_LCD_DIV_NUM fix +;; HUB75_lib_deps = https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a9 ;; S3_LCD_DIV_NUM fix +HUB75_lib_deps = https://github.com/softhack007/ESP32-HUB75-MatrixPanel-DMA_sh7.git#fix_dangling_pointer ;; S3 bugfix for crash in ~MatrixPanel_I2S_DMA() HUB75_lib_ignore = ESP32 HUB75 LED MATRIX PANEL DMA Display ;; to remove the HUB75 lib dependancy (saves a few bytes) NetDebug_build_flags = @@ -1269,9 +1275,12 @@ build_flags = build_flags = -O2 ;; optimize for performance instead of size ;-ffast-math ;; gives a few (2-5) percent speedup on ESP32-S3, but causes slight slowdown on classic ESP32 - -mtarget-align -free -fipa-pta ;; these are very useful, too + -funsafe-math-optimizations ;; less dangerous than -ffast-math; still allows the compiler to exploit FMA and reciprocals (10% faster on -S3) + ;; -mtarget-align ;; this one is included in -O2, so its not necessary to explicitly add it + -free -fipa-pta ;; these are very useful, too -fno-jump-tables -fno-tree-switch-conversion ;; needed -freorder-blocks -Wwrite-strings -fstrict-volatile-bitfields ;; needed +build_flags_V4 = ${Speed_Flags.build_flags} -fsingle-precision-constant ;; old framework does not like this flag build_unflags = -Os ;; to disable standard optimization for size @@ -1602,9 +1611,12 @@ build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_RELEASE_NAME=esp01_1MB_S -D WLED_DISABLE_ALEXA -D WLED_DISABLE_HUESYNC + -D WLED_DISABLE_ESPNOW ;; exceeds flash size limits + -D WLED_DISABLE_INFRARED ;; exceeds flash size limits lib_deps = ${esp8266.lib_deps} -; RAM: [====== ] 59.5% (used 48748 bytes from 81920 bytes) -; Flash: [========= ] 90.7% (used 809992 bytes from 892912 bytes) +lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation +; RAM: [====== ] 60.6% (used 49616 bytes from 81920 bytes) +; Flash: [==========] 99.8% (used 890835 bytes from 892912 bytes) # ------------------------------------------------------------------------------ @@ -1621,6 +1633,7 @@ build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. + -D WLEDMM_SAVE_FLASH -D WLED_DISABLE_LOXONE -D WLED_DISABLE_ALEXA -D WLED_DISABLE_HUESYNC @@ -1678,7 +1691,7 @@ build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} ;; -Wall -Wextra -Wno-unused-value -Wno-format -Wno-type-limits -D WLED_RELEASE_NAME=esp32_4MB_V4_HUB75 - ${Speed_Flags.build_flags} ;; -O2 -> optimize for speed instead of size + ${Speed_Flags.build_flags_V4} ;; -O2 -> optimize for speed instead of size ;; -D DEBUG -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup @@ -1750,7 +1763,7 @@ extends = esp32_4MB_V4_S_base build_unflags = ${esp32_4MB_V4_S_base.build_unflags} ${Speed_Flags.build_unflags} ;; to override -Os build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} - ${Speed_Flags.build_flags} ;; optimize for speed instead of size + ${Speed_Flags.build_flags_V4} ;; optimize for speed instead of size -D WLED_RELEASE_NAME=esp32_16MB_V4_S -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup @@ -1820,7 +1833,9 @@ build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLEDMM_SAVE_FLASH + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_DISABLE_LOXONE ; FLASH 1272 bytes -D WLED_DISABLE_HUESYNC ; RAM 122 bytes; FLASH 6308 bytes -D WLED_DISABLE_ALEXA ; RAM 116 bytes; FLASH 13524 bytes @@ -1853,9 +1868,11 @@ build_flags = ${esp32_4MB_V4_S_base.esp32_build_flags} -DARDUINO_EVENT_RUNNING_CORE=0 ;; assign Wifi to core0, to have more CPU on core#1 (arduino loop) -DARDUINO_RUNNING_CORE=1 ;; should be default, but does not hurt -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1 ;; optional - seems to move more buffers into PSRAM - ;;${Speed_Flags.build_flags} ;; optimize for speed instead of size --> over 100% flash, but works with 256KB filesystem (alternative partitions file) + ;;${Speed_Flags.build_flags_V4} ;; optimize for speed instead of size --> over 100% flash, but works with 256KB filesystem (alternative partitions file) -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLEDMM_SAVE_FLASH + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap ;;-D CONFIG_ESP32_REV_MIN=3 ;; disables PSRAM bug workarounds in the core, reducing the code size and improving overall performance. -D WLED_RELEASE_NAME=esp32_4MB_PSRAM_REV3_S -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET @@ -1885,7 +1902,9 @@ build_flags = ${esp32_4MB_V4_M_base.esp32_build_flags} -D WLED_RELEASE_NAME=esp32_4MB_PSRAM_M -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLEDMM_SAVE_FLASH + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; WLED_USE_PSRAM causes major slow-down (slow LEDs) on some ESP32 boards + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_DISABLE_LOXONE ;; FLASH 1272 bytes -D WLED_DISABLE_HUESYNC ;; RAM 122 bytes; FLASH 6308 bytes -D WLED_DISABLE_ALEXA ;; RAM 116 bytes; FLASH 13524 bytes @@ -1925,6 +1944,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 ${common_mm.build_disable_sync_interfaces} -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. + -D WLEDMM_SAVE_FLASH ; -D WLED_DEBUG ; -D SR_DEBUG lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} @@ -1947,6 +1967,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden ${common_mm.HUB75_build_flags} ${common_mm.animartrix_build_flags} ; -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON -D WLED_USE_PSRAM ;; un-comment in case your board supports PSRAM + ;; -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_RELEASE_NAME=esp32S3_8MB_M -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode @@ -1995,7 +2016,8 @@ build_unflags = ${env:esp32S3_8MB_M.build_unflags} ;; use the same as "normal" build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} ${common_mm.build_flags_M} ${common_mm.HUB75_build_flags} - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM ;; your board supports PSRAM + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; your board supports PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_RELEASE_NAME=esp32S3_8MB_PSRAM_M -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode @@ -2039,7 +2061,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-inden -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode ;;-D WLED_DISABLE_ADALIGHT ;; disables serial protocols - recommended for Hardware-CDC USB (Serial RX will receive junk commands when RX pin is unconnected, unless its pulled down by resistor) - ${Speed_Flags.build_flags} ;; optimize for speed instead of size + ${Speed_Flags.build_flags_V4} ;; optimize for speed instead of size -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. ${common_mm.animartrix_build_flags} -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 @@ -2072,6 +2094,89 @@ board_build.partitions = tools/WLED_ESP32_8MB.csv ; RAM: [=== ] 25.8% (used 84544 bytes from 327680 bytes) ; Flash: [======== ] 78.1% (used 1638737 bytes from 2097152 bytes) + +;; for S3 with 16MB flash, octal PSRAM, +;; MOONHUB HUB75 adapter board +[env:esp32S3_16MB_PSRAM_M_HUB75] +extends = env:esp32S3_8MB_PSRAM_M +board = lilygo-t7-s3 +board_build.arduino.memory_type = qio_opi +board_build.flash_mode = qio + +build_unflags = ${env:esp32S3_8MB_PSRAM_M.build_unflags} + -D WLED_RELEASE_NAME=esp32S3_8MB_M + -D LEDPIN=21 + ${Speed_Flags.build_unflags} ;; to override -Os + +build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation + ${common_mm.build_flags_S} ${common_mm.build_flags_M} + ${Speed_Flags.build_flags_V4} ;; optimize for speed + ${common_mm.HUB75_build_flags} + -D MOONHUB_S3_PINOUT ;; HUB75 pinout + ${common_mm.animartrix_build_flags} + -D WLED_RELEASE_NAME=esp32S3_16MB_PSRAM_M_HUB75 + -D WLEDMM_FASTPATH + -D WLED_DISABLE_BROWNOUT_DET + ;; -D JSON_BUFFER_SIZE=18432 ;; default 54000 + -D MIN_HEAP_SIZE=6144 ;; default 8192 + -D MAX_SEGMENT_DATA=40960 ;; default 32767 + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; your board supports PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap + -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip + ;; -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode + -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 + ;; -DUSERMOD_BATTERY_MEASUREMENT_PIN=2 ;; battery voltage pin + ;; -D STATUSLED=17 ;; onboard LED + -D LEDPIN=14 -D BTNPIN=0 -D RLYPIN=15 -D IRPIN=-1 -D AUDIOPIN=-1 ;; defaults that avoid pin conflicts with HUB75 + -D SR_DMTYPE=1 -D I2S_SDPIN=10 -D I2S_CKPIN=11 -D I2S_WSPIN=12 -D MCLK_PIN=-1 ;; I2S mic + +lib_deps = ${esp32s3.lib_deps} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M} + ${common_mm.HUB75_lib_deps} + ${common_mm.animartrix_lib_deps} +;lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation + +board_build.partitions = ${esp32.extreme_partitions} +board_upload.flash_size = 16MB +board_upload.maximum_size =16777216 +; RAM: [== ] 19.0% (used 62332 bytes from 327680 bytes) +; Flash: [====== ] 57.7% (used 1813585 bytes from 3145728 bytes) + + +;; MM for ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 +;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) +;; includes HUB75 and AnimArtix +[env:esp32S3_WROOM-2_M] +extends = env:esp32S3_8MB_PSRAM_M +board_build.flash_mode = dout ;; dummy value - bootloader will switch to "opi" +board_build.arduino.memory_type = opi_opi +board = esp32s3camlcd ;; this is the only standard board with "opi_opi" +board_upload.flash_size = 16MB +board_upload.maximum_size =16777216 +board_build.partitions = ${esp32.extreme_partitions} +build_unflags = ${env:esp32S3_8MB_PSRAM_M.build_unflags} + -D WLED_RELEASE_NAME=esp32S3_8MB_M + -D LEDPIN=21 + ${Speed_Flags.build_unflags} ;; to override -Os +build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation + ${common_mm.build_flags_S} ${common_mm.build_flags_M} + ${Speed_Flags.build_flags_V4} ;; optimize for speed + ${common_mm.HUB75_build_flags} + ${common_mm.animartrix_build_flags} + -D WLED_RELEASE_NAME=esp32S3_WROOM-2_M + -D WLEDMM_FASTPATH + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM ;; your board supports PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap + -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 ;; for Serial-to-USB chip + ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode + -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 + ;; -D LEDPIN=38 ;; buildin LED + ;; -D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1 + -D LEDPIN=12 -D BTNPIN=0 -D RLYPIN=-1 -D IRPIN=-1 -D AUDIOPIN=-1 ;; to avoid pin conflict with HUB75 + -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} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M} + ${common_mm.HUB75_lib_deps} + ${common_mm.animartrix_lib_deps} + ;; MM for esp32-s3 zero/supermini and lolin S3 mini boards - fastpath, optimize for speed [env:esp32S3_4MB_PSRAM_S] extends = env:esp32S3_8MB_S @@ -2081,14 +2186,16 @@ board_build.partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} -D WLED_RELEASE_NAME=esp32S3_4MB_S - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1 ;; optional - allows some buffers to use PSRAM -DLOLIN_WIFI_FIX -DWLEDMM_WIFI_POWERON_HACK ;; seems to work much better with this -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Serial-to-USB chip ;;-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode -D WLED_DISABLE_ADALIGHT ;; disables serial protocols - recommended for Hardware-CDC USB (Serial RX will receive junk commands when RX pin is unconnected, unless its pulled down by resistor) - ${Speed_Flags.build_flags} ;; optimize for speed instead of size + ${Speed_Flags.build_flags_V4} ;; optimize for speed instead of size -D WLEDMM_FASTPATH ;; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. + -D WLEDMM_SAVE_FLASH ${common_mm.animartrix_build_flags} -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D WLED_DISABLE_LOXONE ; FLASH 1272 bytes @@ -2117,7 +2224,8 @@ board_build.partitions = ${esp32.default_partitions} build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} ${common_mm.build_flags_M} -D WLED_RELEASE_NAME=esp32S3_4MB_M - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -DCONFIG_MBEDTLS_DYNAMIC_BUFFER=1 ;; optional - allows some buffers to use PSRAM -DLOLIN_WIFI_FIX -DWLEDMM_WIFI_POWERON_HACK ;; seems to work much better with this -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Serial-to-USB chip @@ -2180,7 +2288,8 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_DISABLE_ADALIGHT ;; disables serial protocols when using CDC USB (Serial RX will receive junk commands, unless its pulled down by resistor) -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -D SERVERNAME='"WLED-S2"' - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -D WLED_DISABLE_LOXONE ;; FLASH 1272 bytes -D WLED_DISABLE_HUESYNC ;; RAM 122 bytes; FLASH 6308 bytes -D WLED_DISABLE_ALEXA ;; RAM 116 bytes; FLASH 13524 bytes @@ -2215,7 +2324,7 @@ platform = ${esp32s2.platform} ;; using 5.2.0, due to platform_packages = ${esp32s2.platform_packages} board = lolin_s2_mini -board_build.partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, 256KB filesystem (esptool erase_flash needed when changing from "standard WLED" partitions) +board_build.partitions = ${esp32.extended_partitions} ;; 1.65MB firmware, 700KB filesystem (esptool erase_flash needed when changing from "standard WLED" partitions) board_build.flash_mode = dio upload_speed = 256000 ;; 921600 build_unflags = ${common.build_unflags} @@ -2224,7 +2333,6 @@ build_unflags = ${common.build_unflags} -D USERMOD_DALLASTEMPERATURE ;; disabled because it hangs during usermod setup on -S3 (autodetect broken?) -D WLED_ENABLE_DMX ;; disabled because it does not work with ESP-IDF 4.4.x (buggy driver in SparkFunDMX) -D WLED_ENABLE_DMX_INPUT ;; needs more testing - -DWLEDMM_FASTPATH ;; needs more testing on -S2 -D WLED_ENABLE_HUB75MATRIX build_flags = ${common.build_flags} ${esp32s2.build_flags} ;; ${Debug_Flags.build_flags} @@ -2232,7 +2340,8 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} ${common_mm.build_flags_S} ${common_mm.build_flags_M} -Wno-misleading-indentation -Wno-format-truncation -D WLED_RELEASE_NAME=esp32s2_4MB_M - -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM + -DBOARD_HAS_PSRAM ;; -D WLED_USE_PSRAM + -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -DLOLIN_WIFI_FIX -DWLEDMM_WIFI_POWERON_HACK ;; seems to work much better with this -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 -D WLED_DISABLE_ADALIGHT ;; disables serial protocols, as the board only has CDC USB @@ -2255,9 +2364,32 @@ lib_deps = ${esp32s2.lib_deps} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation OneWire ; not needed as we don't include USERMOD_DALLASTEMPERATURE + ${common_mm.HUB75_lib_ignore} + ${common_mm.DMXin_lib_ignore} monitor_filters = esp32_exception_decoder -; RAM: [== ] 21.8% (used 71304 bytes from 327680 bytes) -; Flash: [======== ] 84.0% (used 1596970 bytes from 1900544 bytes) +; RAM: [== ] 20.5% (used 67256 bytes from 327680 bytes) +; Flash: [========= ] 93.3% (used 1590266 bytes from 1703936 bytes) + +[env:esp32s2_PSRAM_S] +extends = env:esp32s2_PSRAM_M +board_build.partitions = ${esp32.default_partitions} ;; 1.55MB firmware, 1MB filesystem +build_unflags = ${env:esp32s2_PSRAM_M.build_unflags} + -DWLED_DISABLE_ADALIGHT + -D WLED_RELEASE_NAME=esp32s2_4MB_M + -DUSE_ALT_DISPLAY + -DUSERMOD_FOUR_LINE_DISPLAY + -DUSERMOD_ROTARY_ENCODER_UI + -DUSERMOD_ANIMARTRIX + ;; -DUSERMOD_ARTIFX ;; uncomment to reduce flash size +build_flags = ${env:esp32s2_PSRAM_M.build_flags} + -D WLED_RELEASE_NAME=esp32s2_4MB_S +lib_deps = ${env:esp32s2_PSRAM_M.lib_deps} +lib_ignore = ${env:esp32s2_PSRAM_M.lib_ignore} + U8g2 + ${common_mm.animartrix_lib_ignore} +; RAM: [== ] 20.4% (used 66792 bytes from 327680 bytes) +; Flash: [========= ] 94.8% (used 1490390 bytes from 1572864 bytes) + # ------------------------------------------------------------------------------ # esp32-C3 environments @@ -2285,6 +2417,7 @@ build_unflags = ${common.build_unflags} -D WLED_ENABLE_DMX_INPUT ;; needs more testing ;-D WLED_DEBUG_HOST='"192.168.x.x"' ;; to disable net print -D USERMOD_ANIMARTRIX ;; Tips our memory usage over the limit + -DUSERMOD_ARTIFX ;; over the limit -DWLEDMM_FASTPATH ;; needs more testing on -C3 build_flags = ${common.build_flags} ${esp32c3.build_flags} @@ -2315,9 +2448,8 @@ lib_ignore = ;IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation OneWire ; not needed as we don't include USERMOD_DALLASTEMPERATURE U8g2 ; not needed as we don't include USERMOD_FOUR_LINE_DISPLAY -; RAM: [=== ] 25.9% (used 84884 bytes from 327680 bytes) -; Flash: [==========] 98.9% (used 1555608 bytes from 1572864 bytes) - +; RAM: [== ] 24.6% (used 80732 bytes from 327680 bytes) +; Flash: [==========] 97.5% (used 1533360 bytes from 1572864 bytes) ;; MM environment for ESP32-C3 "mini" and "super mini" -> flash mode "dio" instead of "qio" (see #101) [env:esp32c3mini_dio_4MB_M] extends = env:esp32c3dev_4MB_M @@ -2334,8 +2466,8 @@ build_flags = ${env:esp32c3dev_4MB_M.build_flags} -D WLED_DISABLE_BROWNOUT_DET ;; the board only has a 500mA LDO, better to disable brownout detection -D WLED_DISABLE_ADALIGHT ;; to disable serial protocols for boards with CDC USB (Serial RX will receive junk commands, unless its pulled down by resistor) -D HW_PIN_SDA=0 -D HW_PIN_SCL=1 ;; avoid pin conflicts -; RAM: [=== ] 25.8% (used 84700 bytes from 327680 bytes) -; Flash: [==========] 98.7% (used 1552582 bytes from 1572864 bytes) +; RAM: [== ] 24.6% (used 80556 bytes from 327680 bytes) +; Flash: [==========] 97.3% (used 1530346 bytes from 1572864 bytes) [env:esp32c3dev_2MB_M] extends = env:esp32c3dev_4MB_M @@ -2361,8 +2493,8 @@ build_flags = ${env:esp32c3dev_4MB_M.build_flags} -D WLED_DISABLE_ADALIGHT ;; to disable serial protocols for boards with CDC USB (Serial RX will receive junk commands, unless its pulled down by resistor) -D HW_PIN_SDA=0 -D HW_PIN_SCL=1 ;; avoid pin conflicts -; RAM: [=== ] 25.3% (used 82828 bytes from 327680 bytes) -; Flash: [==========] 97.9% (used 1540138 bytes from 1572864 bytes) +; RAM: [== ] 24.0% (used 78676 bytes from 327680 bytes) +; Flash: [==========] 96.4% (used 1516068 bytes from 1572864 bytes) ;; MM environment for "seeed xiao -C3" boards [env:seeed_esp32c3_4MB_S] @@ -2396,9 +2528,8 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} lib_deps = ${esp32c3.lib_deps} ${common_mm.lib_deps_S} lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation monitor_filters = esp32_exception_decoder -; RAM: [== ] 23.6% (used 77460 bytes from 327680 bytes) -; Flash: [========= ] 91.7% (used 1442092 bytes from 1572864 bytes) - +; RAM: [== ] 22.4% (used 73388 bytes from 327680 bytes) +; Flash: [========= ] 92.7% (used 1458598 bytes from 1572864 bytes) # ------------------------------------------------------------------------------ # custom board environments # ------------------------------------------------------------------------------ @@ -2678,8 +2809,10 @@ board_build.flash_mode = qio build_unflags = ${env:esp32S3_8MB_M.build_unflags} ;; use the same as "normal" S3 buildenv -D ARDUINO_USB_CDC_ON_BOOT=1 ;; fix warning: "ARDUINO_USB_CDC_ON_BOOT" redefined; comment out for Serial debug + ${Speed_Flags.build_unflags} ;; to override -Os build_flags = ${common.build_flags} ${esp32s3.build_flags} -Wno-misleading-indentation -Wno-format-truncation ${common_mm.build_flags_S} + ${Speed_Flags.build_flags_V4} ;; -O2 -> optimize for speed instead of size -D WLED_RELEASE_NAME=matrixportal_esp32s3 -D SERVERNAME='"WLED-MatrixPortalS3"' ; Serial debug enabled -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 ;; for Hardware-CDC USB mode diff --git a/readme.md b/readme.md index c1d5efabae..83c488e4b4 100644 --- a/readme.md +++ b/readme.md @@ -23,6 +23,10 @@ More info here: HTML tutorial Donations will be used to buy WLED related hardware, software or drinks shared with the contributors of this repo. +## License +WLED-MM is licensed under the [EUPL-1.2](https://joinup.ec.europa.eu/collection/eupl) or later. +The official license text is [available in 23 languages](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12). + ## Contributing We welcome contributions to this project! See [contributing](https://github.com/MoonModules/WLED/blob/mdev/CONTRIBUTING.md) for more information. > We would like to have this repository in a polite and friendly atmosphere, so please be kind and respectful to others. For more details, look at [Code of Conduct](https://github.com/MoonModules/WLED/blob/mdev/CODE_OF_CONDUCT.md). diff --git a/tools/ESP32-Chip_info.hpp b/tools/ESP32-Chip_info.hpp index 417ee44915..f9c5ebecf4 100644 --- a/tools/ESP32-Chip_info.hpp +++ b/tools/ESP32-Chip_info.hpp @@ -469,7 +469,7 @@ void my_verbose_print_reset_reason(int reason) #endif /* - * parts below were created by softhack007, licenced under GPL v3.0 + * parts below were created by softhack007, licenced under EUPL-1.2 */ void show_psram_info_part1(void) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index a6fcbd7c52..a6e317e5f0 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -6,16 +6,8 @@ @repo https://github.com/MoonModules/WLED, submit changes to this file as PRs to MoonModules/WLED @Authors https://github.com/MoonModules/WLED/commits/mdev/ @Copyright © 2024 Github MoonModules Commit Authors (contact moonmodules@icloud.com for details) - @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + @license Licensed under the EUPL-1.2 or later - This file is part of the MoonModules WLED fork also known as "WLED-MM". - WLED-MM 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. - - WLED-MM 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 WLED-MM. If not, see . */ @@ -284,7 +276,7 @@ static volatile float micReal_max2 = 0.0f; // MicIn data max afte // 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) __attribute__((noreturn)); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results +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) static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels, bool i2sFastpath); // post-processing and post-amp of GEQ channels @@ -393,11 +385,11 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningful part of FFT result #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 #ifdef FFT_MAJORPEAK_HUMAN_EAR -static float pinkFactors[samplesFFT] = {0.0f}; // "pink noise" correction factors +static float* pinkFactors = nullptr; // "pink noise" correction factors constexpr float pinkcenter = 23.66; // sqrt(560) - center freq for scaling is 560 hz. constexpr float binWidth = SAMPLE_RATE / (float)samplesFFT; // frequency range of each FFT result bin #endif @@ -414,15 +406,6 @@ constexpr float binWidth = SAMPLE_RATE / (float)samplesFFT; // frequency range o #define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 #include -#if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19 - // arduinoFFT 2.x has a slightly different API - static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); -#else - // recommended version optimized by @softhack007 (API version 1.9) - static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors - static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); -#endif - // Helper functions // float version of map() @@ -460,6 +443,30 @@ constexpr bool skipSecondFFT = true; constexpr bool skipSecondFFT = false; #endif +// allocate FFT sample buffers from heap +static bool alocateFFTBuffers(void) { + #ifdef SR_DEBUG + USER_PRINT(F("\nFree heap ")); USER_PRINTLN(ESP.getFreeHeap()); + #endif + + if (vReal) free(vReal); // should not happen + if (vImag) free(vImag); // should not happen + if ((vReal = (float*) calloc(sizeof(float), samplesFFT)) == nullptr) return false; // calloc or die + if ((vImag = (float*) calloc(sizeof(float), samplesFFT)) == nullptr) return false; +#ifdef FFT_MAJORPEAK_HUMAN_EAR + if (pinkFactors) free(pinkFactors); + if ((pinkFactors = (float*) calloc(sizeof(float), samplesFFT)) == nullptr) return false; +#endif + + #ifdef SR_DEBUG + USER_PRINTLN("\nalocateFFTBuffers() completed successfully."); + USER_PRINT(F("Free heap: ")); USER_PRINTLN(ESP.getFreeHeap()); + USER_PRINT("FFTtask free stack: "); USER_PRINTLN(uxTaskGetStackHighWaterMark(NULL)); + USER_FLUSH(); + #endif + return(true); // success +} + // High-Pass "DC blocker" filter // see https://www.dsprelated.com/freebooks/filters/DC_Blocker.html static void runDCBlocker(uint_fast16_t numSamples, float *sampleBuffer) { @@ -496,9 +503,30 @@ void FFTcode(void * parameter) static bool isFirstRun = false; #ifdef FFT_USE_SLIDING_WINDOW - static float oldSamples[samplesFFT_2] = {0.0f}; // previous 50% of samples + static float* oldSamples = nullptr; // previous 50% of samples static bool haveOldSamples = false; // for sliding window FFT bool usingOldSamples = false; + if (!oldSamples) oldSamples = (float*) calloc(sizeof(float), samplesFFT_2); // allocate on first run + if (!oldSamples) { disableSoundProcessing = true; return; } // no memory -> die +#endif + + bool success = true; + if ((vReal == nullptr) || (vImag == nullptr)) success = alocateFFTBuffers(); // allocate sample buffers on first run + if (success == false) { disableSoundProcessing = true; return; } // no memory -> die + + // create FFT object - we have to do if after allocating buffers +#if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19 + // arduinoFFT 2.x has a slightly different API + static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); +#else + // recommended version optimized by @softhack007 (API version 1.9) + #if defined(WLED_ENABLE_HUB75MATRIX) && defined(CONFIG_IDF_TARGET_ESP32) + static float* windowWeighingFactors = nullptr; + if (!windowWeighingFactors) windowWeighingFactors = (float*) calloc(sizeof(float), samplesFFT); // cache for FFT windowing factors - use heap + #else + static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors - use global RAM + #endif + static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); #endif #ifdef FFT_MAJORPEAK_HUMAN_EAR @@ -542,7 +570,7 @@ void FFTcode(void * parameter) #endif // get a fresh batch of samples from I2S - memset(vReal, 0, sizeof(vReal)); // start clean + memset(vReal, 0, sizeof(float) * samplesFFT); // start clean #ifdef FFT_USE_SLIDING_WINDOW uint16_t readOffset; if (haveOldSamples && (doSlidingFFT > 0)) { @@ -635,7 +663,7 @@ void FFTcode(void * parameter) #endif // set imaginary parts to 0 - memset(vImag, 0, sizeof(vImag)); + memset(vImag, 0, sizeof(float) * samplesFFT); #ifdef FFT_USE_SLIDING_WINDOW memcpy(oldSamples, vReal+samplesFFT_2, sizeof(float) * samplesFFT_2); // copy last 50% to buffer (for sliding window FFT) @@ -762,14 +790,14 @@ void FFTcode(void * parameter) FFT_MajPeakSmth = FFT_MajPeakSmth + 0.42 * (FFT_MajorPeak - FFT_MajPeakSmth); // I like this "swooping peak" look } else { // skip second run --> clear fft results, keep peaks - memset(vReal, 0, sizeof(vReal)); + memset(vReal, 0, sizeof(float) * samplesFFT); } #if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS) haveDoneFFT = true; #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, sizeof(float) * samplesFFT); FFT_MajorPeak = 1; FFT_Magnitude = 0.001; } @@ -2823,6 +2851,16 @@ class AudioReactive : public Usermod { JsonObject top = root[FPSTR(_name)]; bool configComplete = !top.isNull(); +#ifdef ARDUINO_ARCH_ESP32 + // remember previous values + auto oldEnabled = enabled; + auto oldDMType = dmType; + auto oldI2SsdPin = i2ssdPin; + auto oldI2SwsPin = i2swsPin; + auto oldI2SckPin = i2sckPin; + auto oldI2SmclkPin = mclkPin; +#endif + configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); #ifdef ARDUINO_ARCH_ESP32 #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) @@ -2875,6 +2913,17 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled); configComplete &= getJsonValue(top["sync"][F("check_sequence")], audioSyncSequence); + // WLEDMM notify user when a reboot is necessary + #ifdef ARDUINO_ARCH_ESP32 + if (initDone) { + if ((audioSource != nullptr) && (oldDMType != dmType)) errorFlag = ERR_REBOOT_NEEDED; // changing mic type requires reboot + if ( (audioSource != nullptr) && (enabled==true) + && ((oldI2SsdPin != i2ssdPin) || (oldI2SsdPin != i2ssdPin) || (oldI2SckPin != i2sckPin)) ) errorFlag = ERR_REBOOT_NEEDED; // changing mic pins requires reboot + if ((audioSource != nullptr) && (oldI2SmclkPin != mclkPin)) errorFlag = ERR_REBOOT_NEEDED; // changing MCLK pin requires reboot + if ((oldDMType != dmType) && (oldDMType == 0)) errorFlag = ERR_POWEROFF_NEEDED; // changing from analog mic requires power cycle + if ((oldDMType != dmType) && (dmType == 0)) errorFlag = ERR_POWEROFF_NEEDED; // changing to analog mic requires power cycle + } + #endif return configComplete; } diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 91573956d1..5e95c6a15c 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -6,16 +6,7 @@ @repo https://github.com/MoonModules/WLED, submit changes to this file as PRs to MoonModules/WLED @Authors https://github.com/MoonModules/WLED/commits/mdev/ @Copyright © 2024 Github MoonModules Commit Authors (contact moonmodules@icloud.com for details) - @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - - This file is part of the MoonModules WLED fork also known as "WLED-MM". - WLED-MM 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. - - WLED-MM 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 WLED-MM. If not, see . + @license Licensed under the EUPL-1.2 or later */ @@ -37,7 +28,8 @@ #define SRate_t int #endif -constexpr i2s_port_t AR_I2S_PORT = I2S_NUM_0; // I2S port to use (do not change ! I2S_NUM_1 possible but this has limitation -> no MCLK routing, no ADC support) +constexpr i2s_port_t AR_I2S_PORT = I2S_NUM_0; // I2S port to use (do not change! I2S_NUM_1 possible but this has + // strong limitations -> no MCLK routing, no ADC support, no PDM support //#include //#include @@ -73,6 +65,11 @@ constexpr i2s_port_t AR_I2S_PORT = I2S_NUM_0; // I2S port to use (do not c // data type requested from the I2S driver - currently we always use 32bit //#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible +#if defined(WLED_ENABLE_HUB75MATRIX) && defined(CONFIG_IDF_TARGET_ESP32) + // this is bitter, but necessary to survive + #define I2S_USE_16BIT_SAMPLES +#endif + #ifdef I2S_USE_16BIT_SAMPLES #define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT #define I2S_datatype int16_t @@ -301,6 +298,9 @@ class I2SSource : public AudioSource { #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) if (ESP.getChipRevision() == 0) _config.use_apll = false; // APLL is broken on ESP32 revision 0 #endif + #if defined(WLED_ENABLE_HUB75MATRIX) + _config.use_apll = false; // APLL needed for HUB75 DMA driver ? + #endif #endif if (_i2sMaster == false) { diff --git a/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h b/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h index 4cc506715d..a4c557dd21 100644 --- a/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h +++ b/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h @@ -6,16 +6,7 @@ @repo https://github.com/MoonModules/WLED, submit changes to this file as PRs to MoonModules/WLED @Authors https://github.com/MoonModules/WLED/commits/mdev/ @Copyright © 2024 Github MoonModules Commit Authors (contact moonmodules@icloud.com for details) - @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - - This file is part of the MoonModules WLED fork also known as "WLED-MM". - WLED-MM 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. - - WLED-MM 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 WLED-MM. If not, see . + @license Licensed under the EUPL-1.2 or later */ diff --git a/wled00/FX.cpp b/wled00/FX.cpp index dd9c9b661d..093335da48 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3,25 +3,6 @@ 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. - Modified heavily for WLED */ @@ -29,6 +10,13 @@ #include "FX.h" #include "fcn_declare.h" +#ifdef WLEDMM_FASTPATH +#undef SEGMENT +#undef SEGENV +#define SEGMENT (*strip._currentSeg) // saves us many calls to strip._segments[strip.getCurrSegmentId()] +#define SEGENV SEGMENT +#endif + #define IBN 5100 // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) @@ -2399,7 +2387,7 @@ uint16_t mode_colortwinkle() { } } } - return FRAMETIME_FIXED_SLOW; + return FRAMETIME_FIXED; } static const char _data_FX_MODE_COLORTWINKLE[] PROGMEM = "Colortwinkles@Fade speed,Spawn speed;;!;;m12=0"; //pixels @@ -2601,7 +2589,7 @@ uint16_t ripple_base() } else {//randomly create new wave if (random16(IBN + 10000) <= (SEGMENT.intensity >> (SEGMENT.is2D()*3))) { ripples[i].state = 1; - ripples[i].pos = SEGMENT.is2D() ? ((random8(SEGENV.virtualWidth())<<8) | (random8(SEGENV.virtualHeight()))) : random16(SEGLEN); + ripples[i].pos = SEGMENT.is2D() ? ((random16(SEGENV.virtualWidth())<<8) | (random16(SEGENV.virtualHeight()))) : random16(SEGLEN); ripples[i].color = random8(); //color } } @@ -3408,7 +3396,7 @@ uint16_t candle(bool multi) } } - return FRAMETIME_FIXED_SLOW; + return FRAMETIME_FIXED; } @@ -3612,8 +3600,8 @@ uint16_t mode_exploding_fireworks(void) if (segs <= (strip.getMaxSegments() /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg - uint16_t numSparks = min(2 + ((rows*cols) >> 1), maxSparks); - uint16_t dataSize = sizeof(spark) * numSparks; + unsigned numSparks = min(5 + ((rows*cols) >> 1), maxSparks); + unsigned dataSize = sizeof(spark) * numSparks; if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed float *dying_gravity = reinterpret_cast(SEGENV.data + dataSize); @@ -3664,8 +3652,9 @@ uint16_t mode_exploding_fireworks(void) * Explosion happens where the flare ended. * Size is proportional to the height. */ - int nSparks = flare->pos + random8(4); - nSparks = constrain(nSparks, 4, numSparks); + unsigned nSparks = flare->pos + random8(4); + nSparks = std::max(nSparks, 4U); // This is not a standard constrain; numSparks is not guaranteed to be at least 4 + nSparks = std::min(nSparks, numSparks); // initialize sparks if (SEGENV.aux0 == 2) { @@ -5043,7 +5032,7 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so byte dy = lerp8by8(x2, y2, rate); //SEGMENT.setPixelColorXY(dx, dy, grad ? color.nscale8_video(255-rate) : color); // use addPixelColorXY for different look SEGMENT.addPixelColorXY(dx, dy, color); // use setPixelColorXY for different look - if (grad) SEGMENT.fadePixelColorXY(dx, dy, rate); + if (grad) SEGMENT.fadePixelColorXY(dx, dy, gamma8(rate)); } if (dot) { //add white point at the ends of line @@ -5074,9 +5063,21 @@ uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pa SEGMENT.fadeToBlackBy(64); + // WLEDMM optimized to prevent holes at height > 32 + int lastY1 = -1; + int lastY2 = -1; for (int i = 0; i < cols; i++) { - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); + int posY1 = beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ); + int posY2 = beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128); + if ((i==0) || ((abs(lastY1 - posY1) < 2) && (abs(lastY2 - posY2) < 2))) { // use original code when no holes + SEGMENT.setPixelColorXY(i, posY1, ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); + SEGMENT.setPixelColorXY(i, posY2, ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); + } else { // draw line to prevent holes + SEGMENT.drawLine(i-1, lastY1, i, posY1, ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); + SEGMENT.drawLine(i-1, lastY2, i, posY2, ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); + } + lastY1 = posY1; + lastY2 = posY2; } SEGMENT.blur(SEGMENT.intensity>>3); @@ -5137,6 +5138,7 @@ static const char _data_FX_MODE_2DDNASPIRAL[] PROGMEM = "DNA Spiral@Scroll speed // 2D Drift // ///////////////////////// uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmatelights.com/gallery/884-drift , Modified by: Andrew Tuline + // optimized for large panels by @softhack007 if (!strip.isMatrix) return mode_static(); // not a 2D set-up const uint16_t cols = SEGMENT.virtualWidth(); @@ -5147,22 +5149,40 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli SEGMENT.fill(BLACK); } - SEGMENT.fadeToBlackBy(128); + if (SEGMENT.intensity > 1) SEGMENT.fadeToBlackBy(128); + else SEGMENT.fill(BLACK); // WLEDMM fill is faster than fade + const float maxDim = max(cols, rows)/2.0f; - const uint16_t maxDim = MAX(cols, rows)/2; - unsigned long t = strip.now / (32 - (SEGMENT.speed>>3)); - unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup - for (float i = 1; i < maxDim; i += 0.25) { - float angle = radians(t * (maxDim - i)); - uint16_t myX = (cols>>1) + (uint16_t)(sinf(angle) * i) + (cols%2); - uint16_t myY = (rows>>1) + (uint16_t)(cosf(angle) * i) + (rows%2); - SEGMENT.setPixelColorXY(myX, myY, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); - } - SEGMENT.blur(SEGMENT.intensity>>3); + // WLEDMM calculate timebase in float, so we don't need to worry about rounding + const float strip_now = strip.now & 0x003FFFFF; // float can exactly represent numbers up to 22bit + float t; + if (maxDim < 6.0f) t = strip_now / float(16U - (SEGMENT.speed>>4)); // up to 12 (faster) + else if (maxDim <= 16.0f) t = strip_now / float(32U - (SEGMENT.speed>>3)); // 12..32 (standard) + else if (maxDim <= 32.0f) t = strip_now / float(64U - (SEGMENT.speed>>2)); // 32..64 (slower) + else t = strip_now / float(256U - SEGMENT.speed); // above 64 (slowest) + + // WLEDMM pre-calculate some values to speed up the main loop + const int colsCenter = (cols >> 1) + (cols % 2); + const int rowsCenter = (rows >> 1) + (rows % 2); + unsigned t_20 = t/20.0f; // softhack007: pre-calculating this gives about 10% speedup + const float step = (maxDim < 6.0f) ? 0.52f : (maxDim > 24.0f) ? 0.16666666f : 0.25f; // WLEDMM more detail on larger panels + for (float i = 1.0f; i <= maxDim; i += step) { + unsigned i_20 = i * 20.0f; + float t_maxdim = t * (maxDim - i); + float angle = float(DEG_TO_RAD) * t_maxdim; + int mySin = sinf(angle) * i; + int myCos = cosf(angle) * i; + + if ((unsigned(colsCenter+mySin) < cols) && (unsigned(rowsCenter+myCos) < rows)) // don't draw invisible pixels + SEGMENT.setPixelColorXY(colsCenter+mySin, rowsCenter+myCos, ColorFromPalette(SEGPALETTE, i_20 + t_20, 255, LINEARBLEND)); + if ((SEGMENT.check1) && (unsigned(colsCenter+myCos) < cols) && (unsigned(rowsCenter+mySin) < rows)) + SEGMENT.setPixelColorXY(colsCenter+myCos, rowsCenter+mySin, ColorFromPalette(SEGPALETTE, i_20 + t_20, 255, LINEARBLEND)); // twin mode + } + SEGMENT.blur(SEGMENT.intensity>>((!SEGMENT.check2) * 3), SEGMENT.check2); // user-defined blur - thanks @dedehai return FRAMETIME; } // mode_2DDrift() -static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount;;!;2"; +static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur,,,,Twin,Smear;;!;2;ix=0"; ////////////////////////// @@ -5228,22 +5248,69 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f /////////////////////////////////////////// -// 2D Cellular Automata Game of life // +// 2D Cellular Automata Game of Life // /////////////////////////////////////////// -static bool getBitValue(const uint8_t* byteArray, size_t n) { - size_t byteIndex = n / 8; - size_t bitIndex = n % 8; - uint8_t byte = byteArray[byteIndex]; - return (byte >> bitIndex) & 1; -} -static void setBitValue(uint8_t* byteArray, size_t n, bool value) { - size_t byteIndex = n / 8; - size_t bitIndex = n % 8; - if (value) - byteArray[byteIndex] |= (1 << bitIndex); - else - byteArray[byteIndex] &= ~(1 << bitIndex); -} +typedef struct Cell { + uint8_t alive : 1, neighbors : 3, toggleStatus : 1, superDead : 1, oscillatorCheck : 1, spaceshipCheck : 1; +} Cell; + +class GameOfLifeGrid { + private: + Cell* cells; + const int cols, rows, maxIndex; + const int nOffsets[8] = {-cols-1, -cols, -cols+1, -1, 1, cols-1, cols, cols+1}; // Neighbor offsets + const int8_t offsetX[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; + const int8_t offsetY[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; + public: + GameOfLifeGrid(Cell* data, int c, int r) : cells(data), cols(c), rows(r), maxIndex(r * c) {} + void getNeighborIndexes(unsigned neighbors[9], unsigned cIndex, unsigned x, unsigned y, bool wrap) { + unsigned neighborCount = 0; + bool edgeCell = x == 0 || x == cols-1 || y == 0 || y == rows-1; + for (unsigned i = 0; i < 8; ++i) { + unsigned nIndex = cIndex + nOffsets[i]; + if (edgeCell) { + int nX = x + offsetX[i], nY = y + offsetY[i]; + if (wrap) { + if (nX < 0) nIndex += cols; + else if (nX >= cols) nIndex -= cols; + if (nY < 0) nIndex += maxIndex; + else if (nY >= rows) nIndex -= maxIndex; + } + else { // Wrap disabled, skip out of bound neighbors + if (nX < 0 || nX >= cols || nY < 0 || nY >= rows) continue; + } + } + neighbors[++neighborCount] = nIndex; + } + neighbors[0] = neighborCount; + } + void setCell(unsigned cIndex, unsigned x, unsigned y, bool alive, bool wrap) { + Cell* cell = &cells[cIndex]; + if (alive == cell->alive) return; // No change + cell->alive = alive; + unsigned neighbors[9]; + getNeighborIndexes(neighbors, cIndex, x, y, wrap); + int val = alive ? 1 : -1; + for (unsigned i = 1; i <= neighbors[0]; ++i) cells[neighbors[i]].neighbors += val; + } + void recalculateEdgeNeighbors(bool wrap) { + unsigned cIndex = 0; + for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) { + Cell* cell = &cells[cIndex]; + if (x == 0 || x == cols - 1 || y == 0 || y == rows - 1) { + cell->neighbors = 0; + cell->superDead = 0; + + unsigned neighbors[9]; + getNeighborIndexes(neighbors, cIndex, x, y, wrap); + + for (unsigned i = 1; i <= neighbors[0]; ++i) { + if (cells[neighbors[i]].alive) ++cell->neighbors; + } + } + } + } +}; uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ // and https://github.com/DougHaber/nlife-color , Modified By: Brandon Butler @@ -5251,177 +5318,200 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - const size_t dataSize = ((SEGMENT.length() + 7) / 8); // round up to nearest byte - const size_t detectionSize = sizeof(uint16_t) * 3 + 1; // 2 CRCs, gliderLength, soloGlider boolean - const size_t totalSize = dataSize * 2 + detectionSize + sizeof(uint8_t); // detectionSize + prevPalette + const size_t dataSize = SEGMENT.length() * sizeof(Cell); // Cell = 2 bytes + const size_t totalSize = dataSize + 6; // 6 bytes for prevRows(2), prevCols(2), prevPalette, prevWrap if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed - byte *cells = reinterpret_cast(SEGENV.data); - byte *futureCells = reinterpret_cast(SEGENV.data + dataSize); - uint16_t *gliderLength = reinterpret_cast(SEGENV.data + dataSize * 2); - uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint16_t)); - uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint16_t) * 2); - bool *soloGlider = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint16_t) * 3); - uint8_t *prevPalette = reinterpret_cast(SEGENV.data + dataSize * 2 + detectionSize); - - uint16_t &generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability + uint16_t *prevRows = reinterpret_cast(SEGENV.data); + uint16_t *prevCols = reinterpret_cast(SEGENV.data + 2); + uint8_t *prevPalette = reinterpret_cast (SEGENV.data + 4); + bool *prevWrap = reinterpret_cast (SEGENV.data + 5); + Cell *cells = reinterpret_cast (SEGENV.data + 6); + + uint16_t& generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability + uint16_t& gliderLength = SEGENV.aux1; bool allColors = SEGMENT.check1; bool overlayBG = SEGMENT.check2; bool wrap = SEGMENT.check3; bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode - byte blur = bgBlendMode ? map2(SEGMENT.custom1 - 220, 0, 35, 255, 128) : map2(SEGMENT.custom1, 0, 255, 255, 0); + byte blur = overlayBG ? 255 : bgBlendMode ? map2(SEGMENT.custom1 - 220, 0, 35, 255, 128) : map2(SEGMENT.custom1, 0, 220, 255, 10); uint32_t bgColor = SEGCOLOR(1); - uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0); - if (SEGENV.call == 0) { + GameOfLifeGrid grid(cells, cols, rows); + + // If rows or cols change due to mirror/transpose, neighbor counts need to be recalculated. Just reset the game. + bool setup = SEGENV.call == 0 || rows != *prevRows || cols != *prevCols; + + if (setup) { SEGMENT.setUpLeds(); - SEGMENT.fill(BLACK); // to make sure that segment buffer and physical leds are aligned initially + SEGMENT.fill(bgColor); // to make sure that segment buffer and physical leds are aligned initially + SEGENV.step = 0; + *prevRows = rows; + *prevCols = cols; + + // Calculate glider length LCM(rows,cols)*4 once + uint8_t a = rows; + uint8_t b = cols; + while (b) { + uint8_t t = b; + b = a % b; + a = t; + } + gliderLength = cols * rows / a * 4; } + + if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; // Timebase jump fix + bool paused = SEGENV.step > strip.now; + // Setup New Game of Life - if ((SEGENV.call == 0 || generation == 0) && SEGENV.step < strip.now) { + if ((!paused && generation == 0) || setup) { SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds + paused = true; generation = 1; + *prevWrap = wrap; *prevPalette = SEGMENT.palette; - random16_set_seed(strip.now>>2); //seed the random generator + //Setup Grid memset(cells, 0, dataSize); - for (unsigned x = 0; x < cols; x++) for (unsigned y = 0; y < rows; y++) { - if (random8(100) < 32) { // ~32% chance of being alive - setBitValue(cells, y * cols + x, true); - if (overlayBG) SEGMENT.setPixelColorXY(x,y, allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); - else SEGMENT.setPixelColorXY(x,y, bgColor); // Initial color set in redraw loop + random16_set_seed(strip.now>>2); //seed the random generator + unsigned cIndex = 0; + for (unsigned y = 0; y < rows; ++y) { + #if defined(ARDUINO_ARCH_ESP32) + random16_add_entropy(esp_random() & 0xFFFF); + #endif + for (unsigned x = 0; x < cols; ++x, ++cIndex) { + if ((random16() & 0xFF) < 82) { // ~32% + grid.setCell(cIndex, x, y, true, wrap); + cells[cIndex].toggleStatus = 1; // Used to set initial color + } + else cells[cIndex].superDead = 1; } } - memcpy(futureCells, cells, dataSize); - - //Set CRCs - uint16_t crc = crc16((const unsigned char*)cells, dataSize); - *oscillatorCRC = crc; - *spaceshipCRC = crc; - - //Calculate glider length LCM(rows,cols)*4 - uint8_t a = rows; - uint8_t b = cols; - while (b) { - uint8_t t = b; - b = a % b; - a = t; - } - *gliderLength = cols * rows / a * 4; } - bool blurDead = SEGENV.step > strip.now && blur !=255 && !bgBlendMode && !overlayBG; bool palChanged = SEGMENT.palette != *prevPalette && !allColors; - bool newGame = generation == 1; if (palChanged) *prevPalette = SEGMENT.palette; - // Redraw Loop - // Redraw if paused (remove blur), palette changed, overlaying background (avoid flicker) - // Generation 1 draws alive cells randomly and fades dead cells - if (blurDead || newGame || palChanged || overlayBG) { - for (unsigned x = 0; x < cols; x++) for (unsigned y = 0; y < rows; y++) { - unsigned cIndex = y * cols + x; - uint32_t cellColor = SEGMENT.getPixelColorXY(x,y); - bool alive = getBitValue(cells, cIndex); - bool aliveBgColor = (!overlayBG && alive && newGame && cellColor == bgColor ); - - if ( alive && (palChanged || (aliveBgColor && !random(12)))) { // Palette change or spawn initial colors randomly - uint32_t randomColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); - SEGMENT.setPixelColorXY(x,y, randomColor); // Recolor alive cells + // Enter redraw loop if not updating or palette changed. + if (palChanged || paused || (SEGMENT.speed != 255 && strip.now - SEGENV.step < 1000 / map2(SEGMENT.speed,0,254,1,60))) { //(1 - 60) updates/sec 255 is uncapped + // Redraw if paused (remove blur), palette changed, overlaying background if not max speed (avoid flicker) + // Generation 1 draws alive cells randomly and fades dead cells + bool newGame = generation == 1; + if (paused || palChanged || overlayBG) { + unsigned cIndex = 0; + for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) { + Cell& cell = cells[cIndex]; + if (!newGame && cell.superDead) continue; // Skip super dead cells unless new game + bool needsColor = (newGame && cell.toggleStatus); + if (cell.alive) { + if ((needsColor && !random(10)) || palChanged) { + cell.toggleStatus = 0; + uint32_t randomColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); + SEGMENT.setPixelColorXY(x,y, randomColor); // Palette changed or needs initial color + } + else if (overlayBG && !needsColor) SEGMENT.setPixelColorXY(x,y, SEGMENT.getPixelColorXY(x,y)); // Redraw alive cells for overlayBG + } // Dead + else if (paused && !overlayBG) { + uint32_t cellColor = SEGMENT.getPixelColorXY(x,y); + uint32_t blended = color_blend(cellColor, bgColor, bgBlendMode ? 16 : blur); + if (blended == cellColor) blended = bgColor; // color_blend fix + if ((bgBlendMode && newGame) || !bgBlendMode) SEGMENT.setPixelColorXY(x, y, blended); // Blur dead cells when paused + } } - else if ( alive && overlayBG && !aliveBgColor) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells for overlayBG - if (!alive && palChanged && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette - else if (!alive && blurDead) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur));// Blur dead cells (paused) - else if (!alive && !overlayBG && generation == 1) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, 16)); // Fade dead cells on generation 1 } + return FRAMETIME; } - - if (!SEGMENT.speed || SEGENV.step > strip.now || (SEGMENT.speed != 255 && strip.now - SEGENV.step < 1000 / map2(SEGMENT.speed,0,254,0,60))) return FRAMETIME; //(0 - 60) updates/sec 255 is uncapped - - //Update Game of Life - unsigned aliveCount = 0; // Detects dead grids and solo gliders - bool disableWrap = !wrap || (generation % 1500 == 0 || *soloGlider); // Disable wrap every 1500 generations to prevent undetected repeats - //Loop through all cells. Count neighbors, apply rules, setPixel - for (unsigned x = 0; x < cols; x++) for (unsigned y = 0; y < rows; y++) { - unsigned cIndex = y * cols + x; - bool cellValue = getBitValue(cells, cIndex); + + // Repeat detection + unsigned aliveCount = 0; // Detects empty grids and solo gliders (for smaller grids) + bool updateOscillator = generation % 16 == 0; + bool updateSpaceship = gliderLength && generation % gliderLength == 0; + bool repeatingOscillator = true, repeatingSpaceship = true; + + // First Loop: Applies rules, sets toggleStatus, detects repeating patterns + // Does not update cells yet to prevent neighbor counts from changing mid-loop + int maxIndex = rows * cols; + for (unsigned i = 0; i < maxIndex; ++i) { + Cell& cell = cells[i]; + + if (cell.alive) ++aliveCount; + if (repeatingOscillator && cell.oscillatorCheck != cell.alive) repeatingOscillator = false; + if (repeatingSpaceship && cell.spaceshipCheck != cell.alive) repeatingSpaceship = false; + if (updateOscillator) cell.oscillatorCheck = cell.alive; + if (updateSpaceship) cell.spaceshipCheck = cell.alive; + + if (cell.alive && (cell.neighbors < 2 || cell.neighbors > 3)) cell.toggleStatus = 1; // Loneliness or Overpopulation + else if (!cell.alive && cell.neighbors == 3) { cell.toggleStatus = 1; cell.superDead = 0; } // Reproduction + else cell.toggleStatus = 0; // No change + } + + uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); // Backup color + if (generation <= 8 && !bgBlendMode) blur = 255 - (((generation-1) * (255 - blur)) >> 3); // Ramp up blur for first 8 generations + + bool disableWrap = !wrap || generation % 1500 == 0 || aliveCount == 5; // Disable wrap every 1500 generations to prevent undetected repeats + if (*prevWrap != !disableWrap) { grid.recalculateEdgeNeighbors(!disableWrap); *prevWrap = !disableWrap; } + + // Second Loop: Updates cells, sets colors, and detects super dead cells + unsigned cIndex = 0; + for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) { + Cell& cell = cells[cIndex]; + if (cell.superDead) continue; // Skip super dead cells (bgColor dead cells) + uint32_t cellColor = SEGMENT.getPixelColorXY(x, y); - if (cellValue) aliveCount++; - - unsigned neighbors = 0, colorCount = 0; - unsigned neighborIndexes[3]; - - // Count neighbors and store indexes, get neighbor colors later if needed - for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // Iterate through all neighbors - if (i == 0 && j == 0) continue; // Ignore self - if (i == 1 && j == 0 && !cellValue && !neighbors) break; // Cell can't be born with no neighbors and 2 remaining checks - int nX = x + i; - int nY = y + j; - if (nX < 0) {if (disableWrap) continue; nX = cols - 1;} - else if (nX >= cols) {if (disableWrap) continue; nX = 0;} - if (nY < 0) {if (disableWrap) continue; nY = rows - 1;} - else if (nY >= rows) {if (disableWrap) continue; nY = 0;} - - unsigned nIndex = nY * cols + nX; // Neighbor cell index - if (getBitValue(cells, nIndex)) { - ++neighbors; - if (neighbors > 3) break; // Cell dies, stop neighbor loop - neighborIndexes[neighbors - 1] = nIndex; // Store alive neighbor index - } - } - if (!cellValue && neighbors != 3 && cellColor == bgColor) continue; // Skip dead cells with no neighbors and no color - - // Rules of Life - if (cellValue && (neighbors < 2 || neighbors > 3)) { - // Loneliness or Overpopulation - setBitValue(futureCells, cIndex, false); - if (!overlayBG) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur)); - } - else if (neighbors == 3 && !cellValue) { - // Reproduction - // Get Colors - uint32_t nColors[3]; - for (int i = 0; i < 3; i++) { - unsigned nIndex = neighborIndexes[i]; - if (!getBitValue(futureCells, nIndex)) continue; // Parent just died, color lost or blended - uint32_t nColor = SEGMENT.getPixelColorXY(nIndex % cols, nIndex / cols); - if (nColor == bgColor) continue; - color = nColor; // Update last seen color - nColors[colorCount++] = nColor; - + if (cell.toggleStatus) { + if (cell.alive) { // Dies + grid.setCell(cIndex, x, y, false, !disableWrap); + if (cellColor != bgColor) color = cellColor; + if (!overlayBG) SEGMENT.setPixelColorXY(x,y, blur == 255 ? bgColor : color_blend(cellColor, bgColor, blur)); + if (blur == 255 || bgBlendMode) cell.superDead = 1; + } + else { // Reproduction + grid.setCell(cIndex, x, y, true, !disableWrap); + uint32_t birthColor = color; + if (random8() < SEGMENT.intensity) birthColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); + else { + // Get Colors + uint32_t nColors[8]; + unsigned colorCount = 0; + unsigned neighbors[9]; + grid.getNeighborIndexes(neighbors, cIndex, x, y, !disableWrap); + + for (unsigned i = 1; i <= neighbors[0]; ++i) { + unsigned nIndex = neighbors[i]; + if (cells[nIndex].alive) { + uint32_t nColor = SEGMENT.getPixelColorXY(nIndex % cols, nIndex / cols); + if (nColor == bgColor) continue; + nColors[colorCount++] = nColor; + } + } + if (colorCount) { birthColor = nColors[random8(colorCount)]; color = birthColor; } + } + SEGMENT.setPixelColorXY(x,y, birthColor); } - setBitValue(futureCells, cIndex, true); - uint32_t birthColor = colorCount ? nColors[random8(colorCount)] : color; // Uses last seen color if no surviving neighbors - // Mutate color chance - if (random8() < SEGMENT.intensity) birthColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); - SEGMENT.setPixelColorXY(x,y, birthColor); } - else { // Blur dead cells and redraw alive cells - if (cellValue) SEGMENT.setPixelColorXY(x, y, cellColor == bgColor ? color : cellColor); // Redraw alive, fixes fading cells - else if (blur != 255 && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x, y, color_blend(cellColor, bgColor, blur)); + else { // No change in status + if (cell.alive) { + if (cellColor == bgColor) cellColor = color; else color = cellColor; + SEGMENT.setPixelColorXY(x, y, cellColor); // Redraw alive cells + } + else { // Blur dead + if (blur != 255 && !overlayBG && !bgBlendMode) { + uint32_t blended = color_blend(cellColor, bgColor, blur); // color_blend doesn't always converge to bgColor (this fix needed for fast fps with custom bgColor) + if (blended == cellColor) { blended = bgColor; cell.superDead = 1; } + SEGMENT.setPixelColorXY(x, y, blended); + } + } } - } - //update cell values - memcpy(cells, futureCells, dataSize); - - // Get current crc value - uint16_t crc = crc16((const unsigned char*)cells, dataSize); - bool repetition = false; - if (!aliveCount || crc == *oscillatorCRC || crc == *spaceshipCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values - if (repetition) { + if (repeatingOscillator || repeatingSpaceship || !aliveCount) { generation = 0; // reset on next call SEGENV.step += 1000; // pause final generation for 1 second return FRAMETIME; } - // Update CRC values - if (generation % 16 == 0) *oscillatorCRC = crc; - if (*gliderLength && generation % *gliderLength == 0) *spaceshipCRC = crc; - if (aliveCount == 5) *soloGlider = true; else *soloGlider = false; - generation++; + ++generation; SEGENV.step = strip.now; return FRAMETIME; } // mode_2Dgameoflife() @@ -5430,9 +5520,22 @@ static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color M ///////////////////////// // 2D SnowFall // ///////////////////////// - +static bool getBitValue(const uint8_t* byteArray, size_t n) { + size_t byteIndex = n / 8; + size_t bitIndex = n % 8; + uint8_t byte = byteArray[byteIndex]; + return (byte >> bitIndex) & 1; +} +static void setBitValue(uint8_t* byteArray, size_t n, bool value) { + size_t byteIndex = n / 8; + size_t bitIndex = n % 8; + if (value) + byteArray[byteIndex] |= (1 << bitIndex); + else + byteArray[byteIndex] &= ~(1 << bitIndex); +} uint16_t mode_2DSnowFall(void) { // By: Brandon Butler - // Uses Game of Life style bit array to track snow/particles + // Uses bit array to track snow/particles if (!strip.isMatrix) return mode_static(); // Not a 2D set-up const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); @@ -5458,6 +5561,9 @@ uint16_t mode_2DSnowFall(void) { // By: Brandon Butler } } + // fix SEGENV.step in case that timebase jumps + if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; + uint8_t speed = map(SEGMENT.speed, 0, 255, 0, 60); // Updates per second if (!speed || strip.now - SEGENV.step < 1000 / speed) return FRAMETIME; // Not enough time passed @@ -5596,8 +5702,11 @@ uint16_t mode_2DJulia(void) { // An animated Julia set SEGMENT.intensity = 24; } - julias->xcen = julias->xcen + (float)(SEGMENT.custom1 - 128)/100000.f; - julias->ycen = julias->ycen + (float)(SEGMENT.custom2 - 128)/100000.f; + // WLEDMM limit drift, so we don't move away into nothing + constexpr float maxCenter = 2.5f; // just an educated guess + if (fabsf(julias->xcen) < maxCenter) julias->xcen = julias->xcen + (float)(SEGMENT.custom1 - 128)/100000.f; + if (fabsf(julias->ycen) < maxCenter) julias->ycen = julias->ycen + (float)(SEGMENT.custom2 - 128)/100000.f; + julias->xymag = julias->xymag + (float)((SEGMENT.custom3 - 16)<<3)/100000.f; // reduced resolution slider if (julias->xymag < 0.01f) julias->xymag = 0.01f; if (julias->xymag > 1.0f) julias->xymag = 1.0f; @@ -5669,11 +5778,23 @@ uint16_t mode_2DJulia(void) { // An animated Julia set } y += dy; } -// SEGMENT.blur(64); + + // WLEDMM + if(SEGMENT.check1) + SEGMENT.blurRows(48, false); // slight blurr + if(SEGMENT.check2) + SEGMENT.blur(64, true); // strong blurr + if(SEGMENT.check3) { // draw crosshair + int screenX = lround((0.5f / maxCenter) * (julias->xcen + maxCenter) * float(cols)); + int screenY = lround((0.5f / maxCenter) * (julias->ycen + maxCenter) * float(rows)); + int hair = min(min(cols-1, rows-1)/2, 3); + SEGMENT.drawLine(screenX, screenY-hair, screenX, screenY+hair, GREEN, true); + SEGMENT.drawLine(screenX-hair, screenY, screenX+hair, screenY, GREEN, true); + } return FRAMETIME; } // mode_2DJulia() -static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per pixel,X center,Y center,Area size;!;!;2;ix=24,c1=128,c2=128,c3=16"; +static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per pixel,X center,Y center,Area size,Soft Blur,Strong Blur,Show Center;!;!;2;ix=24,c1=128,c2=128,c3=16"; ////////////////////////////// @@ -5781,7 +5902,7 @@ uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. // spawn new falling code if (random8() <= SEGMENT.intensity || emptyScreen) { - uint8_t spawnX = random8(cols); + uint16_t spawnX = random16(cols); SEGMENT.setPixelColorXY(spawnX, 0, spawnColor); // update hint for next run SEGENV.aux0 = spawnX; @@ -5931,9 +6052,11 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); const float maxRows = (rows <= 32) ? 32.0f : (rows <= 64) ? 64.0f : 128.0f; // WLEDMM safe up to 128x128 - const float minScale = (rows <= 32) ? 12.0f : (rows <= 64) ? 4.6f : 2.1f; // WLEDMM + const float minScale = (rows <= 32) ? 12.0f : (rows <= 64) ? 7.2f : 4.6f; // WLEDMM + const float maxCols = (cols <= 32) ? 32.0f : (cols <= 64) ? 64.0f : 128.0f; // WLEDMM safe up to 128x128 const CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000}; + const CRGBPalette16 &effectPalette = SEGENV.check1 ? SEGPALETTE : auroraPalette; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -5941,7 +6064,7 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } float adjustHeight = mapf(rows, 8, maxRows, 28, minScale); // maybe use mapf() ??? // WLEDMM yes! - uint16_t adjScale = map2(cols, 8, 64, 310, 63); + uint16_t adjScale = map2(cols, 8, maxCols, 310, 63); // WLEDMM adjustHeight = max(min(adjustHeight, 28.0f), minScale); // WLEDMM bugfix for larger fixtures adjScale = max(min(adjScale, uint16_t(310)), uint16_t(63)); // WLEDMM @@ -5961,11 +6084,11 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } */ uint16_t _scale = map2(SEGMENT.intensity, 0, 255, 30, adjScale); - byte _speed = map2(SEGMENT.speed, 0, 255, 128, 16); + byte _speed = map2(SEGMENT.speed, 0, 255, 136, 20); //WLEDMM add SuperSync control uint16_t xStart, xEnd, yStart, yEnd; - if (SEGMENT.check1) { //Master (sync on needs to show the whole effect, children only their first panel) + if (SEGMENT.check2) { //Master (sync on needs to show the whole effect, children only their first panel) xStart = strip.panel[0].xOffset; xEnd = strip.panel[0].xOffset + strip.panel[0].width; yStart = strip.panel[0].yOffset; @@ -5983,16 +6106,17 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https for (int x = xStart; x < xEnd; x++) { for (int y = yStart; y < yEnd; y++) { SEGENV.step++; - SEGMENT.setPixelColorXY(x, y, ColorFromPalette(auroraPalette, + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(effectPalette, qsub8( inoise8((SEGENV.step%2) + x * _scale, y * 16 + SEGENV.step % 16, SEGENV.step / _speed), fabsf(rows_2 - (float)y) * adjustHeight))); // WLEDMM } } + if (SEGENV.check3) SEGMENT.blurRows(192); return FRAMETIME; } // mode_2DPolarLights() -static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale,,,,SuperSync;;;2"; +static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale,,,,Use Palette,SuperSync, Blur;;!;2"; ///////////////////////// @@ -6254,8 +6378,8 @@ uint16_t mode_2Dcrazybees(void) { void aimed(uint_fast16_t w, uint_fast16_t h) { if (!true) //WLEDMM SuperSync random16_set_seed(strip.now); - aimX = random8(0, w); - aimY = random8(0, h); + aimX = random8(0, min(UINT8_MAX, int(w))); + aimY = random8(0, min(UINT8_MAX, int(h))); hue = random8(); deltaX = abs(aimX - posX); deltaY = abs(aimY - posY); @@ -6274,8 +6398,8 @@ uint16_t mode_2Dcrazybees(void) { SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); for (size_t i = 0; i < n; i++) { - bee[i].posX = random8(0, cols); - bee[i].posY = random8(0, rows); + bee[i].posX = random8(0, min(UINT8_MAX, int(cols))); + bee[i].posY = random8(0, min(UINT8_MAX, int(rows))); bee[i].aimed(cols, rows); } } @@ -6426,6 +6550,9 @@ uint16_t mode_2Dfloatingblobs(void) { if (!SEGENV.allocateData(sizeof(blob_t))) return mode_static(); //allocation failed blob_t *blob = reinterpret_cast(SEGENV.data); + // WLEDMM fix SEGENV.step in case that timebase jumps + if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; + if (SEGENV.call == 0) {SEGENV.setUpLeds(); SEGMENT.fill(BLACK);} if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { SEGENV.aux0 = cols; // re-initialise if virtual size changes @@ -6535,8 +6662,27 @@ uint16_t mode_2Dscrollingtext(void) { unsigned maxLen = (SEGMENT.name) ? min(32, (int)strlen(SEGMENT.name)) : 0; // WLEDMM make it robust against too long segment names if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; const bool zero = strchr(text, '0') != nullptr; + bool drawShadow = (SEGMENT.check2); // "shadow" is only needed for overlays to improve readability + + // #ERR = show last error code + if ((strlen(text) > 3) && (strncmp_P(text,PSTR("#ERR"),4) == 0)) { + // read wled error code, and keep it for 30sec max + static byte lastErr = ERR_NONE; // errorFlag cache - we can use a static (global) variable here because the error code is global, too + static unsigned long lastErrTime = 0; // time when lastErr was updated + if ((errorFlag != ERR_NONE) && (lastErr != errorFlag)) { // new error code arrived + lastErr = errorFlag; + lastErrTime = millis(); + } + bool haveError = (lastErr != ERR_NONE) && (millis() - lastErrTime < 30000); // true if we have an "active" error code + if (SEGENV.call < 512) haveError = true; // for testing - initially show "E00" + if ((!haveError) && (errorFlag == ERR_NONE)) lastErr = ERR_NONE; // reset error code + // print error number + if (haveError) sprintf_P(text, PSTR("E%-2.2d"), (int)lastErr); + else sprintf_P(text, PSTR(" ")); + } if (!strlen(text) || !strncmp_P(text,PSTR("#F"),2) || !strncmp_P(text,PSTR("#P"),2) || !strncmp_P(text,PSTR("#A"),2) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#DDMM"),5) || !strncmp_P(text,PSTR("#MMDD"),5) || !strncmp_P(text,PSTR("#TIME"),5) || !strncmp_P(text,PSTR("#HH"),3) || !strncmp_P(text,PSTR("#MM"),3)) { // fallback if empty segment name: display date and time + if (!strncmp_P(text,PSTR("#D"),2) || !strncmp_P(text,PSTR("#MM"),3) || !strncmp_P(text,PSTR("#HH"),3)) drawShadow = false; // no seconds - no shadow needed char sec[5]= {'\0'}; byte AmPmHour = hour(localTime); boolean isitAM = true; @@ -6553,23 +6699,30 @@ uint16_t mode_2Dscrollingtext(void) { else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime)); else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), AmPmHour); else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), minute(localTime)); - else if (!strncmp_P(text,PSTR("#FPS"),4)) sprintf_P(text, PSTR("%2d"), (int) strip.getFps()); // WLEDMM - else if ((!strncmp_P(text,PSTR("#AMP"),4)) || (!strncmp_P(text,PSTR("#POW"),4))) sprintf_P(text, PSTR("%3.2fA"), float(strip.currentMilliamps)/1000.0f); // WLEDMM + else if (!strncmp_P(text,PSTR("#FPS"),4)) sprintf_P(text, PSTR("%3d"), (int) strip.getFps()); // WLEDMM + else if ((!strncmp_P(text,PSTR("#AMP"),4)) || (!strncmp_P(text,PSTR("#POW"),4))) sprintf_P(text, PSTR("%3.1fA"), float(strip.currentMilliamps)/1000.0f); // WLEDMM else sprintf_P(text, PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); - } + } else drawShadow = false; // static text does not require shadow const int numberOfLetters = strlen(text); - if (SEGENV.step < strip.now) { + long delayTime = long(strip.now) - long(SEGENV.step); + if ((delayTime >= 0) || (abs(delayTime) > 1500)) { // WLEDMM keep on scrolling if timebase jumps (supersync, or brightness off, or wifi delay) if ((numberOfLetters * letterWidth) > cols) ++SEGENV.aux0 %= (numberOfLetters * letterWidth) + cols; // offset else SEGENV.aux0 = (cols + (numberOfLetters * letterWidth))/2; - ++SEGENV.aux1 &= 0xFF; // color shift - SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 10*FRAMETIME_FIXED, 2*FRAMETIME_FIXED); + SEGENV.aux1 = (SEGENV.aux1 + 1) & 0xFF; // color shift // WLEDMM changed to prevent overflow + SEGENV.step = strip.now + map2(SEGMENT.speed, 0, 255, 10*FRAMETIME_FIXED, 2*FRAMETIME_FIXED); if (!SEGMENT.check2) { for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++ ) SEGMENT.blendPixelColorXY(x, y, SEGCOLOR(1), 255 - (SEGMENT.custom1>>1)); } + } else { // WLEDMM "repaint" segment to prevent flickering + if (!SEGMENT.check2) { + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) + SEGMENT.blendPixelColorXY(x, y, SEGCOLOR(1), 255 - SEGMENT.custom1); // slightly reduced "blending" to keep trails visible + } } - bool drawShadow = (SEGMENT.check2) && (SEGMENT.custom1 == 0); + + if (SEGENV.check2 && ((numberOfLetters * letterWidth) > cols)) drawShadow = true; // scrolling overlay is easier to read with shadow for (int i = 0; i < numberOfLetters; i++) { if (int(cols) - int(SEGENV.aux0) + letterWidth*(i+1) < 0) continue; // don't draw characters off-screen uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0); @@ -6598,7 +6751,13 @@ uint16_t mode_2Ddriftrose(void) { const float CX = (cols-cols%2)/2.f - .5f; const float CY = (rows-rows%2)/2.f - .5f; - const float L = min(cols, rows) / 2.f; + + unsigned L2 = SEGENV.check3 ? max(cols, rows) : min(cols, rows); // WLEDMM we use "max" to use the complete segment + if (SEGENV.check3 && (abs(int(cols) - int(rows)) < 4)) L2 = L2 * 1.4142f; // WLEDMM make "expand" look a bit bigger on square panels + const float L = L2 / 2.f; + // WLEDMM pre-calculate some values + const uint32_t wu_cols = SEGMENT.virtualWidth() * 256; + const uint32_t wu_rows = SEGMENT.virtualHeight() * 256; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -6607,15 +6766,16 @@ uint16_t mode_2Ddriftrose(void) { SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3)); for (size_t i = 1; i < 37; i++) { - uint32_t x = (CX + (sinf(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; - uint32_t y = (CY + (cosf(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; - SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); + float angle = float(DEG_TO_RAD) * i * 10; + uint32_t x = int((CX + (sinf(angle) * (beatsin8(i, 0, L2)-L))) * 255.f); + uint32_t y = int((CY + (cosf(angle) * (beatsin8(i, 0, L2)-L))) * 255.f); + if ((x < wu_cols) && (y < wu_rows)) SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); } SEGMENT.blur((SEGMENT.intensity>>4)+1); return FRAMETIME; } -static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;;2"; +static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur,,,,,,Full Expand ☾;;;2"; #endif // WLED_DISABLE_2D @@ -7982,7 +8142,7 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. colorIndex = map(x, 0, cols-1, 0, 255); //WLEDMM } lastBandHeight = bandHeight; // remember BandHeight (left side) for next iteration - uint16_t barHeight = map(bandHeight, 0, 255, 0, rows); // Now we map bandHeight to barHeight. do not subtract -1 from rows here + uint16_t barHeight = map2(bandHeight, 0, 255, 0, rows); // Now we map bandHeight to barHeight. do not subtract -1 from rows here // WLEDMM end if (barHeight > rows) barHeight = rows; // WLEDMM map() can "overshoot" due to rounding errors @@ -7991,7 +8151,7 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. uint32_t ledColor = BLACK; if ((! SEGMENT.check1) && (barHeight > 0)) { // use faster drawLine when single-color bars are needed ledColor = SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0); - SEGMENT.drawLine(int(x), max(0,int(rows)-barHeight-1), int(x), int(rows-1), ledColor, false); // max(0, ...) to prevent negative Y + SEGMENT.drawLine(int(x), max(0,int(rows)-barHeight), int(x), int(rows-1), ledColor, false); // max(0, ...) to prevent negative Y } else { for (int y=0; y < barHeight; y++) { if (SEGMENT.check1) //color_vertical / color bars toggle @@ -8160,10 +8320,11 @@ uint16_t mode_2DAkemi(void) { int xMax = cols/8; for (int x=0; x < xMax; x++) { size_t band = map2(x, 0, max(xMax,4), 0, 15); // map 0..cols/8 to 16 GEQ bands - CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); + uint32_t color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); band = constrain(band, 0, 15); uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); + barHeight = constrain(barHeight, 0, (rows/2)+1); // map() may overshoot for (int y=0; y < barHeight; y++) { SEGMENT.setPixelColorXY(x, rows/2-y, color); SEGMENT.setPixelColorXY(cols-1-x, rows/2-y, color); @@ -8369,7 +8530,7 @@ uint16_t mode_2Doctopus() { const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - const uint8_t mapp = 180 / MAX(cols,rows); + const uint16_t mapp = max(1, 180 / MAX(cols,rows)); // WLEDMM make sure this value is not 0 typedef struct { uint8_t angle; @@ -8406,11 +8567,11 @@ uint16_t mode_2Doctopus() { SEGENV.aux1 = rows; *offsX = SEGMENT.custom1; *offsY = SEGMENT.custom2; - const uint8_t C_X = cols / 2 + (SEGMENT.custom1 - 128)*cols/255; - const uint8_t C_Y = rows / 2 + (SEGMENT.custom2 - 128)*rows/255; + const uint16_t C_X = cols / 2 + (SEGMENT.custom1 - 128)*cols/255; + const uint16_t C_Y = rows / 2 + (SEGMENT.custom2 - 128)*rows/255; for (int x = xStart; x < xEnd; x++) { for (int y = yStart; y < yEnd; 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 } } @@ -8463,16 +8624,7 @@ static const char _data_FX_MODE_2DWAVINGCELL[] PROGMEM = "Waving Cell@!,,Amplitu @repo https://github.com/MoonModules/WLED, submit changes to this file as PRs to MoonModules/WLED @Authors https://github.com/MoonModules/WLED/commits/mdev/ @Copyright © 2024 Github MoonModules Commit Authors (contact moonmodules@icloud.com for details) - @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - - This function is part of the MoonModules WLED fork also known as "WLED-MM". - WLED-MM 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. - - WLED-MM 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 WLED-MM. If not, see . + @license Licensed under the EUPL-1.2 or later */ ///////////////////////// diff --git a/wled00/FX.h b/wled00/FX.h index 8f8d83b37c..c06934629d 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -2,24 +2,6 @@ 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. Modified for WLED */ @@ -61,20 +43,24 @@ bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented #endif /* Not used in all effects yet */ +#define FPS_UNLIMITED 250 +#define FPS_UNLIMITED_AC 0 // WLEDMM upstream uses "0 fps" for unlimited. We support both ways #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) // WLEDMM go faster on ESP32 -#define WLED_FPS 120 -#define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) -#define WLED_FPS_SLOW 60 -#define FRAMETIME_FIXED_SLOW (15) // = 66 FPS => 1000/66 -//#define FRAMETIME _frametime #define FRAMETIME strip.getFrameTime() +#define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // WLEDMM support higher framerates (up to 250fps) -- 5/8 = 62% +#define WLED_FPS 120 +#define WLED_FPS_SLOW 60 +#define FRAMETIME_FIXED 24 // used in Blurz, Freqmap, Scrolling text, Colortwinkles, Candle +//#define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) +#define FRAMETIME_FIXED_SLOW (15) // = 66 FPS => 1000/66 // used in Solid #else #define WLED_FPS 42 #define FRAMETIME_FIXED (1000/WLED_FPS) -#define WLED_FPS_SLOW 42 +#define WLED_FPS_SLOW 42 #define FRAMETIME_FIXED_SLOW (1000/WLED_FPS_SLOW) -//#define FRAMETIME _frametime #define FRAMETIME strip.getFrameTime() +//#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // Upstream legacy +#define MIN_SHOW_DELAY (_frametime < 16 ? (_frametime <8? (_frametime <7? (_frametime <6 ? 2 :3) :4) : 8) : 15) // WLEDMM support higher framerates (up to 250fps) #endif /* each segment uses 52 bytes of SRAM memory, so if you're application fails because of @@ -100,8 +86,6 @@ bool strip_uses_global_leds(void) __attribute__((pure)); // WLEDMM implemented assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */ #define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments()) -#define MIN_SHOW_DELAY (_frametime < 16 ? (_frametime <8? (_frametime <7? (_frametime <6 ? 2 :3) :4) : 8) : 15) // WLEDMM support higher framerates (up to 250fps) - #define NUM_COLORS 3 /* number of colors per segment */ #define SEGMENT strip._segments[strip.getCurrSegmentId()] #define SEGENV strip._segments[strip.getCurrSegmentId()] @@ -396,6 +380,7 @@ typedef struct Segment { }; uint8_t grouping, spacing; uint8_t opacity; + bool needsBlank; // WLEDMM indicates that Segment needs to be blanked (due to change of mirror / reverse / transpose / spacing) uint32_t colors[NUM_COLORS]; uint8_t cct; //0==1900K, 255==10091K uint8_t custom1, custom2; // custom FX parameters/sliders @@ -405,8 +390,8 @@ typedef struct Segment { bool check2 : 1; // checkmark 2 bool check3 : 1; // checkmark 3 }; - uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows - uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows + uint16_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows, but we cannot be sure. + uint16_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows, but we cannot be sure. char *name = nullptr; // WLEDMM initialize to nullptr // runtime data @@ -446,6 +431,7 @@ typedef struct Segment { bool _firstFill = true; // dirty HACK support uint16_t _2dWidth = 0; // virtualWidth uint16_t _2dHeight = 0; // virtualHeight + uint16_t _virtuallength = 0; // virtualLength void setPixelColorXY_slow(int x, int y, uint32_t c); // set relative pixel within segment with color - full slow version #else @@ -504,6 +490,7 @@ typedef struct Segment { grouping(1), spacing(0), opacity(255), + needsBlank(false), colors{DEFAULT_COLOR,BLACK,BLACK}, cct(127), custom1(DEFAULT_C1), @@ -578,9 +565,9 @@ typedef struct Segment { 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 width(void) const { return (stop > start) ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) inline uint16_t height(void) const { return (stopY > startY) ? (stopY - startY) : 0; } // segment height (if 2D) in physical pixels // WLEDMM make sure its always > 0 - inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels + inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels // WLEDMM fishy ... need to double-check if this is correct inline uint16_t groupLength(void) const { return max(1, grouping + spacing); } // WLEDMM length = 0 could lead to div/0 in virtualWidth() and virtualHeight() inline uint8_t getLightCapabilities(void) const { return _capabilities; } @@ -613,6 +600,7 @@ typedef struct Segment { * Safe to call from interrupts and network requests. */ inline void markForReset(void) { reset = true; } // setOption(SEG_OPTION_RESET, true) + inline void markForBlank(void) { needsBlank = true; } // WLEDMM serialize "blank" requests, avoid parallel drawing from different task void setUpLeds(void); // set up leds[] array for loseless getPixelColor() // transition functions @@ -637,7 +625,12 @@ typedef struct Segment { void setCurrentPalette(void); // 1D strip - uint16_t virtualLength(void) const; + uint16_t calc_virtualLength(void) const; +#ifndef WLEDMM_FASTPATH + inline uint16_t virtualLength(void) const {return calc_virtualLength();} +#else + inline uint16_t virtualLength(void) const {return _virtuallength;} +#endif void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } // automatically inline @@ -678,12 +671,14 @@ typedef struct Segment { if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED return vWidth; } + inline uint16_t calc_virtualWidth() const { return virtualWidth();} inline uint16_t virtualHeight() const { // WLEDMM use fast types, and make function inline uint_fast16_t groupLen = groupLength(); uint_fast16_t vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED return vHeight; } + inline uint16_t calc_virtualHeight() const { return virtualHeight();} #else inline uint16_t virtualWidth() const { return(_2dWidth);} // WLEDMM get pre-calculated virtualWidth inline uint16_t virtualHeight() const { return(_2dHeight);} // WLEDMM get pre-calculated virtualHeight @@ -862,6 +857,7 @@ class WS2812FX { // 96 bytes customMappingTableSize(0), //WLEDMM customMappingSize(0), _lastShow(0), + _lastServiceShow(0), _segment_index(0), _mainSegment(0) { @@ -905,7 +901,7 @@ class WS2812FX { // 96 bytes purgeSegments(bool force = false), setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(uint8_t n), - restartRuntime(), + restartRuntime(bool doReset=true), resetSegments(bool boundsOnly = false), //WLEDMM add boundsOnly makeAutoSegments(bool forceReset = false), fixInvalidSegments(), @@ -1021,10 +1017,10 @@ class WS2812FX { // 96 bytes } panelO; //panelOrientation typedef struct panel_t { - uint8_t xOffset; // x offset relative to the top left of matrix in LEDs. WLEDMM 8 bits/256 is enough - uint8_t yOffset; // y offset relative to the top left of matrix in LEDs. WLEDMM 8 bits/256 is enough - uint8_t width; // width of the panel - uint8_t height; // height of the panel + uint16_t xOffset; // x offset relative to the top left of matrix in LEDs. + uint16_t yOffset; // y offset relative to the top left of matrix in LEDs. + uint16_t width; // width of the panel + uint16_t height; // height of the panel union { uint8_t options; struct { @@ -1066,6 +1062,9 @@ class WS2812FX { // 96 bytes // and color transitions uint32_t _colors_t[3]; // color used for effect (includes transition) uint16_t _virtualSegmentLength; +#ifdef WLEDMM_FASTPATH + segment* _currentSeg = nullptr; // WLEDMM speed up SEGMENT access +#endif std::vector _segments; friend class Segment; @@ -1104,6 +1103,7 @@ class WS2812FX { // 96 bytes uint16_t customMappingSize; /*uint32_t*/ unsigned long _lastShow; // WLEDMM avoid losing precision + unsigned long _lastServiceShow; // WLEDMM last call of strip.show (timestamp) uint8_t _segment_index; uint8_t _mainSegment; diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 34eefb58c3..f6d2bc0b6c 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -1,25 +1,5 @@ /* 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. - Parts of the code adapted from WLED Sound Reactive: Copyright (c) 2022 Andrew Tuline, Ewoud Wijma, Harm Aldick */ #include "wled.h" @@ -149,11 +129,11 @@ void WS2812FX::setUpMatrix() { } if (needLedMap && customMappingTable != nullptr) { // softhack007 - uint16_t x, y, pix=0; //pixel + uint_fast16_t x, y, pix=0; //pixel for (size_t pan = 0; pan < panel.size(); pan++) { Panel &p = panel[pan]; - uint16_t h = p.vertical ? p.height : p.width; - uint16_t v = p.vertical ? p.width : p.height; + uint_fast16_t h = p.vertical ? p.height : p.width; + uint_fast16_t v = p.vertical ? p.width : p.height; for (size_t j = 0; j < v; j++){ for(size_t i = 0; i < h; i++) { y = (p.vertical?p.rightStart:p.bottomStart) ? v-j-1 : j; @@ -272,11 +252,13 @@ void Segment::startFrame(void) { _isSuperSimpleSegment = !mirror && !mirror_y && (grouping == 1) && (spacing == 0); // fastest - we only draw one pixel per call #ifdef WLEDMM_FASTPATH - _isValid2D = isActive() && is2D(); + //_isValid2D = isActive() && is2D(); + _isValid2D = isActive() && strip.isMatrix && length() > 1; _brightness = currentBri(on ? opacity : 0); // if (reverse_y) _isSimpleSegment = false; // for A/B testing - _2dWidth = is2D() ? calc_virtualWidth() : virtualLength(); _2dHeight = calc_virtualHeight(); + _2dWidth = _isValid2D ? calc_virtualWidth() : calc_virtualLength(); + _virtuallength = calc_virtualLength(); #if 0 && defined(WLED_ENABLE_HUB75MATRIX) _firstFill = true; // dirty HACK #endif @@ -302,7 +284,7 @@ void IRAM_ATTR __attribute__((hot)) Segment::setPixelColorXY_fast(int x, int y, } #if 0 // this is still a dangerous optimization - if ((i < UINT_MAX) && sameColor && (call > 0) && (!transitional) && (ledsrgb[i] == CRGB(scaled_col))) return; // WLEDMM looks like nothing to do + if ((i < UINT_MAX) && sameColor && (call > 0) && (!transitional) && (mode != FX_MODE_2DSCROLLTEXT) && (ledsrgb[i] == CRGB(scaled_col))) return; // WLEDMM looks like nothing to do #endif // handle reverse and transpose @@ -319,9 +301,9 @@ void IRAM_ATTR __attribute__((hot)) Segment::setPixelColorXY_fast(int x, int y, #endif if (simpleSegment) return; // WLEDMM shortcut when no mirroring needed - // handle mirroring - const int_fast16_t wid_ = stop - start; - const int_fast16_t hei_ = stopY - startY; + // handle mirroring - minimum width/height is 1 !!! + const int_fast16_t wid_ = max(1,stop - start); + const int_fast16_t hei_ = max(1, stopY - startY); if (mirror) { //set the corresponding horizontally mirrored pixel if (transpose) strip.setPixelColorXY_fast(start + x, startY + hei_ - y - 1, scaled_col); else strip.setPixelColorXY_fast(start + wid_ - x - 1, startY + y, scaled_col); @@ -364,7 +346,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: } #if 0 // this is a dangerous optimization - if ((i < UINT_MAX) && sameColor && (call > 0) && (!transitional) && (ledsrgb[i] == CRGB(col))) return; // WLEDMM looks like nothing to do + if ((i < UINT_MAX) && sameColor && (call > 0) && (!transitional) && (mode != FX_MODE_2DSCROLLTEXT) && (ledsrgb[i] == CRGB(col))) return; // WLEDMM looks like nothing to do #endif if (reverse ) x = cols - x - 1; @@ -383,8 +365,8 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: } const uint_fast16_t glen_ = groupLength(); // WLEDMM optimization - const uint_fast16_t wid_ = width(); - const uint_fast16_t hei_ = height(); + const uint_fast16_t wid_ = max(uint16_t(1), width()); + const uint_fast16_t hei_ = max(uint16_t(1), height()); x *= glen_; // expand to physical pixels y *= glen_; // expand to physical pixels @@ -825,8 +807,13 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 int y = int(intersectY); if (steep) std::swap(x,y); // temporarily swap if steep // pixel coverage is determined by fractional part of y co-ordinate - setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); - setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true)); + + // WLEDMM added out-of-bounds check: "unsigned(x) < cols" catches negative numbers _and_ too large values + if ((unsigned(x) < unsigned(cols)) && (unsigned(y) < unsigned(rows))) setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); + int xx = x+int(steep); + int yy = y+int(!steep); + if ((unsigned(xx) < unsigned(cols)) && (unsigned(yy) < unsigned(rows))) setPixelColorXY(xx, yy, color_blend(c, getPixelColorXY(xx, yy), seep, true)); + intersectY += gradient; if (steep) std::swap(x,y); // restore if steep } @@ -847,8 +834,8 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 void Segment::drawArc(unsigned x0, unsigned y0, int radius, uint32_t color, uint32_t fillColor) { if (!isActive() || (radius <=0)) return; // not active - float minradius = float(radius) - .5; - float maxradius = float(radius) + .5; + float minradius = float(radius) - .5f; + float maxradius = float(radius) + .5f; // WLEDMM pre-calculate values to speed up the loop const int minradius2 = roundf(minradius * minradius); const int maxradius2 = roundf(maxradius * maxradius); @@ -861,17 +848,18 @@ void Segment::drawArc(unsigned x0, unsigned y0, int radius, uint32_t color, uint const int starty = max(0, int(y0)-radius-1); const int endy = min(height, int(y0)+radius+1); - for (int x=startx; x= minradius2) && (distance2 <= maxradius2)) { - setPixelColorXY(x, y, color); - } else { - if (fillColor != 0) - if (distance2 < minradius2) - setPixelColorXY(x, y, fillColor); + for (int x=startx; x= minradius2) && (distance2 <= maxradius2)) { + setPixelColorXY(x, y, color); + } else { + if (fillColor != 0) + if (distance2 < minradius2) + setPixelColorXY(x, y, fillColor); + } } } } @@ -931,10 +919,11 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, const int font = w*h; CRGB col = CRGB(color); - CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col); + CRGBPalette16 grad = CRGBPalette16(col, (col2 != BLACK) ? CRGB(col2) : col); uint32_t bgCol = SEGCOLOR(1); //if (w<5 || w>6 || h!=8) return; + if (drawShadow) w++; // one more column for shadow on right side for (int i = 0; i= 0) || (x0 < cols)) { if ((bits>>(j+(8-w))) & 0x01) { // bit set & drawing on-screen - setPixelColorXY(x0, y0, col); + setPixelColorXY(x0, y0, fgCol); } else { if (drawShadow) { // WLEDMM diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 88f1e1b6b8..f98f5dcbc9 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -2,25 +2,6 @@ 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. - Modified heavily for WLED */ #include "wled.h" @@ -296,6 +277,15 @@ void Segment::resetIfRequired() { next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; // setOption(SEG_OPTION_RESET, false); startFrame(); // WLEDMM update cached propoerties + if (isActive() && !freeze) fill(BLACK); // WLEDMM start clean + DEBUG_PRINTLN("Segment reset"); + } else if (needsBlank) { + startFrame(); // WLEDMM update cached propoerties + if (isActive() && !freeze) { + fill(BLACK); // WLEDMM start clean + DEBUG_PRINTLN("Segment blanked"); + needsBlank = false; + } } } @@ -497,7 +487,7 @@ void Segment::setCurrentPalette() { // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms unsigned long timeMS = millis() - _t->_start; - uint16_t noOfBlends = (255U * timeMS / _t->_dur) - _t->_prevPaletteBlends; + uint16_t noOfBlends = min(64UL, (255U * timeMS / _t->_dur) - _t->_prevPaletteBlends); // WLEDMM limit to 64 blends at once, prevent rollover for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); _currentPalette = _t->_palT; // copy transitioning/temporary palette } @@ -529,7 +519,7 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t stateChanged = true; // send UDP/WS broadcast - if (stop>start) fill(BLACK); //turn old segment range off // WLEDMM stop > start + if (stop>start) markForBlank(); //turn old segment range off // WLEDMM stop > start if (i2 <= i1) { //disable segment stop = 0; markForReset(); @@ -656,13 +646,13 @@ uint16_t Segment::nrOfVStrips() const { if (is2D()) { switch (map1D2D) { case M12_pBar: - vLen = virtualWidth(); + vLen = calc_virtualWidth(); break; case M12_sCircle: //WLEDMM - vLen = (virtualWidth() + virtualHeight()) / 6; // take third of the average width + vLen = (calc_virtualWidth() + calc_virtualHeight()) / 6; // take third of the average width break; case M12_sBlock: //WLEDMM - vLen = (virtualWidth() + virtualHeight()) / 8; // take half of the average width + vLen = (calc_virtualWidth() + calc_virtualHeight()) / 8; // take half of the average width break; } } @@ -701,11 +691,7 @@ class JMapC { if (size > 0) return size; else -#ifndef WLEDMM_FASTPATH - return SEGMENT.virtualWidth() * SEGMENT.virtualHeight(); //pixels -#else return SEGMENT.calc_virtualWidth() * SEGMENT.calc_virtualHeight(); // calc pixel sizes -#endif } void setPixelColor(uint16_t i, uint32_t col) { updatejMapDoc(); @@ -797,11 +783,7 @@ class JMapC { jMapFile.close(); maxWidth++; maxHeight++; -#ifndef WLEDMM_FASTPATH - scale = min(SEGMENT.virtualWidth() / maxWidth, SEGMENT.virtualHeight() / maxHeight); // WLEDMM use native min/max -#else scale = min(SEGMENT.calc_virtualWidth() / maxWidth, SEGMENT.calc_virtualHeight() / maxHeight); // WLEDMM re-calc width/heiht from active settings -#endif dataSize += sizeof(jVectorMap); USER_PRINT("dataSize "); USER_PRINT(dataSize); @@ -877,20 +859,26 @@ static int getPinwheelLength(int vW, int vH) { #endif // 1D strip -uint16_t Segment::virtualLength() const { +uint16_t Segment::calc_virtualLength() const { #ifndef WLED_DISABLE_2D if (is2D()) { - uint16_t vW = virtualWidth(); - uint16_t vH = virtualHeight(); + uint16_t vW = calc_virtualWidth(); + uint16_t vH = calc_virtualHeight(); uint16_t vLen = vW * vH; // use all pixels from segment switch (map1D2D) { case M12_pBar: vLen = vH; break; case M12_pCorner: - case M12_pArc: vLen = max(vW,vH); // get the longest dimension break; + case M12_pArc: + { unsigned vLen2 = vW * vW + vH * vH; // length ^2 + if (vLen2 < UINT16_MAX) vLen = sqrt16(vLen2); // use faster function for 16bit values + else vLen = sqrtf(vLen2); // fall-back to float if bigger + if (vW != vH) vLen++; // round up + } + break; case M12_jMap: //WLEDMM jMap if (jMap) vLen = ((JMapC *)jMap)->length(); @@ -903,7 +891,7 @@ uint16_t Segment::virtualLength() const { if (nrOfVStrips()>1) vLen = max(vW,vH) * 4;//0.5; // get the longest dimension else - vLen = max(vW,vH) * 0.5; // get the longest dimension + vLen = max(vW,vH) * 0.5f; // get the longest dimension break; case M12_sPinwheel: vLen = getPinwheelLength(vW, vH); @@ -914,30 +902,30 @@ uint16_t Segment::virtualLength() const { #endif uint16_t groupLen = groupLength(); uint16_t vLength = (length() + groupLen - 1) / groupLen; - if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED + if (mirror && width() > 1) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED // WLEDMM bugfix for pseudo 2d strips return vLength; } //WLEDMM used for M12_sBlock static void xyFromBlock(uint16_t &x,uint16_t &y, uint16_t i, uint16_t vW, uint16_t vH, uint16_t vStrip) { float i2; - if (i<=SEGLEN*0.25) { //top, left to right - i2 = i/(SEGLEN*0.25); + if (i<=SEGLEN*0.25f) { //top, left to right + i2 = i/(SEGLEN*0.25f); x = vW / 2 - vStrip - 1 + i2 * vStrip * 2; y = vH / 2 - vStrip - 1; } - else if (i <= SEGLEN * 0.5) { //right, top to bottom - i2 = (i-SEGLEN*0.25)/(SEGLEN*0.25); + else if (i <= SEGLEN * 0.5f) { //right, top to bottom + i2 = (i-SEGLEN*0.25f)/(SEGLEN*0.25f); x = vW / 2 + vStrip; y = vH / 2 - vStrip - 1 + i2 * vStrip * 2; } - else if (i <= SEGLEN * 0.75) { //bottom, right to left - i2 = (i-SEGLEN*0.5)/(SEGLEN*0.25); + else if (i <= SEGLEN * 0.75f) { //bottom, right to left + i2 = (i-SEGLEN*0.5f)/(SEGLEN*0.25f); x = vW / 2 + vStrip - i2 * vStrip * 2; y = vH / 2 + vStrip; } else if (i <= SEGLEN) { //left, bottom to top - i2 = (i-SEGLEN*0.75)/(SEGLEN*0.25); + i2 = (i-SEGLEN*0.75f)/(SEGLEN*0.25f); x = vW / 2 - vStrip - 1; y = vH / 2 + vStrip - i2 * vStrip * 2; } @@ -973,6 +961,7 @@ void IRAM_ATTR_YN __attribute__((hot)) Segment::setPixelColor(int i, uint32_t co if (i==0) setPixelColorXY(0, 0, col); else { + if (i == virtualLength() - 1) setPixelColorXY(vW-1, vH-1, col); // Last i always fill corner if (!_isSuperSimpleSegment) { // WLEDMM: drawArc() is faster if it's NOT "super simple" as the regular M12_pArc // can do "useSymmetry" to speed things along, but a more complicated segment likey @@ -1129,7 +1118,7 @@ void IRAM_ATTR_YN __attribute__((hot)) Segment::setPixelColor(int i, uint32_t co // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed) int x = 0, y = 0; if (virtualHeight()>1) y = i; - if (virtualWidth() >1) x = i; + else if (virtualWidth() >1) x = i; setPixelColorXY(x, y, col); return; } @@ -1237,11 +1226,28 @@ uint32_t __attribute__((hot)) Segment::getPixelColor(int i) const if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1); else return getPixelColorXY(0, vH - i -1); break; - case M12_pArc: case M12_pCorner: - // use longest dimension - return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); + case M12_pArc: { + if (i < max(vW, vH)) { + return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); // Corner and Arc + break; + } + float minradius = float(i) - 0.1f; + const int minradius2 = roundf(minradius * minradius); + int startX, startY; + if (vW >= vH) {startX = vW - 1; startY = 1;} // Last Column + else {startX = 1; startY = vH - 1;} // Last Row + // Loop through only last row/column depending on orientation + for (int x = startX; x < vW; x++) { + int newX2 = x * x; + for (int y = startY; y < vH; y++) { + int newY2 = y * y; + if (newX2 + newY2 >= minradius2) return getPixelColorXY(x, y); + } + } + return getPixelColorXY(vW-1, vH-1); // Last pixel break; + } case M12_jMap: //WLEDMM jMap if (jMap) return ((JMapC *)jMap)->getPixelColor(i); @@ -1802,7 +1808,10 @@ void WS2812FX::finalizeInit(void) for (uint8_t i=0; igetStart() + bus->getLength() > MAX_LEDS) break; + if (bus->getStart() + bus->getLength() > MAX_LEDS) { + USER_PRINT("\nError: too many LEDs, max number is "); USER_PRINTLN(MAX_LEDS); + break; + } //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. @@ -1876,17 +1885,30 @@ void WS2812FX::service() { if (OTAisRunning) return; // WLEDMM avoid flickering during OTA now = nowUp + timebase; - #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) - if ((_frametime > 2) && (_frametime < 32) && (nowUp - _lastShow) < (_frametime/2)) return; // WLEDMM experimental - stabilizes frametimes but increases CPU load - else if (nowUp - _lastShow < MIN_SHOW_DELAY) return; // WLEDMM fallback - #else - if (nowUp - _lastShow < MIN_SHOW_DELAY) return; + unsigned long elapsed = nowUp - _lastServiceShow; + #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) // WLEDMM go faster on ESP32 + //if (_suspend) return; + if (elapsed < 2) return; // keep wifi alive + if ( !_triggered && (_targetFps != FPS_UNLIMITED) && (_targetFps != FPS_UNLIMITED_AC)) { +#if 0 + if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service - delivers higher fps +#else + if ((elapsed+1) < _frametime) return; // code from upstream - stricter on FPS +#endif + } + #else // legacy + if (elapsed < _frametime) return; #endif + bool doShow = false; + unsigned speedLimit = (_targetFps != FPS_UNLIMITED) && (_targetFps != FPS_UNLIMITED_AC) ? (0.85f * FRAMETIME) : 1; // WLEDMM minimum for effect frametime _isServicing = true; _segment_index = 0; for (segment &seg : _segments) { +#ifdef WLEDMM_FASTPATH + _currentSeg = &seg; +#endif // reset the segment runtime data if needed seg.resetIfRequired(); @@ -1900,7 +1922,7 @@ void WS2812FX::service() { uint16_t frameDelay = FRAMETIME; // WLEDMM avoid name clash with "delay" function if (!seg.freeze) { //only run effect function if not frozen - _virtualSegmentLength = seg.virtualLength(); + _virtualSegmentLength = seg.calc_virtualLength(); _colors_t[0] = seg.currentColor(0, seg.colors[0]); _colors_t[1] = seg.currentColor(1, seg.colors[1]); _colors_t[2] = seg.currentColor(2, seg.colors[2]); @@ -1908,12 +1930,16 @@ void WS2812FX::service() { if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(seg.cct, true), correctWB); for (uint8_t c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]); - +#if 0 // WARNING this would kill _supersync_ + now = millis() + timebase; +#endif seg.startFrame(); // WLEDMM // effect blending (execute previous effect) // actual code may be a bit more involved as effects have runtime data including allocated memory //if (seg.transitional && seg._modeP) (*_mode[seg._modeP])(progress()); frameDelay = (*_mode[seg.currentMode(seg.mode)])(); + + if (frameDelay < speedLimit) frameDelay = FRAMETIME; // WLEDMM limit effects that want to go faster than target FPS if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; if (seg.transitional && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition @@ -1927,8 +1953,17 @@ void WS2812FX::service() { _virtualSegmentLength = 0; busses.setSegmentCCT(-1); if(doShow) { +#if 0 && defined(ARDUINO_ARCH_ESP32) // EXPERIMENTAL - enabled this to enforce stricter frametime limits + static unsigned long lastTimeShow = 0; + long tdelta = millis() - lastTimeShow; + if ((lastTimeShow > 0) && (tdelta > 1) && (tdelta < _frametime)) // too early - release CPU to slow down + vTaskDelay((tdelta-1) / portTICK_PERIOD_MS); // "-1" because vTaskDelay() may actually delay longer than requested + lastTimeShow = millis(); +#else yield(); +#endif show(); + _lastServiceShow = nowUp; // WLEDMM use correct timestamp } _triggered = false; _isServicing = false; @@ -2052,28 +2087,26 @@ void WS2812FX::show(void) { estimateCurrentAndLimitBri(); - #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) - unsigned long b4show = millis(); // WLEDMM the time before calling "show" + unsigned long showNow = millis(); // include time needed for busses.show() + #ifdef ARDUINO_ARCH_ESP32 // WLEDMM more accurate FPS measurement for ESP32 + uint64_t now500 = esp_timer_get_time() / 2; // native timer; micros /2 -> millis * 500 #endif + // some buses send asynchronously and this method will return before // all of the data has been sent. // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods busses.show(); - unsigned long now = millis(); - unsigned long diff = now - _lastShow; + + unsigned long diff = showNow - _lastShow; uint16_t fpsCurr = 200; if (diff > 0) fpsCurr = 1000 / diff; _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) - #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FASTPATH) - _lastShow = b4show; // WLEDMM this is more accurate, however it also increases CPU load - strip.service will run more frequently - #else - _lastShow = now; - #endif + _lastShow = showNow; + _lastServiceShow = showNow; #ifdef ARDUINO_ARCH_ESP32 // WLEDMM more accurate FPS measurement for ESP32 - uint64_t now500 = esp_timer_get_time() / 2; // native timer; micros /2 -> millis * 500 int64_t diff500 = now500 - _lastShow500; - if ((diff500 > 300) && (diff500 < 800000)) { // exclude stupid values (timer rollover, major hickups) + if ((diff500 > 1) && (diff500 < 800000)) { // exclude stupid values (timer rollover, major hickups) float fpcCurr500 = 500000.0f / float(diff500); if (fpcCurr500 > 2) _cumulativeFps500 = (3 * _cumulativeFps500 + (500.0 * fpcCurr500)) / 4; // average for some smoothing @@ -2104,9 +2137,11 @@ uint16_t WS2812FX::getFps() const { } void WS2812FX::setTargetFps(uint8_t fps) { - if (fps > 0 && fps <= 251) _targetFps = fps; // WLEDMM allow higher framerates - _frametime = 1000 / _targetFps; - if (_frametime < 1) _frametime = 1; // WLEDMM better safe than sorry + if (fps <= 251) _targetFps = fps; // WLEDMM allow higher framerates + //if (fps > 0) _frametime = ((2000 / _targetFps) +1) /2; // with rounding + if (fps > 0) _frametime = 1000 / _targetFps; + else _frametime = 2; // AC WLED compatibility + if (fps >= FPS_UNLIMITED) _frametime = 2; // WLEDMM unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { @@ -2281,8 +2316,15 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, _segments[n].setUp(i1, i2, grouping, spacing, offset, startY, stopY); } -void WS2812FX::restartRuntime() { - for (segment &seg : _segments) seg.markForReset(); +void WS2812FX::restartRuntime(bool doReset) { + for (segment &seg : _segments) { + if (doReset) { // WLEDMM we prefer not to perform a complete restart of all effects + seg.markForReset(); // seg.resetIfRequired(); // WLEDMM calling this function from webserver context will cause troubles + } else { + seg.next_time = 0; seg.step = 0; + seg.markForBlank(); + } + } } void WS2812FX::resetSegments(bool boundsOnly) { //WLEDMM add boundsonly @@ -2436,7 +2478,7 @@ uint8_t WS2812FX::setPixelSegment(uint8_t n) { uint8_t prevSegId = _segment_index; if (n < _segments.size()) { _segment_index = n; - _virtualSegmentLength = _segments[_segment_index].virtualLength(); + _virtualSegmentLength = _segments[_segment_index].calc_virtualLength(); } return prevSegId; } diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index ab124b93a8..e414bf61ef 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -11,14 +11,14 @@ // WLEDMM functions to get/set bits in an array - based on functions created by Brandon for GOL // toDo : make this a class that's completely defined in a header file -bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value +inline bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value size_t byteIndex = position / 8; unsigned bitIndex = position % 8; uint8_t byteValue = byteArray[byteIndex]; return (byteValue >> bitIndex) & 1; } -void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr +inline void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr //if (byteArray == nullptr) return; size_t byteIndex = position / 8; unsigned bitIndex = position % 8; @@ -448,7 +448,7 @@ uint8_t BusOnOff::getPins(uint8_t* pinArray) const { } -BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { +BusNetwork::BusNetwork(BusConfig &bc, const ColorOrderMap &com) : Bus(bc.type, bc.start, bc.autoWhite), _colorOrderMap(com) { _valid = false; USER_PRINT("["); switch (bc.type) { @@ -457,6 +457,11 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { _UDPtype = 2; USER_PRINT("NET_ARTNET_RGB"); break; + case TYPE_NET_ARTNET_RGBW: + _rgbw = true; + _UDPtype = 2; + USER_PRINT("NET_ARTNET_RGBW"); + break; case TYPE_NET_E131_RGB: _rgbw = false; _UDPtype = 1; @@ -469,37 +474,84 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { break; } _UDPchannels = _rgbw ? 4 : 3; - _data = (byte *)malloc(bc.count * _UDPchannels); + #ifdef ESP32 + _data = (byte*) heap_caps_calloc_prefer((bc.count * _UDPchannels)+15, sizeof(byte), 3, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM); + #else + _data = (byte*) calloc((bc.count * _UDPchannels)+15, sizeof(byte)); + #endif if (_data == nullptr) return; - memset(_data, 0, bc.count * _UDPchannels); _len = bc.count; + _colorOrder = bc.colorOrder; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); _broadcastLock = false; _valid = true; - USER_PRINTF(" %u.%u.%u.%u] \n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); + _artnet_outputs = bc.artnet_outputs; + _artnet_leds_per_output = bc.artnet_leds_per_output; + _artnet_fps_limit = max(uint8_t(1), bc.artnet_fps_limit); + USER_PRINTF(" %u.%u.%u.%u]\n", 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 (hasWhite()) c = autoWhiteCalc(c); - if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT - uint16_t offset = pix * _UDPchannels; - _data[offset] = R(c); - _data[offset+1] = G(c); - _data[offset+2] = B(c); - if (_rgbw) _data[offset+3] = W(c); +void IRAM_ATTR_YN BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { + if (!_valid || pix >= _len) return; + if (_rgbw) c = autoWhiteCalc(c); + if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); // color correction from CCT + + uint16_t offset = pix * _UDPchannels; + uint8_t co = _colorOrderMap.getPixelColorOrder(pix + _start, _colorOrder); + + if (_colorOrder != co || _colorOrder != COL_ORDER_RGB) { + switch (co) { + case COL_ORDER_GRB: + _data[offset] = G(c); _data[offset+1] = R(c); _data[offset+2] = B(c); + break; + case COL_ORDER_RGB: + _data[offset] = R(c); _data[offset+1] = G(c); _data[offset+2] = B(c); + break; + case COL_ORDER_BRG: + _data[offset] = B(c); _data[offset+1] = R(c); _data[offset+2] = G(c); + break; + case COL_ORDER_RBG: + _data[offset] = R(c); _data[offset+1] = B(c); _data[offset+2] = G(c); + break; + case COL_ORDER_GBR: + _data[offset] = G(c); _data[offset+1] = B(c); _data[offset+2] = R(c); + break; + case COL_ORDER_BGR: + _data[offset] = B(c); _data[offset+1] = G(c); _data[offset+2] = R(c); + break; + } + if (_rgbw) _data[offset+3] = W(c); + } else { + _data[offset] = R(c); _data[offset+1] = G(c); _data[offset+2] = B(c); + if (_rgbw) _data[offset+3] = W(c); + } } -uint32_t BusNetwork::getPixelColor(uint16_t pix) const { - if (!_valid || pix >= _len) return 0; - uint16_t offset = pix * _UDPchannels; - return RGBW32(_data[offset], _data[offset+1], _data[offset+2], _rgbw ? (_data[offset+3] << 24) : 0); +uint32_t IRAM_ATTR_YN BusNetwork::getPixelColor(uint16_t pix) const { + if (!_valid || pix >= _len) return 0; + uint16_t offset = pix * _UDPchannels; + uint8_t co = _colorOrderMap.getPixelColorOrder(pix + _start, _colorOrder); + + uint8_t r = _data[offset + 0]; + uint8_t g = _data[offset + 1]; + uint8_t b = _data[offset + 2]; + uint8_t w = _rgbw ? _data[offset + 3] : 0; + + switch (co) { + case COL_ORDER_GRB: return RGBW32(g, r, b, w); + case COL_ORDER_RGB: return RGBW32(r, g, b, w); + case COL_ORDER_BRG: return RGBW32(b, r, g, w); + case COL_ORDER_RBG: return RGBW32(r, b, g, w); + case COL_ORDER_GBR: return RGBW32(g, b, r, w); + case COL_ORDER_BGR: return RGBW32(b, g, r, w); + default: return RGBW32(r, g, b, w); // default to RGB order + } } void BusNetwork::show() { if (!_valid || !canShow()) return; _broadcastLock = true; - realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw); + realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw, _artnet_outputs, _artnet_leds_per_output, _artnet_fps_limit); _broadcastLock = false; } @@ -522,25 +574,95 @@ void BusNetwork::cleanup() { #ifdef WLED_ENABLE_HUB75MATRIX #warning "HUB75 driver enabled (experimental)" +// BusHub75Matrix "global" variables (static members) +MatrixPanel_I2S_DMA* BusHub75Matrix::activeDisplay = nullptr; +VirtualMatrixPanel* BusHub75Matrix::activeFourScanPanel = nullptr; +HUB75_I2S_CFG BusHub75Matrix::activeMXconfig = HUB75_I2S_CFG(); +uint8_t BusHub75Matrix::activeType = 0; +uint8_t BusHub75Matrix::instanceCount = 0; +uint8_t BusHub75Matrix::last_bri = 0; + + +// -------------------------- +// Bitdepth reduction based on panel size +// -------------------------- +#if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) + // esp32-S3 with octal PSRAM + #if defined(SPIRAM_FRAMEBUFFER) + // when PSRAM is used for pixel buffers + #define MAX_PIXELS_8BIT (192 * 64) + #define MAX_PIXELS_6BIT ( 64 * 64) // trick: skip this category, so we go directly from 8bit to 4bit + #define MAX_PIXELS_4BIT (256 * 128) + #else + // PSRAM not used for pixel buffers + #define MAX_PIXELS_8BIT (128 * 64) + #define MAX_PIXELS_6BIT (192 * 64) + #define MAX_PIXELS_4BIT (256 * 64) + #endif +#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM) + // standard esp32-S3 with quad PSRAM + #define MAX_PIXELS_8BIT ( 96 * 64) + #define MAX_PIXELS_6BIT (128 * 64) + #define MAX_PIXELS_4BIT (160 * 64) +#elif defined(CONFIG_IDF_TARGET_ESP32S3) + // HD-WF2 is an esp32-S3 without PSRAM - use same limits as classic esp32 + #define MAX_PIXELS_8BIT ( 64 * 64) + #define MAX_PIXELS_6BIT ( 96 * 64) + #define MAX_PIXELS_4BIT (128 * 64) +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + // esp32-S2 only has 320KB RAM + #define MAX_PIXELS_8BIT ( 48 * 48) + #define MAX_PIXELS_6BIT ( 64 * 48) + #define MAX_PIXELS_4BIT ( 96 * 64) +#else + // classic esp32, and anything else + #define MAX_PIXELS_8BIT ( 64 * 64) + #define MAX_PIXELS_6BIT ( 96 * 64) + #define MAX_PIXELS_4BIT (128 * 64) +#endif +// -------------------------- + BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { + MatrixPanel_I2S_DMA* display = nullptr; + VirtualMatrixPanel* fourScanPanel = nullptr; + HUB75_I2S_CFG mxconfig; size_t lastHeap = ESP.getFreeHeap(); _valid = false; - fourScanPanel = nullptr; _len = 0; - mxconfig.double_buff = false; // Use our own memory-optimised buffer rather than the driver's own double-buffer + // allow exactly one instance + if (instanceCount > 0) { + USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! already active - preventing attempt to create more than one driver instance."); + return; + } + + mxconfig.double_buff = false; // Use our own memory-optimised buffer rather than the driver's own double-buffer // mxconfig.driver = HUB75_I2S_CFG::ICN2038S; // experimental - use specific shift register driver // mxconfig.driver = HUB75_I2S_CFG::FM6124; // try this driver in case you panel stays dark, or when colors look too pastel - // mxconfig.latch_blanking = 3; + // mxconfig.latch_blanking = 1; // needed for some ICS panels + // mxconfig.latch_blanking = 3; // use in case you see gost images // mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz // mxconfig.min_refresh_rate = 90; - mxconfig.clkphase = false; // can help in case that the leftmost column is invisible, or pixels on the right side "bleeds out" to the left. - - // How many panels we have connected, cap at sane value - mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[0], (uint8_t) 4)); // prevent bad data preventing boot due to low memory + + mxconfig.clkphase = bc.reversed; + if (bc.refreshReq) mxconfig.latch_blanking = 1; // needed for some ICS panels (default = 2) + // fake bus flags + _needsRefresh = mxconfig.latch_blanking == 1; + reversed = mxconfig.clkphase; + + if (bc.type > 104) mxconfig.driver = HUB75_I2S_CFG::FM6124; // use FM6124 for "outdoor" panels - workaround until we can make the driver user-configurable + + // How many panels we have connected, cap at sane value, prevent bad data preventing boot due to low memory + #if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM) // ESP32-S3: allow up to 6 panels + mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[0], (uint8_t) 6)); + #elif defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32-S2: only 2 panels due to small RAM + mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[0], (uint8_t) 2)); + #else // others: up to 4 panels + mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[0], (uint8_t) 4)); + #endif #if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM) if(bc.pins[0] > 4) { @@ -568,6 +690,10 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh mxconfig.mx_width = 64; mxconfig.mx_height = 64; break; + case 104: + mxconfig.mx_width = 128; + mxconfig.mx_height = 64; + break; case 105: mxconfig.mx_width = 32 * 2; mxconfig.mx_height = 32 / 2; @@ -580,15 +706,19 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh mxconfig.mx_width = 64 * 2; mxconfig.mx_height = 64 / 2; break; + case 108: // untested + mxconfig.mx_width = 128 * 2; + mxconfig.mx_height = 64 / 2; + break; } -#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)// classic esp32, or esp32-s2: reduce bitdepth for large panels - if (mxconfig.mx_height >= 64) { - if (mxconfig.chain_length * mxconfig.mx_width > 192) mxconfig.setPixelColorDepthBits(3); - else if (mxconfig.chain_length * mxconfig.mx_width > 64) mxconfig.setPixelColorDepthBits(4); - else mxconfig.setPixelColorDepthBits(8); - } else mxconfig.setPixelColorDepthBits(8); -#endif + // reduce bitdepth based on total pixels + unsigned numPixels = mxconfig.mx_height * mxconfig.mx_width * mxconfig.chain_length; + if (numPixels <= MAX_PIXELS_8BIT) mxconfig.setPixelColorDepthBits(8); // 24bit + else if (numPixels <= MAX_PIXELS_6BIT) mxconfig.setPixelColorDepthBits(6); // 18bit + else if (numPixels <= MAX_PIXELS_4BIT) mxconfig.setPixelColorDepthBits(4); // 12bit + else mxconfig.setPixelColorDepthBits(3); // 9bit + #if defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 @@ -613,8 +743,15 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh mxconfig.gpio.d = 35; mxconfig.gpio.e = 21; -#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)// ESP32-S3 +#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)// ESP32-S3 with PSRAM + + #if defined(MOONHUB_S3_PINOUT) + USER_PRINTLN("MatrixPanel_I2S_DMA - T7 S3 with PSRAM, MOONHUB pinout"); + + // HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN}; + mxconfig.gpio = { 1, 5, 6, 7, 13, 9, 16, 48, 47, 21, 38, 8, 4, 18 }; + #else USER_PRINTLN("MatrixPanel_I2S_DMA - S3 with PSRAM"); mxconfig.gpio.r1 = 1; @@ -633,8 +770,9 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh mxconfig.gpio.lat = 8; mxconfig.gpio.oe = 3; // 16th pin is GND + #endif -#elif defined(CONFIG_IDF_TARGET_ESP32S3) // ESP32-S3 +#elif defined(CONFIG_IDF_TARGET_ESP32S3) // ESP32-S3 HD-WF2 // Huidu HD-WF2 ESP32-S3 // https://www.aliexpress.com/item/1005002258734810.html @@ -745,11 +883,50 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINTF("MatrixPanel_I2S_DMA config - %ux%u (type %u) length: %u, %u bits/pixel.\n", mxconfig.mx_width, mxconfig.mx_height, bc.type, mxconfig.chain_length, mxconfig.getPixelColorDepthBits() * 3); DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); lastHeap = ESP.getFreeHeap(); + // check if we can re-use the existing display driver + if (activeDisplay) { + if ( (memcmp(&(activeMXconfig.gpio), &(mxconfig.gpio), sizeof(mxconfig.gpio)) != 0) // other pins? + || (activeMXconfig.chain_length != mxconfig.chain_length) // other chain length? + || (activeMXconfig.mx_width != mxconfig.mx_width) || (activeMXconfig.mx_height != mxconfig.mx_height) // other size? + || (bc.type != activeType) // different panel type ? + || (activeMXconfig.clkphase != mxconfig.clkphase) // different driver options ? + || (activeMXconfig.latch_blanking != mxconfig.latch_blanking) + || (activeMXconfig.i2sspeed != mxconfig.i2sspeed) + || (activeMXconfig.driver != mxconfig.driver) + || (activeMXconfig.min_refresh_rate != mxconfig.min_refresh_rate) + || (activeMXconfig.getPixelColorDepthBits() != mxconfig.getPixelColorDepthBits()) ) + { + // not the same as before - delete old driver + DEBUG_PRINTLN("MatrixPanel_I2S_DMA deleting old driver!"); + activeDisplay->stopDMAoutput(); + delay(28); + //#if !defined(CONFIG_IDF_TARGET_ESP32S3) // prevent crash + delete activeDisplay; + //#endif + activeDisplay = nullptr; + activeFourScanPanel = nullptr; + #if defined(CONFIG_IDF_TARGET_ESP32S3) // runtime reconfiguration is not working on -S3 + USER_PRINTLN("\n\n****** MatrixPanel_I2S_DMA !KABOOM WARNING! Reboot needed to change driver options ***********\n"); + errorFlag = ERR_REBOOT_NEEDED; + #endif + } + } + // OK, now we can create our matrix object - display = new MatrixPanel_I2S_DMA(mxconfig); + bool newDisplay = false; // true when the previous display object wasn't re-used + if (!activeDisplay) { + display = new MatrixPanel_I2S_DMA(mxconfig); // create new matrix object + newDisplay = true; + } else { + display = activeDisplay; // continue with existing matrix object + fourScanPanel = activeFourScanPanel; + } + if (display == nullptr) { USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! driver allocation failed ***********"); - USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap()); + activeDisplay = nullptr; + activeFourScanPanel = nullptr; + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); return; } @@ -776,22 +953,26 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINTLN("MatrixPanel_I2S_DMA created"); // let's adjust default brightness - display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% - _bri = 25; + //display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% // [setBrightness()] Tried to set output brightness before begin() + _bri = (last_bri > 0) ? last_bri : 25; // try to restore persistent brightness value delay(24); // experimental - DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(lastHeap - ESP.getFreeHeap()); + DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(int(lastHeap - ESP.getFreeHeap())); // Allocate memory and start DMA display - if( not display->begin() ) { + if (newDisplay && (display->begin() == false)) { USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********"); - USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap()); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + _valid = false; return; } else { - USER_PRINTLN("MatrixPanel_I2S_DMA begin ok"); - USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap()); + if (newDisplay) { USER_PRINTLN("MatrixPanel_I2S_DMA begin, started ok"); } + else { USER_PRINTLN("MatrixPanel_I2S_DMA begin, using existing display."); } + + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); delay(18); // experiment - give the driver a moment (~ one full frame @ 60hz) to settle _valid = true; + display->setBrightness8(_bri); // range is 0-255, 0 - 0%, 255 - 100% // [setBrightness()] Tried to set output brightness before begin() display->clearScreen(); // initially clear the screen buffer USER_PRINTLN("MatrixPanel_I2S_DMA clear ok"); @@ -799,46 +980,51 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh if (_ledsDirty) free(_ledsDirty); // should not happen _ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits + if (_ledsDirty) setBitArray(_ledsDirty, _len, false); // reset dirty bits - if (_ledsDirty == nullptr) { - display->stopDMAoutput(); - delete display; display = nullptr; - _valid = false; - USER_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for dirty bits!")); - USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap()); - return; // fail is we cannot get memory for the buffer - } - setBitArray(_ledsDirty, _len, false); // reset dirty bits - - if (mxconfig.double_buff == false) { - #if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) + #if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) if (psramFound()) { _ledBuffer = (CRGB*) ps_calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) } else { _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) } - #else + #else _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) - #endif - } + #endif + } + + if ((_ledBuffer == nullptr) || (_ledsDirty == nullptr)) { + // fail is we cannot get memory for the buffer + errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag + USER_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for leds buffer!")); + cleanup(); // free buffers, and deallocate pins + _valid = false; + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + return; // fail } switch(bc.type) { case 105: USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_32PX_HIGH - 32x32"); - fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 32, 32); + if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, mxconfig.chain_length, 32, 32); fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); fourScanPanel->setRotation(0); break; case 106: USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_32PX_HIGH - 64x32"); - fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 32); + if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, mxconfig.chain_length, 64, 32); fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); fourScanPanel->setRotation(0); break; case 107: USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_64PX_HIGH"); - fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 64); + if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, mxconfig.chain_length, 64, 64); + fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH); + fourScanPanel->setRotation(0); + break; + case 108: // untested + USER_PRINTLN("MatrixPanel_I2S_DMA 128x64 FOUR_SCAN_64PX_HIGH"); + if (!fourScanPanel) fourScanPanel = new VirtualMatrixPanel((*display), 1, mxconfig.chain_length, 128, 64); fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH); fourScanPanel->setRotation(0); break; @@ -851,7 +1037,6 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINT(F("MatrixPanel_I2S_DMA ")); USER_PRINTF("%sstarted, width=%u, %u pixels.\n", _valid? "":"not ", _panelWidth, _len); - if (mxconfig.double_buff == true) USER_PRINTLN(F("MatrixPanel_I2S_DMA driver native double-buffering enabled.")); if (_ledBuffer != nullptr) USER_PRINTLN(F("MatrixPanel_I2S_DMA LEDS buffer enabled.")); if (_ledsDirty != nullptr) USER_PRINTLN(F("MatrixPanel_I2S_DMA LEDS dirty bit optimization enabled.")); if ((_ledBuffer != nullptr) || (_ledsDirty != nullptr)) { @@ -859,7 +1044,16 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINT((_ledBuffer? _len*sizeof(CRGB) :0) + (_ledsDirty? getBitArrayBytes(_len) :0)); USER_PRINTLN(F(" bytes.")); } - USER_PRINT(F("heap usage: ")); USER_PRINTLN(lastHeap - ESP.getFreeHeap()); + + if (_valid) { + // config is active, copy to global + activeType = bc.type; + activeDisplay = display; + activeFourScanPanel = fourScanPanel; + if (newDisplay) memcpy(&activeMXconfig, &mxconfig, sizeof(mxconfig)); + } + instanceCount++; + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); } void __attribute__((hot)) BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c) { @@ -873,10 +1067,14 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c setBitInArray(_ledsDirty, pix, true); // flag pixel as "dirty" } } + #if 0 + // !! this code is not used any more !! + // BusHub75Matrix::BusHub75Matrix will fail if allocating _ledBuffer fails. + // The fallback code below created lots of flickering so it does not make sense to keep it enabled. else { - if ((c == BLACK) && (getBitFromArray(_ledsDirty, pix) == false)) return; // ignore black if pixel is already black - setBitInArray(_ledsDirty, pix, c != BLACK); // dirty = true means "color is not BLACK" - + // no double buffer allocated --> directly draw pixel + MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay; + VirtualMatrixPanel* fourScanPanel = BusHub75Matrix::activeFourScanPanel; #ifndef NO_CIE1931 c = unGamma24(c); // to use the driver linear brightness feature, we first need to undo WLED gamma correction #endif @@ -896,53 +1094,64 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); } } + #endif } uint32_t BusHub75Matrix::getPixelColor(uint16_t pix) const { - if (!_valid || pix >= _len) return BLACK; - if (_ledBuffer) - return uint32_t(_ledBuffer[pix].scale8(_bri)) & 0x00FFFFFF; // scale8() is needed to mimic NeoPixelBus, which returns scaled-down colours - else - return getBitFromArray(_ledsDirty, pix) ? DARKGREY: BLACK; // just a hack - we only know if the pixel is black or not + if (!_valid || pix >= _len || !_ledBuffer) return BLACK; + return uint32_t(_ledBuffer[pix].scale8(_bri)) & 0x00FFFFFF; // scale8() is needed to mimic NeoPixelBus, which returns scaled-down colours } uint32_t __attribute__((hot)) BusHub75Matrix::getPixelColorRestored(uint16_t pix) const { - if (!_valid || pix >= _len) return BLACK; - if (_ledBuffer) - return uint32_t(_ledBuffer[pix]) & 0x00FFFFFF; - else - return getBitFromArray(_ledsDirty, pix) ? DARKGREY: BLACK; // just a hack - we only know if the pixel is black or not + if (!_valid || pix >= _len || !_ledBuffer) return BLACK; + return uint32_t(_ledBuffer[pix]) & 0x00FFFFFF; } void BusHub75Matrix::setBrightness(uint8_t b, bool immediate) { _bri = b; + if (!_valid) return; + MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay; // if (_bri > 238) _bri=238; // not strictly needed. Enable this line if you see glitches at highest brightness. - display->setBrightness(_bri); + if ((_bri > 253) && (activeMXconfig.latch_blanking < 2)) _bri=253; // prevent glitches at highest brightness. + last_bri = _bri; + if (display) display->setBrightness(_bri); } void __attribute__((hot)) BusHub75Matrix::show(void) { if (!_valid) return; + MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay; + if (!display) return; display->setBrightness(_bri); if (_ledBuffer) { // write out buffered LEDs + VirtualMatrixPanel* fourScanPanel = BusHub75Matrix::activeFourScanPanel; bool isFourScan = (fourScanPanel != nullptr); //if (isFourScan) fourScanPanel->setRotation(0); unsigned height = isFourScan ? fourScanPanel->height() : display->height(); unsigned width = _panelWidth; + // Cache pointers to LED array and bitmask array, to avoid repeated accesses + const byte* ledsDirty = _ledsDirty; + const CRGB* ledBuffer = _ledBuffer; + //while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. size_t pix = 0; // running pixel index for (int y=0; ydrawPixelRGB888(int16_t(x), int16_t(y), r, g, b); else display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); } @@ -950,48 +1159,56 @@ void __attribute__((hot)) BusHub75Matrix::show(void) { } setBitArray(_ledsDirty, _len, false); // buffer shown - reset all dirty bits } - - if(mxconfig.double_buff) { - display->flipDMABuffer(); // Show the back buffer, set current output buffer to the back (i.e. no longer being sent to LED panels) - // while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. - display->clearScreen(); // Now clear the back-buffer - setBitArray(_ledsDirty, _len, false); // dislay buffer is blank - reset all dirty bits - } } void BusHub75Matrix::cleanup() { + MatrixPanel_I2S_DMA* display = BusHub75Matrix::activeDisplay; + VirtualMatrixPanel* fourScanPanel = BusHub75Matrix::activeFourScanPanel; + if (display) display->clearScreen(); + +#if !defined(CONFIG_IDF_TARGET_ESP32S3) // S3: don't stop, as we want to re-use the driver later if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black) - _valid = false; _panelWidth = 0; - deallocatePins(); USER_PRINTLN("HUB75 output ended."); +#else + USER_PRINTLN("HUB75 output paused."); +#endif + _valid = false; + deallocatePins(); //if (fourScanPanel != nullptr) delete fourScanPanel; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior - delete display; - display = nullptr; - fourScanPanel = nullptr; +#if !defined(CONFIG_IDF_TARGET_ESP32S3) // S3: don't delete, as we want to re-use the driver later + if (display) delete display; + activeDisplay = nullptr; + activeFourScanPanel = nullptr; + USER_PRINTLN("HUB75 deleted."); +#else + USER_PRINTLN("HUB75 cleanup done."); +#endif + + if (instanceCount > 0) instanceCount--; if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr; if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr; } void BusHub75Matrix::deallocatePins() { - pinManager.deallocatePin(mxconfig.gpio.r1, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.g1, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.b1, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.r2, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.g2, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.b2, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.r1, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.g1, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.b1, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.r2, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.g2, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.b2, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.lat, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.oe, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.clk, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.lat, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.oe, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.clk, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.a, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.b, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.c, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.d, PinOwner::HUB75); - pinManager.deallocatePin(mxconfig.gpio.e, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.a, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.b, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.c, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.d, PinOwner::HUB75); + pinManager.deallocatePin(activeMXconfig.gpio.e, PinOwner::HUB75); } #endif @@ -1023,7 +1240,7 @@ int BusManager::add(BusConfig &bc) { if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; DEBUG_PRINTF("BusManager::add(bc.type=%u)\n", bc.type); if (bc.type >= TYPE_NET_DDP_RGB && bc.type < 96) { - busses[numBusses] = new BusNetwork(bc); + busses[numBusses] = new BusNetwork(bc, colorOrderMap); } else if (bc.type >= TYPE_HUB75MATRIX && bc.type <= (TYPE_HUB75MATRIX + 10)) { #ifdef WLED_ENABLE_HUB75MATRIX DEBUG_PRINTLN("BusManager::add - Adding BusHub75Matrix"); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 677e71de36..cef0286d00 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -54,12 +54,16 @@ struct BusConfig { uint8_t skipAmount; bool refreshReq; uint8_t autoWhite; + uint8_t artnet_outputs, artnet_fps_limit; + uint16_t artnet_leds_per_output; + uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; // WLEDMM warning: this means that BusConfig cannot handle nore than 5 pins per bus! uint16_t frequency; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U) { + 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, uint8_t art_o=1, uint16_t art_l=1, uint8_t art_f=30) { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; frequency = clock_kHz; + artnet_outputs = art_o; artnet_leds_per_output = art_l; artnet_fps_limit = art_f; uint8_t nPins = 1; // default = only one pin (clockless LEDs like WS281x) if ((type >= TYPE_NET_DDP_RGB) && (type < (TYPE_NET_DDP_RGB + 16))) nPins = 4; // virtual network bus. 4 "pins" store IP address else if ((type > 47) && (type < 63)) nPins = 2; // (data + clock / SPI) busses - two pins @@ -68,7 +72,7 @@ struct BusConfig { for (uint8_t i = 0; i < min(unsigned(nPins), sizeof(pins)/sizeof(pins[0])); i++) pins[i] = ppins[i]; //softhack007 fix for potential array out-of-bounds access } - //validates start and length and extends total if needed + //validates start and length and extends total if needed // WLEDMM this function is not used anywhere bool adjustBounds(uint16_t& total) { if (!count) count = 1; if (count > MAX_LEDS_PER_BUS) count = MAX_LEDS_PER_BUS; @@ -144,6 +148,9 @@ class Bus { virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } virtual uint8_t skippedLeds() const { return 0; } virtual uint16_t getFrequency() const { return 0U; } + virtual uint8_t get_artnet_fps_limit() const { return 0; } + virtual uint8_t get_artnet_outputs() const { return 0; } + virtual uint16_t get_artnet_leds_per_output() const { return 0; } inline uint16_t getStart() const { return _start; } inline void setStart(uint16_t start) { _start = start; } inline uint8_t getType() const { return _type; } @@ -330,7 +337,7 @@ class BusOnOff : public Bus { class BusNetwork : public Bus { public: - BusNetwork(BusConfig &bc); + BusNetwork(BusConfig &bc, const ColorOrderMap &com); uint16_t getMaxPixels() const override { return 4096; }; bool hasRGB() const { return true; } @@ -348,12 +355,30 @@ class BusNetwork : public Bus { return !_broadcastLock; } - uint8_t getPins(uint8_t* pinArray) const; + uint8_t getPins(uint8_t* pinArray) const override; - uint16_t getLength() const override { + uint16_t getLength() const override { return _len; } + uint8_t get_artnet_fps_limit() const override { + return _artnet_fps_limit; + } + + uint8_t get_artnet_outputs() const override { + return _artnet_outputs; + } + + uint16_t get_artnet_leds_per_output() const override { + return _artnet_leds_per_output; + } + + void setColorOrder(uint8_t colorOrder); + + uint8_t getColorOrder() const override { + return _colorOrder; + } + void cleanup(); ~BusNetwork() { @@ -361,12 +386,17 @@ class BusNetwork : public Bus { } private: - IPAddress _client; - uint8_t _UDPtype; - uint8_t _UDPchannels; - bool _rgbw; - bool _broadcastLock; - byte *_data; + IPAddress _client; + uint8_t _UDPtype; + uint8_t _UDPchannels; + bool _rgbw; + bool _broadcastLock; + byte *_data; + uint8_t _colorOrder = COL_ORDER_RGB; + uint8_t _artnet_fps_limit; + uint8_t _artnet_outputs; + uint16_t _artnet_leds_per_output; + const ColorOrderMap &_colorOrderMap; }; #ifdef WLED_ENABLE_HUB75MATRIX @@ -388,7 +418,7 @@ class BusHub75Matrix : public Bus { void setBrightness(uint8_t b, bool immediate) override; uint8_t getPins(uint8_t* pinArray) const override { - pinArray[0] = mxconfig.chain_length; + pinArray[0] = activeMXconfig.chain_length; return 1; } // Fake value due to keep finaliseInit happy @@ -401,12 +431,17 @@ class BusHub75Matrix : public Bus { } private: - MatrixPanel_I2S_DMA *display = nullptr; - VirtualMatrixPanel *fourScanPanel = nullptr; - HUB75_I2S_CFG mxconfig; unsigned _panelWidth = 0; CRGB *_ledBuffer = nullptr; byte *_ledsDirty = nullptr; + // C++ dirty trick: private static variables are actually _not_ part of the class (however only visibile to class instances). + // These variables persist when BusHub75Matrix gets deleted. + static MatrixPanel_I2S_DMA *activeDisplay; // active display object + static VirtualMatrixPanel *activeFourScanPanel; // active fourScan object + static HUB75_I2S_CFG activeMXconfig; // last used mxconfig + static uint8_t activeType; // last used type + static uint8_t instanceCount; // active instances - 0 or 1 + static uint8_t last_bri; // last used brightness value (persists on driver delete) }; #endif @@ -469,4 +504,4 @@ class BusManager { return j; } }; -#endif \ No newline at end of file +#endif diff --git a/wled00/button.cpp b/wled00/button.cpp index c1d0337667..fc8cf0f394 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -189,6 +189,7 @@ void handleAnalog(uint8_t b) briLast = bri; bri = 0; } else { + if (bri == 0) strip.restartRuntime(false); bri = aRead; } } else if (macroDoublePress[b] == 249) { diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 4de49f99af..069b303aa0 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -194,13 +194,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully) ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY; + uint8_t artnet_outputs = elm["artnet_outputs"] | 1; // sanity check + uint16_t artnet_leds_per_output = elm["artnet_leds_per_output"] | length; // sanity check + uint8_t artnet_fps_limit = elm["artnet_fps_limit"] | 24; // sanity check if (fromFS) { - BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz); + BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit); mem += BusManager::memUsage(bc); if (mem <= MAX_LED_MEMORY) if (busses.add(bc) == -1) break; // finalization will be done in WLED::beginStrip() } else { if (busConfigs[s] != nullptr) delete busConfigs[s]; - busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode); + busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit); busesChanged = true; } s++; @@ -829,6 +832,9 @@ void serializeConfig() { ins["ref"] = bus->isOffRefreshRequired(); ins[F("rgbwm")] = bus->getAutoWhiteMode(); ins[F("freq")] = bus->getFrequency(); + ins["artnet_outputs"] = bus->get_artnet_outputs(); + ins["artnet_fps_limit"] = bus->get_artnet_fps_limit(); + ins["artnet_leds_per_output"] = bus->get_artnet_leds_per_output(); } JsonArray hw_com = hw.createNestedArray(F("com")); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 23cf2f1383..bb12b66ab1 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -8,29 +8,37 @@ * color blend function */ IRAM_ATTR_YN __attribute__((hot)) uint32_t color_blend(uint32_t color1, uint32_t color2, uint_fast16_t blend, bool b16) { - if(blend == 0) return color1; - if (color1 == color2) return color1; // WLEDMM shortcut + if ((color1 == color2) || (blend == 0)) return color1; // WLEDMM const uint_fast16_t blendmax = b16 ? 0xFFFF : 0xFF; - if(blend == blendmax) return color2; + if(blend >= blendmax) return color2; const uint_fast8_t shift = b16 ? 16 : 8; - const uint_fast16_t blend2 = blendmax - blend; // WLEDMM pre-calculate value - uint32_t w1 = W(color1); - uint32_t r1 = R(color1); - uint32_t g1 = G(color1); - uint32_t b1 = B(color1); - - uint32_t w2 = W(color2); - uint32_t r2 = R(color2); - uint32_t g2 = G(color2); - uint32_t b2 = B(color2); - - uint32_t w3 = ((w2 * blend) + (w1 * blend2)) >> shift; - uint32_t r3 = ((r2 * blend) + (r1 * blend2)) >> shift; - uint32_t g3 = ((g2 * blend) + (g1 * blend2)) >> shift; - uint32_t b3 = ((b2 * blend) + (b1 * blend2)) >> shift; - - return RGBW32(r3, g3, b3, w3); + uint16_t w1 = W(color1); // WLEDMM 16bit to make sure the compiler uses 32bit (not 64bit) for the math + uint16_t r1 = R(color1); + uint16_t g1 = G(color1); + uint16_t b1 = B(color1); + + uint16_t w2 = W(color2); + uint16_t r2 = R(color2); + uint16_t g2 = G(color2); + uint16_t b2 = B(color2); + + if (b16 == false) { + // WLEDMM based on fastled blend8() - better accuracy for 8bit + uint8_t w3 = (w1+w2 == 0) ? 0 : (((w1 << 8)|w2) + (w2 * blend) - (w1*blend) ) >> 8; + uint8_t r3 = (((r1 << 8)|r2) + (r2 * blend) - (r1*blend) ) >> 8; + uint8_t g3 = (((g1 << 8)|g2) + (g2 * blend) - (g1*blend) ) >> 8; + uint8_t b3 = (((b1 << 8)|b2) + (b2 * blend) - (b1*blend) ) >> 8; + return RGBW32(r3, g3, b3, w3); + } else { + // old code has lots of "jumps" due to roundding errors + const uint_fast16_t blend2 = blendmax - blend; // WLEDMM pre-calculate value + uint32_t w3 = ((w2 * blend) + (w1 * blend2)) >> shift; + uint32_t r3 = ((r2 * blend) + (r1 * blend2)) >> shift; + uint32_t g3 = ((g2 * blend) + (g1 * blend2)) >> shift; + uint32_t b3 = ((b2 * blend) + (b1 * blend2)) >> shift; + return RGBW32(r3, g3, b3, w3); + } } /* @@ -73,27 +81,30 @@ IRAM_ATTR_YN uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) // WLEDMM IRAM_ATTR_YN __attribute__((hot)) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { + if (amount == 255) return c1; // WLEDMM small optimization - plus it avoids over-fading in "video" mode if (amount == 0) return 0; // WLEDMM shortcut - 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 scaledcolor = 0; // color order is: W R G B from MSB to LSB + uint16_t w = W(c1); // WLEDMM 16bit to make sure the compiler uses 32bit (not 64bit) for the math + uint16_t r = R(c1); + uint16_t g = G(c1); + uint16_t b = B(c1); if (video) { - uint32_t scale = amount; // 32bit for faster calculation - 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); - if (w>0) scaledcolor |= (((w * scale) >> 8) << 24) + ((scale) ? 1 : 0); // WLEDMM small speedup when no white channel + uint16_t scale = amount; // 32bit for faster calculation + // bugfix: doing "+1" after shifting is obviously wrong + // optimization: ((r && scale) ? 1 : 0) can be simplified to "if (r > 0) +1" ; if we arive here, then scale != 0 and scale < 255 + if (w>0) scaledcolor |= (((w * scale) >> 8) +1) << 24; // WLEDMM small speedup when no white channel + if (r>0) scaledcolor |= (((r * scale) >> 8) +1) << 16; + if (g>0) scaledcolor |= (((g * scale) >> 8) +1) << 8; + if (b>0) scaledcolor |= ((b * scale) >> 8) +1; return scaledcolor; } else { - uint32_t scale = 1 + amount; - scaledcolor = ((r * scale) >> 8) << 16; - scaledcolor |= ((g * scale) >> 8) << 8; - scaledcolor |= (b * scale) >> 8; + uint16_t scale = 1 + amount; if (w>0) scaledcolor |= ((w * scale) >> 8) << 24; // WLEDMM small speedup when no white channel + scaledcolor |= ((r * scale) >> 8) << 16; + scaledcolor |= (g * scale) & 0x0000FF00; // WLEDMM faster than right-left shift "" >>8 ) <<8" + scaledcolor |= (b * scale) >> 8; return scaledcolor; } } diff --git a/wled00/const.h b/wled00/const.h index 24bb093f60..4cf783044b 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -255,7 +255,8 @@ //Network types (master broadcast) (80-95) #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_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus) +#define TYPE_NET_ARTNET_RGBW 83 //network ArtNet RGB bus (master broadcast bus) #define TYPE_NET_DDP_RGBW 88 //network DDP RGBW bus (master broadcast bus) #define IS_DIGITAL(t) (((t) & 0x10) || ((t)==TYPE_HUB75MATRIX)) //digital are 16-31 and 48-63 // WLEDMM added HUB75 @@ -360,6 +361,10 @@ #define ERR_LOW_WS_MEM 35 // WLEDMM: low memory (ws) #define ERR_LOW_AJAX_MEM 36 // WLEDMM: low memory (oappend) #define ERR_LOW_BUF 37 // WLEDMM: low memory (LED buffer from allocLEDs) +#define ERR_SYS_REBOOT 90 // WLEDMM: reboot after error +#define ERR_SYS_BROWNOUT 91 // WLEDMM: reboot after brownout alert +#define ERR_REBOOT_NEEDED 98 // WLEDMM: reboot needed after changing hardware setting +#define ERR_POWEROFF_NEEDED 99 // WLEDMM: power-cycle needed after changing hardware setting // Timer mode types #define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness @@ -394,7 +399,11 @@ #define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs #else //#define MAX_LEDS 8192 -#define MAX_LEDS 8464 // WLEDMM 92x92 +#if !defined(CONFIG_IDF_TARGET_ESP32S3) + #define MAX_LEDS 8464 // WLEDMM 92x92 for esp32, esp32-S2 and esp32-c3 +#else + #define MAX_LEDS 18436 // WLEDMM 128x128 + 2048 + 4 for esp32-S3 +#endif #endif #endif diff --git a/wled00/data/index.css b/wled00/data/index.css index d8338d935c..617f971bd0 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -352,7 +352,7 @@ button { #putil, #segutil, #segutil2 { min-height: 42px; - margin: 13px auto 0; + margin: 0 auto; } #segutil .segin { @@ -1041,7 +1041,7 @@ textarea { .segname .flr, .pname .flr { transform: rotate(0deg); - right: -6px; + right: 4px; } /* segment power wrapper */ @@ -1331,6 +1331,11 @@ TD .checkmark, TD .radiomark { box-shadow: 0px 0px 10px 4px var(--c-1); } +.lstI .flr:hover { + background: var(--c-6); + border-radius: 100%; +} + #pcont .selected:not([class*="expanded"]) { bottom: 52px; top: 42px; diff --git a/wled00/data/index.js b/wled00/data/index.js index 05aaee058a..86e6465bdd 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2011,8 +2011,20 @@ function readState(s,command=false) case 37: errstr = "no memory for LEDs buffer."; break; + case 90: + errstr = "Unexpected Restart. Check serial monitor."; + break; + case 91: + errstr = "Brownout Restart."; + break; + case 98: + errstr = "Please reboot WLED to activate changed settings."; + break; + case 99: + errstr = "Please switch your device off and back on."; + break; } - showToast('Error ' + s.error + ": " + errstr, true); + showToast(((s.error < 33)?'Error ':'Warning ') + s.error + ": " + errstr, (s.error < 35)||(s.error > 90)); } selectedPal = i.pal; @@ -2357,7 +2369,7 @@ function makeSeg() }); var cn = `
`+ `
`+ - ``+ + ``+ ``+ ``+ ``+ @@ -2383,7 +2395,7 @@ function makeSeg() function resetUtil(off=false) { - gId('segutil').innerHTML = `
` + gId('segutil').innerHTML = `
` + '' + `
Add segment
` + '
' diff --git a/wled00/data/settings_2D.htm b/wled00/data/settings_2D.htm index c602b98c69..c57bf3d9ec 100644 --- a/wled00/data/settings_2D.htm +++ b/wled00/data/settings_2D.htm @@ -111,9 +111,9 @@
Serpentine:
-Dimensions (WxH): x
-Offset X: -Y:
(offset from top-left corner in # LEDs) +Dimensions (WxH): x
+Offset X: +Y:
(offset from top-left corner in # LEDs)
`; p.insertAdjacentHTML("beforeend", b); } @@ -401,7 +401,7 @@

Matrix Setup

Matrix Generator

${isM?'Start X':'Start LED'}