diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e1c9df5
--- /dev/null
+++ b/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.
+
+ painlessMesh
+ Copyright (C) 2016 BlackEdder
+
+ 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:
+
+ painlessMesh Copyright (C) 2016 BlackEdder
+ 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/README.md b/README.md
index ec8ed7d..c450adb 100644
--- a/README.md
+++ b/README.md
@@ -1,94 +1,177 @@
-#Intro to easyMesh
-easyMesh is a library that takes care of the particulars for creating a simple mesh network using Arduino and esp8266. The goal is to allow the programmer to work with a mesh network without having to worry about how the network is structured or managed.
+# Intro to painlessMesh
-###True ad-hoc netoworking
-easyMesh is a true ad-hoc network, meaning that no-planning, central controller, or router is required. Any system of 1 or more nodes will self-organize into fully functional mesh. The maximum size of the mesh is limited (i think) by the amount of memory in the heap that can be allocated to the sub-connections buffer… and so should be really quite high.
+painlessMesh is a library that takes care of the particulars of creating a simple mesh network using esp8266 and esp32 hardware. The goal is to allow the programmer to work with a mesh network without having to worry about how the network is structured or managed.
-###JSON based
-easyMesh uses JSON objects for all its messaging. There a couple of reasons for this. First, it makes the code and the messages human readable and easy to understand and second, it makes it easy to integrate easyMesh with javascript front-ends, web applications, and other apps. Some performance is lost, but I haven’t been running into performance issues yet. Converting to binary messaging would be fairly straight forward if someone wants to contribute.
+### True ad-hoc networking
-###Wifi & Networking
-easyMesh is designed to be used with Arduino, but it does not use the Arduino WiFi libraries, as I was running into performance issues (primarily latency) with them. Rather the networking is all done using the native esp8266 SDK libraries, which are available through the Arduino IDE. Hopefully though, which networking libraries are used won’t matter to most users much as you can just include the .h, run the init() and then work the library through the API.
+painlessMesh is a true ad-hoc network, meaning that no-planning, central controller, or router is required. Any system of 1 or more nodes will self-organize into fully functional mesh. The maximum size of the mesh is limited (we think) by the amount of memory in the heap that can be allocated to the sub-connections buffer and so should be really quite high.
-###easyMesh is not IP networking
-easyMesh does not create a TCP/IP network of nodes. Rather each of the nodes is uniquely identified by its 32bit chipId which is retrieved from the esp8266 using the system_get_chip_id() call in the SDK. Every esp8266 will have a unique number. Messages can either be broadcast to all of the nodes on the mesh, or sent specifically to an individual node which is identified by its chipId.
+### JSON based
-###Examples
-demoToy is currently the only example. It is kind of complex, uses a web server, web sockets, and neopixel animations, so it is not really a great entry level example. That said, it does some pretty cools stuff… here is a video of the demo.
+painlessMesh uses JSON objects for all its messaging. There are a couple of reasons for this. First, it makes the code and the messages human readable and painless to understand and second, it makes it painless to integrate painlessMesh with javascript front-ends, web applications, and other apps. Some performance is lost, but I haven’t been running into performance issues yet. Converting to binary messaging would be fairly straight forward if someone wants to contribute.
-https://www.youtube.com/watch?v=hqjOY8YHdlM&feature=youtu.be
+### Wifi & Networking
-###Dependencies
-easyMesh makes use of the following libraries. They can both be installed through Arduino Library Manager
-- SimpleList *** Available here... https://github.com/blackhack/ArduLibraries/tree/master/SimpleList
-- ArduinoJson *** Available here... https://github.com/bblanchon/ArduinoJson
+painlessMesh is designed to be used with Arduino, but it does not use the Arduino WiFi libraries, as we were running into performance issues (primarily latency) with them. Rather the networking is all done using the native esp32 and esp8266 SDK libraries, which are available through the Arduino IDE. Hopefully though, which networking libraries are used won’t matter to most users much as you can just include painlessMesh.h, run the init() and then work the library through the API.
-#easyMesh API
-Using easyMesh is easy!
+### painlessMesh is not IP networking
-First include the library and create an easyMesh object like this…
+painlessMesh does not create a TCP/IP network of nodes. Rather each of the nodes is uniquely identified by its 32bit chipId which is retrieved from the esp8266/esp32 using the `system_get_chip_id()` call in the SDK. Every node will have a unique number. Messages can either be broadcast to all of the nodes on the mesh, or sent specifically to an individual node which is identified by its `nodeId.
+
+### Limitations and caveats
+
+- Try to avoid using `delay()` in your code. To maintain the mesh we need to perform some tasks in the background. Using `delay()` will stop these tasks from happening and can cause the mesh to lose stability/fall apart. Instead we recommend using the scheduler included in `painlessMesh`. That scheduler is a slightly modified version of the [TaskScheduler](http://playground.arduino.cc/Code/TaskScheduler) library. Documentation can be found [here](http://www.smart4smart.com/TaskScheduler.pdf). For other examples on how to use the scheduler see the example folder.
+- `painlessMesh` uses the sdk provided by [esp8266](http://espressif.com/sites/default/files/documentation/2c-esp8266_non_os_sdk_api_reference_en.pdf)/[esp32](https://esp-idf.readthedocs.io/en/latest/index.html). Please be aware that as a result `painlessMesh` is incompatible with using the `WiFi.h` wrappers provided by the vendor, because both `painlessMesh` and the `WiFi.h` try to bind to the same events (e.g. disconnect event). Instead if you want to use the WiFi chip you will need to use same sdk as `painlessMesh`.
+- Try to be conservative in the number of messages (and especially broadcast messages) you sent per minute. This is to prevent the hardware from overloading. Both esp8266 and esp32 are limited in processing power/memory, making it easy to overload the mesh and destabilise it. And while `painlessMesh` tries to prevent this from happening, it is not always possible to do so.
+- Messages can go missing or be dropped due to high traffic and you can not rely on all messages to be delivered. One suggestion to work around is to resend messages every so often. Even if some go missing, most should go through. Another option is to have your nodes send replies when they receive a message. The sending nodes can the resend the message if they haven’t gotten a reply in a certain amount of time.
+
+### Examples
+
+StartHere is a basic how to use example. It blinks built-in LED (in ESP-12) as many times as nodes are connected to the mesh. Further examples are under the examples directory and shown on the platformio [page](http://platformio.org/lib/show/1269/painlessMesh).
+
+### Dependencies
+
+painlessMesh makes use of the following library, which can be installed through the Arduino Library Manager
+
+- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
+
+If platformio is used to install the library, then the dependency will be installed automatically.
+
+# Getting help
+
+There is help available on the [wiki](https://gitlab.com/BlackEdder/painlessMesh/wikis/home) and you can also reach us on our [gitter channel](https://gitter.im/painlessMesh/Lobby)
+
+# painlessMesh API
+
+Using painlessMesh is painless!
+
+First include the library and create an painlessMesh object like this…
```
-#include
-easyMesh mesh;
+#include
+painlessMesh mesh;
```
-##Member Functions
+## Member Functions
+
+### void painlessMesh::init(String ssid, String password, uint16_t port = 5555, enum nodeMode connectMode = STA_AP, _auth_mode authmode = AUTH_WPA2_PSK, uint8_t channel = 1, phy_mode_t phymode = PHY_MODE_11G, uint8_t maxtpw = 82, uint8_t hidden = 0, uint8_t maxconn = 4)
-###void easyMesh::init( String prefix, String password, uint16_t port )
Add this to your setup() function.
-Initialize the mesh network. This routine does the following things…
+Initialize the mesh network. This routine does the following things.
+
- Starts a wifi network
- Begins searching for other wifi networks that are part of the mesh
- Logs on to the best mesh network node it finds… if it doesn’t find anything, it starts a new search in 5 seconds.
-prefix = the name of your mesh. The wifi ssid of this node will be prefix + chipId
-password = wifi password to your mesh
-port = the TCP port that you want the mesh server to run on
+`ssid` = the name of your mesh. All nodes share same AP ssid. They are distinguished by BSSID.
+`password` = wifi password to your mesh.
+`port` = the TCP port that you want the mesh server to run on. Defaults to 5555 if not specified.
+[`connectMode`](https://gitlab.com/BlackEdder/painlessMesh/wikis/connect-mode:-ap_only,-sta_only,-sta_ap-mode) = switch between AP_ONLY, STA_ONLY and STA_AP (default) mode
+
+### void painlessMesh::stop()
+
+Stop the node. This will cause the node to disconnect from all other nodes and stop/sending messages.
+
+### void painlessMesh::update( void )
-###void easyMesh::update( void )
Add this to your loop() function
This routine runs various maintainance tasks... Not super interesting, but things don't work without it.
+### void painlessMesh::onReceive( &receivedCallback )
-###void easyMesh::setReceiveCallback( &receivedCallback )
-Set a callback routine for any messages that are addressed to this node. The callback routine has the following structure…
+Set a callback routine for any messages that are addressed to this node. Callback routine has the following structure.
-`void receivedCallback( uint32_t from, String &msg )`
+`void receivedCallback( uint32_t from, String &msg )`
Every time this node receives a message, this callback routine will the called. “from” is the id of the original sender of the message, and “msg” is a string that contains the message. The message can be anything. A JSON, some other text string, or binary data.
+### void painlessMesh::onNewConnection( &newConnectionCallback )
+
+This fires every time the local node makes a new connection. The callback has the following structure.
+
+`void newConnectionCallback( uint32_t nodeId )`
+
+`nodeId` is new connected node ID in the mesh.
+
+### void painlessMesh::onChangedConnections( &changedConnectionsCallback )
+
+This fires every time there is a change in mesh topology. Callback has the following structure.
+
+`void onChangedConnections()`
+
+There are no parameters passed. This is a signal only.
-###void easyMesh::setNewConnectionCallback( &newConnectionCallback )
-This fires every time the local node makes a new connection. The callback has the following structure…
+### bool painlessMesh::isConnected( nodeId )
-`void newConnectionCallback( bool adopt )`
+Returns if a given node is currently connected to the mesh.
-`adopt` is a boolean value that indicates whether the mesh has determined to adopt the remote nodes timebase or not. If `adopt == true`, then this node has adopted the remote node’s timebase.
+`nodeId` is node ID that the request refers to.
-The mesh does a simple calculation to determine which nodes adopt and which nodes don’t. When a connection is made, the node with the smaller number of connections to other nodes adopts the timebase of the node with the larger number of connections to other nodes. If there is a tie, then the AP (access point) node wins.
+### void painlessMesh::onNodeTimeAdjusted( &nodeTimeAdjustedCallback )
-######Example 1:
-There are two separate meshes (Mesh A and Mesh B) that have discovered each other and are connecting. Mesh A has 7 nodes and Mesh B has 8 nodes. When the connection is made, Mesh B has more nodes in it, so Mesh A adopts the timebase of Mesh B.
+This fires every time local time is adjusted to synchronize it with mesh time. Callback has the following structure.
-######Example 2:
-A brand new mesh is starting. There are only 2 nodes (Node X and Node Y) and they both just got turned on. They find each other, and as luck would have it, Node X connects as a Station to the wifi network established by Node Y’s AP (access point)… which means that Node X is the wifi client and Node Y is the wifi server in the particular relationship. In this case, since both nodes have zero (0) other connections, Node X adopts Node Y’s timebase because the tie (0 vs 0) goes to the AP.
+`void onNodeTimeAdjusted(int32_t offset)`
+
+`offset` is the adjustment delta that has benn calculated and applied to local clock.
+
+### void onNodeDelayReceived(nodeDelayCallback_t onDelayReceived)
+
+This fires when a time delay masurement response is received, after a request was sent. Callback has the following structure.
+
+`void onNodeDelayReceived(uint32_t nodeId, int32_t delay)`
+
+`nodeId` The node that originated response.
+
+`delay` One way network trip delay in microseconds.
+
+### bool painlessMesh::sendBroadcast( String &msg)
-###bool easyMesh::sendBroadcast( String &msg)
Sends msg to every node on the entire mesh network.
returns true if everything works, false if not. Prints an error message to Serial.print, if there is a failure.
-###bool easyMesh::sendSingle(uint32_t dest, String &msg)
+### bool painlessMesh::sendSingle(uint32_t dest, String &msg)
+
Sends msg to the node with Id == dest.
returns true if everything works, false if not. Prints an error message to Serial.print, if there is a failure.
-###uint16_t easyMesh::connectionCount()
-Returns the total number of nodes connected to this mesh.
+### String painlessMesh::subConnectionJson()
+
+Returns mesh topology in JSON format.
+
+### SimpleList painlessMesh::getNodeList()
+
+Get a list of all known nodes. This includes nodes that are both directly and indirectly connected to the current node.
+
+### uint32_t painlessMesh::getNodeId( void )
-###uint32_t easyMesh::getChipId( void )
Return the chipId of the node that we are running on.
-###uint32_t easyMesh::getNodeTime( void )
-Returns the mesh timebase microsecond counter. Rolls over 71 minutes from startup of the first node.
+### uint32_t painlessMesh::getNodeTime( void )
+
+Returns the mesh timebase microsecond counter. Rolls over 71 minutes from startup of the first node.
+
+Nodes try to keep a common time base synchronizing to each other using [an SNTP based protocol](https://gitlab.com/BlackEdder/painlessMesh/wikis/mesh-protocol#time-sync)
+
+### bool painlessMesh::startDelayMeas(uint32_t nodeId)
+
+Sends a node a packet to measure network trip delay to that node. Returns true if nodeId is connected to the mesh, false otherwise. After calling this function, user program have to wait to the response in the form of a callback specified by `void painlessMesh::onNodeDelayReceived(nodeDelayCallback_t onDelayReceived)`.
+
+nodeDelayCallback_t is a funtion in the form of `void (uint32_t nodeId, int32_t delay)`.
+
+# Included dependencies
+
+`painlessMesh` includes two other projects within its source directory. These projects were included directly in a slightly modified form. The original version of the two projects are:
+
+- [AsyncTCP](https://github.com/me-no-dev/AsyncTCP)
+- [TaskScheduler](http://playground.arduino.cc/Code/TaskScheduler)
+
+# Funding
+
+Most development of painlessMesh has been done as a hobby, but some specific features have been funded by the companies listed below:
+
+
+
+[Sowillo](http://sowillo.com/en/)
diff --git a/examples/basic/basic.ino b/examples/basic/basic.ino
new file mode 100644
index 0000000..70c19ae
--- /dev/null
+++ b/examples/basic/basic.ino
@@ -0,0 +1,61 @@
+//************************************************************
+// this is a simple example that uses the painlessMesh library
+//
+// 1. sends a silly message to every node on the mesh at a random time betweew 1 and 5 seconds
+// 2. prints anything it recieves to Serial.print
+//
+//
+//************************************************************
+#include "painlessMesh.h"
+
+#define MESH_PREFIX "whateverYouLike"
+#define MESH_PASSWORD "somethingSneaky"
+#define MESH_PORT 5555
+
+void sendMessage() ; // Prototype so PlatformIO doesn't complain
+
+painlessMesh mesh;
+Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );
+
+void receivedCallback( uint32_t from, String &msg ) {
+ Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
+}
+
+void newConnectionCallback(uint32_t nodeId) {
+ Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
+}
+
+void changedConnectionCallback() {
+ Serial.printf("Changed connections %s\n",mesh.subConnectionJson().c_str());
+}
+
+void nodeTimeAdjustedCallback(int32_t offset) {
+ Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
+}
+
+void setup() {
+ Serial.begin(115200);
+
+//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
+ mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages
+
+ mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT );
+ mesh.onReceive(&receivedCallback);
+ mesh.onNewConnection(&newConnectionCallback);
+ mesh.onChangedConnections(&changedConnectionCallback);
+ mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
+
+ mesh.scheduler.addTask( taskSendMessage );
+ taskSendMessage.enable() ;
+}
+
+void loop() {
+ mesh.update();
+}
+
+void sendMessage() {
+ String msg = "Hello from node ";
+ msg += mesh.getNodeId();
+ mesh.sendBroadcast( msg );
+ taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
+}
diff --git a/examples/bridge/bridge.ino b/examples/bridge/bridge.ino
new file mode 100644
index 0000000..6ff8967
--- /dev/null
+++ b/examples/bridge/bridge.ino
@@ -0,0 +1,39 @@
+//************************************************************
+// this is a simple example that uses the painlessMesh library to
+// connect to a node on another network. Please see the WIKI on gitlab
+// for more details
+// https://gitlab.com/BlackEdder/painlessMesh/wikis/bridge-between-mesh-and-another-network
+//************************************************************
+#include "painlessMesh.h"
+
+#define MESH_PREFIX "whateverYouLike"
+#define MESH_PASSWORD "somethingSneaky"
+#define MESH_PORT 5555
+
+#define STATION_SSID "mySSID"
+#define STATION_PASSWORD "myPASSWORD"
+#define STATION_PORT 5555
+uint8_t station_ip[4] = {10,10,10,1}; // IP of the server
+
+painlessMesh mesh;
+
+void setup() {
+ Serial.begin(115200);
+ mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
+
+
+ // Channel set to 6. Make sure to use the same channel for your mesh and for you other
+ // network (STATION_SSID)
+ mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, STA_AP, AUTH_WPA2_PSK, 6 );
+ mesh.stationManual(STATION_SSID, STATION_PASSWORD, STATION_PORT,
+ station_ip);
+ mesh.onReceive(&receivedCallback);
+}
+
+void loop() {
+ mesh.update();
+}
+
+void receivedCallback( uint32_t from, String &msg ) {
+ Serial.printf("bridge: Received from %u msg=%s\n", from, msg.c_str());
+}
diff --git a/examples/bridge/platformio.ini b/examples/bridge/platformio.ini
new file mode 100644
index 0000000..4a7a83e
--- /dev/null
+++ b/examples/bridge/platformio.ini
@@ -0,0 +1,10 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+#lib_deps =
+# painlessMesh
diff --git a/examples/echoNode/echoNode.ino b/examples/echoNode/echoNode.ino
new file mode 100644
index 0000000..75605e4
--- /dev/null
+++ b/examples/echoNode/echoNode.ino
@@ -0,0 +1,30 @@
+//************************************************************
+// this is a simple example that uses the painlessMesh library and echos any
+// messages it receives
+//
+//************************************************************
+#include "painlessMesh.h"
+
+#define MESH_PREFIX "whateverYouLike"
+#define MESH_PASSWORD "somethingSneaky"
+#define MESH_PORT 5555
+
+painlessMesh mesh;
+
+void setup() {
+ Serial.begin(115200);
+
+ mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
+
+ mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT );
+ mesh.onReceive(&receivedCallback);
+}
+
+void loop() {
+ mesh.update();
+}
+
+void receivedCallback( uint32_t from, String &msg ) {
+ Serial.printf("echoNode: Received from %u msg=%s\n", from, msg.c_str());
+ mesh.sendSingle(from, msg);
+}
diff --git a/examples/echoNode/platformio.ini b/examples/echoNode/platformio.ini
new file mode 100644
index 0000000..4a7a83e
--- /dev/null
+++ b/examples/echoNode/platformio.ini
@@ -0,0 +1,10 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+#lib_deps =
+# painlessMesh
diff --git a/examples/logClient/logClient.ino b/examples/logClient/logClient.ino
new file mode 100644
index 0000000..cdfaca5
--- /dev/null
+++ b/examples/logClient/logClient.ino
@@ -0,0 +1,66 @@
+//************************************************************
+// this is a simple example that uses the painlessMesh library to
+// setup a node that logs to a central logging node
+// The logServer example shows how to configure the central logging nodes
+//************************************************************
+#include "painlessMesh.h"
+
+#define MESH_PREFIX "whateverYouLike"
+#define MESH_PASSWORD "somethingSneaky"
+#define MESH_PORT 5555
+
+painlessMesh mesh;
+
+size_t logServerId = 0;
+
+// Send message to the logServer every 10 seconds
+Task myLoggingTask(10000, TASK_FOREVER, []() {
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& msg = jsonBuffer.createObject();
+ msg["topic"] = "sensor";
+ msg["value"] = random(0, 180);
+
+ String str;
+ msg.printTo(str);
+ if (logServerId == 0) // If we don't know the logServer yet
+ mesh.sendBroadcast(str);
+ else
+ mesh.sendSingle(logServerId, str);
+
+ // log to serial
+ msg.printTo(Serial);
+ Serial.printf("\n");
+});
+
+void setup() {
+ Serial.begin(115200);
+
+ mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
+
+ mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, STA_AP, AUTH_WPA2_PSK, 6 );
+ mesh.onReceive(&receivedCallback);
+
+ // Add the task to the mesh scheduler
+ mesh.scheduler.addTask(myLoggingTask);
+ myLoggingTask.enable();
+}
+
+void loop() {
+ mesh.update();
+}
+
+void receivedCallback( uint32_t from, String &msg ) {
+ Serial.printf("logClient: Received from %u msg=%s\n", from, msg.c_str());
+
+ // Saving logServer
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& root = jsonBuffer.parseObject(msg);
+ if (root.containsKey("topic")) {
+ if (String("logServer").equals(root["topic"].as())) {
+ // check for on: true or false
+ logServerId = root["nodeId"];
+ Serial.printf("logServer detected!!!\n");
+ }
+ Serial.printf("Handled from %u msg=%s\n", from, msg.c_str());
+ }
+}
diff --git a/examples/logClient/platformio.ini b/examples/logClient/platformio.ini
new file mode 100644
index 0000000..4a7a83e
--- /dev/null
+++ b/examples/logClient/platformio.ini
@@ -0,0 +1,10 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+#lib_deps =
+# painlessMesh
diff --git a/examples/logServer/logServer.ino b/examples/logServer/logServer.ino
new file mode 100644
index 0000000..294e728
--- /dev/null
+++ b/examples/logServer/logServer.ino
@@ -0,0 +1,60 @@
+//************************************************************
+// this is a simple example that uses the painlessMesh library to
+// setup a single node (this node) as a logging node
+// The logClient example shows how to configure the other nodes
+// to log to this server
+//************************************************************
+#include "painlessMesh.h"
+
+#define MESH_PREFIX "whateverYouLike"
+#define MESH_PASSWORD "somethingSneaky"
+#define MESH_PORT 5555
+
+painlessMesh mesh;
+
+// Send my ID every 10 seconds to inform others
+Task logServerTask(10000, TASK_FOREVER, []() {
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& msg = jsonBuffer.createObject();
+ msg["topic"] = "logServer";
+ msg["nodeId"] = mesh.getNodeId();
+
+ String str;
+ msg.printTo(str);
+ mesh.sendBroadcast(str);
+
+ // log to serial
+ msg.printTo(Serial);
+ Serial.printf("\n");
+});
+
+void setup() {
+ Serial.begin(115200);
+
+ //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE | DEBUG ); // all types on
+ //mesh.setDebugMsgTypes( ERROR | CONNECTION | SYNC | S_TIME ); // set before init() so that you can see startup messages
+ mesh.setDebugMsgTypes( ERROR | CONNECTION | S_TIME ); // set before init() so that you can see startup messages
+
+ mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, STA_AP, AUTH_WPA2_PSK, 6 );
+ mesh.onReceive(&receivedCallback);
+
+ mesh.onNewConnection([](size_t nodeId) {
+ Serial.printf("New Connection %u\n", nodeId);
+ });
+
+ mesh.onDroppedConnection([](size_t nodeId) {
+ Serial.printf("Dropped Connection %u\n", nodeId);
+ });
+
+ // Add the task to the mesh scheduler
+ mesh.scheduler.addTask(logServerTask);
+ logServerTask.enable();
+}
+
+void loop() {
+ mesh.update();
+}
+
+void receivedCallback( uint32_t from, String &msg ) {
+ Serial.printf("logServer: Received from %u msg=%s\n", from, msg.c_str());
+}
diff --git a/examples/logServer/platformio.ini b/examples/logServer/platformio.ini
new file mode 100644
index 0000000..4a7a83e
--- /dev/null
+++ b/examples/logServer/platformio.ini
@@ -0,0 +1,10 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+#lib_deps =
+# painlessMesh
diff --git a/examples/startHere/platformio.ini b/examples/startHere/platformio.ini
new file mode 100644
index 0000000..8505bcb
--- /dev/null
+++ b/examples/startHere/platformio.ini
@@ -0,0 +1,18 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+#lib_deps =
+# painlessMesh
+
+[env:esp32]
+platform = espressif32
+board = esp32dev
+framework = arduino
+lib_deps = ArduinoJson
+ arduinoUnity
+ TaskScheduler
diff --git a/examples/startHere/startHere.ino b/examples/startHere/startHere.ino
index c19c16a..1c6b6e7 100644
--- a/examples/startHere/startHere.ino
+++ b/examples/startHere/startHere.ino
@@ -5,74 +5,142 @@
// 2. blink cycle repeats every BLINK_PERIOD
// 3. sends a silly message to every node on the mesh at a random time betweew 1 and 5 seconds
// 4. prints anything it recieves to Serial.print
-//
+//
//
//************************************************************
-#include
+#include
-// some gpio pin that is connected to an LED...
+// some gpio pin that is connected to an LED...
// on my rig, this is 5, change to the right number of your LED.
-#define LED 5 // GPIO number of connected LED
+#define LED 2 // GPIO number of connected LED, ON ESP-12 IS GPIO2
-#define BLINK_PERIOD 1000000 // microseconds until cycle repeat
-#define BLINK_DURATION 100000 // microseconds LED is on for
+#define BLINK_PERIOD 3000 // milliseconds until cycle repeat
+#define BLINK_DURATION 100 // milliseconds LED is on for
-#define MESH_PREFIX "whateverYouLike"
-#define MESH_PASSWORD "somethingSneeky"
+#define MESH_SSID "whateverYouLike"
+#define MESH_PASSWORD "somethingSneaky"
#define MESH_PORT 5555
-easyMesh mesh;
+painlessMesh mesh;
+bool calc_delay = false;
+SimpleList nodes;
+
+void sendMessage() ; // Prototype
+Task taskSendMessage( TASK_SECOND * 1, TASK_FOREVER, &sendMessage ); // start with a one second interval
-uint32_t sendMessageTime = 0;
+// Task to blink the number of nodes
+Task blinkNoNodes;
+bool onFlag = false;
void setup() {
Serial.begin(115200);
-
- pinMode( LED, OUTPUT );
-
-//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
- mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages
-
- mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT );
- mesh.setReceiveCallback( &receivedCallback );
- mesh.setNewConnectionCallback( &newConnectionCallback );
- randomSeed( analogRead( A0 ) );
+ pinMode(LED, OUTPUT);
+
+ //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
+ //mesh.setDebugMsgTypes(ERROR | DEBUG | CONNECTION | COMMUNICATION); // set before init() so that you can see startup messages
+ mesh.setDebugMsgTypes(ERROR | DEBUG | CONNECTION); // set before init() so that you can see startup messages
+
+ mesh.init(MESH_SSID, MESH_PASSWORD, MESH_PORT);
+ mesh.onReceive(&receivedCallback);
+ mesh.onNewConnection(&newConnectionCallback);
+ mesh.onChangedConnections(&changedConnectionCallback);
+ mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
+ mesh.onNodeDelayReceived(&delayReceivedCallback);
+
+ mesh.scheduler.addTask( taskSendMessage );
+ taskSendMessage.enable() ;
+
+ blinkNoNodes.set(BLINK_PERIOD, (mesh.getNodeList().size() + 1) * 2, []() {
+ // If on, switch off, else switch on
+ if (onFlag)
+ onFlag = false;
+ else
+ onFlag = true;
+ blinkNoNodes.delay(BLINK_DURATION);
+
+ if (blinkNoNodes.isLastIteration()) {
+ // Finished blinking. Reset task for next run
+ // blink number of nodes (including this node) times
+ blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
+ // Calculate delay based on current mesh time and BLINK_PERIOD
+ // This results in blinks between nodes being synced
+ blinkNoNodes.enableDelayed(BLINK_PERIOD -
+ (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
+ }
+ });
+ mesh.scheduler.addTask(blinkNoNodes);
+ blinkNoNodes.enable();
+
+ randomSeed(analogRead(A0));
}
void loop() {
mesh.update();
+ digitalWrite(LED, !onFlag);
- // run the blinky
- bool onFlag = false;
- uint32_t cycleTime = mesh.getNodeTime() % BLINK_PERIOD;
- for ( uint8_t i = 0; i < ( mesh.connectionCount() + 1); i++ ) {
- uint32_t onTime = BLINK_DURATION * i * 2;
+}
- if ( cycleTime > onTime && cycleTime < onTime + BLINK_DURATION )
- onFlag = true;
+void sendMessage() {
+ String msg = "Hello from node ";
+ msg += mesh.getNodeId();
+ msg += " myFreeMemory: " + String(ESP.getFreeHeap());
+ msg += " noTasks: " + String(mesh.scheduler.size());
+ bool error = mesh.sendBroadcast(msg);
+
+ if (calc_delay) {
+ SimpleList::iterator node = nodes.begin();
+ while (node != nodes.end()) {
+ mesh.startDelayMeas(*node);
+ node++;
+ }
+ calc_delay = false;
}
- digitalWrite( LED, onFlag );
- // get next random time for send message
- if ( sendMessageTime == 0 ) {
- sendMessageTime = mesh.getNodeTime() + random( 1000000, 5000000 );
- }
+ Serial.printf("Sending message: %s\n", msg.c_str());
+
+ taskSendMessage.setInterval( random(TASK_SECOND * 1, TASK_SECOND * 5)); // between 1 and 5 seconds
+}
- // if the time is ripe, send everyone a message!
- if ( sendMessageTime != 0 && sendMessageTime < mesh.getNodeTime() ){
- String msg = "Hello from node ";
- msg += mesh.getChipId();
- mesh.sendBroadcast( msg );
- sendMessageTime = 0;
- }
+
+void receivedCallback(uint32_t from, String & msg) {
+ Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
+}
+
+void newConnectionCallback(uint32_t nodeId) {
+ // Reset blink task
+ onFlag = false;
+ blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
+ blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
+
+ Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
}
-void receivedCallback( uint32_t from, String &msg ) {
- Serial.printf("startHere: Received from %d msg=%s\n", from, msg.c_str());
+void changedConnectionCallback() {
+ Serial.printf("Changed connections %s\n", mesh.subConnectionJson().c_str());
+ // Reset blink task
+ onFlag = false;
+ blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
+ blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
+
+ nodes = mesh.getNodeList();
+
+ Serial.printf("Num nodes: %d\n", nodes.size());
+ Serial.printf("Connection list:");
+
+ SimpleList::iterator node = nodes.begin();
+ while (node != nodes.end()) {
+ Serial.printf(" %u", *node);
+ node++;
+ }
+ Serial.println();
+ calc_delay = true;
}
-void newConnectionCallback( bool adopt ) {
- Serial.printf("startHere: New Connection, adopt=%d\n", adopt);
+void nodeTimeAdjustedCallback(int32_t offset) {
+ Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset);
}
+void delayReceivedCallback(uint32_t from, int32_t delay) {
+ Serial.printf("Delay to node %u is %d us\n", from, delay);
+}
diff --git a/examples/webServer/platformio.ini b/examples/webServer/platformio.ini
new file mode 100644
index 0000000..01c889f
--- /dev/null
+++ b/examples/webServer/platformio.ini
@@ -0,0 +1,17 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+lib_deps =
+ ESPAsyncWebServer
+
+[env:esp32]
+platform = espressif32
+board = esp32doit-devkit-v1
+framework = arduino
+lib_deps =
+ AsyncWebServer
diff --git a/examples/webServer/webServer.ino b/examples/webServer/webServer.ino
new file mode 100644
index 0000000..9657fb2
--- /dev/null
+++ b/examples/webServer/webServer.ino
@@ -0,0 +1,76 @@
+//************************************************************
+// this is a simple example that uses the painlessMesh library to
+// connect to a another network and broadcast message from a webpage to the edges of the mesh network.
+// This sketch can be extended further using all the abilities of the AsyncWebserver library (WS, events, ...)
+// for more details
+// https://gitlab.com/BlackEdder/painlessMesh/wikis/bridge-between-mesh-and-another-network
+// for more details about my version
+// https://gitlab.com/Assassynv__V/painlessMesh
+// and for more details about the AsyncWebserver library
+// https://github.com/me-no-dev/ESPAsyncWebServer
+//************************************************************
+
+#include "IPAddress.h"
+#include "painlessMesh.h"
+
+#ifdef ESP8266
+#include "Hash.h"
+#include
+#else
+#include
+#endif
+#include
+
+#define MESH_PREFIX "whateverYouLike"
+#define MESH_PASSWORD "somethingSneaky"
+#define MESH_PORT 5555
+
+#define STATION_SSID "mySSID"
+#define STATION_PASSWORD "myPASSWORD"
+
+#define HOSTNAME "HTTP_BRIDGE"
+
+painlessMesh mesh;
+AsyncWebServer server(80);
+IPAddress myIP(0,0,0,0);
+
+void setup() {
+ Serial.begin(115200);
+
+ mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
+
+ // Channel set to 6. Make sure to use the same channel for your mesh and for you other
+ // network (STATION_SSID)
+ mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, STA_AP, WIFI_AUTH_WPA2_PSK, 6 );
+ mesh.onReceive(&receivedCallback);
+
+ mesh.stationManual(STATION_SSID, STATION_PASSWORD);
+ mesh.setHostname(HOSTNAME);
+
+ //Async webserver
+ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
+ request->send(200, "text/html", "");
+ if (request->hasArg("BROADCAST")){
+ String msg = request->arg("BROADCAST");
+ mesh.sendBroadcast(msg);
+ }
+ });
+ server.begin();
+
+}
+
+void loop() {
+ mesh.update();
+ if(myIP != getlocalIP()){
+ myIP = getlocalIP();
+ Serial.println("My IP is " + myIP.toString());
+ }
+}
+
+void receivedCallback( const uint32_t &from, const String &msg ) {
+ Serial.printf("bridge: Received from %u msg=%s\n", from, msg.c_str());
+}
+
+IPAddress getlocalIP() {
+ return IPAddress(mesh.getStationIP().addr);
+}
diff --git a/library.json b/library.json
index f0a71c6..a268f95 100644
--- a/library.json
+++ b/library.json
@@ -1,14 +1,24 @@
{
- "name": “easyMesh”,
- "keywords": “esp8266 mesh network networking”,
- "description": “A simple mesh networking library for esp8266. easyMesh is very simple to implement and establishes a simple and highly adaptable mesh network for any distributed application. Network synchronization, network mapping, time synchronization, package delivery, and dynamic network adaptation are all handled behind the scenes in the library. Just run the init() function and set up the callbacks, then you are off to the races with a dynamic, adaptable, masterless mesh network.“,
- "repository":
- {
- "type": "git",
- "url": "https://github.com/Coopdis/easyMesh"
- },
- "version": “1.0.0”,
- "frameworks": “*”,
- "platforms": "esp8266"
+ "name": "painlessMesh",
+ "keywords": "ethernet, m2m, iot",
+ "description": "A painless way to setup a mesh with ESP8266 and ESP32 devices",
+ "repository": {
+ "type": "git",
+ "url": "https://gitlab.com/BlackEdder/painlessMesh"
+ },
+ "version": "0.6.2",
+ "frameworks": "arduino",
+ "platforms": "espressif8266, espressif32",
+ "dependencies": [
+ { "name": "ArduinoJson" }
+ ],
+ "authors": [
+ { "name": "Scotty Franzyshen" },
+ {
+ "name": "Coopdis",
+ "url": "https://github.com/Coopdis"
+ },
+ { "name": "Edwin van Leeuwen" },
+ { "name": "Germán Martín" }
+ ]
}
-
diff --git a/library.properties b/library.properties
index e0e727a..15ecf42 100644
--- a/library.properties
+++ b/library.properties
@@ -1,9 +1,10 @@
-name= easyMesh by Coopdis
-version=1.0.0
-author=Bill Gray (coopdisdev@gmail.com)
-maintainer=Bill Gray (coopdisdev@gmail.com)
-sentence=A simple mesh networking library for esp8266
-paragraph=easyMesh is very simple to implement and establishes a simple and highly adaptable mesh network for any distributed application. Network synchronization, network mapping, time synchronization, package delivery, and dynamic network adaptation are all handled behind the scenes in the library. Just run the init() function and set up the callbacks, then you are off to the races with a dynamic, adaptable, masterless mesh network.
+name=Painless Mesh
+version=0.6.2
+author=Coopdis,Scotty Franzyshen,Edwin van Leeuwen,Germán Martín
+maintainer=Edwin van Leeuwen
+sentence=A painless way to setup a mesh with ESP8266 and ESP32 devices
+paragraph=A painless way to setup a mesh with ESP8266 and ESP32 devices
category=Communication
-url=https://github.com/Coopdis/easyMesh
-architectures=esp8266
\ No newline at end of file
+url=https://gitlab.com/BlackEdder/painlessMesh
+architectures=esp8266,esp32
+includes=painlessMesh.h,painlessMeshSync.h,painlessScheduler.h
diff --git a/src/AsyncTCP.cpp b/src/AsyncTCP.cpp
new file mode 100644
index 0000000..cb93291
--- /dev/null
+++ b/src/AsyncTCP.cpp
@@ -0,0 +1,1119 @@
+/*
+ Asynchronous TCP library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ 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
+ */
+
+#include "Arduino.h"
+
+#include "AsyncTCP.h"
+extern "C"{
+#include "lwip/opt.h"
+#include "lwip/tcp.h"
+#include "lwip/inet.h"
+#include "lwip/dns.h"
+}
+
+#ifdef ESP32
+/*
+ * TCP/IP Event Task
+ * */
+
+typedef enum {
+ LWIP_TCP_SENT, LWIP_TCP_RECV, LWIP_TCP_ERROR, LWIP_TCP_POLL
+} lwip_event_t;
+
+typedef struct {
+ lwip_event_t event;
+ void *arg;
+ union {
+ struct {
+ void * pcb;
+ int8_t err;
+ } connected;
+ struct {
+ int8_t err;
+ } error;
+ struct {
+ tcp_pcb * pcb;
+ uint16_t len;
+ } sent;
+ struct {
+ tcp_pcb * pcb;
+ pbuf * pb;
+ int8_t err;
+ } recv;
+ struct {
+ tcp_pcb * pcb;
+ } poll;
+ struct {
+ tcp_pcb * pcb;
+ int8_t err;
+ } accept;
+ struct {
+ const char * name;
+ ip_addr_t addr;
+ } dns;
+ };
+} lwip_event_packet_t;
+
+static xQueueHandle _async_queue;
+static TaskHandle_t _async_service_task_handle = NULL;
+
+static void _handle_async_event(lwip_event_packet_t * e){
+
+ if(e->event == LWIP_TCP_RECV){
+ AsyncClient::_s_recv(e->arg, e->recv.pcb, e->recv.pb, e->recv.err);
+ } else if(e->event == LWIP_TCP_SENT){
+ AsyncClient::_s_sent(e->arg, e->sent.pcb, e->sent.len);
+ } else if(e->event == LWIP_TCP_POLL){
+ AsyncClient::_s_poll(e->arg, e->poll.pcb);
+ } else if(e->event == LWIP_TCP_ERROR){
+ AsyncClient::_s_error(e->arg, e->error.err);
+ }
+ free((void*)(e));
+}
+
+static void _async_service_task(void *pvParameters){
+ lwip_event_packet_t * packet = NULL;
+ for (;;) {
+ if(xQueueReceive(_async_queue, &packet, 0) == pdTRUE){
+ //dispatch packet
+ _handle_async_event(packet);
+ } else {
+ vTaskDelay(1);
+ }
+ }
+ vTaskDelete(NULL);
+ _async_service_task_handle = NULL;
+}
+/*
+static void _stop_async_task(){
+ if(_async_service_task_handle){
+ vTaskDelete(_async_service_task_handle);
+ _async_service_task_handle = NULL;
+ }
+}
+*/
+static bool _start_async_task(){
+ if(!_async_queue){
+ _async_queue = xQueueCreate(32, sizeof(lwip_event_packet_t *));
+ if(!_async_queue){
+ return false;
+ }
+ }
+ if(!_async_service_task_handle){
+ xTaskCreatePinnedToCore(_async_service_task, "async_tcp", 8192, NULL, 3, &_async_service_task_handle, 1);
+ if(!_async_service_task_handle){
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * LwIP Callbacks
+ * */
+
+static int8_t _tcp_poll(void * arg, struct tcp_pcb * pcb) {
+ if(!_async_queue){
+ return ERR_OK;
+ }
+ lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t));
+ e->event = LWIP_TCP_POLL;
+ e->arg = arg;
+ e->poll.pcb = pcb;
+ if (xQueueSend(_async_queue, &e, portMAX_DELAY) != pdPASS) {
+ free((void*)(e));
+ }
+ return ERR_OK;
+}
+
+static int8_t _tcp_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) {
+ if(!_async_queue){
+ return ERR_OK;
+ }
+ lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t));
+ e->event = LWIP_TCP_RECV;
+ e->arg = arg;
+ e->recv.pcb = pcb;
+ e->recv.pb = pb;
+ e->recv.err = err;
+ if (xQueueSend(_async_queue, &e, portMAX_DELAY) != pdPASS) {
+ free((void*)(e));
+ }
+ return ERR_OK;
+}
+
+static int8_t _tcp_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) {
+ if(!_async_queue){
+ return ERR_OK;
+ }
+ lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t));
+ e->event = LWIP_TCP_SENT;
+ e->arg = arg;
+ e->sent.pcb = pcb;
+ e->sent.len = len;
+ if (xQueueSend(_async_queue, &e, portMAX_DELAY) != pdPASS) {
+ free((void*)(e));
+ }
+ return ERR_OK;
+}
+
+static void _tcp_error(void * arg, int8_t err) {
+ if(!_async_queue){
+ return;
+ }
+ lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t));
+ e->event = LWIP_TCP_ERROR;
+ e->arg = arg;
+ e->error.err = err;
+ if (xQueueSend(_async_queue, &e, portMAX_DELAY) != pdPASS) {
+ free((void*)(e));
+ }
+}
+
+/*
+ * TCP/IP API Calls
+ * */
+
+#include "lwip/priv/tcpip_priv.h"
+typedef struct {
+ struct tcpip_api_call call;
+ tcp_pcb * pcb;
+ int8_t err;
+ union {
+ struct {
+ const char* data;
+ size_t size;
+ uint8_t apiflags;
+ } write;
+ size_t received;
+ struct {
+ ip_addr_t * addr;
+ uint16_t port;
+ tcp_connected_fn cb;
+ } connect;
+ struct {
+ ip_addr_t * addr;
+ uint16_t port;
+ } bind;
+ uint8_t backlog;
+ };
+} tcp_api_call_t;
+
+static err_t _tcp_output_api(struct tcpip_api_call *api_call_msg){
+ tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg;
+ msg->err = tcp_output(msg->pcb);
+ return msg->err;
+}
+
+static esp_err_t _tcp_output(tcp_pcb * pcb) {
+ tcp_api_call_t msg;
+ msg.pcb = pcb;
+ tcpip_api_call(_tcp_output_api, (struct tcpip_api_call*)&msg);
+ return msg.err;
+}
+
+static err_t _tcp_write_api(struct tcpip_api_call *api_call_msg){
+ tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg;
+ msg->err = tcp_write(msg->pcb, msg->write.data, msg->write.size, msg->write.apiflags);
+ return msg->err;
+}
+
+static esp_err_t _tcp_write(tcp_pcb * pcb, const char* data, size_t size, uint8_t apiflags) {
+ tcp_api_call_t msg;
+ msg.pcb = pcb;
+ msg.write.data = data;
+ msg.write.size = size;
+ msg.write.apiflags = apiflags;
+ tcpip_api_call(_tcp_write_api, (struct tcpip_api_call*)&msg);
+ return msg.err;
+}
+
+static err_t _tcp_recved_api(struct tcpip_api_call *api_call_msg){
+ tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg;
+ msg->err = 0;
+ tcp_recved(msg->pcb, msg->received);
+ return msg->err;
+}
+
+static esp_err_t _tcp_recved(tcp_pcb * pcb, size_t len) {
+ tcp_api_call_t msg;
+ msg.pcb = pcb;
+ msg.received = len;
+ tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call*)&msg);
+ return msg.err;
+}
+
+static err_t _tcp_connect_api(struct tcpip_api_call *api_call_msg){
+ tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg;
+ msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb);
+ return msg->err;
+}
+
+static esp_err_t _tcp_connect(tcp_pcb * pcb, ip_addr_t * addr, uint16_t port, tcp_connected_fn cb) {
+ tcp_api_call_t msg;
+ msg.pcb = pcb;
+ msg.connect.addr = addr;
+ msg.connect.port = port;
+ msg.connect.cb = cb;
+ tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call*)&msg);
+ return msg.err;
+}
+
+static err_t _tcp_close_api(struct tcpip_api_call *api_call_msg){
+ tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg;
+ msg->err = tcp_close(msg->pcb);
+ return msg->err;
+}
+
+static esp_err_t _tcp_close(tcp_pcb * pcb) {
+ tcp_api_call_t msg;
+ msg.pcb = pcb;
+ tcpip_api_call(_tcp_close_api, (struct tcpip_api_call*)&msg);
+ return msg.err;
+}
+
+static err_t _tcp_abort_api(struct tcpip_api_call *api_call_msg){
+ tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg;
+ msg->err = 0;
+ tcp_abort(msg->pcb);
+ return msg->err;
+}
+
+static esp_err_t _tcp_abort(tcp_pcb * pcb) {
+ tcp_api_call_t msg;
+ msg.pcb = pcb;
+ tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call*)&msg);
+ return msg.err;
+}
+
+static err_t _tcp_bind_api(struct tcpip_api_call *api_call_msg){
+ tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg;
+ msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port);
+ return msg->err;
+}
+
+static esp_err_t _tcp_bind(tcp_pcb * pcb, ip_addr_t * addr, uint16_t port) {
+ tcp_api_call_t msg;
+ msg.pcb = pcb;
+ msg.bind.addr = addr;
+ msg.bind.port = port;
+ tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call*)&msg);
+ return msg.err;
+}
+
+static err_t _tcp_listen_api(struct tcpip_api_call *api_call_msg){
+ tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg;
+ msg->err = 0;
+ msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog);
+ return msg->err;
+}
+
+static tcp_pcb * _tcp_listen_with_backlog(tcp_pcb * pcb, uint8_t backlog) {
+ tcp_api_call_t msg;
+ msg.pcb = pcb;
+ msg.backlog = backlog?backlog:0xFF;
+ tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call*)&msg);
+ return msg.pcb;
+}
+#define _tcp_listen(p) _tcp_listen_with_backlog(p, 0xFF);
+#else
+#include "espInterface.h"
+
+void ICACHE_FLASH_ATTR log_w(const char* format ...) {
+}
+
+void ICACHE_FLASH_ATTR log_e(const char* format ...) {
+}
+
+const auto& _tcp_connect = tcp_connect;
+const auto& _tcp_err = tcp_err;
+const auto& _tcp_write = tcp_write;
+const auto& _tcp_output = tcp_output;
+const auto& _tcp_recved = tcp_recved;
+const auto& _tcp_listen_with_backlog = tcp_listen_with_backlog;
+const auto& _tcp_bind = tcp_bind;
+const auto& _tcp_close = tcp_close;
+const auto& _tcp_abort = tcp_abort;
+
+const auto& _tcp_recv = AsyncClient::_s_recv;
+const auto& _tcp_sent = AsyncClient::_s_sent;
+const auto& _tcp_error = AsyncClient::_s_error;
+const auto& _tcp_poll = AsyncClient::_s_poll;
+#endif
+
+
+
+/*
+ Async TCP Client
+ */
+
+AsyncClient::AsyncClient(tcp_pcb* pcb)
+: _connect_cb(0)
+, _connect_cb_arg(0)
+, _discard_cb(0)
+, _discard_cb_arg(0)
+, _sent_cb(0)
+, _sent_cb_arg(0)
+, _error_cb(0)
+, _error_cb_arg(0)
+, _recv_cb(0)
+, _recv_cb_arg(0)
+, _timeout_cb(0)
+, _timeout_cb_arg(0)
+, _pcb_busy(false)
+, _pcb_sent_at(0)
+, _close_pcb(false)
+, _ack_pcb(true)
+, _rx_last_packet(0)
+, _rx_since_timeout(0)
+, _ack_timeout(ASYNC_MAX_ACK_TIME)
+, _connect_port(0)
+, prev(NULL)
+, next(NULL)
+, _in_lwip_thread(false)
+{
+ _pcb = pcb;
+ if(_pcb){
+ _rx_last_packet = millis();
+ tcp_arg(_pcb, this);
+ tcp_recv(_pcb, &_tcp_recv);
+ tcp_sent(_pcb, &_tcp_sent);
+ tcp_err(_pcb, &_tcp_error);
+ tcp_poll(_pcb, &_tcp_poll, 1);
+ }
+}
+
+AsyncClient::~AsyncClient(){
+ if(_pcb)
+ _close();
+}
+
+bool AsyncClient::connect(IPAddress ip, uint16_t port){
+ if (_pcb){
+ log_w("already connected, state %d", _pcb->state);
+ return false;
+ }
+#ifdef ESP32
+ if(!_start_async_task()){
+ log_e("failed to start task");
+ return false;
+ }
+
+ ip_addr_t addr;
+ addr.type = IPADDR_TYPE_V4;
+ addr.u_addr.ip4.addr = ip;
+
+ tcp_pcb* pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
+#else
+ ip_addr_t addr;
+ addr.addr = ip;
+ tcp_pcb* pcb = tcp_new();
+#endif
+
+ if (!pcb){
+ log_e("pcb == NULL");
+ return false;
+ }
+
+ tcp_arg(pcb, this);
+ tcp_err(pcb, &_tcp_error);
+ if(_in_lwip_thread){
+ tcp_connect(pcb, &addr, port,(tcp_connected_fn)&_s_connected);
+ } else {
+ _tcp_connect(pcb, &addr, port,(tcp_connected_fn)&_s_connected);
+ }
+ return true;
+}
+
+AsyncClient& AsyncClient::operator=(const AsyncClient& other){
+ if (_pcb)
+ _close();
+
+ _pcb = other._pcb;
+ if (_pcb) {
+ _rx_last_packet = millis();
+ tcp_arg(_pcb, this);
+ tcp_recv(_pcb, &_tcp_recv);
+ tcp_sent(_pcb, &_tcp_sent);
+ tcp_err(_pcb, &_tcp_error);
+ tcp_poll(_pcb, &_tcp_poll, 1);
+ }
+ return *this;
+}
+
+int8_t AsyncClient::_connected(void* pcb, int8_t err){
+ _pcb = reinterpret_cast(pcb);
+ if(_pcb){
+ _rx_last_packet = millis();
+ _pcb_busy = false;
+ tcp_recv(_pcb, &_tcp_recv);
+ tcp_sent(_pcb, &_tcp_sent);
+ tcp_poll(_pcb, &_tcp_poll, 1);
+ }
+ _in_lwip_thread = true;
+ if(_connect_cb)
+ _connect_cb(_connect_cb_arg, this);
+ _in_lwip_thread = false;
+ return ERR_OK;
+}
+
+int8_t AsyncClient::_close(){
+ int8_t err = ERR_OK;
+ if(_pcb) {
+ //log_i("");
+ tcp_arg(_pcb, NULL);
+ tcp_sent(_pcb, NULL);
+ tcp_recv(_pcb, NULL);
+ tcp_err(_pcb, NULL);
+ tcp_poll(_pcb, NULL, 0);
+ if(_in_lwip_thread){
+ err = tcp_close(_pcb);
+ } else {
+ err = _tcp_close(_pcb);
+ }
+ if(err != ERR_OK) {
+ err = abort();
+ }
+ _pcb = NULL;
+ if(_discard_cb)
+ _discard_cb(_discard_cb_arg, this);
+ }
+ return err;
+}
+
+void AsyncClient::_error(int8_t err) {
+ if(_pcb){
+ tcp_arg(_pcb, NULL);
+ tcp_sent(_pcb, NULL);
+ tcp_recv(_pcb, NULL);
+ tcp_err(_pcb, NULL);
+ tcp_poll(_pcb, NULL, 0);
+ _pcb = NULL;
+ }
+ if(_error_cb)
+ _error_cb(_error_cb_arg, this, err);
+ if(_discard_cb)
+ _discard_cb(_discard_cb_arg, this);
+}
+
+int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) {
+ _rx_last_packet = millis();
+ //log_i("%u", len);
+ _pcb_busy = false;
+ if(_sent_cb)
+ _sent_cb(_sent_cb_arg, this, len, (millis() - _pcb_sent_at));
+ return ERR_OK;
+}
+
+int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) {
+ if(pb == NULL){
+ return _close();
+ }
+
+ while(pb != NULL){
+ _rx_last_packet = millis();
+ //we should not ack before we assimilate the data
+ //log_i("%u", pb->len);
+ //Serial.write((const uint8_t *)pb->payload, pb->len);
+ _ack_pcb = true;
+ pbuf *b = pb;
+ if(_recv_cb)
+ _recv_cb(_recv_cb_arg, this, b->payload, b->len);
+ if(!_ack_pcb)
+ _rx_ack_len += b->len;
+ else
+ _tcp_recved(pcb, b->len);
+ pb = b->next;
+ b->next = NULL;
+ pbuf_free(b);
+ }
+ return ERR_OK;
+}
+
+int8_t AsyncClient::_poll(tcp_pcb* pcb){
+ // Close requested
+ if(_close_pcb){
+ _close_pcb = false;
+ _close();
+ return ERR_OK;
+ }
+ uint32_t now = millis();
+
+ // ACK Timeout
+ if(_pcb_busy && _ack_timeout && (now - _pcb_sent_at) >= _ack_timeout){
+ _pcb_busy = false;
+ log_w("ack timeout %d", pcb->state);
+ if(_timeout_cb)
+ _timeout_cb(_timeout_cb_arg, this, (now - _pcb_sent_at));
+ return ERR_OK;
+ }
+ // RX Timeout
+ if(_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)){
+ log_w("rx timeout %d", pcb->state);
+ _close();
+ return ERR_OK;
+ }
+ // Everything is fine
+ if(_poll_cb)
+ _poll_cb(_poll_cb_arg, this);
+ return ERR_OK;
+}
+
+void AsyncClient::_dns_found(ip_addr_t *ipaddr){
+ _in_lwip_thread = true;
+ if(ipaddr){
+#ifdef ESP32
+ connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port);
+#else
+ connect(IPAddress(ipaddr->addr), _connect_port);
+#endif
+ } else {
+ log_e("dns fail");
+ if(_error_cb)
+ _error_cb(_error_cb_arg, this, -55);
+ if(_discard_cb)
+ _discard_cb(_discard_cb_arg, this);
+ }
+ _in_lwip_thread = false;
+}
+
+bool AsyncClient::operator==(const AsyncClient &other) {
+ return _pcb == other._pcb;
+}
+
+bool AsyncClient::connect(const char* host, uint16_t port){
+ ip_addr_t addr;
+ err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_s_dns_found, this);
+ if(err == ERR_OK) {
+#ifdef ESP32
+ return connect(IPAddress(addr.u_addr.ip4.addr), port);
+#else
+ return connect(IPAddress(addr.addr), port);
+#endif
+ } else if(err == ERR_INPROGRESS) {
+ _connect_port = port;
+ return true;
+ }
+ log_e("error: %d", err);
+ return false;
+}
+
+int8_t AsyncClient::abort(){
+ if(_pcb) {
+ log_w("state %d", _pcb->state);
+ if(_in_lwip_thread){
+ tcp_abort(_pcb);
+ } else {
+ _tcp_abort(_pcb);
+ }
+ _pcb = NULL;
+ }
+ return ERR_ABRT;
+}
+
+void AsyncClient::close(bool now){
+ if(_in_lwip_thread){
+ tcp_recved(_pcb, _rx_ack_len);
+ } else {
+ _tcp_recved(_pcb, _rx_ack_len);
+ }
+ if(now)
+ _close();
+ else
+ _close_pcb = true;
+}
+
+void AsyncClient::stop() {
+ close(false);
+}
+
+bool AsyncClient::free(){
+ if(!_pcb)
+ return true;
+ if(_pcb->state == 0 || _pcb->state > 4)
+ return true;
+ return false;
+}
+
+size_t AsyncClient::space(){
+ if((_pcb != NULL) && (_pcb->state == 4)){
+ return tcp_sndbuf(_pcb);
+ }
+ return 0;
+}
+
+size_t AsyncClient::write(const char* data) {
+ if(data == NULL)
+ return 0;
+ return write(data, strlen(data));
+}
+
+size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) {
+ size_t will_send = add(data, size, apiflags);
+ if(!will_send || !send())
+ return 0;
+ return will_send;
+}
+
+
+size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) {
+ if(!_pcb || size == 0 || data == NULL)
+ return 0;
+ size_t room = space();
+ if(!room)
+ return 0;
+ size_t will_send = (room < size) ? room : size;
+ int8_t err = ERR_OK;
+ if(_in_lwip_thread){
+ err = tcp_write(_pcb, data, will_send, apiflags);
+ } else {
+ err = _tcp_write(_pcb, data, will_send, apiflags);
+ }
+ if(err != ERR_OK)
+ return 0;
+ return will_send;
+}
+
+bool AsyncClient::send(){
+ int8_t err = ERR_OK;
+ if(_in_lwip_thread){
+ err = tcp_output(_pcb);
+ } else {
+ err = _tcp_output(_pcb);
+ }
+ if(err == ERR_OK){
+ _pcb_busy = true;
+ _pcb_sent_at = millis();
+ return true;
+ }
+ return false;
+}
+
+size_t AsyncClient::ack(size_t len){
+ if(len > _rx_ack_len)
+ len = _rx_ack_len;
+ if(len){
+ if(_in_lwip_thread){
+ tcp_recved(_pcb, len);
+ } else {
+ _tcp_recved(_pcb, len);
+ }
+ }
+ _rx_ack_len -= len;
+ return len;
+}
+
+// Operators
+
+AsyncClient & AsyncClient::operator+=(const AsyncClient &other) {
+ if(next == NULL){
+ next = (AsyncClient*)(&other);
+ next->prev = this;
+ } else {
+ AsyncClient *c = next;
+ while(c->next != NULL) c = c->next;
+ c->next =(AsyncClient*)(&other);
+ c->next->prev = c;
+ }
+ return *this;
+}
+
+void AsyncClient::setRxTimeout(uint32_t timeout){
+ _rx_since_timeout = timeout;
+}
+
+uint32_t AsyncClient::getRxTimeout(){
+ return _rx_since_timeout;
+}
+
+uint32_t AsyncClient::getAckTimeout(){
+ return _ack_timeout;
+}
+
+void AsyncClient::setAckTimeout(uint32_t timeout){
+ _ack_timeout = timeout;
+}
+
+void AsyncClient::setNoDelay(bool nodelay){
+ if(!_pcb)
+ return;
+ if(nodelay)
+ tcp_nagle_disable(_pcb);
+ else
+ tcp_nagle_enable(_pcb);
+}
+
+bool AsyncClient::getNoDelay(){
+ if(!_pcb)
+ return false;
+ return tcp_nagle_disabled(_pcb);
+}
+
+uint16_t AsyncClient::getMss(){
+ if(_pcb)
+ return tcp_mss(_pcb);
+ return 0;
+}
+
+uint32_t AsyncClient::getRemoteAddress() {
+ if(!_pcb)
+ return 0;
+#ifdef ESP32
+ return _pcb->remote_ip.u_addr.ip4.addr;
+#else
+ return _pcb->remote_ip.addr;
+#endif
+
+}
+
+uint16_t AsyncClient::getRemotePort() {
+ if(!_pcb)
+ return 0;
+ return _pcb->remote_port;
+}
+
+uint32_t AsyncClient::getLocalAddress() {
+ if(!_pcb)
+ return 0;
+#ifdef ESP32
+ return _pcb->local_ip.u_addr.ip4.addr;
+#else
+ return _pcb->local_ip.addr;
+#endif
+}
+
+uint16_t AsyncClient::getLocalPort() {
+ if(!_pcb)
+ return 0;
+ return _pcb->local_port;
+}
+
+IPAddress AsyncClient::remoteIP() {
+ return IPAddress(getRemoteAddress());
+}
+
+uint16_t AsyncClient::remotePort() {
+ return getRemotePort();
+}
+
+IPAddress AsyncClient::localIP() {
+ return IPAddress(getLocalAddress());
+}
+
+uint16_t AsyncClient::localPort() {
+ return getLocalPort();
+}
+
+uint8_t AsyncClient::state() {
+ if(!_pcb)
+ return 0;
+ return _pcb->state;
+}
+
+bool AsyncClient::connected(){
+ if (!_pcb)
+ return false;
+ return _pcb->state == 4;
+}
+
+bool AsyncClient::connecting(){
+ if (!_pcb)
+ return false;
+ return _pcb->state > 0 && _pcb->state < 4;
+}
+
+bool AsyncClient::disconnecting(){
+ if (!_pcb)
+ return false;
+ return _pcb->state > 4 && _pcb->state < 10;
+}
+
+bool AsyncClient::disconnected(){
+ if (!_pcb)
+ return true;
+ return _pcb->state == 0 || _pcb->state == 10;
+}
+
+bool AsyncClient::freeable(){
+ if (!_pcb)
+ return true;
+ return _pcb->state == 0 || _pcb->state > 4;
+}
+
+bool AsyncClient::canSend(){
+ return space() > 0;
+}
+
+
+// Callback Setters
+
+void AsyncClient::onConnect(AcConnectHandler cb, void* arg){
+ _connect_cb = cb;
+ _connect_cb_arg = arg;
+}
+
+void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg){
+ _discard_cb = cb;
+ _discard_cb_arg = arg;
+}
+
+void AsyncClient::onAck(AcAckHandler cb, void* arg){
+ _sent_cb = cb;
+ _sent_cb_arg = arg;
+}
+
+void AsyncClient::onError(AcErrorHandler cb, void* arg){
+ _error_cb = cb;
+ _error_cb_arg = arg;
+}
+
+void AsyncClient::onData(AcDataHandler cb, void* arg){
+ _recv_cb = cb;
+ _recv_cb_arg = arg;
+}
+
+void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg){
+ _timeout_cb = cb;
+ _timeout_cb_arg = arg;
+}
+
+void AsyncClient::onPoll(AcConnectHandler cb, void* arg){
+ _poll_cb = cb;
+ _poll_cb_arg = arg;
+}
+
+
+void AsyncClient::_s_dns_found(const char * name, ip_addr_t * ipaddr, void * arg){
+ reinterpret_cast(arg)->_dns_found(ipaddr);
+}
+
+int8_t AsyncClient::_s_poll(void * arg, struct tcp_pcb * pcb) {
+ reinterpret_cast(arg)->_poll(pcb);
+ return ERR_OK;
+}
+
+int8_t AsyncClient::_s_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) {
+ reinterpret_cast(arg)->_recv(pcb, pb, err);
+ return ERR_OK;
+}
+
+int8_t AsyncClient::_s_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) {
+ reinterpret_cast(arg)->_sent(pcb, len);
+ return ERR_OK;
+}
+
+void AsyncClient::_s_error(void * arg, int8_t err) {
+ reinterpret_cast(arg)->_error(err);
+}
+
+int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err){
+ reinterpret_cast(arg)->_connected(pcb, err);
+ return ERR_OK;
+}
+
+const char * AsyncClient::errorToString(int8_t error){
+ switch(error){
+ case 0: return "OK";
+ case -1: return "Out of memory error";
+ case -2: return "Buffer error";
+ case -3: return "Timeout";
+ case -4: return "Routing problem";
+ case -5: return "Operation in progress";
+ case -6: return "Illegal value";
+ case -7: return "Operation would block";
+ case -8: return "Connection aborted";
+ case -9: return "Connection reset";
+ case -10: return "Connection closed";
+ case -11: return "Not connected";
+ case -12: return "Illegal argument";
+ case -13: return "Address in use";
+ case -14: return "Low-level netif error";
+ case -15: return "Already connected";
+ case -55: return "DNS failed";
+ default: return "UNKNOWN";
+ }
+}
+
+const char * AsyncClient::stateToString(){
+ switch(state()){
+ case 0: return "Closed";
+ case 1: return "Listen";
+ case 2: return "SYN Sent";
+ case 3: return "SYN Received";
+ case 4: return "Established";
+ case 5: return "FIN Wait 1";
+ case 6: return "FIN Wait 2";
+ case 7: return "Close Wait";
+ case 8: return "Closing";
+ case 9: return "Last ACK";
+ case 10: return "Time Wait";
+ default: return "UNKNOWN";
+ }
+}
+
+/*
+ Async TCP Server
+ */
+struct pending_pcb {
+ tcp_pcb* pcb;
+ pbuf *pb;
+ struct pending_pcb * next;
+};
+
+AsyncServer::AsyncServer(IPAddress addr, uint16_t port)
+: _port(port)
+, _addr(addr)
+, _noDelay(false)
+, _in_lwip_thread(false)
+, _pcb(0)
+, _connect_cb(0)
+, _connect_cb_arg(0)
+{}
+
+AsyncServer::AsyncServer(uint16_t port)
+: _port(port)
+, _addr((uint32_t) IPADDR_ANY)
+, _noDelay(false)
+, _in_lwip_thread(false)
+, _pcb(0)
+, _connect_cb(0)
+, _connect_cb_arg(0)
+{}
+
+AsyncServer::~AsyncServer(){
+ end();
+}
+
+void AsyncServer::onClient(AcConnectHandler cb, void* arg){
+ _connect_cb = cb;
+ _connect_cb_arg = arg;
+}
+
+int8_t AsyncServer::_s_accept(void * arg, tcp_pcb * pcb, int8_t err){
+ reinterpret_cast(arg)->_accept(pcb, err);
+ return ERR_OK;
+}
+
+int8_t AsyncServer::_accept(tcp_pcb* pcb, int8_t err){
+ tcp_accepted(_pcb);
+ if(_connect_cb){
+
+ if (_noDelay)
+ tcp_nagle_disable(pcb);
+ else
+ tcp_nagle_enable(pcb);
+
+ AsyncClient *c = new AsyncClient(pcb);
+ if(c){
+ _in_lwip_thread = true;
+ c->_in_lwip_thread = true;
+ _connect_cb(_connect_cb_arg, c);
+ c->_in_lwip_thread = false;
+ _in_lwip_thread = false;
+ return ERR_OK;
+ }
+ }
+ if(tcp_close(pcb) != ERR_OK){
+ tcp_abort(pcb);
+ }
+ log_e("FAIL");
+ return ERR_OK;
+}
+
+void AsyncServer::begin(){
+ if(_pcb)
+ return;
+
+ int8_t err;
+#ifdef ESP32
+ if(!_start_async_task()){
+ log_e("failed to start task");
+ return;
+ }
+ _pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
+ if (!_pcb){
+ log_e("_pcb == NULL");
+ return;
+ }
+
+ ip_addr_t local_addr;
+ local_addr.type = IPADDR_TYPE_V4;
+ local_addr.u_addr.ip4.addr = (uint32_t) _addr;
+#else
+ _pcb = tcp_new();
+ if (!_pcb){
+ log_e("_pcb == NULL");
+ return;
+ }
+
+ ip_addr_t local_addr;
+ local_addr.addr = (uint32_t) _addr;
+#endif
+ err = _tcp_bind(_pcb, &local_addr, _port);
+
+ if (err != ERR_OK) {
+ _tcp_close(_pcb);
+ log_e("bind error: %d", err);
+ return;
+ }
+
+ //static uint8_t backlog = 5;
+ //_pcb = _tcp_listen_with_backlog(_pcb, backlog);
+#ifdef ESP32
+ _pcb = _tcp_listen(_pcb);
+#else
+ _pcb = tcp_listen(_pcb);
+#endif
+
+ if (!_pcb) {
+ log_e("listen_pcb == NULL");
+ return;
+ }
+ tcp_arg(_pcb, (void*) this);
+ tcp_accept(_pcb, &_s_accept);
+}
+
+void AsyncServer::end(){
+ if(_pcb){
+ tcp_arg(_pcb, NULL);
+ tcp_accept(_pcb, NULL);
+ if(_in_lwip_thread){
+ tcp_abort(_pcb);
+ } else {
+ _tcp_abort(_pcb);
+ }
+ _pcb = NULL;
+ }
+}
+
+void AsyncServer::setNoDelay(bool nodelay){
+ _noDelay = nodelay;
+}
+
+bool AsyncServer::getNoDelay(){
+ return _noDelay;
+}
+
+uint8_t AsyncServer::status(){
+ if (!_pcb)
+ return 0;
+ return _pcb->state;
+}
diff --git a/src/AsyncTCP.h b/src/AsyncTCP.h
new file mode 100644
index 0000000..ddde4c1
--- /dev/null
+++ b/src/AsyncTCP.h
@@ -0,0 +1,195 @@
+/*
+ Asynchronous TCP library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ 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 ASYNCTCP_H_
+#define ASYNCTCP_H_
+
+#include "espInterface.h"
+#include "IPAddress.h"
+#include
+
+#ifdef ESP32
+extern "C" {
+#include "freertos/semphr.h"
+}
+#endif
+
+class AsyncClient;
+
+#define ASYNC_MAX_ACK_TIME 5000
+#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given)
+#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react.
+
+typedef std::function AcConnectHandler;
+typedef std::function AcAckHandler;
+typedef std::function AcErrorHandler;
+typedef std::function AcDataHandler;
+typedef std::function AcTimeoutHandler;
+
+struct tcp_pcb;
+struct pbuf;
+struct _ip_addr;
+
+class AsyncClient {
+ protected:
+ tcp_pcb* _pcb;
+
+ AcConnectHandler _connect_cb;
+ void* _connect_cb_arg;
+ AcConnectHandler _discard_cb;
+ void* _discard_cb_arg;
+ AcAckHandler _sent_cb;
+ void* _sent_cb_arg;
+ AcErrorHandler _error_cb;
+ void* _error_cb_arg;
+ AcDataHandler _recv_cb;
+ void* _recv_cb_arg;
+ AcTimeoutHandler _timeout_cb;
+ void* _timeout_cb_arg;
+ AcConnectHandler _poll_cb;
+ void* _poll_cb_arg;
+
+ bool _pcb_busy;
+ uint32_t _pcb_sent_at;
+ bool _close_pcb;
+ bool _ack_pcb;
+ uint32_t _rx_ack_len;
+ uint32_t _rx_last_packet;
+ uint32_t _rx_since_timeout;
+ uint32_t _ack_timeout;
+ uint16_t _connect_port;
+
+ int8_t _close();
+ int8_t _connected(void* pcb, int8_t err);
+ void _error(int8_t err);
+ int8_t _poll(tcp_pcb* pcb);
+ int8_t _sent(tcp_pcb* pcb, uint16_t len);
+ void _dns_found(ip_addr_t *ipaddr);
+
+
+ public:
+ AsyncClient* prev;
+ AsyncClient* next;
+
+ AsyncClient(tcp_pcb* pcb = 0);
+ ~AsyncClient();
+
+ AsyncClient & operator=(const AsyncClient &other);
+ AsyncClient & operator+=(const AsyncClient &other);
+
+ bool operator==(const AsyncClient &other);
+
+ bool operator!=(const AsyncClient &other) {
+ return !(*this == other);
+ }
+ bool connect(IPAddress ip, uint16_t port);
+ bool connect(const char* host, uint16_t port);
+ void close(bool now = false);
+ void stop();
+ int8_t abort();
+ bool free();
+
+ bool canSend();//ack is not pending
+ size_t space();
+ size_t add(const char* data, size_t size, uint8_t apiflags=0);//add for sending
+ bool send();//send all data added with the method above
+ size_t ack(size_t len); //ack data that you have not acked using the method below
+ void ackLater(){ _ack_pcb = false; } //will not ack the current packet. Call from onData
+
+ size_t write(const char* data);
+ size_t write(const char* data, size_t size, uint8_t apiflags=0); //only when canSend() == true
+
+ uint8_t state();
+ bool connecting();
+ bool connected();
+ bool disconnecting();
+ bool disconnected();
+ bool freeable();//disconnected or disconnecting
+
+ uint16_t getMss();
+ uint32_t getRxTimeout();
+ void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds
+ uint32_t getAckTimeout();
+ void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds
+ void setNoDelay(bool nodelay);
+ bool getNoDelay();
+ uint32_t getRemoteAddress();
+ uint16_t getRemotePort();
+ uint32_t getLocalAddress();
+ uint16_t getLocalPort();
+
+ IPAddress remoteIP();
+ uint16_t remotePort();
+ IPAddress localIP();
+ uint16_t localPort();
+
+ void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect
+ void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected
+ void onAck(AcAckHandler cb, void* arg = 0); //ack received
+ void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error
+ void onData(AcDataHandler cb, void* arg = 0); //data received
+ void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout
+ void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected
+
+ const char * errorToString(int8_t error);
+ const char * stateToString();
+
+ int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err);
+
+ static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb);
+ static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err);
+ static void _s_error(void *arg, int8_t err);
+ static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len);
+ static int8_t _s_connected(void* arg, void* tpcb, int8_t err);
+ static void _s_dns_found(const char *name, ip_addr_t *ipaddr, void *arg);
+
+ bool _in_lwip_thread;
+};
+
+class AsyncServer {
+ protected:
+ uint16_t _port;
+ IPAddress _addr;
+ bool _noDelay;
+ bool _in_lwip_thread;
+ tcp_pcb* _pcb;
+ AcConnectHandler _connect_cb;
+ void* _connect_cb_arg;
+
+ public:
+
+ AsyncServer(IPAddress addr, uint16_t port);
+ AsyncServer(uint16_t port);
+ ~AsyncServer();
+ void onClient(AcConnectHandler cb, void* arg);
+ void begin();
+ void end();
+ void setNoDelay(bool nodelay);
+ bool getNoDelay();
+ uint8_t status();
+
+ static int8_t _s_accept(void *arg, tcp_pcb* newpcb, int8_t err);
+ protected:
+ int8_t _accept(tcp_pcb* newpcb, int8_t err);
+};
+
+
+#endif /* ASYNCTCP_H_ */
diff --git a/src/espInterface.cpp b/src/espInterface.cpp
new file mode 100644
index 0000000..dc14bc5
--- /dev/null
+++ b/src/espInterface.cpp
@@ -0,0 +1,231 @@
+#include "espInterface.h"
+
+#ifdef ESP8266
+#include
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_set_mode(wifi_mode_t mode) {
+ if (wifi_set_opmode(mode))
+ return ESP_OK;
+ return ESP_FAIL;
+}
+
+static std::list _ap_records;
+
+static system_event_cb_t system_event_cb;
+
+void ICACHE_FLASH_ATTR wifiEventCb(System_Event_t *event) {
+ // Forward events to the esp32 system_event_t
+ if (system_event_cb) {
+ system_event_t system_event;
+ switch (event->event) {
+ case EVENT_STAMODE_CONNECTED:
+ system_event.event_id = SYSTEM_EVENT_STA_CONNECTED;
+ system_event_cb(NULL, &system_event);
+ break;
+ case EVENT_STAMODE_DISCONNECTED:
+ system_event.event_id = SYSTEM_EVENT_STA_DISCONNECTED;
+ system_event_cb(NULL, &system_event);
+ break;
+ case EVENT_STAMODE_AUTHMODE_CHANGE:
+ system_event.event_id = SYSTEM_EVENT_STA_AUTHMODE_CHANGE;
+ system_event_cb(NULL, &system_event);
+ break;
+ case EVENT_STAMODE_GOT_IP:
+ system_event.event_id = SYSTEM_EVENT_STA_GOT_IP;
+ system_event_cb(NULL, &system_event);
+ break;
+
+ case EVENT_SOFTAPMODE_STACONNECTED:
+ system_event.event_id = SYSTEM_EVENT_AP_STACONNECTED;
+ system_event_cb(NULL, &system_event);
+ break;
+
+ case EVENT_SOFTAPMODE_STADISCONNECTED:
+ system_event.event_id = SYSTEM_EVENT_AP_STADISCONNECTED;
+ system_event_cb(NULL, &system_event);
+ break;
+
+ default:
+ //staticThis->debugMsg(ERROR, "Unexpected WiFi event: %d\n", event->event);
+ break;
+ }
+ }
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_event_loop_init(system_event_cb_t cb, void *ctx) {
+ wifi_set_event_handler_cb(wifiEventCb);
+ system_event_cb = cb;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_scan_get_ap_num(uint16_t *number) {
+ *number = static_cast(_ap_records.size());
+ return ESP_OK;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records) {
+ if (*number <= _ap_records.size()) {
+ *number = _ap_records.size();
+ }
+ size_t i = 0;
+ for (auto &&record : _ap_records) {
+ ap_records[i] = record;
+ ++i;
+ if (i >= *number)
+ break;
+ }
+ return ESP_OK;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_scan_start(wifi_scan_config_t *config, bool block) {
+ // TODO: throw error if block is used
+ if (block)
+ return ESP_FAIL;
+
+ wifi_station_scan(config, [](void *arg, STATUS) {
+ // TODO: fill ap list and
+ _ap_records.clear();
+ bss_info *bssInfo = (bss_info *) arg;
+ while (bssInfo != NULL) {
+ _ap_records.push_back(*bssInfo);
+#ifdef STAILQ_NEXT
+ bssInfo = STAILQ_NEXT(bssInfo, next);
+#else
+ bssInfo = bssInfo->next;
+#endif
+ }
+
+ // raise event: SYSTEM_EVENT_SCAN_DONE
+ if (system_event_cb) {
+ system_event_t event;
+ event.event_id = SYSTEM_EVENT_SCAN_DONE;
+ system_event_cb(NULL, &event);
+ }
+ });
+ return ESP_OK;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_disconnect(void) {
+ if (wifi_station_disconnect())
+ return ESP_OK;
+ return ESP_FAIL;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_set_auto_connect(bool en) {
+ if (wifi_station_set_auto_connect(en))
+ return ESP_OK;
+ return ESP_FAIL;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_init(wifi_init_config_t *config) {
+ if (wifi_station_get_connect_status() != STATION_IDLE) { // Check if WiFi is idle
+ esp_wifi_disconnect();
+ }
+ return ESP_OK;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_deinit() {
+ return ESP_OK;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_start() {
+ if (system_event_cb) {
+ system_event_t event;
+ event.event_id = SYSTEM_EVENT_STA_START;
+ system_event_cb(NULL, &event);
+ system_event_t event_ap;
+ event_ap.event_id = SYSTEM_EVENT_AP_START;
+ system_event_cb(NULL, &event_ap);
+ }
+ return ESP_OK;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_stop() {
+ return ESP_OK;
+}
+
+void ICACHE_FLASH_ATTR tcpip_adapter_init() {}
+
+esp_err_t ICACHE_FLASH_ATTR tcpip_adapter_get_ip_info(tcpip_adapter_if_t tcpip_if, tcpip_adapter_ip_info_t *ip_info) {
+ if (wifi_get_ip_info(tcpip_if, ip_info))
+ return ESP_OK;
+ return ESP_FAIL;
+}
+
+esp_err_t ICACHE_FLASH_ATTR tcpip_adapter_set_ip_info(tcpip_adapter_if_t tcpip_if, tcpip_adapter_ip_info_t *ip_info) {
+ if (wifi_set_ip_info(tcpip_if, ip_info))
+ return ESP_OK;
+ return ESP_FAIL;
+}
+
+esp_err_t ICACHE_FLASH_ATTR tcpip_adapter_dhcps_start(tcpip_adapter_if_t tcpip_if) {
+ if (tcpip_if == TCPIP_ADAPTER_IF_AP) {
+ if (wifi_softap_dhcps_start())
+ return ESP_OK;
+ }
+ return ESP_FAIL;
+}
+
+esp_err_t ICACHE_FLASH_ATTR tcpip_adapter_dhcps_stop(tcpip_adapter_if_t tcpip_if) {
+ if (tcpip_if == TCPIP_ADAPTER_IF_AP) {
+ if (wifi_softap_dhcps_stop())
+ return ESP_OK;
+ }
+ return ESP_FAIL;
+
+}
+
+esp_err_t ICACHE_FLASH_ATTR tcpip_adapter_set_hostname(tcpip_adapter_if_t tcpip_if,
+ const char *hostname) {
+ // Currently only STA is supported
+ if (tcpip_if == TCPIP_ADAPTER_IF_STA) {
+ wifi_station_set_hostname((char*)hostname);
+ return ESP_OK;
+ }
+ return ESP_FAIL;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_set_config(wifi_interface_t ifx, wifi_config_t *conf) {
+ if (ifx == ESP_IF_WIFI_STA)
+ if (wifi_station_set_config(&conf->sta))
+ return ESP_OK;
+
+ if (ifx == ESP_IF_WIFI_AP)
+ if (wifi_softap_set_config(&conf->ap))
+ return ESP_OK;
+ return ESP_FAIL;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_get_config(wifi_interface_t ifx, wifi_config_t *conf) {
+ if (ifx == ESP_IF_WIFI_STA)
+ if (wifi_station_get_config(&conf->sta))
+ return ESP_OK;
+
+ if (ifx == ESP_IF_WIFI_AP)
+ if (wifi_softap_get_config(&conf->ap))
+ return ESP_OK;
+ return ESP_FAIL;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_connect() {
+ if (wifi_station_connect())
+ return ESP_OK;
+ return ESP_FAIL;
+}
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_get_mac(wifi_interface_t ifx, uint8_t mac[6]) {
+ if (wifi_get_macaddr(ifx, mac))
+ return ESP_OK;
+ return ESP_FAIL;
+}
+
+
+esp_err_t ICACHE_FLASH_ATTR esp_wifi_set_protocol(wifi_interface_t ifx, uint8_t protocol_bitmap) {
+ // NOTE that esp8266 can't use a different mode for ap and station
+ if (ifx == ESP_IF_WIFI_STA ||
+ ifx == ESP_IF_WIFI_AP)
+ if (wifi_set_phy_mode(static_cast(protocol_bitmap)))
+ return ESP_OK;
+ return ESP_FAIL;
+}
+#endif
+
diff --git a/src/espInterface.h b/src/espInterface.h
new file mode 100644
index 0000000..0f23bda
--- /dev/null
+++ b/src/espInterface.h
@@ -0,0 +1,219 @@
+#ifndef ESP_INTERFACE_H
+#define ESP_INTERFACE_H
+
+#ifdef ESP8266
+extern "C" {
+#include "user_interface.h"
+}
+
+// TODO
+
+#define WIFI_AUTH_OPEN AUTH_OPEN
+#define WIFI_AUTH_WEP AUTH_WEP
+#define WIFI_AUTH_WPA_PSK AUTH_WPA_PSK
+#define WIFI_AUTH_WPA2_PSK AUTH_WPA2_PSK
+typedef _auth_mode wifi_auth_mode_t;
+
+#define WIFI_PROTOCOL_11B PHY_MODE_11B
+#define WIFI_PROTOCOL_11G PHY_MODE_11G
+#define WIFI_PROTOCOL_11N PHY_MODE_11N
+
+#define ESP_OK 0
+#define ESP_FAIL -1
+typedef int32_t esp_err_t;
+
+typedef enum {
+ WIFI_MODE_STA = STATION_MODE, /**< WiFi station mode */
+ WIFI_MODE_AP = SOFTAP_MODE, /**< WiFi soft-AP mode */
+ WIFI_MODE_APSTA = STATIONAP_MODE /**< WiFi station + soft-AP mode */
+} wifi_mode_t;
+
+esp_err_t esp_wifi_set_mode(wifi_mode_t mode);
+
+typedef enum {
+ SYSTEM_EVENT_WIFI_READY = 0, /**< ESP32 WiFi ready */
+ SYSTEM_EVENT_SCAN_DONE, /**< ESP32 finish scanning AP */
+ SYSTEM_EVENT_STA_START, /**< ESP32 station start */
+ SYSTEM_EVENT_STA_STOP, /**< ESP32 station stop */
+ SYSTEM_EVENT_STA_CONNECTED, /**< ESP32 station connected to AP */
+ SYSTEM_EVENT_STA_DISCONNECTED, /**< ESP32 station disconnected from AP */
+ SYSTEM_EVENT_STA_AUTHMODE_CHANGE, /**< the auth mode of AP connected by ESP32 station changed */
+ SYSTEM_EVENT_STA_GOT_IP, /**< ESP32 station got IP from connected AP */
+ SYSTEM_EVENT_STA_WPS_ER_SUCCESS, /**< ESP32 station wps succeeds in enrollee mode */
+ SYSTEM_EVENT_STA_WPS_ER_FAILED, /**< ESP32 station wps fails in enrollee mode */
+ SYSTEM_EVENT_STA_WPS_ER_TIMEOUT, /**< ESP32 station wps timeout in enrollee mode */
+ SYSTEM_EVENT_STA_WPS_ER_PIN, /**< ESP32 station wps pin code in enrollee mode */
+ SYSTEM_EVENT_AP_START, /**< ESP32 soft-AP start */
+ SYSTEM_EVENT_AP_STOP, /**< ESP32 soft-AP stop */
+ SYSTEM_EVENT_AP_STACONNECTED, /**< a station connected to ESP32 soft-AP */
+ SYSTEM_EVENT_AP_STADISCONNECTED, /**< a station disconnected from ESP32 soft-AP */
+ SYSTEM_EVENT_AP_PROBEREQRECVED, /**< Receive probe request packet in soft-AP interface */
+ SYSTEM_EVENT_AP_STA_GOT_IP6, /**< ESP32 station or ap interface v6IP addr is preferred */
+ SYSTEM_EVENT_ETH_START, /**< ESP32 ethernet start */
+ SYSTEM_EVENT_ETH_STOP, /**< ESP32 ethernet stop */
+ SYSTEM_EVENT_ETH_CONNECTED, /**< ESP32 ethernet phy link up */
+ SYSTEM_EVENT_ETH_DISCONNECTED, /**< ESP32 ethernet phy link down */
+ SYSTEM_EVENT_ETH_GOT_IP, /**< ESP32 ethernet got IP from connected AP */
+ SYSTEM_EVENT_MAX
+} system_event_id_t;
+
+typedef struct {
+ system_event_id_t event_id; /**< event ID */
+ //system_event_info_t event_info; /**< event information */
+} system_event_t;
+
+/**
+ * @brief Application specified event callback function
+ *
+ * @param void *ctx : reserved for user
+ * @param system_event_t *event : event type defined in this file
+ *
+ * @return ESP_OK : succeed
+ * @return others : fail
+ */
+typedef esp_err_t (*system_event_cb_t)(void *ctx, system_event_t *event);
+
+/**
+ * @brief Initialize event loop
+ * Create the event handler and task
+ *
+ * @param system_event_cb_t cb : application specified event callback, it can be modified by call esp_event_set_cb
+ * @param void *ctx : reserved for user
+ *
+ * @return ESP_OK : succeed
+ * @return others : fail
+ */
+esp_err_t esp_event_loop_init(system_event_cb_t cb, void *ctx);
+
+typedef bss_info wifi_ap_record_t;
+
+/**
+ * @brief Get number of APs found in last scan
+ *
+ * @param[out] number store number of APIs found in last scan
+ *
+ * @attention This API can only be called when the scan is completed, otherwise it may get wrong value.
+ *
+ * @return
+ * - ESP_OK: succeed
+ * - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by eps_wifi_init
+ * - ESP_ERR_WIFI_NOT_STARTED: WiFi is not started by esp_wifi_start
+ * - ESP_ERR_WIFI_ARG: invalid argument
+ */
+esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number);
+
+/**
+ * @brief Get AP list found in last scan
+ *
+ * @param[inout] number As input param, it stores max AP number ap_records can hold.
+ * As output param, it receives the actual AP number this API returns.
+ * @param ap_records wifi_ap_record_t array to hold the found APs
+ *
+ * @return
+ * - ESP_OK: succeed
+ * - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by eps_wifi_init
+ * - ESP_ERR_WIFI_NOT_STARTED: WiFi is not started by esp_wifi_start
+ * - ESP_ERR_WIFI_ARG: invalid argument
+ * - ESP_ERR_WIFI_NO_MEM: out of memory
+ */
+esp_err_t esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records);
+
+
+// Fields are similar except esp32
+typedef scan_config wifi_scan_config_t;
+/**
+ * @brief Scan all available APs.
+ *
+ * @attention If this API is called, the found APs are stored in WiFi driver dynamic allocated memory and the
+ * will be freed in esp_wifi_get_ap_list, so generally, call esp_wifi_get_ap_list to cause
+ * the memory to be freed once the scan is done
+ * @attention The values of maximum active scan time and passive scan time per channel are limited to 1500 milliseconds.
+ * Values above 1500ms may cause station to disconnect from AP and are not recommended.
+ *
+ * @param config configuration of scanning
+ * @param block if block is true, this API will block the caller until the scan is done, otherwise
+ * it will return immediately
+ *
+ * @return
+ * - ESP_OK: succeed
+ * - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by eps_wifi_init
+ * - ESP_ERR_WIFI_NOT_STARTED: WiFi was not started by esp_wifi_start
+ * - ESP_ERR_WIFI_TIMEOUT: blocking scan is timeout
+ * - others: refer to error code in esp_err.h
+ */
+esp_err_t esp_wifi_scan_start(wifi_scan_config_t *config, bool block);
+
+// Disconnection station
+esp_err_t esp_wifi_disconnect();
+esp_err_t esp_wifi_set_auto_connect(bool en);
+
+typedef struct {} wifi_init_config_t;
+#define WIFI_INIT_CONFIG_DEFAULT() wifi_init_config_t();
+esp_err_t esp_wifi_init(wifi_init_config_t *config);
+
+esp_err_t esp_wifi_deinit();
+
+esp_err_t esp_wifi_start();
+
+esp_err_t esp_wifi_stop();
+
+typedef enum {
+ TCPIP_ADAPTER_IF_STA = STATION_IF, /**< ESP32 station interface */
+ TCPIP_ADAPTER_IF_AP = SOFTAP_IF//, /**< ESP32 soft-AP interface */
+ //TCPIP_ADAPTER_IF_ETH, /**< ESP32 ethernet interface */
+ //TCPIP_ADAPTER_IF_MAX
+} tcpip_adapter_if_t;
+
+typedef ip_info tcpip_adapter_ip_info_t;
+typedef ip_addr_t ip4_addr_t;
+
+void tcpip_adapter_init();
+
+esp_err_t tcpip_adapter_get_ip_info(tcpip_adapter_if_t tcpip_if, tcpip_adapter_ip_info_t *ip_info);
+
+esp_err_t tcpip_adapter_set_ip_info(tcpip_adapter_if_t tcpip_if, tcpip_adapter_ip_info_t *ip_info);
+
+esp_err_t tcpip_adapter_dhcps_start(tcpip_adapter_if_t tcpip_if);
+
+esp_err_t tcpip_adapter_dhcps_stop(tcpip_adapter_if_t tcpip_if);
+
+esp_err_t tcpip_adapter_set_hostname(tcpip_adapter_if_t tcpip_if, const char *hostname);
+
+typedef softap_config wifi_ap_config_t;
+typedef station_config wifi_sta_config_t;
+
+typedef union {
+ wifi_ap_config_t ap; /**< configuration of AP */
+ wifi_sta_config_t sta; /**< configuration of STA */
+} wifi_config_t;
+
+typedef enum {
+ ESP_IF_WIFI_STA = STATION_IF, /**< ESP32 station interface */
+ ESP_IF_WIFI_AP = SOFTAP_IF, /**< ESP32 soft-AP interface */
+} wifi_interface_t;
+
+esp_err_t esp_wifi_set_config(wifi_interface_t ifx, wifi_config_t *conf);
+
+esp_err_t esp_wifi_get_config(wifi_interface_t ifx, wifi_config_t *conf);
+
+esp_err_t esp_wifi_connect();
+
+esp_err_t esp_wifi_get_mac(wifi_interface_t ifx, uint8_t mac[6]);
+
+esp_err_t esp_wifi_set_protocol(wifi_interface_t ifx, uint8_t protocol_bitmap);
+
+#else
+#ifdef ESP32
+#define ICACHE_FLASH_ATTR
+extern "C" {
+#include "esp_wifi.h"
+#include "esp_event.h"
+#include "esp_event_loop.h"
+}
+#else
+#error Only ESP8266 or ESP32 platform is allowed
+#endif
+#endif
+#endif
+
+
diff --git a/src/painlessMesh.cpp b/src/painlessMesh.cpp
new file mode 100644
index 0000000..e800e7a
--- /dev/null
+++ b/src/painlessMesh.cpp
@@ -0,0 +1,141 @@
+#include
+#include
+
+#include "painlessMesh.h"
+#include "painlessMeshSync.h"
+
+#include "lwip/init.h"
+
+painlessMesh* staticThis;
+uint16_t count = 0;
+
+// general functions
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::init(String ssid, String password, uint16_t port, nodeMode connectMode, wifi_auth_mode_t authmode, uint8_t channel, uint8_t phymode, uint8_t maxtpw, uint8_t hidden, uint8_t maxconn) {
+ // shut everything down, start with a blank slate.
+
+ randomSeed(analogRead(A0)); // Init random generator seed to generate delay variance
+
+ tcpip_adapter_init();
+
+ wifi_init_config_t init_config = WIFI_INIT_CONFIG_DEFAULT();
+ if ((esp_wifi_init(&init_config)) != ESP_OK) {
+ //debugMsg(ERROR, "Station is doing something... wierd!? status=%d\n", err);
+ }
+ debugMsg(STARTUP, "init(): %d\n", esp_wifi_set_auto_connect(false)); // Disable autoconnect
+
+ if (connectMode == AP_ONLY || connectMode == STA_AP)
+ tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); // Disable ESP8266 Soft-AP DHCP server
+
+ // Should check whether AP_ONLY etc.
+ esp_wifi_set_protocol(ESP_IF_WIFI_STA, phymode);
+ esp_wifi_set_protocol(ESP_IF_WIFI_AP, phymode);
+#ifdef ESP8266
+ system_phy_set_max_tpw(maxtpw); //maximum value of RF Tx Power, unit : 0.25dBm, range [0,82]
+#endif
+ esp_event_loop_init(espWifiEventCb, NULL);
+
+ staticThis = this; // provides a way for static callback methods to access "this" object;
+
+ // start configuration
+ switch (connectMode) {
+ case STA_ONLY:
+ debugMsg(GENERAL, "esp_wifi_set_mode(STATION_MODE) succeeded? %d\n", esp_wifi_set_mode(WIFI_MODE_STA) == ESP_OK);
+ break;
+ case AP_ONLY:
+ debugMsg(GENERAL, "esp_wifi_set_mode(AP_MODE) succeeded? %d\n", esp_wifi_set_mode(WIFI_MODE_AP) == ESP_OK);
+ break;
+ default:
+ debugMsg(GENERAL, "esp_wifi_set_mode(STATION_AP_MODE) succeeded? %d\n", esp_wifi_set_mode(WIFI_MODE_APSTA) == ESP_OK);
+ }
+
+ _meshSSID = ssid;
+ _meshPassword = password;
+ _meshPort = port;
+ _meshChannel = channel;
+ _meshAuthMode = authmode;
+ if (password == "")
+ _meshAuthMode = WIFI_AUTH_OPEN; //if no password ... set auth mode to open
+ _meshHidden = hidden;
+ _meshMaxConn = maxconn;
+
+ uint8_t MAC[] = { 0,0,0,0,0,0 };
+ if (esp_wifi_get_mac(ESP_IF_WIFI_AP, MAC) != ESP_OK) {
+ debugMsg(ERROR, "init(): esp_wifi_get_mac failed.\n");
+ }
+ esp_wifi_start();
+ _nodeId = encodeNodeId(MAC);
+
+ if (connectMode == AP_ONLY || connectMode == STA_AP)
+ apInit(); // setup AP
+ if (connectMode == STA_ONLY || connectMode == STA_AP) {
+ stationScan.init(this, ssid, password, port);
+ scheduler.addTask(stationScan.task);
+ }
+
+ //debugMsg(STARTUP, "init(): tcp_max_con=%u, nodeId = %u\n", espconn_tcp_get_max_con(), _nodeId);
+
+
+ scheduler.enableAll();
+}
+
+void ICACHE_FLASH_ATTR painlessMesh::stop() {
+ // Close all connections
+ while (_connections.size() > 0) {
+ auto connection = _connections.begin();
+ (*connection)->close();
+ }
+
+ // Stop scanning task
+ stationScan.task.setCallback(NULL);
+ scheduler.deleteTask(stationScan.task);
+
+ // Note that this results in the droppedConnections not to be signalled
+ // We might want to change this later
+ newConnectionTask.setCallback(NULL);
+ scheduler.deleteTask(newConnectionTask);
+ droppedConnectionTask.setCallback(NULL);
+ scheduler.deleteTask(droppedConnectionTask);
+
+ // Shutdown wifi hardware
+ esp_wifi_disconnect();
+ tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); // Disable ESP8266 Soft-AP DHCP server
+ esp_wifi_stop();
+ esp_wifi_deinit();
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::update(void) {
+ scheduler.execute();
+ return;
+}
+
+//***********************************************************************
+bool ICACHE_FLASH_ATTR painlessMesh::sendSingle(uint32_t &destId, String &msg) {
+ debugMsg(COMMUNICATION, "sendSingle(): dest=%u msg=%s\n", destId, msg.c_str());
+ return sendMessage(destId, _nodeId, SINGLE, msg);
+}
+
+//***********************************************************************
+bool ICACHE_FLASH_ATTR painlessMesh::sendBroadcast(String &msg) {
+ debugMsg(COMMUNICATION, "sendBroadcast(): msg=%s\n", msg.c_str());
+ return broadcastMessage(_nodeId, BROADCAST, msg);
+}
+
+bool ICACHE_FLASH_ATTR painlessMesh::startDelayMeas(uint32_t nodeId) {
+ String timeStamp;
+ debugMsg(S_TIME, "startDelayMeas(): NodeId %u\n", nodeId);
+
+ auto conn = findConnection(nodeId);
+
+ if (conn) {
+ timeStamp = conn->time.buildTimeStamp(TIME_REQUEST, getNodeTime());
+ //conn->timeDelayStatus = IN_PROGRESS;
+ } else {
+ return false;
+ }
+ debugMsg(S_TIME, "startDelayMeas(): Sent delay calc request -> %s\n", timeStamp.c_str());
+ sendMessage(conn, nodeId, _nodeId, TIME_DELAY, timeStamp);
+ //conn->timeSyncLastRequested = system_get_time();
+ return true;
+}
diff --git a/src/painlessMesh.h b/src/painlessMesh.h
new file mode 100644
index 0000000..a940694
--- /dev/null
+++ b/src/painlessMesh.h
@@ -0,0 +1,198 @@
+#ifndef _EASY_MESH_H_
+#define _EASY_MESH_H_
+
+#define _TASK_STD_FUNCTION
+
+#include
+#include
+#include
+#include
+#include
+#include
+using namespace std;
+#include "espInterface.h"
+#include "AsyncTCP.h"
+
+#include "painlessMeshSync.h"
+#include "painlessMeshSTA.h"
+#include "painlessMeshConnection.h"
+
+#define NODE_TIMEOUT 10*TASK_SECOND
+#define MIN_FREE_MEMORY 16000 // Minimum free memory, besides here all packets in queue are discarded.
+#define MAX_MESSAGE_QUEUE 50 // MAX number of unsent messages in queue. Newer messages are discarded
+#define MAX_CONSECUTIVE_SEND 5 // Max message busrt
+
+enum nodeMode {
+ AP_ONLY = WIFI_MODE_AP,
+ STA_ONLY = WIFI_MODE_STA,
+ STA_AP = WIFI_MODE_APSTA
+};
+
+enum meshPackageType {
+ TIME_DELAY = 3,
+ TIME_SYNC = 4,
+ NODE_SYNC_REQUEST = 5,
+ NODE_SYNC_REPLY = 6,
+ CONTROL = 7, //deprecated
+ BROADCAST = 8, //application data for everyone
+ SINGLE = 9 //application data for a single node
+};
+
+template
+using SimpleList = std::list; // backward compatibility
+
+typedef int debugType;
+
+#define ERROR 1
+#define STARTUP 1<<1
+#define MESH_STATUS 1<<2
+#define CONNECTION 1<<3
+#define SYNC 1<<4
+#define S_TIME 1<<5
+#define COMMUNICATION 1<<6
+#define GENERAL 1<<7
+#define MSG_TYPES 1<<8
+#define REMOTE 1<<9 // not yet implemented
+#define APPLICATION 1<<10
+#define DEBUG 1<<11
+
+using ConnectionList = std::list>;
+
+typedef std::function newConnectionCallback_t;
+typedef std::function droppedConnectionCallback_t;
+typedef std::function receivedCallback_t;
+typedef std::function changedConnectionsCallback_t;
+typedef std::function nodeTimeAdjustedCallback_t;
+typedef std::function nodeDelayCallback_t;
+
+class painlessMesh {
+public:
+ //inline functions
+ uint32_t getNodeId(void) { return _nodeId; };
+
+ // in painlessMeshDebug.cpp
+ void setDebugMsgTypes(uint16_t types);
+ void debugMsg(debugType type, const char* format ...);
+
+ // in painlessMesh.cpp
+#ifdef ESP32
+ void init(String ssid, String password, uint16_t port = 5555, enum nodeMode connectMode = STA_AP, wifi_auth_mode_t authmode = WIFI_AUTH_WPA2_PSK, uint8_t channel = 1, uint8_t phymode = WIFI_PROTOCOL_11G, uint8_t maxtpw = 82, uint8_t hidden = 0, uint8_t maxconn = 10);
+#else
+ void init(String ssid, String password, uint16_t port = 5555, enum nodeMode connectMode = STA_AP, wifi_auth_mode_t authmode = WIFI_AUTH_WPA2_PSK, uint8_t channel = 1, uint8_t phymode = WIFI_PROTOCOL_11G, uint8_t maxtpw = 82, uint8_t hidden = 0, uint8_t maxconn = 4);
+#endif
+ /**
+ * Disconnect and stop this node
+ */
+ void stop();
+ void update(void);
+ bool sendSingle(uint32_t &destId, String &msg);
+ bool sendBroadcast(String &msg);
+ bool startDelayMeas(uint32_t nodeId);
+
+ // in painlessMeshConnection.cpp
+ void onReceive(receivedCallback_t onReceive);
+ void onNewConnection(newConnectionCallback_t onNewConnection);
+ void onDroppedConnection(droppedConnectionCallback_t onDroppedConnection);
+ void onChangedConnections(changedConnectionsCallback_t onChangedConnections);
+ void onNodeTimeAdjusted(nodeTimeAdjustedCallback_t onTimeAdjusted);
+ void onNodeDelayReceived(nodeDelayCallback_t onDelayReceived);
+ String subConnectionJson() { return subConnectionJson(NULL); }
+ bool isConnected(uint32_t nodeId) { return findConnection(nodeId) != NULL; }
+ std::list getNodeList();
+
+ // in painlessMeshSync.cpp
+ uint32_t getNodeTime(void);
+
+ // in painlessMeshSTA.cpp
+ uint32_t encodeNodeId(uint8_t *hwaddr);
+ /// Connect (as a station) to a specified network and ip
+ /// You can pass {0,0,0,0} as IP to have it connect to the gateway
+ void stationManual(String ssid, String password, uint16_t port = 0,
+ uint8_t * remote_ip = NULL);
+ bool setHostname(const char * hostname);
+ ip4_addr_t getStationIP();
+
+ Scheduler scheduler;
+ StationScan stationScan;
+
+ // Rough estimate of the mesh stability (goes from 0-1000)
+ size_t stability = 0;
+
+#ifndef UNITY // Make everything public in unit test mode
+protected:
+#endif
+ // in painlessMeshComm.cpp
+ //must be accessable from callback
+ bool sendMessage(std::shared_ptr conn, uint32_t destId, uint32_t fromId, meshPackageType type, String &msg, bool priority = false);
+ bool sendMessage(uint32_t destId, uint32_t fromId, meshPackageType type, String &msg, bool priority = false);
+ bool broadcastMessage(uint32_t fromId, meshPackageType type, String &msg, std::shared_ptr exclude = NULL);
+
+ String buildMeshPackage(uint32_t destId, uint32_t fromId, meshPackageType type, String &msg);
+
+
+ // in painlessMeshSync.cpp
+ //must be accessable from callback
+ void handleNodeSync(std::shared_ptr conn, JsonObject& root);
+ void startTimeSync(std::shared_ptr conn);
+ void handleTimeSync(std::shared_ptr conn, JsonObject& root, uint32_t receivedAt);
+ void handleTimeDelay(std::shared_ptr conn, JsonObject& root, uint32_t receivedAt);
+ bool adoptionCalc(std::shared_ptr conn);
+
+ // in painlessMeshConnection.cpp
+ //void cleanDeadConnections(void); // Not implemented. Needed?
+ void tcpConnect(void);
+ bool closeConnectionSTA();
+
+ void eraseClosedConnections();
+
+ String subConnectionJson(std::shared_ptr exclude);
+ String subConnectionJsonHelper(
+ ConnectionList &connections,
+ uint32_t exclude = 0);
+ size_t approxNoNodes(); // estimate of numbers of node
+ size_t approxNoNodes(String &subConns); // estimate of numbers of node
+ shared_ptr findConnection(uint32_t nodeId, uint32_t exclude = 0);
+ shared_ptr findConnection(AsyncClient *conn);
+
+ // in painlessMeshAP.cpp
+ void apInit(void);
+
+ void tcpServerInit();
+
+ // callbacks
+ // in painlessMeshConnection.cpp
+ static int espWifiEventCb(void * ctx, system_event_t *event);
+
+ // Callback functions
+ newConnectionCallback_t newConnectionCallback;
+ droppedConnectionCallback_t droppedConnectionCallback;
+ receivedCallback_t receivedCallback;
+ changedConnectionsCallback_t changedConnectionsCallback;
+ nodeTimeAdjustedCallback_t nodeTimeAdjustedCallback;
+ nodeDelayCallback_t nodeDelayReceivedCallback;
+
+ // variables
+ uint32_t _nodeId;
+ String _meshSSID;
+ String _meshPassword;
+ uint16_t _meshPort;
+ uint8_t _meshChannel;
+ wifi_auth_mode_t _meshAuthMode;
+ uint8_t _meshHidden;
+ uint8_t _meshMaxConn;
+
+ ConnectionList _connections;
+
+ AsyncServer *_tcpListener;
+
+ bool _station_got_ip = false;
+
+ Task droppedConnectionTask;
+ Task newConnectionTask;
+
+ friend class StationScan;
+ friend class MeshConnection;
+ friend void onDataCb(void * arg, AsyncClient *client, void *data, size_t len);
+};
+
+#endif // _EASY_MESH_H_
diff --git a/src/painlessMeshAP.cpp b/src/painlessMeshAP.cpp
new file mode 100644
index 0000000..8be04aa
--- /dev/null
+++ b/src/painlessMeshAP.cpp
@@ -0,0 +1,82 @@
+//
+// painlessMeshAP.cpp
+//
+//
+// Created by Bill Gray on 7/26/16.
+//
+//
+
+#include
+
+#include "painlessMesh.h"
+
+extern painlessMesh* staticThis;
+
+// AP functions
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::apInit(void) {
+ // String password( MESH_PASSWORD );
+
+ ip4_addr_t ip, netmask;
+ IP4_ADDR(&ip, 10, (_nodeId & 0xFF00) >> 8, (_nodeId & 0xFF), 1);
+ IP4_ADDR(&netmask, 255, 255, 255, 0);
+
+ tcpip_adapter_ip_info_t ipInfo;
+ ipInfo.ip = ip;
+ ipInfo.gw = ip;
+ ipInfo.netmask = netmask;
+ if (tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo) != ESP_OK) {
+ debugMsg(ERROR, "tcpip_adapter_set_ip_info() failed\n");
+ }
+
+ /*
+ debugMsg(STARTUP, "apInit(): Starting AP with SSID=%s IP=%d.%d.%d.%d GW=%d.%d.%d.%d NM=%d.%d.%d.%d\n",
+ _meshSSID.c_str(),
+ IP2STR(&ipInfo.ip),
+ IP2STR(&ipInfo.gw),
+ IP2STR(&ipInfo.netmask));
+ */
+
+ wifi_config_t apConfig;
+ esp_wifi_get_config(ESP_IF_WIFI_AP, &apConfig);
+
+ memset(apConfig.ap.ssid, 0, 32);
+ memset(apConfig.ap.password, 0, 64);
+ memcpy(apConfig.ap.ssid, _meshSSID.c_str(), _meshSSID.length());
+ memcpy(apConfig.ap.password, _meshPassword.c_str(), _meshPassword.length());
+
+ apConfig.ap.authmode = _meshAuthMode; // AUTH_WPA2_PSK
+ apConfig.ap.ssid_len = _meshSSID.length();
+ apConfig.ap.ssid_hidden = _meshHidden;
+ apConfig.ap.channel = _meshChannel;
+ apConfig.ap.beacon_interval = 100;
+ apConfig.ap.max_connection = _meshMaxConn; // how many stations can connect to ESP8266 softAP at most, max is 4
+
+ esp_wifi_set_config(ESP_IF_WIFI_AP, &apConfig);// Set ESP8266 softap config .
+ if (tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP) != ESP_OK)
+ debugMsg(ERROR, "DHCP server failed\n");
+ else
+ debugMsg(STARTUP, "DHCP server started\n");
+
+ // establish AP tcpServers
+ tcpServerInit();
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::tcpServerInit() {
+ debugMsg(GENERAL, "tcpServerInit():\n");
+
+ _tcpListener = new AsyncServer(_meshPort);
+ _tcpListener->setNoDelay(true);
+
+ _tcpListener->onClient([](void * arg, AsyncClient *client) {
+ staticThis->debugMsg(CONNECTION, "New AP connection incoming\n");
+ auto conn = std::make_shared(client, staticThis, false);
+ staticThis->_connections.push_back(conn);
+ }, NULL);
+
+ _tcpListener->begin();
+
+ debugMsg(STARTUP, "AP tcp server established on port %d\n", _meshPort);
+ return;
+}
diff --git a/src/painlessMeshComm.cpp b/src/painlessMeshComm.cpp
new file mode 100644
index 0000000..ec379ee
--- /dev/null
+++ b/src/painlessMeshComm.cpp
@@ -0,0 +1,99 @@
+//
+// painlessMeshComm.cpp
+//
+//
+// Created by Bill Gray on 7/26/16.
+//
+//
+
+#include
+#include
+#include "painlessMesh.h"
+
+extern painlessMesh* staticThis;
+
+// communications functions
+//***********************************************************************
+bool ICACHE_FLASH_ATTR painlessMesh::sendMessage(std::shared_ptr conn, uint32_t destId, uint32_t fromId, meshPackageType type, String &msg, bool priority) {
+ debugMsg(COMMUNICATION, "sendMessage(conn): conn-nodeId=%u destId=%u type=%d msg=%s\n", conn->nodeId, destId, (uint8_t)type, msg.c_str());
+
+ String package = buildMeshPackage(destId, fromId, type, msg);
+
+ return conn->addMessage(package, priority);
+}
+
+//***********************************************************************
+bool ICACHE_FLASH_ATTR painlessMesh::sendMessage(uint32_t destId, uint32_t fromId, meshPackageType type, String &msg, bool priority) {
+ debugMsg(COMMUNICATION, "In sendMessage(destId): destId=%u type=%d, msg=%s\n",
+ destId, type, msg.c_str());
+
+ std::shared_ptr conn = findConnection(destId);
+ if (conn) {
+ return sendMessage(conn, destId, fromId, type, msg, priority);
+ } else {
+ debugMsg(ERROR, "In sendMessage(destId): findConnection( %u ) failed\n", destId);
+ return false;
+ }
+}
+
+
+//***********************************************************************
+bool ICACHE_FLASH_ATTR painlessMesh::broadcastMessage(
+ uint32_t from,
+ meshPackageType type,
+ String &msg,
+ std::shared_ptr exclude) {
+
+ // send a message to every node on the mesh
+ bool errCode = false;
+
+ if (exclude != NULL)
+ debugMsg(COMMUNICATION, "broadcastMessage(): from=%u type=%d, msg=%s exclude=%u\n", from, type, msg.c_str(), exclude->nodeId);
+ else
+ debugMsg(COMMUNICATION, "broadcastMessage(): from=%u type=%d, msg=%s exclude=NULL\n", from, type, msg.c_str());
+
+ auto connection = _connections.begin();
+ if (_connections.size() > 0)
+ errCode = true; // Assume true if at least one connections
+ for (auto &&connection : _connections) {
+ if (!exclude || connection->nodeId != exclude->nodeId) {
+ if (!sendMessage(connection, connection->nodeId, from, type, msg))
+ errCode = false; // If any error return 0
+ }
+ }
+ return errCode;
+}
+
+//***********************************************************************
+String ICACHE_FLASH_ATTR painlessMesh::buildMeshPackage(uint32_t destId, uint32_t fromId, meshPackageType type, String &msg) {
+ debugMsg(GENERAL, "In buildMeshPackage(): msg=%s\n", msg.c_str());
+
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& root = jsonBuffer.createObject();
+ root["dest"] = destId;
+ //root["from"] = _nodeId;
+ root["from"] = fromId;
+ root["type"] = (uint8_t)type;
+
+ switch (type) {
+ case NODE_SYNC_REQUEST:
+ case NODE_SYNC_REPLY:
+ {
+ JsonArray& subs = jsonBuffer.parseArray(msg);
+ if (!subs.success()) {
+ debugMsg(GENERAL, "buildMeshPackage(): subs = jsonBuffer.parseArray( msg ) failed!");
+ }
+ root["subs"] = subs;
+ break;
+ }
+ case TIME_SYNC:
+ root["msg"] = jsonBuffer.parseObject(msg);
+ break;
+ default:
+ root["msg"] = msg;
+ }
+
+ String ret;
+ root.printTo(ret);
+ return ret;
+}
diff --git a/src/painlessMeshConnection.cpp b/src/painlessMeshConnection.cpp
new file mode 100644
index 0000000..11eadb3
--- /dev/null
+++ b/src/painlessMeshConnection.cpp
@@ -0,0 +1,648 @@
+//
+// painlessMeshConnection.cpp
+//
+//
+// Created by Bill Gray on 7/26/16.
+//
+//
+
+#include
+#include
+
+#include "painlessMesh.h"
+
+//#include "lwip/priv/tcpip_priv.h"
+
+extern painlessMesh* staticThis;
+
+static temp_buffer_t shared_buffer;
+
+ICACHE_FLASH_ATTR ReceiveBuffer::ReceiveBuffer() {
+ buffer = String();
+}
+
+void ICACHE_FLASH_ATTR ReceiveBuffer::push(const char * cstr,
+ size_t length, temp_buffer_t &buf) {
+ auto data_ptr = cstr;
+ do {
+ auto len = strnlen(data_ptr, length);
+ do {
+ auto read_len = min(len, buf.length);
+ memcpy(buf.buffer, data_ptr, read_len);
+ buf.buffer[read_len] = '\0';
+ auto newBuffer = String(buf.buffer);
+ buffer.concat(newBuffer);
+ len -= newBuffer.length();
+ length -= newBuffer.length();
+ data_ptr += newBuffer.length()*sizeof(char);
+ } while (len > 0);
+ if (length > 0) {
+ length -= 1;
+ data_ptr += 1*sizeof(char);
+ if (buffer.length() > 0) { // skip empty buffers
+ jsonStrings.push_back(buffer);
+ buffer = String();
+ }
+ }
+ } while (length > 0);
+ staticThis->debugMsg(COMMUNICATION, "ReceiveBuffer::push(): buffer size=%u, %u\n", jsonStrings.size(), buffer.length());
+}
+
+String ICACHE_FLASH_ATTR ReceiveBuffer::front() {
+ if (!empty())
+ return (*jsonStrings.begin());
+ return String();
+}
+
+void ICACHE_FLASH_ATTR ReceiveBuffer::pop_front() {
+ jsonStrings.pop_front();
+}
+
+bool ICACHE_FLASH_ATTR ReceiveBuffer::empty() {
+ return jsonStrings.empty();
+}
+
+void ICACHE_FLASH_ATTR ReceiveBuffer::clear() {
+ jsonStrings.clear();
+ buffer = String();
+}
+
+ICACHE_FLASH_ATTR SentBuffer::SentBuffer() {};
+
+size_t ICACHE_FLASH_ATTR SentBuffer::requestLength(size_t buffer_length) {
+ if (jsonStrings.empty())
+ return 0;
+ else
+ return min(buffer_length - 1, jsonStrings.begin()->length() + 1);
+}
+
+void ICACHE_FLASH_ATTR SentBuffer::push(String &message, bool priority) {
+ if (priority)
+ jsonStrings.push_front(message);
+ else
+ jsonStrings.push_back(message);
+}
+
+void ICACHE_FLASH_ATTR SentBuffer::read(size_t length, temp_buffer_t &buf) {
+ jsonStrings.front().toCharArray(buf.buffer, length + 1);
+ last_read_size = length;
+}
+
+void ICACHE_FLASH_ATTR SentBuffer::freeRead() {
+ staticThis->debugMsg(COMMUNICATION, "SentBuffer::freeRead(): %u\n", last_read_size);
+ if (last_read_size == jsonStrings.begin()->length() + 1)
+ jsonStrings.pop_front();
+ else
+ jsonStrings.begin()->remove(0, last_read_size);
+ last_read_size = 0;
+}
+
+bool ICACHE_FLASH_ATTR SentBuffer::empty() {
+ return jsonStrings.empty();
+}
+
+void ICACHE_FLASH_ATTR SentBuffer::clear() {
+ jsonStrings.clear();
+}
+
+
+void meshRecvCb(void * arg, AsyncClient *, void * data, size_t len);
+void tcpSentCb(void * arg, AsyncClient * tpcb, size_t len, uint32_t time);
+
+ICACHE_FLASH_ATTR MeshConnection::MeshConnection(AsyncClient *client_ptr, painlessMesh *pMesh, bool is_station) {
+ station = is_station;
+ mesh = pMesh;
+ client = client_ptr;
+
+ client->setNoDelay(true);
+ client->setRxTimeout(NODE_TIMEOUT/TASK_SECOND);
+
+ //tcp_arg(pcb, static_cast(this));
+ auto arg = static_cast(this);
+ client->onData(meshRecvCb, arg);
+
+ client->onAck(tcpSentCb, arg);
+
+ if (station) { // we are the station, start nodeSync
+ staticThis->debugMsg(CONNECTION, "meshConnectedCb(): we are STA\n");
+ } else {
+ staticThis->debugMsg(CONNECTION, "meshConnectedCb(): we are AP\n");
+ }
+
+ client->onError([](void * arg, AsyncClient *client, int8_t err) {
+ staticThis->debugMsg(CONNECTION, "tcp_err(): MeshConnection %s\n", client->errorToString(err));
+ }, arg);
+
+ client->onDisconnect([](void *arg, AsyncClient *client) {
+ if (arg == NULL) {
+ staticThis->debugMsg(CONNECTION, "onDisconnect(): MeshConnection NULL\n");
+ if (client->connected())
+ client->close(true);
+ return;
+ }
+ auto conn = static_cast(arg);
+ staticThis->debugMsg(CONNECTION, "onDisconnect():\n");
+ staticThis->debugMsg(CONNECTION, "onDisconnect(): dropping %u now= %u\n", conn->nodeId, staticThis->getNodeTime());
+ conn->close();
+ }, arg);
+
+ auto syncInterval = NODE_TIMEOUT/2;
+ if (!station)
+ syncInterval = NODE_TIMEOUT*2;
+
+ this->nodeSyncTask.set(
+ syncInterval, TASK_FOREVER, [this](){
+ staticThis->debugMsg(SYNC, "nodeSyncTask(): \n");
+ staticThis->debugMsg(SYNC, "nodeSyncTask(): request with %u\n",
+ this->nodeId);
+ auto saveConn = staticThis->findConnection(client);
+ String subs = staticThis->subConnectionJson(saveConn);
+ staticThis->sendMessage(saveConn, this->nodeId,
+ staticThis->_nodeId, NODE_SYNC_REQUEST, subs, true);
+ });
+ staticThis->scheduler.addTask(this->nodeSyncTask);
+ if (station)
+ this->nodeSyncTask.enable();
+ else
+ this->nodeSyncTask.enableDelayed();
+
+ receiveBuffer = ReceiveBuffer();
+ readBufferTask.set(100*TASK_MILLISECOND, TASK_FOREVER, [this]() {
+ if (!this->receiveBuffer.empty()) {
+ String frnt = this->receiveBuffer.front();
+ this->handleMessage(frnt, staticThis->getNodeTime());
+ this->receiveBuffer.pop_front();
+ if (!this->receiveBuffer.empty())
+ this->readBufferTask.forceNextIteration();
+ }
+ });
+ staticThis->scheduler.addTask(readBufferTask);
+ readBufferTask.enableDelayed();
+
+ staticThis->debugMsg(GENERAL, "MeshConnection(): leaving\n");
+}
+
+ICACHE_FLASH_ATTR MeshConnection::~MeshConnection() {
+ staticThis->debugMsg(CONNECTION, "~MeshConnection():\n");
+ this->close();
+ if (!client->freeable()) {
+ mesh->debugMsg(CONNECTION, "~MeshConnection(): Closing pcb\n");
+ client->close(true);
+ }
+ client->abort();
+ delete client;
+ /*if (esp_conn)
+ espconn_disconnect(esp_conn);*/
+}
+
+void ICACHE_FLASH_ATTR MeshConnection::close() {
+ if (!connected)
+ return;
+
+ staticThis->debugMsg(CONNECTION, "MeshConnection::close().\n");
+ this->connected = false;
+
+ this->timeSyncTask.setCallback(NULL);
+ this->nodeSyncTask.setCallback(NULL);
+ this->readBufferTask.setCallback(NULL);
+ this->nodeId = 0;
+
+ auto nodeId = this->nodeId;
+
+ mesh->droppedConnectionTask.set(TASK_SECOND, TASK_ONCE, [nodeId]() {
+ staticThis->debugMsg(CONNECTION, "closingTask():\n");
+ staticThis->debugMsg(CONNECTION, "closingTask(): dropping %u now= %u\n", nodeId, staticThis->getNodeTime());
+ if (staticThis->changedConnectionsCallback)
+ staticThis->changedConnectionsCallback(); // Connection dropped. Signal user
+ if (staticThis->droppedConnectionCallback)
+ staticThis->droppedConnectionCallback(nodeId); // Connection dropped. Signal user
+ for (auto &&connection : staticThis->_connections) {
+ if (connection->nodeId != nodeId) { // Exclude current
+ connection->nodeSyncTask.forceNextIteration();
+ }
+ }
+ staticThis->stability /= 2;
+ });
+ mesh->scheduler.addTask(staticThis->droppedConnectionTask);
+ mesh->droppedConnectionTask.enable();
+
+ if (client->connected()) {
+ mesh->debugMsg(CONNECTION, "close(): Closing pcb\n");
+ client->close();
+ }
+
+ if (station) {
+ staticThis->debugMsg(CONNECTION, "close(): call esp_wifi_disconnect().\n");
+ esp_wifi_disconnect();
+ }
+
+ receiveBuffer.clear();
+ sentBuffer.clear();
+
+ mesh->eraseClosedConnections();
+
+ if (station && mesh->_station_got_ip)
+ mesh->_station_got_ip = false;
+}
+
+
+bool ICACHE_FLASH_ATTR MeshConnection::addMessage(String &message, bool priority) {
+ /*
+ mesh->debugMsg(DEBUG, "No connections: %u, sentMessages: %u, receiveMessages: %u, station: %u, canSend: %u, nodeId: %u\n",
+ mesh->_connections.size(), sentBuffer.jsonStrings.size(), receiveBuffer.jsonStrings.size(), station, client->canSend(), nodeId);
+ if (receiveBuffer.jsonStrings.size() > 3)
+ mesh->debugMsg(DEBUG, "Msg %s\n", message.c_str());
+ */
+ if (ESP.getFreeHeap() - message.length() >= MIN_FREE_MEMORY) { // If memory heap is enough, queue the message
+ if (priority) {
+ sentBuffer.push(message, priority);
+ mesh->debugMsg(COMMUNICATION, "addMessage(): Package sent to queue beginning -> %d , FreeMem: %d\n", sentBuffer.jsonStrings.size(), ESP.getFreeHeap());
+ } else {
+ if (sentBuffer.jsonStrings.size() < MAX_MESSAGE_QUEUE) {
+ sentBuffer.push(message, priority);
+ staticThis->debugMsg(COMMUNICATION, "addMessage(): Package sent to queue end -> %d , FreeMem: %d\n", sentBuffer.jsonStrings.size(), ESP.getFreeHeap());
+ } else {
+ staticThis->debugMsg(ERROR, "addMessage(): Message queue full -> %d , FreeMem: %d\n", sentBuffer.jsonStrings.size(), ESP.getFreeHeap());
+ if (client->canSend())
+ writeNext();
+ return false;
+ }
+ }
+ if (client->canSend())
+ writeNext();
+ return true;
+ } else {
+ //connection->sendQueue.clear(); // Discard all messages if free memory is low
+ staticThis->debugMsg(DEBUG, "addMessage(): Memory low, message was discarded\n");
+ if (client->canSend())
+ writeNext();
+ return false;
+ }
+}
+
+bool ICACHE_FLASH_ATTR MeshConnection::writeNext() {
+ if (sentBuffer.empty()) {
+ staticThis->debugMsg(COMMUNICATION, "writeNext(): sendQueue is empty\n");
+ return false;
+ }
+ auto len = sentBuffer.requestLength(shared_buffer.length);
+ auto snd_len = client->space();
+ if (len > snd_len)
+ len = snd_len;
+ if (len > 0) {
+ sentBuffer.read(len, shared_buffer);
+ auto written = client->write(shared_buffer.buffer, len, 1);
+ if (written == len) {
+ staticThis->debugMsg(COMMUNICATION, "writeNext(): Package sent = %s\n", shared_buffer.buffer);
+ client->send(); // TODO only do this for priority messages
+ sentBuffer.freeRead();
+ writeNext();
+ return true;
+ } else if (written == 0) {
+ staticThis->debugMsg(COMMUNICATION, "writeNext(): tcp_write Failed node=%u. Resending later\n", nodeId);
+ return false;
+ } else {
+ staticThis->debugMsg(ERROR, "writeNext(): Less written than requested. Please report bug on the issue tracker\n");
+ return false;
+ }
+ } else {
+ staticThis->debugMsg(COMMUNICATION, "writeNext(): tcp_sndbuf not enough space\n");
+ return false;
+ }
+
+}
+
+// connection managment functions
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::onReceive(receivedCallback_t cb) {
+ debugMsg(GENERAL, "onReceive():\n");
+ receivedCallback = cb;
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::onNewConnection(newConnectionCallback_t cb) {
+ debugMsg(GENERAL, "onNewConnection():\n");
+ newConnectionCallback = cb;
+}
+
+void ICACHE_FLASH_ATTR painlessMesh::onDroppedConnection(droppedConnectionCallback_t cb) {
+ debugMsg(GENERAL, "onDroppedConnection():\n");
+ droppedConnectionCallback = cb;
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::onChangedConnections(changedConnectionsCallback_t cb) {
+ debugMsg(GENERAL, "onChangedConnections():\n");
+ changedConnectionsCallback = cb;
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::onNodeTimeAdjusted(nodeTimeAdjustedCallback_t cb) {
+ debugMsg(GENERAL, "onNodeTimeAdjusted():\n");
+ nodeTimeAdjustedCallback = cb;
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::onNodeDelayReceived(nodeDelayCallback_t cb) {
+ debugMsg(GENERAL, "onNodeDelayReceived():\n");
+ nodeDelayReceivedCallback = cb;
+}
+
+void ICACHE_FLASH_ATTR painlessMesh::eraseClosedConnections() {
+ auto connection = _connections.begin();
+ while (connection != _connections.end()) {
+ if (!(*connection)->connected) {
+ connection = _connections.erase(connection);
+ debugMsg(CONNECTION, "eraseClosedConnections():\n");
+ } else {
+ ++connection;
+ }
+ }
+}
+
+bool ICACHE_FLASH_ATTR painlessMesh::closeConnectionSTA()
+{
+ auto connection = _connections.begin();
+ while (connection != _connections.end()) {
+ if ((*connection)->station) {
+ // We found the STA connection, close it
+ (*connection)->close();
+ return true;
+ }
+ ++connection;
+ }
+ return false;
+}
+
+// Check whether a string contains a numeric substring as a complete number
+//
+// "a:800" does contain "800", but does not contain "80"
+bool ICACHE_FLASH_ATTR stringContainsNumber(const String &subConnections,
+ const String & nodeIdStr, int from = 0) {
+ auto index = subConnections.indexOf(nodeIdStr, from);
+ if (index == -1)
+ return false;
+ // Check that the preceding and following characters are not a number
+ else if (index > 0 &&
+ index + nodeIdStr.length() + 1 < subConnections.length() &&
+ // Preceding character is not a number
+ (subConnections.charAt(index - 1) < '0' ||
+ subConnections.charAt(index - 1) > '9') &&
+ // Following character is not a number
+ (subConnections.charAt(index + nodeIdStr.length() + 1) < '0' ||
+ subConnections.charAt(index + nodeIdStr.length() + 1) > '9')
+ ) {
+ return true;
+ } else { // Check whether the nodeid occurs further in the subConnections string
+ return stringContainsNumber(subConnections, nodeIdStr,
+ index + nodeIdStr.length());
+ }
+ return false;
+}
+
+//***********************************************************************
+// Search for a connection to a given nodeID
+std::shared_ptr ICACHE_FLASH_ATTR painlessMesh::findConnection(uint32_t nodeId, uint32_t exclude) {
+ debugMsg(GENERAL, "In findConnection(nodeId)\n");
+
+ for (auto &&connection : _connections) {
+ if (connection->nodeId == exclude) {
+ debugMsg(GENERAL, "findConnection(%u): Skipping excluded connection\n", nodeId);
+ continue;
+ }
+
+ if (connection->nodeId == nodeId) { // check direct connections
+ debugMsg(GENERAL, "findConnection(%u): Found Direct Connection\n", nodeId);
+ return connection;
+ }
+
+ if (stringContainsNumber(connection->subConnections,
+ String(nodeId))) { // check sub-connections
+ debugMsg(GENERAL, "findConnection(%u): Found Sub Connection through %u\n", nodeId, connection->nodeId);
+ return connection;
+ }
+ }
+ debugMsg(CONNECTION, "findConnection(%u): did not find connection\n", nodeId);
+ return NULL;
+}
+
+//***********************************************************************
+std::shared_ptr ICACHE_FLASH_ATTR painlessMesh::findConnection(AsyncClient *client) {
+ debugMsg(GENERAL, "In findConnection(esp_conn) conn=0x%x\n", client);
+
+ for (auto &&connection : _connections) {
+ if ((*connection->client) == (*client)) {
+ return connection;
+ }
+ }
+
+ debugMsg(CONNECTION, "findConnection(espconn): Did not Find\n");
+ return NULL;
+}
+
+//***********************************************************************
+String ICACHE_FLASH_ATTR painlessMesh::subConnectionJson(std::shared_ptr exclude) {
+ if (exclude == NULL)
+ return subConnectionJsonHelper(_connections);
+ else
+ return subConnectionJsonHelper(_connections, exclude->nodeId);
+}
+
+//***********************************************************************
+String ICACHE_FLASH_ATTR painlessMesh::subConnectionJsonHelper(
+ ConnectionList &connections,
+ uint32_t exclude) {
+ if (exclude != 0)
+ debugMsg(GENERAL, "subConnectionJson(), exclude=%u\n", exclude);
+
+ String ret = "[";
+ for (auto &&sub : connections) {
+ if (!sub->connected) {
+ debugMsg(ERROR, "subConnectionJsonHelper(): Found closed connection %u\n",
+ sub->nodeId);
+ } else if (sub->nodeId != exclude && sub->nodeId != 0) { //exclude connection that we are working with & anything too new.
+ if (ret.length() > 1)
+ ret += String(",");
+ ret += String("{\"nodeId\":") + String(sub->nodeId) +
+ String(",\"subs\":") + sub->subConnections + String("}");
+ }
+ }
+ ret += String("]");
+
+ debugMsg(GENERAL, "subConnectionJson(): ret=%s\n", ret.c_str());
+ return ret;
+}
+
+// Calculating the actual number of connected nodes is fairly expensive,
+// this calculates a cheap approximation
+size_t ICACHE_FLASH_ATTR painlessMesh::approxNoNodes() {
+ debugMsg(GENERAL, "approxNoNodes()\n");
+ auto sc = subConnectionJson();
+ return approxNoNodes(sc);
+}
+
+size_t ICACHE_FLASH_ATTR painlessMesh::approxNoNodes(String &subConns) {
+ return max((long int) 1,round(subConns.length()/30.0));
+}
+
+//***********************************************************************
+std::list ICACHE_FLASH_ATTR painlessMesh::getNodeList() {
+ std::list nodeList;
+
+ String nodeJson = subConnectionJson();
+
+ uint index = 0;
+
+ while (index < nodeJson.length()) {
+ uint comma = 0;
+ index = nodeJson.indexOf("\"nodeId\":");
+ if (index == -1)
+ break;
+ comma = nodeJson.indexOf(',', index);
+ String temp = nodeJson.substring(index + 9, comma);
+ uint32_t id = strtoul(temp.c_str(), NULL, 10);
+ nodeList.push_back(id);
+ index = comma + 1;
+ nodeJson = nodeJson.substring(index);
+ }
+
+ return nodeList;
+
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR tcpSentCb(void * arg, AsyncClient * client, size_t len, uint32_t time) {
+ if (arg == NULL) {
+ staticThis->debugMsg(COMMUNICATION, "tcpSentCb(): no valid connection found\n");
+ }
+ auto conn = static_cast(arg);
+ conn->writeNext();
+}
+
+void ICACHE_FLASH_ATTR meshRecvCb(void * arg, AsyncClient *client, void * data, size_t len) {
+ if (arg == NULL) {
+ staticThis->debugMsg(COMMUNICATION, "meshRecvCb(): no valid connection found\n");
+ }
+ auto receiveConn = static_cast(arg);
+
+ uint32_t receivedAt = staticThis->getNodeTime();
+
+ staticThis->debugMsg(COMMUNICATION, "meshRecvCb(): fromId=%u\n", receiveConn ? receiveConn->nodeId : 0);
+
+ receiveConn->receiveBuffer.push(static_cast(data), len, shared_buffer);
+
+ // Signal that we are done
+ client->ack(len); // ackLater?
+ //client->ackLater();
+ //tcp_recved(receiveConn->pcb, total_length);
+
+ receiveConn->readBufferTask.forceNextIteration();
+}
+
+void ICACHE_FLASH_ATTR MeshConnection::handleMessage(String &buffer, uint32_t receivedAt) {
+ staticThis->debugMsg(COMMUNICATION, "meshRecvCb(): Recvd from %u-->%s<--\n", this->nodeId, buffer.c_str());
+
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& root = jsonBuffer.parseObject(jsonBuffer.strdup(buffer), 100);
+ if (!root.success()) { // Test if parsing succeeded.
+ staticThis->debugMsg(ERROR, "meshRecvCb(): parseObject() failed. total_length=%d, data=%s<--\n", buffer.length(), buffer.c_str());
+ return;
+ }
+
+ String msg = root["msg"];
+ meshPackageType t_message = (meshPackageType)(int)root["type"];
+
+ staticThis->debugMsg(COMMUNICATION, "meshRecvCb(): lastRecieved=%u fromId=%u type=%d\n", staticThis->getNodeTime(), this->nodeId, t_message);
+
+ auto rConn = staticThis->findConnection(this->client);
+ switch (t_message) {
+ case NODE_SYNC_REQUEST:
+ case NODE_SYNC_REPLY:
+ // TODO: These findConnections are not the most efficient way of doing it.
+ staticThis->handleNodeSync(rConn, root);
+ break;
+
+ case TIME_SYNC:
+ staticThis->handleTimeSync(rConn, root, receivedAt);
+ break;
+
+ case SINGLE:
+ case TIME_DELAY:
+ if ((uint32_t)root["dest"] == staticThis->getNodeId()) { // msg for us!
+ if (t_message == TIME_DELAY) {
+ staticThis->handleTimeDelay(rConn, root, receivedAt);
+ } else {
+ if (staticThis->receivedCallback)
+ staticThis->receivedCallback((uint32_t)root["from"], msg);
+ }
+ } else { // pass it along
+ String tempStr;
+ root.printTo(tempStr);
+ auto conn = staticThis->findConnection((uint32_t)root["dest"], this->nodeId);
+ if (conn) {
+ conn->addMessage(tempStr);
+ staticThis->debugMsg(COMMUNICATION, "meshRecvCb(): Message %s to %u forwarded through %u\n", tempStr.c_str(), (uint32_t)root["dest"], conn->nodeId);
+ }
+ }
+ break;
+
+ case BROADCAST:
+ staticThis->broadcastMessage((uint32_t)root["from"], BROADCAST, msg, rConn);
+ if (staticThis->receivedCallback)
+ staticThis->receivedCallback((uint32_t)root["from"], msg);
+ break;
+
+ default:
+ staticThis->debugMsg(ERROR, "meshRecvCb(): unexpected json, root[\"type\"]=%d", (int)root["type"]);
+ }
+ return;
+}
+
+//***********************************************************************
+// Wifi event handler
+int ICACHE_FLASH_ATTR painlessMesh::espWifiEventCb(void * ctx, system_event_t *event) {
+ switch (event->event_id) {
+ case SYSTEM_EVENT_SCAN_DONE:
+ staticThis->debugMsg(CONNECTION, "espWifiEventCb(): SYSTEM_EVENT_SCAN_DONE\n");
+ // Call the same thing original callback called
+ staticThis->stationScan.scanComplete();
+ break;
+ case SYSTEM_EVENT_STA_START:
+ staticThis->stationScan.task.forceNextIteration();
+ staticThis->debugMsg(CONNECTION, "espWifiEventCb(): SYSTEM_EVENT_STA_START\n");
+ break;
+ case SYSTEM_EVENT_STA_CONNECTED:
+ staticThis->debugMsg(CONNECTION, "espWifiEventCb(): SYSTEM_EVENT_STA_CONNECTED\n");
+ break;
+ case SYSTEM_EVENT_STA_DISCONNECTED:
+ staticThis->_station_got_ip = false;
+ staticThis->debugMsg(CONNECTION, "espWifiEventCb(): SYSTEM_EVENT_STA_DISCONNECTED\n");
+ //staticThis->closeConnectionSTA();
+#ifdef ESP8266
+ esp_wifi_disconnect(); // Make sure we are disconnected
+#endif
+ staticThis->stationScan.connectToAP(); // Search for APs and connect to the best one
+ break;
+ case SYSTEM_EVENT_STA_AUTHMODE_CHANGE:
+ staticThis->debugMsg(CONNECTION, "espWifiEventCb(): SYSTEM_EVENT_STA_AUTHMODE_CHANGE\n");
+ break;
+ case SYSTEM_EVENT_STA_GOT_IP:
+ staticThis->_station_got_ip = true;
+ staticThis->debugMsg(CONNECTION, "espWifiEventCb(): SYSTEM_EVENT_STA_GOT_IP\n");
+ staticThis->tcpConnect(); // Connect to TCP port
+ break;
+
+ case SYSTEM_EVENT_AP_STACONNECTED:
+ staticThis->debugMsg(CONNECTION, "espWifiEventCb(): SYSTEM_EVENT_AP_STACONNECTED\n");
+ break;
+
+ case SYSTEM_EVENT_AP_STADISCONNECTED:
+ staticThis->debugMsg(CONNECTION, "espWifiEventCb(): SYSTEM_EVENT_AP_STADISCONNECTED\n");
+ break;
+
+ default:
+ staticThis->debugMsg(ERROR, "Unhandled WiFi event: %d\n", event->event_id);
+ break;
+ }
+ return ESP_OK;
+}
diff --git a/src/painlessMeshConnection.h b/src/painlessMeshConnection.h
new file mode 100644
index 0000000..217fb3d
--- /dev/null
+++ b/src/painlessMeshConnection.h
@@ -0,0 +1,90 @@
+#ifndef _PAINLESS_MESH_CONNECTION_H_
+#define _PAINLESS_MESH_CONNECTION_H_
+
+#define _TASK_STD_FUNCTION
+#include
+
+#include "espInterface.h"
+
+#include
+
+// Temporary buffer used by ReceiveBuffer and SentBuffer
+struct temp_buffer_t {
+ size_t length = TCP_MSS;
+ char buffer[TCP_MSS];
+};
+
+/**
+ * \brief ReceivedBuffer handles pbuf pointers.
+ *
+ * Behaviour:
+ * When a split is encountered, it will store all the preceding text into the jsonObjects.
+ * The pbuffer is copied.
+ */
+class ReceiveBuffer {
+ public:
+ String buffer;
+ std::list jsonStrings;
+
+ ReceiveBuffer();
+
+ void push(const char * cstr, size_t length, temp_buffer_t &buf);
+
+ String front();
+ void pop_front();
+
+ bool empty();
+ void clear();
+};
+
+class SentBuffer {
+ public:
+ size_t last_read_size = 0;
+
+ std::list jsonStrings;
+
+ SentBuffer();
+
+ void push(String &message, bool priority = false);
+
+ size_t requestLength(size_t buffer_size);
+
+ void read(size_t length, temp_buffer_t &buf);
+
+ void freeRead();
+
+ bool empty();
+ void clear();
+};
+
+class MeshConnection {
+ public:
+ AsyncClient *client;
+ painlessMesh *mesh;
+ uint32_t nodeId = 0;
+ String subConnections;
+ timeSync time;
+ bool newConnection = true;
+ bool connected = true;
+ bool station = true;
+
+ uint32_t timeDelayLastRequested = 0; // Timestamp to be compared in manageConnections() to check response for timeout
+
+ bool addMessage(String &message, bool priority = false);
+ bool writeNext();
+ ReceiveBuffer receiveBuffer;
+ SentBuffer sentBuffer;
+
+ Task nodeSyncTask;
+ Task timeSyncTask;
+ Task readBufferTask;
+
+ MeshConnection(AsyncClient *client, painlessMesh *pMesh, bool station);
+ ~MeshConnection();
+
+ void handleMessage(String &msg, uint32_t receivedAt);
+
+ void close();
+ friend class painlessMesh;
+};
+#endif
diff --git a/src/painlessMeshDebug.cpp b/src/painlessMeshDebug.cpp
new file mode 100644
index 0000000..150d4bb
--- /dev/null
+++ b/src/painlessMeshDebug.cpp
@@ -0,0 +1,43 @@
+//
+// painlessMeshDebug.cpp
+//
+//
+// Created by Bill Gray on 8/18/16.
+//
+//
+
+#include
+#include
+
+#include "painlessMesh.h"
+
+uint16_t types = 0;
+char str[200];
+//char *str = NULL;
+
+void ICACHE_FLASH_ATTR painlessMesh::setDebugMsgTypes(uint16_t newTypes) {
+ // set the different kinds of debug messages you want to generate.
+ types = newTypes;
+ Serial.printf("\nsetDebugTypes 0x%x\n", types);
+ //if (!str)
+ // str = (char*) malloc(200 * sizeof(char));
+}
+
+// To assign a debug message to several type use | (bitwise or) operator
+// Example: debugMsg( GENERAL | CONNECTION , "Debug message");
+void ICACHE_FLASH_ATTR painlessMesh::debugMsg(debugType type, const char* format ...) {
+ if (type & types) { //Print only the message types set for output
+ va_list args;
+ va_start(args, format);
+
+ vsnprintf(str, 200, format, args);
+ //perror(str);
+
+ if (types && MSG_TYPES)
+ Serial.printf("0x%x\t", type, types);
+
+ Serial.print(str);
+
+ va_end(args);
+ }
+}
diff --git a/src/painlessMeshSTA.cpp b/src/painlessMeshSTA.cpp
new file mode 100644
index 0000000..72ee0cb
--- /dev/null
+++ b/src/painlessMeshSTA.cpp
@@ -0,0 +1,266 @@
+//
+// painlessMeshSTA.cpp
+//
+//
+// Created by Bill Gray on 7/26/16.
+//
+//
+
+#include
+#include
+#include
+
+#include "painlessMeshSTA.h"
+#include "painlessMesh.h"
+
+#include "lwip/ip_addr.h"
+
+extern painlessMesh* staticThis;
+
+void ICACHE_FLASH_ATTR painlessMesh::stationManual(
+ String ssid, String password, uint16_t port,
+ uint8_t *remote_ip) {
+ // Set station config
+ if (remote_ip != NULL) memcpy(stationScan.manualIP, remote_ip, 4 * sizeof(uint8_t));
+
+ // Start scan
+ stationScan.init(this, ssid, password, port);
+ stationScan.manual = true;
+}
+
+bool ICACHE_FLASH_ATTR painlessMesh::setHostname(const char * hostname){
+ if(strlen(hostname) > 32) {
+ return false;
+ }
+ return (tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, hostname) == ESP_OK);
+}
+
+ip4_addr_t ICACHE_FLASH_ATTR painlessMesh::getStationIP(){
+ tcpip_adapter_ip_info_t ipconfig;
+ tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipconfig);
+ return ipconfig.ip;
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::tcpConnect(void) {
+ // TODO: move to Connection or StationConnection?
+ debugMsg(GENERAL, "tcpConnect():\n");
+ if (stationScan.manual && stationScan.port == 0) return; // We have been configured not to connect to the mesh
+
+ // TODO: We could pass this to tcpConnect instead of loading it here
+ tcpip_adapter_ip_info_t ipconfig;
+ tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipconfig);
+
+ if (_station_got_ip && ipconfig.ip.addr != 0) {
+ AsyncClient *pConn = new AsyncClient();
+
+ pConn->onError([](void *, AsyncClient * client, int8_t err) {
+ staticThis->debugMsg(CONNECTION, "tcp_err(): tcpStationConnection %d\n", err);
+ if (client->connected())
+ client->close();
+ esp_wifi_disconnect();
+ });
+
+ auto ip = ipconfig.gw;
+ if (stationScan.manualIP[0] != 0)
+ //ip = stationScan.manualIP;
+ memcpy(&ip, &stationScan.manualIP, 4);
+
+ pConn->onConnect([](void *, AsyncClient *client) {
+ staticThis->debugMsg(CONNECTION, "New STA connection incoming\n");
+ auto conn = std::make_shared(client, staticThis, true);
+ staticThis->_connections.push_back(conn);
+ }, NULL);
+
+ pConn->connect(IPAddress(ip.addr), stationScan.port);
+ } else {
+ debugMsg(ERROR, "tcpConnect(): err Something un expected in tcpConnect()\n");
+ }
+}
+
+//***********************************************************************
+// Calculate NodeID from a hardware MAC address
+uint32_t ICACHE_FLASH_ATTR painlessMesh::encodeNodeId(uint8_t *hwaddr) {
+ debugMsg(GENERAL, "encodeNodeId():\n");
+ uint32_t value = 0;
+
+ value |= hwaddr[2] << 24; //Big endian (aka "network order"):
+ value |= hwaddr[3] << 16;
+ value |= hwaddr[4] << 8;
+ value |= hwaddr[5];
+ return value;
+}
+
+void ICACHE_FLASH_ATTR StationScan::init(painlessMesh *pMesh, String &pssid,
+ String &ppassword, uint16_t pPort) {
+ ssid = pssid;
+ password = ppassword;
+ mesh = pMesh;
+ port = pPort;
+
+ task.set(SCAN_INTERVAL, TASK_FOREVER, [this](){
+ stationScan();
+ });
+}
+
+// Starts scan for APs whose name is Mesh SSID
+void ICACHE_FLASH_ATTR StationScan::stationScan() {
+ staticThis->debugMsg(CONNECTION, "stationScan(): %s\n", ssid.c_str());
+
+ char tempssid[32];
+ wifi_scan_config_t scanConfig;
+ memset(&scanConfig, 0, sizeof(scanConfig));
+ ssid.toCharArray(tempssid, ssid.length() + 1);
+
+ scanConfig.ssid = (uint8_t *) tempssid; // limit scan to mesh ssid
+ scanConfig.bssid = 0;
+ scanConfig.channel = mesh->_meshChannel; // also limit scan to mesh channel to speed things up ...
+ scanConfig.show_hidden = 1; // add hidden APs ... why not? we might want to hide ...
+
+ task.delay(1000*SCAN_INTERVAL); // Scan should be completed by them and next step called. If not then we restart here.
+
+ if (esp_wifi_scan_start(&scanConfig, false) != ESP_OK)
+ staticThis->debugMsg(ERROR, "wifi_station_scan() failed!?\n");
+}
+
+void ICACHE_FLASH_ATTR StationScan::scanComplete() {
+ staticThis->debugMsg(CONNECTION, "scanComplete():-- > scan finished @ %u < --\n", staticThis->getNodeTime());
+
+ aps.clear();
+ staticThis->debugMsg(CONNECTION, "scanComplete():-- > Cleared old aps.\n");
+
+ uint16_t num = 0;
+ auto err = esp_wifi_scan_get_ap_num(&num);
+ if (err != ESP_OK)
+ staticThis->debugMsg(CONNECTION, "scanComplete():-- > Error in scanning.\n");
+ //wifi_ap_record_t *records = new wifi_ap_record_t[num];
+ wifi_ap_record_t *records = (wifi_ap_record_t*)malloc(num*sizeof(wifi_ap_record_t));
+ //records = (wifi_ap_record_t *)malloc(num*sizeof(wifi_ap_record_t));
+ staticThis->debugMsg(CONNECTION, "scanComplete(): num=%d, err=%d\n", num, err);
+ err = esp_wifi_scan_get_ap_records(&num, records);
+ staticThis->debugMsg(CONNECTION, "scanComplete(): After getting records, num=%d, err=%d\n", num, err);
+ for (uint16_t i = 0; i < num; ++i) {
+ aps.push_back(records[i]);
+ staticThis->debugMsg(CONNECTION, "\tfound : % s, % ddBm\n", (char*) records[i].ssid, (int16_t) records[i].rssi);
+ }
+ //delete[] records;
+ free(records);
+ staticThis->debugMsg(CONNECTION, "\tFound % d nodes\n", aps.size());
+
+ task.yield([this]() {
+ // Task filter all unknown
+ filterAPs();
+
+ // Next task is to sort by strength
+ task.yield([this] {
+ aps.sort([](wifi_ap_record_t a, wifi_ap_record_t b) {
+ return a.rssi > b.rssi;
+ });
+ // Next task is to connect to the top ap
+ task.yield([this]() {
+ connectToAP();
+ });
+ });
+ });
+}
+
+void ICACHE_FLASH_ATTR StationScan::filterAPs() {
+ auto ap = aps.begin();
+ while (ap != aps.end()) {
+ auto apNodeId = staticThis->encodeNodeId(ap->bssid);
+ if (staticThis->findConnection(apNodeId) != NULL) {
+ ap = aps.erase(ap);
+ // debugMsg( GENERAL, "<--already connected\n");
+ } else {
+ ap++;
+ // debugMsg( GENERAL, "\n");
+ }
+ }
+}
+
+void ICACHE_FLASH_ATTR StationScan::requestIP(wifi_ap_record_t &ap) {
+ mesh->debugMsg(CONNECTION, "connectToAP(): Best AP is %u<---\n",
+ mesh->encodeNodeId(ap.bssid));
+ wifi_sta_config_t stationConf;
+ stationConf.bssid_set = 1;
+ memcpy(&stationConf.bssid, ap.bssid, 6); // Connect to this specific HW Address
+ memcpy(&stationConf.ssid, ap.ssid, 32);
+ memcpy(&stationConf.password, password.c_str(), 64);
+ wifi_config_t cfg;
+ cfg.sta = stationConf;
+ esp_wifi_set_config(ESP_IF_WIFI_STA, &cfg);
+ esp_wifi_connect();
+}
+
+void ICACHE_FLASH_ATTR StationScan::connectToAP() {
+ mesh->debugMsg(CONNECTION, "connectToAP():");
+ // Next task will be to rescan
+ task.setCallback([this]() {
+ stationScan();
+ });
+
+ if (manual) {
+ wifi_config_t stationConf;
+ if (esp_wifi_get_config(ESP_IF_WIFI_STA, &stationConf) != ESP_OK) {
+ mesh->debugMsg(CONNECTION, "connectToAP(): failed to get current station config. Retrying later\n");
+ task.delay(SCAN_INTERVAL);
+ return;
+ }
+
+ if (ssid.equals((char *) stationConf.sta.ssid) &&
+ mesh->_station_got_ip) {
+ mesh->debugMsg(CONNECTION, "connectToAP(): Already connected using manual connection. Disabling scanning.\n");
+ task.disable();
+ return;
+ } else {
+ if (mesh->_station_got_ip) {
+ mesh->closeConnectionSTA();
+ task.enableDelayed(1000*SCAN_INTERVAL);
+ return;
+ } else if (aps.empty() ||
+ !ssid.equals((char *)aps.begin()->ssid)) {
+ task.enableDelayed(SCAN_INTERVAL);
+ return;
+ }
+ }
+ }
+
+ if (aps.empty()) {
+ // No unknown nodes found
+ if (mesh->_station_got_ip) {
+ // if already connected -> scan slow
+ mesh->debugMsg(CONNECTION, "connectToAP(): Already connected, and no unknown nodes found: scan rate set to slow\n");
+ task.delay(random(25,36)*SCAN_INTERVAL);
+ } else {
+ // else scan fast (SCAN_INTERVAL)
+ mesh->debugMsg(CONNECTION, "connectToAP(): No unknown nodes found scan rate set to normal\n");
+ task.setInterval(SCAN_INTERVAL);
+ }
+ mesh->stability += min(1000-mesh->stability,(size_t)25);
+ } else {
+ if (mesh->_station_got_ip) {
+ mesh->debugMsg(CONNECTION, "connectToAP(): Unknown nodes found. Current stability: %s\n", String(mesh->stability).c_str());
+ auto prob = mesh->stability/mesh->approxNoNodes();
+ if (random(0, 1000) < prob) {
+ mesh->debugMsg(CONNECTION, "connectToAP(): Reconfigure network: %s\n", String(prob).c_str());
+ // close STA connection, this will trigger station disconnect which will trigger
+ // connectToAP()
+ mesh->closeConnectionSTA();
+ mesh->stability = 0; // Discourage switching again
+ // wifiEventCB should be triggered before this delay runs out
+ // and reset the connecting
+ task.delay(1000*SCAN_INTERVAL);
+ } else {
+ task.delay(random(4,7)*SCAN_INTERVAL);
+ }
+ } else {
+ // Else try to connect to first
+ auto ap = aps.front();
+ aps.pop_front(); // drop bestAP from mesh list, so if doesn't work out, we can try the next one
+ requestIP(ap);
+ // Trying to connect, if that fails we will reconnect later
+ mesh->debugMsg(CONNECTION, "connectToAP(): Trying to connect, scan rate set to 4*normal\n");
+ task.delay(4*SCAN_INTERVAL);
+ }
+ }
+}
diff --git a/src/painlessMeshSTA.h b/src/painlessMeshSTA.h
new file mode 100644
index 0000000..25c4e3b
--- /dev/null
+++ b/src/painlessMeshSTA.h
@@ -0,0 +1,42 @@
+#ifndef _PAINLESS_MESH_STA_H_
+#define _PAINLESS_MESH_STA_H_
+
+#include
+
+#define _TASK_STD_FUNCTION
+#include
+
+#include "espInterface.h"
+
+#define SCAN_INTERVAL 10*TASK_SECOND // AP scan period in ms
+
+class painlessMesh;
+
+class StationScan {
+ public:
+ Task task; // Station scanning for connections
+
+ StationScan() {}
+ void init(painlessMesh *pMesh, String &ssid, String &password,
+ uint16_t port);
+ void stationScan();
+ void scanComplete();
+ void filterAPs();
+ void connectToAP();
+
+ private:
+ String ssid;
+ String password;
+ painlessMesh *mesh;
+ uint16_t port;
+ std::list aps;
+
+ void requestIP(wifi_ap_record_t &ap);
+
+ // Manually configure network and ip
+ bool manual = false;
+ uint8_t manualIP[4] = {0, 0, 0, 0};
+ friend class painlessMesh;
+};
+
+#endif
diff --git a/src/painlessMeshSync.cpp b/src/painlessMeshSync.cpp
new file mode 100644
index 0000000..123acc4
--- /dev/null
+++ b/src/painlessMeshSync.cpp
@@ -0,0 +1,377 @@
+#include
+#include
+
+#include "painlessMesh.h"
+#include "painlessMeshSync.h"
+
+#include "time.h"
+
+extern painlessMesh* staticThis;
+uint32_t timeAdjuster = 0;
+
+// timeSync Functions
+//***********************************************************************
+uint32_t ICACHE_FLASH_ATTR painlessMesh::getNodeTime(void) {
+#ifdef ESP32
+ timeval tv;
+ gettimeofday(&tv, NULL);
+ auto base_time = tv.tv_sec*1000000 + tv.tv_usec;
+#else
+ auto base_time = system_get_time();
+#endif
+ uint32_t ret = base_time + timeAdjuster;
+ debugMsg(GENERAL, "getNodeTime(): time=%u\n", ret);
+ return ret;
+}
+
+//***********************************************************************
+String ICACHE_FLASH_ATTR timeSync::buildTimeStamp(timeSyncMessageType_t timeSyncMessageType, uint32_t originateTS, uint32_t receiveTS, uint32_t transmitTS) {
+ staticThis->debugMsg(S_TIME, "buildTimeStamp(): Type = %u, t0 = %u, t1 = %u, t2 = %u\n", timeSyncMessageType, originateTS, receiveTS, transmitTS);
+ StaticJsonBuffer<75> jsonBuffer;
+ JsonObject& timeStampObj = jsonBuffer.createObject();
+ timeStampObj["type"] = (int)timeSyncMessageType;
+ if (originateTS > 0)
+ timeStampObj["t0"] = originateTS;
+ if (receiveTS > 0)
+ timeStampObj["t1"] = receiveTS;
+ if (transmitTS > 0)
+ timeStampObj["t2"] = transmitTS;
+
+ String timeStampStr;
+ timeStampObj.printTo(timeStampStr);
+ staticThis->debugMsg(S_TIME, "buildTimeStamp(): timeStamp=%s\n", timeStampStr.c_str());
+
+ return timeStampStr;
+}
+
+//***********************************************************************
+timeSyncMessageType_t ICACHE_FLASH_ATTR timeSync::processTimeStampDelay(String &str) {
+ // Extracts and fills timestamp values from json
+ timeSyncMessageType_t ret = TIME_SYNC_ERROR;
+
+ staticThis->debugMsg(S_TIME, "processTimeStamp(): str=%s\n", str.c_str());
+
+ DynamicJsonBuffer jsonBuffer(75);
+ JsonObject& timeStampObj = jsonBuffer.parseObject(str);
+ if (!timeStampObj.success()) {
+ staticThis->debugMsg(ERROR, "processTimeStamp(): out of memory1?\n");
+ return TIME_SYNC_ERROR;
+ }
+
+ ret = static_cast(timeStampObj.get("type"));
+ if (ret == TIME_REQUEST || ret == TIME_RESPONSE) {
+ timeDelay[0] = timeStampObj.get("t0");
+ }
+ if (ret == TIME_RESPONSE) {
+ timeDelay[1] = timeStampObj.get("t1");
+ timeDelay[2] = timeStampObj.get("t2");
+ }
+ return ret; // return type of sync message
+
+}
+
+//***********************************************************************
+int32_t ICACHE_FLASH_ATTR timeSync::calcAdjustment(uint32_t times[NUMBER_OF_TIMESTAMPS]) {
+ staticThis->debugMsg(S_TIME, "calcAdjustment(): Start calculation. t0 = %u, t1 = %u, t2 = %u, t3 = %u\n", times[0], times[1], times[2], times[3]);
+
+ if (times[0] == 0 || times[1] == 0 || times[2] == 0 || times[3] == 0) {
+ // if any value is 0
+ staticThis->debugMsg(ERROR, "calcAdjustment(): TimeStamp error.\n");
+ return 0x7FFFFFFF; // return max value
+ }
+
+ // We use the SNTP protocol https://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm.
+ uint32_t offset = ((int32_t)(times[1] - times[0]) / 2) + ((int32_t)(times[2] - times[3]) / 2);
+
+ timeAdjuster += offset; // Accumulate offset
+ staticThis->debugMsg(S_TIME,
+ "calcAdjustment(): Calculated offset %d us.\n", offset);
+ staticThis->debugMsg(S_TIME, "calcAdjustment(): New adjuster = %u. New time = %u\n", timeAdjuster, staticThis->getNodeTime());
+
+ return offset; // return offset to decide if sync is OK
+}
+
+//***********************************************************************
+int32_t ICACHE_FLASH_ATTR timeSync::delayCalc() {
+ staticThis->debugMsg(S_TIME, "delayCalc(): Start calculation. t0 = %u, t1 = %u, t2 = %u, t3 = %u\n", timeDelay[0], timeDelay[1], timeDelay[2], timeDelay[3]);
+
+ if (timeDelay[0] == 0 || timeDelay[1] == 0 || timeDelay[2] == 0 || timeDelay[3] == 0) {
+ // if any value is 0
+ staticThis->debugMsg(ERROR, "delayCalc(): TimeStamp error.\n");
+ return -1; // return max value
+ }
+
+ // We use the SNTP protocol https://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm.
+ uint32_t tripDelay = ((timeDelay[3] - timeDelay[0]) - (timeDelay[2] - timeDelay[1]))/2;
+
+ staticThis->debugMsg(S_TIME, "delayCalc(): Calculated Network delay %d us\n", tripDelay);
+
+ return tripDelay;
+}
+
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::handleNodeSync(std::shared_ptr conn, JsonObject& root) {
+ debugMsg(SYNC, "handleNodeSync(): with %u\n", conn->nodeId);
+
+ meshPackageType message_type = (meshPackageType)(int)root["type"];
+ uint32_t remoteNodeId = root["from"];
+ bool reSyncAllSubConnections = false;
+
+ /*for (auto && connection : _connections) {
+ debugMsg(SYNC, "handleNodeSync(): Sanity check %d\n", connection->esp_conn);
+ debugMsg(SYNC, "handleNodeSync(): Sanity check Id %u\n", connection->nodeId);
+ }*/
+
+ if (conn->nodeId != remoteNodeId) {
+ if (auto oldConnection = findConnection(remoteNodeId)) {
+ if (oldConnection->nodeId == remoteNodeId) {
+ // Direct connection.
+ debugMsg(SYNC, "handleNodeSync(): Already connected, close connection %u.\n",
+ remoteNodeId);
+ oldConnection->close();
+ } else {
+ debugMsg(SYNC, "handleNodeSync(): Out of date subConnection information %u.\n",
+ remoteNodeId);
+ oldConnection->subConnections = "[]";
+ oldConnection->nodeSyncTask.delay(100*TASK_MILLISECOND);
+ }
+ }
+ debugMsg(SYNC, "handleNodeSync(): conn->nodeId updated from %u to %u\n", conn->nodeId, remoteNodeId);
+ conn->nodeId = remoteNodeId;
+
+ if (conn->newConnection) {
+ debugMsg(SYNC, "handleNodeSync(): conn->nodeId updated from %u to %u\n", conn->nodeId, remoteNodeId);
+
+ // TODO: Move this to its own function
+ newConnectionTask.set(TASK_SECOND, TASK_ONCE, [nodeId = remoteNodeId]() {
+ staticThis->debugMsg(CONNECTION, "newConnectionTask():\n");
+ staticThis->debugMsg(CONNECTION, "newConnectionTask(): adding %u now= %u\n", nodeId, staticThis->getNodeTime());
+ if (staticThis->newConnectionCallback)
+ staticThis->newConnectionCallback(nodeId); // Connection dropped. Signal user
+ for (auto &&connection : staticThis->_connections) {
+ if (connection->nodeId != nodeId) { // Exclude current
+ connection->nodeSyncTask.delay(100*TASK_MILLISECOND);
+ }
+ }
+ staticThis->stability /= 2;
+ });
+
+ scheduler.addTask(newConnectionTask);
+ newConnectionTask.enable();
+ conn->newConnection = false;
+
+ // Initially interval is every 10 seconds,
+ // this will slow down to TIME_SYNC_INTERVAL
+ // after first succesfull sync
+ conn->timeSyncTask.set(10*TASK_SECOND, TASK_FOREVER,
+ [conn]() {
+ staticThis->debugMsg(S_TIME,
+ "timeSyncTask(): %u\n", conn->nodeId);
+ staticThis->startTimeSync(conn);
+ });
+ scheduler.addTask(conn->timeSyncTask);
+ if (conn->station)
+ // We are STA, request time immediately
+ conn->timeSyncTask.enable();
+ else
+ // We are the AP, give STA the change to initiate time sync
+ conn->timeSyncTask.enableDelayed();
+ } else {
+ debugMsg(ERROR, "handleNodeSync(): invalid state for %u to %u\n", conn->nodeId, remoteNodeId);
+ }
+ }
+
+ // check to see if subs have changed.
+ String inComingSubs = root["subs"];
+ if (!conn->subConnections.equals(inComingSubs)) { // change in the network
+ reSyncAllSubConnections = true;
+ conn->subConnections = inComingSubs;
+ if (changedConnectionsCallback)
+ changedConnectionsCallback();
+ }
+
+ String tempstr;
+ root.printTo(tempstr);
+ debugMsg(SYNC, "handleNodeSync(): json = %s\n", tempstr.c_str());
+
+ switch (message_type) {
+ case NODE_SYNC_REQUEST:
+ {
+ debugMsg(SYNC, "handleNodeSync(): valid NODE_SYNC_REQUEST %u sending NODE_SYNC_REPLY\n", conn->nodeId);
+ String myOtherSubConnections = subConnectionJson(conn);
+ sendMessage(conn, conn->nodeId, _nodeId, NODE_SYNC_REPLY, myOtherSubConnections, true);
+ break;
+ }
+ case NODE_SYNC_REPLY:
+ debugMsg(SYNC, "handleNodeSync(): valid NODE_SYNC_REPLY from %u\n", conn->nodeId);
+ break;
+ default:
+ debugMsg(ERROR, "handleNodeSync(): weird type? %d\n", message_type);
+ }
+
+ if (reSyncAllSubConnections == true) {
+ for (auto &&connection : _connections) {
+ if (connection->nodeId != conn->nodeId) { // Exclude current
+ connection->nodeSyncTask.delay(100*TASK_MILLISECOND);
+ }
+ }
+ stability /= 2;
+ } else {
+ stability += min(1000-stability,(size_t)25);
+ }
+
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::startTimeSync(std::shared_ptr conn) {
+ String timeStamp;
+
+ debugMsg(S_TIME, "startTimeSync(): with %u, local port: %d\n", conn->nodeId, conn->client->getLocalPort());
+ auto adopt = adoptionCalc(conn);
+ if (adopt) {
+ timeStamp = conn->time.buildTimeStamp(TIME_REQUEST, getNodeTime()); // Ask other party its time
+ debugMsg(S_TIME, "startTimeSync(): Requesting %u to adopt our time\n", conn->nodeId);
+ } else {
+ timeStamp = conn->time.buildTimeStamp(TIME_SYNC_REQUEST); // Tell other party to ask me the time
+ debugMsg(S_TIME, "startTimeSync(): Requesting time from %u\n", conn->nodeId);
+ }
+ sendMessage(conn, conn->nodeId, _nodeId, TIME_SYNC, timeStamp, true);
+}
+
+//***********************************************************************
+bool ICACHE_FLASH_ATTR painlessMesh::adoptionCalc(std::shared_ptr conn) {
+ if (conn == NULL) // Missing connection
+ return false;
+ // make the adoption calulation. Figure out how many nodes I am connected to exclusive of this connection.
+
+ // We use length as an indicator for how many subconnections both nodes have
+ uint16_t mySubCount = subConnectionJson(conn).length(); //exclude this connection.
+ uint16_t remoteSubCount = conn->subConnections.length();
+ bool ap = conn->client->getLocalPort() == _meshPort;
+
+ // ToDo. Simplify this logic
+ bool ret = (mySubCount > remoteSubCount) ? false : true;
+ if (mySubCount == remoteSubCount && ap) { // in case of draw, ap wins
+ ret = false;
+ }
+
+ debugMsg(S_TIME, "adoptionCalc(): mySubCount=%d remoteSubCount=%d role=%s adopt=%s\n", mySubCount, remoteSubCount, ap ? "AP" : "STA", ret ? "true" : "false");
+
+ return ret;
+}
+
+//***********************************************************************
+void ICACHE_FLASH_ATTR painlessMesh::handleTimeSync(std::shared_ptr conn, JsonObject& root, uint32_t receivedAt) {
+ auto timeSyncMessageType = static_cast(root["msg"]["type"].as());
+ String msg;
+
+ switch (timeSyncMessageType) {
+ case (TIME_SYNC_REQUEST): // Other party request me to ask it for time
+ debugMsg(S_TIME, "handleTimeSync(): Received requesto to start TimeSync with node: %u\n", conn->nodeId);
+ root["msg"]["type"] = static_cast(TIME_REQUEST);
+ root["msg"]["t0"] = getNodeTime();
+ msg = root["msg"].as();
+ staticThis->sendMessage(conn, conn->nodeId, _nodeId, TIME_SYNC, msg, true);
+ break;
+
+ case (TIME_REQUEST):
+ root["msg"]["type"] = static_cast(TIME_RESPONSE);
+ root["msg"]["t1"] = receivedAt;
+ root["msg"]["t2"] = getNodeTime();
+ msg = root["msg"].as();
+ staticThis->sendMessage(conn, conn->nodeId, _nodeId, TIME_SYNC, msg, true);
+
+ // Build time response
+ debugMsg(S_TIME, "handleTimeSync(): Response sent %s\n", msg.c_str());
+ debugMsg(S_TIME, "handleTimeSync(): timeSyncStatus with %u completed\n", conn->nodeId);
+
+ // After response is sent I assume sync is completed
+ conn->timeSyncTask.delay(TIME_SYNC_INTERVAL);
+ break;
+
+ case (TIME_RESPONSE):
+ debugMsg(S_TIME, "handleTimeSync(): TIME RESPONSE received.\n");
+ uint32_t times[NUMBER_OF_TIMESTAMPS] = {
+ root["msg"]["t0"],
+ root["msg"]["t1"],
+ root["msg"]["t2"],
+ receivedAt};
+
+ int32_t offset = conn->time.calcAdjustment(times); // Adjust time and get calculated offset
+
+ // flag all connections for re-timeSync
+ if (nodeTimeAdjustedCallback) {
+ nodeTimeAdjustedCallback(offset);
+ }
+
+ if (offset < MIN_ACCURACY && offset > -MIN_ACCURACY) {
+ // mark complete only if offset was less than 10 ms
+ conn->timeSyncTask.delay(TIME_SYNC_INTERVAL);
+ debugMsg(S_TIME, "handleTimeSync(): timeSyncStatus with %u completed\n", conn->nodeId);
+
+ // Time has changed, update other nodes
+ for (auto &&connection : _connections) {
+ if (connection->nodeId != conn->nodeId) { // exclude this connection
+ connection->timeSyncTask.forceNextIteration();
+ debugMsg(S_TIME, "handleTimeSync(): timeSyncStatus with %u brought forward\n", connection->nodeId);
+ }
+ }
+ } else {
+ // Iterate sync procedure if accuracy was not enough
+ conn->timeSyncTask.delay(200*TASK_MILLISECOND); // Small delay
+ debugMsg(S_TIME, "handleTimeSync(): timeSyncStatus with %u needs further tries\n", conn->nodeId);
+
+ }
+ break;
+ }
+
+ debugMsg(S_TIME, "handleTimeSync(): ----------------------------------\n");
+
+}
+
+void ICACHE_FLASH_ATTR painlessMesh::handleTimeDelay(std::shared_ptr conn, JsonObject& root, uint32_t receivedAt) {
+ String timeStamp = root["msg"];
+ uint32_t from = root["from"];
+ debugMsg(S_TIME, "handleTimeDelay(): from %u in timestamp = %s\n", from, timeStamp.c_str());
+
+ timeSyncMessageType_t timeSyncMessageType = conn->time.processTimeStampDelay(timeStamp); // Extract timestamps and get type of message
+
+ String t_stamp;
+
+ switch (timeSyncMessageType) {
+
+ case (TIME_REQUEST):
+ //conn->timeSyncStatus == IN_PROGRESS;
+ debugMsg(S_TIME, "handleTimeDelay(): TIME REQUEST received.\n");
+
+ // Build time response
+ t_stamp = conn->time.buildTimeStamp(TIME_RESPONSE, conn->time.timeDelay[0], receivedAt, getNodeTime());
+ staticThis->sendMessage(conn, from, _nodeId, TIME_DELAY, t_stamp);
+
+ debugMsg(S_TIME, "handleTimeDelay(): Response sent %s\n", t_stamp.c_str());
+
+ // After response is sent I assume sync is completed
+ //conn->timeSyncStatus == COMPLETE;
+ //conn->lastTimeSync = getNodeTime();
+ break;
+
+ case (TIME_RESPONSE):
+ debugMsg(S_TIME, "handleTimeDelay(): TIME RESPONSE received.\n");
+ conn->time.timeDelay[3] = receivedAt; // Calculate fourth timestamp (response received time)
+
+ int32_t delay = conn->time.delayCalc(); // Adjust time and get calculated offset
+ debugMsg(S_TIME, "handleTimeDelay(): Delay is %d\n", delay);
+
+ //conn->timeSyncStatus == COMPLETE;
+
+ if (nodeDelayReceivedCallback)
+ nodeDelayReceivedCallback(from, delay);
+
+
+ break;
+ }
+
+ debugMsg(S_TIME, "handleTimeSync(): ----------------------------------\n");
+
+}
diff --git a/src/painlessMeshSync.h b/src/painlessMeshSync.h
new file mode 100644
index 0000000..f876815
--- /dev/null
+++ b/src/painlessMeshSync.h
@@ -0,0 +1,29 @@
+#ifndef _MESH_SYNC_H_
+#define _MESH_SYNC_H_
+
+#include
+
+#define TIME_SYNC_INTERVAL 5*TASK_MINUTE // Time resync period
+#define NUMBER_OF_TIMESTAMPS 4 // 4 timestamps are needed for time offset calculation
+#define MIN_ACCURACY 10000 // Minimum time sync accuracy
+
+enum timeSyncMessageType_t {
+ TIME_SYNC_ERROR = -1,
+ TIME_SYNC_REQUEST,
+ TIME_REQUEST,
+ TIME_RESPONSE
+};
+
+class timeSync {
+public:
+ uint32_t timeDelay[NUMBER_OF_TIMESTAMPS]; // timestamp array
+
+ String buildTimeStamp(timeSyncMessageType_t timeSyncMessageType, uint32_t originateTS = 0, uint32_t receiveTS = 0, uint32_t transmitTS = 0);
+ timeSyncMessageType_t processTimeStampDelay(String &str);
+ int32_t calcAdjustment(uint32_t times[NUMBER_OF_TIMESTAMPS]);
+ int32_t delayCalc();
+
+};
+
+#endif // _MESH_SYNC_H_
+
diff --git a/src/painlessScheduler.h b/src/painlessScheduler.h
new file mode 100644
index 0000000..9f2b18b
--- /dev/null
+++ b/src/painlessScheduler.h
@@ -0,0 +1,931 @@
+// Cooperative multitasking library for Arduino
+// Copyright (c) 2015, 2016 Anatoli Arkhipenko
+//
+// Changelog:
+// v1.0.0:
+// 2015-02-24 - Initial release
+// 2015-02-28 - added delay() and disableOnLastIteration() methods
+// 2015-03-25 - changed scheduler execute() method for a more precise delay calculation:
+// 1. Do not delay if any of the tasks ran (making request for immediate execution redundant)
+// 2. Delay is invoked only if none of the tasks ran
+// 3. Delay is based on the min anticipated wait until next task _AND_ the runtime of execute method itself.
+// 2015-05-11 - added restart() and restartDelayed() methods to restart tasks which are on hold after running all iterations
+// 2015-05-19 - completely removed delay from the scheduler since there are no power saving there. using 1 ms sleep instead
+//
+// v1.4.1:
+// 2015-09-15 - more careful placement of AVR-specific includes for sleep method (compatibility with DUE)
+// sleep on idle run is no longer a default and should be explicitly compiled with
+// _TASK_SLEEP_ON_IDLE_RUN defined
+//
+// v1.5.0:
+// 2015-09-20 - access to currently executing task (for callback methods)
+// 2015-09-20 - pass scheduler as a parameter to the task constructor to append the task to the end of the chain
+// 2015-09-20 - option to create a task already enabled
+//
+// v1.5.1:
+// 2015-09-21 - bug fix: incorrect handling of active tasks via set() and setIterations().
+// Thanks to Hannes Morgenstern for catching this one
+//
+// v1.6.0:
+// 2015-09-22 - revert back to having all tasks disable on last iteration.
+// 2015-09-22 - deprecated disableOnLastIteration method as a result
+// 2015-09-22 - created a separate branch 'disable-on-last-iteration' for this
+// 2015-10-01 - made version numbers semver compliant (documentation only)
+//
+// v1.7.0:
+// 2015-10-08 - introduced callback run counter - callback methods can branch on the iteration number.
+// 2015-10-11 - enableIfNot() - enable a task only if it is not already enabled. Returns true if was already enabled,
+// false if was disabled.
+// 2015-10-11 - disable() returns previous enable state (true if was enabled, false if was already disabled)
+// 2015-10-11 - introduced callback methods "on enable" and "on disable". On enable runs every time enable is called,
+// on disable runs only if task was enabled
+// 2015-10-12 - new Task method: forceNextIteration() - makes next iteration happen immediately during the next pass
+// regardless how much time is left
+//
+// v1.8.0:
+// 2015-10-13 - support for status request objects allowing tasks waiting on requests
+// 2015-10-13 - moved to a single header file to allow compilation control via #defines from the main sketch
+//
+// v1.8.1:
+// 2015-10-22 - implement Task id and control points to support identification of failure points for watchdog timer logging
+//
+// v1.8.2:
+// 2015-10-27 - implement Local Task Storage Pointer (allow use of same callback code for different tasks)
+// 2015-10-27 - bug: currentTask() method returns incorrect Task reference if called within OnEnable and OnDisable methods
+// 2015-10-27 - protection against infinite loop in OnEnable (if enable() methods are called within OnEnable)
+// 2015-10-29 - new currentLts() method in the scheduler class returns current task's LTS pointer in one call
+//
+// v1.8.3:
+// 2015-11-05 - support for task activation on a status request with arbitrary interval and number of iterations
+// (0 and 1 are still default values)
+// 2015-11-05 - implement waitForDelayed() method to allow task activation on the status request completion
+// delayed for one current interval
+// 2015-11-09 - added callback methods prototypes to all examples for Arduino IDE 1.6.6 compatibility
+// 2015-11-14 - added several constants to be used as task parameters for readability (e.g, TASK_FOREVER, TASK_SECOND, etc.)
+// 2015-11-14 - significant optimization of the scheduler's execute loop, including millis() rollover fix option
+//
+// v1.8.4:
+// 2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before.
+// Especially since OnEnable method can change the interval
+// 2015-11-16 - further optimizations of the task scheduler execute loop
+//
+// v1.8.5:
+// 2015-11-23 - bug fix: incorrect calculation of next task invocation in case callback changed the interval
+// 2015-11-23 - bug fix: Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner
+//
+// v1.9.0:
+// 2015-11-24 - packed three byte-long status variables into bit array structure data type - saving 2 bytes per each task instance
+//
+// v1.9.2:
+// 2015-11-28 - _TASK_ROLLOVER_FIX is deprecated (not necessary)
+// 2015-12-16 - bug fixes: automatic millis rollover support for delay methods
+// 2015-12-17 - new method for _TASK_TIMECRITICAL option: getStartDelay()
+//
+// v2.0.0:
+// 2015-12-22 - _TASK_PRIORITY - support for layered task prioritization
+//
+// v2.0.1:
+// 2016-01-02 - bug fix: issue#11 Xtensa compiler (esp8266): Declaration of constructor does not match implementation
+//
+// v2.0.2:
+// 2016-01-05 - bug fix: time constants wrapped inside compile option
+// 2016-01-05 - support for ESP8266 wifi power saving mode for _TASK_SLEEP_ON_IDLE_RUN compile option
+//
+// v2.1.0:
+// 2016-02-01 - support for microsecond resolution
+// 2016-02-02 - added Scheduler baseline start time reset method: startNow()
+//
+// v2.2.0:
+// 2016-11-17 - all methods made 'inline' to support inclusion of TaskSchedule.h file into other header files
+//
+// v2.2.1:
+// 2016-11-30 - inlined constructors. Added "yield()" and "yieldOnce()" functions to easily break down and chain
+// back together long running callback methods
+// 2016-12-16 - added "getCount()" to StatusRequest objects, made every task StatusRequest enabled.
+// Internal StatusRequest objects are accessible via "getInternalStatusRequest()" method.
+//
+// v2.3.0:
+// 2017-02-24 - new timeUntilNextIteration() method within Scheduler class - inquire when a particlar task is
+// scheduled to run next time
+
+#include
+
+#ifndef _TASKSCHEDULER_H_
+#define _TASKSCHEDULER_H_
+
+/** ----------------------------------------
+ * The following "defines" control library functionality at compile time,
+ * and should be used in the main sketch depending on the functionality required
+ *
+ * #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
+ * #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
+ * #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
+ * #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
+ * #define _TASK_LTS_POINTER // Compile with support for local task storage pointer
+ * #define _TASK_PRIORITY // Support for layered scheduling priority
+ * #define _TASK_MICRO_RES // Support for microsecond resolution
+ * #define _TASK_STD_FUNCTION // Support for std::function
+ */
+
+
+ #ifdef _TASK_MICRO_RES
+
+ #undef _TASK_SLEEP_ON_IDLE_RUN // SLEEP_ON_IDLE has only millisecond resolution
+ #define _TASK_TIME_FUNCTION() micros()
+
+ #else
+
+ #define _TASK_TIME_FUNCTION() millis()
+
+ #endif // _TASK_MICRO_RES
+
+
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+
+#ifdef ARDUINO_ARCH_AVR
+#include
+#include
+#endif // ARDUINO_ARCH_AVR
+
+#ifdef ARDUINO_ARCH_ESP8266
+extern "C" {
+#include "user_interface.h"
+}
+#define _TASK_ESP8266_DLY_THRESHOLD 200L
+#endif // ARDUINO_ARCH_ESP8266
+
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+
+#define TASK_IMMEDIATE 0
+#define TASK_FOREVER (-1)
+#define TASK_ONCE 1
+
+
+#ifndef _TASK_MICRO_RES
+
+#define TASK_MILLISECOND 1UL
+#define TASK_SECOND 1000UL
+#define TASK_MINUTE 60000UL
+#define TASK_HOUR 3600000UL
+
+#else
+
+#define TASK_MILLISECOND 1000UL
+#define TASK_SECOND 1000000UL
+#define TASK_MINUTE 60000000UL
+#define TASK_HOUR 3600000000UL
+
+#endif // _TASK_MICRO_RES
+
+
+#ifdef _TASK_STATUS_REQUEST
+
+#define _TASK_SR_NODELAY 1
+#define _TASK_SR_DELAY 2
+
+class StatusRequest {
+ public:
+ inline StatusRequest() {iCount = 0; iStatus = 0; }
+ inline void setWaiting(unsigned int aCount = 1) { iCount = aCount; iStatus = 0; }
+ inline bool signal(int aStatus = 0);
+ inline void signalComplete(int aStatus = 0);
+ inline bool pending() { return (iCount != 0); }
+ inline bool completed() { return (iCount == 0); }
+ inline int getStatus() { return iStatus; }
+ inline int getCount() { return iCount; }
+
+ private:
+ unsigned int iCount; // number of statuses to wait for. waiting for more that 65000 events seems unreasonable: unsigned int should be sufficient
+ int iStatus; // status of the last completed request. negative = error; zero = OK; >positive = OK with a specific status
+};
+#endif // _TASK_STATUS_REQUEST
+
+#ifdef _TASK_STD_FUNCTION
+#define _TASK_STD_FUNCTION
+#include
+typedef std::function callback_t;
+typedef std::function onDisable_cb_t;
+typedef std::function onEnable_cb_t;
+#else
+typedef void (*callback_t)();
+typedef void (*onDisable_cb_t)();
+typedef bool (*onEnable_cb_t)();
+#endif
+
+typedef struct {
+ bool enabled : 1; // indicates that task is enabled or not.
+ bool inonenable : 1; // indicates that task execution is inside OnEnable method (preventing infinite loops)
+#ifdef _TASK_STATUS_REQUEST
+ byte waiting : 2; // indication if task is waiting on the status request
+#endif
+} __task_status;
+
+class Scheduler;
+
+
+#ifdef _TASK_WDT_IDS
+ static unsigned int __task_id_counter = 0; // global task ID counter for assiging task IDs automatically.
+#endif // _TASK_WDT_IDS
+
+class Task {
+ friend class Scheduler;
+ public:
+ inline Task(unsigned long aInterval=0, long aIterations=0, callback_t aCallback=NULL, Scheduler* aScheduler=NULL, bool aEnable=false, onEnable_cb_t aOnEnable=NULL, onDisable_cb_t aOnDisable=NULL);
+#ifdef _TASK_STATUS_REQUEST
+ inline Task(callback_t aCallback=NULL, Scheduler* aScheduler=NULL, onEnable_cb_t aOnEnable=NULL, onDisable_cb_t aOnDisable=NULL);
+#endif // _TASK_STATUS_REQUEST
+ inline ~Task();
+
+ inline void enable();
+ inline bool enableIfNot();
+ inline void enableDelayed(unsigned long aDelay=0);
+ inline void delay(unsigned long aDelay=0);
+ inline void forceNextIteration();
+ inline void restart();
+ inline void restartDelayed(unsigned long aDelay=0);
+ inline bool disable();
+ inline bool isEnabled() { return iStatus.enabled; }
+ inline void set(unsigned long aInterval, long aIterations, callback_t aCallback,onEnable_cb_t aOnEnable=NULL, onDisable_cb_t aOnDisable=NULL);
+ inline void setInterval(unsigned long aInterval);
+ inline unsigned long getInterval() { return iInterval; }
+ inline void setIterations(long aIterations);
+ inline long getIterations() { return iIterations; }
+ inline unsigned long getRunCounter() { return iRunCounter; }
+ inline void setCallback(callback_t aCallback) { iCallback = aCallback; }
+ inline void setOnEnable(onEnable_cb_t aCallback) { iOnEnable = aCallback; }
+ inline void setOnDisable(callback_t aCallback) { iOnDisable = aCallback; }
+ inline void yield(callback_t aCallback);
+ inline void yieldOnce(callback_t aCallback);
+#ifdef _TASK_TIMECRITICAL
+ inline long getOverrun() { return iOverrun; }
+ inline long getStartDelay() { return iStartDelay; }
+#endif // _TASK_TIMECRITICAL
+ inline bool isFirstIteration() { return (iRunCounter <= 1); }
+ inline bool isLastIteration() { return (iIterations == 0); }
+#ifdef _TASK_STATUS_REQUEST
+ inline void waitFor(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
+ inline void waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
+ inline StatusRequest* getStatusRequest() {return iStatusRequest; }
+ inline StatusRequest* getInternalStatusRequest() {return &iMyStatusRequest; }
+#endif // _TASK_STATUS_REQUEST
+#ifdef _TASK_WDT_IDS
+ inline void setId(unsigned int aID) { iTaskID = aID; }
+ inline unsigned int getId() { return iTaskID; }
+ inline void setControlPoint(unsigned int aPoint) { iControlPoint = aPoint; }
+ inline unsigned int getControlPoint() { return iControlPoint; }
+#endif // _TASK_WDT_IDS
+#ifdef _TASK_LTS_POINTER
+ inline void setLtsPointer(void *aPtr) { iLTS = aPtr; }
+ inline void* getLtsPointer() { return iLTS; }
+#endif // _TASK_LTS_POINTER
+
+ private:
+ inline void reset();
+
+ volatile __task_status iStatus;
+ volatile unsigned long iInterval; // execution interval in milliseconds (or microseconds). 0 - immediate
+ volatile unsigned long iDelay; // actual delay until next execution (usually equal iInterval)
+ volatile unsigned long iPreviousMillis; // previous invocation time (millis). Next invocation = iPreviousMillis + iInterval. Delayed tasks will "catch up"
+#ifdef _TASK_TIMECRITICAL
+ volatile long iOverrun; // negative if task is "catching up" to it's schedule (next invocation time is already in the past)
+ volatile long iStartDelay; // actual execution of the task's callback method was delayed by this number of millis
+#endif // _TASK_TIMECRITICAL
+ volatile long iIterations; // number of iterations left. 0 - last iteration. -1 - infinite iterations
+ long iSetIterations; // number of iterations originally requested (for restarts)
+ unsigned long iRunCounter; // current number of iteration (starting with 1). Resets on enable.
+ callback_t iCallback; // pointer to the void callback method
+ onEnable_cb_t iOnEnable; // pointer to the bolol OnEnable callback method
+ onDisable_cb_t iOnDisable; // pointer to the void OnDisable method
+ Task *iPrev, *iNext; // pointers to the previous and next tasks in the chain
+ Scheduler *iScheduler; // pointer to the current scheduler
+#ifdef _TASK_STATUS_REQUEST
+ StatusRequest *iStatusRequest; // pointer to the status request task is or was waiting on
+ StatusRequest iMyStatusRequest; // internal Status request to let other tasks know of completion
+#endif // _TASK_STATUS_REQUEST
+#ifdef _TASK_WDT_IDS
+ unsigned int iTaskID; // task ID (for debugging and watchdog identification)
+ unsigned int iControlPoint; // current control point within the callback method. Reset to 0 by scheduler at the beginning of each pass
+#endif // _TASK_WDT_IDS
+#ifdef _TASK_LTS_POINTER
+ void *iLTS; // pointer to task's local storage. Needs to be recast to appropriate type (usually a struct).
+#endif // _TASK_LTS_POINTER
+};
+
+
+#ifdef _TASK_PRIORITY
+ static Scheduler* iCurrentScheduler;
+#endif // _TASK_PRIORITY
+
+class Scheduler {
+ friend class Task;
+ public:
+ inline Scheduler();
+ inline void init();
+ inline void addTask(Task& aTask);
+ inline void deleteTask(Task& aTask);
+ inline void disableAll(bool aRecursive = true);
+ inline void enableAll(bool aRecursive = true);
+ inline bool execute(); // Returns true if none of the tasks' callback methods was invoked (true = idle run)
+ inline void startNow(bool aRecursive = true); // reset ALL active tasks to immediate execution NOW.
+ inline Task& currentTask() {return *iCurrent; }
+ inline long timeUntilNextIteration(Task& aTask); // return number of ms until next iteration of a given Task
+
+ inline bool empty() { return iFirst == NULL; }
+ /**
+ * \brief Return the number of tasks
+ */
+ inline size_t size();
+
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ inline void allowSleep(bool aState = true);
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+#ifdef _TASK_LTS_POINTER
+ inline void* currentLts() {return iCurrent->iLTS; }
+#endif // _TASK_LTS_POINTER
+#ifdef _TASK_TIMECRITICAL
+ inline bool isOverrun() { return (iCurrent->iOverrun < 0); }
+#endif // _TASK_TIMECRITICAL
+#ifdef _TASK_PRIORITY
+ inline void setHighPriorityScheduler(Scheduler* aScheduler);
+ static Scheduler& currentScheduler() { return *(iCurrentScheduler); };
+#endif // _TASK_PRIORITY
+
+ private:
+ Task *iFirst, *iLast, *iCurrent; // pointers to first, last and current tasks in the chain
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ bool iAllowSleep; // indication if putting avr to IDLE_SLEEP mode is allowed by the program at this time.
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+#ifdef _TASK_PRIORITY
+ Scheduler *iHighPriority; // Pointer to a higher priority scheduler
+#endif // _TASK_PRIORITY
+};
+
+
+// ------------------ TaskScheduler implementation --------------------
+
+/** Constructor, uses default values for the parameters
+ * so could be called with no parameters.
+ */
+Task::Task( unsigned long aInterval, long aIterations, callback_t aCallback, Scheduler* aScheduler, bool aEnable, onEnable_cb_t aOnEnable, onDisable_cb_t aOnDisable ) {
+ reset();
+ set(aInterval, aIterations, aCallback, aOnEnable, aOnDisable);
+ if (aScheduler) aScheduler->addTask(*this);
+#ifdef _TASK_STATUS_REQUEST
+ iStatusRequest = NULL;
+#endif // _TASK_STATUS_REQUEST
+#ifdef _TASK_WDT_IDS
+ iTaskID = ++__task_id_counter;
+#endif // _TASK_WDT_IDS
+ if (aEnable) enable();
+}
+
+Task::~Task() {
+ if (iScheduler)
+ iScheduler->deleteTask(*this);
+ disable();
+}
+
+
+#ifdef _TASK_STATUS_REQUEST
+
+/** Constructor with reduced parameter list for tasks created for
+ * StatusRequest only triggering (always immediate and only 1 iteration)
+ */
+Task::Task( callback_t aCallback, Scheduler* aScheduler, onEnable_cb_t aOnEnable, onDisable_cb_t aOnDisable ) {
+ reset();
+ set(TASK_IMMEDIATE, TASK_ONCE, aCallback, aOnEnable, aOnDisable);
+ if (aScheduler) aScheduler->addTask(*this);
+ iStatusRequest = NULL;
+#ifdef _TASK_WDT_IDS
+ iTaskID = ++__task_id_counter;
+#endif // _TASK_WDT_IDS
+}
+
+/** Signals completion of the StatusRequest by one of the participating events
+ * @param: aStatus - if provided, sets the return code of the StatusRequest: negative = error, 0 (default) = OK, positive = OK with a specific status code
+ * Negative status will complete Status Request fully (since an error occured).
+ * @return: true, if StatusRequest is complete, false otherwise (still waiting for other events)
+ */
+bool StatusRequest::signal(int aStatus) {
+ if ( iCount) { // do not update the status request if it was already completed
+ if (iCount > 0) --iCount;
+ if ( (iStatus = aStatus) < 0 ) iCount = 0; // if an error is reported, the status is requested to be completed immediately
+ }
+ return (iCount == 0);
+}
+
+void StatusRequest::signalComplete(int aStatus) {
+ if (iCount) { // do not update the status request if it was already completed
+ iCount = 0;
+ iStatus = aStatus;
+ }
+}
+
+/** Sets a Task to wait until a particular event completes
+ * @param: aStatusRequest - a pointer for the StatusRequest to wait for.
+ * If aStatusRequest is NULL, request for waiting is ignored, and the waiting task is not enabled.
+ */
+void Task::waitFor(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
+ if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL
+ setIterations(aIterations);
+ setInterval(aInterval);
+ iStatus.waiting = _TASK_SR_NODELAY; // no delay
+ enable();
+ }
+}
+
+void Task::waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
+ if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL
+ setIterations(aIterations);
+ if ( aInterval ) setInterval(aInterval); // For the dealyed version only set the interval if it was not a zero
+ iStatus.waiting = _TASK_SR_DELAY; // with delay equal to the current interval
+ enable();
+ }
+}
+#endif // _TASK_STATUS_REQUEST
+
+/** Resets (initializes) the task/
+ * Task is not enabled and is taken out
+ * out of the execution chain as a result
+ */
+void Task::reset() {
+ iStatus.enabled = false;
+ iStatus.inonenable = false;
+ iPreviousMillis = 0;
+ iInterval = iDelay = 0;
+ iPrev = NULL;
+ iNext = NULL;
+ iScheduler = NULL;
+ iRunCounter = 0;
+#ifdef _TASK_TIMECRITICAL
+ iOverrun = 0;
+ iStartDelay = 0;
+#endif // _TASK_TIMECRITICAL
+#ifdef _TASK_WDT_IDS
+ iControlPoint = 0;
+#endif // _TASK_WDT_IDS
+#ifdef _TASK_LTS_POINTER
+ iLTS = NULL;
+#endif // _TASK_LTS_POINTER
+#ifdef _TASK_STATUS_REQUEST
+ iStatus.waiting = 0;
+ iMyStatusRequest.signalComplete();
+#endif // _TASK_STATUS_REQUEST
+}
+
+/** Explicitly set Task execution parameters
+ * @param aInterval - execution interval in ms
+ * @param aIterations - number of iterations, use -1 for no limit
+ * @param aCallback - pointer to the callback method which executes the task actions
+ * @param aOnEnable - pointer to the callback method which is called on enable()
+ * @param aOnDisable - pointer to the callback method which is called on disable()
+ */
+void Task::set(unsigned long aInterval, long aIterations, callback_t aCallback, onEnable_cb_t aOnEnable, onDisable_cb_t aOnDisable) {
+ setInterval(aInterval);
+ iSetIterations = iIterations = aIterations;
+ iCallback = aCallback;
+ iOnEnable = aOnEnable;
+ iOnDisable = aOnDisable;
+}
+
+/** Sets number of iterations for the task
+ * if task is enabled, schedule for immediate execution
+ * @param aIterations - number of iterations, use -1 for no limit
+ */
+void Task::setIterations(long aIterations) {
+ iSetIterations = iIterations = aIterations;
+}
+
+/** Prepare task for next step iteration following yielding of control to the scheduler
+ * @param aCallback - pointer to the callback method for the next step
+ */
+void Task::yield (callback_t aCallback) {
+ iCallback = aCallback;
+ forceNextIteration();
+
+ // The next 2 lines adjust runcounter and number of iterations
+ // as if it is the same run of the callback, just split between
+ // a series of callback methods
+ iRunCounter--;
+ if ( iIterations >= 0 ) iIterations++;
+}
+
+/** Prepare task for next step iteration following yielding of control to the scheduler
+ * @param aCallback - pointer to the callback method for the next step
+ */
+void Task::yieldOnce (callback_t aCallback) {
+ yield(aCallback);
+ iIterations = 1;
+}
+
+/** Enables the task
+ * schedules it for execution as soon as possible,
+ * and resets the RunCounter back to zero
+ */
+void Task::enable() {
+ if (iScheduler) { // activation without active scheduler does not make sense
+ iRunCounter = 0;
+ if ( iOnEnable && !iStatus.inonenable ) {
+ Task *current = iScheduler->iCurrent;
+ iScheduler->iCurrent = this;
+ iStatus.inonenable = true; // Protection against potential infinite loop
+ iStatus.enabled = iOnEnable();
+ iStatus.inonenable = false; // Protection against potential infinite loop
+ iScheduler->iCurrent = current;
+ }
+ else {
+ iStatus.enabled = true;
+ }
+ iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval);
+#ifdef _TASK_STATUS_REQUEST
+ if ( iStatus.enabled ) {
+ iMyStatusRequest.setWaiting();
+ }
+#endif
+ }
+}
+
+/** Enables the task only if it was not enabled already
+ * Returns previous state (true if was already enabled, false if was not)
+ */
+bool Task::enableIfNot() {
+ bool previousEnabled = iStatus.enabled;
+ if ( !previousEnabled ) enable();
+ return (previousEnabled);
+}
+
+/** Enables the task
+ * and schedules it for execution after a delay = aInterval
+ */
+void Task::enableDelayed(unsigned long aDelay) {
+ enable();
+ delay(aDelay);
+}
+
+/** Delays Task for execution after a delay = aInterval (if task is enabled).
+ * leaves task enabled or disabled
+ * if aDelay is zero, delays for the original scheduling interval from now
+ */
+void Task::delay(unsigned long aDelay) {
+// if (!aDelay) aDelay = iInterval;
+ iDelay = aDelay ? aDelay : iInterval;
+ // -1 is to make sure it is in the past
+ iPreviousMillis = _TASK_TIME_FUNCTION() - 1; // - iInterval + aDelay;
+}
+
+/** Schedules next iteration of Task for execution immediately (if enabled)
+ * leaves task enabled or disabled
+ * Task's original schedule is shifted, and all subsequent iterations will continue from this point in time
+ */
+void Task::forceNextIteration() {
+ iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval);
+}
+
+/** Sets the execution interval.
+ * Task execution is delayed for aInterval
+ * Use enable() to schedule execution ASAP
+ * @param aInterval - new execution interval
+ */
+void Task::setInterval (unsigned long aInterval) {
+ iInterval = aInterval;
+ delay(); // iDelay will be updated by the delay() function
+}
+
+/** Disables task
+ * Task will no longer be executed by the scheduler
+ * Returns status of the task before disable was called (i.e., if the task was already disabled)
+ */
+bool Task::disable() {
+ bool previousEnabled = iStatus.enabled;
+ iStatus.enabled = false;
+ iStatus.inonenable = false;
+ if (previousEnabled && iOnDisable) {
+ Task *current = iScheduler->iCurrent;
+ iScheduler->iCurrent = this;
+ iOnDisable();
+ iScheduler->iCurrent = current;
+ }
+#ifdef _TASK_STATUS_REQUEST
+ iMyStatusRequest.signalComplete();
+#endif
+ return (previousEnabled);
+}
+
+/** Restarts task
+ * Task will run number of iterations again
+ */
+void Task::restart() {
+ iIterations = iSetIterations;
+ enable();
+}
+
+/** Restarts task delayed
+ * Task will run number of iterations again
+ */
+void Task::restartDelayed(unsigned long aDelay) {
+ iIterations = iSetIterations;
+ enableDelayed(aDelay);
+}
+
+// ------------------ Scheduler implementation --------------------
+
+/** Default constructor.
+ * Creates a scheduler with an empty execution chain.
+ */
+Scheduler::Scheduler() {
+ init();
+}
+
+/** Initializes all internal varaibles
+ */
+void Scheduler::init() {
+ iFirst = NULL;
+ iLast = NULL;
+ iCurrent = NULL;
+#ifdef _TASK_PRIORITY
+ iHighPriority = NULL;
+#endif // _TASK_PRIORITY
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ allowSleep(true);
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+}
+
+/** Appends task aTask to the tail of the execution chain.
+ * @param &aTask - reference to the Task to be appended.
+ * @note Task can only be part of the chain once.
+ */
+ void Scheduler::addTask(Task& aTask) {
+ if (aTask.iScheduler == this)
+ return;
+
+ aTask.iScheduler = this;
+// First task situation:
+ if (iFirst == NULL) {
+ iFirst = &aTask;
+ aTask.iPrev = NULL;
+ }
+ else {
+// This task gets linked back to the previous last one
+ aTask.iPrev = iLast;
+ iLast->iNext = &aTask;
+ }
+// "Previous" last task gets linked to this one - as this one becomes the last one
+ aTask.iNext = NULL;
+ iLast = &aTask;
+}
+
+/** Deletes specific Task from the execution chain
+ * @param &aTask - reference to the task to be deleted from the chain
+ */
+void Scheduler::deleteTask(Task& aTask) {
+ if (!aTask.iScheduler) {
+ return;
+ } else {
+ aTask.iScheduler = NULL;
+ if (aTask.iPrev == NULL) {
+ if (aTask.iNext == NULL) {
+ iFirst = NULL;
+ iLast = NULL;
+ return;
+ }
+ else {
+ aTask.iNext->iPrev = NULL;
+ iFirst = aTask.iNext;
+ aTask.iNext = NULL;
+ return;
+ }
+ }
+
+ if (aTask.iNext == NULL) {
+ aTask.iPrev->iNext = NULL;
+ iLast = aTask.iPrev;
+ aTask.iPrev = NULL;
+ return;
+ }
+
+ aTask.iPrev->iNext = aTask.iNext;
+ aTask.iNext->iPrev = aTask.iPrev;
+ aTask.iPrev = NULL;
+ aTask.iNext = NULL;
+ }
+}
+
+/** Disables all tasks in the execution chain
+ * Convenient for error situations, when the only
+ * task remaining active is an error processing task
+ * @param aRecursive - if true, tasks of the higher priority chains are disabled as well recursively
+ */
+void Scheduler::disableAll(bool aRecursive) {
+ Task *current = iFirst;
+ while (current) {
+ current->disable();
+ current = current->iNext;
+ }
+#ifdef _TASK_PRIORITY
+ if (aRecursive && iHighPriority) iHighPriority->disableAll(true);
+#endif // _TASK_PRIORITY
+}
+
+
+/** Enables all the tasks in the execution chain
+ * @param aRecursive - if true, tasks of the higher priority chains are enabled as well recursively
+ */
+ void Scheduler::enableAll(bool aRecursive) {
+ Task *current = iFirst;
+ while (current) {
+ current->enable();
+ current = current->iNext;
+ }
+#ifdef _TASK_PRIORITY
+ if (aRecursive && iHighPriority) iHighPriority->enableAll(true);
+#endif // _TASK_PRIORITY
+}
+
+/** Sets scheduler for the higher priority tasks (support for layered task priority)
+ * @param aScheduler - pointer to a scheduler for the higher priority tasks
+ */
+#ifdef _TASK_PRIORITY
+void Scheduler::setHighPriorityScheduler(Scheduler* aScheduler) {
+ if (aScheduler != this) iHighPriority = aScheduler; // Setting yourself as a higher priority one will create infinite recursive call
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ if (iHighPriority) {
+ iHighPriority->allowSleep(false); // Higher priority schedulers should not do power management
+ }
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+};
+#endif // _TASK_PRIORITY
+
+
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+void Scheduler::allowSleep(bool aState) {
+ iAllowSleep = aState;
+
+#ifdef ARDUINO_ARCH_ESP8266
+ wifi_set_sleep_type( iAllowSleep ? LIGHT_SLEEP_T : NONE_SLEEP_T );
+#endif // ARDUINO_ARCH_ESP8266
+
+}
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+
+
+void Scheduler::startNow( bool aRecursive ) {
+ unsigned long t = _TASK_TIME_FUNCTION();
+
+ iCurrent = iFirst;
+ while (iCurrent) {
+ if ( iCurrent->iStatus.enabled ) iCurrent->iPreviousMillis = t - iCurrent->iDelay;
+ iCurrent = iCurrent->iNext;
+ }
+
+#ifdef _TASK_PRIORITY
+ if (aRecursive && iHighPriority) iHighPriority->startNow( true );
+#endif // _TASK_PRIORITY
+}
+
+/** Returns number millis or micros until next scheduled iteration of a given task
+ *
+ * @param aTask - reference to task which next iteration is in question
+ */
+long Scheduler::timeUntilNextIteration(Task& aTask) {
+
+#ifdef _TASK_STATUS_REQUEST
+
+ StatusRequest *s = aTask.getStatusRequest();
+ if ( s != NULL && s->pending() )
+ return (-1); // cannot be determined
+#endif
+ if ( !aTask.isEnabled() )
+ return (-1); // cannot be determined
+
+ long d = (long) aTask.iDelay - ( (long) ((_TASK_TIME_FUNCTION() - aTask.iPreviousMillis)) );
+
+ if ( d < 0 )
+ return (0); // Task will run as soon as possible
+ return ( d );
+}
+
+
+/** Returns the size of the task lists (number of tasks)
+ *
+ */
+size_t Scheduler::size() {
+ size_t no = 0;
+ auto iTask = iFirst;
+ while (iTask) {
+ iTask = iTask->iNext;
+ ++no;
+ }
+ return no;
+}
+
+/** Makes one pass through the execution chain.
+ * Tasks are executed in the order they were added to the chain
+ * There is no concept of priority
+ * Different pseudo "priority" could be achieved
+ * by running task more frequently
+ */
+bool Scheduler::execute() {
+ bool idleRun = true;
+ register unsigned long m, i; // millis, interval;
+
+#ifdef ARDUINO_ARCH_ESP8266
+ unsigned long t1 = micros();
+ unsigned long t2 = 0;
+#endif // ARDUINO_ARCH_ESP8266
+
+ iCurrent = iFirst;
+
+ while (iCurrent) {
+
+#ifdef _TASK_PRIORITY
+ // If scheduler for higher priority tasks is set, it's entire chain is executed on every pass of the base scheduler
+ if (iHighPriority) idleRun = iHighPriority->execute() && idleRun;
+ iCurrentScheduler = this;
+#endif // _TASK_PRIORITY
+
+ do {
+ if ( iCurrent->iStatus.enabled ) {
+
+#ifdef _TASK_WDT_IDS
+ // For each task the control points are initialized to avoid confusion because of carry-over:
+ iCurrent->iControlPoint = 0;
+#endif // _TASK_WDT_IDS
+
+ // Disable task on last iteration:
+ if (iCurrent->iIterations == 0) {
+ iCurrent->disable();
+ break;
+ }
+ m = _TASK_TIME_FUNCTION();
+ i = iCurrent->iInterval;
+
+#ifdef _TASK_STATUS_REQUEST
+ // If StatusRequest object was provided, and still pending, and task is waiting, this task should not run
+ // Otherwise, continue with execution as usual. Tasks waiting to StatusRequest need to be rescheduled according to
+ // how they were placed into waiting state (waitFor or waitForDelayed)
+ if ( iCurrent->iStatus.waiting ) {
+ if ( (iCurrent->iStatusRequest)->pending() ) break;
+ if (iCurrent->iStatus.waiting == _TASK_SR_NODELAY) {
+ iCurrent->iPreviousMillis = m - (iCurrent->iDelay = i);
+ }
+ else {
+ iCurrent->iPreviousMillis = m;
+ }
+ iCurrent->iStatus.waiting = 0;
+ }
+#endif // _TASK_STATUS_REQUEST
+
+ if ( m - iCurrent->iPreviousMillis < iCurrent->iDelay ) break;
+
+ if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of never-ending task
+ iCurrent->iRunCounter++;
+ iCurrent->iPreviousMillis += iCurrent->iDelay;
+
+#ifdef _TASK_TIMECRITICAL
+ // Updated_previous+current interval should put us into the future, so iOverrun should be positive or zero.
+ // If negative - the task is behind (next execution time is already in the past)
+ unsigned long p = iCurrent->iPreviousMillis;
+ iCurrent->iOverrun = (long) ( p + i - m );
+ iCurrent->iStartDelay = (long) ( m - p );
+#endif // _TASK_TIMECRITICAL
+
+ iCurrent->iDelay = i;
+ if ( iCurrent->iCallback ) {
+ iCurrent->iCallback();
+ idleRun = false;
+ }
+ }
+ } while (0); //guaranteed single run - allows use of "break" to exit
+ iCurrent = iCurrent->iNext;
+#ifdef ARDUINO_ARCH_ESP8266
+ yield();
+#endif // ARDUINO_ARCH_ESP8266
+ }
+
+#ifdef _TASK_SLEEP_ON_IDLE_RUN
+ if (idleRun && iAllowSleep) {
+
+#ifdef ARDUINO_ARCH_AVR // Could be used only for AVR-based boards.
+ set_sleep_mode(SLEEP_MODE_IDLE);
+ sleep_enable();
+ /* Now enter sleep mode. */
+ sleep_mode();
+
+ /* The program will continue from here after the timer timeout ~1 ms */
+ sleep_disable(); /* First thing to do is disable sleep. */
+#endif // ARDUINO_ARCH_AVR
+
+#ifdef ARDUINO_ARCH_ESP8266
+// to do: find suitable sleep function for esp8266
+ t2 = micros() - t1;
+ if (t2 < _TASK_ESP8266_DLY_THRESHOLD) delay(1); // ESP8266 implementation of delay() uses timers and yield
+#endif // ARDUINO_ARCH_ESP8266
+ }
+#endif // _TASK_SLEEP_ON_IDLE_RUN
+
+ return (idleRun);
+}
+
+
+
+#endif /* _TASKSCHEDULER_H_ */
diff --git a/test/basic/basic.ino b/test/basic/basic.ino
new file mode 100644
index 0000000..189fff9
--- /dev/null
+++ b/test/basic/basic.ino
@@ -0,0 +1,116 @@
+#include
+
+#include
+
+#ifdef UNITY
+
+void test_findConnection() {
+ painlessMesh mesh;
+ TEST_ASSERT(NULL == mesh.findConnection(1));
+
+ auto conn = std::make_shared();
+ mesh._connections.push_back(conn);
+ TEST_ASSERT(NULL == mesh.findConnection(1));
+
+ auto conn2 = std::make_shared();
+ conn2->nodeId = 1;
+ mesh._connections.push_back(conn2);
+ TEST_ASSERT(mesh.findConnection(1));
+
+ // Add test for meshConnection with indirect connection
+ auto conn3 = std::make_shared();
+ conn3->nodeId = 2;
+ conn3->subConnections = "[{\"nodeId\":887034362,\"subs\":[{\"nodeId\":43,\"subs\":[]}]}]";
+ mesh._connections.push_back(conn3);
+ TEST_ASSERT(mesh.findConnection(887034362));
+
+ TEST_ASSERT(NULL == mesh.findConnection(70));
+ TEST_ASSERT(NULL == mesh.findConnection(88));
+ TEST_ASSERT(NULL == mesh.findConnection(87));
+ TEST_ASSERT(NULL == mesh.findConnection(62));
+ TEST_ASSERT(NULL == mesh.findConnection(36));
+ // 43 should match again
+ TEST_ASSERT(mesh.findConnection(43));
+}
+
+bool lesserThan(uint32_t lesser, uint32_t than) {
+ return
+ (int) lesser - (int) than < 0;// Cast to int in case of time rollover
+}
+
+void test_comparison() {
+ uint32_t uint32_max = 4294967295;
+ TEST_ASSERT(lesserThan(uint32_max-1,uint32_max));
+ TEST_ASSERT(lesserThan(uint32_max/2-1,uint32_max/2));
+ TEST_ASSERT(lesserThan(uint32_max/2,uint32_max/2+1));
+ TEST_ASSERT(lesserThan(10,100));
+
+ // Overflow
+ TEST_ASSERT(lesserThan(uint32_max,0));
+ TEST_ASSERT(lesserThan(uint32_max,100));
+}
+
+void test_subConnectionJsonHelper() {
+ auto node1 = std::make_shared();
+ auto node2 = std::make_shared();
+ node1->nodeId = -1; // Max uint value
+ node1->subConnections = String("[{\"nodeId\":886599975,\"subs\":[{\"nodeId\":2139268534,\"subs\":[{\"nodeId\":2132113212,\"subs\":[{\"nodeId\":2132046046}]}]}]}]");
+
+ node2->nodeId = 886231565;
+ node2->subConnections = String("[{\"nodeId\":2132113139,\"subs\":[]},{\"nodeId\":2132111373,\"subs\":[]}]");
+
+ ConnectionList connections;
+ connections.push_back(node1);
+ connections.push_back(node2);
+
+ painlessMesh mesh;
+ TEST_ASSERT(
+ String("[{\"nodeId\":4294967295,\"subs\":[{\"nodeId\":886599975,\"subs\":[{\"nodeId\":2139268534,\"subs\":[{\"nodeId\":2132113212,\"subs\":[{\"nodeId\":2132046046}]}]}]}]},{\"nodeId\":886231565,\"subs\":[{\"nodeId\":2132113139,\"subs\":[]},{\"nodeId\":2132111373,\"subs\":[]}]}]") ==
+ mesh.subConnectionJsonHelper(connections));
+
+ TEST_ASSERT(String("[{\"nodeId\":886231565,\"subs\":[{\"nodeId\":2132113139,\"subs\":[]},{\"nodeId\":2132111373,\"subs\":[]}]}]") ==
+ mesh.subConnectionJsonHelper(connections, node1->nodeId));
+
+ TEST_ASSERT(String("[{\"nodeId\":4294967295,\"subs\":[{\"nodeId\":886599975,\"subs\":[{\"nodeId\":2139268534,\"subs\":[{\"nodeId\":2132113212,\"subs\":[{\"nodeId\":2132046046}]}]}]}]}]") ==
+ mesh.subConnectionJsonHelper(connections, node2->nodeId));
+}
+
+void test_approxNoNodes() {
+ auto node1 = std::make_shared();
+ auto node2 = std::make_shared();
+ node1->subConnections = String("[{\"nodeId\":886599975,\"subs\":[{\"nodeId\":2139268534,\"subs\":[{\"nodeId\":2132113212,\"subs\":[{\"nodeId\":2132046046}]}]}]}]");
+ painlessMesh mesh;
+
+ TEST_ASSERT_EQUAL(4, mesh.approxNoNodes(node1->subConnections));
+
+
+ node2->subConnections = String("[{\"nodeId\":2132113139,\"subs\":[]},{\"nodeId\":2132111373,\"subs\":[]}]");
+ TEST_ASSERT_EQUAL(2, mesh.approxNoNodes(node2->subConnections));
+}
+
+void test_jsonnodeid() {
+ uint32_t mx = 0 - 1;
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& root = jsonBuffer.createObject();
+ root["test"] = mx;
+ TEST_ASSERT_EQUAL(root["test"], mx);
+ TEST_ASSERT(String(mx).equals(root["test"].as()));
+ TEST_ASSERT(!String(mx).equals(
+ String(root["test"].as())));
+ TEST_ASSERT(String(mx).equals(
+ String(root["test"].as())));
+}
+
+void setup() {
+ UNITY_BEGIN(); // IMPORTANT LINE!
+}
+
+void loop() {
+ RUN_TEST(test_findConnection);
+ RUN_TEST(test_comparison);
+ RUN_TEST(test_subConnectionJsonHelper);
+ RUN_TEST(test_approxNoNodes);
+ RUN_TEST(test_jsonnodeid);
+ UNITY_END(); // stop unit testing
+}
+#endif
diff --git a/test/basic/platformio.ini b/test/basic/platformio.ini
new file mode 100644
index 0000000..106887a
--- /dev/null
+++ b/test/basic/platformio.ini
@@ -0,0 +1,11 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+lib_deps = ArduinoJson
+ arduinoUnity
+ TaskScheduler
diff --git a/test/buffers/buffers.ino b/test/buffers/buffers.ino
new file mode 100644
index 0000000..0391e08
--- /dev/null
+++ b/test/buffers/buffers.ino
@@ -0,0 +1,289 @@
+#include
+
+#include
+
+#ifdef UNITY
+
+void test_string_split() {
+ const char* c1 = "abcdef";
+ String s1 = String(c1);
+ TEST_ASSERT_EQUAL(s1.length(), 6);
+
+ const char* c2 = "abcdef\0";
+ String s2 = String(c2);
+ TEST_ASSERT_EQUAL(s2.length(), 6);
+
+ const char* c3 = "abcdef\0ghi";
+ String s3 = String(c3);
+ TEST_ASSERT_EQUAL(s3.length(), 6);
+ const char *c4 = c3 + 7;
+ s3 = String(c4);
+ TEST_ASSERT_EQUAL(s3.length(), 3);
+ const char* c5 = "\0abcdef\0ghi";
+ String s5 = String(c5);
+ TEST_ASSERT_EQUAL(s5.length(), 0);
+
+ s3.concat(c3);
+ TEST_ASSERT_EQUAL(s3.length(), 9);
+ TEST_ASSERT(s3.equals(String("ghiabcdef")));
+
+ char v1[7] = {'a', 'b', 'c', 'd', 'e', 'f', '\0'};
+ s1 = String(v1);
+ TEST_ASSERT_EQUAL(s1.length(), 6);
+
+ // Next step is make sure we can actually send '\0' over tcp
+}
+
+void use_buffer(temp_buffer_t &buf) {
+ for(auto i = 0; i < buf.length; ++i) {
+ buf.buffer[i] = (char)random(65, 90);
+ }
+}
+
+void test_string_parse() {
+ temp_buffer_t buf;
+
+ const char* c1 = "abcdef\0";
+ auto rb = ReceiveBuffer();
+ rb.push(c1, 7, buf);
+ TEST_ASSERT_EQUAL(rb.jsonStrings.size(), 1);
+ TEST_ASSERT_EQUAL(rb.buffer.length(), 0);
+ TEST_ASSERT((rb.jsonStrings.end() - 1)->equals("abcdef"));
+ rb.push(c1, 7, buf);
+ TEST_ASSERT_EQUAL(rb.jsonStrings.size(), 2);
+ TEST_ASSERT_EQUAL(rb.buffer.length(), 0);
+
+ const char* c2 = "\0abcdef";
+ rb.push(c2, 7, buf);
+ use_buffer(buf);
+ TEST_ASSERT_EQUAL(rb.jsonStrings.size(), 2);
+ TEST_ASSERT_EQUAL(rb.buffer.length(), 6);
+
+ const char* c3 = "ghi\0abcdef";
+ rb.push(c3, 10, buf);
+ use_buffer(buf);
+ TEST_ASSERT_EQUAL(rb.jsonStrings.size(), 3);
+ TEST_ASSERT((rb.jsonStrings.end() - 1)->equals("abcdefghi"));
+ TEST_ASSERT_EQUAL((rb.jsonStrings.end() - 1)->length(), 9);
+ TEST_ASSERT_EQUAL(rb.buffer.length(), 6);
+
+ rb.push(c2, 7, buf);
+ TEST_ASSERT_EQUAL(rb.jsonStrings.size(), 4);
+ TEST_ASSERT((rb.jsonStrings.end() - 1)->equals("abcdef"));
+ TEST_ASSERT_EQUAL(rb.buffer.length(), 6);
+
+
+ rb.clear();
+ TEST_ASSERT_EQUAL(rb.jsonStrings.size(), 0);
+ TEST_ASSERT_EQUAL(rb.buffer.length(), 0);
+ use_buffer(buf);
+
+ // Skip empty
+ const char* c4 = "abc\0\0def";
+ rb.push(c4, 8, buf);
+ TEST_ASSERT_EQUAL(rb.jsonStrings.size(), 1);
+ TEST_ASSERT_EQUAL(rb.buffer.length(), 3);
+
+ rb.clear();
+ const char v1[4] = {'a', 'b', '\0', 'c'};
+ rb.push(v1, 4, buf);
+ TEST_ASSERT_EQUAL(rb.jsonStrings.size(), 1);
+ TEST_ASSERT(rb.front().equals("ab"));
+ TEST_ASSERT_EQUAL(rb.buffer.length(), 1);
+}
+
+// Test freeing pbuf afterwards
+void test_pbuf_parse() {
+ pbuf *p1 = pbuf_alloc(PBUF_RAW, 7, PBUF_POOL);
+ pbuf *p2 = pbuf_alloc(PBUF_RAW, 17, PBUF_POOL);
+ pbuf *p3 = pbuf_alloc(PBUF_RAW, 7, PBUF_POOL);
+
+ p1->len = 7;
+ p1->payload = (void*)"abcdef\0";
+ p1->tot_len = 7;
+
+ p2->len = 10;
+ p2->payload = (void*)"ghi\0jklmno";
+ p2->tot_len = 17;
+
+ p3->len = 7;
+ p3->payload = (void*)"pqr\0stu";
+ p3->tot_len = 7;
+
+ pbuf_cat(p2, p3);
+ TEST_ASSERT(p2->next != NULL);
+
+ temp_buffer_t buf;
+ auto rb = ReceiveBuffer();
+ rb.push(p1, buf);
+ TEST_ASSERT_EQUAL(1, rb.jsonStrings.size());
+ TEST_ASSERT_EQUAL(0, rb.buffer.length());
+
+ rb.push(p2, buf);
+ TEST_ASSERT_EQUAL(3, rb.jsonStrings.size());
+ TEST_ASSERT_EQUAL(3, rb.buffer.length());
+ use_buffer(buf);
+
+ //pbuf_free(p1); //This seems to crash always.
+ //pbuf_free(p2);
+
+ TEST_ASSERT(rb.front().equals("abcdef"));
+ rb.pop_front();
+ TEST_ASSERT(rb.front().equals("ghi"));
+ rb.pop_front();
+ TEST_ASSERT(rb.front().equals("jklmnopqr"));
+ rb.pop_front();
+ TEST_ASSERT(rb.empty());
+}
+
+void test_pushing_sent_buffer() {
+ temp_buffer_t buf;
+ SentBuffer sb;
+ TEST_ASSERT_EQUAL(0, sb.requestLength(buf.length));
+ TEST_ASSERT(sb.empty());
+
+ // Make sure that we correctly add + 1 length for strings
+ String s1 = "abc";
+ sb.push(s1);
+ TEST_ASSERT_EQUAL(4, sb.requestLength(buf.length));
+ TEST_ASSERT_EQUAL(1, sb.jsonStrings.size());
+ TEST_ASSERT(!sb.empty());
+
+ String s2 = "def";
+ sb.push(s2);
+
+ String s3 = "ghi";
+ sb.push(s3, true);
+ TEST_ASSERT((*sb.jsonStrings.begin()).equals("ghi"))
+ TEST_ASSERT((sb.jsonStrings.begin()+1)->equals("abc"))
+
+ sb.clear();
+ TEST_ASSERT(sb.empty());
+ TEST_ASSERT_EQUAL(0, sb.jsonStrings.size());
+}
+
+void test_sent_buffer_read() {
+ temp_buffer_t buf;
+ SentBuffer sb;
+ String s1 = "ab";
+ sb.push(s1);
+ s1 = "defghi";
+ sb.push(s1);
+ s1 = "jklxyz";
+ sb.push(s1);
+ s1 = "mnopqr";
+ sb.push(s1);
+ s1 = "wvu";
+ sb.push(s1);
+
+ TEST_ASSERT_EQUAL(3, sb.requestLength(buf.length));
+ sb.read(sb.requestLength(buf.length), buf);
+ TEST_ASSERT_EQUAL('a', buf.buffer[0]);
+ TEST_ASSERT_EQUAL('\0', buf.buffer[2]);
+
+ sb.freeRead();
+ TEST_ASSERT_EQUAL(6, sb.jsonStrings.begin()->length());
+ TEST_ASSERT(sb.jsonStrings.begin()->equals("defghi"));
+
+ sb.read(2, buf);
+ TEST_ASSERT_EQUAL('d', buf.buffer[0]);
+ TEST_ASSERT_EQUAL('e', buf.buffer[1]);
+
+ sb.freeRead();
+ TEST_ASSERT_EQUAL(4, sb.jsonStrings.begin()->length());
+ TEST_ASSERT_EQUAL(5, sb.requestLength(buf.length));
+ sb.read(3, buf);
+ TEST_ASSERT_EQUAL('f', buf.buffer[0]);
+ TEST_ASSERT_EQUAL('g', buf.buffer[1]);
+ TEST_ASSERT_EQUAL('h', buf.buffer[2]);
+ use_buffer(buf);
+ sb.freeRead();
+
+ TEST_ASSERT_EQUAL(2, sb.requestLength(buf.length));
+ sb.read(sb.requestLength(buf.length), buf);
+ TEST_ASSERT_EQUAL('i', buf.buffer[0]);
+ TEST_ASSERT_EQUAL('\0', buf.buffer[1]);
+ sb.freeRead();
+ use_buffer(buf);
+
+ sb.read(sb.requestLength(buf.length), buf);
+ TEST_ASSERT_EQUAL('j', buf.buffer[0]);
+ TEST_ASSERT_EQUAL('k', buf.buffer[1]);
+ TEST_ASSERT_EQUAL('l', buf.buffer[2]);
+ TEST_ASSERT_EQUAL('\0', buf.buffer[6]);
+ use_buffer(buf);
+}
+
+String randomString(uint32_t length) {
+ String str;
+ for(auto i = 0; i < length; ++i) {
+ char rnd = random(65, 90);
+ str += String(rnd);
+ }
+ return str;
+}
+
+void test_random_sent_receive() {
+ temp_buffer_t buf;
+ ReceiveBuffer rb;
+ SentBuffer sb;
+
+ SimpleList sent;
+ size_t i = 0;
+
+ #ifdef ESP32
+ auto end_i = 30;
+ #else
+ auto end_i = 5;
+ #endif
+
+ while(i < end_i || !sb.empty()) {
+ if (i < end_i) {
+ auto str = randomString(random(100, 3000));
+ sb.push(str);
+ sent.push_back(str);
+ }
+ ++i;
+
+ temp_buffer_t rb_buf;
+ size_t rq_len = sb.requestLength(rb_buf.length);
+ if (random(0,3) < 1) {
+ rq_len = random(0, rq_len);
+ }
+
+ use_buffer(buf);
+ sb.read(rq_len, buf);
+ use_buffer(rb_buf);
+ rb.push(buf.buffer, rq_len, rb_buf);
+ use_buffer(buf);
+ use_buffer(rb_buf);
+ sb.freeRead();
+
+ if (random(0,3) < 2) {
+ while(!rb.empty()) {
+ TEST_ASSERT(rb.front().equals((*sent.begin())));
+ sent.pop_front();
+ rb.pop_front();
+ }
+ }
+ }
+}
+
+// Make sure that empty only returns true if the void * buffer is empty too
+
+void setup() {
+ UNITY_BEGIN(); // IMPORTANT LINE!
+}
+
+void loop() {
+ RUN_TEST(test_string_split);
+ RUN_TEST(test_string_parse);
+ RUN_TEST(test_pbuf_parse);
+
+ RUN_TEST(test_pushing_sent_buffer);
+ RUN_TEST(test_sent_buffer_read);
+
+ RUN_TEST(test_random_sent_receive);
+ UNITY_END(); // stop unit testing
+}
+#endif
diff --git a/test/buffers/platformio.ini b/test/buffers/platformio.ini
new file mode 100644
index 0000000..e265885
--- /dev/null
+++ b/test/buffers/platformio.ini
@@ -0,0 +1,18 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+lib_deps = ArduinoJson
+ arduinoUnity
+
+[env:esp32]
+platform = espressif32
+board = esp32doit-devkit-v1
+framework = arduino
+lib_deps = ArduinoJson
+ arduinoUnity
+
diff --git a/test/echoNode/echoNode.ino b/test/echoNode/echoNode.ino
new file mode 100644
index 0000000..101180b
--- /dev/null
+++ b/test/echoNode/echoNode.ino
@@ -0,0 +1,68 @@
+//************************************************************
+// this is a simple example that uses the painlessMesh library and echos any
+// messages it receives
+//
+//************************************************************
+#include "painlessMesh.h"
+
+#define MESH_PREFIX "whateverYouLike"
+#define MESH_PASSWORD "somethingSneaky"
+#define MESH_PORT 5555
+
+painlessMesh mesh;
+
+size_t logServerId = 0;
+
+// Send message to the logServer every 10 seconds
+Task myLoggingTask(10000, TASK_FOREVER, []() {
+ if (logServerId != 0) {
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& msg = jsonBuffer.createObject();
+ msg["topic"] = "nodeStatus";
+ msg["nodeId"] = mesh.getNodeId();
+ msg["subs"] = mesh.subConnectionJson();
+ msg["logNode"] = logServerId;
+ msg["stability"] = mesh.stability;
+ msg["time"] = mesh.getNodeTime();
+
+ String str;
+ msg.printTo(str);
+ mesh.sendSingle(logServerId, str);
+
+ // log to serial
+ msg.printTo(Serial);
+ Serial.printf("\n");
+ }
+});
+
+void setup() {
+ Serial.begin(115200);
+
+ mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
+
+ mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT );
+ mesh.onReceive(&receivedCallback);
+
+ mesh.scheduler.addTask(myLoggingTask);
+ myLoggingTask.enable();
+}
+
+void loop() {
+ mesh.update();
+}
+
+void receivedCallback( uint32_t from, String &msg ) {
+ Serial.printf("echoNode: Received from %u msg=%s\n", from, msg.c_str());
+ mesh.sendSingle(from, msg);
+
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& root = jsonBuffer.parseObject(msg);
+ if (root.containsKey("topic")) {
+ if (String("logServer").equals(root["topic"].as())) {
+ // check for on: true or false
+ logServerId = root["nodeId"];
+ Serial.printf("logServer detected!!!\n");
+ }
+ Serial.printf("Handled from %u msg=%s\n", from, msg.c_str());
+ }
+}
diff --git a/test/echoNode/platformio.ini b/test/echoNode/platformio.ini
new file mode 100644
index 0000000..4a7a83e
--- /dev/null
+++ b/test/echoNode/platformio.ini
@@ -0,0 +1,10 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+#lib_deps =
+# painlessMesh
diff --git a/test/network.sh b/test/network.sh
new file mode 100755
index 0000000..a3676fc
--- /dev/null
+++ b/test/network.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+echo ""
+echo "Note that this test requires a running echoNode, which can be flashed with the following command:"
+echo "pio run -d test/echoNode -t upload"
+echo ""
+echo ""
+
+pio run -d test/network/ -t upload -e nodemcuv2; pio device monitor -b 115200
diff --git a/test/network/network.ino b/test/network/network.ino
new file mode 100644
index 0000000..72d40ca
--- /dev/null
+++ b/test/network/network.ino
@@ -0,0 +1,180 @@
+#include
+
+#include
+
+#ifdef UNITY
+/**
+Here we test the network behaviour of a node. These tests rely on a echoNode to be present
+*/
+
+#define MESH_PREFIX "whateverYouLike"
+#define MESH_PASSWORD "somethingSneaky"
+#define MESH_PORT 5555
+bool endEchoTest = false;
+bool sendingDone = false;
+bool endTest = false;
+
+painlessMesh mesh;
+
+SimpleList expected;
+SimpleList received;
+String lastNodeStatus;
+uint32_t lastNSReceived;
+
+size_t noCBs = 0;
+
+String randomString(uint32_t length) {
+ String str;
+ for(auto i = 0; i < length; ++i) {
+ char rnd = random(65, 90);
+ str += String(rnd);
+ }
+ return str;
+}
+
+void addMessage(painlessMesh &m, String msg, uint32_t nodeId) {
+ if (m.sendSingle(nodeId, msg))
+ expected.push_back(msg);
+}
+
+void newConnectionCallback(uint32_t nodeId) {
+ auto msg = String("Hello boy");
+ mesh.sendSingle(nodeId, msg);
+ expected.push_back(msg);
+
+ // Test 10 short msgs
+ for(auto i = 0; i < 10; ++i)
+ addMessage(mesh, randomString(random(10,50)), nodeId);
+
+ // Test 10 long msgs
+ for(auto i = 0; i < 10; ++i)
+ addMessage(mesh, randomString(random(1000,1300)), nodeId);
+
+
+ //addMessage(mesh, randomString(1800), nodeId);
+ //addMessage(mesh, randomString(3600), nodeId);
+
+ sendingDone = true;
+}
+
+void receivedCallback(uint32_t from, String &msg) {
+ ++noCBs;
+ received.push_back(msg);
+ Serial.printf("CB: %d, %d\n", expected.size(), received.size());
+ if (sendingDone && expected.size() == received.size())
+ endEchoTest = true;
+}
+
+void nodeStatusReceivedCallback(uint32_t from, String &msg) {
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& root = jsonBuffer.parseObject(msg);
+ if (root.containsKey("topic")) {
+ if (String("nodeStatus").equals(root["topic"].as())) {
+ lastNodeStatus = msg;
+ lastNSReceived = mesh.getNodeTime();
+ }
+ }
+}
+
+void test_no_received() {
+ TEST_ASSERT(noCBs > 1);
+ TEST_ASSERT_EQUAL(expected.size(), noCBs);
+}
+
+void test_received_equals_expected() {
+ auto r = received.begin();
+ auto e = expected.begin();
+ while(r != received.end()) {
+ TEST_ASSERT((*r).equals(*e));
+ ++r;
+ ++e;
+ }
+}
+
+void test_node_status() {
+ Serial.printf("Status: %s\n", lastNodeStatus.c_str());
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& root = jsonBuffer.parseObject(lastNodeStatus);
+ uint32_t t = root["time"];
+ // Time delay within 50ms
+ if (t > lastNSReceived) {
+ TEST_ASSERT(t - lastNSReceived < 50000);
+ } else {
+ TEST_ASSERT(lastNSReceived - t < 50000);
+ }
+ uint32_t otherNode = root["nodeId"];
+ TEST_ASSERT(mesh.findConnection(otherNode));
+ uint32_t logNode = root["logNode"];
+ TEST_ASSERT_EQUAL(logNode, mesh.getNodeId());
+}
+
+void test_stop() {
+ mesh.stop();
+ TEST_ASSERT_EQUAL(mesh.getNodeList().size(), 0);
+ TEST_ASSERT(mesh.scheduler.empty());
+}
+
+void logMessages() {
+ auto r = received.begin();
+ auto e = expected.begin();
+ while(r != received.end()) {
+ Serial.printf("Received: %s\n", (*r).c_str());
+ Serial.printf("Expected: %s\n", (*e).c_str());
+ ++r;
+ ++e;
+ }
+}
+
+Task logServerTask(10000, TASK_FOREVER, []() {
+ DynamicJsonBuffer jsonBuffer;
+ JsonObject& msg = jsonBuffer.createObject();
+ msg["topic"] = "logServer";
+ msg["nodeId"] = mesh.getNodeId();
+
+ String str;
+ msg.printTo(str);
+ mesh.sendBroadcast(str);
+
+ // log to serial
+ //msg.printTo(Serial);
+ //Serial.printf("\n");
+});
+
+Task waitTask;
+
+void setup() {
+ UNITY_BEGIN(); // IMPORTANT LINE!
+ //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE | DEBUG ); // all types on
+ mesh.setDebugMsgTypes(S_TIME | ERROR | CONNECTION | DEBUG);
+ mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT);
+ mesh.onNewConnection(&newConnectionCallback);
+ mesh.onReceive(&receivedCallback);
+}
+
+void loop() {
+ mesh.update();
+ //UNITY_END(); // stop unit testing
+ if (endEchoTest) {
+ RUN_TEST(test_no_received);
+ RUN_TEST(test_received_equals_expected);
+
+ //logMessages();
+ waitTask.set(5*TASK_SECOND, TASK_ONCE, []() {
+ endTest = true;
+ });
+ mesh.scheduler.addTask(waitTask);
+ waitTask.enableDelayed();
+ mesh.scheduler.addTask(logServerTask);
+ logServerTask.enable();
+ endEchoTest = false;
+ mesh.onReceive(&nodeStatusReceivedCallback);
+ }
+ if (endTest) {
+ RUN_TEST(test_node_status);
+ mesh.scheduler.deleteTask(waitTask);
+ mesh.scheduler.deleteTask(logServerTask);
+ RUN_TEST(test_stop);
+ UNITY_END();
+ }
+}
+#endif
diff --git a/test/network/platformio.ini b/test/network/platformio.ini
new file mode 100644
index 0000000..e180568
--- /dev/null
+++ b/test/network/platformio.ini
@@ -0,0 +1,19 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+lib_deps = ArduinoJson
+ arduinoUnity
+ TaskScheduler
+
+[env:esp32]
+platform = espressif32
+board = esp32doit-devkit-v1
+framework = arduino
+lib_deps = ArduinoJson
+ arduinoUnity
+ TaskScheduler
diff --git a/test/node.sh b/test/node.sh
new file mode 100755
index 0000000..275e925
--- /dev/null
+++ b/test/node.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+#inotify-hookable -w src -c "platformio ci --lib="." --board=nodemcuv2 examples/basic/basic.ino"
+pio run -d test/basic/ -t upload; pio device monitor -b 115200
+
+pio run -d test/time/ -t upload; pio device monitor -b 115200
diff --git a/test/time/platformio.ini b/test/time/platformio.ini
new file mode 100644
index 0000000..106887a
--- /dev/null
+++ b/test/time/platformio.ini
@@ -0,0 +1,11 @@
+[platformio]
+src_dir = .
+lib_extra_dirs = .piolibdeps/, ../../
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+lib_deps = ArduinoJson
+ arduinoUnity
+ TaskScheduler
diff --git a/test/time/time.ino b/test/time/time.ino
new file mode 100644
index 0000000..8163ac3
--- /dev/null
+++ b/test/time/time.ino
@@ -0,0 +1,57 @@
+#include
+
+extern uint32_t timeAdjuster;
+#include
+
+#ifdef UNITY
+
+void test_adjust_calc() {
+ timeSync ts;
+
+ uint32_t times[4];
+ times[0] = 0; times[1] = 0;
+ times[2] = 0; times[3] = 0;
+ TEST_ASSERT_EQUAL(0x7FFFFFFF, ts.calcAdjustment(times));
+ TEST_ASSERT_EQUAL(0, timeAdjuster);
+
+ times[0] = 1; times[1] = 1;
+ times[2] = 1; times[3] = 1;
+ TEST_ASSERT_EQUAL(0, ts.calcAdjustment(times));
+ TEST_ASSERT_EQUAL(0, timeAdjuster);
+
+ times[0] = 1; times[1] = 3;
+ times[2] = 1; times[3] = 3;
+ TEST_ASSERT_EQUAL(0, ts.calcAdjustment(times));
+ TEST_ASSERT_EQUAL(0, timeAdjuster);
+
+ times[0] = 1; times[1] = 3;
+ times[2] = 3; times[3] = 1;
+ TEST_ASSERT_EQUAL(2, ts.calcAdjustment(times));
+ TEST_ASSERT_EQUAL(2, timeAdjuster);
+
+ times[0] = 1; times[1] = 3;
+ times[2] = 5; times[3] = 1;
+ TEST_ASSERT_EQUAL(3, ts.calcAdjustment(times));
+ TEST_ASSERT_EQUAL(5, timeAdjuster);
+ timeAdjuster = 0;
+
+ times[0] = 3; times[1] = 1;
+ times[2] = 1; times[3] = 3;
+ TEST_ASSERT_EQUAL(-2, ts.calcAdjustment(times));
+ TEST_ASSERT_EQUAL(0xFFFFFFFF-1, timeAdjuster);
+
+ times[0] = 0xFFFFFFFF; times[1] = 1;
+ times[2] = 1; times[3] = 0xFFFFFFFF;
+ TEST_ASSERT_EQUAL(2, ts.calcAdjustment(times));
+ TEST_ASSERT_EQUAL(0, timeAdjuster);
+}
+
+void setup() {
+ UNITY_BEGIN(); // IMPORTANT LINE!
+}
+
+void loop() {
+ RUN_TEST(test_adjust_calc);
+ UNITY_END(); // stop unit testing
+}
+#endif