diff --git a/boards.txt b/boards.txt index 2b213a8dfd9..74707865c72 100644 --- a/boards.txt +++ b/boards.txt @@ -201,6 +201,9 @@ esp32wrover.menu.PartitionScheme.min_spiffs.build.partitions=min_spiffs esp32wrover.menu.PartitionScheme.min_spiffs.upload.maximum_size=1966080 esp32wrover.menu.PartitionScheme.fatflash=16M Flash (2MB APP/12.5MB FAT) esp32wrover.menu.PartitionScheme.fatflash.build.partitions=ffat +esp32wrover.menu.PartitionScheme.rzo_partition=Face Recognitions (2621440 bytes) +esp32wrover.menu.PartitionScheme.rzo_partition.build.partitions=rzo_partitions +esp32wrover.menu.PartitionScheme.rzo_partition.upload.maximum_size=2621440 esp32wrover.menu.FlashMode.qio=QIO esp32wrover.menu.FlashMode.qio.build.flash_mode=dio diff --git a/libraries/ArduinoWebsockets/LICENSE b/libraries/ArduinoWebsockets/LICENSE new file mode 100644 index 00000000000..f288702d2fa --- /dev/null +++ b/libraries/ArduinoWebsockets/LICENSE @@ -0,0 +1,674 @@ + 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 +. diff --git a/libraries/ArduinoWebsockets/README.md b/libraries/ArduinoWebsockets/README.md new file mode 100644 index 00000000000..f3bce51ba11 --- /dev/null +++ b/libraries/ArduinoWebsockets/README.md @@ -0,0 +1,305 @@ +[![arduino-library-badge](https://www.ardu-badge.com/badge/ArduinoWebsockets.svg?)](https://www.ardu-badge.com/ArduinoWebsockets) [![Build Status](https://travis-ci.org/gilmaimon/ArduinoWebsockets.svg?branch=master)](https://travis-ci.org/gilmaimon/ArduinoWebsockets) + +# Arduino Websockets + +A library for writing modern websockets applications with Arduino (ESP8266 and ESP32). This project is based on my project [TinyWebsockets](https://github.com/gilmaimon/TinyWebsockets). + +The library provides simple and easy interface for websockets work (Client and Server). See the [basic-usage](#Basic-Usage) guide and the [examples](#Full-Examples). + +### Please check out the [TinyWebsockets Wiki](https://github.com/gilmaimon/TinyWebsockets/wiki) for many more details! + +## Getting Started +This section should help you get started with the library. If you have any questions feel free to open an issue. + +### Prerequisites +Currently (version 0.4.*) the library only works with `ESP8266` and `ESP32`. + +### Installing + +You can install the library from the Arduino IDE or using a release ZIP file from the [Github release page](https://github.com/gilmaimon/ArduinoWebsockets/releases). +Detailed instructions can be found [here](https://www.ardu-badge.com/ArduinoWebsockets). + +## Basic Usage + +### Client + +Creating a client and connecting to a server: +```c++ +WebsocketsClient client; +client.connect("ws://your-server-ip:port/uri"); +``` + +Sending a message: +```c++ +client.send("Hello Server!"); +``` + +Waiting for messages: +```c++ +client.onMessage([](WebsocketsMessage msg){ + Serial.prinln("Got Message: " + msg.data()); +}); +``` + +In order to keep receiving messages, you should: +```c++ +void loop() { + client.poll(); +} +``` +### Server + +Creating a server and listening for connections: +```c++ +WebsocketsServer server; +server.listen(8080); +``` + +Accepting connections: +```c++ +WebsocketsClient client = server.accept(); +// handle client as described before :) +``` + +## Full Examples + +### Client + +```c++ +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password +const char* websockets_server = "www.myserver.com:8080"; //server adress and port + +using namespace websockets; + +void onMessageCallback(WebsocketsMessage message) { + Serial.print("Got Message: "); + Serial.println(message.data()); +} + +void onEventsCallback(WebsocketsEvent event, String data) { + if(event == WebsocketsEvent::ConnectionOpened) { + Serial.println("Connnection Opened"); + } else if(event == WebsocketsEvent::ConnectionClosed) { + Serial.println("Connnection Closed"); + } else if(event == WebsocketsEvent::GotPing) { + Serial.println("Got a Ping!"); + } else if(event == WebsocketsEvent::GotPong) { + Serial.println("Got a Pong!"); + } +} + +WebsocketsClient client; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + // Setup Callbacks + client.onMessage(onMessageCallback); + client.onEvent(onEventsCallback); + + // Connect to server + client.connect(websockets_server); + + // Send a message + client.send("Hi Server!"); + // Send a ping + client.ping(); +} + +void loop() { + client.poll(); +} +``` +***Note:** for ESP32 you only need to change to code that connects to WiFi (replace `#include ` with `#include `), everything else stays the same.* + +### Server +```c++ +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password + +using namespace websockets; + +WebsocketsServer server; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 15 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); //You can get IP address assigned to ESP + + server.listen(80); + Serial.print("Is server live? "); + Serial.println(server.available()); +} + +void loop() { + auto client = server.accept(); + if(client.available()) { + auto msg = client.readBlocking(); + + // log + Serial.print("Got Message: "); + Serial.println(msg.data()); + + // return echo + client.send("Echo: " + msg.data()); + + // close the connection + client.close(); + } + + delay(1000); +} +``` +***Note:** for ESP32 you only need to change to code that connects to WiFi (replace `#include ` with `#include `), everything else stays the same.* + +## Binary Data + +For binary data it is recommended to use `msg.rawData()` which returns a `std::string`, or `msg.c_str()` which returns a `const char*`. +The reason is that `msg.data()` returns an Arduino `String`, which is great for Serial printing and very basic memory handling but bad for most binary usages. + +See [issue #32](https://github.com/gilmaimon/ArduinoWebsockets/issues/32) for further information. + +## SSL and WSS Support + +No matter what board you are using, in order to use WSS (websockets over SSL) you need to use +```c++ +client.connect("wss://your-secured-server-ip:port/uri"); +``` + +The next sections describe board-specific code for using WSS with the library. + +### ESP8266 +With the esp8266 there are 2 ways for using WSS. By default, `ArduinoWebsockets` does not validate the certificate chain. This can be set explicitly using: +```c++ +client.setInsecure(); +``` + +You can also use a `SSL Fingerprint` to validate the SSL connection, for example: +```c++ +const char ssl_fingerprint[] PROGMEM = "D5 07 4D 79 B2 D2 53 D7 74 E6 1B 46 C5 86 4E FE AD 00 F1 98"; + +client.setFingerprint(ssl_fingerprint); +``` + +### ESP32 +With the esp32 you could either provide the full certificate, or provide no certificate. An example for setting CA Certificate: +```c++ +const char ssl_ca_cert[] PROGMEM = \ + "-----BEGIN CERTIFICATE-----\n" \ + "MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \ + "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ + "DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \ + "SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \ + "GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \ + "AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \ + "q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \ + "SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \ + "Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \ + "a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \ + "/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \ + "AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \ + "CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \ + "bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \ + "c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \ + "VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \ + "ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \ + "MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \ + "Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \ + "AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \ + "uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \ + "wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \ + "X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \ + "PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \ + "KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \ + "-----END CERTIFICATE-----\n"; + +client.setCACert(ssl_ca_cert); +``` + +## Contributing +Contributions are welcomed! Please open issues if you have troubles while using the library or any queshtions on how to get started. Pull requests are welcomed, please open an issue first. + +## Contributors +Thanks for everyone who reported a bug, suggested a feature and contributed to the development of this library. + + + + + + + + + + + + + + + + + + + + + + + + + +
ramdor
⭐️ ramdor

xgarb
⭐️ xgarb

matsujirushi
matsujirushi

bastienvans
bastienvans

johneakin
johneakin

lalten
lalten

elielmarcos
elielmarcos

adelin-mcbsoft
⭐️ adelin-mcbsoft

Jonty
⭐️ Jonty

Nufflee
Nufflee

mmcArg
mmcArg

JohnInWI
JohnInWI

logdog2709
logdog2709

elC0mpa
elC0mpa

oofnik
⭐️ oofnik

zastrixarundell
⭐️ zastrixarundell

+ +## Change Log +- **14/02/2019 (v0.1.1)** - Initial commits and support for ESP32 and ESP8266 Websocket Clients. +- **16/02/2019 (v0.1.2)** - Added support for events (Pings, Pongs) and more internal improvements (events handling according to [RFC-6455](https://tools.ietf.org/html/rfc6455)) +- **20/02/2019 (v0.1.3)** - Users now dont have to specify TCP client types (ESP8266/ESP32) they are selected automatically. +- **21/02/2019 (v0.1.5)** - Bug Fixes. Client now exposes a single string connect interface. +- **24/02/2019 (v0.2.0)** - User-facing interface is now done with Arduino's `String` class. Merged more changes (mainly optimizations) from TinyWebsockets. +- **25/02/2019 (v0.2.1)** - A tiny patch. Fixed missing user-facing strings for client interface. +- **07/03/2019 (v0.3.0)** - A version update. Now supports a websockets server, better support for fragmented messages and streams. bug fixes and more optimized networking implementations. +- **08/03/2019 (v0.3.1)** - Small patch. Merged changes from TinyWebsockets - interface changes to callbacks (partial callbacks without WebsocketsClient& as first parameter). +- **12/03/2019 (v0.3.2)** - Fixed a bug with behaviour of WebsokcetsClient (copy c'tor and assignment operator). Added close codes from TinyWebsockets. Thank you [@ramdor](https://github.com/gilmaimon/ArduinoWebsockets/issues/2) +- **13/03/2019 (v0.3.3)** - Fixed a bug in the esp8266 networking impl. Thank you [@ramdor](https://github.com/gilmaimon/ArduinoWebsockets/issues/2) +- **14/03/2019 (v0.3.4)** - changed underling tcp impl for esp8266 and esp32 to use `setNoDelay(true)` instead of sync communication. This makes communication faster and more relaiable than default. Thank you @ramdor for pointing out these methods. +- **06/04/2019 (v0.3.5)** - added very basic support for WSS in esp8266 (no support for fingerprint/ca or any kind of chain validation). +- **22/04/2019 (v0.4.0)** - Added WSS support for both esp8266 and esp32. E328266 can use `client.setInsecure()` (does not validate certificate chain) or `client.setFingerprint(fingerprint)` in order to use WSS. With ESP32 there is `client.setCACert(certificate)` that can be used. (Usage is same as the built in `WiFiClientSecure`). +- **18/05/2019 (v0.4.1)** - Patch! Addressed an error with some servers. The bugs where first noted in [issue #9](https://github.com/gilmaimon/ArduinoWebsockets/issues/9). Bug was that some servers will drop connections that don't use masking, and TinyWebsockets does not use masking by default. TinyWebsockets changed that and this library merged the changes. +- **24/05/2019 (v0.4.2)** - Patch! Adressed masking issues - server to client messages would get masked but RFC forbbids it. (changes merged from TinyWebsockets) +- **07/06/2019 (v0.4.3)** - Patch! Fixed a bug on some clients (mainly FireFox websokcets impl). Thank you @xgarb ([related issue](https://github.com/gilmaimon/ArduinoWebsockets/issues/11)) +- **09/06/2019 (v0.4.4)** - Patch! Fixed an issue with `close` event callback not called in some cases (sudden disconnect, for example). Thank you @adelin-mcbsoft for pointing out the issue ([related issue](https://github.com/gilmaimon/ArduinoWebsockets/issues/14)) +- **14/06/2019 (v0.4.5)** - Patch! Fixed a memory leak and an unnecessary use of heap memory in case of masking messages. This was discoverd thanks to [issue #16](https://github.com/gilmaimon/ArduinoWebsockets/issues/16). Thank you [xgarb](https://github.com/xgarb)! +- **13/07/2019 (v0.4.6)** - Very small update. Changed readme to document esp32's secured client behvior ([As discussed in issue #18](https://github.com/gilmaimon/ArduinoWebsockets/issues/18)). Also esp32's version of WebsocketsClient now has a `setInsecure` method. Thank you [adelin-mcbsoft](https://github.com/adelin-mcbsoft)! +- **25/07/2019 (v0.4.7)** - Bugfix. Fixed issues with receving large messages (unchecked reads) which was pointed out in [in issue #21](https://github.com/gilmaimon/ArduinoWebsockets/issues/21)). Thank you [Jonty](https://github.com/Jonty)! +- **26/07/2019 (v0.4.8)** - Feature. Added an `addHeader` method as suggested [in issue #22](https://github.com/gilmaimon/ArduinoWebsockets/issues/21)). Thank you [mmcArg](https://github.com/mmcArg)! +- **01/08/2019 (v0.4.9)** - Patch - Bugfix. Worked around a bug where connecting to unavailable endpoints would not return false (this is a bug with the `WiFiClient` library itself). Added some missing keywords. Thank you [Nufflee](https://github.com/Nufflee) for pointing out the [issue](https://github.com/gilmaimon/ArduinoWebsockets/issues/25)! +- **10/08/2019 (v0.4.10)** - Patch - Bugfix. Fixed a bug (and general in-stability) caused from unchecked and unsafe read operations on sockets. Also improved memory usage and management. Thank you [Jonty](https://github.com/Jonty) for openning and helping with the [issue](https://github.com/gilmaimon/ArduinoWebsockets/issues/26)! +- **14/09/2019 (v0.4.11)** - Bugfixes - masking settings used to not get copied when using assignment between `WebsocketClient` instances. Also handshake validation is now case insensitive. Thank you [logdog2709](https://github.com/logdog2709) for pointing out the [issue](https://github.com/gilmaimon/ArduinoWebsockets/issues/34). +- **12/10/2019 (v0.4.12)** - Patch - Messages are now sent as a single TCP buffer instead of separate messages. Thank you [elC0mpa](https://github.com/elC0mpa) for posting the [issue](https://github.com/gilmaimon/ArduinoWebsockets/issues/44). +- **19/10/2019 (v0.4.13)** - Patch - added `yield` calls in order to prevent software-watchdog resets on esp8266 (on long messages). Thank you [elC0mpa](https://github.com/elC0mpa) for documenting and helping with the [issue](https://github.com/gilmaimon/ArduinoWebsockets/issues/43). +- **22/11/2019 (v0.4.14)** - Added `rawData` and `c_str` as acccessors in `WebsocketsMessage` so now the raw data can be acccessed which should solve issue #32 and not break any existing sketch. +- **24/02/20 (v0.4.15)** - Added `Origin` and `User-Agent` headers to requests sent by the library, this seems to be required by some servers. Thank you [imesut](https://github.com/imesut) for pointing out the issue. +- **21/04/20 (v0.4.16)** - Merged pull request by @oofnik which added 2 way SSL auth for ESP32 and ES8266. Thank you very [oofnik](https://github.com/oofnik) for the contribuation. +- **25/04/20 (v0.4.17)** - Merged pull request by Luka Bodroža (@zastrixarundell) which fixed [issue #69](https://github.com/gilmaimon/ArduinoWebsockets/issues/69) - default headers (like Origin, Host) are now customizable via the `addHeader` method. Thank you [zastrixarundell](https://github.com/zastrixarundell) for the contribution. diff --git a/libraries/ArduinoWebsockets/_config.yml b/libraries/ArduinoWebsockets/_config.yml new file mode 100644 index 00000000000..c4192631f25 --- /dev/null +++ b/libraries/ArduinoWebsockets/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/examples/Esp32-Client/Esp32-Client.ino b/libraries/ArduinoWebsockets/examples/Esp32-Client/Esp32-Client.ino new file mode 100644 index 00000000000..e209ba59bb1 --- /dev/null +++ b/libraries/ArduinoWebsockets/examples/Esp32-Client/Esp32-Client.ino @@ -0,0 +1,70 @@ +/* + Esp32 Websockets Client + + This sketch: + 1. Connects to a WiFi network + 2. Connects to a Websockets server + 3. Sends the websockets server a message ("Hello Server") + 4. Prints all incoming messages while the connection is open + + Hardware: + For this sketch you only need an ESP32 board. + + Created 15/02/2019 + By Gil Maimon + https://github.com/gilmaimon/ArduinoWebsockets + +*/ + +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password +const char* websockets_server_host = "serverip_or_name"; //Enter server adress +const uint16_t websockets_server_port = 8080; // Enter server port + +using namespace websockets; + +WebsocketsClient client; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + // Check if connected to wifi + if(WiFi.status() != WL_CONNECTED) { + Serial.println("No Wifi!"); + return; + } + + Serial.println("Connected to Wifi, Connecting to server."); + // try to connect to Websockets server + bool connected = client.connect(websockets_server_host, websockets_server_port, "/"); + if(connected) { + Serial.println("Connected!"); + client.send("Hello Server"); + } else { + Serial.println("Not Connected!"); + } + + // run callback when messages are received + client.onMessage([&](WebsocketsMessage message){ + Serial.print("Got Message: "); + Serial.println(message.data()); + }); +} + +void loop() { + // let the websockets client check for incoming messages + if(client.available()) { + client.poll(); + } + delay(500); +} diff --git a/libraries/ArduinoWebsockets/examples/Esp32-Server/Esp32-Server.ino b/libraries/ArduinoWebsockets/examples/Esp32-Server/Esp32-Server.ino new file mode 100644 index 00000000000..14a98f3a2d9 --- /dev/null +++ b/libraries/ArduinoWebsockets/examples/Esp32-Server/Esp32-Server.ino @@ -0,0 +1,67 @@ +/* + Minimal Esp32 Websockets Server + + This sketch: + 1. Connects to a WiFi network + 2. Starts a websocket server on port 80 + 3. Waits for connections + 4. Once a client connects, it wait for a message from the client + 5. Sends an "echo" message to the client + 6. closes the connection and goes back to step 3 + + Hardware: + For this sketch you only need an ESP32 board. + + Created 15/02/2019 + By Gil Maimon + https://github.com/gilmaimon/ArduinoWebsockets +*/ + +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password + +using namespace websockets; + +WebsocketsServer server; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 15 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); //You can get IP address assigned to ESP + + server.listen(80); + Serial.print("Is server live? "); + Serial.println(server.available()); +} + +void loop() { + WebsocketsClient client = server.accept(); + if(client.available()) { + WebsocketsMessage msg = client.readBlocking(); + + // log + Serial.print("Got Message: "); + Serial.println(msg.data()); + + // return echo + client.send("Echo: " + msg.data()); + + // close the connection + client.close(); + } + + delay(1000); +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/examples/Esp8266-Client/Esp8266-Client.ino b/libraries/ArduinoWebsockets/examples/Esp8266-Client/Esp8266-Client.ino new file mode 100644 index 00000000000..3d0ac32318b --- /dev/null +++ b/libraries/ArduinoWebsockets/examples/Esp8266-Client/Esp8266-Client.ino @@ -0,0 +1,70 @@ +/* + Esp8266 Websockets Client + + This sketch: + 1. Connects to a WiFi network + 2. Connects to a Websockets server + 3. Sends the websockets server a message ("Hello Server") + 4. Prints all incoming messages while the connection is open + + Hardware: + For this sketch you only need an ESP8266 board. + + Created 15/02/2019 + By Gil Maimon + https://github.com/gilmaimon/ArduinoWebsockets + +*/ + +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password +const char* websockets_server_host = "serverip_or_name"; //Enter server adress +const uint16_t websockets_server_port = 8080; // Enter server port + +using namespace websockets; + +WebsocketsClient client; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + // Check if connected to wifi + if(WiFi.status() != WL_CONNECTED) { + Serial.println("No Wifi!"); + return; + } + + Serial.println("Connected to Wifi, Connecting to server."); + // try to connect to Websockets server + bool connected = client.connect(websockets_server_host, websockets_server_port, "/"); + if(connected) { + Serial.println("Connecetd!"); + client.send("Hello Server"); + } else { + Serial.println("Not Connected!"); + } + + // run callback when messages are received + client.onMessage([&](WebsocketsMessage message) { + Serial.print("Got Message: "); + Serial.println(message.data()); + }); +} + +void loop() { + // let the websockets client check for incoming messages + if(client.available()) { + client.poll(); + } + delay(500); +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/examples/Esp8266-Server/Esp8266-Server.ino b/libraries/ArduinoWebsockets/examples/Esp8266-Server/Esp8266-Server.ino new file mode 100644 index 00000000000..0eb4519debf --- /dev/null +++ b/libraries/ArduinoWebsockets/examples/Esp8266-Server/Esp8266-Server.ino @@ -0,0 +1,67 @@ +/* + Minimal Esp8266 Websockets Server + + This sketch: + 1. Connects to a WiFi network + 2. Starts a websocket server on port 80 + 3. Waits for connections + 4. Once a client connects, it wait for a message from the client + 5. Sends an "echo" message to the client + 6. closes the connection and goes back to step 3 + + Hardware: + For this sketch you only need an ESP8266 board. + + Created 15/02/2019 + By Gil Maimon + https://github.com/gilmaimon/ArduinoWebsockets +*/ + +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password + +using namespace websockets; + +WebsocketsServer server; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 15 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); //You can get IP address assigned to ESP + + server.listen(80); + Serial.print("Is server live? "); + Serial.println(server.available()); +} + +void loop() { + WebsocketsClient client = server.accept(); + if(client.available()) { + WebsocketsMessage msg = client.readBlocking(); + + // log + Serial.print("Got Message: "); + Serial.println(msg.data()); + + // return echo + client.send("Echo: " + msg.data()); + + // close the connection + client.close(); + } + + delay(1000); +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/examples/Minimal-Esp32-Client/Minimal-Esp32-Client.ino b/libraries/ArduinoWebsockets/examples/Minimal-Esp32-Client/Minimal-Esp32-Client.ino new file mode 100644 index 00000000000..6909af3b6ba --- /dev/null +++ b/libraries/ArduinoWebsockets/examples/Minimal-Esp32-Client/Minimal-Esp32-Client.ino @@ -0,0 +1,82 @@ +/* + Minimal Esp32 Websockets Client + + This sketch: + 1. Connects to a WiFi network + 2. Connects to a Websockets server + 3. Sends the websockets server a message ("Hello Server") + 4. Sends the websocket server a "ping" + 5. Prints all incoming messages while the connection is open + + NOTE: + The sketch dosen't check or indicate about errors while connecting to + WiFi or to the websockets server. For full example you might want + to try the example named "Esp32-Client". + + Hardware: + For this sketch you only need an ESP8266 board. + + Created 15/02/2019 + By Gil Maimon + https://github.com/gilmaimon/ArduinoWebsockets + +*/ + +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password +const char* websockets_server_host = "serverip_or_name"; //Enter server adress +const uint16_t websockets_server_port = 8080; // Enter server port + +using namespace websockets; + +void onMessageCallback(WebsocketsMessage message) { + Serial.print("Got Message: "); + Serial.println(message.data()); +} + +void onEventsCallback(WebsocketsEvent event, String data) { + if(event == WebsocketsEvent::ConnectionOpened) { + Serial.println("Connnection Opened"); + } else if(event == WebsocketsEvent::ConnectionClosed) { + Serial.println("Connnection Closed"); + } else if(event == WebsocketsEvent::GotPing) { + Serial.println("Got a Ping!"); + } else if(event == WebsocketsEvent::GotPong) { + Serial.println("Got a Pong!"); + } +} + +WebsocketsClient client; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + // run callback when messages are received + client.onMessage(onMessageCallback); + + // run callback when events are occuring + client.onEvent(onEventsCallback); + + // Connect to server + client.connect(websockets_server_host, websockets_server_port, "/"); + + // Send a message + client.send("Hello Server"); + + // Send a ping + client.ping(); +} + +void loop() { + client.poll(); +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/examples/Minimal-Esp8266-Client/Minimal-Esp8266-Client.ino b/libraries/ArduinoWebsockets/examples/Minimal-Esp8266-Client/Minimal-Esp8266-Client.ino new file mode 100644 index 00000000000..66a88464ed9 --- /dev/null +++ b/libraries/ArduinoWebsockets/examples/Minimal-Esp8266-Client/Minimal-Esp8266-Client.ino @@ -0,0 +1,82 @@ +/* + Minimal Esp8266 Websockets Client + + This sketch: + 1. Connects to a WiFi network + 2. Connects to a Websockets server + 3. Sends the websockets server a message ("Hello Server") + 4. Sends the websocket server a "ping" + 5. Prints all incoming messages while the connection is open + + NOTE: + The sketch dosen't check or indicate about errors while connecting to + WiFi or to the websockets server. For full example you might want + to try the example named "Esp8266-Client". + + Hardware: + For this sketch you only need an ESP8266 board. + + Created 15/02/2019 + By Gil Maimon + https://github.com/gilmaimon/ArduinoWebsockets + +*/ + +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password +const char* websockets_server_host = "serverip_or_name"; //Enter server adress +const uint16_t websockets_server_port = 8080; // Enter server port + +using namespace websockets; + +void onMessageCallback(WebsocketsMessage message) { + Serial.print("Got Message: "); + Serial.println(message.data()); +} + +void onEventsCallback(WebsocketsEvent event, String data) { + if(event == WebsocketsEvent::ConnectionOpened) { + Serial.println("Connnection Opened"); + } else if(event == WebsocketsEvent::ConnectionClosed) { + Serial.println("Connnection Closed"); + } else if(event == WebsocketsEvent::GotPing) { + Serial.println("Got a Ping!"); + } else if(event == WebsocketsEvent::GotPong) { + Serial.println("Got a Pong!"); + } +} + +WebsocketsClient client; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + // run callback when messages are received + client.onMessage(onMessageCallback); + + // run callback when events are occuring + client.onEvent(onEventsCallback); + + // Connect to server + client.connect(websockets_server_host, websockets_server_port, "/"); + + // Send a message + client.send("Hello Server"); + + // Send a ping + client.ping(); +} + +void loop() { + client.poll(); +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/examples/Secured-Esp32-Client/Secured-Esp32-Client.ino b/libraries/ArduinoWebsockets/examples/Secured-Esp32-Client/Secured-Esp32-Client.ino new file mode 100644 index 00000000000..431b477fb89 --- /dev/null +++ b/libraries/ArduinoWebsockets/examples/Secured-Esp32-Client/Secured-Esp32-Client.ino @@ -0,0 +1,115 @@ +/* + Secured Esp32 Websockets Client + + This sketch: + 1. Connects to a WiFi network + 2. Connects to a Websockets server (using WSS) + 3. Sends the websockets server a message ("Hello Server") + 4. Sends the websocket server a "ping" + 5. Prints all incoming messages while the connection is open + + NOTE: + The sketch dosen't check or indicate about errors while connecting to + WiFi or to the websockets server. For full example you might want + to try the example named "Esp32-Client" (And use the ssl methods). + + Hardware: + For this sketch you only need an Esp32 board. + + Created 15/02/2019 + By Gil Maimon + https://github.com/gilmaimon/ArduinoWebsockets + +*/ + +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password + +const char* websockets_connection_string = "wss://echo.websocket.org/"; //Enter server adress + +// This certificate was updated 20.04.2019 +const char echo_org_ssl_ca_cert[] PROGMEM = \ + "-----BEGIN CERTIFICATE-----\n" \ + "MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \ + "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ + "DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \ + "SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \ + "GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \ + "AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \ + "q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \ + "SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \ + "Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \ + "a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \ + "/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \ + "AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \ + "CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \ + "bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \ + "c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \ + "VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \ + "ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \ + "MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \ + "Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \ + "AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \ + "uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \ + "wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \ + "X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \ + "PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \ + "KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \ + "-----END CERTIFICATE-----\n"; + +using namespace websockets; + +void onMessageCallback(WebsocketsMessage message) { + Serial.print("Got Message: "); + Serial.println(message.data()); +} + +void onEventsCallback(WebsocketsEvent event, String data) { + if(event == WebsocketsEvent::ConnectionOpened) { + Serial.println("Connnection Opened"); + } else if(event == WebsocketsEvent::ConnectionClosed) { + Serial.println("Connnection Closed"); + } else if(event == WebsocketsEvent::GotPing) { + Serial.println("Got a Ping!"); + } else if(event == WebsocketsEvent::GotPong) { + Serial.println("Got a Pong!"); + } +} + +WebsocketsClient client; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + // run callback when messages are received + client.onMessage(onMessageCallback); + + // run callback when events are occuring + client.onEvent(onEventsCallback); + + // Before connecting, set the ssl fingerprint of the server + client.setCACert(echo_org_ssl_ca_cert); + + // Connect to server + client.connect(websockets_connection_string); + + // Send a message + client.send("Hello Server"); + + // Send a ping + client.ping(); +} + +void loop() { + client.poll(); +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/examples/Secured-Esp8266-Client/Secured-Esp8266-Client.ino b/libraries/ArduinoWebsockets/examples/Secured-Esp8266-Client/Secured-Esp8266-Client.ino new file mode 100644 index 00000000000..1d3acd118c1 --- /dev/null +++ b/libraries/ArduinoWebsockets/examples/Secured-Esp8266-Client/Secured-Esp8266-Client.ino @@ -0,0 +1,88 @@ +/* + Secured Esp8266 Websockets Client + + This sketch: + 1. Connects to a WiFi network + 2. Connects to a Websockets server (using WSS) + 3. Sends the websockets server a message ("Hello Server") + 4. Sends the websocket server a "ping" + 5. Prints all incoming messages while the connection is open + + NOTE: + The sketch dosen't check or indicate about errors while connecting to + WiFi or to the websockets server. For full example you might want + to try the example named "Esp8266-Client" (And use the ssl methods). + + Hardware: + For this sketch you only need an ESP8266 board. + + Created 15/02/2019 + By Gil Maimon + https://github.com/gilmaimon/ArduinoWebsockets + +*/ + +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password + +const char* websockets_connection_string = "wss://echo.websocket.org/"; //Enter server adress + +// This fingerprint was updated 20.04.2019 +const char echo_org_ssl_fingerprint[] PROGMEM = "E0 E7 13 AE F4 38 0F 7F 22 39 5C 32 B3 16 EC BB 95 F3 0B 5B"; + +using namespace websockets; + +void onMessageCallback(WebsocketsMessage message) { + Serial.print("Got Message: "); + Serial.println(message.data()); +} + +void onEventsCallback(WebsocketsEvent event, String data) { + if(event == WebsocketsEvent::ConnectionOpened) { + Serial.println("Connnection Opened"); + } else if(event == WebsocketsEvent::ConnectionClosed) { + Serial.println("Connnection Closed"); + } else if(event == WebsocketsEvent::GotPing) { + Serial.println("Got a Ping!"); + } else if(event == WebsocketsEvent::GotPong) { + Serial.println("Got a Pong!"); + } +} + +WebsocketsClient client; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + // run callback when messages are received + client.onMessage(onMessageCallback); + + // run callback when events are occuring + client.onEvent(onEventsCallback); + + // Before connecting, set the ssl fingerprint of the server + client.setFingerprint(echo_org_ssl_fingerprint); + + // Connect to server + client.connect(websockets_connection_string); + + // Send a message + client.send("Hello Server"); + + // Send a ping + client.ping(); +} + +void loop() { + client.poll(); +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/examples/SecuredTwoWay-Esp8266-Client/SecuredTwoWay-Esp8266-Client.ino b/libraries/ArduinoWebsockets/examples/SecuredTwoWay-Esp8266-Client/SecuredTwoWay-Esp8266-Client.ino new file mode 100644 index 00000000000..9a953360941 --- /dev/null +++ b/libraries/ArduinoWebsockets/examples/SecuredTwoWay-Esp8266-Client/SecuredTwoWay-Esp8266-Client.ino @@ -0,0 +1,168 @@ +/* + Secured Esp8266 Websockets Client + + This sketch: + 1. Connects to a WiFi network + 2. Connects to a Websockets server (using WSS) + 3. Sends the websockets server a message ("Hello Server") + 4. Sends the websocket server a "ping" + 5. Prints all incoming messages while the connection is open + + NOTE: + The sketch dosen't check or indicate about errors while connecting to + WiFi or to the websockets server. For full example you might want + to try the example named "Esp8266-Client" (And use the ssl methods). + + Hardware: + For this sketch you only need an ESP8266 board. + + Created 15/02/2019 + By Gil Maimon + https://github.com/gilmaimon/ArduinoWebsockets + +*/ + +#include +#include + +const char* ssid = "ssid"; //Enter SSID +const char* password = "password"; //Enter Password + +const char* websockets_connection_string = "wss://echo.websocket.org/"; //Enter server adress + +// The hardcoded certificate authority for this example. +// Don't use it on your own apps!!!!! +const char ca_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIIC1TCCAb2gAwIBAgIJAMPt1Ms37+hLMA0GCSqGSIb3DQEBCwUAMCExCzAJBgNV +BAYTAlVTMRIwEAYDVQQDDAkxMjcuMC4wLjMwHhcNMTgwMzE0MDQyMTU0WhcNMjkw +NTMxMDQyMTU0WjAhMQswCQYDVQQGEwJVUzESMBAGA1UEAwwJMTI3LjAuMC4zMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxsa4qU/tlzN4YTcnn/I/ffsi +jOPc8QRcwClKzasIZNFEye4uThl+LGZWFIFb8X8Dc+xmmBaWlPJbqtphgFKStpar +DdduHSW1ud6Y1FVKxljo3UwCMrYm76Q/jNzXJvGs6Z1MDNsVZzGJaoqit2H2Hkvk +y+7kk3YbEDlcyVsLOw0zCKL4cd2DSNDyhIZxWo2a8Qn5IdjWAYtsTnW6MvLk/ya4 +abNeRfSZwi+r37rqi9CIs++NpL5ynqkKKEMrbeLactWgHbWrZeaMyLpuUEL2GF+w +MRaAwaj7ERwT5gFJRqYwj6bbfIdx5PC7h7ucbyp272MbrDa6WNBCMwQO222t4wID +AQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCmXfrC42nW +IpL3JDkB8YlB2QUvD9JdMp98xxo33+xE69Gov0e6984F1Gluao0p6sS7KF+q3YLS +4hjnzuGzF9GJMimIB7NMQ20yXKfKpmKJ7YugMaKTDWDhHn5679mKVbLSQxHCUMEe +tEnMT93/UaDbWBjV6zu876q5vjPMYgDHODqO295ySaA71UkijaCn6UwKUT49286T +V9ZtzgabNGHXfklHgUPWoShyze+G3g29I1BR0qABoJI63zaNu8ua42v5g1RldxsW +X8yKI14mFOGxuvcygG8L2xxysW7Zq+9g+O7gW0Pm6RDYnUQmIwY83h1KFCtYCJdS +2PgozwkkUNyP +-----END CERTIFICATE----- +)EOF"; + +// The client's private key which must be kept secret +const char client_private_key[] PROGMEM = R"EOF( +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsRNVTvqP++YUh8NrbXwE83xVsDqcB3F76xcXNKFDERfVd2P/ +LvyDovCcoQtT0UCRgPcxRp894EuPH/Ru6Z2Lu85sV//i7ce27tc2WRFSfuhlRxHP +LJWHxTl1CEfXp/owkECQ4MB3pw6Ekc16iTEPiezTG+T+mQ/BkiIwcIK6CMlpR9DI +eYUTqv0f9NrUfAjdBrqlEO2gpgFvLFrkDEU2ntAIc4aPOP7yDOym/xzfy6TiG8Wo +7nlh6M97xTZGfbEPCH9rZDjo5istym1HzF5P+COq+OTSPscjFGXoi978o6hZwa7i +zxorg4h5a5lGnshRu2Gl+Ybfa14OwnIrv/yCswIDAQABAoIBAHxwgbsHCriTcEoY +Yx6F0VTrQ6ydA5mXfuYvS/eIfIE+pp1IgMScYEXZobjrJPQg1CA1l0NyFSHS97oV +JPy34sMQxcLx6KABgeVHCMJ/EeJtnv7a3SUP0GIhhsVS95Lsl8RIG4hWub+EzFVK +eZqAB9N9wr4Pp3wZPodbz37B38rb1QPyMFmQOLlHjKTOmoxsXhL2ot+R3+aLYSur +oPO1kQo7/d0UAZoy8h9OQN4a2EXvawh4O2EvFGbc5X/yXwAdEQ4NPp9VZhkNIRkV ++XZ3FcIqEVOploKtRF/tVBTz3g61/lFz21L9PMmV5y8tvSafr2SpJugGVmp2rrVQ +VNyGlIECgYEA10JSI5gmeCU3zK6kvOfBp54hY/5dDrSUpjKkMxpmm7WZQ6Il/k7A +hMcLeMzHiriT7WhRIXF8AOr2MoEkHkH3DhVNN4ccieVZx2SE5P5mVkItZGLrrpfU +dysR/ARAI1HYegGUiKacZtf9SrRavU0m7fOVOiYwbFRhjyX+MyuteYkCgYEA0pbz +4ZosetScP68uZx1sGlTfkcqLl7i15DHk3gnj6jKlfhvC2MjeLMhNDtKeUAuY7rLQ +guZ0CCghWAv0Glh5eYdfIiPhgqFfX4P5F3Om4zQHVPYj8xHfHG4ZP7dKQTndrO1Q +fLdGDTQLVXabAUSp2YGrijC8J9idSW1pYClvF1sCgYEAjkDn41nzYkbGP1/Swnwu +AEWCL4Czoro32jVxScxSrugt5wJLNWp508VukWBTJhugtq3Pn9hNaJXeKbYqVkyl +pgrxwpZph7+nuxt0r5hnrO2C7eppcjIoWLB/7BorAKxf8REGReBFT7nBTBMwPBW2 +el4U6h6+tXh2GJG1Eb/1nnECgYAydVb0THOx7rWNkNUGggc/++why61M6kYy6j2T +cj05BW+f2tkCBoctpcTI83BZb53yO8g4RS2yMqNirGKN2XspwmTqEjzbhv0KLt4F +X4GyWOoU0nFksXiLIFpOaQWSwWG7KJWrfGJ9kWXR0Xxsfl5QLoDCuNCsn3t4d43T +K7phlwKBgHDzF+50+/Wez3YHCy2a/HgSbHCpLQjkknvgwkOh1z7YitYBUm72HP8Z +Ge6b4wEfNuBdlZll/y9BQQOZJLFvJTE5t51X9klrkGrOb+Ftwr7eI/H5xgcadI52 +tPYglR5fjuRF/wnt3oX9JlQ2RtSbs+3naXH8JoherHaqNn8UpH0t +-----END RSA PRIVATE KEY----- +)EOF"; + +// The clint's public certificate which must be shared +const char client_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIIDTzCCAjcCCQDPXvMRYOpeuDANBgkqhkiG9w0BAQsFADCBpjESMBAGA1UEAwwJ +MTI3LjAuMC4xMQswCQYDVQQGEwJVUzElMCMGA1UECgwcTXkgT3duIENlcnRpZmlj +YXRlIEF1dGhvcml0eTEUMBIGA1UECAwLQXJkdWlub0xhbmQxFTATBgNVBAcMDEFy +ZHVpbm9WaWxsZTEVMBMGA1UECgwMRVNQODI2NlVzZXJzMRgwFgYDVQQLDA9FU1A4 +MjY2LUFyZHVpbm8wHhcNMTgwMzE0MDQwMDAwWhcNMjkwMjI0MDQwMDAwWjAsMRYw +FAYDVQQKDA1NeSBTZXJ2ZXIgT3JnMRIwEAYDVQQDDAkxMjcuMC4wLjMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxE1VO+o/75hSHw2ttfATzfFWwOpwH +cXvrFxc0oUMRF9V3Y/8u/IOi8JyhC1PRQJGA9zFGnz3gS48f9G7pnYu7zmxX/+Lt +x7bu1zZZEVJ+6GVHEc8slYfFOXUIR9en+jCQQJDgwHenDoSRzXqJMQ+J7NMb5P6Z +D8GSIjBwgroIyWlH0Mh5hROq/R/02tR8CN0GuqUQ7aCmAW8sWuQMRTae0Ahzho84 +/vIM7Kb/HN/LpOIbxajueWHoz3vFNkZ9sQ8If2tkOOjmKy3KbUfMXk/4I6r45NI+ +xyMUZeiL3vyjqFnBruLPGiuDiHlrmUaeyFG7YaX5ht9rXg7Cciu//IKzAgMBAAEw +DQYJKoZIhvcNAQELBQADggEBAEnG+FNyNCOkBvzHiUpHHpScxZqM2f+XDcewJgeS +L6HkYEDIZZDNnd5gduSvkHpdJtWgsvJ7dJZL40w7Ba5sxpZHPIgKJGl9hzMkG+aA +z5GMkjys9h2xpQZx9KL3q7G6A+C0bll7ODZlwBtY07CFMykT4Mp2oMRrQKRucMSV +AB1mKujLAnMRKJ3NM89RQJH4GYiRps9y/HvM5lh7EIK/J0/nEZeJxY5hJngskPKb +oPPdmkR97kaQnll4KNsC3owVlHVU2fMftgYkgQLzyeWgzcNa39AF3B6JlcOzNyQY +seoK24dHmt6tWmn/sbxX7Aa6TL/4mVlFoOgcaTJyVaY/BrY= +-----END CERTIFICATE----- +)EOF"; + +using namespace websockets; + +void onMessageCallback(WebsocketsMessage message) { + Serial.print("Got Message: "); + Serial.println(message.data()); +} + +void onEventsCallback(WebsocketsEvent event, String data) { + if(event == WebsocketsEvent::ConnectionOpened) { + Serial.println("Connnection Opened"); + } else if(event == WebsocketsEvent::ConnectionClosed) { + Serial.println("Connnection Closed"); + } else if(event == WebsocketsEvent::GotPing) { + Serial.println("Got a Ping!"); + } else if(event == WebsocketsEvent::GotPong) { + Serial.println("Got a Pong!"); + } +} + +WebsocketsClient client; +void setup() { + Serial.begin(115200); + // Connect to wifi + WiFi.begin(ssid, password); + + // Wait some time to connect to wifi + for(int i = 0; i < 10 && WiFi.status() != WL_CONNECTED; i++) { + Serial.print("."); + delay(1000); + } + + // run callback when messages are received + client.onMessage(onMessageCallback); + + // run callback when events are occuring + client.onEvent(onEventsCallback); + + // Before connecting, set the ssl certificates and key of the server + X509List cert(ca_cert); + client.setTrustAnchors(&cert); + + X509List *serverCertList = new X509List(client_cert); + PrivateKey *serverPrivKey = new PrivateKey(client_private_key); + client.setClientRSACert(serverCertList, serverPrivKey); + + // Connect to server + client.connect(websockets_connection_string); + + // Send a message + client.send("Hello Server"); + + // Send a ping + client.ping(); +} + +void loop() { + client.poll(); +} diff --git a/libraries/ArduinoWebsockets/keywords.txt b/libraries/ArduinoWebsockets/keywords.txt new file mode 100644 index 00000000000..e4d97ff52ef --- /dev/null +++ b/libraries/ArduinoWebsockets/keywords.txt @@ -0,0 +1,82 @@ +websockets KEYWORD1 +network KEYWORD1 +WebsocketsClient KEYWORD1 +WebsocketsServer KEYWORD1 + +connect KEYWORD2 +send KEYWORD2 +sendBinary KEYWORD2 +onMessage KEYWORD2 +onEvent KEYWORD2 +available KEYWORD2 +poll KEYWORD2 +ping KEYWORD2 +pong KEYWORD2 +close KEYWORD2 +setCACert KEYWORD2 +setFingerprint KEYWORD2 +setInsecure KEYWORD2 +readBlocking KEYWORD2 +addHeader KEYWORD2 + +setFragmentsPolicy KEYWORD2 +getFragmentsPolicy KEYWORD2 +getCloseReason KEYWORD2 + + +# Server +listen KEYWORD2 +poll KEYWORD2 +accept KEYWORD2 + +# Message +isEmpty KEYWORD2 +isText KEYWORD2 +isBinary KEYWORD2 +isPing KEYWORD2 +isPong KEYWORD2 +isClose KEYWORD2 +isComplete KEYWORD2 +isPartial KEYWORD2 +isFirst KEYWORD2 +isContinuation KEYWORD2 +isLast KEYWORD2 + +WebsocketsMessage KEYWORD1 +data KEYWORD2 +type KEYWORD2 +rawData KEYWORD2 +c_str KEYWORD2 + +WebsocketsEvent KEYWORD1 +ConnectionOpened LITERAL1 +ConnectionClosed LITERAL1 +GotPing LITERAL1 +GotPong LITERAL1 + +WSString KEYWORD1 +MessageType KEYWORD1 +Text LITERAL1 +Binary LITERAL1 +Close LITERAL1 +Ping LITERAL1 +Pong LITERAL1 + + +CloseReason KEYWORD1 +CloseReason_None LITERAL1 +CloseReason_NormalClosure LITERAL1 +CloseReason_GoingAway LITERAL1 +CloseReason_ProtocolError LITERAL1 +CloseReason_UnsupportedData LITERAL1 +CloseReason_NoStatusRcvd LITERAL1 +CloseReason_AbnormalClosure LITERAL1 +CloseReason_InvalidPayloadData LITERAL1 +CloseReason_PolicyViolation LITERAL1 +CloseReason_MessageTooBig LITERAL1 +CloseReason_InternalServerError LITERAL1 + + +FragmentsPolicy KEYWORD1 +FragmentsPolicy_Aggregate LITERAL1 +FragmentsPolicy_Notify LITERAL1 \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/library.properties b/libraries/ArduinoWebsockets/library.properties new file mode 100644 index 00000000000..277a1b01eb7 --- /dev/null +++ b/libraries/ArduinoWebsockets/library.properties @@ -0,0 +1,9 @@ +name=ArduinoWebsockets +version=0.4.17 +author=Gil Maimon +maintainer=Gil Maimon +sentence=A library for writing modern Websockets applications with Arduino. +paragraph=Featuring modern callbacks (supports lambdas) and a minimal interface. Contains a websockets Client and Server. Supports all features of the RFC (pings, pongs, binary and text data, error codes) and WSS (Websockets over SSL). +category=Communication +url=https://github.com/gilmaimon/ArduinoWebsockets +includes=ArduinoWebsockets.h diff --git a/libraries/ArduinoWebsockets/src/ArduinoWebsockets.h b/libraries/ArduinoWebsockets/src/ArduinoWebsockets.h new file mode 100644 index 00000000000..b48bb97b881 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/ArduinoWebsockets.h @@ -0,0 +1,8 @@ +#ifndef _WEBSOCKETS_CLIENT_H +#define _WEBSOCKETS_CLIENT_H + +#include "tiny_websockets/message.hpp" +#include "tiny_websockets/client.hpp" +#include "tiny_websockets/server.hpp" + +#endif //_WEBSOCKETS_CLIENT_H \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/crypto.cpp b/libraries/ArduinoWebsockets/src/crypto.cpp new file mode 100644 index 00000000000..c4da631996b --- /dev/null +++ b/libraries/ArduinoWebsockets/src/crypto.cpp @@ -0,0 +1,58 @@ +#include +#include +#include + +#ifndef _WS_CONFIG_NO_TRUE_RANDOMNESS +#include +#endif + +namespace websockets { namespace crypto { + WSString base64Encode(WSString data) { + return internals::base64_encode(reinterpret_cast(data.c_str()), data.size()); + } + WSString base64Encode(uint8_t* data, size_t len) { + return internals::base64_encode(reinterpret_cast(data), len); + } + + WSString base64Decode(WSString data) { + return internals::base64_decode(data); + } + + WSString websocketsHandshakeEncodeKey(WSString key) { + char base64[30]; + internals::sha1(key.c_str()) + .add("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") + .finalize() + .print_base64(base64); + + return WSString(base64); + } + +#ifdef _WS_CONFIG_NO_TRUE_RANDOMNESS + WSString randomBytes(size_t len) { + WSString result; + result.reserve(len); + + for(size_t i = 0; i < len; i++) { + result += "0123456789abcdef"[i % 16]; + } + + return result; + } +#else + WSString randomBytes(size_t len) { + static int onlyOnce = [](){ + srand(time(NULL)); + return 0; + }(); + + WSString result; + result.reserve(len); + + for(size_t i = onlyOnce; i < len; i++) { + result += "0123456789abcdefABCDEFGHIJKLMNOPQRSTUVEXYZ"[rand() % 42]; + } + return result; + } +#endif +}} // websockets::crypto \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/message.cpp b/libraries/ArduinoWebsockets/src/message.cpp new file mode 100644 index 00000000000..47609603470 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/message.cpp @@ -0,0 +1,14 @@ +#include + +namespace websockets { + MessageType messageTypeFromOpcode(uint8_t opcode) { + switch(opcode) { + case internals::ContentType::Binary: return MessageType::Binary; + case internals::ContentType::Text: return MessageType::Text; + case internals::ContentType::Ping: return MessageType::Ping; + case internals::ContentType::Pong: return MessageType::Pong; + case internals::ContentType::Close: return MessageType::Close; + default: return MessageType::Empty; + } + } +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/client.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/client.hpp new file mode 100644 index 00000000000..f4026985a6e --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/client.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace websockets { + enum class WebsocketsEvent { + ConnectionOpened, + ConnectionClosed, + GotPing, GotPong + }; + + class WebsocketsClient; + typedef std::function MessageCallback; + typedef std::function PartialMessageCallback; + + typedef std::function EventCallback; + typedef std::function PartialEventCallback; + + class WebsocketsClient { + public: + WebsocketsClient(); + WebsocketsClient(std::shared_ptr client); + + WebsocketsClient(const WebsocketsClient& other); + WebsocketsClient(const WebsocketsClient&& other); + + WebsocketsClient& operator=(const WebsocketsClient& other); + WebsocketsClient& operator=(const WebsocketsClient&& other); + + void addHeader(const WSInterfaceString key, const WSInterfaceString value); + + bool connect(const WSInterfaceString url); + bool connect(const WSInterfaceString host, const int port, const WSInterfaceString path); + + void onMessage(const MessageCallback callback); + void onMessage(const PartialMessageCallback callback); + + void onEvent(const EventCallback callback); + void onEvent(const PartialEventCallback callback); + + bool poll(); + bool available(const bool activeTest = false); + + bool send(const WSInterfaceString&& data); + bool send(const WSInterfaceString& data); + bool send(const char* data); + bool send(const char* data, const size_t len); + + bool sendBinary(const WSInterfaceString data); + bool sendBinary(const char* data, const size_t len); + + // stream messages + bool stream(const WSInterfaceString data = ""); + bool streamBinary(const WSInterfaceString data = ""); + bool end(const WSInterfaceString data = ""); + + void setFragmentsPolicy(const FragmentsPolicy newPolicy); + FragmentsPolicy getFragmentsPolicy() const; + + WebsocketsMessage readBlocking(); + + bool ping(const WSInterfaceString data = ""); + bool pong(const WSInterfaceString data = ""); + + void close(const CloseReason reason = CloseReason_NormalClosure); + CloseReason getCloseReason() const; + + void setUseMasking(bool useMasking) { + _endpoint.setUseMasking(useMasking); + } + + void setInsecure(); + #ifdef ESP8266 + void setFingerprint(const char* fingerprint); + void setClientRSACert(const X509List *cert, const PrivateKey *sk); + void setTrustAnchors(const X509List *ta); + #elif defined(ESP32) + void setCACert(const char* ca_cert); + void setCertificate(const char* client_ca); + void setPrivateKey(const char* private_key); + #endif + + virtual ~WebsocketsClient(); + + private: + std::shared_ptr _client; + std::vector> _customHeaders; + internals::WebsocketsEndpoint _endpoint; + bool _connectionOpen; + MessageCallback _messagesCallback; + EventCallback _eventsCallback; + enum SendMode { + SendMode_Normal, + SendMode_Streaming + } _sendMode; + + + #ifdef ESP8266 + const char* _optional_ssl_fingerprint = nullptr; + const X509List* _optional_ssl_trust_anchors = nullptr; + const X509List* _optional_ssl_cert = nullptr; + const PrivateKey* _optional_ssl_private_key = nullptr; + #elif defined(ESP32) + const char* _optional_ssl_ca_cert = nullptr; + const char* _optional_ssl_client_ca = nullptr; + const char* _optional_ssl_private_key = nullptr; + #endif + + void _handlePing(WebsocketsMessage); + void _handlePong(WebsocketsMessage); + void _handleClose(WebsocketsMessage); + + void upgradeToSecuredConnection(); + }; +} diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/internals/data_frame.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/data_frame.hpp new file mode 100644 index 00000000000..c34e9996b80 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/data_frame.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include + +namespace websockets { namespace internals { + enum ContentType { + // None as error value + None = -1, + // Default value for empty messages + Continuation = 0x0, + + // Data opcdoes + Text = 0x1, + Binary = 0x2, + + // Control opcodes + Close = 0x8, + Ping = 0x9, + Pong = 0xA + }; + + struct WebsocketsFrame { + uint8_t fin : 1; + uint8_t opcode : 4; + uint8_t mask : 1; + uint8_t mask_buf[4]; + uint64_t payload_length; + WSString payload; + + bool isControlFrame() { + return fin && (opcode == 0x8 || opcode == 0x9 || opcode == 0xA); + } + + bool isEmpty() { + return (fin == 0) && (opcode == 0) && (payload_length == 0); + } + + bool isBeginningOfFragmentsStream() const { + return (fin == 0) && (opcode != 0); + } + + bool isContinuesFragment() const { + return (fin == 0) && (opcode == 0); + } + + bool isEndOfFragmentsStream() const { + return (fin == 1) && (opcode == 0); + } + + bool isNormalUnfragmentedMessage() const { + return (fin == 1) && (opcode != 0); + } + }; + + template HeaderTy MakeHeader(size_t len, uint8_t opcode, bool fin, bool mask) { + HeaderTy header; + header.fin = fin; + header.flags = 0; + header.opcode = opcode; + header.mask = mask? 1: 0; + + // set payload + if(len < 126) { + header.payload = len; + } else if(len < 65536) { + header.payload = 126; + } else { + header.payload = 127; + } + + return header; + } + + struct Header { + uint8_t opcode : 4; + uint8_t flags : 3; + uint8_t fin : 1; + uint8_t payload : 7; + uint8_t mask : 1; + }; + + struct HeaderWithExtended16 : Header { + uint16_t extendedPayload; + }; + + struct HeaderWithExtended64 : Header { + uint64_t extendedPayload; + }; +}} // websockets::internals \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/internals/websockets_endpoint.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/websockets_endpoint.hpp new file mode 100644 index 00000000000..99b577ec86f --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/websockets_endpoint.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define __TINY_WS_INTERNAL_DEFAULT_MASK "\00\00\00\00" + +namespace websockets { + enum FragmentsPolicy { + FragmentsPolicy_Aggregate, + FragmentsPolicy_Notify + }; + + enum CloseReason { + CloseReason_None = -1, + CloseReason_NormalClosure = 1000, + CloseReason_GoingAway = 1001, + CloseReason_ProtocolError = 1002, + CloseReason_UnsupportedData = 1003, + CloseReason_NoStatusRcvd = 1005, + CloseReason_AbnormalClosure = 1006, + CloseReason_InvalidPayloadData = 1007, + CloseReason_PolicyViolation = 1008, + CloseReason_MessageTooBig = 1009, + CloseReason_InternalServerError = 1011, + }; + + CloseReason GetCloseReason(uint16_t reasonCode); + + namespace internals { + + class WebsocketsEndpoint { + public: + WebsocketsEndpoint(std::shared_ptr socket, FragmentsPolicy fragmentsPolicy = FragmentsPolicy_Aggregate); + + WebsocketsEndpoint(const WebsocketsEndpoint& other); + WebsocketsEndpoint(const WebsocketsEndpoint&& other); + + WebsocketsEndpoint& operator=(const WebsocketsEndpoint& other); + WebsocketsEndpoint& operator=(const WebsocketsEndpoint&& other); + + void setInternalSocket(std::shared_ptr socket); + + bool poll(); + WebsocketsMessage recv(); + bool send(const char* data, const size_t len, const uint8_t opcode, const bool fin, const bool mask, const char* maskingKey = __TINY_WS_INTERNAL_DEFAULT_MASK); + bool send(const WSString& data, const uint8_t opcode, const bool fin, const bool mask, const char* maskingKey = __TINY_WS_INTERNAL_DEFAULT_MASK); + + bool send(const char* data, const size_t len, const uint8_t opcode, const bool fin); + bool send(const WSString& data, const uint8_t opcode, const bool fin); + + bool ping(const WSString& msg); + bool ping(const WSString&& msg); + + bool pong(const WSString& msg); + bool pong(const WSString&& msg); + + void close(const CloseReason reason = CloseReason_NormalClosure); + CloseReason getCloseReason() const; + + void setFragmentsPolicy(const FragmentsPolicy newPolicy); + FragmentsPolicy getFragmentsPolicy() const; + + void setUseMasking(bool useMasking) { + _useMasking = useMasking; + } + + virtual ~WebsocketsEndpoint(); + private: + std::shared_ptr _client; + FragmentsPolicy _fragmentsPolicy; + enum RecvMode { + RecvMode_Normal, + RecvMode_Streaming + } _recvMode; + WebsocketsMessage::StreamBuilder _streamBuilder; + CloseReason _closeReason; + bool _useMasking = true; + + WebsocketsFrame _recv(); + void handleMessageInternally(WebsocketsMessage& msg); + + WebsocketsMessage handleFrameInStreamingMode(WebsocketsFrame& frame); + WebsocketsMessage handleFrameInStandardMode(WebsocketsFrame& frame); + + std::string getHeader(uint64_t len, uint8_t opcode, bool fin, bool mask); + }; +}} // websockets::internals \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/internals/ws_common.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/ws_common.hpp new file mode 100644 index 00000000000..af4a304591c --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/ws_common.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +namespace websockets { + typedef std::string WSString; + typedef String WSInterfaceString; + + namespace internals { + WSString fromInterfaceString(const WSInterfaceString& str); + WSString fromInterfaceString(const WSInterfaceString&& str); + WSInterfaceString fromInternalString(const WSString& str); + WSInterfaceString fromInternalString(const WSString&& str); + } +} + +#ifdef ESP8266 + #define PLATFORM_DOES_NOT_SUPPORT_BLOCKING_READ + + #include + #define WSDefaultTcpClient websockets::network::Esp8266TcpClient + #define WSDefaultTcpServer websockets::network::Esp8266TcpServer + + #ifndef _WS_CONFIG_NO_SSL + // OpenSSL Dependent + #define WSDefaultSecuredTcpClient websockets::network::SecuredEsp8266TcpClient + #endif //_WS_CONFIG_NO_SSL + +#elif defined(ESP32) + + #define PLATFORM_DOES_NOT_SUPPORT_BLOCKING_READ + + #include + #define WSDefaultTcpClient websockets::network::Esp32TcpClient + #define WSDefaultTcpServer websockets::network::Esp32TcpServer + + #ifndef _WS_CONFIG_NO_SSL + // OpenSSL Dependent + #define WSDefaultSecuredTcpClient websockets::network::SecuredEsp32TcpClient + #endif //_WS_CONFIG_NO_SSL +#endif diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/internals/wscrypto/base64.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/wscrypto/base64.hpp new file mode 100644 index 00000000000..119a56453c8 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/wscrypto/base64.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include + +namespace websockets { namespace crypto { namespace internals { +/* + base64.cpp and base64.hpp + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +static const WSString base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +WSString base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + WSString ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + + } + + return ret; + +} +WSString base64_decode(WSString const& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + WSString ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} + +}}} // websockets::crypto::internals \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/internals/wscrypto/crypto.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/wscrypto/crypto.hpp new file mode 100644 index 00000000000..d578498da53 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/wscrypto/crypto.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace websockets { namespace crypto { + WSString base64Encode(WSString data); + WSString base64Encode(uint8_t* data, size_t len); + WSString base64Decode(WSString data); + WSString websocketsHandshakeEncodeKey(WSString key); + WSString randomBytes(size_t len); +}} // websockets::crypto \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/internals/wscrypto/sha1.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/wscrypto/sha1.hpp new file mode 100644 index 00000000000..e9bb1979be1 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/internals/wscrypto/sha1.hpp @@ -0,0 +1,261 @@ +#pragma once + +#include +#include + +/* + CREDIT: this implementation is from https://github.com/983/SHA1 +*/ +namespace websockets { namespace crypto { namespace internals { +#define SHA1_HEX_SIZE (40 + 1) +#define SHA1_BASE64_SIZE (28 + 1) + +class sha1 { +private: + void add_byte_dont_count_bits(uint8_t x){ + buf[i++] = x; + + if (i >= sizeof(buf)){ + i = 0; + process_block(buf); + } + } + + static uint32_t rol32(uint32_t x, uint32_t n){ + return (x << n) | (x >> (32 - n)); + } + + static uint32_t make_word(const uint8_t *p){ + return + static_cast(p[0] << 3*8) | + static_cast(p[1] << 2*8) | + static_cast(p[2] << 1*8) | + static_cast(p[3] << 0*8); + } + + void process_block(const uint8_t *ptr){ + const uint32_t c0 = 0x5a827999; + const uint32_t c1 = 0x6ed9eba1; + const uint32_t c2 = 0x8f1bbcdc; + const uint32_t c3 = 0xca62c1d6; + + uint32_t a = state[0]; + uint32_t b = state[1]; + uint32_t c = state[2]; + uint32_t d = state[3]; + uint32_t e = state[4]; + + uint32_t w[16]; + + for (int _i = 0; _i < 16; _i++) w[_i] = make_word(ptr + _i*4); + +#define SHA1_LOAD(i) w[i&15] = rol32(w[(i+13)&15] ^ w[(i+8)&15] ^ w[(i+2)&15] ^ w[i&15], 1); +#define SHA1_ROUND_0(v,u,x,y,z,i) z += ((u & (x ^ y)) ^ y) + w[i&15] + c0 + rol32(v, 5); u = rol32(u, 30); +#define SHA1_ROUND_1(v,u,x,y,z,i) SHA1_LOAD(i) z += ((u & (x ^ y)) ^ y) + w[i&15] + c0 + rol32(v, 5); u = rol32(u, 30); +#define SHA1_ROUND_2(v,u,x,y,z,i) SHA1_LOAD(i) z += (u ^ x ^ y) + w[i&15] + c1 + rol32(v, 5); u = rol32(u, 30); +#define SHA1_ROUND_3(v,u,x,y,z,i) SHA1_LOAD(i) z += (((u | x) & y) | (u & x)) + w[i&15] + c2 + rol32(v, 5); u = rol32(u, 30); +#define SHA1_ROUND_4(v,u,x,y,z,i) SHA1_LOAD(i) z += (u ^ x ^ y) + w[i&15] + c3 + rol32(v, 5); u = rol32(u, 30); + + SHA1_ROUND_0(a, b, c, d, e, 0); + SHA1_ROUND_0(e, a, b, c, d, 1); + SHA1_ROUND_0(d, e, a, b, c, 2); + SHA1_ROUND_0(c, d, e, a, b, 3); + SHA1_ROUND_0(b, c, d, e, a, 4); + SHA1_ROUND_0(a, b, c, d, e, 5); + SHA1_ROUND_0(e, a, b, c, d, 6); + SHA1_ROUND_0(d, e, a, b, c, 7); + SHA1_ROUND_0(c, d, e, a, b, 8); + SHA1_ROUND_0(b, c, d, e, a, 9); + SHA1_ROUND_0(a, b, c, d, e, 10); + SHA1_ROUND_0(e, a, b, c, d, 11); + SHA1_ROUND_0(d, e, a, b, c, 12); + SHA1_ROUND_0(c, d, e, a, b, 13); + SHA1_ROUND_0(b, c, d, e, a, 14); + SHA1_ROUND_0(a, b, c, d, e, 15); + SHA1_ROUND_1(e, a, b, c, d, 16); + SHA1_ROUND_1(d, e, a, b, c, 17); + SHA1_ROUND_1(c, d, e, a, b, 18); + SHA1_ROUND_1(b, c, d, e, a, 19); + SHA1_ROUND_2(a, b, c, d, e, 20); + SHA1_ROUND_2(e, a, b, c, d, 21); + SHA1_ROUND_2(d, e, a, b, c, 22); + SHA1_ROUND_2(c, d, e, a, b, 23); + SHA1_ROUND_2(b, c, d, e, a, 24); + SHA1_ROUND_2(a, b, c, d, e, 25); + SHA1_ROUND_2(e, a, b, c, d, 26); + SHA1_ROUND_2(d, e, a, b, c, 27); + SHA1_ROUND_2(c, d, e, a, b, 28); + SHA1_ROUND_2(b, c, d, e, a, 29); + SHA1_ROUND_2(a, b, c, d, e, 30); + SHA1_ROUND_2(e, a, b, c, d, 31); + SHA1_ROUND_2(d, e, a, b, c, 32); + SHA1_ROUND_2(c, d, e, a, b, 33); + SHA1_ROUND_2(b, c, d, e, a, 34); + SHA1_ROUND_2(a, b, c, d, e, 35); + SHA1_ROUND_2(e, a, b, c, d, 36); + SHA1_ROUND_2(d, e, a, b, c, 37); + SHA1_ROUND_2(c, d, e, a, b, 38); + SHA1_ROUND_2(b, c, d, e, a, 39); + SHA1_ROUND_3(a, b, c, d, e, 40); + SHA1_ROUND_3(e, a, b, c, d, 41); + SHA1_ROUND_3(d, e, a, b, c, 42); + SHA1_ROUND_3(c, d, e, a, b, 43); + SHA1_ROUND_3(b, c, d, e, a, 44); + SHA1_ROUND_3(a, b, c, d, e, 45); + SHA1_ROUND_3(e, a, b, c, d, 46); + SHA1_ROUND_3(d, e, a, b, c, 47); + SHA1_ROUND_3(c, d, e, a, b, 48); + SHA1_ROUND_3(b, c, d, e, a, 49); + SHA1_ROUND_3(a, b, c, d, e, 50); + SHA1_ROUND_3(e, a, b, c, d, 51); + SHA1_ROUND_3(d, e, a, b, c, 52); + SHA1_ROUND_3(c, d, e, a, b, 53); + SHA1_ROUND_3(b, c, d, e, a, 54); + SHA1_ROUND_3(a, b, c, d, e, 55); + SHA1_ROUND_3(e, a, b, c, d, 56); + SHA1_ROUND_3(d, e, a, b, c, 57); + SHA1_ROUND_3(c, d, e, a, b, 58); + SHA1_ROUND_3(b, c, d, e, a, 59); + SHA1_ROUND_4(a, b, c, d, e, 60); + SHA1_ROUND_4(e, a, b, c, d, 61); + SHA1_ROUND_4(d, e, a, b, c, 62); + SHA1_ROUND_4(c, d, e, a, b, 63); + SHA1_ROUND_4(b, c, d, e, a, 64); + SHA1_ROUND_4(a, b, c, d, e, 65); + SHA1_ROUND_4(e, a, b, c, d, 66); + SHA1_ROUND_4(d, e, a, b, c, 67); + SHA1_ROUND_4(c, d, e, a, b, 68); + SHA1_ROUND_4(b, c, d, e, a, 69); + SHA1_ROUND_4(a, b, c, d, e, 70); + SHA1_ROUND_4(e, a, b, c, d, 71); + SHA1_ROUND_4(d, e, a, b, c, 72); + SHA1_ROUND_4(c, d, e, a, b, 73); + SHA1_ROUND_4(b, c, d, e, a, 74); + SHA1_ROUND_4(a, b, c, d, e, 75); + SHA1_ROUND_4(e, a, b, c, d, 76); + SHA1_ROUND_4(d, e, a, b, c, 77); + SHA1_ROUND_4(c, d, e, a, b, 78); + SHA1_ROUND_4(b, c, d, e, a, 79); + +#undef SHA1_LOAD +#undef SHA1_ROUND_0 +#undef SHA1_ROUND_1 +#undef SHA1_ROUND_2 +#undef SHA1_ROUND_3 +#undef SHA1_ROUND_4 + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + } + +public: + + uint32_t state[5]; + uint8_t buf[64]; + uint32_t i; + uint64_t n_bits; + + sha1(const char *text = NULL): i(0), n_bits(0){ + state[0] = 0x67452301; + state[1] = 0xEFCDAB89; + state[2] = 0x98BADCFE; + state[3] = 0x10325476; + state[4] = 0xC3D2E1F0; + add(text); + } + + sha1& add(uint8_t x){ + add_byte_dont_count_bits(x); + n_bits += 8; + return *this; + } + + sha1& add(char c){ + return add( static_cast(c) ); + } + + sha1& add(const void *data, uint32_t n){ + if (!data) return *this; + + const uint8_t *ptr = reinterpret_cast(data); + + // fill up block if not full + for (; n && i % sizeof(buf); n--) add(*ptr++); + + // process full blocks + for (; n >= sizeof(buf); n -= sizeof(buf)){ + process_block(ptr); + ptr += sizeof(buf); + n_bits += sizeof(buf) * 8; + } + + // process remaining part of block + for (; n; n--) add(*ptr++); + + return *this; + } + + sha1& add(const char *text){ + return add(text, strlen(text)); + } + + sha1& finalize(){ + // hashed text ends with 0x80, some padding 0x00 and the length in bits + add_byte_dont_count_bits(0x80); + while (i % 64 != 56) add_byte_dont_count_bits(0x00); + for (int j = 7; j >= 0; j--) add_byte_dont_count_bits(n_bits >> j * 8); + + return *this; + } + + const sha1& print_hex( + char *hex, + bool zero_terminate = true, + const char *alphabet = "0123456789abcdef" + ) const { + // print hex + int k = 0; + for (int _i = 0; _i < 5; _i++){ + for (int j = 7; j >= 0; j--){ + hex[k++] = alphabet[(state[_i] >> j * 4) & 0xf]; + } + } + if (zero_terminate) hex[k] = '\0'; + return *this; + } + + const sha1& print_base64(char *base64, bool zero_terminate = true) const { + static const uint8_t *table = reinterpret_cast( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/" + ); + + uint32_t triples[7] = { + ((state[0] & 0xffffff00) >> 1*8), + ((state[0] & 0x000000ff) << 2*8) | ((state[1] & 0xffff0000) >> 2*8), + ((state[1] & 0x0000ffff) << 1*8) | ((state[2] & 0xff000000) >> 3*8), + ((state[2] & 0x00ffffff) << 0*8), + ((state[3] & 0xffffff00) >> 1*8), + ((state[3] & 0x000000ff) << 2*8) | ((state[4] & 0xffff0000) >> 2*8), + ((state[4] & 0x0000ffff) << 1*8), + }; + + for (int _i = 0; _i < 7; _i++){ + uint32_t x = triples[_i]; + base64[_i*4 + 0] = table[(x >> 3*6) % 64]; + base64[_i*4 + 1] = table[(x >> 2*6) % 64]; + base64[_i*4 + 2] = table[(x >> 1*6) % 64]; + base64[_i*4 + 3] = table[(x >> 0*6) % 64]; + } + + base64[SHA1_BASE64_SIZE - 2] = '='; + if (zero_terminate) base64[SHA1_BASE64_SIZE - 1] = '\0'; + return *this; + } +}; +}}} // websockets::crypto::internals \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/message.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/message.hpp new file mode 100644 index 00000000000..c4edaf344db --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/message.hpp @@ -0,0 +1,186 @@ +#pragma once + +#include +#include + +namespace websockets { + enum class MessageType { + Empty, + Text, Binary, + Ping, Pong, Close + }; + + MessageType messageTypeFromOpcode(uint8_t opcode); + + enum class MessageRole { + Complete, First, Continuation, Last + }; + + // The class the user will interact with as a message + // This message can be partial (so practically this is a Frame and not a message) + struct WebsocketsMessage { + WebsocketsMessage(MessageType msgType, const WSString& msgData, MessageRole msgRole = MessageRole::Complete) : _type(msgType), _length(msgData.size()), _data(msgData), _role(msgRole) {} + WebsocketsMessage() : WebsocketsMessage(MessageType::Empty, "", MessageRole::Complete) {} + + static WebsocketsMessage CreateFromFrame(internals::WebsocketsFrame frame, MessageType overrideType = MessageType::Empty) { + auto type = overrideType; + if(type == MessageType::Empty) { + type = messageTypeFromOpcode(frame.opcode); + } + + // deduce role + MessageRole msgRole = MessageRole::Complete; + if(frame.isNormalUnfragmentedMessage()) { + msgRole = MessageRole::Complete; + } else if(frame.isBeginningOfFragmentsStream()) { + msgRole = MessageRole::First; + } else if(frame.isContinuesFragment()) { + msgRole = MessageRole::Continuation; + } else if(frame.isEndOfFragmentsStream()) { + msgRole = MessageRole::Last; + } + + return WebsocketsMessage(type, std::move(frame.payload), msgRole); + } + + // for validation + bool isEmpty() const { return this->_type == MessageType::Empty; } + + // Type Helper Functions + MessageType type() const { return this->_type; } + + bool isText() const { return this->_type == MessageType::Text; } + bool isBinary() const { return this->_type == MessageType::Binary; } + + bool isPing() const { return this->_type == MessageType::Ping; } + bool isPong() const { return this->_type == MessageType::Pong; } + + bool isClose() const { return this->_type == MessageType::Close; } + + + // Role Helper Function + MessageRole role() const { return this->_role; } + + bool isComplete() const { return this->_role == MessageRole::Complete; } + bool isPartial() const { return this->_role != MessageRole::Complete; } + bool isFirst() const { return this->_role == MessageRole::First; } + bool isContinuation() const { return this->_role == MessageRole::Continuation; } + bool isLast() const { return this->_role == MessageRole::Last; } + + + WSInterfaceString data() const { return internals::fromInternalString(this->_data); } + const WSString& rawData() const { return this->_data; } + const char* c_str() const { return this->_data.c_str(); } + + uint32_t length() const { return this->_length; } + + class StreamBuilder { + public: + StreamBuilder(bool dummyMode = false) : _dummyMode(dummyMode), _empty(true) {} + + void first(const internals::WebsocketsFrame& frame) { + if(this->_empty == false) { + badFragment(); + return; + } + + this->_empty = false; + if(frame.isBeginningOfFragmentsStream()) { + this->_isComplete = false; + this->_didErrored = false; + + if(this->_dummyMode == false) { + this->_content = std::move(frame.payload); + } + + this->_type = messageTypeFromOpcode(frame.opcode); + if(this->_type == MessageType::Empty) { + badFragment(); + } + } else { + this->_didErrored = true; + } + } + + void append(const internals::WebsocketsFrame& frame) { + if(isErrored()) return; + if(isEmpty() || isComplete()) { + badFragment(); + return; + } + + if(frame.isContinuesFragment()) { + if(this->_dummyMode == false) { + this->_content += std::move(frame.payload); + } + } else { + badFragment(); + } + } + + void end(const internals::WebsocketsFrame& frame) { + if(isErrored()) return; + if(isEmpty() || isComplete()) { + badFragment(); + return; + } + + if(frame.isEndOfFragmentsStream()) { + if(this->_dummyMode == false) { + this->_content += std::move(frame.payload); + } + this->_isComplete = true; + } else { + badFragment(); + } + } + + void badFragment() { + this->_didErrored = true; + this->_isComplete = false; + } + + bool isErrored() { + return this->_didErrored; + } + + bool isOk() { + return !this->_didErrored; + } + + bool isComplete() { + return this->_isComplete; + } + + bool isEmpty() { + return this->_empty; + } + + MessageType type() { + return this->_type; + } + + WebsocketsMessage build() { + return WebsocketsMessage( + this->_type, + std::move(this->_content), + MessageRole::Complete + ); + } + + private: + bool _dummyMode; + bool _empty; + bool _isComplete = false; + WSString _content; + MessageType _type; + bool _didErrored; + }; + + private: + const MessageType _type; + const uint32_t _length; + const WSString _data; + const MessageRole _role; + }; +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/esp32/esp32_tcp.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/esp32/esp32_tcp.hpp new file mode 100644 index 00000000000..9ed526655c3 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/esp32/esp32_tcp.hpp @@ -0,0 +1,81 @@ +#pragma once + +#ifdef ESP32 + +#include +#include +#include +#include + +#include +#include + +namespace websockets { namespace network { + typedef GenericEspTcpClient Esp32TcpClient; + + class SecuredEsp32TcpClient : public GenericEspTcpClient { + public: + void setCACert(const char* ca_cert) { + this->client.setCACert(ca_cert); + } + + void setCertificate(const char* client_ca) { + this->client.setCertificate(client_ca); + } + + void setPrivateKey(const char* private_key) { + this->client.setPrivateKey(private_key); + } + }; + + + class Esp32TcpServer : public TcpServer { + public: + Esp32TcpServer() {} + bool poll() override { + yield(); + return server.hasClient(); + } + + bool listen(const uint16_t port) override { + yield(); + server = WiFiServer(port); + server.begin(port); + return available(); + } + + TcpClient* accept() override { + while(available()) { + auto client = server.available(); + if(client) { + return new Esp32TcpClient{client}; + } + } + return new Esp32TcpClient; + } + + bool available() override { + yield(); + return static_cast(server); + } + + void close() override { + yield(); + server.close(); + } + + virtual ~Esp32TcpServer() { + if(available()) close(); + } + + protected: + int getSocket() const override { + return -1; // Not Implemented + } + + private: + WiFiServer server; + }; +}} // websockets::network + +#endif // #ifdef ESP32 diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/esp8266/esp8266_tcp.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/esp8266/esp8266_tcp.hpp new file mode 100644 index 00000000000..c11456637cd --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/esp8266/esp8266_tcp.hpp @@ -0,0 +1,84 @@ +#pragma once + +#ifdef ESP8266 + +#include +#include +#include +#include + +#include + +namespace websockets { namespace network { + typedef GenericEspTcpClient Esp8266TcpClient; + + class SecuredEsp8266TcpClient : public GenericEspTcpClient { + public: + void setInsecure() { + this->client.setInsecure(); + } + + void setFingerprint(const char* fingerprint) { + this->client.setFingerprint(fingerprint); + } + + void setClientRSACert(const X509List *cert, const PrivateKey *sk) { + this->client.setClientRSACert(cert, sk); + } + + void setTrustAnchors(const X509List *ta){ + this->client.setTrustAnchors(ta); + } + + }; + + #define DUMMY_PORT 0 + + class Esp8266TcpServer : public TcpServer { + public: + Esp8266TcpServer() : server(DUMMY_PORT) {} + bool poll() override { + yield(); + return server.hasClient(); + } + + bool listen(const uint16_t port) override { + yield(); + server.begin(port); + return available(); + } + + TcpClient* accept() override { + while(available()) { + yield(); + auto client = server.available(); + if(client) return new Esp8266TcpClient{client}; + } + return new Esp8266TcpClient; + } + + bool available() override { + yield(); + return server.status() != CLOSED; + } + + void close() override { + yield(); + server.close(); + } + + virtual ~Esp8266TcpServer() { + if(available()) close(); + } + + protected: + int getSocket() const override { + return -1; + } + + private: + WiFiServer server; + }; +}} // websockets::network + +#endif // #ifdef ESP8266 diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/generic_esp/generic_esp_clients.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/generic_esp/generic_esp_clients.hpp new file mode 100644 index 00000000000..96bf86a7a97 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/generic_esp/generic_esp_clients.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +namespace websockets { namespace network { + template + class GenericEspTcpClient : public TcpClient { + public: + GenericEspTcpClient(WifiClientImpl c) : client(c) { + client.setNoDelay(true); + } + + GenericEspTcpClient() {} + + bool connect(const WSString& host, const int port) { + yield(); + auto didConnect = client.connect(host.c_str(), port); + client.setNoDelay(true); + return didConnect; + } + + bool poll() { + yield(); + return client.available(); + } + + bool available() override { + return client.connected(); + } + + void send(const WSString& data) override { + yield(); + client.write(reinterpret_cast(const_cast(data.c_str())), data.size()); + yield(); + } + + void send(const WSString&& data) override { + yield(); + client.write(reinterpret_cast(const_cast(data.c_str())), data.size()); + yield(); + } + + void send(const uint8_t* data, const uint32_t len) override { + yield(); + client.write(data, len); + yield(); + } + + WSString readLine() override { + WSString line = ""; + + int ch = -1; + while( ch != '\n' && available()) { + ch = client.read(); + if (ch < 0) continue; + line += (char) ch; + } + + return line; + } + + uint32_t read(uint8_t* buffer, const uint32_t len) override { + yield(); + return client.read(buffer, len); + } + + void close() override { + yield(); + client.stop(); + } + + virtual ~GenericEspTcpClient() { + client.stop(); + } + + protected: + WifiClientImpl client; + + int getSocket() const override { + return -1; + } + }; +}} // websockets::network diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/linux/linux_tcp_client.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/linux/linux_tcp_client.hpp new file mode 100644 index 00000000000..77978589146 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/linux/linux_tcp_client.hpp @@ -0,0 +1,34 @@ +#pragma once + +#ifdef __linux__ + +#include +#include +#include + +#define INVALID_SOCKET -1 + +namespace websockets { namespace network { + class LinuxTcpClient : public TcpClient { + public: + LinuxTcpClient(int socket = INVALID_SOCKET); + bool connect(const WSString& host, int port) override; + bool poll() override; + bool available() override; + void send(const WSString& data) override; + void send(const WSString&& data) override; + void send(const uint8_t* data, const uint32_t len) override; + WSString readLine() override; + void read(uint8_t* buffer, const uint32_t len) override; + void close() override; + virtual ~LinuxTcpClient(); + + protected: + virtual int getSocket() const override { return _socket; } + + private: + int _socket; + }; +}} // websockets::network + +#endif // #ifdef __linux__ diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/linux/linux_tcp_server.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/linux/linux_tcp_server.hpp new file mode 100644 index 00000000000..f1cfe0c6b93 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/linux/linux_tcp_server.hpp @@ -0,0 +1,31 @@ +#pragma once + +#ifdef __linux__ + +#include +#include +#include + +#define DEFAULT_BACKLOG_SIZE 5 + +namespace websockets { namespace network { + class LinuxTcpServer : public TcpServer { + public: + LinuxTcpServer(size_t backlog = DEFAULT_BACKLOG_SIZE) : _num_backlog(backlog) {} + bool listen(const uint16_t port) override; + bool poll() override; + TcpClient* accept() override; + bool available() override; + void close() override; + virtual ~LinuxTcpServer(); + + protected: + virtual int getSocket() const override { return _socket; } + + private: + int _socket; + size_t _num_backlog; + }; +}} // websockets::network + +#endif // #ifdef __linux__ diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/tcp_client.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/tcp_client.hpp new file mode 100644 index 00000000000..f0b45aff686 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/tcp_client.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace websockets { namespace network { + struct TcpClient : public TcpSocket { + virtual bool poll() = 0; + virtual void send(const WSString& data) = 0; + virtual void send(const WSString&& data) = 0; + virtual void send(const uint8_t* data, const uint32_t len) = 0; + virtual WSString readLine() = 0; + virtual uint32_t read(uint8_t* buffer, const uint32_t len) = 0; + virtual bool connect(const WSString& host, int port) = 0; + virtual ~TcpClient() {} + }; +}} // websockets::network \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/tcp_server.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/tcp_server.hpp new file mode 100644 index 00000000000..1ad38b126b3 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/tcp_server.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include + +namespace websockets { namespace network { + struct TcpServer : public TcpSocket { + virtual bool poll() = 0; + virtual bool listen(const uint16_t port) = 0; + virtual TcpClient* accept() = 0; + virtual ~TcpServer() {} + }; +}} // websockets::network \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/tcp_socket.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/tcp_socket.hpp new file mode 100644 index 00000000000..42dec6f37f3 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/tcp_socket.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace websockets { namespace network { + struct TcpSocket { + public: + virtual bool available() = 0; + virtual void close() = 0; + virtual ~TcpSocket() {} + protected: + virtual int getSocket() const = 0; + }; +}} // websockets::network \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/windows/win_tcp_client.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/windows/win_tcp_client.hpp new file mode 100644 index 00000000000..96c79c70784 --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/windows/win_tcp_client.hpp @@ -0,0 +1,45 @@ +#pragma once + +#ifdef _WIN32 + +#include +#include + +#define WIN32_LEAN_AND_MEAN + +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x501 + +#include +#include +#include +#include +#include + +#include + +namespace websockets { namespace network { + class WinTcpClient : public TcpClient { + public: + WinTcpClient(const SOCKET s = INVALID_SOCKET); + bool connect(const WSString& host, const int port) override; + bool poll() override; + bool available() override; + void send(const WSString& data) override; + void send(const WSString&& data) override; + void send(const uint8_t* data, const uint32_t len) override; + WSString readLine() override; + void read(uint8_t* buffer, const uint32_t len) override; + void close() override; + virtual ~WinTcpClient(); + + protected: + int getSocket() const override; + + private: + SOCKET socket; + }; +}} // websockets::network + + +#endif // #ifdef _WIN32 \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/network/windows/win_tcp_server.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/network/windows/win_tcp_server.hpp new file mode 100644 index 00000000000..a6b22e3fd5e --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/network/windows/win_tcp_server.hpp @@ -0,0 +1,38 @@ +#pragma once + +#ifdef _WIN32 + +#include +#include + +#define WIN32_LEAN_AND_MEAN + +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x501 + +#include +#include +#include +#include +#include + +namespace websockets { namespace network { + class WinTcpServer : public TcpServer { + public: + bool listen(uint16_t port) override; + TcpClient* accept() override; + bool available() override; + bool poll() override; + void close() override; + virtual ~WinTcpServer(); + + protected: + int getSocket() const override; + + private: + SOCKET socket; + }; +}} // websockets::network + + +#endif // #ifdef _WIN32 \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/server.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/server.hpp new file mode 100644 index 00000000000..09a56125bba --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/server.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +namespace websockets { + class WebsocketsServer { + public: + WebsocketsServer(network::TcpServer* server = new WSDefaultTcpServer); + + WebsocketsServer(const WebsocketsServer& other) = delete; + WebsocketsServer(const WebsocketsServer&& other) = delete; + + WebsocketsServer& operator=(const WebsocketsServer& other) = delete; + WebsocketsServer& operator=(const WebsocketsServer&& other) = delete; + + bool available(); + void listen(uint16_t port); + bool poll(); + WebsocketsClient accept(); + + virtual ~WebsocketsServer(); + + private: + network::TcpServer* _server; + }; +} \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/tiny_websockets/ws_config_defs.hpp b/libraries/ArduinoWebsockets/src/tiny_websockets/ws_config_defs.hpp new file mode 100644 index 00000000000..9ceecbc513c --- /dev/null +++ b/libraries/ArduinoWebsockets/src/tiny_websockets/ws_config_defs.hpp @@ -0,0 +1,4 @@ +#pragma once + +#define _WS_CONFIG_NO_TRUE_RANDOMNESS +#define _WS_BUFFER_SIZE 512 \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/websockets_client.cpp b/libraries/ArduinoWebsockets/src/websockets_client.cpp new file mode 100644 index 00000000000..92bd0ba696b --- /dev/null +++ b/libraries/ArduinoWebsockets/src/websockets_client.cpp @@ -0,0 +1,621 @@ +#include +#include +#include +#include +#include + +namespace websockets { + WebsocketsClient::WebsocketsClient() : WebsocketsClient(std::make_shared()) { + // Empty + } + + WebsocketsClient::WebsocketsClient(std::shared_ptr client) : + _client(client), + _endpoint(client), + _connectionOpen(client->available()), + _messagesCallback([](WebsocketsClient&, WebsocketsMessage){}), + _eventsCallback([](WebsocketsClient&, WebsocketsEvent, WSInterfaceString){}), + _sendMode(SendMode_Normal) { + // Empty + } + + WebsocketsClient::WebsocketsClient(const WebsocketsClient& other) : + _client(other._client), + _endpoint(other._endpoint), + _connectionOpen(other._client->available()), + _messagesCallback(other._messagesCallback), + _eventsCallback(other._eventsCallback), + _sendMode(other._sendMode) { + + // delete other's client + const_cast(other)._client = nullptr; + const_cast(other)._connectionOpen = false; + } + + WebsocketsClient::WebsocketsClient(const WebsocketsClient&& other) : + _client(other._client), + _endpoint(other._endpoint), + _connectionOpen(other._client->available()), + _messagesCallback(other._messagesCallback), + _eventsCallback(other._eventsCallback), + _sendMode(other._sendMode) { + + // delete other's client + const_cast(other)._client = nullptr; + const_cast(other)._connectionOpen = false; + } + + WebsocketsClient& WebsocketsClient::operator=(const WebsocketsClient& other) { + // call endpoint's copy operator + _endpoint = other._endpoint; + + // get callbacks and data from other + this->_client = other._client; + this->_messagesCallback = other._messagesCallback; + this->_eventsCallback = other._eventsCallback; + this->_connectionOpen = other._connectionOpen; + this->_sendMode = other._sendMode; + + // delete other's client + const_cast(other)._client = nullptr; + const_cast(other)._connectionOpen = false; + return *this; + } + + WebsocketsClient& WebsocketsClient::operator=(const WebsocketsClient&& other) { + // call endpoint's copy operator + _endpoint = other._endpoint; + + // get callbacks and data from other + this->_client = other._client; + this->_messagesCallback = other._messagesCallback; + this->_eventsCallback = other._eventsCallback; + this->_connectionOpen = other._connectionOpen; + this->_sendMode = other._sendMode; + + // delete other's client + const_cast(other)._client = nullptr; + const_cast(other)._connectionOpen = false; + return *this; + } + + struct HandshakeRequestResult { + WSString requestStr; + WSString expectedAcceptKey; + }; + + bool shouldAddDefaultHeader(const std::string& keyWord, const std::vector>& customHeaders) { + for (const auto& header : customHeaders) { + if(!keyWord.compare(header.first)) { + return false; + } + } + + return true; + } + + HandshakeRequestResult generateHandshake(const WSString& host, const WSString& uri, + const std::vector>& customHeaders) { + + WSString key = crypto::base64Encode(crypto::randomBytes(16)); + + WSString handshake = "GET " + uri + " HTTP/1.1\r\n"; + handshake += "Host: " + host + "\r\n"; + handshake += "Sec-WebSocket-Key: " + key + "\r\n"; + + for (const auto& header: customHeaders) { + handshake += header.first + ": " + header.second + "\r\n"; + } + + if (shouldAddDefaultHeader("Upgrade", customHeaders)) { + handshake += "Upgrade: websocket\r\n"; + } + + if (shouldAddDefaultHeader("Connection", customHeaders)) { + handshake += "Connection: Upgrade\r\n"; + } + + if (shouldAddDefaultHeader("Sec-WebSocket-Version", customHeaders)) { + handshake += "Sec-WebSocket-Version: 13\r\n"; + } + + if (shouldAddDefaultHeader("User-Agent", customHeaders)) { + handshake += "User-Agent: TinyWebsockets Client\r\n"; + } + + if (shouldAddDefaultHeader("Origin", customHeaders)) { + handshake += "Origin: https://github.com/gilmaimon/TinyWebsockets\r\n"; + } + + handshake += "\r\n"; + + HandshakeRequestResult result; + result.requestStr = handshake; +#ifndef _WS_CONFIG_SKIP_HANDSHAKE_ACCEPT_VALIDATION + result.expectedAcceptKey = crypto::websocketsHandshakeEncodeKey(key); +#endif + return std::move(result); + } + + bool isWhitespace(char ch) { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; + } + + struct HandshakeResponseResult { + bool isSuccess; + WSString serverAccept; + }; + + bool isCaseInsensetiveEqual(const WSString lhs, const WSString rhs) { + if (lhs.size() != rhs.size()) return false; + + for (size_t i = 0; i < lhs.size(); i++) { + char leftLowerCaseChar = lhs[i] >= 'A' && lhs[i] <= 'Z' ? lhs[i] - 'A' + 'a' : lhs[i]; + char righerLowerCaseChar = rhs[i] >= 'A' && rhs[i] <= 'Z' ? rhs[i] - 'A' + 'a' : rhs[i]; + if (leftLowerCaseChar != righerLowerCaseChar) return false; + } + + return true; + } + + HandshakeResponseResult parseHandshakeResponse(std::vector responseHeaders) { + bool didUpgradeToWebsockets = false, isConnectionUpgraded = false; + WSString serverAccept = ""; + for(WSString header : responseHeaders) { + auto colonIndex = header.find_first_of(':'); + + WSString key = header.substr(0, colonIndex); + WSString value = header.substr(colonIndex + 2); // +2 (ignore space and ':') + + if(isCaseInsensetiveEqual(key, "upgrade")) { + didUpgradeToWebsockets = isCaseInsensetiveEqual(value, "Websocket"); + } else if(isCaseInsensetiveEqual(key, "connection")) { + isConnectionUpgraded = isCaseInsensetiveEqual(value, "upgrade"); + } else if(isCaseInsensetiveEqual(key, "Sec-WebSocket-Accept")) { + serverAccept = value; + } + } + + HandshakeResponseResult result; + result.isSuccess = serverAccept != "" && didUpgradeToWebsockets && isConnectionUpgraded; + result.serverAccept = serverAccept; + return result; + } + + bool doestStartsWith(WSString str, WSString prefix) { + if(str.size() < prefix.size()) return false; + for(size_t i = 0; i < prefix.size(); i++) { + if(str[i] != prefix[i]) return false; + } + + return true; + } + + void WebsocketsClient::upgradeToSecuredConnection() { + #ifndef _WS_CONFIG_NO_SSL + auto client = new WSDefaultSecuredTcpClient; + + #ifdef ESP8266 + if(this->_optional_ssl_fingerprint || (this->_optional_ssl_cert && this->_optional_ssl_private_key) || this->_optional_ssl_trust_anchors) { + if(this->_optional_ssl_fingerprint) { + client->setFingerprint(this->_optional_ssl_fingerprint); + } + if(this->_optional_ssl_cert && this->_optional_ssl_private_key) { + client->setClientRSACert(this->_optional_ssl_cert, this->_optional_ssl_private_key); + } + if(this->_optional_ssl_trust_anchors) { + client->setTrustAnchors(this->_optional_ssl_trust_anchors); + } + } + else { + client->setInsecure(); + } + #elif defined(ESP32) + if(this->_optional_ssl_ca_cert) { + client->setCACert(this->_optional_ssl_ca_cert); + } + if(this->_optional_ssl_client_ca) { + client->setCertificate(this->_optional_ssl_client_ca); + } + if(this->_optional_ssl_private_key) { + client->setPrivateKey(this->_optional_ssl_private_key); + } + #endif + + this->_client = std::shared_ptr(client); + this->_endpoint.setInternalSocket(this->_client); + #endif //_WS_CONFIG_NO_SSL + } + + void WebsocketsClient::addHeader(const WSInterfaceString key, const WSInterfaceString value) { + _customHeaders.push_back({internals::fromInterfaceString(key), internals::fromInterfaceString(value)}); + } + + bool WebsocketsClient::connect(WSInterfaceString _url) { + WSString url = internals::fromInterfaceString(_url); + WSString protocol = ""; + int defaultPort = 0; + + if(doestStartsWith(url, "http://")) { + defaultPort = 80; + protocol = "http"; + url = url.substr(7); //strlen("http://") == 7 + } else if(doestStartsWith(url, "ws://")) { + defaultPort = 80; + protocol = "ws"; + url = url.substr(5); //strlen("ws://") == 5 + } + + #ifndef _WS_CONFIG_NO_SSL + else if(doestStartsWith(url, "wss://")) { + defaultPort = 443; + protocol = "wss"; + url = url.substr(6); //strlen("wss://") == 6 + + upgradeToSecuredConnection(); + } else if(doestStartsWith(url, "https://")) { + defaultPort = 443; + protocol = "https"; + url = url.substr(8); //strlen("https://") == 8 + + upgradeToSecuredConnection(); + } + #endif + + else { + return false; + // Not supported + } + + auto uriBeg = url.find_first_of('/'); + std::string host = url, uri = "/"; + + if(static_cast(uriBeg) != -1) { + uri = url.substr(uriBeg); + host = url.substr(0, uriBeg); + } + + auto portIdx = host.find_first_of(':'); + int port = defaultPort; + if(static_cast(portIdx) != -1) { + auto onlyHost = host.substr(0, portIdx); + ++portIdx; + port = 0; + while(portIdx < host.size() && host[portIdx] >= '0' && host[portIdx] <= '9') { + port = port * 10 + (host[portIdx] - '0'); + ++portIdx; + } + + host = onlyHost; + } + + return this->connect( + internals::fromInternalString(host), + port, + internals::fromInternalString(uri) + ); + } + + bool WebsocketsClient::connect(WSInterfaceString host, int port, WSInterfaceString path) { + this->_connectionOpen = this->_client->connect(internals::fromInterfaceString(host), port); + if (!this->_connectionOpen) return false; + + auto handshake = generateHandshake(internals::fromInterfaceString(host), internals::fromInterfaceString(path), _customHeaders); + this->_client->send(handshake.requestStr); + + // This check is needed because of an ESP32 lib bug that wont signal that the connection had + // failed in `->connect` (called above), sometimes the disconnect will only be noticed here (after a `send`) + if(!available()) { + return false; + } + + auto head = this->_client->readLine(); + if(!doestStartsWith(head, "HTTP/1.1 101")) { + close(CloseReason_ProtocolError); + return false; + } + + std::vector serverResponseHeaders; + WSString line = ""; + while (true) { + line = this->_client->readLine(); + + if (line.size() < 2) { + close(CloseReason_ProtocolError); + return false; + } + + if (line == "\r\n") break; + // remove /r/n from line end + line = line.substr(0, line.size() - 2); + + serverResponseHeaders.push_back(line); + } + auto parsedResponse = parseHandshakeResponse(serverResponseHeaders); + +#ifdef _WS_CONFIG_SKIP_HANDSHAKE_ACCEPT_VALIDATION + bool serverAcceptMismatch = false; +#else + bool serverAcceptMismatch = parsedResponse.serverAccept != handshake.expectedAcceptKey; +#endif + if(parsedResponse.isSuccess == false || serverAcceptMismatch) { + close(CloseReason_ProtocolError); + return false; + } + + this->_eventsCallback(*this, WebsocketsEvent::ConnectionOpened, {}); + return true; + } + + void WebsocketsClient::onMessage(MessageCallback callback) { + this->_messagesCallback = callback; + } + + void WebsocketsClient::onMessage(PartialMessageCallback callback) { + this->_messagesCallback = [callback](WebsocketsClient&, WebsocketsMessage msg) { + callback(msg); + }; + } + + void WebsocketsClient::onEvent(EventCallback callback) { + this->_eventsCallback = callback; + } + + void WebsocketsClient::onEvent(PartialEventCallback callback) { + this->_eventsCallback = [callback](WebsocketsClient&, WebsocketsEvent event, WSInterfaceString data) { + callback(event, data); + }; + } + + bool WebsocketsClient::poll() { + bool messageReceived = false; + while(available() && _endpoint.poll()) { + auto msg = _endpoint.recv(); + if(msg.isEmpty()) { + continue; + } + messageReceived = true; + + if(msg.isBinary() || msg.isText()) { + this->_messagesCallback(*this, std::move(msg)); + } else if(msg.isContinuation()) { + // continuation messages will only be returned when policy is appropriate + this->_messagesCallback(*this, std::move(msg)); + } else if(msg.isPing()) { + _handlePing(std::move(msg)); + } else if(msg.isPong()) { + _handlePong(std::move(msg)); + } else if(msg.isClose()) { + this->_connectionOpen = false; + _handleClose(std::move(msg)); + } + } + + return messageReceived; + } + + WebsocketsMessage WebsocketsClient::readBlocking() { + while(available()) { +#ifdef PLATFORM_DOES_NOT_SUPPORT_BLOCKING_READ + while(available() && _endpoint.poll() == false) continue; +#endif + auto msg = _endpoint.recv(); + if(!msg.isEmpty()) return msg; + } + return {}; + } + + bool WebsocketsClient::send(const WSInterfaceString& data) { + auto str = internals::fromInterfaceString(data); + return this->send(str.c_str(), str.size()); + } + + bool WebsocketsClient::send(const WSInterfaceString&& data) { + auto str = internals::fromInterfaceString(data); + return this->send(str.c_str(), str.size()); + } + + bool WebsocketsClient::send(const char* data) { + return this->send(data, strlen(data)); + } + + bool WebsocketsClient::send(const char* data, const size_t len) { + if(available()) { + // if in normal mode + if(this->_sendMode == SendMode_Normal) { + // send a normal message + return _endpoint.send( + data, + len, + internals::ContentType::Text, + true + ); + } + // if in streaming mode + else if(this->_sendMode == SendMode_Streaming) { + // send a continue frame + return _endpoint.send( + data, + len, + internals::ContentType::Continuation, + false + ); + } + } + return false; + } + + bool WebsocketsClient::sendBinary(WSInterfaceString data) { + auto str = internals::fromInterfaceString(data); + return this->sendBinary(str.c_str(), str.size()); + } + + bool WebsocketsClient::sendBinary(const char* data, const size_t len) { + if(available()) { + // if in normal mode + if(this->_sendMode == SendMode_Normal) { + // send a normal message + return _endpoint.send( + data, + len, + internals::ContentType::Binary, + true + ); + } + // if in streaming mode + else if(this->_sendMode == SendMode_Streaming) { + // send a continue frame + return _endpoint.send( + data, + len, + internals::ContentType::Continuation, + false + ); + } + } + return false; + } + + bool WebsocketsClient::stream(const WSInterfaceString data) { + if(available() && this->_sendMode == SendMode_Normal) { + this->_sendMode = SendMode_Streaming; + return _endpoint.send( + internals::fromInterfaceString(data), + internals::ContentType::Text, + false + ); + } + return false; + } + + + bool WebsocketsClient::streamBinary(const WSInterfaceString data) { + if(available() && this->_sendMode == SendMode_Normal) { + this->_sendMode = SendMode_Streaming; + return _endpoint.send( + internals::fromInterfaceString(data), + internals::ContentType::Binary, + false + ); + } + return false; + } + + bool WebsocketsClient::end(const WSInterfaceString data) { + if(available() && this->_sendMode == SendMode_Streaming) { + this->_sendMode = SendMode_Normal; + return _endpoint.send( + internals::fromInterfaceString(data), + internals::ContentType::Continuation, + true + ); + } + return false; + } + + void WebsocketsClient::setFragmentsPolicy(const FragmentsPolicy newPolicy) { + _endpoint.setFragmentsPolicy(newPolicy); + } + + bool WebsocketsClient::available(const bool activeTest) { + if(activeTest) { + _endpoint.ping(""); + } + + bool updatedConnectionOpen = this->_connectionOpen && this->_client && this->_client->available(); + + if(updatedConnectionOpen != this->_connectionOpen) { + _endpoint.close(CloseReason_AbnormalClosure); + this->_eventsCallback(*this, WebsocketsEvent::ConnectionClosed, ""); + } + + this->_connectionOpen = updatedConnectionOpen; + return this->_connectionOpen; + } + + bool WebsocketsClient::ping(const WSInterfaceString data) { + if(available()) { + return _endpoint.ping(internals::fromInterfaceString(data)); + } + return false; + } + + bool WebsocketsClient::pong(const WSInterfaceString data) { + if(available()) { + return _endpoint.pong(internals::fromInterfaceString(data)); + } + return false; + } + + void WebsocketsClient::close(const CloseReason reason) { + if(available()) { + this->_connectionOpen = false; + _endpoint.close(reason); + _handleClose({}); + } + } + + CloseReason WebsocketsClient::getCloseReason() const { + return _endpoint.getCloseReason(); + } + + void WebsocketsClient::_handlePing(const WebsocketsMessage message) { + this->_eventsCallback(*this, WebsocketsEvent::GotPing, message.data()); + } + + void WebsocketsClient::_handlePong(const WebsocketsMessage message) { + this->_eventsCallback(*this, WebsocketsEvent::GotPong, message.data()); + } + + void WebsocketsClient::_handleClose(const WebsocketsMessage message) { + this->_eventsCallback(*this, WebsocketsEvent::ConnectionClosed, message.data()); + } + + +#ifdef ESP8266 + void WebsocketsClient::setFingerprint(const char* fingerprint) { + this->_optional_ssl_fingerprint = fingerprint; + } + + void WebsocketsClient::setInsecure() { + this->_optional_ssl_fingerprint = nullptr; + this->_optional_ssl_cert = nullptr; + this->_optional_ssl_private_key = nullptr; + this->_optional_ssl_trust_anchors = nullptr; + } + + void WebsocketsClient::setClientRSACert(const X509List *cert, const PrivateKey *sk) { + this->_optional_ssl_cert = cert; + this->_optional_ssl_private_key = sk; + } + + void WebsocketsClient::setTrustAnchors(const X509List *ta){ + this->_optional_ssl_trust_anchors = ta; + } + +#elif defined(ESP32) + void WebsocketsClient::setCACert(const char* ca_cert) { + this->_optional_ssl_ca_cert = ca_cert; + } + + void WebsocketsClient::setCertificate(const char* client_ca) { + this->_optional_ssl_client_ca = client_ca; + } + + void WebsocketsClient::setPrivateKey(const char* private_key) { + this->_optional_ssl_private_key = private_key; + } + + void WebsocketsClient::setInsecure() { + this->_optional_ssl_ca_cert = nullptr; + this->_optional_ssl_client_ca = nullptr; + this->_optional_ssl_private_key = nullptr; + } +#endif + + WebsocketsClient::~WebsocketsClient() { + if(available()) { + this->close(CloseReason_GoingAway); + } + } +} diff --git a/libraries/ArduinoWebsockets/src/websockets_endpoint.cpp b/libraries/ArduinoWebsockets/src/websockets_endpoint.cpp new file mode 100644 index 00000000000..61b2049f5ee --- /dev/null +++ b/libraries/ArduinoWebsockets/src/websockets_endpoint.cpp @@ -0,0 +1,468 @@ +#include + +namespace websockets { + + CloseReason GetCloseReason(uint16_t reasonCode) { + switch(reasonCode) { + case CloseReason_NormalClosure: + return CloseReason_NormalClosure; + + case CloseReason_GoingAway: + return CloseReason_GoingAway; + + case CloseReason_ProtocolError: + return CloseReason_ProtocolError; + + case CloseReason_UnsupportedData: + return CloseReason_UnsupportedData; + + case CloseReason_AbnormalClosure: + return CloseReason_AbnormalClosure; + + case CloseReason_InvalidPayloadData: + return CloseReason_InvalidPayloadData; + + case CloseReason_PolicyViolation: + return CloseReason_PolicyViolation; + + case CloseReason_MessageTooBig: + return CloseReason_MessageTooBig; + + case CloseReason_NoStatusRcvd: + return CloseReason_NoStatusRcvd; + + case CloseReason_InternalServerError: + return CloseReason_InternalServerError; + + default: return CloseReason_None; + } + } + +namespace internals { + + uint32_t swapEndianess(uint32_t num) { + uint32_t highest = (num >> 24); + uint32_t second = (num << 8) >> 24; + uint32_t third = (num << 16) >> 24; + uint32_t lowest = (num << 24) >> 24; + + return highest | (second << 8) | (third << 16) | (lowest << 24); + } + + uint64_t swapEndianess(uint64_t num) { + uint32_t upper = (num >> 32); + uint32_t lower = (num << 32) >> 32; + + upper = swapEndianess(upper); + lower = swapEndianess(lower); + + uint64_t upperLong = upper; + uint64_t lowerLong = lower; + + return upperLong | (lowerLong << 32); + } + + WebsocketsEndpoint::WebsocketsEndpoint(std::shared_ptr client, FragmentsPolicy fragmentsPolicy) : + _client(client), + _fragmentsPolicy(fragmentsPolicy), + _recvMode(RecvMode_Normal), + _streamBuilder(fragmentsPolicy == FragmentsPolicy_Notify? true: false), + _closeReason(CloseReason_None) { + // Empty + } + + WebsocketsEndpoint::WebsocketsEndpoint(const WebsocketsEndpoint& other) : + _client(other._client), + _fragmentsPolicy(other._fragmentsPolicy), + _recvMode(other._recvMode), + _streamBuilder(other._streamBuilder), + _closeReason(other._closeReason), + _useMasking(other._useMasking) { + + const_cast(other)._client = nullptr; + } + WebsocketsEndpoint::WebsocketsEndpoint(const WebsocketsEndpoint&& other) : + _client(other._client), + _fragmentsPolicy(other._fragmentsPolicy), + _recvMode(other._recvMode), + _streamBuilder(other._streamBuilder), + _closeReason(other._closeReason), + _useMasking(other._useMasking) { + + const_cast(other)._client = nullptr; + } + + WebsocketsEndpoint& WebsocketsEndpoint::operator=(const WebsocketsEndpoint& other) { + this->_client = other._client; + this->_fragmentsPolicy = other._fragmentsPolicy; + this->_recvMode = other._recvMode; + this->_streamBuilder = other._streamBuilder; + this->_closeReason = other._closeReason; + this->_useMasking = other._useMasking; + + const_cast(other)._client = nullptr; + + return *this; + } + WebsocketsEndpoint& WebsocketsEndpoint::operator=(const WebsocketsEndpoint&& other) { + this->_client = other._client; + this->_fragmentsPolicy = other._fragmentsPolicy; + this->_recvMode = other._recvMode; + this->_streamBuilder = other._streamBuilder; + this->_closeReason = other._closeReason; + this->_useMasking = other._useMasking; + + const_cast(other)._client = nullptr; + + return *this; + } + + void WebsocketsEndpoint::setInternalSocket(std::shared_ptr socket) { + this->_client = socket; + } + + bool WebsocketsEndpoint::poll() { + return this->_client->poll(); + } + + uint32_t readUntilSuccessfullOrError(network::TcpClient& socket, uint8_t* buffer, const uint32_t len) { + auto numRead = socket.read(buffer, len); + while(numRead == static_cast(-1) && socket.available()) { + numRead = socket.read(buffer, len); + } + return numRead; + } + + Header readHeaderFromSocket(network::TcpClient& socket) { + Header header; + header.payload = 0; + readUntilSuccessfullOrError(socket, reinterpret_cast(&header), 2); + return header; + } + + uint64_t readExtendedPayloadLength(network::TcpClient& socket, const Header& header) { + uint64_t extendedPayload = header.payload; + // in case of extended payload length + if (header.payload == 126) { + // read next 16 bits as payload length + uint16_t tmp = 0; + readUntilSuccessfullOrError(socket, reinterpret_cast(&tmp), 2); + tmp = (tmp << 8) | (tmp >> 8); + extendedPayload = tmp; + } + else if (header.payload == 127) { + uint64_t tmp = 0; + readUntilSuccessfullOrError(socket, reinterpret_cast(&tmp), 8); + extendedPayload = swapEndianess(tmp); + } + + return extendedPayload; + } + + void readMaskingKey(network::TcpClient& socket, uint8_t* outputBuffer) { + readUntilSuccessfullOrError(socket, reinterpret_cast(outputBuffer), 4); + } + + WSString readData(network::TcpClient& socket, uint64_t extendedPayload) { + const uint64_t BUFFER_SIZE = _WS_BUFFER_SIZE; + + WSString data(extendedPayload, '\0'); + uint8_t buffer[BUFFER_SIZE]; + uint64_t done_reading = 0; + while (done_reading < extendedPayload && socket.available()) { + uint64_t to_read = extendedPayload - done_reading >= BUFFER_SIZE ? BUFFER_SIZE : extendedPayload - done_reading; + uint32_t numReceived = readUntilSuccessfullOrError(socket, buffer, to_read); + + // On failed reads, skip + if(!socket.available()) break; + + for (uint64_t i = 0; i < numReceived; i++) { + data[done_reading + i] = static_cast(buffer[i]); + } + + done_reading += numReceived; + } + return std::move(data); + } + + void remaskData(WSString& data, const uint8_t* const maskingKey, uint64_t payloadLength) { + for (uint64_t i = 0; i < payloadLength; i++) { + data[i] = data[i] ^ maskingKey[i % 4]; + } + } + + WebsocketsFrame WebsocketsEndpoint::_recv() { + auto header = readHeaderFromSocket(*this->_client); + if(!_client->available()) return WebsocketsFrame(); // In case of faliure + + uint64_t payloadLength = readExtendedPayloadLength(*this->_client, header); + if(!_client->available()) return WebsocketsFrame(); // In case of faliure + +#ifdef _WS_CONFIG_MAX_MESSAGE_SIZE + if(payloadLength > _WS_CONFIG_MAX_MESSAGE_SIZE) { + return WebsocketsFrame(); + } +#endif + + uint8_t maskingKey[4]; + // if masking is set + if (header.mask) { + readMaskingKey(*this->_client, maskingKey); + if(!_client->available()) return WebsocketsFrame(); // In case of faliure + } + + WebsocketsFrame frame; + // read the message's payload (data) according to the read length + frame.payload = readData(*this->_client, payloadLength); + if(!_client->available()) return WebsocketsFrame(); // In case of faliure + + // if masking is set un-mask the message + if (header.mask) { + remaskData(frame.payload, maskingKey, payloadLength); + } + + // Construct frame from data and header that was read + frame.fin = header.fin; + frame.mask = header.mask; + + frame.mask_buf[0] = maskingKey[0]; + frame.mask_buf[1] = maskingKey[1]; + frame.mask_buf[2] = maskingKey[2]; + frame.mask_buf[3] = maskingKey[3]; + + frame.opcode = header.opcode; + frame.payload_length = payloadLength; + + return std::move(frame); + } + + WebsocketsMessage WebsocketsEndpoint::handleFrameInStreamingMode(WebsocketsFrame& frame) { + if(frame.isControlFrame()) { + auto msg = WebsocketsMessage::CreateFromFrame(std::move(frame)); + this->handleMessageInternally(msg); + return std::move(msg); + } + else if(frame.isBeginningOfFragmentsStream()) { + this->_recvMode = RecvMode_Streaming; + + if(this->_streamBuilder.isEmpty()) { + this->_streamBuilder.first(frame); + // if policy is set to notify, return the frame to the user + if(this->_fragmentsPolicy == FragmentsPolicy_Notify) { + return WebsocketsMessage(this->_streamBuilder.type(), std::move(frame.payload), MessageRole::First); + } + else return {}; + } + } + else if(frame.isContinuesFragment()) { + this->_streamBuilder.append(frame); + if(this->_streamBuilder.isOk()) { + // if policy is set to notify, return the frame to the user + if(this->_fragmentsPolicy == FragmentsPolicy_Notify) { + return WebsocketsMessage(this->_streamBuilder.type(), std::move(frame.payload), MessageRole::Continuation); + } + else return {}; + } + } + else if(frame.isEndOfFragmentsStream()) { + this->_recvMode = RecvMode_Normal; + this->_streamBuilder.end(frame); + if(this->_streamBuilder.isOk()) { + // if policy is set to notify, return the frame to the user + if(this->_fragmentsPolicy == FragmentsPolicy_Aggregate) { + auto completeMessage = this->_streamBuilder.build(); + this->_streamBuilder = WebsocketsMessage::StreamBuilder(false); + this->handleMessageInternally(completeMessage); + return completeMessage; + } + else { // in case of notify policy + auto messageType = this->_streamBuilder.type(); + this->_streamBuilder = WebsocketsMessage::StreamBuilder(true); + return WebsocketsMessage(messageType, std::move(frame.payload), MessageRole::Last); + } + } + } + + // Error + close(CloseReason_ProtocolError); + return {}; + } + + WebsocketsMessage WebsocketsEndpoint::handleFrameInStandardMode(WebsocketsFrame& frame) { + // Normal (unfragmented) frames are handled as a complete message + if(frame.isNormalUnfragmentedMessage() || frame.isControlFrame()) { + auto msg = WebsocketsMessage::CreateFromFrame(std::move(frame)); + this->handleMessageInternally(msg); + return std::move(msg); + } + else if(frame.isBeginningOfFragmentsStream()) { + return handleFrameInStreamingMode(frame); + } + + // This is an error. a bad combination of opcodes and fin flag arrived. + close(CloseReason_ProtocolError); + return {}; + } + + WebsocketsMessage WebsocketsEndpoint::recv() { + auto frame = _recv(); + if (frame.isEmpty()) { + return {}; + } + + if(this->_recvMode == RecvMode_Normal) { + return handleFrameInStandardMode(frame); + } + else /* this->_recvMode == RecvMode_Streaming */ { + return handleFrameInStreamingMode(frame); + } + } + + void WebsocketsEndpoint::handleMessageInternally(WebsocketsMessage& msg) { + if(msg.isPing()) { + pong(internals::fromInterfaceString(msg.data())); + } else if(msg.isClose()) { + // is there a reason field + if(internals::fromInterfaceString(msg.data()).size() >= 2) { + uint16_t reason = *(reinterpret_cast(msg.data().c_str())); + reason = reason >> 8 | reason << 8; + this->_closeReason = GetCloseReason(reason); + } else { + this->_closeReason = CloseReason_GoingAway; + } + close(this->_closeReason); + } + } + + bool WebsocketsEndpoint::send(const char* data, const size_t len, const uint8_t opcode, const bool fin) { + return this->send(data, len, opcode, fin, this->_useMasking); + } + + bool WebsocketsEndpoint::send(const WSString& data, const uint8_t opcode, const bool fin) { + return this->send(data, opcode, fin, this->_useMasking); + } + + bool WebsocketsEndpoint::send(const WSString& data, const uint8_t opcode, const bool fin, const bool mask, const char* maskingKey) { + return send(data.c_str(), data.size(), opcode, fin, mask, maskingKey); + } + + std::string WebsocketsEndpoint::getHeader(uint64_t len, uint8_t opcode, bool fin, bool mask) { + std::string header_data; + + if(len < 126) { + auto header = MakeHeader
(len, opcode, fin, mask); + header_data = std::string(reinterpret_cast(&header), 2 + 0); + } else if(len < 65536) { + auto header = MakeHeader(len, opcode, fin, mask); + header.extendedPayload = (len << 8) | (len >> 8); + header_data = std::string(reinterpret_cast(&header), 2 + 2); + } else { + auto header = MakeHeader(len, opcode, fin, mask); + // header.extendedPayload = swapEndianess(len); + header.extendedPayload = swapEndianess(len); + + header_data = std::string(reinterpret_cast(&header), 2); + header_data += std::string(reinterpret_cast(&header.extendedPayload), 8); + } + + return header_data; + } + + void remaskData(WSString& data, const char* const maskingKey, size_t first, size_t len) { + for (size_t i = first; i < first + len; i++) { + data[i] = data[i] ^ maskingKey[i % 4]; + } + } + + bool WebsocketsEndpoint::send(const char* data, const size_t len, const uint8_t opcode, const bool fin, const bool mask, const char* maskingKey) { + +#ifdef _WS_CONFIG_MAX_MESSAGE_SIZE + if(len > _WS_CONFIG_MAX_MESSAGE_SIZE) { + return false; + } +#endif + // send the header + std::string message_data = getHeader(len, opcode, fin, mask); + + if (mask) { + message_data += std::string(maskingKey, 4); + } + + size_t data_start = message_data.size(); + message_data += std::string(data, len); + + if (mask && memcmp(maskingKey, __TINY_WS_INTERNAL_DEFAULT_MASK, 4) != 0) { + remaskData(message_data, maskingKey, data_start, len); + } + + this->_client->send(reinterpret_cast(message_data.c_str()), message_data.size()); + return true; // TODO dont assume success + } + + void WebsocketsEndpoint::close(CloseReason reason) { + this->_closeReason = reason; + + if(!this->_client->available()) return; + + if(reason == CloseReason_None) { + send(nullptr, 0, internals::ContentType::Close, true, this->_useMasking); + } else { + uint16_t reasonNum = static_cast(reason); + reasonNum = (reasonNum >> 8) | (reasonNum << 8); + send(reinterpret_cast(&reasonNum), 2, internals::ContentType::Close, true, this->_useMasking); + } + this->_client->close(); + } + + CloseReason WebsocketsEndpoint::getCloseReason() const { + return _closeReason; + } + + bool WebsocketsEndpoint::ping(const WSString& msg) { + // Ping data must be shorter than 125 bytes + if(msg.size() > 125) { + return false; + } + else { + return send(msg, ContentType::Ping, true, this->_useMasking); + } + } + bool WebsocketsEndpoint::ping(const WSString&& msg) { + // Ping data must be shorter than 125 bytes + if(msg.size() > 125) { + return false; + } + else { + return send(msg, ContentType::Ping, true, this->_useMasking); + } + } + + bool WebsocketsEndpoint::pong(const WSString& msg) { + // Pong data must be shorter than 125 bytes + if(msg.size() > 125) { + return false; + } + else { + return this->send(msg, ContentType::Pong, true, this->_useMasking); + } + } + bool WebsocketsEndpoint::pong(const WSString&& msg) { + // Pong data must be shorter than 125 bytes + if(msg.size() > 125) { + return false; + } + else { + return this->send(msg, ContentType::Pong, true, this->_useMasking); + } + } + + void WebsocketsEndpoint::setFragmentsPolicy(FragmentsPolicy newPolicy) { + this->_fragmentsPolicy = newPolicy; + } + + FragmentsPolicy WebsocketsEndpoint::getFragmentsPolicy() const { + return this->_fragmentsPolicy; + } + + WebsocketsEndpoint::~WebsocketsEndpoint() {} +}} // websockets::internals diff --git a/libraries/ArduinoWebsockets/src/websockets_server.cpp b/libraries/ArduinoWebsockets/src/websockets_server.cpp new file mode 100644 index 00000000000..eb80d907aac --- /dev/null +++ b/libraries/ArduinoWebsockets/src/websockets_server.cpp @@ -0,0 +1,93 @@ +#include +#include +#include +#include + +namespace websockets { + WebsocketsServer::WebsocketsServer(network::TcpServer* server) : _server(server) {} + + bool WebsocketsServer::available() { + return this->_server->available(); + } + + void WebsocketsServer::listen(uint16_t port) { + this->_server->listen(port); + } + + bool WebsocketsServer::poll() { + return this->_server->poll(); + } + + struct ParsedHandshakeParams { + WSString head; + std::map headers; + }; + + ParsedHandshakeParams recvHandshakeRequest(network::TcpClient& client) { + ParsedHandshakeParams result; + + result.head = client.readLine(); + + WSString line = client.readLine(); + do { + WSString key = "", value = ""; + size_t idx = 0; + + // read key + while(idx < line.size() && line[idx] != ':') { + key += line[idx]; + idx++; + } + + // skip key and whitespace + idx++; + while(idx < line.size() && (line[idx] == ' ' || line[idx] == '\t')) idx++; + + // read value (until \r\n) + while(idx < line.size() && line[idx] != '\r') { + value += line[idx]; + idx++; + } + + // store header + result.headers[key] = value; + + line = client.readLine(); + } while(client.available() && line != "\r\n"); + + return result; + } + + WebsocketsClient WebsocketsServer::accept() { + std::shared_ptr tcpClient(_server->accept()); + if(tcpClient->available() == false) return {}; + + auto params = recvHandshakeRequest(*tcpClient); + + if(params.headers["Connection"].find("Upgrade") == std::string::npos) return {}; + if(params.headers["Upgrade"] != "websocket") return {}; + if(params.headers["Sec-WebSocket-Version"] != "13") return {}; + if(params.headers["Sec-WebSocket-Key"] == "") return {}; + + auto serverAccept = crypto::websocketsHandshakeEncodeKey( + params.headers["Sec-WebSocket-Key"] + ); + + tcpClient->send("HTTP/1.1 101 Switching Protocols\r\n"); + tcpClient->send("Connection: Upgrade\r\n"); + tcpClient->send("Upgrade: websocket\r\n"); + tcpClient->send("Sec-WebSocket-Version: 13\r\n"); + tcpClient->send("Sec-WebSocket-Accept: " + serverAccept + "\r\n"); + tcpClient->send("\r\n"); + + WebsocketsClient wsClient(tcpClient); + // Don't use masking from server to client (according to RFC) + wsClient.setUseMasking(false); + return wsClient; + } + + WebsocketsServer::~WebsocketsServer() { + this->_server->close(); + } + +} //websockets \ No newline at end of file diff --git a/libraries/ArduinoWebsockets/src/ws_common.cpp b/libraries/ArduinoWebsockets/src/ws_common.cpp new file mode 100644 index 00000000000..2d68c1e39dc --- /dev/null +++ b/libraries/ArduinoWebsockets/src/ws_common.cpp @@ -0,0 +1,17 @@ +#include + +namespace websockets { namespace internals { + WSString fromInterfaceString(const WSInterfaceString& str) { + return str.c_str(); + } + WSString fromInterfaceString(const WSInterfaceString&& str) { + return str.c_str(); + } + + WSInterfaceString fromInternalString(const WSString& str) { + return str.c_str(); + } + WSInterfaceString fromInternalString(const WSString&& str) { + return str.c_str(); + } +}} \ No newline at end of file diff --git a/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/HTML interface.html b/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/HTML interface.html new file mode 100644 index 00000000000..debe5bf1879 --- /dev/null +++ b/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/HTML interface.html @@ -0,0 +1,247 @@ + + + + + + +Face Recognition Access Control + + + +
+
+
+
+
+
+ +
+
+ + +
+
+ + + +
+
+

Captured Faces

+
    +
+
+
+ +
+
+ + + \ No newline at end of file diff --git a/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/PersonErkennung_esp32_cam.ino b/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/PersonErkennung_esp32_cam.ino new file mode 100644 index 00000000000..52baf9ceaf5 --- /dev/null +++ b/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/PersonErkennung_esp32_cam.ino @@ -0,0 +1,1033 @@ +/* +DEUTSCH!!! + +!!!!Stimmen hier Einstellungen nicht, funktioniert die Cam nicht!!!! + +Boards URL = https://raw.githubusercontent.com/elzershark/arduino-esp32/master/package_esp32_index.json +Board = ESP32 Wrover Module +Uploadspeed = 921600 +80Mhz +QIO +Partitions Scheme = Face Recocnitions 2621440 bytes +Debug = Verbose +!!!!Stimmen hier Einstellungen nicht, funktioniert die Cam nicht!!!! + + +Webserver erstellen unter Linux(wo ioBroker ist): + +Mit Putty verbinden. +--------------- +sudo apt-get update +sudo apt-get install apache2 +sudo apt-get install php +sudo mkdir /var/www/html/upload +sudo nano /var/www/html/upload.php + +Das Folgende kopieren und mit rechtem Mausklick einfügen: + + + +Mit STRG + O u. Enter und mit STRG + X u. Enter verlassen + +Weiter mit: +sudo chmod 755 /var/www/html/upload.php +sudo chown -R www-data /var/www/html/upload.php +sudo chown -R www-data /var/www/html/upload +--------------- + +Fertig!! + +Erklärung: + +Erstellen der php Datei. Name = upload.php +php Datei ist direkt auf dem Webserver. Der Webserver hat ein Unterordner, Namens upload. +Die Rechte nicht vergessen, damit in dem Ordner was gespeichert werden kann. + +Beispiel vom Inhalt der php Datei: + + + +Kamera Funktionen: +1. Kamera mit Strom versorgen. +2. LED leuchtet. +3. Cam verbindet sich mit W-Lan und MQTT +4. Cam geht in den offline/inaktive Modus. (Gesichtserkennung ist aktiviert). +5. LED geht aus. +6. Wenn die LED anbleibt, stimmt was nicht. Im Serieller Monitor von Arduino überprüfen. +GPIO2 = Relais anschließen. +GPIO12 = Taster Anschließen mit GND (-). Bei Tastendruck wird ein Foto erstellt. (Klingelfuntion) +Bei Namenserkennung wird ein Foto erstellt. +Fotos können leicht automatisch im Telegramm angezeigt werden. + +MQTT Funktion: +Datenpunkte: +Erkannt = Ein Gesicht wurde erkannt. "Unbekannt" wird geschrieben/aktualisiert. +Foto = Ein Foto wird erstellt bei eingabe von "true" "1" "on" +IP = Die IP von der Cam. +Name = Ein Gesicht mit Name wurde erkannt. Name wird angezeigt. +Relay = Der Zustand des Relais.(GPIO12) +Ring = Bei "true" "1" "on" wurde geklingelt(GPIO12 -> GND) und/oder ein Foto wird erstellt bei eingabe von "true" "1" "on". +WiFi = Stärke des Signales. Je kleiner die Zahl um so besser "-5 ist besser als -50" (~-50bis-40) +Door_Opener = Türöffner betätigen. GPIO2 mit Türöffner verbinden. +info = Welcher ESP32 Cam online ist. + +Foto Funktion: +Webserver wird benötigt. +php wird benötigt. +Kamera löst ein php Script auf dem Webserver aus. Das Script speichert ein Bild in einem Unterorder. + +######################################### +######################################### +######################################### + +ENGLISH +!!!If the settings are not correct here, the cam will not work !!!! +Boards URL = https://raw.githubusercontent.com/elzershark/arduino-esp32/master/package_esp32_index.json + +Board = ESP32 Wrover Module +Uploadspeed = 921600 +80Mhz +QIO +Partitions Scheme = Face Recocnitions 2621440 bytes +Debug = Verbose + +Connect with putty. +--------------- +sudo apt-get update +sudo apt-get install apache2 +sudo apt-get install php +sudo mkdir /var/www/html/upload +sudo nano /var/www/html/upload.php + +Copy and paste it with a right mouse click. +Save with CTRL + O and exit with CTRL + X + + + +Continue with: +sudo chmod 755 /var/www/html/upload.php +sudo chown -R www-data /var/www/html/upload.php +sudo chown -R www-data /var/www/html/upload +--------------- + +Finished!! + +Explanation: + +Create php file. Name = upload.php +php file is directly on the web server. The web server has a subfolder called upload. +Don't forget the rights so that something can be saved in the folder + +Example of the content of the php file: + + + +Camera functions: +1. Supply the camera with power. +2. LED lights up. +3. Cam connects to WiFi and MQTT +4. Cam goes into offline / inactive mode. (Face recognition is activated). +5. LED goes out. +6. If the LED stays on, something is wrong. Check in the Arduino serial monitor. +GPIO2 = connect relay. +GPIO12 = push button connection with GND (-). A photo is created when a button is pressed. (Bell function) +A photo is created when the name is recognized. +Photos can easily be automatically displayed in Telegram. + +MQTT function: +Data points: +Erkannt = A face was recognized. "Unbekannt" is written / updated. +Foto = A photo will be created by entering "true" "1" "on" +IP = the IP of the cam. +Name = A face with a name was recognized. Name is displayed. +Relay = The state of the relay. (GPIO12) +Ring = "true" "1" "on" rang the bell (GPIO12 -> GND) and / or a photo is created when entering "true" "1" "on". +WiFi = strength of the signal. The smaller the number, the better "-5 is better than -50" (~ -50 to -40) +Door_Opener = press the door opener. Connect GPIO2 with door opener. +info = Which ESP32 Cam is online. + +Photo function: +Web server is required. +php is required. +The camera triggers a php script on the web server. The script saves an image in a subfolder. +*/ + +#include +#include "esp_http_client.h" +#include "esp_http_server.h" +#include "esp_timer.h" +#include "esp_camera.h" +#include "camera_index.h" +#include "Arduino.h" +#include "fd_forward.h" +#include "fr_forward.h" +#include "fr_flash.h" +#include +unsigned long check_wifi = 30000; +// String chipid; // MAC-ID from SSP32 Cam +String wifist; +String inTopic; +String wifiip; +String msgTopic; +String ipconnect; +String nerkannt; +String foto; +String opener; +String relaise; +// +// +// !!!ALIGN DATA / DATEN ANGLEICHEN !!! +const char* ssid = "WlanName"; // your network SSID (name) / Wlan- Name +const char* password = "WlanPassword"; // your network password / Wlan Passwort +const char* mqttServer = "ip_from_mqtt_server"; // your MQTT Server / MQTT Server IP +const int mqttPort = 1883; // your MQTT Port / MQTT PORT (z.B.1883) +const char* mqttUser = "username"; // your MQTT Username / MQTT Benutzername +const char* mqttPassword = "userpassword"; // your MQTT password / MQTT Passwort +String espName = "ESP_NAME"; // How the ESP should be called e.g. ESP_front_door / Wie der ESP heissen soll z.B. ESP_Haustür +const char *post_url = "http://IPvomWebserver/upload.php"; // where the php file is to download the image / Wo die php Datei ist zum runterladen des Bildes +#define relay_pin 2 // GPIO2 connect a relay there. - GPIO2 dort ein Relais anklemmen. +#define relay_pin1 12 // GPIO12 connect with GND for Picture. - GPIO12 mit GND erstellt Foto. +long interval = 5000; // open lock for ... milliseconds /öffnet das Relais für ... Millisekunden +String noname = "Unbekannt"; // In the data point "Gesicht erkannt", "Unbekannt" is written on recognition / Im Datenpunkt "Gesicht erkannt" wird "Unbekannt" geschrieben bei Erkennung +// The paths can be changed under "void setup()". / Unter "void setup()" können die Pfade geändert werden. +// +// + +String rssii; +unsigned long door_opened_millis = 0; +unsigned long led_millis = 0; +HTTPClient http; + +WiFiClient espClient; +void callback(char * topic, byte * payload, unsigned int length); +PubSubClient mqttClient(mqttServer, mqttPort, callback, espClient); + +#define ENROLL_CONFIRM_TIMES 5 +#define FACE_ID_SAVE_NUMBER 50 +int freq = 5000; +int ledCHannel = 1; +int res = 8; +const int ledPin = 4; +int brightness = 0; +// Select camera model +//#define CAMERA_MODEL_WROVER_KIT +//#define CAMERA_MODEL_ESP_EYE +//#define CAMERA_MODEL_M5STACK_PSRAM +//#define CAMERA_MODEL_M5STACK_WIDE +#define CAMERA_MODEL_AI_THINKER +#include "camera_pins.h" + +using namespace websockets; +WebsocketsServer socket_server; + +camera_fb_t * fb = NULL; +String gesicht; +long current_millis; +long last_detected_millis = 0; +bool websockets_active = false; +const char* get_url = "http://postman-echo.com/get?foo="; // Location to send data + +bool face_recognised = false; + +void app_facenet_main(); +void app_httpserver_init(); + +typedef struct +{ + uint8_t *image; + box_array_t *net_boxes; + dl_matrix3d_t *face_id; +} http_img_process_result; + +static inline mtmn_config_t app_mtmn_config() +{ + mtmn_config_t mtmn_config = {0}; + mtmn_config.type = FAST; + mtmn_config.min_face = 80; + mtmn_config.pyramid = 0.707; + mtmn_config.pyramid_times = 4; + mtmn_config.p_threshold.score = 0.6; + mtmn_config.p_threshold.nms = 0.7; + mtmn_config.p_threshold.candidate_number = 20; + mtmn_config.r_threshold.score = 0.7; + mtmn_config.r_threshold.nms = 0.7; + mtmn_config.r_threshold.candidate_number = 10; + mtmn_config.o_threshold.score = 0.7; + mtmn_config.o_threshold.nms = 0.7; + mtmn_config.o_threshold.candidate_number = 1; + return mtmn_config; +} +mtmn_config_t mtmn_config = app_mtmn_config(); + +face_id_name_list st_face_list; +static dl_matrix3du_t *aligned_face = NULL; + +httpd_handle_t camera_httpd = NULL; + +typedef enum +{ + START_STREAM, + START_DETECT, + SHOW_FACES, + START_RECOGNITION, + START_ENROLL, + ENROLL_COMPLETE, + DELETE_ALL, +} en_fsm_state; +en_fsm_state g_state; + +typedef struct +{ + char enroll_name[ENROLL_NAME_LEN]; +} httpd_resp_value; + +httpd_resp_value st_name; + +void setup() { + + // MQTT Pfade: + nerkannt = espName; + nerkannt += "/Erkannt"; + msgTopic = espName; + msgTopic += "/Name"; + wifist = espName; + wifist += "/WiFi"; + wifiip = espName; + wifiip += "/IP"; + inTopic = espName; + inTopic += "/Ring"; + nerkannt = espName; + nerkannt += "/Erkannt"; + foto = espName; + foto += "/Foto"; + relaise = espName; + relaise += "/Relay"; + opener = espName; + opener += "/Door_Opener"; + // + + ledcSetup(ledCHannel, freq, res); + ledcAttachPin(ledPin, ledCHannel); + brightness = 2; + ledcWrite(ledCHannel, brightness); + + Serial.begin(115200); + Serial.setDebugOutput(true); + Serial.println(); + + digitalWrite(relay_pin, LOW); + digitalWrite(relay_pin1, HIGH); + pinMode(relay_pin, OUTPUT); + pinMode(relay_pin1, INPUT_PULLUP); + camera_config_t config; + config.ledc_channel = LEDC_CHANNEL_0; + config.ledc_timer = LEDC_TIMER_0; + config.pin_d0 = Y2_GPIO_NUM; + config.pin_d1 = Y3_GPIO_NUM; + config.pin_d2 = Y4_GPIO_NUM; + config.pin_d3 = Y5_GPIO_NUM; + config.pin_d4 = Y6_GPIO_NUM; + config.pin_d5 = Y7_GPIO_NUM; + config.pin_d6 = Y8_GPIO_NUM; + config.pin_d7 = Y9_GPIO_NUM; + config.pin_xclk = XCLK_GPIO_NUM; + config.pin_pclk = PCLK_GPIO_NUM; + config.pin_vsync = VSYNC_GPIO_NUM; + config.pin_href = HREF_GPIO_NUM; + config.pin_sscb_sda = SIOD_GPIO_NUM; + config.pin_sscb_scl = SIOC_GPIO_NUM; + config.pin_pwdn = PWDN_GPIO_NUM; + config.pin_reset = RESET_GPIO_NUM; + config.xclk_freq_hz = 20000000; + config.pixel_format = PIXFORMAT_JPEG; + //init with high specs to pre-allocate larger buffers + if (psramFound()) { + config.frame_size = FRAMESIZE_UXGA; + config.jpeg_quality = 10; + config.fb_count = 2; + } else { + config.frame_size = FRAMESIZE_SVGA; + config.jpeg_quality = 12; + config.fb_count = 1; + } + +#if defined(CAMERA_MODEL_ESP_EYE) + pinMode(13, INPUT_PULLUP); + pinMode(14, INPUT_PULLUP); +#endif + + // camera init + esp_err_t err = esp_camera_init(&config); + if (err != ESP_OK) { + Serial.printf("Camera init failed with error 0x%x", err); + return; + } + + sensor_t * s = esp_camera_sensor_get(); + s->set_framesize(s, FRAMESIZE_QVGA); + +#if defined(CAMERA_MODEL_M5STACK_WIDE) + s->set_vflip(s, 1); + s->set_hmirror(s, 1); +#endif + + while (WiFi.status() != WL_CONNECTED) { + brightness = 2; + ledcWrite(ledCHannel, brightness); + WiFi.disconnect(); + WiFi.begin(ssid, password); + delay(3000); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println(); + app_httpserver_init(); + app_facenet_main(); + socket_server.listen(82); + Serial.print("Camera Ready! Use 'http://"); + Serial.print(WiFi.localIP()); + Serial.println("' to connect"); + long rssi = WiFi.RSSI(); + Serial.print("RSSI:"); + Serial.println(rssi); + rssii = rssi; + ipconnect = "http://"; + ipconnect += WiFi.localIP().toString().c_str(); + ipconnect += ":82"; + mqttClient.connect(espName.c_str(), mqttUser, mqttPassword); //MQTT Server ESP32 Name + mqttClient.publish(nerkannt.c_str(), ""); //MQTT Server ESP32 Noname + mqttClient.publish(msgTopic.c_str(), ""); //MQTT Server ESP32 Face Name + mqttClient.publish(wifist.c_str(), rssii.c_str()); //MQTT Server ESP32 wifi strength + mqttClient.publish(wifiip.c_str(), WiFi.localIP().toString().c_str()); //MQTT Server ESP32 IP Number + mqttClient.publish(inTopic.c_str(), "false"); + mqttClient.publish(foto.c_str(), "false"); + mqttClient.publish(relaise.c_str(), "false"); + mqttClient.publish(opener.c_str(), "false"); + reconnect(); //inaktive Modus + + brightness = 0; + ledcWrite(ledCHannel, brightness); +} + +//auto aktive setzen bei Seite öffnen +static esp_err_t index_handler(httpd_req_t *req) { + websockets_active = true; + httpd_resp_set_type(req, "text/html"); + httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); + int gesicht = 0; + return httpd_resp_send(req, (const char *)index_ov2640_html_gz, index_ov2640_html_gz_len); +} + +httpd_uri_t index_uri = { + .uri = "/", + .method = HTTP_GET, + .handler = index_handler, + .user_ctx = NULL +}; + +void app_httpserver_init () +{ + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + if (httpd_start(&camera_httpd, &config) == ESP_OK) + Serial.println("httpd_start"); + { + httpd_register_uri_handler(camera_httpd, &index_uri); + } +} + +//CLIENT +esp_err_t _http_event_handle(esp_http_client_event_t *evt) +{ + switch (evt->event_id) { + case HTTP_EVENT_ERROR: + ESP_LOGI(TAG, "HTTP_EVENT_ERROR"); + break; + case HTTP_EVENT_ON_CONNECTED: + ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED"); + break; + case HTTP_EVENT_HEADER_SENT: + ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT"); + break; + case HTTP_EVENT_ON_HEADER: + ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER"); + printf("%.*s", evt->data_len, (char*)evt->data); + break; + case HTTP_EVENT_ON_DATA: + ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len); + if (!esp_http_client_is_chunked_response(evt->client)) { + printf("%.*s", evt->data_len, (char*)evt->data); + } + + break; + case HTTP_EVENT_ON_FINISH: + ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH"); + break; + case HTTP_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED"); + break; + } + return ESP_OK; +} +static esp_err_t send_get_request(const char* name) +{ + esp_err_t res = ESP_OK; + Serial.println("sending.."); + esp_http_client_config_t client_config = {0}; + + char full_url[50];// space for URL and name + strcpy(full_url, get_url); + strcat(full_url, name); + + client_config.url = full_url; + client_config.event_handler = _http_event_handle; + + esp_http_client_handle_t http_client = esp_http_client_init(&client_config); + esp_err_t err = esp_http_client_perform(http_client); + + if (err == ESP_OK) { + ESP_LOGI(TAG, "Status = %d, content_length = %d", + esp_http_client_get_status_code(http_client), + esp_http_client_get_content_length(http_client)); + } + esp_http_client_cleanup(http_client); +} + +String urlencode(String str) +{ + String encodedString = ""; + char c; + char code0; + char code1; + char code2; + for (int i = 0; i < str.length(); i++) { + c = str.charAt(i); + if (c == ' ') { + encodedString += '+'; + } else if (isalnum(c)) { + encodedString += c; + } else { + code1 = (c & 0xf) + '0'; + if ((c & 0xf) > 9) { + code1 = (c & 0xf) - 10 + 'A'; + } + c = (c >> 4) & 0xf; + code0 = c + '0'; + if (c > 9) { + code0 = c - 10 + 'A'; + } + code2 = '\0'; + encodedString += '%'; + encodedString += code0; + encodedString += code1; + } + yield(); + } + return encodedString; + +} + +unsigned char h2int(char c) +{ + if (c >= '0' && c <= '9') { + return ((unsigned char)c - '0'); + } + if (c >= 'a' && c <= 'f') { + return ((unsigned char)c - 'a' + 10); + } + if (c >= 'A' && c <= 'F') { + return ((unsigned char)c - 'A' + 10); + } + return (0); +} + +void app_facenet_main() +{ + face_id_name_init(&st_face_list, FACE_ID_SAVE_NUMBER, ENROLL_CONFIRM_TIMES); + aligned_face = dl_matrix3du_alloc(1, FACE_WIDTH, FACE_HEIGHT, 3); + read_face_id_from_flash_with_name(&st_face_list); +} + +static inline int do_enrollment(face_id_name_list *face_list, dl_matrix3d_t *new_id) +{ + ESP_LOGD(TAG, "START ENROLLING"); + int left_sample_face = enroll_face_id_to_flash_with_name(face_list, new_id, st_name.enroll_name); + ESP_LOGD(TAG, "Face ID %s Enrollment: Sample %d", + st_name.enroll_name, + ENROLL_CONFIRM_TIMES - left_sample_face); + return left_sample_face; +} + +static esp_err_t send_face_list(WebsocketsClient &client) +{ + client.send("delete_faces"); // tell browser to delete all faces + face_id_node *head = st_face_list.head; + char add_face[64]; + for (int i = 0; i < st_face_list.count; i++) // loop current faces + { + sprintf(add_face, "listface:%s", head->id_name); + client.send(add_face); //send face to browser + head = head->next; + } +} + +static esp_err_t delete_all_faces(WebsocketsClient &client) +{ + delete_face_all_in_flash_with_name(&st_face_list); + client.send("delete_faces"); +} + +void handle_message(WebsocketsClient &client, WebsocketsMessage msg) +{ + if (msg.data() == "stream") { + g_state = START_STREAM; + client.send("STREAMING"); + } + if (msg.data() == "detect") { + g_state = START_DETECT; + client.send("DETECTING"); + } + if (msg.data().substring(0, 8) == "capture:") { + g_state = START_ENROLL; + char person[FACE_ID_SAVE_NUMBER * ENROLL_NAME_LEN] = {0,}; + msg.data().substring(8).toCharArray(person, sizeof(person)); + memcpy(st_name.enroll_name, person, strlen(person) + 1); + client.send("CAPTURING"); + } + if (msg.data() == "recognise") { + g_state = START_RECOGNITION; + client.send("RECOGNISING"); + } + if (msg.data() == "disconnect") { + websockets_active = false; + } + if (msg.data().substring(0, 7) == "remove:") { + char person[ENROLL_NAME_LEN * FACE_ID_SAVE_NUMBER]; + msg.data().substring(7).toCharArray(person, sizeof(person)); + delete_face_id_in_flash_with_name(&st_face_list, person); + send_face_list(client); // reset faces in the browser + } + if (msg.data() == "delete_all") { + delete_all_faces(client); + } +} + +//Save Face +void save_face() { + if (brightness == 0) { + brightness = 2; + ledcWrite(ledCHannel, brightness); + led_millis = millis(); // led + long rssi = WiFi.RSSI(); + rssii = rssi; + +// MAC ID from ESP32#### +//chipid = "ESP32_"; +//chipid += String((uint32_t)ESP.getEfuseMac(), HEX); +//Serial.printf("Chip id: %s\n", chipid.c_str()); +// espName = chipid; + + take_send_photo(); + if (!mqttClient.connected()) { + mqttClient.connect(espName.c_str(), mqttUser, mqttPassword); //MQTT Server ESP32 Name + } + mqttClient.loop(); + mqttClient.publish(msgTopic.c_str(), gesicht.c_str()); //MQTT Server ESP32 Face Name + mqttClient.publish(wifist.c_str(), rssii.c_str()); //MQTT Server ESP32 wifi strength + mqttClient.publish(wifiip.c_str(), WiFi.localIP().toString().c_str()); //MQTT Server ESP32 IP Number + + } +} + +//Aktive Modus +void interface_active(WebsocketsClient &client) { + mqttpubsub(); + client.onMessage(handle_message); + dl_matrix3du_t *image_matrix = dl_matrix3du_alloc(1, 320, 240, 3); + http_img_process_result out_res = {0}; + out_res.image = image_matrix->item; + + send_face_list(client); + client.send("STREAMING"); + + while (client.available() && websockets_active) { + client.poll(); + if ((WiFi.status() != WL_CONNECTED) && (millis() > check_wifi)) { + brightness = 2; + ledcWrite(ledCHannel, brightness); + Serial.println("Reconnecting to WiFi..."); + WiFi.disconnect(); + WiFi.begin(ssid, password); + check_wifi = millis() + 30000; + reconnect(); //inaktive Modus + } + if (!mqttClient.connected()) { + mqttClient.connect(espName.c_str(), mqttUser, mqttPassword); //MQTT Server ESP32 Name + mqttpubsub(); + } + mqttClient.loop(); + + if (digitalRead(relay_pin) == HIGH) { + if (millis() - interval > door_opened_millis) { // current time - face recognised time > 5 secs + digitalWrite(relay_pin, LOW); //close relay + mqttClient.publish(relaise.c_str(), "false"); + mqttClient.publish(opener.c_str(), "false"); + } + } + if (brightness == 2) { + + if (millis() - 5000 > led_millis) { // current time - face recognised time > 5 secs + brightness = 0; + ledcWrite(ledCHannel, brightness); + } + } + + ring(); //Klingel + + fb = esp_camera_fb_get(); + + if (g_state == START_DETECT || g_state == START_ENROLL || g_state == START_RECOGNITION) + { + + out_res.net_boxes = NULL; + out_res.face_id = NULL; + + fmt2rgb888(fb->buf, fb->len, fb->format, out_res.image); + + out_res.net_boxes = face_detect(image_matrix, &mtmn_config); + + if (out_res.net_boxes) + { + if (align_face(out_res.net_boxes, image_matrix, aligned_face) == ESP_OK) + { + + out_res.face_id = get_face_id(aligned_face); + last_detected_millis = millis(); + if (g_state == START_DETECT) { + client.send("GESICHT ERKANNT"); + } + + if (g_state == START_ENROLL) + { + int left_sample_face = do_enrollment(&st_face_list, out_res.face_id); + char enrolling_message[64]; + sprintf(enrolling_message, "STICHPROBENNUMMER %d FOR %s", ENROLL_CONFIRM_TIMES - left_sample_face, st_name.enroll_name); + client.send(enrolling_message); + if (left_sample_face == 0) + { + ESP_LOGI(TAG, "Enrolled Face ID: %s", st_face_list.tail->id_name); + g_state = START_STREAM; + char captured_message[64]; + sprintf(captured_message, "GESICHT ERFASST VON %s", st_face_list.tail->id_name); + client.send(captured_message); + send_face_list(client); + if (brightness == 0) { + brightness = 2; + ledcWrite(ledCHannel, brightness); + led_millis = millis(); // led + } + } + } + if (g_state == START_RECOGNITION && (st_face_list.count > 0)) + { + face_id_node *f = recognize_face_with_name(&st_face_list, out_res.face_id); + if (f) + { + char recognised_message[64]; + gesicht = String(f->id_name); + sprintf(recognised_message, "TÜR ÖFFNEN FÜR %s ?", f->id_name); + save_face(); + client.send(recognised_message); + } + else + { + mqttClient.publish(nerkannt.c_str(), noname.c_str()); //MQTT Server ESP32 Noname + client.send("GESICHT NICHT ERKANNT"); + } + } + dl_matrix3d_free(out_res.face_id); + } + } + else + { + if (g_state != START_DETECT) { + client.send("KEIN GESICHT ERKANNT"); + } + } + + if (g_state == START_DETECT && millis() - last_detected_millis > 500) { // Detecting but no face detected + client.send("ERKENNUNG"); + } + } + client.sendBinary((const char *)fb->buf, fb->len); + esp_camera_fb_return(fb); + fb = NULL; + } +} + +//Inaktive Modus +void interface_inactive(WebsocketsClient &client) { + mqttpubsub(); + dl_matrix3du_t *image_matrix = dl_matrix3du_alloc(1, 320, 240, 3); + http_img_process_result out_res = {0}; + out_res.image = image_matrix->item; + while (!websockets_active) { + + if ((WiFi.status() != WL_CONNECTED) && (millis() > check_wifi)) { + Serial.println("Reconnecting to WiFi..."); + brightness = 2; + ledcWrite(ledCHannel, brightness); + WiFi.disconnect(); + WiFi.begin(ssid, password); + check_wifi = millis() + 30000; + reconnect(); //inaktive Modus + } + if (!mqttClient.connected()) { + mqttClient.connect(espName.c_str(), mqttUser, mqttPassword); //MQTT Server ESP32 Name + mqttpubsub(); + } + mqttClient.loop(); + if (brightness == 2) { + + if (millis() - 5000 > led_millis) { // current time - face recognised time > 5 secs + brightness = 0; + ledcWrite(ledCHannel, brightness); + } + } + if (digitalRead(relay_pin) == HIGH) { + if (millis() - interval > door_opened_millis) { // current time - face recognised time > 5 secs + digitalWrite(relay_pin, LOW); //close relay + mqttClient.publish(relaise.c_str(), "false"); + mqttClient.publish(opener.c_str(), "false"); + } + } + ring(); //Klingel + + fb = esp_camera_fb_get(); + + out_res.net_boxes = NULL; + out_res.face_id = NULL; + + fmt2rgb888(fb->buf, fb->len, fb->format, out_res.image); + + out_res.net_boxes = face_detect(image_matrix, &mtmn_config); + + if (out_res.net_boxes) + { + if (align_face(out_res.net_boxes, image_matrix, aligned_face) == ESP_OK) + { + + out_res.face_id = get_face_id(aligned_face); + if (st_face_list.count) // recorded faces exist + { + face_id_node *f = recognize_face_with_name(&st_face_list, out_res.face_id); + if (f) // face recognised + { + gesicht = String(f->id_name); + save_face(); + // send_get_request(f->id_name); + } + else + { + mqttClient.publish(nerkannt.c_str(), noname.c_str()); //MQTT Server ESP32 Noname + } + } + dl_matrix3d_free(out_res.face_id); + free(out_res.net_boxes); + } + } + esp_camera_fb_return(fb); + fb = NULL; + } +} + +void loop() { + + auto client = socket_server.accept(); + Serial.println("loopin"); + + if (client.available() && websockets_active) { + interface_active(client); + Serial.println("end active"); + } + + Serial.println("inactive selected"); + interface_inactive(client); + } + +//MQTT subscribe +void callback(char * topic, byte * payload, unsigned int length) { + + // let's transform a subject (topic) and value (payload) to a line + payload[length] = '\0'; + String strTopic = String(topic); + String strPayload = String((char * ) payload); + if (strTopic == inTopic) { + if (strPayload == "on" || strPayload == "1" || strPayload == "true"){ + take_send_photo(); + mqttClient.publish(inTopic.c_str(), "false"); + } + } + else + if (strTopic == foto) { + if (strPayload == "on" || strPayload == "1" || strPayload == "true"){ + take_send_photo(); + mqttClient.publish(foto.c_str(), "false"); + } + } + else + if (strTopic == opener) { + if (strPayload == "on" || strPayload == "1" || strPayload == "true"){ + mqttClient.publish(relaise.c_str(), "true"); +open_door_pur(); + } + } +} + +//Inaktive Modus +void reconnect() { + + websockets_active = false; + http.begin(ipconnect.c_str()); + int httpCode = http.POST("Message from ESP8266"); //Send the request + String payload = http.getString(); //Get the response payload + Serial.println(httpCode); //Print HTTP return code + Serial.println(payload); //Print request response payload + // http.end(); //Close connection +} + +esp_err_t _http_event_handler(esp_http_client_event_t *evt) +{ + switch (evt->event_id) { + case HTTP_EVENT_ERROR: + Serial.println("HTTP_EVENT_ERROR"); + break; + case HTTP_EVENT_ON_CONNECTED: + Serial.println("HTTP_EVENT_ON_CONNECTED"); + break; + case HTTP_EVENT_HEADER_SENT: + Serial.println("HTTP_EVENT_HEADER_SENT"); + break; + case HTTP_EVENT_ON_HEADER: + Serial.println(); + Serial.printf("HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value); + break; + case HTTP_EVENT_ON_DATA: + Serial.println(); + Serial.printf("HTTP_EVENT_ON_DATA, len=%d", evt->data_len); + if (!esp_http_client_is_chunked_response(evt->client)) { + // Write out data + // printf("%.*s", evt->data_len, (char*)evt->data); + } + break; + case HTTP_EVENT_ON_FINISH: + Serial.println(""); + Serial.println("HTTP_EVENT_ON_FINISH"); + break; + case HTTP_EVENT_DISCONNECTED: + Serial.println("HTTP_EVENT_DISCONNECTED"); + break; + } + return ESP_OK; +} + +//Foto senden +static esp_err_t take_send_photo() +{ + Serial.println("Taking picture..."); + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + + fb = esp_camera_fb_get(); + if (!fb) { + Serial.println("Camera capture failed"); + return ESP_FAIL; + } + + esp_http_client_handle_t http_client; + + esp_http_client_config_t config_client = {0}; + config_client.url = post_url; + config_client.event_handler = _http_event_handler; + config_client.method = HTTP_METHOD_POST; + + http_client = esp_http_client_init(&config_client); + + esp_http_client_set_post_field(http_client, (const char *)fb->buf, fb->len); + + esp_http_client_set_header(http_client, "Content-Type", "image/jpg"); + + esp_err_t err = esp_http_client_perform(http_client); + if (err == ESP_OK) { + Serial.print("esp_http_client_get_status_code: "); + Serial.println(esp_http_client_get_status_code(http_client)); + } + + esp_http_client_cleanup(http_client); + + esp_camera_fb_return(fb); +} + +//Klingel +void ring() { + if (digitalRead(relay_pin1) == LOW) { + if (!mqttClient.connected()) { + mqttClient.connect(espName.c_str(), mqttUser, mqttPassword); //MQTT Server ESP32 Name + } + mqttClient.loop(); + mqttClient.publish(inTopic.c_str(), "true"); + delay(2000); + } +} +void mqttpubsub() { + mqttClient.subscribe(inTopic.c_str()); + mqttClient.subscribe(foto.c_str()); + mqttClient.subscribe(opener.c_str()); +} + +void open_door_pur() { + + if (digitalRead(relay_pin) == LOW) { + + digitalWrite(relay_pin, HIGH); //open (energise) relay so door unlocks + door_opened_millis = millis(); // time relay closed and door opened + + } + if (brightness == 0) { + brightness = 2; + ledcWrite(ledCHannel, brightness); + led_millis = millis(); // led + long rssi = WiFi.RSSI(); + rssii = rssi; + +// MAC ID from ESP32#### +//chipid = "ESP32_"; +//chipid += String((uint32_t)ESP.getEfuseMac(), HEX); +//Serial.printf("Chip id: %s\n", chipid.c_str()); +// espName = chipid; + + if (!mqttClient.connected()) { + mqttClient.connect(espName.c_str(), mqttUser, mqttPassword); //MQTT Server ESP32 Name + } + mqttClient.loop(); + mqttClient.publish(wifist.c_str(), rssii.c_str()); //MQTT Server ESP32 wifi strength + mqttClient.publish(wifiip.c_str(), WiFi.localIP().toString().c_str()); //MQTT Server ESP32 IP Number + + } +} diff --git a/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/camera_index.h b/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/camera_index.h new file mode 100644 index 00000000000..77bb1d7e8f7 --- /dev/null +++ b/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/camera_index.h @@ -0,0 +1,6 @@ + +//File: index_ov2640.html.gz, Size: 4316 +#define index_ov2640_html_gz_len 4316 +const uint8_t index_ov2640_html_gz[] = { + 0x1f, 0x8b, 0x08, 0x08, 0x53, 0xb3, 0x08, 0x5f, 0x00, 0xff, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x2e, 0x67, 0x7a, 0x00, 0xb5, 0x58, 0xff, 0x73, 0xe2, 0x36, 0x16, 0xff, 0x39, 0xfc, 0x15, 0x3a, 0xf7, 0xee, 0x86, 0x5c, 0xc1, 0x90, 0x90, 0x76, 0x52, 0x36, 0x64, 0x2e, 0x9b, 0x92, 0xdb, 0x9d, 0xcb, 0x26, 0x9d, 0x64, 0x77, 0x7a, 0xbf, 0x6d, 0x85, 0x2d, 0x40, 0xb7, 0xb2, 0xe5, 0x4a, 0x32, 0x84, 0xeb, 0xe4, 0x7f, 0xef, 0x7b, 0x92, 0x6c, 0xcb, 0x0e, 0xd9, 0xa4, 0x9d, 0x39, 0x98, 0xc1, 0x20, 0xbd, 0xaf, 0x1f, 0xbd, 0x6f, 0xa2, 0x77, 0xf6, 0x97, 0x54, 0x26, 0x66, 0x57, 0x30, 0xb2, 0x36, 0x99, 0x38, 0xef, 0x9d, 0x55, 0x0f, 0x46, 0x53, 0x78, 0x64, 0xcc, 0x50, 0x92, 0xac, 0xa9, 0xd2, 0xcc, 0xcc, 0xa2, 0xd2, 0x2c, 0x87, 0xa7, 0x51, 0xb5, 0x9c, 0xd3, 0x8c, 0xcd, 0xa2, 0x0d, 0x67, 0xdb, 0x42, 0x2a, 0x13, 0x91, 0x44, 0xe6, 0x86, 0xe5, 0x40, 0xb6, 0xe5, 0xa9, 0x59, 0xcf, 0x52, 0xb6, 0xe1, 0x09, 0x1b, 0xda, 0x1f, 0x03, 0x9e, 0x73, 0xc3, 0xa9, 0x18, 0xea, 0x84, 0x0a, 0x36, 0x3b, 0x42, 0x19, 0x86, 0x1b, 0xc1, 0xce, 0xaf, 0x68, 0xc2, 0xc8, 0x1d, 0x4b, 0xe4, 0x0a, 0x29, 0x64, 0x4e, 0x2e, 0x92, 0x84, 0x69, 0x4d, 0x2e, 0x41, 0x98, 0x92, 0xe2, 0x6c, 0xe4, 0xc8, 0x7a, 0x67, 0xda, 0xec, 0xf0, 0xf9, 0xcf, 0x8c, 0xa5, 0x9c, 0x12, 0x99, 0x8b, 0x1d, 0xd1, 0x89, 0x62, 0x2c, 0x27, 0x34, 0x4f, 0x49, 0x3f, 0xe3, 0xb9, 0x53, 0x35, 0x25, 0xa7, 0xdf, 0x8d, 0x8b, 0x87, 0x43, 0xf2, 0x5b, 0xef, 0x60, 0x21, 0xd3, 0x1d, 0x3e, 0x0f, 0x48, 0xca, 0x75, 0x21, 0xe8, 0x6e, 0x4a, 0x96, 0x82, 0x3d, 0xbc, 0xe9, 0x1d, 0x3c, 0xf6, 0x0e, 0xc8, 0x37, 0xde, 0xe0, 0xa1, 0xe2, 0xab, 0xb5, 0x71, 0x74, 0x19, 0x55, 0x2b, 0x90, 0x24, 0xd8, 0xd2, 0x4c, 0xc9, 0x11, 0xc8, 0x41, 0x5a, 0x42, 0x7a, 0x8f, 0x3d, 0x2f, 0x8b, 0xe0, 0x6b, 0x09, 0x8c, 0xc3, 0x25, 0xcd, 0xb8, 0x00, 0x89, 0x17, 0x0a, 0x1c, 0x1b, 0x90, 0x77, 0x4c, 0x6c, 0x98, 0xe1, 0x09, 0x1d, 0x10, 0x4d, 0x73, 0x3d, 0xd4, 0x4c, 0xf1, 0xe5, 0x1b, 0x47, 0xbf, 0xa0, 0xc9, 0x97, 0x95, 0x92, 0x65, 0x9e, 0x4e, 0xc9, 0x37, 0x47, 0xa7, 0xf8, 0xf6, 0x3b, 0x89, 0x14, 0x52, 0xc1, 0xe2, 0xfc, 0x0a, 0xdf, 0x7e, 0xd1, 0x8a, 0xd7, 0xfc, 0x7f, 0x0c, 0x4c, 0xf8, 0x1e, 0x4d, 0x78, 0xec, 0x35, 0xc6, 0xa2, 0x69, 0x95, 0x1d, 0x19, 0x7d, 0xa8, 0x9c, 0x3e, 0x19, 0x3b, 0x63, 0xad, 0x83, 0xc0, 0xd7, 0x66, 0xaa, 0x3c, 0x7c, 0x1d, 0x97, 0x36, 0x8a, 0xd1, 0xac, 0x22, 0xf7, 0xa4, 0x47, 0xe3, 0xf1, 0xdf, 0xaa, 0x6d, 0x6a, 0x4a, 0x3d, 0xf4, 0x90, 0x56, 0x64, 0x6b, 0x86, 0x3a, 0xa6, 0xe4, 0xf8, 0x3b, 0x94, 0xe8, 0xdc, 0x96, 0x2a, 0x65, 0xe0, 0x5d, 0x2e, 0x73, 0xe6, 0x97, 0x0a, 0x9a, 0xa6, 0x3c, 0x5f, 0x55, 0xd8, 0xd6, 0xee, 0xc2, 0xc2, 0x69, 0xf1, 0x30, 0x3a, 0x3e, 0x2e, 0x1e, 0x9e, 0xc2, 0xe7, 0x0f, 0x65, 0x21, 0x8d, 0x91, 0x59, 0x8b, 0xd5, 0x69, 0x18, 0x2a, 0x9a, 0xf2, 0x52, 0x4f, 0x49, 0xa0, 0x3a, 0x40, 0x7c, 0x85, 0x51, 0xe2, 0xd7, 0x0d, 0x7b, 0x30, 0x43, 0x2a, 0xf8, 0x2a, 0x9f, 0x92, 0x04, 0x90, 0x61, 0xca, 0xf9, 0x54, 0x30, 0xa5, 0x21, 0xf8, 0x5a, 0x2e, 0x3b, 0x8f, 0xff, 0x8c, 0x73, 0xc7, 0x60, 0xe1, 0xff, 0xdb, 0x43, 0xc5, 0x5c, 0x84, 0x04, 0xfa, 0x17, 0xf2, 0x01, 0xc3, 0xc6, 0x9a, 0xe0, 0xf9, 0x60, 0xc9, 0x3a, 0xb8, 0x28, 0x41, 0x72, 0xed, 0x5f, 0x9d, 0x0d, 0x0b, 0x21, 0x93, 0x2f, 0x2d, 0x1b, 0xac, 0x0a, 0x32, 0xee, 0x7a, 0x34, 0x26, 0x47, 0x60, 0x78, 0xc7, 0xf5, 0x8a, 0xaa, 0x0a, 0xa6, 0xd3, 0x0a, 0x2e, 0xc1, 0x73, 0x36, 0xac, 0x31, 0x03, 0xb7, 0xab, 0x68, 0x2f, 0x01, 0x65, 0x60, 0x2c, 0x24, 0x77, 0xd0, 0xb7, 0x72, 0x60, 0xb9, 0xdc, 0x9b, 0x2f, 0xcb, 0xe5, 0x64, 0x3c, 0x39, 0x79, 0x09, 0x8e, 0x27, 0x39, 0x63, 0x57, 0x65, 0x69, 0xd0, 0x16, 0x6b, 0x2a, 0xa0, 0x10, 0x3b, 0x18, 0x74, 0x27, 0x66, 0x4f, 0x2c, 0xdc, 0x35, 0x4a, 0xd3, 0xb5, 0xdc, 0x30, 0x55, 0xd1, 0x74, 0x6c, 0x39, 0xf9, 0xe1, 0x24, 0x0d, 0x69, 0x69, 0x62, 0xf8, 0x86, 0xed, 0x27, 0x3e, 0x3e, 0x4a, 0x8e, 0x5d, 0x4a, 0x79, 0x62, 0xc0, 0x9d, 0x2e, 0x04, 0x4b, 0x2b, 0xf2, 0x0a, 0x8f, 0x94, 0x2d, 0x69, 0x29, 0xcc, 0x3e, 0xef, 0xe9, 0x18, 0xdf, 0xce, 0xfa, 0x30, 0xf5, 0x97, 0x42, 0x52, 0x00, 0x17, 0x97, 0xdc, 0x66, 0x2b, 0xc5, 0xfd, 0xae, 0x5d, 0x73, 0xdb, 0x3c, 0xa3, 0x2b, 0x36, 0xc4, 0x7a, 0x40, 0x01, 0x90, 0xda, 0xbb, 0x42, 0x6a, 0x5b, 0x76, 0x81, 0x96, 0x09, 0x8a, 0xae, 0x38, 0xf2, 0x76, 0x09, 0x78, 0x5a, 0x31, 0x80, 0xa6, 0x14, 0xd5, 0xb6, 0xe0, 0x1a, 0xb0, 0xc7, 0xfa, 0xbc, 0x3f, 0x1d, 0x9a, 0x53, 0xf2, 0x31, 0xe6, 0xfc, 0x11, 0xbc, 0xb6, 0x22, 0xa0, 0xac, 0x8e, 0x2a, 0x65, 0x82, 0x99, 0x67, 0x80, 0xfd, 0x4a, 0x44, 0x40, 0xd2, 0x36, 0xe1, 0xf6, 0x24, 0xb0, 0xf6, 0xa5, 0xff, 0xd3, 0x88, 0xc5, 0x44, 0x7d, 0x2e, 0x62, 0xc1, 0xb4, 0xf5, 0xa4, 0xc1, 0xa5, 0x95, 0xb6, 0x13, 0x87, 0xcc, 0xd9, 0xc8, 0xf7, 0xaa, 0xb3, 0x91, 0xef, 0xa2, 0xd8, 0x3b, 0xe0, 0x91, 0xf2, 0x0d, 0xe1, 0xe9, 0x2c, 0x0a, 0x4b, 0x39, 0x74, 0x42, 0x42, 0xea, 0x1d, 0x07, 0x7b, 0x73, 0x4c, 0xd0, 0x54, 0x05, 0xd5, 0x7a, 0x16, 0x75, 0x8e, 0x2f, 0x3a, 0x27, 0x67, 0x3c, 0x5b, 0x05, 0x3c, 0x11, 0xd1, 0x2a, 0x99, 0x45, 0xb8, 0x31, 0x02, 0x69, 0xa8, 0xdc, 0x3d, 0xba, 0x4a, 0x6d, 0x4c, 0x74, 0xb5, 0x86, 0x05, 0x1d, 0x45, 0xe8, 0x82, 0xe6, 0x8e, 0xab, 0x54, 0x0a, 0xb9, 0x1c, 0x49, 0x74, 0x0e, 0xbe, 0xc1, 0x56, 0xad, 0x24, 0x10, 0xe2, 0x2a, 0xe8, 0x10, 0x87, 0x02, 0x2b, 0x1d, 0xb6, 0x78, 0x5e, 0x94, 0x26, 0xd8, 0x8c, 0x08, 0x0e, 0x1a, 0xb3, 0x08, 0x4f, 0x21, 0x22, 0x1b, 0x2a, 0x4a, 0xf8, 0x11, 0x11, 0x50, 0x9a, 0xb0, 0xb5, 0x14, 0x70, 0x8c, 0xb3, 0xe8, 0x06, 0xf8, 0x21, 0x1f, 0x14, 0xc9, 0x59, 0x09, 0xbd, 0xfd, 0x27, 0x57, 0x97, 0x19, 0xa0, 0xcf, 0x8b, 0x82, 0xe5, 0xce, 0xf0, 0x96, 0x72, 0x8f, 0x90, 0x4f, 0xed, 0x4a, 0xb7, 0x2f, 0x78, 0xa8, 0xdc, 0x7d, 0x05, 0x17, 0x1c, 0x4e, 0x9e, 0xde, 0x81, 0x7f, 0x79, 0xf1, 0x61, 0x7e, 0x77, 0x41, 0xee, 0x3f, 0xde, 0xcd, 0x2f, 0x3e, 0x9c, 0x8d, 0x1c, 0xe9, 0xb3, 0x22, 0x52, 0x88, 0xc8, 0x04, 0x27, 0x1d, 0x27, 0xc2, 0x43, 0x79, 0xf3, 0xe9, 0x8e, 0xcc, 0xef, 0xfe, 0x3d, 0xbf, 0xb9, 0x99, 0xdf, 0x84, 0x22, 0xfe, 0x9c, 0x99, 0x09, 0x2d, 0x4c, 0xa9, 0x58, 0xdb, 0x4e, 0x62, 0xa7, 0xa0, 0x59, 0x34, 0x57, 0xda, 0x20, 0x18, 0x80, 0x0c, 0x02, 0x65, 0x81, 0x59, 0xb1, 0x05, 0xe2, 0x72, 0x33, 0xff, 0x34, 0x27, 0x3f, 0xcd, 0xef, 0xee, 0x6f, 0x5b, 0x46, 0xec, 0xd5, 0xa1, 0xdc, 0xbc, 0xa5, 0x1b, 0x2d, 0xde, 0x15, 0xc7, 0xbf, 0xd7, 0x9b, 0xfd, 0x80, 0x70, 0x0d, 0x71, 0x95, 0xef, 0x01, 0xe5, 0xc7, 0xf7, 0xf7, 0x97, 0xb7, 0x20, 0xe3, 0xf2, 0x63, 0x2d, 0xe4, 0xe0, 0x39, 0x4c, 0x0a, 0x26, 0x0b, 0x51, 0x47, 0xcd, 0x7a, 0x72, 0x7e, 0xe9, 0x30, 0x48, 0x09, 0x4e, 0x87, 0x1a, 0xd2, 0x68, 0xe2, 0xf7, 0x4a, 0x18, 0x4d, 0xed, 0x97, 0x91, 0xfb, 0xf6, 0x07, 0x21, 0x76, 0x35, 0xe5, 0x33, 0x15, 0x22, 0x3a, 0xbf, 0xb8, 0xbe, 0x9e, 0x93, 0xeb, 0xbf, 0xdf, 0x96, 0x99, 0x78, 0x73, 0x7f, 0xf9, 0x6e, 0xef, 0xd1, 0x55, 0x0f, 0x18, 0x34, 0x79, 0x61, 0xce, 0x7b, 0x30, 0x2a, 0x97, 0x80, 0xba, 0x89, 0xa1, 0x5e, 0xcd, 0x37, 0xf0, 0xe5, 0x1a, 0x2a, 0x1f, 0x83, 0x7c, 0xec, 0x47, 0x3f, 0xde, 0x7e, 0xc0, 0x91, 0x15, 0xd7, 0x24, 0x4d, 0x59, 0x1a, 0x0d, 0xc8, 0xb2, 0xcc, 0xa1, 0x39, 0xc8, 0xbc, 0xcf, 0x90, 0x14, 0x07, 0x52, 0x02, 0x41, 0xaf, 0xa0, 0x96, 0x69, 0xf6, 0x4e, 0xc2, 0x41, 0xce, 0x48, 0x2d, 0x10, 0xfa, 0x30, 0x54, 0x5f, 0x99, 0xc7, 0x12, 0xf0, 0xe3, 0x76, 0x5a, 0x41, 0x52, 0x17, 0xb3, 0x9f, 0x94, 0x00, 0xda, 0x9a, 0xed, 0x5b, 0x12, 0x4d, 0x4f, 0x8f, 0x22, 0xa4, 0x01, 0xf0, 0x61, 0xe1, 0xe7, 0xfb, 0xcf, 0x9f, 0xee, 0xae, 0x81, 0x24, 0xda, 0xea, 0xe9, 0x68, 0x14, 0x01, 0xc5, 0x96, 0xe7, 0xa9, 0xdc, 0x36, 0x62, 0xd7, 0x35, 0xe3, 0x71, 0xc0, 0xb8, 0xd5, 0xc0, 0x94, 0xb3, 0x2d, 0xf9, 0x99, 0x2d, 0xee, 0x61, 0x12, 0x60, 0xa6, 0xef, 0x64, 0x1d, 0xbe, 0xe9, 0xd5, 0x44, 0x38, 0xe0, 0x87, 0xa6, 0xae, 0x98, 0x99, 0x0b, 0x86, 0x5f, 0xdf, 0xee, 0xde, 0xa7, 0xfd, 0xaa, 0xfe, 0x00, 0x4b, 0xc5, 0xe1, 0xb2, 0xfd, 0x4a, 0xaa, 0xec, 0x8a, 0x33, 0x91, 0x7e, 0x8d, 0xd9, 0x17, 0x86, 0x80, 0xd9, 0x89, 0x7b, 0xeb, 0x0e, 0xed, 0x2b, 0x9c, 0xed, 0xac, 0x0e, 0x04, 0xb8, 0x24, 0x7d, 0xb5, 0x00, 0x9f, 0xd3, 0x81, 0x00, 0x9f, 0x81, 0xaf, 0x96, 0x50, 0x65, 0x6c, 0x20, 0xa2, 0x4e, 0xb0, 0x57, 0x0b, 0x69, 0x52, 0x32, 0x74, 0xa5, 0x4e, 0xaf, 0xd7, 0xbb, 0xd3, 0x64, 0xe4, 0xe1, 0x1b, 0xb8, 0xb8, 0x34, 0xa8, 0x60, 0xe8, 0x5f, 0x08, 0xf1, 0xb2, 0xa4, 0x20, 0x4b, 0x5c, 0x1c, 0x8c, 0x46, 0x64, 0x05, 0x6d, 0x07, 0x22, 0x5a, 0xb1, 0x5f, 0xa1, 0x2a, 0x27, 0xbb, 0x01, 0x49, 0x4b, 0x65, 0x23, 0x0b, 0xb6, 0xe9, 0x0c, 0x43, 0xe8, 0xa2, 0x4c, 0xb9, 0xb4, 0x29, 0xf0, 0x60, 0xfa, 0xd6, 0x87, 0x2a, 0xfe, 0x09, 0x5c, 0xf9, 0x94, 0xb9, 0xc7, 0xe6, 0xdd, 0xdf, 0x0e, 0x1e, 0x06, 0xbb, 0x43, 0xd7, 0x3e, 0x37, 0x33, 0x1a, 0xc3, 0x0d, 0x8e, 0x1a, 0x76, 0xab, 0x13, 0x2e, 0x60, 0xfc, 0x90, 0xca, 0x71, 0x12, 0x52, 0xd6, 0x7b, 0xff, 0x02, 0xcd, 0xd5, 0xea, 0x26, 0xf6, 0xbe, 0xf5, 0xcb, 0x7a, 0xa5, 0xb6, 0x29, 0x76, 0x0d, 0xc5, 0x77, 0xee, 0x4d, 0xec, 0x7a, 0x8d, 0xfe, 0xb5, 0xa4, 0x70, 0x36, 0x5e, 0x6a, 0xcd, 0x4f, 0x61, 0xbe, 0xd0, 0x86, 0xe7, 0xd6, 0x87, 0x4a, 0x67, 0x8c, 0x5e, 0x7a, 0x31, 0xdb, 0x7f, 0x8c, 0xe3, 0x31, 0x0c, 0x6f, 0x4e, 0x16, 0xb4, 0x3f, 0x85, 0x4c, 0xbe, 0x1d, 0x7e, 0xe4, 0x19, 0xab, 0x0d, 0xd0, 0x46, 0x16, 0xed, 0xad, 0x6f, 0x77, 0xc8, 0x3c, 0x3e, 0xb2, 0x24, 0x8f, 0x88, 0xe0, 0x56, 0xc7, 0x32, 0x97, 0xd0, 0xbf, 0x00, 0xf7, 0xfe, 0x21, 0x99, 0x9d, 0xfb, 0x09, 0x02, 0xcf, 0x46, 0x0a, 0x06, 0x79, 0xba, 0xea, 0xff, 0x02, 0xe0, 0xa1, 0x6d, 0x50, 0xf6, 0x8c, 0x24, 0x7f, 0xfd, 0xcd, 0x65, 0xe2, 0xe3, 0x2f, 0x4e, 0x0a, 0x7e, 0x58, 0x29, 0x19, 0xdc, 0x8e, 0x61, 0x16, 0x00, 0x41, 0xf5, 0xb7, 0x4a, 0x1a, 0x5f, 0x92, 0x3e, 0x7a, 0x2d, 0x97, 0xd5, 0x5e, 0x9c, 0x52, 0xb8, 0xa8, 0xcf, 0x66, 0x50, 0x1a, 0x20, 0x51, 0xa0, 0x57, 0x44, 0xae, 0x0c, 0x55, 0xd4, 0x21, 0x59, 0xac, 0xcb, 0x05, 0x10, 0xf5, 0xc7, 0x03, 0x72, 0x0a, 0x26, 0x02, 0x0b, 0xce, 0x75, 0x4b, 0xa8, 0xc0, 0x01, 0x13, 0x9c, 0x76, 0x9a, 0x62, 0x55, 0xfe, 0x28, 0xef, 0xed, 0xed, 0x7b, 0xaf, 0x88, 0x1f, 0x0e, 0x3d, 0x3a, 0x60, 0x38, 0x61, 0x42, 0xb3, 0x27, 0xca, 0xac, 0x7c, 0x1f, 0x68, 0xa8, 0x42, 0xb7, 0x74, 0xd4, 0xc1, 0x6a, 0xeb, 0xff, 0x95, 0x92, 0x99, 0x57, 0xf6, 0x3a, 0xb1, 0x52, 0xaa, 0xcf, 0x08, 0x76, 0x4b, 0x26, 0x58, 0xde, 0x84, 0xe1, 0xd1, 0x78, 0x70, 0x3c, 0x99, 0x0c, 0x60, 0x58, 0x84, 0x24, 0x09, 0xd6, 0x27, 0x83, 0xef, 0xc7, 0x93, 0xc1, 0x31, 0x2e, 0xb7, 0x15, 0x85, 0x72, 0x9e, 0x4d, 0x9c, 0xce, 0xb0, 0x74, 0x18, 0x73, 0x38, 0x50, 0xf5, 0xee, 0xe3, 0x07, 0xac, 0xcd, 0xa1, 0x99, 0x95, 0xf0, 0xaf, 0x4a, 0xeb, 0x4c, 0x65, 0x87, 0x10, 0x68, 0x30, 0x55, 0xc6, 0xcd, 0x24, 0x8c, 0x05, 0xdf, 0xde, 0x6d, 0x7d, 0x74, 0x63, 0xac, 0x35, 0x9f, 0x4f, 0xa0, 0xe1, 0x50, 0x06, 0x68, 0x9e, 0x60, 0x74, 0xbc, 0x15, 0x72, 0xd1, 0x60, 0x83, 0x8d, 0xa6, 0x54, 0xe2, 0x76, 0xf1, 0x5f, 0x88, 0x3e, 0x10, 0x0a, 0x51, 0x57, 0xe5, 0xa5, 0x5d, 0x82, 0xdf, 0x2d, 0x49, 0x35, 0x36, 0xd8, 0x19, 0x62, 0x18, 0x3b, 0x81, 0xa7, 0xe6, 0x77, 0x7b, 0x68, 0x82, 0x0d, 0xfc, 0xb0, 0x9a, 0x43, 0xf0, 0x26, 0x82, 0x27, 0x5f, 0x3a, 0x39, 0x00, 0x51, 0xad, 0x19, 0x60, 0xdf, 0x6a, 0x24, 0x36, 0xde, 0xc3, 0x4a, 0xfe, 0x12, 0x73, 0x58, 0xc6, 0x2d, 0x73, 0xab, 0x8a, 0x3f, 0xc3, 0xed, 0xba, 0xcf, 0x67, 0x9c, 0x59, 0x5f, 0xd3, 0xa4, 0x5c, 0x69, 0x70, 0x1e, 0xd6, 0x8a, 0xbd, 0x9e, 0x29, 0x76, 0xdd, 0x40, 0x5e, 0x63, 0x48, 0xa7, 0x17, 0xbc, 0xe4, 0x48, 0xa7, 0x11, 0x38, 0x20, 0x3a, 0x7d, 0xe0, 0x45, 0x30, 0xc2, 0x26, 0x60, 0x85, 0xd8, 0x4e, 0xd0, 0xe9, 0x01, 0x2f, 0x43, 0x1a, 0xf6, 0x01, 0x6f, 0x4a, 0xa7, 0xb7, 0x83, 0x8c, 0x2f, 0x6c, 0x57, 0x16, 0xdd, 0xba, 0xd6, 0x02, 0xbf, 0xbe, 0xf6, 0xce, 0xc8, 0x92, 0x42, 0x36, 0x79, 0x61, 0x61, 0x8b, 0x78, 0x3e, 0xe3, 0xbd, 0x44, 0xe8, 0x41, 0x8e, 0x46, 0x83, 0x0c, 0xf8, 0xcb, 0x10, 0x6b, 0x13, 0x84, 0x34, 0x59, 0x28, 0xb9, 0x85, 0x7f, 0x52, 0xec, 0x1f, 0x82, 0x75, 0x49, 0x35, 0x96, 0x06, 0x87, 0xb3, 0xf0, 0x58, 0xa1, 0x4d, 0xa8, 0xdd, 0x3d, 0x08, 0x49, 0xb0, 0xd1, 0x44, 0xa5, 0x77, 0x0b, 0x3c, 0x5e, 0x73, 0xc1, 0x48, 0xbf, 0xe2, 0x89, 0x97, 0x1c, 0xc6, 0xec, 0x4b, 0x58, 0x4c, 0x9b, 0x24, 0xd9, 0xb3, 0x19, 0x2b, 0x96, 0xc1, 0x3f, 0x05, 0x55, 0x51, 0x72, 0x69, 0xd7, 0xc5, 0xc7, 0x86, 0x0c, 0x66, 0xaa, 0x4f, 0xd2, 0x67, 0x91, 0x31, 0xca, 0x85, 0x96, 0x4d, 0x9b, 0xa6, 0x75, 0x76, 0xaa, 0x6d, 0x18, 0x61, 0x41, 0x0f, 0xf9, 0x83, 0x0e, 0x03, 0x8e, 0x16, 0xc0, 0xf7, 0x86, 0x65, 0x21, 0x83, 0xcb, 0x7a, 0x1f, 0xfb, 0x7d, 0xa8, 0xff, 0x21, 0x43, 0x22, 0xa4, 0x66, 0x2f, 0x70, 0xe0, 0xe5, 0xb0, 0xe2, 0xa9, 0xe9, 0x63, 0x3b, 0x97, 0x5b, 0xf0, 0xc0, 0x9d, 0x2a, 0xae, 0x9e, 0x92, 0x71, 0x84, 0x21, 0x70, 0xb0, 0xbb, 0xff, 0x74, 0xe8, 0xb6, 0xe1, 0x1b, 0x4e, 0xda, 0xcd, 0x79, 0x05, 0xf9, 0x84, 0x87, 0xb4, 0x37, 0x3d, 0x01, 0xec, 0xca, 0x3f, 0x0f, 0x46, 0x4c, 0xf1, 0x92, 0x99, 0xda, 0xe3, 0xed, 0x7b, 0x49, 0xcf, 0xfa, 0x0a, 0xff, 0x4f, 0x63, 0x43, 0xb5, 0x64, 0x87, 0x31, 0xce, 0x3d, 0xfe, 0x06, 0xb0, 0xcf, 0x8d, 0xbd, 0x1a, 0x6a, 0xdf, 0xba, 0xec, 0xd1, 0x7f, 0x7c, 0xb8, 0xd4, 0x61, 0x17, 0xb2, 0x55, 0xb2, 0x9a, 0xf9, 0xe2, 0x85, 0xa8, 0x42, 0x37, 0xe1, 0xe6, 0xee, 0x6f, 0x31, 0x70, 0xd1, 0x71, 0xff, 0x47, 0x8c, 0xec, 0x7f, 0xfd, 0xbf, 0x03, 0x55, 0x6e, 0x31, 0x19, 0x03, 0x18, 0x00, 0x00 +}; diff --git a/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/camera_pins.h b/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/camera_pins.h new file mode 100644 index 00000000000..d61dc86c81a --- /dev/null +++ b/libraries/ESP32/examples/Camera/PersonErkennung_esp32_cam/camera_pins.h @@ -0,0 +1,99 @@ + +#if defined(CAMERA_MODEL_WROVER_KIT) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 21 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 19 +#define Y4_GPIO_NUM 18 +#define Y3_GPIO_NUM 5 +#define Y2_GPIO_NUM 4 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#elif defined(CAMERA_MODEL_ESP_EYE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 4 +#define SIOD_GPIO_NUM 18 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 36 +#define Y8_GPIO_NUM 37 +#define Y7_GPIO_NUM 38 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 35 +#define Y4_GPIO_NUM 14 +#define Y3_GPIO_NUM 13 +#define Y2_GPIO_NUM 34 +#define VSYNC_GPIO_NUM 5 +#define HREF_GPIO_NUM 27 +#define PCLK_GPIO_NUM 25 + +#elif defined(CAMERA_MODEL_M5STACK_PSRAM) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 25 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 22 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_M5STACK_WIDE) +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM 15 +#define XCLK_GPIO_NUM 27 +#define SIOD_GPIO_NUM 22 +#define SIOC_GPIO_NUM 23 + +#define Y9_GPIO_NUM 19 +#define Y8_GPIO_NUM 36 +#define Y7_GPIO_NUM 18 +#define Y6_GPIO_NUM 39 +#define Y5_GPIO_NUM 5 +#define Y4_GPIO_NUM 34 +#define Y3_GPIO_NUM 35 +#define Y2_GPIO_NUM 32 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 26 +#define PCLK_GPIO_NUM 21 + +#elif defined(CAMERA_MODEL_AI_THINKER) +#define PWDN_GPIO_NUM 32 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 0 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 + +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 21 +#define Y4_GPIO_NUM 19 +#define Y3_GPIO_NUM 18 +#define Y2_GPIO_NUM 5 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +#else +#error "Camera model not selected" +#endif diff --git a/libraries/PubSubClient/CHANGES.txt b/libraries/PubSubClient/CHANGES.txt new file mode 100644 index 00000000000..e23d5315f2f --- /dev/null +++ b/libraries/PubSubClient/CHANGES.txt @@ -0,0 +1,85 @@ +2.8 + * Add setBufferSize() to override MQTT_MAX_PACKET_SIZE + * Add setKeepAlive() to override MQTT_KEEPALIVE + * Add setSocketTimeout() to overide MQTT_SOCKET_TIMEOUT + * Added check to prevent subscribe/unsubscribe to empty topics + * Declare wifi mode prior to connect in ESP example + * Use `strnlen` to avoid overruns + * Support pre-connected Client objects + +2.7 + * Fix remaining-length handling to prevent buffer overrun + * Add large-payload API - beginPublish/write/publish/endPublish + * Add yield call to improve reliability on ESP + * Add Clean Session flag to connect options + * Add ESP32 support for functional callback signature + * Various other fixes + +2.4 + * Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely + whilst waiting for inbound data + * Fixed return code when publishing >256 bytes + +2.3 + * Add publish(topic,payload,retained) function + +2.2 + * Change code layout to match Arduino Library reqs + +2.1 + * Add MAX_TRANSFER_SIZE def to chunk messages if needed + * Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE + +2.0 + * Add (and default to) MQTT 3.1.1 support + * Fix PROGMEM handling for Intel Galileo/ESP8266 + * Add overloaded constructors for convenience + * Add chainable setters for server/callback/client/stream + * Add state function to return connack return code + +1.9 + * Do not split MQTT packets over multiple calls to _client->write() + * API change: All constructors now require an instance of Client + to be passed in. + * Fixed example to match 1.8 api changes - dpslwk + * Added username/password support - WilHall + * Added publish_P - publishes messages from PROGMEM - jobytaffey + +1.8 + * KeepAlive interval is configurable in PubSubClient.h + * Maximum packet size is configurable in PubSubClient.h + * API change: Return boolean rather than int from various functions + * API change: Length parameter in message callback changed + from int to unsigned int + * Various internal tidy-ups around types +1.7 + * Improved keepalive handling + * Updated to the Arduino-1.0 API +1.6 + * Added the ability to publish a retained message + +1.5 + * Added default constructor + * Fixed compile error when used with arduino-0021 or later + +1.4 + * Fixed connection lost handling + +1.3 + * Fixed packet reading bug in PubSubClient.readPacket + +1.2 + * Fixed compile error when used with arduino-0016 or later + + +1.1 + * Reduced size of library + * Added support for Will messages + * Clarified licensing - see LICENSE.txt + + +1.0 + * Only Quality of Service (QOS) 0 messaging is supported + * The maximum message size, including header, is 128 bytes + * The keepalive interval is set to 30 seconds + * No support for Will messages diff --git a/libraries/PubSubClient/LICENSE.txt b/libraries/PubSubClient/LICENSE.txt new file mode 100644 index 00000000000..12c1689e6e0 --- /dev/null +++ b/libraries/PubSubClient/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2020 Nicholas O'Leary + +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. diff --git a/libraries/PubSubClient/README.md b/libraries/PubSubClient/README.md new file mode 100644 index 00000000000..2e131718505 --- /dev/null +++ b/libraries/PubSubClient/README.md @@ -0,0 +1,50 @@ +# Arduino Client for MQTT + +This library provides a client for doing simple publish/subscribe messaging with +a server that supports MQTT. + +## Examples + +The library comes with a number of example sketches. See File > Examples > PubSubClient +within the Arduino application. + +Full API documentation is available here: https://pubsubclient.knolleary.net + +## Limitations + + - It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1. + - The maximum message size, including header, is **256 bytes** by default. This + is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h` or can be changed + by calling `PubSubClient::setBufferSize(size)`. + - The keepalive interval is set to 15 seconds by default. This is configurable + via `MQTT_KEEPALIVE` in `PubSubClient.h` or can be changed by calling + `PubSubClient::setKeepAlive(keepAlive)`. + - The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by + changing value of `MQTT_VERSION` in `PubSubClient.h`. + + +## Compatible Hardware + +The library uses the Arduino Ethernet Client api for interacting with the +underlying network hardware. This means it Just Works with a growing number of +boards and shields, including: + + - Arduino Ethernet + - Arduino Ethernet Shield + - Arduino YUN – use the included `YunClient` in place of `EthernetClient`, and + be sure to do a `Bridge.begin()` first + - Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield, + enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`. + - Sparkfun WiFly Shield – [library](https://github.com/dpslwk/WiFly) + - TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library) + - Intel Galileo/Edison + - ESP8266 + - ESP32 + +The library cannot currently be used with hardware based on the ENC28J60 chip – +such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an +[alternative library](https://github.com/njh/NanodeMQTT) available. + +## License + +This code is released under the MIT License. diff --git a/libraries/PubSubClient/examples/mqtt_auth/mqtt_auth.ino b/libraries/PubSubClient/examples/mqtt_auth/mqtt_auth.ino new file mode 100644 index 00000000000..04bd7bb2928 --- /dev/null +++ b/libraries/PubSubClient/examples/mqtt_auth/mqtt_auth.ino @@ -0,0 +1,43 @@ +/* + Basic MQTT example with Authentication + + - connects to an MQTT server, providing username + and password + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic" +*/ + +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +EthernetClient ethClient; +PubSubClient client(server, 1883, callback, ethClient); + +void setup() +{ + Ethernet.begin(mac, ip); + // Note - the default maximum packet size is 128 bytes. If the + // combined length of clientId, username and password exceed this use the + // following to increase the buffer size: + // client.setBufferSize(255); + + if (client.connect("arduinoClient", "testuser", "testpass")) { + client.publish("outTopic","hello world"); + client.subscribe("inTopic"); + } +} + +void loop() +{ + client.loop(); +} diff --git a/libraries/PubSubClient/examples/mqtt_basic/mqtt_basic.ino b/libraries/PubSubClient/examples/mqtt_basic/mqtt_basic.ino new file mode 100644 index 00000000000..f545adef82b --- /dev/null +++ b/libraries/PubSubClient/examples/mqtt_basic/mqtt_basic.ino @@ -0,0 +1,77 @@ +/* + Basic MQTT example + + This sketch demonstrates the basic capabilities of the library. + It connects to an MQTT server then: + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic", printing out any messages + it receives. NB - it assumes the received payloads are strings not binary + + It will reconnect to the server if the connection is lost using a blocking + reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to + achieve the same result without blocking the main loop. + +*/ + +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + for (int i=0;i Preferences -> Additional Boards Manager URLs": + http://arduino.esp8266.com/stable/package_esp8266com_index.json + - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266" + - Select your ESP8266 in "Tools -> Board" +*/ + +#include +#include + +// Update these with values suitable for your network. + +const char* ssid = "........"; +const char* password = "........"; +const char* mqtt_server = "broker.mqtt-dashboard.com"; + +WiFiClient espClient; +PubSubClient client(espClient); +unsigned long lastMsg = 0; +#define MSG_BUFFER_SIZE (50) +char msg[MSG_BUFFER_SIZE]; +int value = 0; + +void setup_wifi() { + + delay(10); + // We start by connecting to a WiFi network + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + randomSeed(micros()); + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); +} + +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + for (int i = 0; i < length; i++) { + Serial.print((char)payload[i]); + } + Serial.println(); + + // Switch on the LED if an 1 was received as first character + if ((char)payload[0] == '1') { + digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level + // but actually the LED is on; this is because + // it is active low on the ESP-01) + } else { + digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH + } + +} + +void reconnect() { + // Loop until we're reconnected + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + // Create a random client ID + String clientId = "ESP8266Client-"; + clientId += String(random(0xffff), HEX); + // Attempt to connect + if (client.connect(clientId.c_str())) { + Serial.println("connected"); + // Once connected, publish an announcement... + client.publish("outTopic", "hello world"); + // ... and resubscribe + client.subscribe("inTopic"); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setup() { + pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output + Serial.begin(115200); + setup_wifi(); + client.setServer(mqtt_server, 1883); + client.setCallback(callback); +} + +void loop() { + + if (!client.connected()) { + reconnect(); + } + client.loop(); + + unsigned long now = millis(); + if (now - lastMsg > 2000) { + lastMsg = now; + ++value; + snprintf (msg, MSG_BUFFER_SIZE, "hello world #%ld", value); + Serial.print("Publish message: "); + Serial.println(msg); + client.publish("outTopic", msg); + } +} diff --git a/libraries/PubSubClient/examples/mqtt_large_message/mqtt_large_message.ino b/libraries/PubSubClient/examples/mqtt_large_message/mqtt_large_message.ino new file mode 100644 index 00000000000..e048c3ed363 --- /dev/null +++ b/libraries/PubSubClient/examples/mqtt_large_message/mqtt_large_message.ino @@ -0,0 +1,179 @@ +/* + Long message ESP8266 MQTT example + + This sketch demonstrates sending arbitrarily large messages in combination + with the ESP8266 board/library. + + It connects to an MQTT server then: + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "greenBottles/#", printing out any messages + it receives. NB - it assumes the received payloads are strings not binary + - If the sub-topic is a number, it publishes a "greenBottles/lyrics" message + with a payload consisting of the lyrics to "10 green bottles", replacing + 10 with the number given in the sub-topic. + + It will reconnect to the server if the connection is lost using a blocking + reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to + achieve the same result without blocking the main loop. + + To install the ESP8266 board, (using Arduino 1.6.4+): + - Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs": + http://arduino.esp8266.com/stable/package_esp8266com_index.json + - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266" + - Select your ESP8266 in "Tools -> Board" + +*/ + +#include +#include + +// Update these with values suitable for your network. + +const char* ssid = "........"; +const char* password = "........"; +const char* mqtt_server = "broker.mqtt-dashboard.com"; + +WiFiClient espClient; +PubSubClient client(espClient); +long lastMsg = 0; +char msg[50]; +int value = 0; + +void setup_wifi() { + + delay(10); + // We start by connecting to a WiFi network + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + randomSeed(micros()); + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); +} + +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + for (int i = 0; i < length; i++) { + Serial.print((char)payload[i]); + } + Serial.println(); + + // Find out how many bottles we should generate lyrics for + String topicStr(topic); + int bottleCount = 0; // assume no bottles unless we correctly parse a value from the topic + if (topicStr.indexOf('/') >= 0) { + // The topic includes a '/', we'll try to read the number of bottles from just after that + topicStr.remove(0, topicStr.indexOf('/')+1); + // Now see if there's a number of bottles after the '/' + bottleCount = topicStr.toInt(); + } + + if (bottleCount > 0) { + // Work out how big our resulting message will be + int msgLen = 0; + for (int i = bottleCount; i > 0; i--) { + String numBottles(i); + msgLen += 2*numBottles.length(); + if (i == 1) { + msgLen += 2*String(" green bottle, standing on the wall\n").length(); + } else { + msgLen += 2*String(" green bottles, standing on the wall\n").length(); + } + msgLen += String("And if one green bottle should accidentally fall\nThere'll be ").length(); + switch (i) { + case 1: + msgLen += String("no green bottles, standing on the wall\n\n").length(); + break; + case 2: + msgLen += String("1 green bottle, standing on the wall\n\n").length(); + break; + default: + numBottles = i-1; + msgLen += numBottles.length(); + msgLen += String(" green bottles, standing on the wall\n\n").length(); + break; + }; + } + + // Now we can start to publish the message + client.beginPublish("greenBottles/lyrics", msgLen, false); + for (int i = bottleCount; i > 0; i--) { + for (int j = 0; j < 2; j++) { + client.print(i); + if (i == 1) { + client.print(" green bottle, standing on the wall\n"); + } else { + client.print(" green bottles, standing on the wall\n"); + } + } + client.print("And if one green bottle should accidentally fall\nThere'll be "); + switch (i) { + case 1: + client.print("no green bottles, standing on the wall\n\n"); + break; + case 2: + client.print("1 green bottle, standing on the wall\n\n"); + break; + default: + client.print(i-1); + client.print(" green bottles, standing on the wall\n\n"); + break; + }; + } + // Now we're done! + client.endPublish(); + } +} + +void reconnect() { + // Loop until we're reconnected + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + // Create a random client ID + String clientId = "ESP8266Client-"; + clientId += String(random(0xffff), HEX); + // Attempt to connect + if (client.connect(clientId.c_str())) { + Serial.println("connected"); + // Once connected, publish an announcement... + client.publish("outTopic", "hello world"); + // ... and resubscribe + client.subscribe("greenBottles/#"); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setup() { + pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output + Serial.begin(115200); + setup_wifi(); + client.setServer(mqtt_server, 1883); + client.setCallback(callback); +} + +void loop() { + + if (!client.connected()) { + reconnect(); + } + client.loop(); +} diff --git a/libraries/PubSubClient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino b/libraries/PubSubClient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino new file mode 100644 index 00000000000..42afb2a3a49 --- /dev/null +++ b/libraries/PubSubClient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino @@ -0,0 +1,60 @@ +/* + Publishing in the callback + + - connects to an MQTT server + - subscribes to the topic "inTopic" + - when a message is received, republishes it to "outTopic" + + This example shows how to publish messages within the + callback function. The callback function header needs to + be declared before the PubSubClient constructor and the + actual callback defined afterwards. + This ensures the client reference in the callback function + is valid. + +*/ + +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +// Callback function header +void callback(char* topic, byte* payload, unsigned int length); + +EthernetClient ethClient; +PubSubClient client(server, 1883, callback, ethClient); + +// Callback function +void callback(char* topic, byte* payload, unsigned int length) { + // In order to republish this payload, a copy must be made + // as the orignal payload buffer will be overwritten whilst + // constructing the PUBLISH packet. + + // Allocate the correct amount of memory for the payload copy + byte* p = (byte*)malloc(length); + // Copy the payload to the new buffer + memcpy(p,payload,length); + client.publish("outTopic", p, length); + // Free the memory + free(p); +} + +void setup() +{ + + Ethernet.begin(mac, ip); + if (client.connect("arduinoClient")) { + client.publish("outTopic","hello world"); + client.subscribe("inTopic"); + } +} + +void loop() +{ + client.loop(); +} diff --git a/libraries/PubSubClient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino b/libraries/PubSubClient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino new file mode 100644 index 00000000000..080b7391c6a --- /dev/null +++ b/libraries/PubSubClient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino @@ -0,0 +1,67 @@ +/* + Reconnecting MQTT example - non-blocking + + This sketch demonstrates how to keep the client connected + using a non-blocking reconnect function. If the client loses + its connection, it attempts to reconnect every 5 seconds + without blocking the main loop. + +*/ + +#include +#include +#include + +// Update these with values suitable for your hardware/network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +EthernetClient ethClient; +PubSubClient client(ethClient); + +long lastReconnectAttempt = 0; + +boolean reconnect() { + if (client.connect("arduinoClient")) { + // Once connected, publish an announcement... + client.publish("outTopic","hello world"); + // ... and resubscribe + client.subscribe("inTopic"); + } + return client.connected(); +} + +void setup() +{ + client.setServer(server, 1883); + client.setCallback(callback); + + Ethernet.begin(mac, ip); + delay(1500); + lastReconnectAttempt = 0; +} + + +void loop() +{ + if (!client.connected()) { + long now = millis(); + if (now - lastReconnectAttempt > 5000) { + lastReconnectAttempt = now; + // Attempt to reconnect + if (reconnect()) { + lastReconnectAttempt = 0; + } + } + } else { + // Client connected + + client.loop(); + } + +} diff --git a/libraries/PubSubClient/examples/mqtt_stream/mqtt_stream.ino b/libraries/PubSubClient/examples/mqtt_stream/mqtt_stream.ino new file mode 100644 index 00000000000..67c22872cf7 --- /dev/null +++ b/libraries/PubSubClient/examples/mqtt_stream/mqtt_stream.ino @@ -0,0 +1,57 @@ +/* + Example of using a Stream object to store the message payload + + Uses SRAM library: https://github.com/ennui2342/arduino-sram + but could use any Stream based class such as SD + + - connects to an MQTT server + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic" +*/ + +#include +#include +#include +#include + +// Update these with values suitable for your network. +byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; +IPAddress ip(172, 16, 0, 100); +IPAddress server(172, 16, 0, 2); + +SRAM sram(4, SRAM_1024); + +void callback(char* topic, byte* payload, unsigned int length) { + sram.seek(1); + + // do something with the message + for(uint8_t i=0; i +maintainer=Nick O'Leary +sentence=A client library for MQTT messaging. +paragraph=MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000. +category=Communication +url=http://pubsubclient.knolleary.net +architectures=* diff --git a/libraries/PubSubClient/src/PubSubClient.cpp b/libraries/PubSubClient/src/PubSubClient.cpp new file mode 100644 index 00000000000..2b48d2b6b8f --- /dev/null +++ b/libraries/PubSubClient/src/PubSubClient.cpp @@ -0,0 +1,769 @@ +/* + + PubSubClient.cpp - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#include "PubSubClient.h" +#include "Arduino.h" + +PubSubClient::PubSubClient() { + this->_state = MQTT_DISCONNECTED; + this->_client = NULL; + this->stream = NULL; + setCallback(NULL); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(Client& client) { + this->_state = MQTT_DISCONNECTED; + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::~PubSubClient() { + free(this->buffer); +} + +boolean PubSubClient::connect(const char *id) { + return connect(id,NULL,NULL,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { + return connect(id,user,pass,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { + if (!connected()) { + int result = 0; + + + if(_client->connected()) { + result = 1; + } else { + if (domain != NULL) { + result = _client->connect(this->domain, this->port); + } else { + result = _client->connect(this->ip, this->port); + } + } + + if (result == 1) { + nextMsgId = 1; + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + unsigned int j; + +#if MQTT_VERSION == MQTT_VERSION_3_1 + uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 9 +#elif MQTT_VERSION == MQTT_VERSION_3_1_1 + uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 7 +#endif + for (j = 0;jbuffer[length++] = d[j]; + } + + uint8_t v; + if (willTopic) { + v = 0x04|(willQos<<3)|(willRetain<<5); + } else { + v = 0x00; + } + if (cleanSession) { + v = v|0x02; + } + + if(user != NULL) { + v = v|0x80; + + if(pass != NULL) { + v = v|(0x80>>1); + } + } + this->buffer[length++] = v; + + this->buffer[length++] = ((this->keepAlive) >> 8); + this->buffer[length++] = ((this->keepAlive) & 0xFF); + + CHECK_STRING_LENGTH(length,id) + length = writeString(id,this->buffer,length); + if (willTopic) { + CHECK_STRING_LENGTH(length,willTopic) + length = writeString(willTopic,this->buffer,length); + CHECK_STRING_LENGTH(length,willMessage) + length = writeString(willMessage,this->buffer,length); + } + + if(user != NULL) { + CHECK_STRING_LENGTH(length,user) + length = writeString(user,this->buffer,length); + if(pass != NULL) { + CHECK_STRING_LENGTH(length,pass) + length = writeString(pass,this->buffer,length); + } + } + + write(MQTTCONNECT,this->buffer,length-MQTT_MAX_HEADER_SIZE); + + lastInActivity = lastOutActivity = millis(); + + while (!_client->available()) { + unsigned long t = millis(); + if (t-lastInActivity >= ((int32_t) this->socketTimeout*1000UL)) { + _state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } + } + uint8_t llen; + uint32_t len = readPacket(&llen); + + if (len == 4) { + if (buffer[3] == 0) { + lastInActivity = millis(); + pingOutstanding = false; + _state = MQTT_CONNECTED; + return true; + } else { + _state = buffer[3]; + } + } + _client->stop(); + } else { + _state = MQTT_CONNECT_FAILED; + } + return false; + } + return true; +} + +// reads a byte into result +boolean PubSubClient::readByte(uint8_t * result) { + uint32_t previousMillis = millis(); + while(!_client->available()) { + yield(); + uint32_t currentMillis = millis(); + if(currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)){ + return false; + } + } + *result = _client->read(); + return true; +} + +// reads a byte into result[*index] and increments index +boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ + uint16_t current_index = *index; + uint8_t * write_address = &(result[current_index]); + if(readByte(write_address)){ + *index = current_index + 1; + return true; + } + return false; +} + +uint32_t PubSubClient::readPacket(uint8_t* lengthLength) { + uint16_t len = 0; + if(!readByte(this->buffer, &len)) return 0; + bool isPublish = (this->buffer[0]&0xF0) == MQTTPUBLISH; + uint32_t multiplier = 1; + uint32_t length = 0; + uint8_t digit = 0; + uint16_t skip = 0; + uint32_t start = 0; + + do { + if (len == 5) { + // Invalid remaining length encoding - kill the connection + _state = MQTT_DISCONNECTED; + _client->stop(); + return 0; + } + if(!readByte(&digit)) return 0; + this->buffer[len++] = digit; + length += (digit & 127) * multiplier; + multiplier <<=7; //multiplier *= 128 + } while ((digit & 128) != 0); + *lengthLength = len-1; + + if (isPublish) { + // Read in topic length to calculate bytes to skip over for Stream writing + if(!readByte(this->buffer, &len)) return 0; + if(!readByte(this->buffer, &len)) return 0; + skip = (this->buffer[*lengthLength+1]<<8)+this->buffer[*lengthLength+2]; + start = 2; + if (this->buffer[0]&MQTTQOS1) { + // skip message id + skip += 2; + } + } + uint32_t idx = len; + + for (uint32_t i = start;istream) { + if (isPublish && idx-*lengthLength-2>skip) { + this->stream->write(digit); + } + } + + if (len < this->bufferSize) { + this->buffer[len] = digit; + len++; + } + idx++; + } + + if (!this->stream && idx > this->bufferSize) { + len = 0; // This will cause the packet to be ignored. + } + return len; +} + +boolean PubSubClient::loop() { + if (connected()) { + unsigned long t = millis(); + if ((t - lastInActivity > this->keepAlive*1000UL) || (t - lastOutActivity > this->keepAlive*1000UL)) { + if (pingOutstanding) { + this->_state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } else { + this->buffer[0] = MQTTPINGREQ; + this->buffer[1] = 0; + _client->write(this->buffer,2); + lastOutActivity = t; + lastInActivity = t; + pingOutstanding = true; + } + } + if (_client->available()) { + uint8_t llen; + uint16_t len = readPacket(&llen); + uint16_t msgId = 0; + uint8_t *payload; + if (len > 0) { + lastInActivity = t; + uint8_t type = this->buffer[0]&0xF0; + if (type == MQTTPUBLISH) { + if (callback) { + uint16_t tl = (this->buffer[llen+1]<<8)+this->buffer[llen+2]; /* topic length in bytes */ + memmove(this->buffer+llen+2,this->buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */ + this->buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */ + char *topic = (char*) this->buffer+llen+2; + // msgId only present for QOS>0 + if ((this->buffer[0]&0x06) == MQTTQOS1) { + msgId = (this->buffer[llen+3+tl]<<8)+this->buffer[llen+3+tl+1]; + payload = this->buffer+llen+3+tl+2; + callback(topic,payload,len-llen-3-tl-2); + + this->buffer[0] = MQTTPUBACK; + this->buffer[1] = 2; + this->buffer[2] = (msgId >> 8); + this->buffer[3] = (msgId & 0xFF); + _client->write(this->buffer,4); + lastOutActivity = t; + + } else { + payload = this->buffer+llen+3+tl; + callback(topic,payload,len-llen-3-tl); + } + } + } else if (type == MQTTPINGREQ) { + this->buffer[0] = MQTTPINGRESP; + this->buffer[1] = 0; + _client->write(this->buffer,2); + } else if (type == MQTTPINGRESP) { + pingOutstanding = false; + } + } else if (!connected()) { + // readPacket has closed the connection + return false; + } + } + return true; + } + return false; +} + +boolean PubSubClient::publish(const char* topic, const char* payload) { + return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,false); +} + +boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { + return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,retained); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { + return publish(topic, payload, plength, false); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { + if (connected()) { + if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) { + // Too long + return false; + } + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + length = writeString(topic,this->buffer,length); + + // Add payload + uint16_t i; + for (i=0;ibuffer[length++] = payload[i]; + } + + // Write the header + uint8_t header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + return write(header,this->buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) { + return publish_P(topic, (const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0, retained); +} + +boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { + uint8_t llen = 0; + uint8_t digit; + unsigned int rc = 0; + uint16_t tlen; + unsigned int pos = 0; + unsigned int i; + uint8_t header; + unsigned int len; + int expectedLength; + + if (!connected()) { + return false; + } + + tlen = strnlen(topic, this->bufferSize); + + header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + this->buffer[pos++] = header; + len = plength + 2 + tlen; + do { + digit = len & 127; //digit = len %128 + len >>= 7; //len = len / 128 + if (len > 0) { + digit |= 0x80; + } + this->buffer[pos++] = digit; + llen++; + } while(len>0); + + pos = writeString(topic,this->buffer,pos); + + rc += _client->write(this->buffer,pos); + + for (i=0;iwrite((char)pgm_read_byte_near(payload + i)); + } + + lastOutActivity = millis(); + + expectedLength = 1 + llen + 2 + tlen + plength; + + return (rc == expectedLength); +} + +boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) { + if (connected()) { + // Send the header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + length = writeString(topic,this->buffer,length); + uint8_t header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + size_t hlen = buildHeader(header, this->buffer, plength+length-MQTT_MAX_HEADER_SIZE); + uint16_t rc = _client->write(this->buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen)); + lastOutActivity = millis(); + return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen))); + } + return false; +} + +int PubSubClient::endPublish() { + return 1; +} + +size_t PubSubClient::write(uint8_t data) { + lastOutActivity = millis(); + return _client->write(data); +} + +size_t PubSubClient::write(const uint8_t *buffer, size_t size) { + lastOutActivity = millis(); + return _client->write(buffer,size); +} + +size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) { + uint8_t lenBuf[4]; + uint8_t llen = 0; + uint8_t digit; + uint8_t pos = 0; + uint16_t len = length; + do { + + digit = len & 127; //digit = len %128 + len >>= 7; //len = len / 128 + if (len > 0) { + digit |= 0x80; + } + lenBuf[pos++] = digit; + llen++; + } while(len>0); + + buf[4-llen] = header; + for (int i=0;i 0) && result) { + bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; + rc = _client->write(writeBuf,bytesToWrite); + result = (rc == bytesToWrite); + bytesRemaining -= rc; + writeBuf += rc; + } + return result; +#else + rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); + lastOutActivity = millis(); + return (rc == hlen+length); +#endif +} + +boolean PubSubClient::subscribe(const char* topic) { + return subscribe(topic, 0); +} + +boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { + size_t topicLength = strnlen(topic, this->bufferSize); + if (topic == 0) { + return false; + } + if (qos > 1) { + return false; + } + if (this->bufferSize < 9 + topicLength) { + // Too long + return false; + } + if (connected()) { + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + this->buffer[length++] = (nextMsgId >> 8); + this->buffer[length++] = (nextMsgId & 0xFF); + length = writeString((char*)topic, this->buffer,length); + this->buffer[length++] = qos; + return write(MQTTSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +boolean PubSubClient::unsubscribe(const char* topic) { + size_t topicLength = strnlen(topic, this->bufferSize); + if (topic == 0) { + return false; + } + if (this->bufferSize < 9 + topicLength) { + // Too long + return false; + } + if (connected()) { + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + this->buffer[length++] = (nextMsgId >> 8); + this->buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, this->buffer,length); + return write(MQTTUNSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +void PubSubClient::disconnect() { + this->buffer[0] = MQTTDISCONNECT; + this->buffer[1] = 0; + _client->write(this->buffer,2); + _state = MQTT_DISCONNECTED; + _client->flush(); + _client->stop(); + lastInActivity = lastOutActivity = millis(); +} + +uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { + const char* idp = string; + uint16_t i = 0; + pos += 2; + while (*idp) { + buf[pos++] = *idp++; + i++; + } + buf[pos-i-2] = (i >> 8); + buf[pos-i-1] = (i & 0xFF); + return pos; +} + + +boolean PubSubClient::connected() { + boolean rc; + if (_client == NULL ) { + rc = false; + } else { + rc = (int)_client->connected(); + if (!rc) { + if (this->_state == MQTT_CONNECTED) { + this->_state = MQTT_CONNECTION_LOST; + _client->flush(); + _client->stop(); + } + } else { + return this->_state == MQTT_CONNECTED; + } + } + return rc; +} + +PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { + IPAddress addr(ip[0],ip[1],ip[2],ip[3]); + return setServer(addr,port); +} + +PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { + this->ip = ip; + this->port = port; + this->domain = NULL; + return *this; +} + +PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { + this->domain = domain; + this->port = port; + return *this; +} + +PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { + this->callback = callback; + return *this; +} + +PubSubClient& PubSubClient::setClient(Client& client){ + this->_client = &client; + return *this; +} + +PubSubClient& PubSubClient::setStream(Stream& stream){ + this->stream = &stream; + return *this; +} + +int PubSubClient::state() { + return this->_state; +} + +boolean PubSubClient::setBufferSize(uint16_t size) { + if (size == 0) { + // Cannot set it back to 0 + return false; + } + if (this->bufferSize == 0) { + this->buffer = (uint8_t*)malloc(size); + } else { + uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size); + if (newBuffer != NULL) { + this->buffer = newBuffer; + } else { + return false; + } + } + this->bufferSize = size; + return (this->buffer != NULL); +} + +uint16_t PubSubClient::getBufferSize() { + return this->bufferSize; +} +PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) { + this->keepAlive = keepAlive; + return *this; +} +PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) { + this->socketTimeout = timeout; + return *this; +} diff --git a/libraries/PubSubClient/src/PubSubClient.h b/libraries/PubSubClient/src/PubSubClient.h new file mode 100644 index 00000000000..7f195261c13 --- /dev/null +++ b/libraries/PubSubClient/src/PubSubClient.h @@ -0,0 +1,184 @@ +/* + PubSubClient.h - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#ifndef PubSubClient_h +#define PubSubClient_h + +#include +#include "IPAddress.h" +#include "Client.h" +#include "Stream.h" + +#define MQTT_VERSION_3_1 3 +#define MQTT_VERSION_3_1_1 4 + +// MQTT_VERSION : Pick the version +//#define MQTT_VERSION MQTT_VERSION_3_1 +#ifndef MQTT_VERSION +#define MQTT_VERSION MQTT_VERSION_3_1_1 +#endif + +// MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize(). +#ifndef MQTT_MAX_PACKET_SIZE +#define MQTT_MAX_PACKET_SIZE 512 +#endif + +// MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive() +#ifndef MQTT_KEEPALIVE +#define MQTT_KEEPALIVE 15 +#endif + +// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds. Override with setSocketTimeout() +#ifndef MQTT_SOCKET_TIMEOUT +#define MQTT_SOCKET_TIMEOUT 15 +#endif + +// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client +// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to +// pass the entire MQTT packet in each write call. +//#define MQTT_MAX_TRANSFER_SIZE 80 + +// Possible values for client.state() +#define MQTT_CONNECTION_TIMEOUT -4 +#define MQTT_CONNECTION_LOST -3 +#define MQTT_CONNECT_FAILED -2 +#define MQTT_DISCONNECTED -1 +#define MQTT_CONNECTED 0 +#define MQTT_CONNECT_BAD_PROTOCOL 1 +#define MQTT_CONNECT_BAD_CLIENT_ID 2 +#define MQTT_CONNECT_UNAVAILABLE 3 +#define MQTT_CONNECT_BAD_CREDENTIALS 4 +#define MQTT_CONNECT_UNAUTHORIZED 5 + +#define MQTTCONNECT 1 << 4 // Client request to connect to Server +#define MQTTCONNACK 2 << 4 // Connect Acknowledgment +#define MQTTPUBLISH 3 << 4 // Publish message +#define MQTTPUBACK 4 << 4 // Publish Acknowledgment +#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1) +#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2) +#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3) +#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request +#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment +#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request +#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment +#define MQTTPINGREQ 12 << 4 // PING Request +#define MQTTPINGRESP 13 << 4 // PING Response +#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting +#define MQTTReserved 15 << 4 // Reserved + +#define MQTTQOS0 (0 << 1) +#define MQTTQOS1 (1 << 1) +#define MQTTQOS2 (2 << 1) + +// Maximum size of fixed header and variable length size header +#define MQTT_MAX_HEADER_SIZE 5 + +#if defined(ESP8266) || defined(ESP32) +#include +#define MQTT_CALLBACK_SIGNATURE std::function callback +#else +#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int) +#endif + +#define CHECK_STRING_LENGTH(l,s) if (l+2+strnlen(s, this->bufferSize) > this->bufferSize) {_client->stop();return false;} + +class PubSubClient : public Print { +private: + Client* _client; + uint8_t* buffer; + uint16_t bufferSize; + uint16_t keepAlive; + uint16_t socketTimeout; + uint16_t nextMsgId; + unsigned long lastOutActivity; + unsigned long lastInActivity; + bool pingOutstanding; + MQTT_CALLBACK_SIGNATURE; + uint32_t readPacket(uint8_t*); + boolean readByte(uint8_t * result); + boolean readByte(uint8_t * result, uint16_t * index); + boolean write(uint8_t header, uint8_t* buf, uint16_t length); + uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); + // Build up the header ready to send + // Returns the size of the header + // Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start + // (MQTT_MAX_HEADER_SIZE - ) bytes into the buffer + size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length); + IPAddress ip; + const char* domain; + uint16_t port; + Stream* stream; + int _state; +public: + PubSubClient(); + PubSubClient(Client& client); + PubSubClient(IPAddress, uint16_t, Client& client); + PubSubClient(IPAddress, uint16_t, Client& client, Stream&); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, Client& client); + PubSubClient(uint8_t *, uint16_t, Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(const char*, uint16_t, Client& client); + PubSubClient(const char*, uint16_t, Client& client, Stream&); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + + ~PubSubClient(); + + PubSubClient& setServer(IPAddress ip, uint16_t port); + PubSubClient& setServer(uint8_t * ip, uint16_t port); + PubSubClient& setServer(const char * domain, uint16_t port); + PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); + PubSubClient& setClient(Client& client); + PubSubClient& setStream(Stream& stream); + PubSubClient& setKeepAlive(uint16_t keepAlive); + PubSubClient& setSocketTimeout(uint16_t timeout); + + boolean setBufferSize(uint16_t size); + uint16_t getBufferSize(); + + boolean connect(const char* id); + boolean connect(const char* id, const char* user, const char* pass); + boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession); + void disconnect(); + boolean publish(const char* topic, const char* payload); + boolean publish(const char* topic, const char* payload, boolean retained); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + boolean publish_P(const char* topic, const char* payload, boolean retained); + boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + // Start to publish a message. + // This API: + // beginPublish(...) + // one or more calls to write(...) + // endPublish() + // Allows for arbitrarily large payloads to be sent without them having to be copied into + // a new buffer and held in memory at one time + // Returns 1 if the message was started successfully, 0 if there was an error + boolean beginPublish(const char* topic, unsigned int plength, boolean retained); + // Finish off this publish message (started with beginPublish) + // Returns 1 if the packet was sent successfully, 0 if there was an error + int endPublish(); + // Write a single byte of payload (only to be used with beginPublish/endPublish) + virtual size_t write(uint8_t); + // Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish) + // Returns the number of bytes written + virtual size_t write(const uint8_t *buffer, size_t size); + boolean subscribe(const char* topic); + boolean subscribe(const char* topic, uint8_t qos); + boolean unsubscribe(const char* topic); + boolean loop(); + boolean connected(); + int state(); + +}; + + +#endif diff --git a/libraries/PubSubClient/tests/Makefile b/libraries/PubSubClient/tests/Makefile new file mode 100644 index 00000000000..1f7163675fd --- /dev/null +++ b/libraries/PubSubClient/tests/Makefile @@ -0,0 +1,25 @@ +SRC_PATH=./src +OUT_PATH=./bin +TEST_SRC=$(wildcard ${SRC_PATH}/*_spec.cpp) +TEST_BIN= $(TEST_SRC:${SRC_PATH}/%.cpp=${OUT_PATH}/%) +VPATH=${SRC_PATH} +SHIM_FILES=${SRC_PATH}/lib/*.cpp +PSC_FILE=../src/PubSubClient.cpp +CC=g++ +CFLAGS=-I${SRC_PATH}/lib -I../src + +all: $(TEST_BIN) + +${OUT_PATH}/%: ${SRC_PATH}/%.cpp ${PSC_FILE} ${SHIM_FILES} + mkdir -p ${OUT_PATH} + ${CC} ${CFLAGS} $^ -o $@ + +clean: + @rm -rf ${OUT_PATH} + +test: + @bin/connect_spec + @bin/publish_spec + @bin/receive_spec + @bin/subscribe_spec + @bin/keepalive_spec diff --git a/libraries/PubSubClient/tests/README.md b/libraries/PubSubClient/tests/README.md new file mode 100644 index 00000000000..e5700a6d646 --- /dev/null +++ b/libraries/PubSubClient/tests/README.md @@ -0,0 +1,93 @@ +# Arduino Client for MQTT Test Suite + +This is a regression test suite for the `PubSubClient` library. + +There are two parts: + + - Tests that can be compiled and run on any machine + - Tests that build the example sketches using the Arduino IDE + + +It is a work-in-progress and is subject to complete refactoring as the whim takes +me. + + +## Local tests + +These are a set of executables that can be run to test specific areas of functionality. +They do not require a real Arduino to be attached, nor the use of the Arduino IDE. + +The tests include a set of mock files to stub out the parts of the Arduino environment the library +depends on. + +### Dependencies + + - g++ + +### Running + +Build the tests using the provided `Makefile`: + + $ make + +This will create a set of executables in `./bin/`. Run each of these executables to test the corresponding functionality. + +*Note:* the `connect_spec` and `keepalive_spec` tests involve testing keepalive timers so naturally take a few minutes to run through. + +## Arduino tests + +*Note:* INO Tool doesn't currently play nicely with Arduino 1.5. This has broken this test suite. + +Without a suitable arduino plugged in, the test suite will only check the +example sketches compile cleanly against the library. + +With an arduino plugged in, each sketch that has a corresponding python +test case is built, uploaded and then the tests run. + +### Dependencies + + - Python 2.7+ + - [INO Tool](http://inotool.org/) - this provides command-line build/upload of Arduino sketches + +### Running + +The test suite _does not_ run an MQTT server - it is assumed to be running already. + + $ python testsuite.py + +A summary of activity is printed to the console. More comprehensive logs are written +to the `logs` directory. + +### What it does + +For each sketch in the library's `examples` directory, e.g. `mqtt_basic.ino`, the suite looks for a matching test case +`testcases/mqtt_basic.py`. + +The test case must follow these conventions: + - sub-class `unittest.TestCase` + - provide the class methods `setUpClass` and `tearDownClass` (TODO: make this optional) + - all test method names begin with `test_` + +The suite will call the `setUpClass` method _before_ uploading the sketch. This +allows any test setup to be performed before the sketch runs - such as connecting +a client and subscribing to topics. + + +### Settings + +The file `testcases/settings.py` is used to config the test environment. + + - `server_ip` - the IP address of the broker the client should connect to (the broker port is assumed to be 1883). + - `arduino_ip` - the IP address the arduino should use (when not testing DHCP). + +Before each sketch is compiled, these values are automatically substituted in. To +do this, the suite looks for lines that _start_ with the following: + + byte server[] = { + byte ip[] = { + +and replaces them with the appropriate values. + + + + diff --git a/libraries/PubSubClient/tests/src/connect_spec.cpp b/libraries/PubSubClient/tests/src/connect_spec.cpp new file mode 100644 index 00000000000..e8545c49d29 --- /dev/null +++ b/libraries/PubSubClient/tests/src/connect_spec.cpp @@ -0,0 +1,336 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" + + +byte server[] = { 172, 16, 0, 2 }; + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + + +int test_connect_fails_no_network() { + IT("fails to connect if underlying client doesn't connect"); + ShimClient shimClient; + shimClient.setAllowConnect(false); + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_FALSE(rc); + int state = client.state(); + IS_TRUE(state == MQTT_CONNECT_FAILED); + END_IT +} + +int test_connect_fails_on_no_response() { + IT("fails to connect if no response received after 15 seconds"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_FALSE(rc); + int state = client.state(); + IS_TRUE(state == MQTT_CONNECTION_TIMEOUT); + END_IT +} + +int test_connect_properly_formatted() { + IT("sends a properly formatted connect packet and succeeds"); + ShimClient shimClient; + + shimClient.setAllowConnect(true); + byte expectServer[] = { 172, 16, 0, 2 }; + shimClient.expectConnect(expectServer,1883); + byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + + shimClient.expect(connect,26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int state = client.state(); + IS_TRUE(state == MQTT_DISCONNECTED); + + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + state = client.state(); + IS_TRUE(state == MQTT_CONNECTED); + + END_IT +} + +int test_connect_properly_formatted_hostname() { + IT("accepts a hostname"); + ShimClient shimClient; + + shimClient.setAllowConnect(true); + shimClient.expectConnect((char* const)"localhost",1883); + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client((char* const)"localhost", 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_connect_fails_on_bad_rc() { + IT("fails to connect if a bad return code is received"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + byte connack[] = { 0x20, 0x02, 0x00, 0x01 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_FALSE(rc); + + int state = client.state(); + IS_TRUE(state == 0x01); + + END_IT +} + +int test_connect_non_clean_session() { + IT("sends a properly formatted non-clean session connect packet and succeeds"); + ShimClient shimClient; + + shimClient.setAllowConnect(true); + byte expectServer[] = { 172, 16, 0, 2 }; + shimClient.expectConnect(expectServer,1883); + byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x0,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + + shimClient.expect(connect,26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int state = client.state(); + IS_TRUE(state == MQTT_DISCONNECTED); + + int rc = client.connect((char*)"client_test1",0,0,0,0,0,0,0); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + state = client.state(); + IS_TRUE(state == MQTT_CONNECTED); + + END_IT +} + +int test_connect_accepts_username_password() { + IT("accepts a username and password"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = { 0x10,0x24,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x4,0x70,0x61,0x73,0x73}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"pass"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_accepts_username_no_password() { + IT("accepts a username but no password"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = { 0x10,0x1e,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x82,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x20); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"user",0); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} +int test_connect_accepts_username_blank_password() { + IT("accepts a username and blank password"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = { 0x10,0x20,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x0}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"pass"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_ignores_password_no_username() { + IT("ignores a password but no username"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",0,(char*)"pass"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_with_will() { + IT("accepts a will"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = {0x10,0x30,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xe,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x32); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"willTopic",1,0,(char*)"willMessage"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_with_will_username_password() { + IT("accepts a will, username and password"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connect[] = {0x10,0x40,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xce,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x8,0x70,0x61,0x73,0x73,0x77,0x6f,0x72,0x64}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.expect(connect,0x42); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"password",(char*)"willTopic",1,0,(char*)"willMessage"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_connect_disconnect_connect() { + IT("connects, disconnects and connects again"); + ShimClient shimClient; + + shimClient.setAllowConnect(true); + byte expectServer[] = { 172, 16, 0, 2 }; + shimClient.expectConnect(expectServer,1883); + byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + + shimClient.expect(connect,26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + + int state = client.state(); + IS_TRUE(state == MQTT_DISCONNECTED); + + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + state = client.state(); + IS_TRUE(state == MQTT_CONNECTED); + + byte disconnect[] = {0xE0,0x00}; + shimClient.expect(disconnect,2); + + client.disconnect(); + + IS_FALSE(client.connected()); + IS_FALSE(shimClient.connected()); + IS_FALSE(shimClient.error()); + + state = client.state(); + IS_TRUE(state == MQTT_DISCONNECTED); + + shimClient.expect(connect,28); + shimClient.respond(connack,4); + rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + state = client.state(); + IS_TRUE(state == MQTT_CONNECTED); + + END_IT +} + +int test_connect_custom_keepalive() { + IT("sends a properly formatted connect packet with custom keepalive value"); + ShimClient shimClient; + + shimClient.setAllowConnect(true); + byte expectServer[] = { 172, 16, 0, 2 }; + shimClient.expectConnect(expectServer,1883); + + // Set keepalive to 300secs == 0x01 0x2c + byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x01,0x2c,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + + shimClient.expect(connect,26); + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int state = client.state(); + IS_TRUE(state == MQTT_DISCONNECTED); + + client.setKeepAlive(300); + + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + + state = client.state(); + IS_TRUE(state == MQTT_CONNECTED); + + END_IT +} + + +int main() +{ + SUITE("Connect"); + + test_connect_fails_no_network(); + test_connect_fails_on_no_response(); + + test_connect_properly_formatted(); + test_connect_non_clean_session(); + test_connect_accepts_username_password(); + test_connect_fails_on_bad_rc(); + test_connect_properly_formatted_hostname(); + + test_connect_accepts_username_no_password(); + test_connect_ignores_password_no_username(); + test_connect_with_will(); + test_connect_with_will_username_password(); + test_connect_disconnect_connect(); + + test_connect_custom_keepalive(); + FINISH +} diff --git a/libraries/PubSubClient/tests/src/keepalive_spec.cpp b/libraries/PubSubClient/tests/src/keepalive_spec.cpp new file mode 100644 index 00000000000..ea643cf17b2 --- /dev/null +++ b/libraries/PubSubClient/tests/src/keepalive_spec.cpp @@ -0,0 +1,185 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" +#include + +byte server[] = { 172, 16, 0, 2 }; + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + + +int test_keepalive_pings_idle() { + IT("keeps an idle connection alive (takes 1 minute)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte pingreq[] = { 0xC0,0x0 }; + shimClient.expect(pingreq,2); + byte pingresp[] = { 0xD0,0x0 }; + shimClient.respond(pingresp,2); + + for (int i = 0; i < 50; i++) { + sleep(1); + if ( i == 15 || i == 31 || i == 47) { + shimClient.expect(pingreq,2); + shimClient.respond(pingresp,2); + } + rc = client.loop(); + IS_TRUE(rc); + } + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_keepalive_pings_with_outbound_qos0() { + IT("keeps a connection alive that only sends qos0 (takes 1 minute)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + + for (int i = 0; i < 50; i++) { + TRACE(i<<":"); + shimClient.expect(publish,16); + rc = client.publish((char*)"topic",(char*)"payload"); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + sleep(1); + if ( i == 15 || i == 31 || i == 47) { + byte pingreq[] = { 0xC0,0x0 }; + shimClient.expect(pingreq,2); + byte pingresp[] = { 0xD0,0x0 }; + shimClient.respond(pingresp,2); + } + rc = client.loop(); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + } + + END_IT +} + +int test_keepalive_pings_with_inbound_qos0() { + IT("keeps a connection alive that only receives qos0 (takes 1 minute)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + + for (int i = 0; i < 50; i++) { + TRACE(i<<":"); + sleep(1); + if ( i == 15 || i == 31 || i == 47) { + byte pingreq[] = { 0xC0,0x0 }; + shimClient.expect(pingreq,2); + byte pingresp[] = { 0xD0,0x0 }; + shimClient.respond(pingresp,2); + } + shimClient.respond(publish,16); + rc = client.loop(); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + } + + END_IT +} + +int test_keepalive_no_pings_inbound_qos1() { + IT("does not send pings for connections with inbound qos1 (takes 1 minute)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + byte puback[] = {0x40,0x2,0x12,0x34}; + + for (int i = 0; i < 50; i++) { + shimClient.respond(publish,18); + shimClient.expect(puback,4); + sleep(1); + rc = client.loop(); + IS_TRUE(rc); + IS_FALSE(shimClient.error()); + } + + END_IT +} + +int test_keepalive_disconnects_hung() { + IT("disconnects a hung connection (takes 30 seconds)"); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte pingreq[] = { 0xC0,0x0 }; + shimClient.expect(pingreq,2); + + for (int i = 0; i < 32; i++) { + sleep(1); + rc = client.loop(); + } + IS_FALSE(rc); + + int state = client.state(); + IS_TRUE(state == MQTT_CONNECTION_TIMEOUT); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int main() +{ + SUITE("Keep-alive"); + test_keepalive_pings_idle(); + test_keepalive_pings_with_outbound_qos0(); + test_keepalive_pings_with_inbound_qos0(); + test_keepalive_no_pings_inbound_qos1(); + test_keepalive_disconnects_hung(); + + FINISH +} diff --git a/libraries/PubSubClient/tests/src/lib/Arduino.h b/libraries/PubSubClient/tests/src/lib/Arduino.h new file mode 100644 index 00000000000..2a00f24bceb --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/Arduino.h @@ -0,0 +1,26 @@ +#ifndef Arduino_h +#define Arduino_h + +#include +#include +#include +#include +#include "Print.h" + + +extern "C"{ + typedef uint8_t byte ; + typedef uint8_t boolean ; + + /* sketch */ + extern void setup( void ) ; + extern void loop( void ) ; + uint32_t millis( void ); +} + +#define PROGMEM +#define pgm_read_byte_near(x) *(x) + +#define yield(x) {} + +#endif // Arduino_h diff --git a/libraries/PubSubClient/tests/src/lib/BDDTest.cpp b/libraries/PubSubClient/tests/src/lib/BDDTest.cpp new file mode 100644 index 00000000000..a72bf65e23f --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/BDDTest.cpp @@ -0,0 +1,50 @@ +#include "BDDTest.h" +#include "trace.h" +#include +#include +#include +#include + +int testCount = 0; +int testPasses = 0; +const char* testDescription; + +std::list failureList; + +void bddtest_suite(const char* name) { + LOG(name << "\n"); +} + +int bddtest_test(const char* file, int line, const char* assertion, int result) { + if (!result) { + LOG("✗\n"); + std::ostringstream os; + os << " ! "<::iterator it = failureList.begin(); it != failureList.end(); it++) { + LOG("\n"); + LOG(*it); + LOG("\n"); + } + + LOG(std::dec << testPasses << "/" << testCount << " tests passed\n\n"); + if (testPasses == testCount) { + return 0; + } + return 1; +} diff --git a/libraries/PubSubClient/tests/src/lib/BDDTest.h b/libraries/PubSubClient/tests/src/lib/BDDTest.h new file mode 100644 index 00000000000..1197fdd4244 --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/BDDTest.h @@ -0,0 +1,23 @@ +#ifndef bddtest_h +#define bddtest_h + +void bddtest_suite(const char* name); +int bddtest_test(const char*, int, const char*, int); +void bddtest_start(const char*); +void bddtest_end(); +int bddtest_summary(); + +#define SUITE(x) { bddtest_suite(x); } +#define TEST(x) { if (!bddtest_test(__FILE__, __LINE__, #x, (x))) return false; } + +#define IT(x) { bddtest_start(x); } +#define END_IT { bddtest_end();return true;} + +#define FINISH { return bddtest_summary(); } + +#define IS_TRUE(x) TEST(x) +#define IS_FALSE(x) TEST(!(x)) +#define IS_EQUAL(x,y) TEST(x==y) +#define IS_NOT_EQUAL(x,y) TEST(x!=y) + +#endif diff --git a/libraries/PubSubClient/tests/src/lib/Buffer.cpp b/libraries/PubSubClient/tests/src/lib/Buffer.cpp new file mode 100644 index 00000000000..f07759a3a52 --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/Buffer.cpp @@ -0,0 +1,34 @@ +#include "Buffer.h" +#include "Arduino.h" + +Buffer::Buffer() { + this->pos = 0; + this->length = 0; +} + +Buffer::Buffer(uint8_t* buf, size_t size) { + this->pos = 0; + this->length = 0; + this->add(buf,size); +} +bool Buffer::available() { + return this->pos < this->length; +} + +uint8_t Buffer::next() { + if (this->available()) { + return this->buffer[this->pos++]; + } + return 0; +} + +void Buffer::reset() { + this->pos = 0; +} + +void Buffer::add(uint8_t* buf, size_t size) { + uint16_t i = 0; + for (;ibuffer[this->length++] = buf[i]; + } +} diff --git a/libraries/PubSubClient/tests/src/lib/Buffer.h b/libraries/PubSubClient/tests/src/lib/Buffer.h new file mode 100644 index 00000000000..c6a2cb5847f --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/Buffer.h @@ -0,0 +1,23 @@ +#ifndef buffer_h +#define buffer_h + +#include "Arduino.h" + +class Buffer { +private: + uint8_t buffer[2048]; + uint16_t pos; + uint16_t length; + +public: + Buffer(); + Buffer(uint8_t* buf, size_t size); + + virtual bool available(); + virtual uint8_t next(); + virtual void reset(); + + virtual void add(uint8_t* buf, size_t size); +}; + +#endif diff --git a/libraries/PubSubClient/tests/src/lib/Client.h b/libraries/PubSubClient/tests/src/lib/Client.h new file mode 100644 index 00000000000..9e18c07648b --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/Client.h @@ -0,0 +1,21 @@ +#ifndef client_h +#define client_h +#include "IPAddress.h" + +class Client { +public: + virtual int connect(IPAddress ip, uint16_t port) =0; + virtual int connect(const char *host, uint16_t port) =0; + virtual size_t write(uint8_t) =0; + virtual size_t write(const uint8_t *buf, size_t size) =0; + virtual int available() = 0; + virtual int read() = 0; + virtual int read(uint8_t *buf, size_t size) = 0; + virtual int peek() = 0; + virtual void flush() = 0; + virtual void stop() = 0; + virtual uint8_t connected() = 0; + virtual operator bool() = 0; +}; + +#endif diff --git a/libraries/PubSubClient/tests/src/lib/IPAddress.cpp b/libraries/PubSubClient/tests/src/lib/IPAddress.cpp new file mode 100644 index 00000000000..610ff4c5344 --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/IPAddress.cpp @@ -0,0 +1,44 @@ + +#include +#include + +IPAddress::IPAddress() +{ + memset(_address, 0, sizeof(_address)); +} + +IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) +{ + _address[0] = first_octet; + _address[1] = second_octet; + _address[2] = third_octet; + _address[3] = fourth_octet; +} + +IPAddress::IPAddress(uint32_t address) +{ + memcpy(_address, &address, sizeof(_address)); +} + +IPAddress::IPAddress(const uint8_t *address) +{ + memcpy(_address, address, sizeof(_address)); +} + +IPAddress& IPAddress::operator=(const uint8_t *address) +{ + memcpy(_address, address, sizeof(_address)); + return *this; +} + +IPAddress& IPAddress::operator=(uint32_t address) +{ + memcpy(_address, (const uint8_t *)&address, sizeof(_address)); + return *this; +} + +bool IPAddress::operator==(const uint8_t* addr) +{ + return memcmp(addr, _address, sizeof(_address)) == 0; +} + diff --git a/libraries/PubSubClient/tests/src/lib/IPAddress.h b/libraries/PubSubClient/tests/src/lib/IPAddress.h new file mode 100644 index 00000000000..e75a8fe65d5 --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/IPAddress.h @@ -0,0 +1,72 @@ +/* + * + * MIT License: + * Copyright (c) 2011 Adrian McEwen + * 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. + * + * adrianm@mcqn.com 1/1/2011 + */ + +#ifndef IPAddress_h +#define IPAddress_h + + +// A class to make it easier to handle and pass around IP addresses + +class IPAddress { +private: + uint8_t _address[4]; // IPv4 address + // Access the raw byte array containing the address. Because this returns a pointer + // to the internal structure rather than a copy of the address this function should only + // be used when you know that the usage of the returned uint8_t* will be transient and not + // stored. + uint8_t* raw_address() { return _address; }; + +public: + // Constructors + IPAddress(); + IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); + IPAddress(uint32_t address); + IPAddress(const uint8_t *address); + + // Overloaded cast operator to allow IPAddress objects to be used where a pointer + // to a four-byte uint8_t array is expected + operator uint32_t() { return *((uint32_t*)_address); }; + bool operator==(const IPAddress& addr) { return (*((uint32_t*)_address)) == (*((uint32_t*)addr._address)); }; + bool operator==(const uint8_t* addr); + + // Overloaded index operator to allow getting and setting individual octets of the address + uint8_t operator[](int index) const { return _address[index]; }; + uint8_t& operator[](int index) { return _address[index]; }; + + // Overloaded copy operators to allow initialisation of IPAddress objects from other types + IPAddress& operator=(const uint8_t *address); + IPAddress& operator=(uint32_t address); + + + friend class EthernetClass; + friend class UDP; + friend class Client; + friend class Server; + friend class DhcpClass; + friend class DNSClient; +}; + + +#endif diff --git a/libraries/PubSubClient/tests/src/lib/Print.h b/libraries/PubSubClient/tests/src/lib/Print.h new file mode 100644 index 00000000000..02ef77c2cb7 --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/Print.h @@ -0,0 +1,28 @@ +/* + Print.h - Base class that provides print() and println() + Copyright (c) 2008 David A. Mellis. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef Print_h +#define Print_h + +class Print { + public: + virtual size_t write(uint8_t) = 0; +}; + +#endif diff --git a/libraries/PubSubClient/tests/src/lib/ShimClient.cpp b/libraries/PubSubClient/tests/src/lib/ShimClient.cpp new file mode 100644 index 00000000000..f70115fa847 --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/ShimClient.cpp @@ -0,0 +1,153 @@ +#include "ShimClient.h" +#include "trace.h" +#include +#include +#include + +extern "C" { + uint32_t millis(void) { + return time(0)*1000; + } +} + +ShimClient::ShimClient() { + this->responseBuffer = new Buffer(); + this->expectBuffer = new Buffer(); + this->_allowConnect = true; + this->_connected = false; + this->_error = false; + this->expectAnything = true; + this->_received = 0; + this->_expectedPort = 0; +} + +int ShimClient::connect(IPAddress ip, uint16_t port) { + if (this->_allowConnect) { + this->_connected = true; + } + if (this->_expectedPort !=0) { + // if (memcmp(ip,this->_expectedIP,4) != 0) { + // TRACE( "ip mismatch\n"); + // this->_error = true; + // } + if (port != this->_expectedPort) { + TRACE( "port mismatch\n"); + this->_error = true; + } + } + return this->_connected; +} +int ShimClient::connect(const char *host, uint16_t port) { + if (this->_allowConnect) { + this->_connected = true; + } + if (this->_expectedPort !=0) { + if (strcmp(host,this->_expectedHost) != 0) { + TRACE( "host mismatch\n"); + this->_error = true; + } + if (port != this->_expectedPort) { + TRACE( "port mismatch\n"); + this->_error = true; + } + + } + return this->_connected; +} +size_t ShimClient::write(uint8_t b) { + this->_received += 1; + TRACE(std::hex << (unsigned int)b); + if (!this->expectAnything) { + if (this->expectBuffer->available()) { + uint8_t expected = this->expectBuffer->next(); + if (expected != b) { + this->_error = true; + TRACE("!=" << (unsigned int)expected); + } + } else { + this->_error = true; + } + } + TRACE("\n"<< std::dec); + return 1; +} +size_t ShimClient::write(const uint8_t *buf, size_t size) { + this->_received += size; + TRACE( "[" << std::dec << (unsigned int)(size) << "] "); + uint16_t i=0; + for (;i0) { + TRACE(":"); + } + TRACE(std::hex << (unsigned int)(buf[i])); + + if (!this->expectAnything) { + if (this->expectBuffer->available()) { + uint8_t expected = this->expectBuffer->next(); + if (expected != buf[i]) { + this->_error = true; + TRACE("!=" << (unsigned int)expected); + } + } else { + this->_error = true; + } + } + } + TRACE("\n"<responseBuffer->available(); +} +int ShimClient::read() { return this->responseBuffer->next(); } +int ShimClient::read(uint8_t *buf, size_t size) { + uint16_t i = 0; + for (;iread(); + } + return size; +} +int ShimClient::peek() { return 0; } +void ShimClient::flush() {} +void ShimClient::stop() { + this->setConnected(false); +} +uint8_t ShimClient::connected() { return this->_connected; } +ShimClient::operator bool() { return true; } + + +ShimClient* ShimClient::respond(uint8_t *buf, size_t size) { + this->responseBuffer->add(buf,size); + return this; +} + +ShimClient* ShimClient::expect(uint8_t *buf, size_t size) { + this->expectAnything = false; + this->expectBuffer->add(buf,size); + return this; +} + +void ShimClient::setConnected(bool b) { + this->_connected = b; +} +void ShimClient::setAllowConnect(bool b) { + this->_allowConnect = b; +} + +bool ShimClient::error() { + return this->_error; +} + +uint16_t ShimClient::received() { + return this->_received; +} + +void ShimClient::expectConnect(IPAddress ip, uint16_t port) { + this->_expectedIP = ip; + this->_expectedPort = port; +} + +void ShimClient::expectConnect(const char *host, uint16_t port) { + this->_expectedHost = host; + this->_expectedPort = port; +} diff --git a/libraries/PubSubClient/tests/src/lib/ShimClient.h b/libraries/PubSubClient/tests/src/lib/ShimClient.h new file mode 100644 index 00000000000..2e3f874fc84 --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/ShimClient.h @@ -0,0 +1,51 @@ +#ifndef shimclient_h +#define shimclient_h + +#include "Arduino.h" +#include "Client.h" +#include "IPAddress.h" +#include "Buffer.h" + + +class ShimClient : public Client { +private: + Buffer* responseBuffer; + Buffer* expectBuffer; + bool _allowConnect; + bool _connected; + bool expectAnything; + bool _error; + uint16_t _received; + IPAddress _expectedIP; + uint16_t _expectedPort; + const char* _expectedHost; + +public: + ShimClient(); + virtual int connect(IPAddress ip, uint16_t port); + virtual int connect(const char *host, uint16_t port); + virtual size_t write(uint8_t); + virtual size_t write(const uint8_t *buf, size_t size); + virtual int available(); + virtual int read(); + virtual int read(uint8_t *buf, size_t size); + virtual int peek(); + virtual void flush(); + virtual void stop(); + virtual uint8_t connected(); + virtual operator bool(); + + virtual ShimClient* respond(uint8_t *buf, size_t size); + virtual ShimClient* expect(uint8_t *buf, size_t size); + + virtual void expectConnect(IPAddress ip, uint16_t port); + virtual void expectConnect(const char *host, uint16_t port); + + virtual uint16_t received(); + virtual bool error(); + + virtual void setAllowConnect(bool b); + virtual void setConnected(bool b); +}; + +#endif diff --git a/libraries/PubSubClient/tests/src/lib/Stream.cpp b/libraries/PubSubClient/tests/src/lib/Stream.cpp new file mode 100644 index 00000000000..b0ecbb44e7b --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/Stream.cpp @@ -0,0 +1,39 @@ +#include "Stream.h" +#include "trace.h" +#include +#include + +Stream::Stream() { + this->expectBuffer = new Buffer(); + this->_error = false; + this->_written = 0; +} + +size_t Stream::write(uint8_t b) { + this->_written++; + TRACE(std::hex << (unsigned int)b); + if (this->expectBuffer->available()) { + uint8_t expected = this->expectBuffer->next(); + if (expected != b) { + this->_error = true; + TRACE("!=" << (unsigned int)expected); + } + } else { + this->_error = true; + } + TRACE("\n"<< std::dec); + return 1; +} + + +bool Stream::error() { + return this->_error; +} + +void Stream::expect(uint8_t *buf, size_t size) { + this->expectBuffer->add(buf,size); +} + +uint16_t Stream::length() { + return this->_written; +} diff --git a/libraries/PubSubClient/tests/src/lib/Stream.h b/libraries/PubSubClient/tests/src/lib/Stream.h new file mode 100644 index 00000000000..4e41f86fa07 --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/Stream.h @@ -0,0 +1,22 @@ +#ifndef Stream_h +#define Stream_h + +#include "Arduino.h" +#include "Buffer.h" + +class Stream { +private: + Buffer* expectBuffer; + bool _error; + uint16_t _written; + +public: + Stream(); + virtual size_t write(uint8_t); + + virtual bool error(); + virtual void expect(uint8_t *buf, size_t size); + virtual uint16_t length(); +}; + +#endif diff --git a/libraries/PubSubClient/tests/src/lib/trace.h b/libraries/PubSubClient/tests/src/lib/trace.h new file mode 100644 index 00000000000..42eb99104ab --- /dev/null +++ b/libraries/PubSubClient/tests/src/lib/trace.h @@ -0,0 +1,10 @@ +#ifndef trace_h +#define trace_h +#include + +#include + +#define LOG(x) {std::cout << x << std::flush; } +#define TRACE(x) {if (getenv("TRACE")) { std::cout << x << std::flush; }} + +#endif diff --git a/libraries/PubSubClient/tests/src/publish_spec.cpp b/libraries/PubSubClient/tests/src/publish_spec.cpp new file mode 100644 index 00000000000..ee3d3bedbeb --- /dev/null +++ b/libraries/PubSubClient/tests/src/publish_spec.cpp @@ -0,0 +1,191 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" + + +byte server[] = { 172, 16, 0, 2 }; + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +int test_publish() { + IT("publishes a null-terminated string"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.expect(publish,16); + + rc = client.publish((char*)"topic",(char*)"payload"); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_publish_bytes() { + IT("publishes a byte array"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte payload[] = { 0x01,0x02,0x03,0x0,0x05 }; + int length = 5; + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5}; + shimClient.expect(publish,14); + + rc = client.publish((char*)"topic",payload,length); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_publish_retained() { + IT("publishes retained - 1"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte payload[] = { 0x01,0x02,0x03,0x0,0x05 }; + int length = 5; + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5}; + shimClient.expect(publish,14); + + rc = client.publish((char*)"topic",payload,length,true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_retained_2() { + IT("publishes retained - 2"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,'A','B','C','D','E'}; + shimClient.expect(publish,14); + + rc = client.publish((char*)"topic",(char*)"ABCDE",true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_not_connected() { + IT("publish fails when not connected"); + ShimClient shimClient; + + PubSubClient client(server, 1883, callback, shimClient); + + int rc = client.publish((char*)"topic",(char*)"payload"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_too_long() { + IT("publish fails when topic/payload are too long"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + client.setBufferSize(128); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + rc = client.publish((char*)"topic",(char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_publish_P() { + IT("publishes using PROGMEM"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte payload[] = { 0x01,0x02,0x03,0x0,0x05 }; + int length = 5; + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5}; + shimClient.expect(publish,14); + + rc = client.publish_P((char*)"topic",payload,length,true); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + + + + +int main() +{ + SUITE("Publish"); + test_publish(); + test_publish_bytes(); + test_publish_retained(); + test_publish_retained_2(); + test_publish_not_connected(); + test_publish_too_long(); + test_publish_P(); + + FINISH +} diff --git a/libraries/PubSubClient/tests/src/receive_spec.cpp b/libraries/PubSubClient/tests/src/receive_spec.cpp new file mode 100644 index 00000000000..93e909aebc2 --- /dev/null +++ b/libraries/PubSubClient/tests/src/receive_spec.cpp @@ -0,0 +1,340 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" + + +byte server[] = { 172, 16, 0, 2 }; + +bool callback_called = false; +char lastTopic[1024]; +char lastPayload[1024]; +unsigned int lastLength; + +void reset_callback() { + callback_called = false; + lastTopic[0] = '\0'; + lastPayload[0] = '\0'; + lastLength = 0; +} + +void callback(char* topic, byte* payload, unsigned int length) { + TRACE("Callback received topic=[" << topic << "] length=" << length << "\n") + callback_called = true; + strcpy(lastTopic,topic); + memcpy(lastPayload,payload,length); + lastLength = length; +} + +int test_receive_callback() { + IT("receives a callback message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.respond(publish,16); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(memcmp(lastPayload,"payload",7)==0); + IS_TRUE(lastLength == 7); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_receive_stream() { + IT("receives a streamed callback message"); + reset_callback(); + + Stream stream; + stream.expect((uint8_t*)"payload",7); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient, stream); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.respond(publish,16); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(lastLength == 7); + + IS_FALSE(stream.error()); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_receive_max_sized_message() { + IT("receives an max-sized message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int length = 80; // If this is changed to > 128 then the publish packet below + // is no longer valid as it assumes the remaining length + // is a single-byte. Don't make that mistake like I just + // did and lose a whole evening tracking down the issue. + client.setBufferSize(length); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + + byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + byte bigPublish[length]; + memset(bigPublish,'A',length); + bigPublish[length] = 'B'; + memcpy(bigPublish,publish,16); + shimClient.respond(bigPublish,length); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(lastLength == length-9); + IS_TRUE(memcmp(lastPayload,bigPublish+9,lastLength)==0); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_receive_oversized_message() { + IT("drops an oversized message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + int length = 80; // See comment in test_receive_max_sized_message before changing this value + + PubSubClient client(server, 1883, callback, shimClient); + client.setBufferSize(length-1); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + byte bigPublish[length]; + memset(bigPublish,'A',length); + bigPublish[length] = 'B'; + memcpy(bigPublish,publish,16); + shimClient.respond(bigPublish,length); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_FALSE(callback_called); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_drop_invalid_remaining_length_message() { + IT("drops invalid remaining length message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,0x92,0x92,0x92,0x92,0x01,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.respond(publish,20); + + rc = client.loop(); + + IS_FALSE(rc); + + IS_FALSE(callback_called); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_resize_buffer() { + IT("receives a message larger than the default maximum"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + int length = 80; // See comment in test_receive_max_sized_message before changing this value + + PubSubClient client(server, 1883, callback, shimClient); + client.setBufferSize(length-1); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + byte bigPublish[length]; + memset(bigPublish,'A',length); + bigPublish[length] = 'B'; + memcpy(bigPublish,publish,16); + // Send it twice + shimClient.respond(bigPublish,length); + shimClient.respond(bigPublish,length); + + rc = client.loop(); + IS_TRUE(rc); + + // First message fails as it is too big + IS_FALSE(callback_called); + + // Resize the buffer + client.setBufferSize(length); + + rc = client.loop(); + IS_TRUE(rc); + + IS_TRUE(callback_called); + + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(lastLength == length-9); + IS_TRUE(memcmp(lastPayload,bigPublish+9,lastLength)==0); + + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_receive_oversized_stream_message() { + IT("receive an oversized streamed message"); + reset_callback(); + + Stream stream; + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + int length = 80; // See comment in test_receive_max_sized_message before changing this value + + PubSubClient client(server, 1883, callback, shimClient, stream); + client.setBufferSize(length-1); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + + byte bigPublish[length]; + memset(bigPublish,'A',length); + bigPublish[length] = 'B'; + memcpy(bigPublish,publish,16); + + shimClient.respond(bigPublish,length); + stream.expect(bigPublish+9,length-9); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + + IS_TRUE(lastLength == length-10); + + IS_FALSE(stream.error()); + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_receive_qos1() { + IT("receives a qos1 message"); + reset_callback(); + + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; + shimClient.respond(publish,18); + + byte puback[] = {0x40,0x2,0x12,0x34}; + shimClient.expect(puback,4); + + rc = client.loop(); + + IS_TRUE(rc); + + IS_TRUE(callback_called); + IS_TRUE(strcmp(lastTopic,"topic")==0); + IS_TRUE(memcmp(lastPayload,"payload",7)==0); + IS_TRUE(lastLength == 7); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int main() +{ + SUITE("Receive"); + test_receive_callback(); + test_receive_stream(); + test_receive_max_sized_message(); + test_drop_invalid_remaining_length_message(); + test_receive_oversized_message(); + test_resize_buffer(); + test_receive_oversized_stream_message(); + test_receive_qos1(); + + FINISH +} diff --git a/libraries/PubSubClient/tests/src/subscribe_spec.cpp b/libraries/PubSubClient/tests/src/subscribe_spec.cpp new file mode 100644 index 00000000000..22dc8a4438d --- /dev/null +++ b/libraries/PubSubClient/tests/src/subscribe_spec.cpp @@ -0,0 +1,178 @@ +#include "PubSubClient.h" +#include "ShimClient.h" +#include "Buffer.h" +#include "BDDTest.h" +#include "trace.h" + + +byte server[] = { 172, 16, 0, 2 }; + +void callback(char* topic, byte* payload, unsigned int length) { + // handle message arrived +} + +int test_subscribe_no_qos() { + IT("subscribe without qos defaults to 0"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x0 }; + shimClient.expect(subscribe,12); + byte suback[] = { 0x90,0x3,0x0,0x2,0x0 }; + shimClient.respond(suback,5); + + rc = client.subscribe((char*)"topic"); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_qos_1() { + IT("subscribes qos 1"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1 }; + shimClient.expect(subscribe,12); + byte suback[] = { 0x90,0x3,0x0,0x2,0x1 }; + shimClient.respond(suback,5); + + rc = client.subscribe((char*)"topic",1); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_not_connected() { + IT("subscribe fails when not connected"); + ShimClient shimClient; + + PubSubClient client(server, 1883, callback, shimClient); + + int rc = client.subscribe((char*)"topic"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_invalid_qos() { + IT("subscribe fails with invalid qos values"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + rc = client.subscribe((char*)"topic",2); + IS_FALSE(rc); + rc = client.subscribe((char*)"topic",254); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_subscribe_too_long() { + IT("subscribe fails with too long topic"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + client.setBufferSize(128); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + // max length should be allowed + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + rc = client.subscribe((char*)"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); + IS_TRUE(rc); + + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + rc = client.subscribe((char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + + +int test_unsubscribe() { + IT("unsubscribes"); + ShimClient shimClient; + shimClient.setAllowConnect(true); + + byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; + shimClient.respond(connack,4); + + PubSubClient client(server, 1883, callback, shimClient); + int rc = client.connect((char*)"client_test1"); + IS_TRUE(rc); + + byte unsubscribe[] = { 0xA2,0x9,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63 }; + shimClient.expect(unsubscribe,12); + byte unsuback[] = { 0xB0,0x2,0x0,0x2 }; + shimClient.respond(unsuback,4); + + rc = client.unsubscribe((char*)"topic"); + IS_TRUE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int test_unsubscribe_not_connected() { + IT("unsubscribe fails when not connected"); + ShimClient shimClient; + + PubSubClient client(server, 1883, callback, shimClient); + + int rc = client.unsubscribe((char*)"topic"); + IS_FALSE(rc); + + IS_FALSE(shimClient.error()); + + END_IT +} + +int main() +{ + SUITE("Subscribe"); + test_subscribe_no_qos(); + test_subscribe_qos_1(); + test_subscribe_not_connected(); + test_subscribe_invalid_qos(); + test_subscribe_too_long(); + test_unsubscribe(); + test_unsubscribe_not_connected(); + FINISH +} diff --git a/libraries/PubSubClient/tests/testcases/mqtt_basic.py b/libraries/PubSubClient/tests/testcases/mqtt_basic.py new file mode 100644 index 00000000000..f23ef71c13e --- /dev/null +++ b/libraries/PubSubClient/tests/testcases/mqtt_basic.py @@ -0,0 +1,39 @@ +import unittest +import settings +import time +import mosquitto + + +def on_message(mosq, obj, msg): + obj.message_queue.append(msg) + + +class mqtt_basic(unittest.TestCase): + + message_queue = [] + + @classmethod + def setUpClass(self): + self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self) + self.client.connect(settings.server_ip) + self.client.on_message = on_message + self.client.subscribe("outTopic", 0) + + @classmethod + def tearDownClass(self): + self.client.disconnect() + + def test_one(self): + i = 30 + while len(self.message_queue) == 0 and i > 0: + self.client.loop() + time.sleep(0.5) + i -= 1 + self.assertTrue(i > 0, "message receive timed-out") + self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") + msg = self.message_queue[0] + self.assertEqual(msg.mid, 0, "message id not 0") + self.assertEqual(msg.topic, "outTopic", "message topic incorrect") + self.assertEqual(msg.payload, "hello world") + self.assertEqual(msg.qos, 0, "message qos not 0") + self.assertEqual(msg.retain, False, "message retain flag incorrect") diff --git a/libraries/PubSubClient/tests/testcases/mqtt_publish_in_callback.py b/libraries/PubSubClient/tests/testcases/mqtt_publish_in_callback.py new file mode 100644 index 00000000000..45b0a851552 --- /dev/null +++ b/libraries/PubSubClient/tests/testcases/mqtt_publish_in_callback.py @@ -0,0 +1,59 @@ +import unittest +import settings +import time +import mosquitto + + +def on_message(mosq, obj, msg): + obj.message_queue.append(msg) + + +class mqtt_publish_in_callback(unittest.TestCase): + + message_queue = [] + + @classmethod + def setUpClass(self): + self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self) + self.client.connect(settings.server_ip) + self.client.on_message = on_message + self.client.subscribe("outTopic", 0) + + @classmethod + def tearDownClass(self): + self.client.disconnect() + + def test_connect(self): + i = 30 + while len(self.message_queue) == 0 and i > 0: + self.client.loop() + time.sleep(0.5) + i -= 1 + self.assertTrue(i > 0, "message receive timed-out") + self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") + msg = self.message_queue.pop(0) + self.assertEqual(msg.mid, 0, "message id not 0") + self.assertEqual(msg.topic, "outTopic", "message topic incorrect") + self.assertEqual(msg.payload, "hello world") + self.assertEqual(msg.qos, 0, "message qos not 0") + self.assertEqual(msg.retain, False, "message retain flag incorrect") + + def test_publish(self): + self.assertEqual(len(self.message_queue), 0, "message queue not empty") + payload = "abcdefghij" + self.client.publish("inTopic", payload) + + i = 30 + while len(self.message_queue) == 0 and i > 0: + self.client.loop() + time.sleep(0.5) + i -= 1 + + self.assertTrue(i > 0, "message receive timed-out") + self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") + msg = self.message_queue.pop(0) + self.assertEqual(msg.mid, 0, "message id not 0") + self.assertEqual(msg.topic, "outTopic", "message topic incorrect") + self.assertEqual(msg.payload, payload) + self.assertEqual(msg.qos, 0, "message qos not 0") + self.assertEqual(msg.retain, False, "message retain flag incorrect") diff --git a/libraries/PubSubClient/tests/testcases/settings.py b/libraries/PubSubClient/tests/testcases/settings.py new file mode 100644 index 00000000000..4ad8719d85f --- /dev/null +++ b/libraries/PubSubClient/tests/testcases/settings.py @@ -0,0 +1,2 @@ +server_ip = "172.16.0.2" +arduino_ip = "172.16.0.100" diff --git a/package.json b/package.json index 1a7f443f58a..cc3d1980d43 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,270 @@ { - "name": "framework-arduinoespressif32", - "description": "Arduino Wiring-based Framework (ESP32 Core)", - "version": "0.0.0", - "url": "https://github.com/espressif/arduino-esp32" -} \ No newline at end of file + "packages": [ + { + "maintainer": "elzershark", + "help": { + "online": "https://www.elzershark.com" + }, + "websiteURL": "https://github.com/elzershark", + "platforms": [ + { + "category": "ESP32", + "name": "elzershark", + "url": "https://github.com/elzershark/arduino-esp32/releases/download/1.0.4a/esp32-1.0.4a.zip", + "checksum": "SHA-256:96B9FB8F4252FCA1D604FBF9DEC13B6331C3D26D53DC0644BA1F1A3DE432E147", + "help": { + "online": "" + }, + "version": "1.0.4", + "architecture": "esp32", + "archiveFileName": "esp32-1.0.4a.zip", + "boards": [ + { + "name": "ESP32 Dev Module" + }, + { + "name": "WEMOS LoLin32" + }, + { + "name": "WEMOS D1 MINI ESP32" + } + ], + "toolsDependencies": [ + { + "packager": "esp32", + "version": "1.22.0-80-g6c4433a-5.2.0", + "name": "xtensa-esp32-elf-gcc" + }, + { + "packager": "esp32", + "version": "2.6.1", + "name": "esptool_py" + }, + { + "packager": "esp32", + "version": "0.2.3", + "name": "mkspiffs" + } + ], + "size": "36061734" + } + ], + "tools": [ + { + "version": "2.6.1", + "name": "esptool_py", + "systems": [ + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-windows.zip", + "checksum": "SHA-256:84cf0b369a7707fe566434faba148852fc464992111d5baa95b658b374802f96", + "host": "i686-mingw32", + "archiveFileName": "esptool-2.6.1-windows.zip", + "size": "3422445" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-macos.tar.gz", + "checksum": "SHA-256:f4eb758a301d6902cc9dfcd49d36345d2f075ad123da7cf8132d15cfb7533457", + "host": "x86_64-apple-darwin", + "archiveFileName": "esptool-2.6.1-macos.tar.gz", + "size": "3837085" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-linux.tar.gz", + "checksum": "SHA-256:eaf82ff4070d9792f6a42ae1e485375de5a87bec59ef01dfb95de901519ec7fb", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "esptool-2.6.1-linux.tar.gz", + "size": "44762" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-linux.tar.gz", + "checksum": "SHA-256:eaf82ff4070d9792f6a42ae1e485375de5a87bec59ef01dfb95de901519ec7fb", + "host": "i686-pc-linux-gnu", + "archiveFileName": "esptool-2.6.1-linux.tar.gz", + "size": "44762" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-linux.tar.gz", + "checksum": "SHA-256:eaf82ff4070d9792f6a42ae1e485375de5a87bec59ef01dfb95de901519ec7fb", + "host": "arm-linux-gnueabihf", + "archiveFileName": "esptool-2.6.1-linux.tar.gz", + "size": "44762" + } + ] + }, + { + "version": "2.6.0", + "name": "esptool_py", + "systems": [ + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-windows.zip", + "checksum": "SHA-256:a73f4cf68db240d7f1d250c5c7f2dfcb53c17a37483729f1bf71f8f43d79a799", + "host": "i686-mingw32", + "archiveFileName": "esptool-2.6.0-windows.zip", + "size": "3421208" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-macos.tar.gz", + "checksum": "SHA-256:0a881b91547c840fab8c72ae3d031069384278b8c2e5241647e8c8292c5e4a4b", + "host": "x86_64-apple-darwin", + "archiveFileName": "esptool-2.6.0-macos.tar.gz", + "size": "3835660" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-linux.tar.gz", + "checksum": "SHA-256:6d162f70f395ca31f5008829dd7e833e729f044a9c7355d5be8ce333a054e110", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "esptool-2.6.0-linux.tar.gz", + "size": "43535" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-linux.tar.gz", + "checksum": "SHA-256:6d162f70f395ca31f5008829dd7e833e729f044a9c7355d5be8ce333a054e110", + "host": "i686-pc-linux-gnu", + "archiveFileName": "esptool-2.6.0-linux.tar.gz", + "size": "43535" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-linux.tar.gz", + "checksum": "SHA-256:6d162f70f395ca31f5008829dd7e833e729f044a9c7355d5be8ce333a054e110", + "host": "arm-linux-gnueabihf", + "archiveFileName": "esptool-2.6.0-linux.tar.gz", + "size": "43535" + } + ] + }, + { + "version": "0.2.3", + "name": "mkspiffs", + "systems": [ + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-win32.zip", + "checksum": "SHA-256:b647f2c2efe6949819c85ea9404271b55c7c9c25bcb98d3b98a1d0ba771adf56", + "host": "i686-mingw32", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-win32.zip", + "size": "249809" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-osx.tar.gz", + "checksum": "SHA-256:9f43fc74a858cf564966b5035322c3e5e61c31a647c5a1d71b388ed6efc48423", + "host": "x86_64-apple-darwin", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-osx.tar.gz", + "size": "130270" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-osx.tar.gz", + "checksum": "SHA-256:9f43fc74a858cf564966b5035322c3e5e61c31a647c5a1d71b388ed6efc48423", + "host": "i386-apple-darwin", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-osx.tar.gz", + "size": "130270" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-linux64.tar.gz", + "checksum": "SHA-256:5e1a4ff41385e842f389f6b5254102a547e566a06b49babeffa93ef37115cb5d", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-linux64.tar.gz", + "size": "50646" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-linux32.tar.gz", + "checksum": "SHA-256:464463a93e8833209cdc29ba65e1a12fec31718dc10075c195a2445b2c3f6cb0", + "host": "i686-pc-linux-gnu", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-linux32.tar.gz", + "size": "48751" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-linux-armhf.tar.gz", + "checksum": "SHA-256:ade3dc00117912ac08a1bdbfbfe76b12d21a34bc5fa1de0cfc45fe7a8d0a0185", + "host": "arm-linux-gnueabihf", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-linux-armhf.tar.gz", + "size": "40665" + } + ] + }, + { + "version": "2.3.1", + "name": "esptool", + "systems": [ + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-windows.zip", + "checksum": "SHA-256:c187763d0faac7da7c30a292a23c759bbc256fcd084dc8846ed284000cb0fe29", + "host": "i686-mingw32", + "archiveFileName": "esptool-2.3.1-windows.zip", + "size": "3396085" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-macos.tar.gz", + "checksum": "SHA-256:cd922418f02e0ca11dc066b36a22646a1b441da00d762b4464ca598c902c5ecb", + "host": "x86_64-apple-darwin", + "archiveFileName": "esptool-2.3.1-macos.tar.gz", + "size": "3810932" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-linux.tar.gz", + "checksum": "SHA-256:cff30841dad80ed5d7d2d58a31843b63afa57528979a9c839806568167691d8e", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "esptool-2.3.1-linux.tar.gz", + "size": "39563" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-linux.tar.gz", + "checksum": "SHA-256:cff30841dad80ed5d7d2d58a31843b63afa57528979a9c839806568167691d8e", + "host": "i686-pc-linux-gnu", + "archiveFileName": "esptool-2.3.1-linux.tar.gz", + "size": "39563" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-linux.tar.gz", + "checksum": "SHA-256:cff30841dad80ed5d7d2d58a31843b63afa57528979a9c839806568167691d8e", + "host": "arm-linux-gnueabihf", + "archiveFileName": "esptool-2.3.1-linux.tar.gz", + "size": "39563" + } + ] + }, + { + "version": "1.22.0-80-g6c4433a-5.2.0", + "name": "xtensa-esp32-elf-gcc", + "systems": [ + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-win32-1.22.0-80-g6c4433a-5.2.0.zip", + "checksum": "SHA-256:f217fccbeaaa8c92db239036e0d6202458de4488b954a3a38f35ac2ec48058a4", + "host": "i686-mingw32", + "archiveFileName": "xtensa-esp32-elf-win32-1.22.0-80-g6c4433a-5.2.0.zip", + "size": "125719261" + }, + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "checksum": "SHA-256:a4307a97945d2f2f2745f415fbe80d727750e19f91f9a1e7e2f8a6065652f9da", + "host": "x86_64-apple-darwin", + "archiveFileName": "xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "size": "46517409" + }, + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "checksum": "SHA-256:3fe96c151d46c1d4e5edc6ed690851b8e53634041114bad04729bc16b0445156", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "size": "44219107" + }, + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-linux32-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "checksum": "SHA-256:b4055695ffc2dfc0bcb6dafdc2572a6e01151c4179ef5fa972b3fcb2183eb155", + "host": "i686-pc-linux-gnu", + "archiveFileName": "xtensa-esp32-elf-linux32-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "size": "45566336" + }, + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-linux-armel-1.22.0-87-gb57bad3-5.2.0.tar.gz", + "checksum": "SHA-256:9c68c87bb23b1256dc0a1859b515946763e5292dcab4a4159a52fae5618ce861", + "host": "arm-linux-gnueabihf", + "archiveFileName": "xtensa-esp32-elf-linux-armel-1.22.0-87-gb57bad3-5.2.0.tar.gz", + "size": "50655584" + } + ] + } + ], + "email": "hristo@espressif.com", + "name": "esp32" + } + ] +} diff --git a/package_esp32_index.json b/package_esp32_index.json new file mode 100644 index 00000000000..4c2f1992c99 --- /dev/null +++ b/package_esp32_index.json @@ -0,0 +1,270 @@ +{ + "packages": [ + { + "maintainer": "elzershark", + "help": { + "online": "https://www.elzershark.com" + }, + "websiteURL": "https://github.com/elzershark", + "platforms": [ + { + "category": "ESP32", + "name": "elzershark", + "url": "https://github.com/elzershark/arduino-esp32/releases/download/1.0.4a/esp32-1.0.4a.zip", + "checksum": "SHA-256:AE172BA3C7639F069545C9275D27B6CE829E4300240467BC166C411109F96ABB", + "help": { + "online": "" + }, + "version": "1.0.4", + "architecture": "esp32", + "archiveFileName": "esp32-1.0.4a.zip", + "boards": [ + { + "name": "ESP32 Dev Module" + }, + { + "name": "WEMOS LoLin32" + }, + { + "name": "WEMOS D1 MINI ESP32" + } + ], + "toolsDependencies": [ + { + "packager": "esp32", + "version": "1.22.0-80-g6c4433a-5.2.0", + "name": "xtensa-esp32-elf-gcc" + }, + { + "packager": "esp32", + "version": "2.6.1", + "name": "esptool_py" + }, + { + "packager": "esp32", + "version": "0.2.3", + "name": "mkspiffs" + } + ], + "size": "36061658" + } + ], + "tools": [ + { + "version": "2.6.1", + "name": "esptool_py", + "systems": [ + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-windows.zip", + "checksum": "SHA-256:84cf0b369a7707fe566434faba148852fc464992111d5baa95b658b374802f96", + "host": "i686-mingw32", + "archiveFileName": "esptool-2.6.1-windows.zip", + "size": "3422445" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-macos.tar.gz", + "checksum": "SHA-256:f4eb758a301d6902cc9dfcd49d36345d2f075ad123da7cf8132d15cfb7533457", + "host": "x86_64-apple-darwin", + "archiveFileName": "esptool-2.6.1-macos.tar.gz", + "size": "3837085" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-linux.tar.gz", + "checksum": "SHA-256:eaf82ff4070d9792f6a42ae1e485375de5a87bec59ef01dfb95de901519ec7fb", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "esptool-2.6.1-linux.tar.gz", + "size": "44762" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-linux.tar.gz", + "checksum": "SHA-256:eaf82ff4070d9792f6a42ae1e485375de5a87bec59ef01dfb95de901519ec7fb", + "host": "i686-pc-linux-gnu", + "archiveFileName": "esptool-2.6.1-linux.tar.gz", + "size": "44762" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.1-linux.tar.gz", + "checksum": "SHA-256:eaf82ff4070d9792f6a42ae1e485375de5a87bec59ef01dfb95de901519ec7fb", + "host": "arm-linux-gnueabihf", + "archiveFileName": "esptool-2.6.1-linux.tar.gz", + "size": "44762" + } + ] + }, + { + "version": "2.6.0", + "name": "esptool_py", + "systems": [ + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-windows.zip", + "checksum": "SHA-256:a73f4cf68db240d7f1d250c5c7f2dfcb53c17a37483729f1bf71f8f43d79a799", + "host": "i686-mingw32", + "archiveFileName": "esptool-2.6.0-windows.zip", + "size": "3421208" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-macos.tar.gz", + "checksum": "SHA-256:0a881b91547c840fab8c72ae3d031069384278b8c2e5241647e8c8292c5e4a4b", + "host": "x86_64-apple-darwin", + "archiveFileName": "esptool-2.6.0-macos.tar.gz", + "size": "3835660" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-linux.tar.gz", + "checksum": "SHA-256:6d162f70f395ca31f5008829dd7e833e729f044a9c7355d5be8ce333a054e110", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "esptool-2.6.0-linux.tar.gz", + "size": "43535" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-linux.tar.gz", + "checksum": "SHA-256:6d162f70f395ca31f5008829dd7e833e729f044a9c7355d5be8ce333a054e110", + "host": "i686-pc-linux-gnu", + "archiveFileName": "esptool-2.6.0-linux.tar.gz", + "size": "43535" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.6.0-linux.tar.gz", + "checksum": "SHA-256:6d162f70f395ca31f5008829dd7e833e729f044a9c7355d5be8ce333a054e110", + "host": "arm-linux-gnueabihf", + "archiveFileName": "esptool-2.6.0-linux.tar.gz", + "size": "43535" + } + ] + }, + { + "version": "0.2.3", + "name": "mkspiffs", + "systems": [ + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-win32.zip", + "checksum": "SHA-256:b647f2c2efe6949819c85ea9404271b55c7c9c25bcb98d3b98a1d0ba771adf56", + "host": "i686-mingw32", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-win32.zip", + "size": "249809" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-osx.tar.gz", + "checksum": "SHA-256:9f43fc74a858cf564966b5035322c3e5e61c31a647c5a1d71b388ed6efc48423", + "host": "x86_64-apple-darwin", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-osx.tar.gz", + "size": "130270" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-osx.tar.gz", + "checksum": "SHA-256:9f43fc74a858cf564966b5035322c3e5e61c31a647c5a1d71b388ed6efc48423", + "host": "i386-apple-darwin", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-osx.tar.gz", + "size": "130270" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-linux64.tar.gz", + "checksum": "SHA-256:5e1a4ff41385e842f389f6b5254102a547e566a06b49babeffa93ef37115cb5d", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-linux64.tar.gz", + "size": "50646" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-linux32.tar.gz", + "checksum": "SHA-256:464463a93e8833209cdc29ba65e1a12fec31718dc10075c195a2445b2c3f6cb0", + "host": "i686-pc-linux-gnu", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-linux32.tar.gz", + "size": "48751" + }, + { + "url": "https://github.com/igrr/mkspiffs/releases/download/0.2.3/mkspiffs-0.2.3-arduino-esp32-linux-armhf.tar.gz", + "checksum": "SHA-256:ade3dc00117912ac08a1bdbfbfe76b12d21a34bc5fa1de0cfc45fe7a8d0a0185", + "host": "arm-linux-gnueabihf", + "archiveFileName": "mkspiffs-0.2.3-arduino-esp32-linux-armhf.tar.gz", + "size": "40665" + } + ] + }, + { + "version": "2.3.1", + "name": "esptool", + "systems": [ + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-windows.zip", + "checksum": "SHA-256:c187763d0faac7da7c30a292a23c759bbc256fcd084dc8846ed284000cb0fe29", + "host": "i686-mingw32", + "archiveFileName": "esptool-2.3.1-windows.zip", + "size": "3396085" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-macos.tar.gz", + "checksum": "SHA-256:cd922418f02e0ca11dc066b36a22646a1b441da00d762b4464ca598c902c5ecb", + "host": "x86_64-apple-darwin", + "archiveFileName": "esptool-2.3.1-macos.tar.gz", + "size": "3810932" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-linux.tar.gz", + "checksum": "SHA-256:cff30841dad80ed5d7d2d58a31843b63afa57528979a9c839806568167691d8e", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "esptool-2.3.1-linux.tar.gz", + "size": "39563" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-linux.tar.gz", + "checksum": "SHA-256:cff30841dad80ed5d7d2d58a31843b63afa57528979a9c839806568167691d8e", + "host": "i686-pc-linux-gnu", + "archiveFileName": "esptool-2.3.1-linux.tar.gz", + "size": "39563" + }, + { + "url": "https://dl.espressif.com/dl/esptool-2.3.1-linux.tar.gz", + "checksum": "SHA-256:cff30841dad80ed5d7d2d58a31843b63afa57528979a9c839806568167691d8e", + "host": "arm-linux-gnueabihf", + "archiveFileName": "esptool-2.3.1-linux.tar.gz", + "size": "39563" + } + ] + }, + { + "version": "1.22.0-80-g6c4433a-5.2.0", + "name": "xtensa-esp32-elf-gcc", + "systems": [ + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-win32-1.22.0-80-g6c4433a-5.2.0.zip", + "checksum": "SHA-256:f217fccbeaaa8c92db239036e0d6202458de4488b954a3a38f35ac2ec48058a4", + "host": "i686-mingw32", + "archiveFileName": "xtensa-esp32-elf-win32-1.22.0-80-g6c4433a-5.2.0.zip", + "size": "125719261" + }, + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "checksum": "SHA-256:a4307a97945d2f2f2745f415fbe80d727750e19f91f9a1e7e2f8a6065652f9da", + "host": "x86_64-apple-darwin", + "archiveFileName": "xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "size": "46517409" + }, + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "checksum": "SHA-256:3fe96c151d46c1d4e5edc6ed690851b8e53634041114bad04729bc16b0445156", + "host": "x86_64-pc-linux-gnu", + "archiveFileName": "xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "size": "44219107" + }, + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-linux32-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "checksum": "SHA-256:b4055695ffc2dfc0bcb6dafdc2572a6e01151c4179ef5fa972b3fcb2183eb155", + "host": "i686-pc-linux-gnu", + "archiveFileName": "xtensa-esp32-elf-linux32-1.22.0-80-g6c4433a-5.2.0.tar.gz", + "size": "45566336" + }, + { + "url": "https://dl.espressif.com/dl/xtensa-esp32-elf-linux-armel-1.22.0-87-gb57bad3-5.2.0.tar.gz", + "checksum": "SHA-256:9c68c87bb23b1256dc0a1859b515946763e5292dcab4a4159a52fae5618ce861", + "host": "arm-linux-gnueabihf", + "archiveFileName": "xtensa-esp32-elf-linux-armel-1.22.0-87-gb57bad3-5.2.0.tar.gz", + "size": "50655584" + } + ] + } + ], + "email": "hristo@espressif.com", + "name": "esp32" + } + ] +} diff --git a/tools/partitions/rzo_partitions.csv b/tools/partitions/rzo_partitions.csv new file mode 100644 index 00000000000..4192994e043 --- /dev/null +++ b/tools/partitions/rzo_partitions.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x280000, +fr, 32, 32, 0x290000, 0xEF000, +eeprom, data, 0x99, 0x37f000, 0x1000, +spiffs, data, spiffs, 0x380000, 0x2F000, \ No newline at end of file