From 2b8bd8a0071083f5902787261730bcc0d82074f3 Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Sun, 8 Jan 2023 18:45:44 -0500 Subject: [PATCH] v1.5.2 for `ESP32 & LwIP W6100` #### Releases v1.5.2 1. Initial coding to support ESP32 boards using `W6100 LwIP Ethernet`. Sync with [WebServer_ESP32_W6100 v1.5.2](https://github.com/khoih-prog/WebServer_ESP32_W6100) --- .codespellrc | 7 + CONTRIBUTING.md | 81 ++ LICENSE | 674 +++++++++++ LibraryPatches/esp32/cores/esp32/Server.h | 35 + changelog.md | 30 + .../AdvancedWebServer/AdvancedWebServer.ino | 275 +++++ examples/HelloServer/HelloServer.ino | 167 +++ examples/HelloServer2/HelloServer2.ino | 190 ++++ examples/HttpBasicAuth/HttpBasicAuth.ino | 150 +++ examples/MQTTClient_Auth/MQTTClient_Auth.ino | 245 ++++ .../MQTTClient_Basic/MQTTClient_Basic.ino | 247 ++++ .../MQTT_ThingStream/MQTT_ThingStream.ino | 293 +++++ examples/PostServer/PostServer.ino | 215 ++++ .../SimpleAuthentication.ino | 268 +++++ examples/UdpNTPClient/UdpNTPClient.ino | 236 ++++ examples/UdpSendReceive/UdpSendReceive.ino | 237 ++++ examples/WebClient/WebClient.ino | 166 +++ .../WebClientRepeating/WebClientRepeating.ino | 179 +++ examples/WebServer/WebServer.ino | 190 ++++ .../multiFileProject/multiFileProject.cpp | 14 + examples/multiFileProject/multiFileProject.h | 36 + .../multiFileProject/multiFileProject.ino | 37 + library.json | 30 + library.properties | 11 + pics/AdvancedWebServer.png | Bin 0 -> 17511 bytes pics/W6100.png | Bin 0 -> 118672 bytes platformio/platformio.ini | 157 +++ src/WebServer_ESP32_W6100.h | 91 ++ src/WebServer_ESP32_W6100.hpp | 84 ++ src/WebServer_ESP32_W6100_Debug.h | 101 ++ src/WebServer_ESP32_W6100_Impl.h | 157 +++ src/w6100/esp32_w6100.cpp | 440 +++++++ src/w6100/esp32_w6100.h | 106 ++ src/w6100/esp_eth/esp_eth_mac_w6100.c | 1012 +++++++++++++++++ src/w6100/esp_eth/esp_eth_phy_w6100.c | 379 ++++++ src/w6100/esp_eth/esp_eth_spi_w6100.c | 135 +++ src/w6100/esp_eth/esp_eth_w6100.h | 153 +++ src/w6100/esp_eth/w6100.h | 199 ++++ utils/astyle_library.conf | 70 ++ utils/restyle.sh | 6 + 40 files changed, 7103 insertions(+) create mode 100644 .codespellrc create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 LibraryPatches/esp32/cores/esp32/Server.h create mode 100644 changelog.md create mode 100644 examples/AdvancedWebServer/AdvancedWebServer.ino create mode 100644 examples/HelloServer/HelloServer.ino create mode 100644 examples/HelloServer2/HelloServer2.ino create mode 100644 examples/HttpBasicAuth/HttpBasicAuth.ino create mode 100644 examples/MQTTClient_Auth/MQTTClient_Auth.ino create mode 100644 examples/MQTTClient_Basic/MQTTClient_Basic.ino create mode 100644 examples/MQTT_ThingStream/MQTT_ThingStream.ino create mode 100644 examples/PostServer/PostServer.ino create mode 100644 examples/SimpleAuthentication/SimpleAuthentication.ino create mode 100644 examples/UdpNTPClient/UdpNTPClient.ino create mode 100644 examples/UdpSendReceive/UdpSendReceive.ino create mode 100644 examples/WebClient/WebClient.ino create mode 100644 examples/WebClientRepeating/WebClientRepeating.ino create mode 100644 examples/WebServer/WebServer.ino create mode 100644 examples/multiFileProject/multiFileProject.cpp create mode 100644 examples/multiFileProject/multiFileProject.h create mode 100644 examples/multiFileProject/multiFileProject.ino create mode 100644 library.json create mode 100644 library.properties create mode 100644 pics/AdvancedWebServer.png create mode 100644 pics/W6100.png create mode 100644 platformio/platformio.ini create mode 100644 src/WebServer_ESP32_W6100.h create mode 100644 src/WebServer_ESP32_W6100.hpp create mode 100644 src/WebServer_ESP32_W6100_Debug.h create mode 100644 src/WebServer_ESP32_W6100_Impl.h create mode 100644 src/w6100/esp32_w6100.cpp create mode 100644 src/w6100/esp32_w6100.h create mode 100644 src/w6100/esp_eth/esp_eth_mac_w6100.c create mode 100644 src/w6100/esp_eth/esp_eth_phy_w6100.c create mode 100644 src/w6100/esp_eth/esp_eth_spi_w6100.c create mode 100644 src/w6100/esp_eth/esp_eth_w6100.h create mode 100644 src/w6100/esp_eth/w6100.h create mode 100644 utils/astyle_library.conf create mode 100644 utils/restyle.sh diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..00fe362 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,7 @@ +# See: https://github.com/codespell-project/codespell#using-a-config-file +[codespell] +# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: +ignore-words-list = , +check-filenames = +check-hidden = +skip = ./.git,./src,./examples,./Packages_Patches,./LibraryPatches diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..41aa27d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,81 @@ +## Contributing to WebServer_ESP32_W6100 + +### Reporting Bugs + +Please report bugs in WebServer_ESP32_W6100 if you find them. + +However, before reporting a bug please check through the following: + +* [Existing Open Issues](https://github.com/khoih-prog/WebServer_ESP32_W6100/issues) - someone might have already encountered this. + +If you don't find anything, please [open a new issue](https://github.com/khoih-prog/WebServer_ESP32_W6100/issues/new). + +--- + +### How to submit a bug report + +Please ensure to specify the following: + +* Arduino IDE version (e.g. 1.8.19) or Platform.io version +* Board type (e.g. ESP32_DEV) +* Board Core Version (e.g. ESP32 core v2.0.6) +* Contextual information (e.g. what you were trying to achieve) +* Simplest possible steps to reproduce +* Anything that might be relevant in your opinion, such as: + * Operating system (Windows, Ubuntu, etc.) and the output of `uname -a` + * Network configuration + + +Please be educated, civilized and constructive as you've always been. Disrespective posts against [GitHub Code of Conduct](https://docs.github.com/en/site-policy/github-terms/github-event-code-of-conduct) will be ignored and deleted. + +--- + +### Example + +``` +Arduino IDE version: 1.8.19 +ESP32_DEV board +ESP32 core v2.0.6 +OS: Ubuntu 20.04 LTS +Linux xy-Inspiron-3593 5.15.0-57-generic #63~20.04.1-Ubuntu SMP Wed Nov 30 13:40:16 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux + +Context: +I encountered a crash while using this library + +Steps to reproduce: +1. ... +2. ... +3. ... +4. ... +``` + +--- + +### Sending Feature Requests + +Feel free to post feature requests. It's helpful if you can explain exactly why the feature would be useful. + +There are usually some outstanding feature requests in the [existing issues list](https://github.com/khoih-prog/WebServer_ESP32_W6100/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. + +--- + +### Sending Pull Requests + +Pull Requests with changes and fixes are also welcome! + +Please use the `astyle` to reformat the updated library code as follows (demo for Ubuntu Linux) + +1. Change directory to the library GitHub + +``` +xy@xy-Inspiron-3593:~$ cd Arduino/xy/WebServer_ESP32_W6100_GitHub/ +xy@xy-Inspiron-3593:~/Arduino/xy/WebServer_ESP32_W6100_GitHub$ +``` + +2. Issue astyle command + +``` +xy@xy-Inspiron-3593:~/Arduino/xy/WebServer_ESP32_W6100_GitHub$ bash utils/restyle.sh +``` + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /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. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LibraryPatches/esp32/cores/esp32/Server.h b/LibraryPatches/esp32/cores/esp32/Server.h new file mode 100644 index 0000000..2a677df --- /dev/null +++ b/LibraryPatches/esp32/cores/esp32/Server.h @@ -0,0 +1,35 @@ +/* + Server.h - Base class that provides Server + Copyright (c) 2011 Adrian McEwen. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef server_h +#define server_h + +#include "Print.h" + +class Server: public Print +{ + public: + // KH, change to fix compiler error for EthernetWebServer + // error: cannot declare field 'EthernetWebServer::_server' to be of abstract type 'EthernetServer' + // virtual void begin(uint16_t port=0) =0; + //virtual void begin() = 0; + void begin() {}; +}; + +#endif diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..d823c4f --- /dev/null +++ b/changelog.md @@ -0,0 +1,30 @@ +## WebServer_ESP32_W6100 Library + +[![arduino-library-badge](https://www.ardu-badge.com/badge/WebServer_ESP32_W6100.svg?)](https://www.ardu-badge.com/WebServer_ESP32_W6100) +[![GitHub release](https://img.shields.io/github/release/khoih-prog/WebServer_ESP32_W6100.svg)](https://github.com/khoih-prog/WebServer_ESP32_W6100/releases) +[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#Contributing) +[![GitHub issues](https://img.shields.io/github/issues/khoih-prog/WebServer_ESP32_W6100.svg)](http://github.com/khoih-prog/WebServer_ESP32_W6100/issues) + +Donate to my libraries using BuyMeACoffee + + + +--- +--- + +## Table of Contents + + +* [Changelog](#changelog) + * [Releases v1.5.2](#releases-v152) + +--- +--- + +## Changelog + +#### Releases v1.5.2 + +1. Initial coding to support ESP32 boards using `W6100 LwIP Ethernet`. Sync with [WebServer_ESP32_W6100 v1.5.2](https://github.com/khoih-prog/WebServer_ESP32_W6100) + + diff --git a/examples/AdvancedWebServer/AdvancedWebServer.ino b/examples/AdvancedWebServer/AdvancedWebServer.ino new file mode 100644 index 0000000..1ade9ed --- /dev/null +++ b/examples/AdvancedWebServer/AdvancedWebServer.ino @@ -0,0 +1,275 @@ +/**************************************************************************************************************************** + AdvancedWebServer.h + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Copyright (c) 2015, Majenko Technologies + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + Neither the name of Majenko Technologies nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WebServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +int reqCount = 0; // number of requests received + +void handleRoot() +{ +#define BUFFER_SIZE 400 + + char temp[BUFFER_SIZE]; + int sec = millis() / 1000; + int min = sec / 60; + int hr = min / 60; + int day = hr / 24; + + hr = hr % 24; + + snprintf(temp, BUFFER_SIZE - 1, + "\ +\ +\ +AdvancedWebServer %s\ +\ +\ +\ +

Hi from WebServer_ESP32_W6100!

\ +

on %s

\ +

Uptime: %d d %02d:%02d:%02d

\ +\ +\ +", BOARD_NAME, BOARD_NAME, day, hr % 24, min % 60, sec % 60); + + server.send(200, F("text/html"), temp); +} + +void handleNotFound() +{ + String message = F("File Not Found\n\n"); + + message += F("URI: "); + message += server.uri(); + message += F("\nMethod: "); + message += (server.method() == HTTP_GET) ? F("GET") : F("POST"); + message += F("\nArguments: "); + message += server.args(); + message += F("\n"); + + for (uint8_t i = 0; i < server.args(); i++) + { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(404, F("text/plain"), message); +} + +void drawGraph() +{ + String out; + out.reserve(3000); + char temp[70]; + + out += F("\n"); + out += F("\n"); + out += F("\n"); + int y = rand() % 130; + + for (int x = 10; x < 300; x += 10) + { + int y2 = rand() % 130; + sprintf(temp, "\n", x, 140 - y, x + 10, 140 - y2); + out += temp; + y = y2; + } + + out += F("\n\n"); + + server.send(200, F("image/svg+xml"), out); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart AdvancedWebServer on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + server.on(F("/"), handleRoot); + server.on(F("/test.svg"), drawGraph); + server.on(F("/inline"), []() + { + server.send(200, F("text/plain"), F("This works as well")); + }); + + server.onNotFound(handleNotFound); + server.begin(); + + Serial.print(F("HTTP EthernetWebServer is @ IP : ")); + Serial.println(ETH.localIP()); +} + +void heartBeatPrint() +{ + static int num = 1; + + Serial.print(F(".")); + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } +} + +void check_status() +{ + static unsigned long checkstatus_timeout = 0; + +#define STATUS_CHECK_INTERVAL 10000L + + // Send status report every STATUS_REPORT_INTERVAL (60) seconds: we don't need to send updates frequently if there is no status change. + if ((millis() > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = millis() + STATUS_CHECK_INTERVAL; + } +} + +void loop() +{ + server.handleClient(); + check_status(); +} diff --git a/examples/HelloServer/HelloServer.ino b/examples/HelloServer/HelloServer.ino new file mode 100644 index 0000000..888a10d --- /dev/null +++ b/examples/HelloServer/HelloServer.ino @@ -0,0 +1,167 @@ +/**************************************************************************************************************************** + HelloServer.ino - Dead simple web-server for Ethernet shields + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WebServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +void handleRoot() +{ + String html = F("Hello from HelloServer running on "); + + html += String(BOARD_NAME); + + server.send(200, F("text/plain"), html); +} + +void handleNotFound() +{ + String message = F("File Not Found\n\n"); + + message += F("URI: "); + message += server.uri(); + message += F("\nMethod: "); + message += (server.method() == HTTP_GET) ? F("GET") : F("POST"); + message += F("\nArguments: "); + message += server.args(); + message += F("\n"); + + for (uint8_t i = 0; i < server.args(); i++) + { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(404, F("text/plain"), message); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart HelloServer on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + server.on(F("/"), handleRoot); + + server.on(F("/inline"), []() + { + server.send(200, F("text/plain"), F("This works as well")); + }); + + server.onNotFound(handleNotFound); + + server.begin(); + + Serial.print(F("HTTP EthernetWebServer is @ IP : ")); + Serial.println(ETH.localIP()); +} + +void loop() +{ + server.handleClient(); +} diff --git a/examples/HelloServer2/HelloServer2.ino b/examples/HelloServer2/HelloServer2.ino new file mode 100644 index 0000000..d7b8a7e --- /dev/null +++ b/examples/HelloServer2/HelloServer2.ino @@ -0,0 +1,190 @@ +/**************************************************************************************************************************** + HelloServer2.h - Dead simple web-server for Ethernet shields + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WebServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +void handleRoot() +{ + String html = F("Hello from HelloServer2 running on "); + + html += String(BOARD_NAME); + + server.send(200, F("text/plain"), html); +} + +void handleNotFound() +{ + String message = F("File Not Found\n\n"); + + message += F("URI: "); + message += server.uri(); + message += F("\nMethod: "); + message += (server.method() == HTTP_GET) ? F("GET") : F("POST"); + message += F("\nArguments: "); + message += server.args(); + message += F("\n"); + + for (uint8_t i = 0; i < server.args(); i++) + { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(404, F("text/plain"), message); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart HelloServer2 on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + server.on(F("/"), handleRoot); + + server.on(F("/inline"), []() + { + server.send(200, F("text/plain"), F("This works as well")); + }); + + server.on(F("/gif"), []() + { + static const uint8_t gif[] PROGMEM = + { + 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80, 0x01, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x19, 0x8c, 0x8f, 0xa9, 0xcb, 0x9d, + 0x00, 0x5f, 0x74, 0xb4, 0x56, 0xb0, 0xb0, 0xd2, 0xf2, 0x35, 0x1e, 0x4c, + 0x0c, 0x24, 0x5a, 0xe6, 0x89, 0xa6, 0x4d, 0x01, 0x00, 0x3b + }; + + char gif_colored[sizeof(gif)]; + + memcpy_P(gif_colored, gif, sizeof(gif)); + + // Set the background to a random set of colors + gif_colored[16] = millis() % 256; + gif_colored[17] = millis() % 256; + gif_colored[18] = millis() % 256; + + server.send_P(200, "image/gif", gif_colored, sizeof(gif_colored)); + }); + + server.onNotFound(handleNotFound); + + server.begin(); + + Serial.print(F("HTTP HelloServer2 started @ IP : ")); + Serial.println(ETH.localIP()); +} + +void loop() +{ + server.handleClient(); +} diff --git a/examples/HttpBasicAuth/HttpBasicAuth.ino b/examples/HttpBasicAuth/HttpBasicAuth.ino new file mode 100644 index 0000000..a4de958 --- /dev/null +++ b/examples/HttpBasicAuth/HttpBasicAuth.ino @@ -0,0 +1,150 @@ +/**************************************************************************************************************************** + HTTPBasicAuth.h - Dead simple web-server for Ethernet shields + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WebServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +const char* www_username = "admin"; +const char* www_password = "esp32_W6100"; + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart HTTPBasicAuth on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + server.on(F("/"), []() + { + if (!server.authenticate(www_username, www_password)) + { + return server.requestAuthentication(); + } + + server.send(200, F("text/plain"), F("Login OK")); + }); + + server.begin(); + + Serial.print(F("HTTP HTTPBasicAuth started @ IP : ")); + Serial.println(ETH.localIP()); + + Serial.print(F("Open http://")); + Serial.print(ETH.localIP()); + Serial.println(F("/ in your browser to see it working")); + Serial.print(F("Using username : ")); + Serial.print(www_username); + Serial.print(F(" and password : ")); + Serial.println(www_password); +} + +void loop() +{ + server.handleClient(); +} diff --git a/examples/MQTTClient_Auth/MQTTClient_Auth.ino b/examples/MQTTClient_Auth/MQTTClient_Auth.ino new file mode 100644 index 0000000..641331f --- /dev/null +++ b/examples/MQTTClient_Auth/MQTTClient_Auth.ino @@ -0,0 +1,245 @@ +/**************************************************************************************************************************** + MQTTClient_Auth.ino - Dead simple MQTT Client for Ethernet shields + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +/* + Basic MQTT example (without SSL!) with Authentication + This sketch demonstrates the basic capabilities of the library. + It connects to an MQTT server then: + - providing username and password + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic", printing out any messages + it receives. NB - it assumes the received payloads are strings not binary + It will reconnect to the server if the connection is lost using a blocking + reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to + achieve the same result without blocking the main loop. +*/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WebServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +#include + +// Update these with values suitable for your network. +//const char* mqttServer = "broker.example"; // Broker address +const char* mqttServer = "broker.emqx.io"; // Broker address +//const char* mqttServer = "broker.shiftr.io"; // Broker address + +const char *ID = "MQTTClient_SSL-Client"; // Name of our device, must be unique +const char *TOPIC = "MQTT_Pub"; // Topic to subscribe to +const char *subTopic = "MQTT_Sub"; // Topic to subscribe to + +//IPAddress mqttServer(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) +{ + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + + for (unsigned int i = 0; i < length; i++) + { + Serial.print((char)payload[i]); + } + + Serial.println(); +} + +WiFiClient ethClient; +PubSubClient client(mqttServer, 1883, callback, ethClient); + +void reconnect() +{ + // Loop until we're reconnected + while (!client.connected()) + { + Serial.print("Attempting MQTT connection to "); + Serial.print(mqttServer); + + // Attempt to connect + if (client.connect("arduino", "try", "try")) + { + Serial.println("...connected"); + + // Once connected, publish an announcement... + String data = "Hello from MQTTClient_SSL on " + String(BOARD_NAME); + + client.publish(TOPIC, data.c_str()); + + //Serial.println("Published connection message successfully!"); + //Serial.print("Subscribed to: "); + //Serial.println(subTopic); + + // This is a workaround to address https://github.com/OPEnSLab-OSU/SSLClient/issues/9 + //ethClientSSL.flush(); + // ... and resubscribe + client.subscribe(subTopic); + // for loopback testing + client.subscribe(TOPIC); + // This is a workaround to address https://github.com/OPEnSLab-OSU/SSLClient/issues/9 + //ethClientSSL.flush(); + } + else + { + Serial.print("...failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart MQTTClient_Auth on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + // Note - the default maximum packet size is 128 bytes. If the + // combined length of clientId, username and password exceed this use the + // following to increase the buffer size: + // client.setBufferSize(255); +} + +#define MQTT_PUBLISH_INTERVAL_MS 5000L + +String data = "Hello from MQTTClient_Auth on " + String(ARDUINO_BOARD) + " with " + String(SHIELD_TYPE); +const char *pubData = data.c_str(); + +unsigned long lastMsg = 0; + +void loop() +{ + static unsigned long now; + + if (!client.connected()) + { + reconnect(); + } + + // Sending Data + now = millis(); + + if (now - lastMsg > MQTT_PUBLISH_INTERVAL_MS) + { + lastMsg = now; + + if (!client.publish(TOPIC, pubData)) + { + Serial.println("Message failed to send."); + } + + Serial.print("Message Send : " + String(TOPIC) + " => "); + Serial.println(data); + } + + client.loop(); +} diff --git a/examples/MQTTClient_Basic/MQTTClient_Basic.ino b/examples/MQTTClient_Basic/MQTTClient_Basic.ino new file mode 100644 index 0000000..c5af51b --- /dev/null +++ b/examples/MQTTClient_Basic/MQTTClient_Basic.ino @@ -0,0 +1,247 @@ +/**************************************************************************************************************************** + MQTTClient_Basic.ino - Dead simple MQTT Client for Ethernet shields + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +/* + Basic MQTT example (without SSL!) with Authentication + This sketch demonstrates the basic capabilities of the library. + It connects to an MQTT server then: + - providing username and password + - publishes "hello world" to the topic "outTopic" + - subscribes to the topic "inTopic", printing out any messages + it receives. NB - it assumes the received payloads are strings not binary + + It will reconnect to the server if the connection is lost using a blocking + reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to + achieve the same result without blocking the main loop. +*/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WebServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +#include + +// Update these with values suitable for your network. +//const char* mqttServer = "broker.example"; // Broker address +const char* mqttServer = "broker.emqx.io"; // Broker address +//const char* mqttServer = "broker.shiftr.io"; // Broker address + +const char *ID = "MQTTClient_SSL-Client"; // Name of our device, must be unique +const char *TOPIC = "MQTT_Pub"; // Topic to subscribe to +const char *subTopic = "MQTT_Sub"; // Topic to subscribe to + +//IPAddress mqttServer(172, 16, 0, 2); + +void callback(char* topic, byte* payload, unsigned int length) +{ + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + + for (unsigned int i = 0; i < length; i++) + { + Serial.print((char)payload[i]); + } + + Serial.println(); +} + +WiFiClient ethClient; +PubSubClient client(mqttServer, 1883, callback, ethClient); + +void reconnect() +{ + // Loop until we're reconnected + while (!client.connected()) + { + //Serial.print("Attempting MQTT connection to "); + //Serial.print(mqttServer); + + // Attempt to connect + if (client.connect(ID, "try", "try")) + { + //Serial.println("...connected"); + + // Once connected, publish an announcement... + String data = "Hello from MQTTClient_SSL on " + String(BOARD_NAME); + + client.publish(TOPIC, data.c_str()); + + //Serial.println("Published connection message successfully!"); + //Serial.print("Subscribed to: "); + //Serial.println(subTopic); + + // This is a workaround to address https://github.com/OPEnSLab-OSU/SSLClient/issues/9 + //ethClientSSL.flush(); + // ... and resubscribe + client.subscribe(subTopic); + // for loopback testing + client.subscribe(TOPIC); + // This is a workaround to address https://github.com/OPEnSLab-OSU/SSLClient/issues/9 + //ethClientSSL.flush(); + } + else + { + Serial.print("...failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart MQTTClient_Basic on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + client.setServer(mqttServer, 1883); + client.setCallback(callback); + + // Allow the hardware to sort itself out + delay(1500); +} + +#define MQTT_PUBLISH_INTERVAL_MS 5000L + +String data = "Hello from MQTTClient_Basic on " + String(ARDUINO_BOARD) + " with " + String(SHIELD_TYPE); +const char *pubData = data.c_str(); + +unsigned long lastMsg = 0; + +void loop() +{ + static unsigned long now; + + if (!client.connected()) + { + reconnect(); + } + + // Sending Data + now = millis(); + + if (now - lastMsg > MQTT_PUBLISH_INTERVAL_MS) + { + lastMsg = now; + + if (!client.publish(TOPIC, pubData)) + { + Serial.println("Message failed to send."); + } + + Serial.print("Message Send : " + String(TOPIC) + " => "); + Serial.println(data); + } + + client.loop(); +} diff --git a/examples/MQTT_ThingStream/MQTT_ThingStream.ino b/examples/MQTT_ThingStream/MQTT_ThingStream.ino new file mode 100644 index 0000000..9b5a5ec --- /dev/null +++ b/examples/MQTT_ThingStream/MQTT_ThingStream.ino @@ -0,0 +1,293 @@ +/**************************************************************************************************************************** + MQTT_ThingStream.ino - Dead simple MQTT Client for Ethernet shields + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ +/* + Basic MQTT example (without SSL!) + This sketch demonstrates the basic capabilities of the library. + It connects to an MQTT server then: + - publishes {Hello from MQTTClient_SSL on NUCLEO_F767ZI} to the topic [STM32_Pub] + - subscribes to the topic [STM32_Sub], printing out any messages + it receives. NB - it assumes the received payloads are strings not binary + It will reconnect to the server if the connection is lost using a blocking + reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to + achieve the same result without blocking the main loop. + + You will need to populate "certificates.h" with your trust anchors + (see https://github.com/OPEnSLab-OSU/SSLClient/blob/master/TrustAnchors.md) + and my_cert/my_key with your certificate/private key pair + (see https://github.com/OPEnSLab-OSU/SSLClient#mtls). +*/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WebServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +#include + +const char my_cert[] = "FIXME"; +const char my_key[] = "FIXME"; + +#define USING_THINGSTREAM_IO false //true + +#if USING_THINGSTREAM_IO + + const char *MQTT_PREFIX_TOPIC = "esp32-sniffer/"; + const char *MQTT_ANNOUNCE_TOPIC = "/status"; + const char *MQTT_CONTROL_TOPIC = "/control"; + const char *MQTT_BLE_TOPIC = "/ble"; + + + // GOT FROM ThingsStream! + const char *MQTT_SERVER = "mqtt.thingstream.io"; + const char *MQTT_USER = "MQTT_USER"; + const char *MQTT_PASS = "MQTT_PASS"; + const char *MQTT_CLIENT_ID = "MQTT_CLIENT_ID"; + + String topic = MQTT_PREFIX_TOPIC + String("12345678") + MQTT_BLE_TOPIC; + String subTopic = MQTT_PREFIX_TOPIC + String("12345678") + MQTT_BLE_TOPIC; + +#else + + const char* MQTT_SERVER = "broker.emqx.io"; // Broker address + + const char* ID = "MQTTClient_SSL-Client"; // Name of our device, must be unique + String topic = "ESP32_Pub"; // Topic to subscribe to + String subTopic = "ESP32_Sub"; // Topic to subscribe to + +#endif + +void mqtt_receive_callback(char* topic, byte* payload, unsigned int length); + +const int MQTT_PORT = 1883; //if you use SSL //1883 no SSL + +unsigned long lastMsg = 0; + +// Initialize the SSL client library +// Arguments: EthernetClient, our trust anchors + + +WiFiClient ethClient; + +PubSubClient client(MQTT_SERVER, MQTT_PORT, mqtt_receive_callback, ethClient); + +/* + Called whenever a payload is received from a subscribed MQTT topic +*/ +void mqtt_receive_callback(char* topic, byte* payload, unsigned int length) +{ + Serial.print("MQTT Message receive ["); + Serial.print(topic); + Serial.print("] "); + + for (unsigned int i = 0; i < length; i++) + { + Serial.print((char)payload[i]); + } + + Serial.println(); +} + +void reconnect() +{ + // Loop until we're reconnected + while (!client.connected()) + { + //Serial.print("Attempting MQTT connection to "); + //Serial.println(MQTT_SERVER); + + // Attempt to connect + +#if USING_THINGSTREAM_IO + int connect_status = client.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS, topic.c_str(), 2, false, ""); +#else + int connect_status = client.connect(ID); +#endif + + if (connect_status) + { + //Serial.println("...connected"); + + // Once connected, publish an announcement... + String data = "Hello from MQTTClient_SSL on " + String(BOARD_NAME); + + client.publish(topic.c_str(), data.c_str()); + + //Serial.println("Published connection message successfully!"); + + //Serial.print("Subscribed to: "); + //Serial.println(subTopic); + + // This is a workaround to address https://github.com/OPEnSLab-OSU/SSLClient/issues/9 + //ethClientSSL.flush(); + // ... and resubscribe + client.subscribe(subTopic.c_str()); + // for loopback testing + client.subscribe(topic.c_str()); + // This is a workaround to address https://github.com/OPEnSLab-OSU/SSLClient/issues/9 + //ethClientSSL.flush(); + } + else + { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart MQTT_ThingStream on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + // Note - the default maximum packet size is 256 bytes. If the + // combined length of clientId, username and password exceed this use the + // following to increase the buffer size: + //client.setBufferSize(256); + + Serial.println("***************************************"); + Serial.println(topic); + Serial.println("***************************************"); +} + +#define MQTT_PUBLISH_INTERVAL_MS 5000L + +String data = "Hello from MQTT_ThingStream on " + String(BOARD_NAME) + " with " + String(SHIELD_TYPE); +const char *pubData = data.c_str(); + +void loop() +{ + static unsigned long now; + + if (!client.connected()) + { + reconnect(); + } + + // Sending Data + now = millis(); + + if (now - lastMsg > MQTT_PUBLISH_INTERVAL_MS) + { + lastMsg = now; + + if (!client.publish(topic.c_str(), pubData)) + { + Serial.println("Message failed to send."); + } + + Serial.print("MQTT Message Send : " + topic + " => "); + Serial.println(data); + } + + client.loop(); +} diff --git a/examples/PostServer/PostServer.ino b/examples/PostServer/PostServer.ino new file mode 100644 index 0000000..70fcd02 --- /dev/null +++ b/examples/PostServer/PostServer.ino @@ -0,0 +1,215 @@ +/**************************************************************************************************************************** + PostServer.h - Dead simple web-server for Ethernet shields + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WebServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +const String postForms = + F("\ +\ +WebServer_ESP32_ENC POST handling\ +\ +\ +\ +

POST plain text to /postplain/


\ +
\ +
\ +\ +
\ +

POST form data to /postform/


\ +
\ +
\ +\ +
\ +\ +"); + +void handleRoot() +{ + server.send(200, F("text/html"), postForms); +} + +void handlePlain() +{ + if (server.method() != HTTP_POST) + { + server.send(405, F("text/plain"), F("Method Not Allowed")); + } + else + { + server.send(200, F("text/plain"), "POST body was:\n" + server.arg("plain")); + } +} + +void handleForm() +{ + if (server.method() != HTTP_POST) + { + server.send(405, F("text/plain"), F("Method Not Allowed")); + } + else + { + String message = F("POST form was:\n"); + + for (uint8_t i = 0; i < server.args(); i++) + { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(200, F("text/plain"), message); + } +} + +void handleNotFound() +{ + String message = F("File Not Found\n\n"); + + message += F("URI: "); + message += server.uri(); + message += F("\nMethod: "); + message += (server.method() == HTTP_GET) ? F("GET") : F("POST"); + message += F("\nArguments: "); + message += server.args(); + message += F("\n"); + + for (uint8_t i = 0; i < server.args(); i++) + { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(404, F("text/plain"), message); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart POSTServer on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + server.on(F("/"), handleRoot); + + server.on(F("/postplain/"), handlePlain); + + server.on(F("/postform/"), handleForm); + + server.onNotFound(handleNotFound); + + server.begin(); + + Serial.print(F("HTTP POSTServer started @ IP : ")); + Serial.println(ETH.localIP()); +} + +void loop() +{ + server.handleClient(); +} diff --git a/examples/SimpleAuthentication/SimpleAuthentication.ino b/examples/SimpleAuthentication/SimpleAuthentication.ino new file mode 100644 index 0000000..108bde8 --- /dev/null +++ b/examples/SimpleAuthentication/SimpleAuthentication.ino @@ -0,0 +1,268 @@ +/**************************************************************************************************************************** + SimpleAuthentication.ino - Dead simple web-server for Ethernet shields + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WebServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +//Check if header is present and correct +bool is_authenticated() +{ + Serial.println(F("Enter is_authenticated")); + + if (server.hasHeader(F("Cookie"))) + { + Serial.print(F("Found cookie: ")); + String cookie = server.header(F("Cookie")); + Serial.println(cookie); + + if (cookie.indexOf(F("ESPSESSIONID=1")) != -1) + { + Serial.println(F("Authentication Successful")); + return true; + } + } + + Serial.println(F("Authentication Failed")); + return false; +} + +//login page, also called for disconnect +void handleLogin() +{ + String msg; + + if (server.hasHeader(F("Cookie"))) + { + Serial.print(F("Found cookie: ")); + String cookie = server.header(F("Cookie")); + Serial.println(cookie); + } + + if (server.hasArg(F("DISCONNECT"))) + { + Serial.println(F("Disconnection")); + server.sendHeader(F("Location"), F("/login")); + server.sendHeader(F("Cache-Control"), F("no-cache")); + server.sendHeader(F("Set-Cookie"), F("ESPSESSIONID=0")); + server.send(301); + return; + } + + if (server.hasArg(F("USERNAME")) && server.hasArg(F("PASSWORD"))) + { + if (server.arg(F("USERNAME")) == F("admin") && server.arg(F("PASSWORD")) == F("password")) + { + server.sendHeader(F("Location"), F("/")); + server.sendHeader(F("Cache-Control"), F("no-cache")); + server.sendHeader(F("Set-Cookie"), F("ESPSESSIONID=1")); + server.send(301); + Serial.println(F("Log in Successful")); + return; + } + + msg = F("Wrong username/password! try again."); + Serial.println(F("Log in Failed")); + } + + String content = F("
To log in, please use : admin/password
"); + content += F("User:
"); + content += F("Password:
"); + content += F("
"); + content += msg; + content += F("
"); + content += F("You also can go here"); + server.send(200, F("text/html"), content); +} + +//root page can be accessed only if authentication is ok +void handleRoot() +{ + String header; + + Serial.println(F("Enter handleRoot")); + + if (!is_authenticated()) + { + server.sendHeader(F("Location"), F("/login")); + server.sendHeader(F("Cache-Control"), F("no-cache")); + server.send(301); + return; + } + + String content = F("

Hello, you're connected to WebServer_ESP32_W6100 running on "); + + content += String(ARDUINO_BOARD); + content += F("!


"); + + if (server.hasHeader(F("User-Agent"))) + { + content += F("the user agent used is : "); + content += server.header(F("User-Agent")); + content += F("

"); + } + + content += F("You can access this page until you disconnect"); + server.send(200, F("text/html"), content); +} + +//no need authentication +void handleNotFound() +{ + String message = F("File Not Found\n\n"); + + message += F("URI: "); + message += server.uri(); + message += F("\nMethod: "); + message += (server.method() == HTTP_GET) ? F("GET") : F("POST"); + message += F("\nArguments: "); + message += server.args(); + message += F("\n"); + + for (uint8_t i = 0; i < server.args(); i++) + { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(404, F("text/plain"), message); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart SimpleAuthentication on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + server.on(F("/"), handleRoot); + + server.on(F("/login"), handleLogin); + + server.on(F("/inline"), []() + { + server.send(200, F("text/plain"), F("This works without need of authentication")); + }); + + server.onNotFound(handleNotFound); + + //here the list of headers to be recorded + const char * headerkeys[] = {"User-Agent", "Cookie"} ; + size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); + + //ask server to track these headers + server.collectHeaders(headerkeys, headerkeyssize); + server.begin(); + + Serial.print(F("HTTP SimpleAuthentication is @ IP : ")); + Serial.println(ETH.localIP()); +} + +void loop() +{ + server.handleClient(); +} diff --git a/examples/UdpNTPClient/UdpNTPClient.ino b/examples/UdpNTPClient/UdpNTPClient.ino new file mode 100644 index 0000000..b6ac191 --- /dev/null +++ b/examples/UdpNTPClient/UdpNTPClient.ino @@ -0,0 +1,236 @@ +/**************************************************************************************************************************** + UdpNTPClient.ino - Simple Arduino web server sample for ESP8266 AT-command shield + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ +/* + The Arduino board communicates with the shield using the SPI bus. This is on digital pins 11, 12, and 13 on the Uno + and pins 50, 51, and 52 on the Mega. On both boards, pin 10 is used as SS. On the Mega, the hardware SS pin, 53, + is not used to select the Ethernet controller chip, but it must be kept as an output or the SPI interface won't work. +*/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +char timeServer[] = "time.nist.gov"; // NTP server +unsigned int localPort = 2390; // local port to listen for UDP packets + +const int NTP_PACKET_SIZE = 48; // NTP timestamp is in the first 48 bytes of the message +const int UDP_TIMEOUT = 2000; // timeout in milliseconds to wait for an UDP packet to arrive + +byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets + +// A UDP instance to let us send and receive packets over UDP +WiFiUDP Udp; + +// send an NTP request to the time server at the given address +void sendNTPpacket(char *ntpSrv) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + Udp.beginPacket(ntpSrv, 123); //NTP requests are to port 123 + + Udp.write(packetBuffer, NTP_PACKET_SIZE); + + Udp.endPacket(); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart UdpNTPClient on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + Udp.begin(localPort); +} + +void loop() +{ + sendNTPpacket(timeServer); // send an NTP packet to a time server + + // wait for a reply for UDP_TIMEOUT milliseconds + unsigned long startMs = millis(); + + while (!Udp.available() && (millis() - startMs) < UDP_TIMEOUT) {} + + // if there's data available, read a packet + int packetSize = Udp.parsePacket(); + + if (packetSize) + { + Serial.print(F("UDP Packet received, size ")); + Serial.println(packetSize); + Serial.print(F("From ")); + IPAddress remoteIp = Udp.remoteIP(); + Serial.print(remoteIp); + Serial.print(F(", port ")); + Serial.println(Udp.remotePort()); + + // We've received a packet, read the data from it into the buffer + Udp.read(packetBuffer, NTP_PACKET_SIZE); + + // the timestamp starts at byte 40 of the received packet and is four bytes, + // or two words, long. First, esxtract the two words: + + unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); + unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); + + // combine the four bytes (two words) into a long integer + // this is NTP time (seconds since Jan 1 1900): + unsigned long secsSince1900 = highWord << 16 | lowWord; + + Serial.print(F("Seconds since Jan 1 1900 = ")); + Serial.println(secsSince1900); + + // now convert NTP time into )everyday time: + Serial.print(F("Unix time = ")); + // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: + const unsigned long seventyYears = 2208988800UL; + // subtract seventy years: + unsigned long epoch = secsSince1900 - seventyYears; + // print Unix time: + Serial.println(epoch); + + // print the hour, minute and second: + Serial.print(F("The UTC time is ")); // UTC is the time at Greenwich Meridian (GMT) + Serial.print((epoch % 86400L) / 3600); // print the hour (86400 equals secs per day) + Serial.print(F(":")); + + if (((epoch % 3600) / 60) < 10) + { + // In the first 10 minutes of each hour, we'll want a leading '0' + Serial.print(F("0")); + } + + Serial.print((epoch % 3600) / 60); // print the minute (3600 equals secs per minute) + Serial.print(F(":")); + + if ((epoch % 60) < 10) + { + // In the first 10 seconds of each minute, we'll want a leading '0' + Serial.print(F("0")); + } + + Serial.println(epoch % 60); // print the second + } + + // wait ten seconds before asking for the time again + delay(10000); +} diff --git a/examples/UdpSendReceive/UdpSendReceive.ino b/examples/UdpSendReceive/UdpSendReceive.ino new file mode 100644 index 0000000..2a3d2d4 --- /dev/null +++ b/examples/UdpSendReceive/UdpSendReceive.ino @@ -0,0 +1,237 @@ +/**************************************************************************************************************************** + UDPSendReceive.ino - Simple Arduino web server sample for ESP8266/ESP32 AT-command shield + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +char timeServer[] = "time.nist.gov"; // NTP server +unsigned int localPort = 2390; // local port to listen for UDP packets + +const int NTP_PACKET_SIZE = 48; // NTP timestamp is in the first 48 bytes of the message +const int UDP_TIMEOUT = 2000; // timeout in milliseconds to wait for an UDP packet to arrive + +byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming packet +byte ReplyBuffer[] = "ACK"; // a string to send back + +// A UDP instance to let us send and receive packets over UDP +WiFiUDP Udp; + +// send an NTP request to the time server at the given address +void sendNTPpacket(char *ntpSrv) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + Udp.beginPacket(ntpSrv, 123); //NTP requests are to port 123 + + Udp.write(packetBuffer, NTP_PACKET_SIZE); + + Udp.endPacket(); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart UDPSendReceive on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + Serial.println(F("\nStarting connection to server...")); + // if you get a connection, report back via serial: + Udp.begin(localPort); + + Serial.print(F("Listening on port ")); + Serial.println(localPort); +} + +void loop() +{ + sendNTPpacket(timeServer); // send an NTP packet to a time server + + // wait for a reply for UDP_TIMEOUT milliseconds + unsigned long startMs = millis(); + + while (!Udp.available() && (millis() - startMs) < UDP_TIMEOUT) {} + + // if there's data available, read a packet + int packetSize = Udp.parsePacket(); + + if (packetSize) + { + Serial.print(F("UDP Packet received, size ")); + Serial.println(packetSize); + Serial.print(F("From ")); + IPAddress remoteIp = Udp.remoteIP(); + Serial.print(remoteIp); + Serial.print(F(", port ")); + Serial.println(Udp.remotePort()); + + // We've received a packet, read the data from it into the buffer + Udp.read(packetBuffer, NTP_PACKET_SIZE); + + // the timestamp starts at byte 40 of the received packet and is four bytes, + // or two words, long. First, esxtract the two words: + + unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); + unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); + + // combine the four bytes (two words) into a long integer + // this is NTP time (seconds since Jan 1 1900): + unsigned long secsSince1900 = highWord << 16 | lowWord; + + Serial.print(F("Seconds since Jan 1 1900 = ")); + Serial.println(secsSince1900); + + // now convert NTP time into )everyday time: + Serial.print(F("Unix time = ")); + // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: + const unsigned long seventyYears = 2208988800UL; + // subtract seventy years: + unsigned long epoch = secsSince1900 - seventyYears; + // print Unix time: + Serial.println(epoch); + + // print the hour, minute and second: + Serial.print(F("The UTC time is ")); // UTC is the time at Greenwich Meridian (GMT) + Serial.print((epoch % 86400L) / 3600); // print the hour (86400 equals secs per day) + Serial.print(F(":")); + + if (((epoch % 3600) / 60) < 10) + { + // In the first 10 minutes of each hour, we'll want a leading '0' + Serial.print(F("0")); + } + + Serial.print((epoch % 3600) / 60); // print the minute (3600 equals secs per minute) + Serial.print(F(":")); + + if ((epoch % 60) < 10) + { + // In the first 10 seconds of each minute, we'll want a leading '0' + Serial.print(F("0")); + } + + Serial.println(epoch % 60); // print the second + } + + // wait ten seconds before asking for the time again + delay(10000); +} diff --git a/examples/WebClient/WebClient.ino b/examples/WebClient/WebClient.ino new file mode 100644 index 0000000..28a3065 --- /dev/null +++ b/examples/WebClient/WebClient.ino @@ -0,0 +1,166 @@ +/**************************************************************************************************************************** + WebClient.ino - Simple Arduino web server sample for Ethernet shield + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +char server[] = "arduino.tips"; + +// Initialize the Ethernet client object +WiFiClient client; + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart WebClient on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + // KH, Dynamic not OK yet + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + Serial.println(); + Serial.println(F("Starting connection to server...")); + + // if you get a connection, report back via serial + if (client.connect(server, 80)) + { + Serial.println(F("Connected to server")); + // Make a HTTP request + client.println(F("GET /asciilogo.txt HTTP/1.1")); + client.println(F("Host: arduino.tips")); + client.println(F("Connection: close")); + client.println(); + } +} + +void printoutData() +{ + // if there are incoming bytes available + // from the server, read them and print them + while (client.available()) + { + char c = client.read(); + Serial.write(c); + Serial.flush(); + } +} + +void loop() +{ + printoutData(); + + // if the server's disconnected, stop the client + if (!client.connected()) + { + Serial.println(); + Serial.println(F("Disconnecting from server...")); + client.stop(); + + // do nothing forever + while (true) + yield(); + } +} diff --git a/examples/WebClientRepeating/WebClientRepeating.ino b/examples/WebClientRepeating/WebClientRepeating.ino new file mode 100644 index 0000000..bf5b6bb --- /dev/null +++ b/examples/WebClientRepeating/WebClientRepeating.ino @@ -0,0 +1,179 @@ +/**************************************************************************************************************************** + WebClientRepeating.ino - Simple Arduino web server sample for Ethernet shield + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ +/* + The Arduino board communicates with the shield using the SPI bus. This is on digital pins 11, 12, and 13 on the Uno + and pins 50, 51, and 52 on the Mega. On both boards, pin 10 is used as SS. On the Mega, the hardware SS pin, 53, + is not used to select the Ethernet controller chip, but it must be kept as an output or the SPI interface won't work. +*/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +char server[] = "arduino.tips"; + +unsigned long lastConnectionTime = 0; // last time you connected to the server, in milliseconds +const unsigned long postingInterval = 10000L; // delay between updates, in milliseconds + +// Initialize the Web client object +WiFiClient client; + +// this method makes a HTTP connection to the server +void httpRequest() +{ + Serial.println(); + + // close any connection before send a new request + // this will free the socket on the WiFi shield + client.stop(); + + // if there's a successful connection + if (client.connect(server, 80)) + { + Serial.println(F("Connecting...")); + + // send the HTTP PUT request + client.println(F("GET /asciilogo.txt HTTP/1.1")); + client.println(F("Host: arduino.tips")); + client.println(F("Connection: close")); + client.println(); + + // note the time that the connection was made + lastConnectionTime = millis(); + } + else + { + // if you couldn't make a connection + Serial.println(F("Connection failed")); + } +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart WebClientRepeating on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// +} + +void loop() +{ + // if there's incoming data from the net connection send it out the serial port + // this is for debugging purposes only + while (client.available()) + { + char c = client.read(); + Serial.write(c); + Serial.flush(); + } + + // if 10 seconds have passed since your last connection, + // then connect again and send data + if (millis() - lastConnectionTime > postingInterval) + { + httpRequest(); + } +} diff --git a/examples/WebServer/WebServer.ino b/examples/WebServer/WebServer.ino new file mode 100644 index 0000000..e8123ad --- /dev/null +++ b/examples/WebServer/WebServer.ino @@ -0,0 +1,190 @@ +/**************************************************************************************************************************** + WebServer.ino - Simple Arduino web server sample for Ethernet shield + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for (ESP32 + W6100) to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +#define DEBUG_ETHERNET_WEBSERVER_PORT Serial + +// Debug Level from 0 to 4 +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 3 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +WiFiServer server(80); + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +IPAddress myIP(192, 168, 2, 232); +IPAddress myGW(192, 168, 2, 1); +IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +IPAddress myDNS(8, 8, 8, 8); + +int reqCount = 0; // number of requests received + +void setup() +{ + Serial.begin(115200); + + while (!Serial && (millis() < 5000)); + + Serial.print(F("\nStart WebServer on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + ET_LOGWARN(F("Default SPI pinout:")); + ET_LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + ET_LOGWARN1(F("MOSI:"), MOSI_GPIO); + ET_LOGWARN1(F("MISO:"), MISO_GPIO); + ET_LOGWARN1(F("SCK:"), SCK_GPIO); + ET_LOGWARN1(F("CS:"), CS_GPIO); + ET_LOGWARN1(F("INT:"), INT_GPIO); + ET_LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + ET_LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W6100_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W6100_Mac = W6100_Default_Mac); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[millis() % NUMBER_OF_MAC] ); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(myIP, myGW, mySN, myDNS); + + ESP32_W6100_waitForConnect(); + + /////////////////////////////////// + + // start the web server on port 80 + server.begin(); +} + +void loop() +{ + // listen for incoming clients + WiFiClient client = server.available(); + + if (client) + { + Serial.println(F("New client")); + // an http request ends with a blank line + bool currentLineIsBlank = true; + + while (client.connected()) + { + if (client.available()) + { + char c = client.read(); + Serial.write(c); + + // if you've gotten to the end of the line (received a newline + // character) and the line is blank, the http request has ended, + // so you can send a reply + if (c == '\n' && currentLineIsBlank) + { + Serial.println(F("Sending response")); + + // send a standard http response header + // use \r\n instead of many println statements to speedup data send + client.print( + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n" // the connection will be closed after completion of the response + "Refresh: 20\r\n" // refresh the page automatically every 20 sec + "\r\n"); + client.print("\r\n"); + client.print("\r\n"); + client.print(String("

Hello World from ") + BOARD_NAME + "!

\r\n"); + client.print("Requests received: "); + client.print(++reqCount); + client.print("
\r\n"); + client.print("
\r\n"); + client.print("\r\n"); + break; + } + + if (c == '\n') + { + // you're starting a new line + currentLineIsBlank = true; + } + else if (c != '\r') + { + // you've gotten a character on the current line + currentLineIsBlank = false; + } + } + } + + // give the web browser time to receive the data + delay(10); + + // close the connection: + client.stop(); + Serial.println(F("Client disconnected")); + } +} diff --git a/examples/multiFileProject/multiFileProject.cpp b/examples/multiFileProject/multiFileProject.cpp new file mode 100644 index 0000000..84d35b1 --- /dev/null +++ b/examples/multiFileProject/multiFileProject.cpp @@ -0,0 +1,14 @@ +/**************************************************************************************************************************** + multiFileProject.cpp + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license +*****************************************************************************************************************************/ + +// To demo how to include files in multi-file Projects + +#include "multiFileProject.h" diff --git a/examples/multiFileProject/multiFileProject.h b/examples/multiFileProject/multiFileProject.h new file mode 100644 index 0000000..32a365c --- /dev/null +++ b/examples/multiFileProject/multiFileProject.h @@ -0,0 +1,36 @@ +/**************************************************************************************************************************** + multiFileProject.h + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license +*****************************************************************************************************************************/ + +// To demo how to include files in multi-file Projects + +#pragma once + +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 1 + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +// Can be included as many times as necessary, without `Multiple Definitions` Linker Error +#include "WebServer_ESP32_W6100.hpp" diff --git a/examples/multiFileProject/multiFileProject.ino b/examples/multiFileProject/multiFileProject.ino new file mode 100644 index 0000000..212be29 --- /dev/null +++ b/examples/multiFileProject/multiFileProject.ino @@ -0,0 +1,37 @@ +/**************************************************************************************************************************** + multiFileProject.ino + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license +*****************************************************************************************************************************/ + +// To demo how to include files in multi-file Projects + +#include "multiFileProject.h" + +// Can be included as many times as necessary, without `Multiple Definitions` Linker Error +#include "WebServer_ESP32_W6100.h" + +void setup() +{ + Serial.begin(115200); + + while (!Serial); + + delay(500); + + Serial.println("\nStart multiFileProject"); + Serial.println(WEBSERVER_ESP32_W6100_VERSION); + + + Serial.print("You're OK now"); +} + +void loop() +{ + // put your main code here, to run repeatedly: +} diff --git a/library.json b/library.json new file mode 100644 index 0000000..8d58940 --- /dev/null +++ b/library.json @@ -0,0 +1,30 @@ +{ + "name": "WebServer_ESP32_W6100", + "version": "1.5.2", + "keywords": "WebServer, Ethernet, MQTT, MQTTS, HTTP, HTTPS, SSL, Arduino, ESP32, W6100, HTTP-Client, WebSocket-Client, MQTT-Client, server, client, websocket, LittleFS, SPIFFS, ThingStream", + "description": "Simple Ethernet WebServer, HTTP/HTTPS Client wrapper library for ESP32 boards using W6100 with LwIP Ethernet library. The WebServer supports HTTP(S) GET and POST requests, provides argument parsing, handles one client at a time. It provides HTTP(S), MQTT(S) Client and supports WebServer serving from LittleFS/SPIFFS", + "authors": + { + "name": "Khoi Hoang", + "url": "https://github.com/khoih-prog", + "maintainer": true + }, + "repository": + { + "type": "git", + "url": "https://github.com/khoih-prog/WebServer_ESP32_W6100" + }, + "homepage": "https://github.com/khoih-prog/WebServer_ESP32_W6100", + "export": { + "exclude": [ + "linux", + "extras", + "tests" + ] + }, + "license": "GPL-3.0", + "frameworks": "*", + "platforms": "espressif32", + "examples": "examples/*/*/*.ino", + "headers": ["WebServer_ESP32_W6100.h", "WebServer_ESP32_W6100.hpp"] +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..58d9cf6 --- /dev/null +++ b/library.properties @@ -0,0 +1,11 @@ +name=WebServer_ESP32_W6100 +version=1.5.2 +author=Khoi Hoang +license=GPLv3 +maintainer=Khoi Hoang +sentence=Simple Ethernet WebServer, HTTP/HTTPS Client wrapper library for ESP32 boards using W6100 with LwIP Ethernet library. +paragraph=The WebServer supports HTTP(S) GET and POST requests, provides argument parsing, handles one client at a time. It provides HTTP(S), MQTT(S) Client and supports WebServer serving from LittleFS/SPIFFS +category=Communication +url=https://github.com/khoih-prog/WebServer_ESP32_W6100 +architectures=esp32 +includes=WebServer_ESP32_W6100.h, WebServer_ESP32_W6100.hpp diff --git a/pics/AdvancedWebServer.png b/pics/AdvancedWebServer.png new file mode 100644 index 0000000000000000000000000000000000000000..8c9d5131ad4c18e0ccf19b0fed7c3851a27360b7 GIT binary patch literal 17511 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\>cQ_|&5X~0gOmfu)F{5y2FdkK)Hb(| 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%0AH@AP`nGZcX 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

-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 literal 0 HcmV?d00001 diff --git a/platformio/platformio.ini b/platformio/platformio.ini new file mode 100644 index 0000000..6e30ad4 --- /dev/null +++ b/platformio/platformio.ini @@ -0,0 +1,157 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +; ============================================================ +; chose environment: +; ESP32 +; esp32s2 +; esp32s3 +; esp32c3 + +; ============================================================ +default_envs = ESP32 + +[env] +; ============================================================ +; Serial configuration +; choose upload speed, serial-monitor speed +; ============================================================ +upload_speed = 921600 +;upload_port = COM11 +;monitor_speed = 9600 +;monitor_port = COM11 + +; Checks for the compatibility with frameworks and dev/platforms +lib_compat_mode = strict +lib_ldf_mode = chain+ +;lib_ldf_mode = deep+ + +lib_deps = + +build_flags = +; set your debug output (default=Serial) + -D DEBUG_ESP_PORT=Serial +; comment the following line to enable WiFi debugging + -D NDEBUG + +[env:ESP32] +platform = espressif32 +framework = arduino + +; ============================================================ +; Board configuration +; choose your board by uncommenting one of the following lines +; ============================================================ +;board = esp32cam +;board = alksesp32 +;board = featheresp32 +;board = espea32 +;board = bpi-bit +;board = d-duino-32 +board = esp32doit-devkit-v1 +;board = pocket_32 +;board = fm-devkit +;board = pico32 +;board = esp32-evb +;board = esp32-gateway +;board = esp32-pro +;board = esp32-poe +;board = oroca_edubot +;board = onehorse32dev +;board = lopy +;board = lopy4 +;board = wesp32 +;board = esp32thing +;board = sparkfun_lora_gateway_1-channel +;board = ttgo-lora32-v1 +;board = ttgo-t-beam +;board = turta_iot_node +;board = lolin_d32 +;board = lolin_d32_pro +;board = lolin32 +;board = wemosbat +;board = widora-air +;board = xinabox_cw02 +;board = iotbusio +;board = iotbusproteus +;board = nina_w10 + +; ============================================================ + +[env:esp32s2] +platform = espressif32 +framework = arduino + +; toolchain download links see +; refer "name": "xtensa-esp32s2-elf-gcc","version": "gcc8_4_0-esp-2021r1" section of +; https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json +; e.g. Windows: https://github.com/espressif/crosstool-NG/releases/download/esp-2021r1/xtensa-esp32s2-elf-gcc8_4_0-esp-2021r1-win32.zip +platform_packages = + toolchain-xtensa32s2@file://C:\Users\Max\Downloads\xtensa-esp32s2-elf + framework-arduinoespressif32@https://github.com/espressif/arduino-esp32.git#a4118ea88987c28aac3a49bcb9cc5d6c0acc6f3f + platformio/tool-esptoolpy @ ~1.30100 +framework = arduino +board = esp32dev +board_build.mcu = esp32s2 +board_build.partitions = huge_app.csv +board_build.variant = esp32s2 +board_build.f_cpu = 240000000L +board_build.f_flash = 80000000L +board_build.flash_mode = qio +board_build.arduino.ldscript = esp32s2_out.ld +build_unflags = + -DARDUINO_ESP32_DEV + -DARDUINO_VARIANT="esp32" +build_flags = + -DARDUINO_ESP32S2_DEV + -DARDUINO_VARIANT="esp32s2" + +; ============================================================ + +[env:esp32s3] +platform = espressif32 +framework = arduino + +board_build.mcu = esp32s3 +board_build.partitions = huge_app.csv +board_build.variant = esp32s3 +board_build.f_cpu = 240000000L +board_build.f_flash = 80000000L +board_build.flash_mode = qio +board_build.arduino.ldscript = esp32s3_out.ld +build_unflags = + -DARDUINO_ESP32_DEV + -DARDUINO_VARIANT="esp32" +build_flags = + -DARDUINO_ESP32S3_DEV + -DARDUINO_VARIANT="esp32s3" + +; ============================================================ + +[env:esp32sc3] +platform = espressif32 +framework = arduino + +board_build.mcu = esp32c3 +board_build.partitions = huge_app.csv +board_build.variant = esp32c3 +board_build.f_cpu = 160000000L +board_build.f_flash = 80000000L +board_build.flash_mode = qio +board_build.arduino.ldscript = esp32c3_out.ld +build_unflags = + -DARDUINO_ESP32_DEV + -DARDUINO_VARIANT="esp32" +build_flags = + -DARDUINO_ESP32S3_DEV + -DARDUINO_VARIANT="esp32c3" + +; ============================================================ diff --git a/src/WebServer_ESP32_W6100.h b/src/WebServer_ESP32_W6100.h new file mode 100644 index 0000000..af1a2cd --- /dev/null +++ b/src/WebServer_ESP32_W6100.h @@ -0,0 +1,91 @@ +/**************************************************************************************************************************** + WebServer_ESP32_W6100.h + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W5500 v1.5.2 + *****************************************************************************************************************************/ + +#pragma once + +#ifndef WEBSERVER_ESP32_W6100_H +#define WEBSERVER_ESP32_W6100_H + +////////////////////////////////////////////////////////////// + +//#if !defined(USING_CORE_ESP32_CORE_V200_PLUS) +#if ( ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) && ( ARDUINO_ESP32_GIT_VER != 0x46d5afb1 ) ) + #define USING_CORE_ESP32_CORE_V200_PLUS true + + #if (_ETHERNET_WEBSERVER_LOGLEVEL_ > 3) + #warning Using code for ESP32 core v2.0.0+ in WebServer_ESP32_W6100.h + #endif + + #define WEBSERVER_ESP32_W6100_VERSION "WebServer_ESP32_W6100 v1.5.2 for core v2.0.0+" +#else + #if (_ETHERNET_WEBSERVER_LOGLEVEL_ > 3) + #warning Using code for ESP32 core v1.0.6- in WebServer_ESP32_W6100.h + #endif + + #define WEBSERVER_ESP32_W6100_VERSION "WebServer_ESP32_W6100 v1.5.2 for core v1.0.6-" +#endif + +#define WEBSERVER_ESP32_W6100_VERSION_MAJOR 1 +#define WEBSERVER_ESP32_W6100_VERSION_MINOR 5 +#define WEBSERVER_ESP32_W6100_VERSION_PATCH 2 + +#define WEBSERVER_ESP32_W6100_VERSION_INT 1005002 + +////////////////////////////////////////////////////////////// + +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_ESP32S2_THING_PLUS || ARDUINO_MICROS2 || \ + ARDUINO_METRO_ESP32S2 || ARDUINO_MAGTAG29_ESP32S2 || ARDUINO_FUNHOUSE_ESP32S2 || \ + ARDUINO_ADAFRUIT_FEATHER_ESP32S2_NOPSRAM ) + + #error ESP32_S2 not supported. Use WebServer_ESP32_SC_W6100 library + +#elif ( ARDUINO_ESP32C3_DEV ) + + #error ESP32_C3 not supported. Use WebServer_ESP32_SC_W6100 library + +#elif ( defined(ARDUINO_ESP32S3_DEV) || defined(ARDUINO_ESP32_S3_BOX) || defined(ARDUINO_TINYS3) || \ + defined(ARDUINO_PROS3) || defined(ARDUINO_FEATHERS3) ) + + #error ESP32_S3 not supported. Use WebServer_ESP32_SC_W6100 library + +#elif ESP32 + + #if (_ETHERNET_WEBSERVER_LOGLEVEL_ > 3) + #warning Using ESP32 architecture for WebServer_ESP32_W6100 + #endif + +#define BOARD_NAME ARDUINO_BOARD + +#else + #error This code is designed to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +////////////////////////////////////////////////////////////// + +#include + +#include "WebServer_ESP32_W6100_Debug.h" + +////////////////////////////////////////////////////////////// + +#include "w6100/esp32_w6100.h" + +#include "WebServer_ESP32_W6100.hpp" +#include "WebServer_ESP32_W6100_Impl.h" + +#endif // WEBSERVER_ESP32_W6100_H diff --git a/src/WebServer_ESP32_W6100.hpp b/src/WebServer_ESP32_W6100.hpp new file mode 100644 index 0000000..8e467fc --- /dev/null +++ b/src/WebServer_ESP32_W6100.hpp @@ -0,0 +1,84 @@ +/**************************************************************************************************************************** + WebServer_ESP32_W6100.h + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W5500 v1.5.2 + *****************************************************************************************************************************/ + +#pragma once + +#ifndef WEBSERVER_ESP32_W6100_HPP +#define WEBSERVER_ESP32_W6100_HPP + +////////////////////////////////////////////////////////////// + +//#define CONFIG_ETH_SPI_ETHERNET_W6100 true + +////////////////////////////////////////////////////////////// + +#include +#include // Introduce corresponding libraries + +#include + +////////////////////////////////////////////////////////////// + +#if !defined(ETH_SPI_HOST) + #define ETH_SPI_HOST SPI3_HOST +#endif + +#if !defined(SPI_CLOCK_MHZ) + // Using 25MHz for W6100, 14MHz for W5100 + #define SPI_CLOCK_MHZ 25 +#endif + +#if !defined(INT_GPIO) + #define INT_GPIO 4 +#endif + +#if !defined(MISO_GPIO) + #define MISO_GPIO 19 +#endif + +#if !defined(MOSI_GPIO) + #define MOSI_GPIO 23 +#endif + +#if !defined(SCK_GPIO) + #define SCK_GPIO 18 +#endif + +#if !defined(CS_GPIO) + #define CS_GPIO 5 +#endif + +////////////////////////////////////////////////////////////// + +#ifndef SHIELD_TYPE + #define SHIELD_TYPE "ESP32_W6100" +#endif + +extern bool ESP32_W6100_eth_connected; + +extern void ESP32_W6100_onEvent(); + +extern void ESP32_W6100_waitForConnect(); + +extern bool ESP32_W6100_isConnected(); + +extern void ESP32_W6100_event(WiFiEvent_t event); + +////////////////////////////////////////////////////////////// + +#endif // WEBSERVER_ESP32_W6100_HPP diff --git a/src/WebServer_ESP32_W6100_Debug.h b/src/WebServer_ESP32_W6100_Debug.h new file mode 100644 index 0000000..fd4c922 --- /dev/null +++ b/src/WebServer_ESP32_W6100_Debug.h @@ -0,0 +1,101 @@ +/**************************************************************************************************************************** + WebServer_ESP32_W6100_Debug.h + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP8266 https://github.com/esp8266/Arduino/releases + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W5500 v1.5.2 + *****************************************************************************************************************************/ + +#pragma once + +#ifndef WEBSERVER_ESP32_W6100_DEBUG_H +#define WEBSERVER_ESP32_W6100_DEBUG_H + +#include +#include + +#ifdef DEBUG_ETHERNET_WEBSERVER_PORT + #define ET_DEBUG_OUTPUT DEBUG_ETHERNET_WEBSERVER_PORT +#else + #define ET_DEBUG_OUTPUT Serial +#endif + +// Change _ETHERNET_WEBSERVER_LOGLEVEL_ to set tracing and logging verbosity +// 0: DISABLED: no logging +// 1: ERROR: errors +// 2: WARN: errors and warnings +// 3: INFO: errors, warnings and informational (default) +// 4: DEBUG: errors, warnings, informational and debug + +#ifndef _ETHERNET_WEBSERVER_LOGLEVEL_ + #define _ETHERNET_WEBSERVER_LOGLEVEL_ 0 +#endif + +const char EWS_MARK[] = "[EWS] "; +const char EWS_SPACE[] = " "; +const char EWS_LINE[] = "========================================\n"; + +#define EWS_PRINT_MARK EWS_PRINT(EWS_MARK) +#define EWS_PRINT_SP EWS_PRINT(EWS_SPACE) +#define EWS_PRINT_LINE EWS_PRINT(EWS_LINE) + +#define EWS_PRINT ET_DEBUG_OUTPUT.print +#define EWS_PRINTLN ET_DEBUG_OUTPUT.println + +/////////////////////////////////////// + +#define ET_LOG(x) { EWS_PRINTLN(x); } +#define ET_LOG0(x) { EWS_PRINT(x); } +#define ET_LOG1(x,y) { EWS_PRINT(x); EWS_PRINTLN(y); } +#define ET_LOG2(x,y,z) { EWS_PRINT(x); EWS_PRINT(y); EWS_PRINTLN(z); } +#define ET_LOG3(x,y,z,w) { EWS_PRINT(x); EWS_PRINT(y); EWS_PRINT(z); EWS_PRINTLN(w); } + +/////////////////////////////////////// + +#define ET_LOGERROR(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>0) { EWS_PRINT_MARK; EWS_PRINTLN(x); } +#define ET_LOGERROR_LINE(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>0) { EWS_PRINT_MARK; EWS_PRINTLN(x); EWS_PRINT_LINE; } +#define ET_LOGERROR0(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>0) { EWS_PRINT(x); } +#define ET_LOGERROR1(x,y) if(_ETHERNET_WEBSERVER_LOGLEVEL_>0) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINTLN(y); } +#define ET_LOGERROR2(x,y,z) if(_ETHERNET_WEBSERVER_LOGLEVEL_>0) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINT(y); EWS_PRINT_SP; EWS_PRINTLN(z); } +#define ET_LOGERROR3(x,y,z,w) if(_ETHERNET_WEBSERVER_LOGLEVEL_>0) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINT(y); EWS_PRINT_SP; EWS_PRINT(z); EWS_PRINT_SP; EWS_PRINTLN(w); } + +/////////////////////////////////////// + +#define ET_LOGWARN(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>1) { EWS_PRINT_MARK; EWS_PRINTLN(x); } +#define ET_LOGWARN_LINE(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>1) { EWS_PRINT_MARK; EWS_PRINTLN(x); EWS_PRINT_LINE; } +#define ET_LOGWARN0(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>1) { EWS_PRINT(x); } +#define ET_LOGWARN1(x,y) if(_ETHERNET_WEBSERVER_LOGLEVEL_>1) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINTLN(y); } +#define ET_LOGWARN2(x,y,z) if(_ETHERNET_WEBSERVER_LOGLEVEL_>1) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINT(y); EWS_PRINT_SP; EWS_PRINTLN(z); } +#define ET_LOGWARN3(x,y,z,w) if(_ETHERNET_WEBSERVER_LOGLEVEL_>1) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINT(y); EWS_PRINT_SP; EWS_PRINT(z); EWS_PRINT_SP; EWS_PRINTLN(w); } + +/////////////////////////////////////// + +#define ET_LOGINFO(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>2) { EWS_PRINT_MARK; EWS_PRINTLN(x); } +#define ET_LOGINFO_LINE(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>2) { EWS_PRINT_MARK; EWS_PRINTLN(x); EWS_PRINT_LINE; } +#define ET_LOGINFO0(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>2) { EWS_PRINT(x); } +#define ET_LOGINFO1(x,y) if(_ETHERNET_WEBSERVER_LOGLEVEL_>2) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINTLN(y); } +#define ET_LOGINFO2(x,y,z) if(_ETHERNET_WEBSERVER_LOGLEVEL_>2) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINT(y); EWS_PRINT_SP; EWS_PRINTLN(z); } +#define ET_LOGINFO3(x,y,z,w) if(_ETHERNET_WEBSERVER_LOGLEVEL_>2) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINT(y); EWS_PRINT_SP; EWS_PRINT(z); EWS_PRINT_SP; EWS_PRINTLN(w); } + +/////////////////////////////////////// + +#define ET_LOGDEBUG(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>3) { EWS_PRINT_MARK; EWS_PRINTLN(x); } +#define ET_LOGDEBUG_LINE(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>3) { EWS_PRINT_MARK; EWS_PRINTLN(x); EWS_PRINT_LINE; } +#define ET_LOGDEBUG0(x) if(_ETHERNET_WEBSERVER_LOGLEVEL_>3) { EWS_PRINT(x); } +#define ET_LOGDEBUG1(x,y) if(_ETHERNET_WEBSERVER_LOGLEVEL_>3) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINTLN(y); } +#define ET_LOGDEBUG2(x,y,z) if(_ETHERNET_WEBSERVER_LOGLEVEL_>3) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINT(y); EWS_PRINT_SP; EWS_PRINTLN(z); } +#define ET_LOGDEBUG3(x,y,z,w) if(_ETHERNET_WEBSERVER_LOGLEVEL_>3) { EWS_PRINT_MARK; EWS_PRINT(x); EWS_PRINT_SP; EWS_PRINT(y); EWS_PRINT_SP; EWS_PRINT(z); EWS_PRINT_SP; EWS_PRINTLN(w); } + +#endif // WEBSERVER_ESP32_W6100_DEBUG_H + + diff --git a/src/WebServer_ESP32_W6100_Impl.h b/src/WebServer_ESP32_W6100_Impl.h new file mode 100644 index 0000000..b7624de --- /dev/null +++ b/src/WebServer_ESP32_W6100_Impl.h @@ -0,0 +1,157 @@ +/**************************************************************************************************************************** + WebServer_ESP32_W6100_Impl.h + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W5500 v1.5.2 + *****************************************************************************************************************************/ + +#pragma once + +#ifndef WEBSERVER_ESP32_W6100_IMPL_H +#define WEBSERVER_ESP32_W6100_IMPL_H + +////////////////////////////////////////////////////////////// + +bool ESP32_W6100_eth_connected = false; + +////////////////////////////////////////////////////////////// + +void ESP32_W6100_onEvent() +{ + WiFi.onEvent(ESP32_W6100_event); +} + +////////////////////////////////////////////////////////////// + +void ESP32_W6100_waitForConnect() +{ + while (!ESP32_W6100_eth_connected) + delay(100); +} + +////////////////////////////////////////////////////////////// + +bool ESP32_W6100_isConnected() +{ + return ESP32_W6100_eth_connected; +} + +////////////////////////////////////////////////////////////// + +void ESP32_W6100_event(WiFiEvent_t event) +{ + switch (event) + { + //#if USING_CORE_ESP32_CORE_V200_PLUS +#if ( ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) && ( ARDUINO_ESP32_GIT_VER != 0x46d5afb1 ) ) + // For breaking core v2.0.0 + // Why so strange to define a breaking enum arduino_event_id_t in WiFiGeneric.h + // compared to the old system_event_id_t, now in tools/sdk/esp32/include/esp_event/include/esp_event_legacy.h + // You can preserve the old enum order and just adding new items to do no harm + case ARDUINO_EVENT_ETH_START: + ET_LOG(F("\nETH Started")); + //set eth hostname here + ETH.setHostname("ESP32_W6100"); + break; + + case ARDUINO_EVENT_ETH_CONNECTED: + ET_LOG(F("ETH Connected")); + break; + + case ARDUINO_EVENT_ETH_GOT_IP: + if (!ESP32_W6100_eth_connected) + { + ET_LOG3(F("ETH MAC: "), ETH.macAddress(), F(", IPv4: "), ETH.localIP()); + + if (ETH.fullDuplex()) + { + ET_LOG0(F("FULL_DUPLEX, ")); + } + else + { + ET_LOG0(F("HALF_DUPLEX, ")); + } + + ET_LOG1(ETH.linkSpeed(), F("Mbps")); + + ESP32_W6100_eth_connected = true; + } + + break; + + case ARDUINO_EVENT_ETH_DISCONNECTED: + ET_LOG("ETH Disconnected"); + ESP32_W6100_eth_connected = false; + break; + + case ARDUINO_EVENT_ETH_STOP: + ET_LOG("\nETH Stopped"); + ESP32_W6100_eth_connected = false; + break; + +#else + + // For old core v1.0.6- + // Core v2.0.0 defines a stupid enum arduino_event_id_t, breaking any code for ESP32_W6100 written for previous core + // Why so strange to define a breaking enum arduino_event_id_t in WiFiGeneric.h + // compared to the old system_event_id_t, now in tools/sdk/esp32/include/esp_event/include/esp_event_legacy.h + // You can preserve the old enum order and just adding new items to do no harm + case SYSTEM_EVENT_ETH_START: + ET_LOG(F("\nETH Started")); + //set eth hostname here + ETH.setHostname("ESP32_W6100"); + break; + + case SYSTEM_EVENT_ETH_CONNECTED: + ET_LOG(F("ETH Connected")); + break; + + case SYSTEM_EVENT_ETH_GOT_IP: + if (!ESP32_W6100_eth_connected) + { + ET_LOG3(F("ETH MAC: "), ETH.macAddress(), F(", IPv4: "), ETH.localIP()); + + if (ETH.fullDuplex()) + { + ET_LOG0(F("FULL_DUPLEX, ")); + } + else + { + ET_LOG0(F("HALF_DUPLEX, ")); + } + + ET_LOG1(ETH.linkSpeed(), F("Mbps")); + + ESP32_W6100_eth_connected = true; + } + + break; + + case SYSTEM_EVENT_ETH_DISCONNECTED: + ET_LOG("ETH Disconnected"); + ESP32_W6100_eth_connected = false; + break; + + case SYSTEM_EVENT_ETH_STOP: + ET_LOG("\nETH Stopped"); + ESP32_W6100_eth_connected = false; + break; +#endif + + default: + break; + } +} + +#endif // WEBSERVER_ESP32_W6100_IMPL_H diff --git a/src/w6100/esp32_w6100.cpp b/src/w6100/esp32_w6100.cpp new file mode 100644 index 0000000..d7d6fba --- /dev/null +++ b/src/w6100/esp32_w6100.cpp @@ -0,0 +1,440 @@ +/**************************************************************************************************************************** + esp32_w6100.cpp + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W6100 v1.5.2 + *****************************************************************************************************************************/ + +#define _ETHERNET_WEBSERVER_LOGLEVEL_ 1 + +#include "WebServer_ESP32_W6100.hpp" +#include "WebServer_ESP32_W6100_Debug.h" + +#include "esp32_w6100.h" + +//////////////////////////////////////// + +extern "C" +{ + esp_eth_mac_t* w6100_begin(int MISO, int MOSI, int SCLK, int CS, int INT, int SPICLOCK_MHZ, + int SPIHOST); +#include "esp_eth/esp_eth_w6100.h" +} + +//////////////////////////////////////// + +#include "esp_event.h" +#include "esp_eth_phy.h" +#include "esp_eth_mac.h" +#include "esp_eth_com.h" + +#if CONFIG_IDF_TARGET_ESP32 + #include "soc/emac_ext_struct.h" + #include "soc/rtc.h" +#endif + +#include "lwip/err.h" +#include "lwip/dns.h" + +extern void tcpipInit(); + +//////////////////////////////////////// + +ESP32_W6100::ESP32_W6100() + : initialized(false) + , staticIP(false) + , eth_handle(NULL) + , started(false) + , eth_link(ETH_LINK_DOWN) +{ +} + +//////////////////////////////////////// + +ESP32_W6100::~ESP32_W6100() +{} + +//////////////////////////////////////// + +bool ESP32_W6100::begin(int MISO, int MOSI, int SCLK, int CS, int INT, int SPICLOCK_MHZ, int SPIHOST, + uint8_t *W6100_Mac) +{ + tcpipInit(); + + if ( esp_read_mac(mac_eth, ESP_MAC_ETH) == ESP_OK ) + { + char macStr[18] = { 0 }; + + sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac_eth[0], mac_eth[1], mac_eth[2], + mac_eth[3], mac_eth[4], mac_eth[5]); + + ET_LOGINFO1("Using built-in mac_eth =", macStr); + + esp_base_mac_addr_set( mac_eth ); + } + else + { + ET_LOGINFO("Using user mac_eth"); + + memcpy(mac_eth, W6100_Mac, sizeof(mac_eth)); + + esp_base_mac_addr_set( W6100_Mac ); + } + + tcpip_adapter_set_default_eth_handlers(); + + esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH(); + esp_netif_t *eth_netif = esp_netif_new(&cfg); + + esp_eth_mac_t *eth_mac = w6100_begin(MISO, MOSI, SCLK, CS, INT, SPICLOCK_MHZ, SPIHOST); + + if (eth_mac == NULL) + { + ET_LOGERROR("esp_eth_mac_new_esp32 failed"); + + return false; + } + + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + phy_config.autonego_timeout_ms = 0; // W6100 doesn't support auto-negotiation + phy_config.reset_gpio_num = -1; // W6100 doesn't have a pin to reset internal PHY + esp_eth_phy_t *eth_phy = esp_eth_phy_new_w6100(&phy_config); + + if (eth_phy == NULL) + { + ET_LOGERROR("esp_eth_phy_new failed"); + + return false; + } + + eth_handle = NULL; + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(eth_mac, eth_phy); + + if (esp_eth_driver_install(ð_config, ð_handle) != ESP_OK || eth_handle == NULL) + { + ET_LOG("esp_eth_driver_install failed"); + + return false; + } + + eth_mac->set_addr(eth_mac, mac_eth); + + if ( (SPICLOCK_MHZ < 14) || (SPICLOCK_MHZ > 25) ) + { + ET_LOGERROR("SPI Clock must be >= 8 and <= 25 MHz for W6100"); + ESP_ERROR_CHECK(ESP_FAIL); + } + + /* attach Ethernet driver to TCP/IP stack */ + if (esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)) != ESP_OK) + { + ET_LOGERROR("esp_netif_attach failed"); + + return false; + } + + if (esp_eth_start(eth_handle) != ESP_OK) + { + ET_LOG("esp_eth_start failed"); + + return false; + } + + // holds a few microseconds to let DHCP start and enter into a good state + // FIX ME -- addresses issue https://github.com/espressif/arduino-esp32/issues/5733 + delay(50); + + return true; +} + +//////////////////////////////////////// + +bool ESP32_W6100::config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1, IPAddress dns2) +{ + esp_err_t err = ESP_OK; + tcpip_adapter_ip_info_t info; + + if (static_cast(local_ip) != 0) + { + info.ip.addr = static_cast(local_ip); + info.gw.addr = static_cast(gateway); + info.netmask.addr = static_cast(subnet); + } + else + { + info.ip.addr = 0; + info.gw.addr = 0; + info.netmask.addr = 0; + } + + err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_ETH); + + if (err != ESP_OK && err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) + { + ET_LOGERROR1("DHCP could not be stopped! Error =", err); + return false; + } + + err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_ETH, &info); + + if (err != ERR_OK) + { + ET_LOGERROR1("STA IP could not be configured! Error = ", err); + return false; + } + + if (info.ip.addr) + { + staticIP = true; + } + else + { + err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_ETH); + + if (err != ESP_OK && err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STARTED) + { + ET_LOGWARN1("DHCP could not be started! Error =", err); + return false; + } + + staticIP = false; + } + + ip_addr_t d; + d.type = IPADDR_TYPE_V4; + + if (static_cast(dns1) != 0) + { + // Set DNS1-Server + d.u_addr.ip4.addr = static_cast(dns1); + dns_setserver(0, &d); + } + + if (static_cast(dns2) != 0) + { + // Set DNS2-Server + d.u_addr.ip4.addr = static_cast(dns2); + dns_setserver(1, &d); + } + + return true; +} + +//////////////////////////////////////// + +IPAddress ESP32_W6100::localIP() +{ + tcpip_adapter_ip_info_t ip; + + if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) + { + ET_LOGDEBUG("localIP NULL"); + + return IPAddress(); + } + + ET_LOGDEBUG1("localIP =", IPAddress(ip.ip.addr)); + + return IPAddress(ip.ip.addr); +} + +//////////////////////////////////////// + +IPAddress ESP32_W6100::subnetMask() +{ + tcpip_adapter_ip_info_t ip; + + if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) + { + return IPAddress(); + } + + return IPAddress(ip.netmask.addr); +} + +//////////////////////////////////////// + +IPAddress ESP32_W6100::gatewayIP() +{ + tcpip_adapter_ip_info_t ip; + + if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) + { + return IPAddress(); + } + + return IPAddress(ip.gw.addr); +} + +//////////////////////////////////////// + +IPAddress ESP32_W6100::dnsIP(uint8_t dns_no) +{ + const ip_addr_t * dns_ip = dns_getserver(dns_no); + + return IPAddress(dns_ip->u_addr.ip4.addr); +} + +//////////////////////////////////////// + +IPAddress ESP32_W6100::broadcastIP() +{ + tcpip_adapter_ip_info_t ip; + + if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) + { + return IPAddress(); + } + + return WiFiGenericClass::calculateBroadcast(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr)); +} + +//////////////////////////////////////// + +IPAddress ESP32_W6100::networkID() +{ + tcpip_adapter_ip_info_t ip; + + if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) + { + return IPAddress(); + } + + return WiFiGenericClass::calculateNetworkID(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr)); +} + +//////////////////////////////////////// + +uint8_t ESP32_W6100::subnetCIDR() +{ + tcpip_adapter_ip_info_t ip; + + if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) + { + return (uint8_t)0; + } + + return WiFiGenericClass::calculateSubnetCIDR(IPAddress(ip.netmask.addr)); +} + +//////////////////////////////////////// + +const char * ESP32_W6100::getHostname() +{ + const char * hostname; + + if (tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_ETH, &hostname)) + { + return NULL; + } + + return hostname; +} + +//////////////////////////////////////// + +bool ESP32_W6100::setHostname(const char * hostname) +{ + return tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, hostname) == 0; +} + +//////////////////////////////////////// + +bool ESP32_W6100::fullDuplex() +{ +#ifdef ESP_IDF_VERSION_MAJOR + return true;//todo: do not see an API for this +#else + return eth_config.phy_get_duplex_mode(); +#endif +} + +//////////////////////////////////////// + +bool ESP32_W6100::linkUp() +{ +#ifdef ESP_IDF_VERSION_MAJOR + return eth_link == ETH_LINK_UP; +#else + return eth_config.phy_check_link(); +#endif +} + +//////////////////////////////////////// + +uint8_t ESP32_W6100::linkSpeed() +{ +#ifdef ESP_IDF_VERSION_MAJOR + eth_speed_t link_speed; + esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &link_speed); + return (link_speed == ETH_SPEED_10M) ? 10 : 100; +#else + return eth_config.phy_get_speed_mode() ? 100 : 10; +#endif +} + +//////////////////////////////////////// + +bool ESP32_W6100::enableIpV6() +{ + return tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_ETH) == 0; +} + +//////////////////////////////////////// + +IPv6Address ESP32_W6100::localIPv6() +{ + static ip6_addr_t addr; + + if (tcpip_adapter_get_ip6_linklocal(TCPIP_ADAPTER_IF_ETH, &addr)) + { + return IPv6Address(); + } + + return IPv6Address(addr.addr); +} + +//////////////////////////////////////// + +uint8_t * ESP32_W6100::macAddress(uint8_t* mac) +{ + if (!mac) + { + return NULL; + } + +#ifdef ESP_IDF_VERSION_MAJOR + esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac); +#else + esp_eth_get_mac(mac); +#endif + + return mac; +} + +//////////////////////////////////////// + +String ESP32_W6100::macAddress() +{ + uint8_t mac[6] = {0, 0, 0, 0, 0, 0}; + char macStr[18] = { 0 }; + macAddress(mac); + sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + return String(macStr); +} + +//////////////////////////////////////// + +ESP32_W6100 ETH; diff --git a/src/w6100/esp32_w6100.h b/src/w6100/esp32_w6100.h new file mode 100644 index 0000000..d1e0d9b --- /dev/null +++ b/src/w6100/esp32_w6100.h @@ -0,0 +1,106 @@ +/**************************************************************************************************************************** + esp32_w6100.h + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W6100 v1.5.2 + *****************************************************************************************************************************/ + +#pragma once + +#ifndef _ESP32_W6100_H_ +#define _ESP32_W6100_H_ + +//////////////////////////////////////// + +#include "WiFi.h" +#include "esp_system.h" +#include "esp_eth.h" + +#include + +//////////////////////////////////////// + +#if ESP_IDF_VERSION_MAJOR < 4 || ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,4,0) + #error "This version of Arduino is too old" +#endif + +//////////////////////////////////////// + +static uint8_t W6100_Default_Mac[] = { 0xFE, 0xED, 0xDE, 0xAD, 0xBE, 0xEF }; + +//////////////////////////////////////// + +class ESP32_W6100 +{ + private: + bool initialized; + bool staticIP; + + uint8_t mac_eth[6] = { 0xFE, 0xED, 0xDE, 0xAD, 0xBE, 0xEF }; + +#if ESP_IDF_VERSION_MAJOR > 3 + esp_eth_handle_t eth_handle; + + protected: + bool started; + eth_link_t eth_link; + static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); +#else + bool started; + eth_config_t eth_config; +#endif + + public: + ESP32_W6100(); + ~ESP32_W6100(); + + bool begin(int MISO, int MOSI, int SCLK, int CS, int INT, int SPICLOCK_MHZ = 25, int SPIHOST = SPI3_HOST, + uint8_t *W6100_Mac = W6100_Default_Mac); + + bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = (uint32_t)0x00000000, + IPAddress dns2 = (uint32_t)0x00000000); + + const char * getHostname(); + bool setHostname(const char * hostname); + + bool fullDuplex(); + bool linkUp(); + uint8_t linkSpeed(); + + bool enableIpV6(); + IPv6Address localIPv6(); + + IPAddress localIP(); + IPAddress subnetMask(); + IPAddress gatewayIP(); + IPAddress dnsIP(uint8_t dns_no = 0); + + IPAddress broadcastIP(); + IPAddress networkID(); + uint8_t subnetCIDR(); + + uint8_t * macAddress(uint8_t* mac); + String macAddress(); + + friend class WiFiClient; + friend class WiFiServer; +}; + +//////////////////////////////////////// + +extern ESP32_W6100 ETH; + +//////////////////////////////////////// + +#endif /* _ESP32_W6100_H_ */ diff --git a/src/w6100/esp_eth/esp_eth_mac_w6100.c b/src/w6100/esp_eth/esp_eth_mac_w6100.c new file mode 100644 index 0000000..0ed7777 --- /dev/null +++ b/src/w6100/esp_eth/esp_eth_mac_w6100.c @@ -0,0 +1,1012 @@ +/**************************************************************************************************************************** + esp_eth_mac_w6100.c + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W6100 v1.5.2 + *****************************************************************************************************************************/ + +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//////////////////////////////////////// + +#include +#include +#include +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_eth.h" +#include "esp_system.h" +#include "esp_intr_alloc.h" +#include "esp_heap_caps.h" +#include "esp_rom_gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "hal/cpu_hal.h" +#include "w6100.h" +#include "sdkconfig.h" + +// KH for W6100 +#define eth_w6100_config_t eth_w5500_config_t +////// + +//////////////////////////////////////// + +static const char *TAG = "w6100.mac"; + +#define W6100_SPI_LOCK_TIMEOUT_MS (50) +#define W6100_TX_MEM_SIZE (0x4000) +#define W6100_RX_MEM_SIZE (0x4000) + +//////////////////////////////////////// + +typedef struct +{ + esp_eth_mac_t parent; + esp_eth_mediator_t *eth; + spi_device_handle_t spi_hdl; + SemaphoreHandle_t spi_lock; + TaskHandle_t rx_task_hdl; + uint32_t sw_reset_timeout_ms; + int int_gpio_num; + uint8_t addr[6]; + bool packets_remain; +} emac_w6100_t; + +//////////////////////////////////////// + +static inline bool w6100_lock(emac_w6100_t *emac) +{ + return xSemaphoreTake(emac->spi_lock, pdMS_TO_TICKS(W6100_SPI_LOCK_TIMEOUT_MS)) == pdTRUE; +} + +//////////////////////////////////////// + +static inline bool w6100_unlock(emac_w6100_t *emac) +{ + return xSemaphoreGive(emac->spi_lock) == pdTRUE; +} + +//////////////////////////////////////// + +static esp_err_t w6100_write(emac_w6100_t *emac, uint32_t address, const void *value, uint32_t len) +{ + esp_err_t ret = ESP_OK; + + spi_transaction_t trans = + { + .cmd = (address >> W6100_ADDR_OFFSET), + .addr = ((address & 0xFFFF) | (W6100_ACCESS_MODE_WRITE << W6100_RWB_OFFSET) | W6100_SPI_OP_MODE_VDM), + .length = 8 * len, + .tx_buffer = value + }; + + if (w6100_lock(emac)) + { + if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) + { + ESP_LOGE(TAG, "%s(%d): SPI transmit failed", __FUNCTION__, __LINE__); + ret = ESP_FAIL; + } + + w6100_unlock(emac); + } + else + { + ret = ESP_ERR_TIMEOUT; + } + + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_read(emac_w6100_t *emac, uint32_t address, void *value, uint32_t len) +{ + esp_err_t ret = ESP_OK; + + spi_transaction_t trans = + { + // use direct reads for registers to prevent overwrites by 4-byte boundary writes + .flags = len <= 4 ? SPI_TRANS_USE_RXDATA : 0, + .cmd = (address >> W6100_ADDR_OFFSET), + .addr = ((address & 0xFFFF) | (W6100_ACCESS_MODE_READ << W6100_RWB_OFFSET) | W6100_SPI_OP_MODE_VDM), + .length = 8 * len, + .rx_buffer = value + }; + + if (w6100_lock(emac)) + { + if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) + { + ESP_LOGE(TAG, "%s(%d): SPI transmit failed", __FUNCTION__, __LINE__); + ret = ESP_FAIL; + } + + w6100_unlock(emac); + } + else + { + ret = ESP_ERR_TIMEOUT; + } + + if ((trans.flags & SPI_TRANS_USE_RXDATA) && len <= 4) + { + memcpy(value, trans.rx_data, len); // copy register values to output + } + + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_send_command(emac_w6100_t *emac, uint8_t command, uint32_t timeout_ms) +{ + esp_err_t ret = ESP_OK; + + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_CR(0), &command, sizeof(command)), err, TAG, "Write SCR failed"); + + // after W6100 accepts the command, the command register will be cleared automatically + uint32_t to = 0; + + for (to = 0; to < timeout_ms / 10; to++) + { + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SOCK_CR(0), &command, sizeof(command)), err, TAG, "Read SCR failed"); + + if (!command) + { + break; + } + + vTaskDelay(pdMS_TO_TICKS(10)); + } + + ESP_GOTO_ON_FALSE(to < timeout_ms / 10, ESP_ERR_TIMEOUT, err, TAG, "Send command timeout"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_get_tx_free_size(emac_w6100_t *emac, uint16_t *size) +{ + esp_err_t ret = ESP_OK; + uint16_t free0, free1 = 0; + + // read TX_FSR register more than once, until we get the same value + // this is a trick because we might be interrupted between reading the high/low part of the TX_FSR register (16 bits in length) + + do + { + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SOCK_TX_FSR(0), &free0, sizeof(free0)), err, TAG, "Read TX FSR failed"); + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SOCK_TX_FSR(0), &free1, sizeof(free1)), err, TAG, "Read TX FSR failed"); + } while (free0 != free1); + + *size = __builtin_bswap16(free0); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_get_rx_received_size(emac_w6100_t *emac, uint16_t *size) +{ + esp_err_t ret = ESP_OK; + uint16_t received0, received1 = 0; + + do + { + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SOCK_RX_RSR(0), &received0, sizeof(received0)), err, TAG, + "Read RX RSR failed"); + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SOCK_RX_RSR(0), &received1, sizeof(received1)), err, TAG, + "Read RX RSR failed"); + } while (received0 != received1); + + *size = __builtin_bswap16(received0); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_write_buffer(emac_w6100_t *emac, const void *buffer, uint32_t len, uint16_t offset) +{ + esp_err_t ret = ESP_OK; + uint32_t remain = len; + const uint8_t *buf = buffer; + offset %= W6100_TX_MEM_SIZE; + + if (offset + len > W6100_TX_MEM_SIZE) + { + remain = (offset + len) % W6100_TX_MEM_SIZE; + len = W6100_TX_MEM_SIZE - offset; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_MEM_SOCK_TX(0, offset), buf, len), err, TAG, "Write TX buffer failed"); + offset += len; + buf += len; + } + + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_MEM_SOCK_TX(0, offset), buf, remain), err, TAG, "Write TX buffer failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_read_buffer(emac_w6100_t *emac, void *buffer, uint32_t len, uint16_t offset) +{ + esp_err_t ret = ESP_OK; + uint32_t remain = len; + uint8_t *buf = buffer; + offset %= W6100_RX_MEM_SIZE; + + if (offset + len > W6100_RX_MEM_SIZE) + { + remain = (offset + len) % W6100_RX_MEM_SIZE; + len = W6100_RX_MEM_SIZE - offset; + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_MEM_SOCK_RX(0, offset), buf, len), err, TAG, "Read RX buffer failed"); + offset += len; + buf += len; + } + + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_MEM_SOCK_RX(0, offset), buf, remain), err, TAG, "Read RX buffer failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_set_mac_addr(emac_w6100_t *emac) +{ + esp_err_t ret = ESP_OK; + + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_MAC, emac->addr, 6), err, TAG, "Write MAC address register failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +// KH +static esp_err_t w6100_reset(emac_w6100_t *emac) +{ + esp_err_t ret = ESP_OK; + + // Unlock SYSR[CHPL] + uint8_t mr = W6100_CHPLCKR_UNLOCK; + + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_CHPLCKR_W6100, &mr, sizeof(mr)), err, TAG, "Write CHPLCKR_W6100 failed"); + + uint32_t to = 0; + + for (to = 0; to < emac->sw_reset_timeout_ms / 10; to++) + { + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SYSR_W6100, &mr, sizeof(mr)), err, TAG, "Read SYSR_W6100 failed"); + + // Check CHPL = 0 + if (! ( (mr & W6100_SYSR_CHPL_LOCK) ^ W6100_SYSR_CHPL_ULOCK) ) + { + break; + } + + vTaskDelay(pdMS_TO_TICKS(10)); + } + + ESP_GOTO_ON_FALSE(to < emac->sw_reset_timeout_ms / 10, ESP_ERR_TIMEOUT, err, TAG, "Unlock timeout"); + + /* software reset */ + mr = 0; + + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SYCR0, &mr, sizeof(mr)), err, TAG, "Write SYCR0 failed"); + + for (to = 0; to < emac->sw_reset_timeout_ms / 10; to++) + { + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SYSR_W6100, &mr, sizeof(mr)), err, TAG, "Read SYSR_W6100 failed"); + + // Check CHPL = 1 + // Wait Lock Complete + if (! ( (mr & W6100_SYSR_CHPL_LOCK) ^ W6100_SYSR_CHPL_LOCK) ) + { + break; + } + + vTaskDelay(pdMS_TO_TICKS(10)); + } + + ESP_GOTO_ON_FALSE(to < emac->sw_reset_timeout_ms / 10, ESP_ERR_TIMEOUT, err, TAG, "Reset timeout"); + + // Unlock + mr = W6100_CHPLCKR_UNLOCK; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_CHPLCKR_W6100, &mr, sizeof(mr)), err, TAG, "Write CHPLCKR_W6100 failed"); + + mr = W6100_NETLCKR_UNLOCK; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_NETLCKR_W6100, &mr, sizeof(mr)), err, TAG, "Write NETLCKR_W6100 failed"); + + mr = W6100_PHYLCKR_UNLOCK; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_PHYLCKR_W6100, &mr, sizeof(mr)), err, TAG, "Write PHYLCKR_W6100 failed"); + + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SYSR_W6100, &mr, sizeof(mr)), err, TAG, "Read SYSR_W6100 failed"); + + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_VERSIONR_W6100, &mr, sizeof(mr)), err, TAG, "Read VERSIONR_W6100 failed"); + ESP_LOGI(TAG, "version=0x%x", mr); + + ESP_GOTO_ON_FALSE(mr == 0x61, ESP_ERR_TIMEOUT, err, TAG, "Wrong Version"); + + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_CVERSIONR_W6100 + 1, &mr, sizeof(mr)), err, TAG, + "Read CVERSIONR_W6100 failed"); + ESP_LOGI(TAG, "cversion=0x%x", mr); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_verify_id(emac_w6100_t *emac) +{ + esp_err_t ret = ESP_OK; + uint8_t version = 0; + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_VERSIONR_W6100, &version, sizeof(version)), err, TAG, + "Read W6100_REG_VERSIONR_W6100 failed"); + + // W6100 doesn't have chip ID, we just print the version number instead + ESP_LOGI(TAG, "version=%x", version); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_setup_default(emac_w6100_t *emac) +{ + esp_err_t ret = ESP_OK; + uint8_t reg_value = 16; + + // Only SOCK0 can be used as MAC RAW mode, so we give the whole buffer (16KB TX and 16KB RX) to SOCK0 + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_RXBUF_SIZE(0), ®_value, sizeof(reg_value)), err, TAG, + "Set rx buffer size failed"); + + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_TXBUF_SIZE(0), ®_value, sizeof(reg_value)), err, TAG, + "Set tx buffer size failed"); + + reg_value = 0; + + for (int i = 1; i < 8; i++) + { + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_RXBUF_SIZE(i), ®_value, sizeof(reg_value)), err, TAG, + "Set SOCK_RXBUF_SIZE failed"); + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_TXBUF_SIZE(i), ®_value, sizeof(reg_value)), err, TAG, + "Set SOCK_TXBUF_SIZE failed"); + } + + /* Enable ping block, disable PPPoE, WOL */ + reg_value = W6100_MR_PB; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_MR, ®_value, sizeof(reg_value)), err, TAG, "Write MR failed"); + + /* Disable interrupt for all sockets by default */ + reg_value = 0; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SIMR, ®_value, sizeof(reg_value)), err, TAG, "Write SIMR failed"); + + /* Enable MAC RAW mode for SOCK0, enable MAC filter, no blocking broadcast and multicast */ + reg_value = W6100_SMR_MAC_RAW | W6100_SMR_MAC_FILTER; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_MR(0), ®_value, sizeof(reg_value)), err, TAG, + "Write SOCK0 MR failed"); + + /* Enable receive event for SOCK0 */ + reg_value = W6100_SIR_RECV; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_IMR(0), ®_value, sizeof(reg_value)), err, TAG, + "Write SOCK0 IMR failed"); + + /* Set the interrupt re-assert level to maximum (~1.5ms) to lower the chances of missing it */ + //uint16_t int_level = __builtin_bswap16(0xFFFF); + //ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_INTLEVEL, &int_level, sizeof(int_level)), err, TAG, + // "Write INTLEVEL failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_start(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + + uint8_t reg_value = 0; + /* open SOCK0 */ + ESP_GOTO_ON_ERROR(w6100_send_command(emac, W6100_SCR_OPEN, 100), err, TAG, "Issue OPEN command failed"); + + /* enable interrupt for SOCK0 */ + reg_value = W6100_SIMR_SOCK0; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SIMR, ®_value, sizeof(reg_value)), err, TAG, "Write SIMR failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_stop(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + uint8_t reg_value = 0; + /* disable interrupt */ + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SIMR, ®_value, sizeof(reg_value)), err, TAG, "Write SIMR failed"); + /* close SOCK0 */ + ESP_GOTO_ON_ERROR(w6100_send_command(emac, W6100_SCR_CLOSE, 100), err, TAG, "Issue SCR_CLOSE command failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +IRAM_ATTR static void w6100_isr_handler(void *arg) +{ + emac_w6100_t *emac = (emac_w6100_t *)arg; + BaseType_t high_task_wakeup = pdFALSE; + + /* notify w6100 task */ + vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup); + + if (high_task_wakeup != pdFALSE) + { + portYIELD_FROM_ISR(); + } +} + +//////////////////////////////////////// + +static void emac_w6100_task(void *arg) +{ + emac_w6100_t *emac = (emac_w6100_t *)arg; + uint8_t status = 0; + uint8_t *buffer = NULL; + uint32_t length = 0; + + while (1) + { + // check if the task receives any notification + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 && // if no notification ... + gpio_get_level(emac->int_gpio_num) != 0) + { + // ...and no interrupt asserted + continue; // -> just continue to check again + } + + /* read interrupt status */ + w6100_read(emac, W6100_REG_SOCK_IR(0), &status, sizeof(status)); + + /* packet received */ + if (status & W6100_SIR_RECV) + { + status = W6100_SIR_RECV; + // clear interrupt status + w6100_write(emac, W6100_REG_SOCK_IR(0), &status, sizeof(status)); + + do + { + length = ETH_MAX_PACKET_SIZE; + buffer = heap_caps_malloc(length, MALLOC_CAP_DMA); + + if (!buffer) + { + ESP_LOGE(TAG, "No mem for receive buffer"); + break; + } + else if (emac->parent.receive(&emac->parent, buffer, &length) == ESP_OK) + { + /* pass the buffer to stack (e.g. TCP/IP layer) */ + if (length) + { + emac->eth->stack_input(emac->eth, buffer, length); + } + else + { + free(buffer); + } + } + else + { + free(buffer); + } + } while (emac->packets_remain); + } + } + + vTaskDelete(NULL); +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(eth, ESP_ERR_INVALID_ARG, err, TAG, "Can't set mac's mediator to null"); + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + emac->eth = eth; + return ESP_OK; +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_write_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t reg_value) +{ + esp_err_t ret = ESP_OK; + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + + // PHY register and MAC registers are mixed together in W6100 + // The only PHY register is PHYCFGR + ESP_GOTO_ON_FALSE(phy_reg == W6100_REG_PHYCFGR, ESP_FAIL, err, TAG, "Wrong PHY register"); + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_PHYCFGR, ®_value, sizeof(uint8_t)), err, TAG, + "write PHY register failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_read_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t *reg_value) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(reg_value, ESP_ERR_INVALID_ARG, err, TAG, "Can't set reg_value to null"); + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + + // PHY register and MAC registers are mixed together in W6100 + // The only PHY register is PHYCFGR + ESP_GOTO_ON_FALSE(phy_reg == W6100_REG_PHYCFGR, ESP_FAIL, err, TAG, "Wrong PHY register"); + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_PHYCFGR, reg_value, sizeof(uint8_t)), err, TAG, + "read PHY register failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_set_addr(esp_eth_mac_t *mac, uint8_t *addr) +{ + esp_err_t ret = ESP_OK; + + ESP_GOTO_ON_FALSE(addr, ESP_ERR_INVALID_ARG, err, TAG, "Invalid argument"); + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + memcpy(emac->addr, addr, 6); + ESP_GOTO_ON_ERROR(w6100_set_mac_addr(emac), err, TAG, "Set mac address failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_get_addr(esp_eth_mac_t *mac, uint8_t *addr) +{ + esp_err_t ret = ESP_OK; + + ESP_GOTO_ON_FALSE(addr, ESP_ERR_INVALID_ARG, err, TAG, "Invalid argument"); + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + memcpy(addr, emac->addr, 6); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_set_link(esp_eth_mac_t *mac, eth_link_t link) +{ + esp_err_t ret = ESP_OK; + + switch (link) + { + case ETH_LINK_UP: + ESP_LOGD(TAG, "Link is up"); + ESP_GOTO_ON_ERROR(mac->start(mac), err, TAG, "W6100 start failed"); + break; + + case ETH_LINK_DOWN: + ESP_LOGD(TAG, "link is down"); + ESP_GOTO_ON_ERROR(mac->stop(mac), err, TAG, "W6100 stop failed"); + break; + + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "Unknown link status"); + break; + } + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_set_speed(esp_eth_mac_t *mac, eth_speed_t speed) +{ + esp_err_t ret = ESP_OK; + + switch (speed) + { + case ETH_SPEED_10M: + ESP_LOGD(TAG, "Setting to 10Mbps"); + break; + + case ETH_SPEED_100M: + ESP_LOGD(TAG, "Setting to 100Mbps"); + break; + + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "Unknown speed"); + break; + } + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex) +{ + esp_err_t ret = ESP_OK; + + switch (duplex) + { + case ETH_DUPLEX_HALF: + ESP_LOGD(TAG, "Setting to HALF_DUPLEX"); + break; + + case ETH_DUPLEX_FULL: + ESP_LOGD(TAG, "Setting to FULL_DUPLEX"); + break; + + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "Unknown duplex"); + break; + } + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_set_promiscuous(esp_eth_mac_t *mac, bool enable) +{ + esp_err_t ret = ESP_OK; + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + + uint8_t smr = 0; + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SOCK_MR(0), &smr, sizeof(smr)), err, TAG, "Read SOCK0 MR failed"); + + if (enable) + { + smr &= ~W6100_SMR_MAC_FILTER; + } + else + { + smr |= W6100_SMR_MAC_FILTER; + } + + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_MR(0), &smr, sizeof(smr)), err, TAG, "Write SOCK0 MR failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_enable_flow_ctrl(esp_eth_mac_t *mac, bool enable) +{ + /* w6100 doesn't support flow control function, so accept any value */ + return ESP_ERR_NOT_SUPPORTED; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_set_peer_pause_ability(esp_eth_mac_t *mac, uint32_t ability) +{ + /* w6100 doesn't support PAUSE function, so accept any value */ + return ESP_ERR_NOT_SUPPORTED; +} + +//////////////////////////////////////// + +static inline bool is_w6100_sane_for_rxtx(emac_w6100_t *emac) +{ + uint8_t phycfg; + + /* phy is ok for rx and tx operations if bits RST and LNK are set (no link down, no reset) */ + if (w6100_read(emac, W6100_REG_PHYCFGR, &phycfg, 1) == ESP_OK && (phycfg & 0x8001)) + { + return true; + } + + return false; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length) +{ + esp_err_t ret = ESP_OK; + + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + uint16_t offset = 0; + + // check if there're free memory to store this packet + uint16_t free_size = 0; + ESP_GOTO_ON_ERROR(w6100_get_tx_free_size(emac, &free_size), err, TAG, "Get free size failed"); + + ESP_GOTO_ON_FALSE(length <= free_size, ESP_ERR_NO_MEM, err, TAG, "Free size (%d) < send length (%d)", free_size, + length); + + // get current write pointer + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SOCK_TX_WR(0), &offset, sizeof(offset)), err, TAG, "Read TX WR failed"); + offset = __builtin_bswap16(offset); + + // copy data to tx memory + ESP_GOTO_ON_ERROR(w6100_write_buffer(emac, buf, length, offset), err, TAG, "Write frame failed"); + + // update write pointer + offset += length; + offset = __builtin_bswap16(offset); + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_TX_WR(0), &offset, sizeof(offset)), err, TAG, "Write TX WR failed"); + + // issue SEND command + ESP_GOTO_ON_ERROR(w6100_send_command(emac, W6100_SCR_SEND, 100), err, TAG, "Issue SEND command failed"); + + // pooling the TX done event + int retry = 0; + uint8_t status = 0; + + while (!(status & W6100_SIR_SEND)) + { + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SOCK_IR(0), &status, sizeof(status)), err, TAG, "Read SOCK0 IR failed"); + + if ((retry++ > 3 && !is_w6100_sane_for_rxtx(emac)) || retry > 10) + { + return ESP_FAIL; + } + } + + // clear the event bit + status = W6100_SIR_SEND; + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_IR(0), &status, sizeof(status)), err, TAG, "Write SOCK0 IR failed"); + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) +{ + esp_err_t ret = ESP_OK; + + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + + uint16_t offset = 0; + uint16_t rx_len = 0; + uint16_t remain_bytes = 0; + emac->packets_remain = false; + + w6100_get_rx_received_size(emac, &remain_bytes); + + if (remain_bytes) + { + // get current read pointer + ESP_GOTO_ON_ERROR(w6100_read(emac, W6100_REG_SOCK_RX_RD(0), &offset, sizeof(offset)), err, TAG, "Read RX RD failed"); + + offset = __builtin_bswap16(offset); + + // read head first + ESP_GOTO_ON_ERROR(w6100_read_buffer(emac, &rx_len, sizeof(rx_len), offset), err, TAG, "Read frame header failed"); + + rx_len = __builtin_bswap16(rx_len) - 2; // data size includes 2 bytes of header + offset += 2; + + // read the payload + ESP_GOTO_ON_ERROR(w6100_read_buffer(emac, buf, rx_len, offset), err, TAG, "Read payload failed, len=%d, offset=%d", + rx_len, offset); + + offset += rx_len; + + // update read pointer + offset = __builtin_bswap16(offset); + ESP_GOTO_ON_ERROR(w6100_write(emac, W6100_REG_SOCK_RX_RD(0), &offset, sizeof(offset)), err, TAG, "Write RX RD failed"); + + /* issue RECV command */ + ESP_GOTO_ON_ERROR(w6100_send_command(emac, W6100_SCR_RECV, 100), err, TAG, "Issue RECV command failed"); + + // check if there're more data need to process + remain_bytes -= rx_len + 2; + emac->packets_remain = remain_bytes > 0; + } + + *length = rx_len; + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_init(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + + esp_eth_mediator_t *eth = emac->eth; + esp_rom_gpio_pad_select_gpio(emac->int_gpio_num); + gpio_set_direction(emac->int_gpio_num, GPIO_MODE_INPUT); + gpio_set_pull_mode(emac->int_gpio_num, GPIO_PULLUP_ONLY); + gpio_set_intr_type(emac->int_gpio_num, GPIO_INTR_NEGEDGE); // active low + gpio_intr_enable(emac->int_gpio_num); + gpio_isr_handler_add(emac->int_gpio_num, w6100_isr_handler, emac); + + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_LLINIT, NULL), err, TAG, "Lowlevel init failed"); + + /* reset w6100 */ + ESP_GOTO_ON_ERROR(w6100_reset(emac), err, TAG, "Reset w6100 failed"); + + /* verify chip id */ + ESP_GOTO_ON_ERROR(w6100_verify_id(emac), err, TAG, "Verify chip ID failed"); + + /* default setup of internal registers */ + ESP_GOTO_ON_ERROR(w6100_setup_default(emac), err, TAG, "W6100 default setup failed"); + + return ESP_OK; + +err: + gpio_isr_handler_remove(emac->int_gpio_num); + gpio_reset_pin(emac->int_gpio_num); + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + + return ret; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_deinit(esp_eth_mac_t *mac) +{ + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + + esp_eth_mediator_t *eth = emac->eth; + mac->stop(mac); + gpio_isr_handler_remove(emac->int_gpio_num); + gpio_reset_pin(emac->int_gpio_num); + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + + return ESP_OK; +} + +//////////////////////////////////////// + +static esp_err_t emac_w6100_del(esp_eth_mac_t *mac) +{ + emac_w6100_t *emac = __containerof(mac, emac_w6100_t, parent); + + vTaskDelete(emac->rx_task_hdl); + vSemaphoreDelete(emac->spi_lock); + free(emac); + + return ESP_OK; +} + +//////////////////////////////////////// + +esp_eth_mac_t *esp_eth_mac_new_w6100(const eth_w6100_config_t *w6100_config, const eth_mac_config_t *mac_config) +{ + esp_eth_mac_t *ret = NULL; + emac_w6100_t *emac = NULL; + + ESP_GOTO_ON_FALSE(w6100_config && mac_config, NULL, err, TAG, "Invalid argument"); + + emac = calloc(1, sizeof(emac_w6100_t)); + ESP_GOTO_ON_FALSE(emac, NULL, err, TAG, "No mem for MAC instance"); + + /* w6100 driver is interrupt driven */ + ESP_GOTO_ON_FALSE(w6100_config->int_gpio_num >= 0, NULL, err, TAG, "Invalid interrupt gpio number"); + + /* bind methods and attributes */ + emac->sw_reset_timeout_ms = mac_config->sw_reset_timeout_ms; + emac->int_gpio_num = w6100_config->int_gpio_num; + emac->spi_hdl = w6100_config->spi_hdl; + emac->parent.set_mediator = emac_w6100_set_mediator; + emac->parent.init = emac_w6100_init; + emac->parent.deinit = emac_w6100_deinit; + emac->parent.start = emac_w6100_start; + emac->parent.stop = emac_w6100_stop; + emac->parent.del = emac_w6100_del; + emac->parent.write_phy_reg = emac_w6100_write_phy_reg; + emac->parent.read_phy_reg = emac_w6100_read_phy_reg; + emac->parent.set_addr = emac_w6100_set_addr; + emac->parent.get_addr = emac_w6100_get_addr; + emac->parent.set_speed = emac_w6100_set_speed; + emac->parent.set_duplex = emac_w6100_set_duplex; + emac->parent.set_link = emac_w6100_set_link; + emac->parent.set_promiscuous = emac_w6100_set_promiscuous; + emac->parent.set_peer_pause_ability = emac_w6100_set_peer_pause_ability; + emac->parent.enable_flow_ctrl = emac_w6100_enable_flow_ctrl; + emac->parent.transmit = emac_w6100_transmit; + emac->parent.receive = emac_w6100_receive; + + /* create mutex */ + emac->spi_lock = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(emac->spi_lock, NULL, err, TAG, "Create lock failed"); + + /* create w6100 task */ + BaseType_t core_num = tskNO_AFFINITY; + + if (mac_config->flags & ETH_MAC_FLAG_PIN_TO_CORE) + { + core_num = cpu_hal_get_core_id(); + } + + BaseType_t xReturned = xTaskCreatePinnedToCore(emac_w6100_task, "w6100_tsk", mac_config->rx_task_stack_size, emac, + mac_config->rx_task_prio, &emac->rx_task_hdl, core_num); + ESP_GOTO_ON_FALSE(xReturned == pdPASS, NULL, err, TAG, "Create w6100 task failed"); + + return &(emac->parent); + +err: + + if (emac) + { + if (emac->rx_task_hdl) + { + vTaskDelete(emac->rx_task_hdl); + } + + if (emac->spi_lock) + { + vSemaphoreDelete(emac->spi_lock); + } + + free(emac); + } + + return ret; +} + +//////////////////////////////////////// + diff --git a/src/w6100/esp_eth/esp_eth_phy_w6100.c b/src/w6100/esp_eth/esp_eth_phy_w6100.c new file mode 100644 index 0000000..0ff4e3c --- /dev/null +++ b/src/w6100/esp_eth/esp_eth_phy_w6100.c @@ -0,0 +1,379 @@ +/**************************************************************************************************************************** + esp_eth_phy_w6100.c + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W6100 v1.5.2 + *****************************************************************************************************************************/ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//////////////////////////////////////// + +#include +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "esp_eth.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_rom_gpio.h" +#include "esp_rom_sys.h" +#include "w6100.h" + +//////////////////////////////////////// + +static const char *TAG = "w6100.phy"; + +//////////////////////////////////////// + +/***************Vendor Specific Register***************/ +/** + @brief PHYCFGR(PHY Configuration Register) + +*/ +typedef union +{ + struct + { + uint8_t link: 1; /*!< Link status */ + uint8_t speed: 1; /*!< Speed status */ + uint8_t duplex: 1; /*!< Duplex status */ + uint8_t opmode: 3; /*!< Operation mode */ + uint8_t opsel: 1; /*!< Operation select */ + uint8_t reset: 1; /*!< Reset, when this bit is '0', PHY will get reset */ + }; + + uint8_t val; +} phycfg_reg_t; + +//////////////////////////////////////// + +typedef struct +{ + esp_eth_phy_t parent; + esp_eth_mediator_t *eth; + int addr; + uint32_t reset_timeout_ms; + uint32_t autonego_timeout_ms; + eth_link_t link_status; + int reset_gpio_num; +} phy_w6100_t; + +//////////////////////////////////////// + +static esp_err_t w6100_update_link_duplex_speed(phy_w6100_t *w6100) +{ + esp_err_t ret = ESP_OK; + + esp_eth_mediator_t *eth = w6100->eth; + eth_speed_t speed = ETH_SPEED_10M; + eth_duplex_t duplex = ETH_DUPLEX_HALF; + phycfg_reg_t phycfg; + + ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, w6100->addr, W6100_REG_PHYCFGR, (uint32_t *) & (phycfg.val)), err, TAG, + "Read PHYCFG failed"); + eth_link_t link = phycfg.link ? ETH_LINK_UP : ETH_LINK_DOWN; + + /* check if link status changed */ + if (w6100->link_status != link) + { + /* when link up, read negotiation result */ + if (link == ETH_LINK_UP) + { + if (phycfg.speed) + { + //Inverted compared to W5500 + speed = ETH_SPEED_10M; + } + else + { + //Inverted compared to W5500 + speed = ETH_SPEED_100M; + } + + if (phycfg.duplex) + { + duplex = ETH_DUPLEX_FULL; + } + else + { + duplex = ETH_DUPLEX_HALF; + } + + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *)speed), err, TAG, "Change speed failed"); + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *)duplex), err, TAG, "Change duplex failed"); + } + + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)link), err, TAG, "Change link failed"); + w6100->link_status = link; + } + + return ESP_OK; + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth) +{ + esp_err_t ret = ESP_OK; + + ESP_GOTO_ON_FALSE(eth, ESP_ERR_INVALID_ARG, err, TAG, "Can't set mediator to null"); + phy_w6100_t *w6100 = __containerof(phy, phy_w6100_t, parent); + w6100->eth = eth; + + return ESP_OK; + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_get_link(esp_eth_phy_t *phy) +{ + esp_err_t ret = ESP_OK; + + phy_w6100_t *w6100 = __containerof(phy, phy_w6100_t, parent); + + /* Updata information about link, speed, duplex */ + ESP_GOTO_ON_ERROR(w6100_update_link_duplex_speed(w6100), err, TAG, "Update link duplex speed failed"); + + return ESP_OK; + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_reset(esp_eth_phy_t *phy) +{ + esp_err_t ret = ESP_OK; + + phy_w6100_t *w6100 = __containerof(phy, phy_w6100_t, parent); + + w6100->link_status = ETH_LINK_DOWN; + esp_eth_mediator_t *eth = w6100->eth; + + phycfg_reg_t phycfg; + ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, w6100->addr, W6100_REG_PHYCFGR, (uint32_t *) & (phycfg.val)), err, TAG, + "Read PHYCFG failed"); + + phycfg.reset = 0; // set to '0' will reset internal PHY + ESP_GOTO_ON_ERROR(eth->phy_reg_write(eth, w6100->addr, W6100_REG_PHYCFGR, phycfg.val), err, TAG, "Write PHYCFG failed"); + + vTaskDelay(pdMS_TO_TICKS(10)); + + phycfg.reset = 1; // set to '1' after reset + ESP_GOTO_ON_ERROR(eth->phy_reg_write(eth, w6100->addr, W6100_REG_PHYCFGR, phycfg.val), err, TAG, "Write PHYCFG failed"); + + return ESP_OK; + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_reset_hw(esp_eth_phy_t *phy) +{ + phy_w6100_t *w6100 = __containerof(phy, phy_w6100_t, parent); + + // set reset_gpio_num to a negative value can skip hardware reset phy chip + if (w6100->reset_gpio_num >= 0) + { + esp_rom_gpio_pad_select_gpio(w6100->reset_gpio_num); + gpio_set_direction(w6100->reset_gpio_num, GPIO_MODE_OUTPUT); + gpio_set_level(w6100->reset_gpio_num, 0); + esp_rom_delay_us(100); // insert min input assert time + gpio_set_level(w6100->reset_gpio_num, 1); + } + + return ESP_OK; +} + +//////////////////////////////////////// + +static esp_err_t w6100_negotiate(esp_eth_phy_t *phy) +{ + esp_err_t ret = ESP_OK; + + phy_w6100_t *w6100 = __containerof(phy, phy_w6100_t, parent); + + esp_eth_mediator_t *eth = w6100->eth; + + /* in case any link status has changed, let's assume we're in link down status */ + w6100->link_status = ETH_LINK_DOWN; + phycfg_reg_t phycfg; + ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, w6100->addr, W6100_REG_PHYCFGR, (uint32_t *) & (phycfg.val)), err, TAG, + "Read PHYCFG failed"); + + phycfg.opsel = 1; // PHY working mode configured by register + phycfg.opmode = 7; // all capable, auto-negotiation enabled + ESP_GOTO_ON_ERROR(eth->phy_reg_write(eth, w6100->addr, W6100_REG_PHYCFGR, phycfg.val), err, TAG, "Write PHYCFG failed"); + + return ESP_OK; + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_pwrctl(esp_eth_phy_t *phy, bool enable) +{ + // power control is not supported for W6100 internal PHY + return ESP_OK; +} + +//////////////////////////////////////// + +static esp_err_t w6100_set_addr(esp_eth_phy_t *phy, uint32_t addr) +{ + phy_w6100_t *w6100 = __containerof(phy, phy_w6100_t, parent); + w6100->addr = addr; + + return ESP_OK; +} + +//////////////////////////////////////// + +static esp_err_t w6100_get_addr(esp_eth_phy_t *phy, uint32_t *addr) +{ + esp_err_t ret = ESP_OK; + + ESP_GOTO_ON_FALSE(addr, ESP_ERR_INVALID_ARG, err, TAG, "Addr can't be null"); + phy_w6100_t *w6100 = __containerof(phy, phy_w6100_t, parent); + *addr = w6100->addr; + + return ESP_OK; + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_del(esp_eth_phy_t *phy) +{ + phy_w6100_t *w6100 = __containerof(phy, phy_w6100_t, parent); + free(w6100); + + return ESP_OK; +} + +//////////////////////////////////////// + +static esp_err_t w6100_advertise_pause_ability(esp_eth_phy_t *phy, uint32_t ability) +{ + // pause ability advertisement is not supported for W6100 internal PHY + return ESP_OK; +} + +//////////////////////////////////////// + +static esp_err_t w6100_loopback(esp_eth_phy_t *phy, bool enable) +{ + // Loopback is not supported for W6100 internal PHY + return ESP_ERR_NOT_SUPPORTED; +} + +//////////////////////////////////////// + +static esp_err_t w6100_init(esp_eth_phy_t *phy) +{ + esp_err_t ret = ESP_OK; + + /* Power on Ethernet PHY */ + ESP_GOTO_ON_ERROR(w6100_pwrctl(phy, true), err, TAG, "Power control failed"); + + /* Reset Ethernet PHY */ + ESP_GOTO_ON_ERROR(w6100_reset(phy), err, TAG, "Reset failed"); + + return ESP_OK; + +err: + return ret; +} + +//////////////////////////////////////// + +static esp_err_t w6100_deinit(esp_eth_phy_t *phy) +{ + esp_err_t ret = ESP_OK; + + /* Power off Ethernet PHY */ + ESP_GOTO_ON_ERROR(w6100_pwrctl(phy, false), err, TAG, "Power control failed"); + + return ESP_OK; + +err: + return ret; +} + +//////////////////////////////////////// + +esp_eth_phy_t *esp_eth_phy_new_w6100(const eth_phy_config_t *config) +{ + esp_eth_phy_t *ret = NULL; + + ESP_GOTO_ON_FALSE(config, NULL, err, TAG, "Invalid arguments"); + + phy_w6100_t *w6100 = calloc(1, sizeof(phy_w6100_t)); + ESP_GOTO_ON_FALSE(w6100, NULL, err, TAG, "No mem for PHY instance"); + + /* bind methods and attributes */ + w6100->addr = config->phy_addr; + w6100->reset_timeout_ms = config->reset_timeout_ms; + w6100->reset_gpio_num = config->reset_gpio_num; + w6100->link_status = ETH_LINK_DOWN; + w6100->autonego_timeout_ms = config->autonego_timeout_ms; + w6100->parent.reset = w6100_reset; + w6100->parent.reset_hw = w6100_reset_hw; + w6100->parent.init = w6100_init; + w6100->parent.deinit = w6100_deinit; + w6100->parent.set_mediator = w6100_set_mediator; + w6100->parent.negotiate = w6100_negotiate; + w6100->parent.get_link = w6100_get_link; + w6100->parent.pwrctl = w6100_pwrctl; + w6100->parent.get_addr = w6100_get_addr; + w6100->parent.set_addr = w6100_set_addr; + w6100->parent.advertise_pause_ability = w6100_advertise_pause_ability; + w6100->parent.loopback = w6100_loopback; + w6100->parent.del = w6100_del; + + return &(w6100->parent); + +err: + return ret; +} + +//////////////////////////////////////// + diff --git a/src/w6100/esp_eth/esp_eth_spi_w6100.c b/src/w6100/esp_eth/esp_eth_spi_w6100.c new file mode 100644 index 0000000..2ec53f9 --- /dev/null +++ b/src/w6100/esp_eth/esp_eth_spi_w6100.c @@ -0,0 +1,135 @@ +/**************************************************************************************************************************** + esp_eth_spi_w6100.c + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W6100 v1.5.2 + *****************************************************************************************************************************/ + +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_netif.h" +#include "esp_eth.h" +#include "esp_event.h" +#include "driver/gpio.h" +#include "esp_eth_w6100.h" +#include "driver/spi_master.h" + +#include "esp_log.h" +#include "esp_check.h" + +static const char *TAG = "w6100.spi"; + +/* + // From tools/sdk/esp32/include/esp_eth/include/esp_eth_mac.h + #define ETH_MAC_DEFAULT_CONFIG() \ + { \ + .sw_reset_timeout_ms = 100, \ + .rx_task_stack_size = 2048, \ + .rx_task_prio = 15, \ + .smi_mdc_gpio_num = 23, \ + .smi_mdio_gpio_num = 18, \ + .flags = 0, \ + .interface = EMAC_DATA_INTERFACE_RMII, \ + .clock_config = \ + { \ + .rmii = \ + { \ + .clock_mode = EMAC_CLK_DEFAULT, \ + .clock_gpio = EMAC_CLK_IN_GPIO \ + } \ + } \ + } +*/ + +//////////////////////////////////////// + +// KH for W6100 +#define ETH_W6100_DEFAULT_CONFIG ETH_W5500_DEFAULT_CONFIG + +//////////////////////////////////////// + +esp_eth_mac_t* w6100_new_mac( spi_device_handle_t *spi_handle, int INT_GPIO ) +{ + eth_w6100_config_t w6100_config = ETH_W6100_DEFAULT_CONFIG( *spi_handle ); + w6100_config.int_gpio_num = INT_GPIO; + + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + + //eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + //phy_config.reset_gpio_num = -1; + + mac_config.smi_mdc_gpio_num = -1; // w6100 doesn't have SMI interface + mac_config.smi_mdio_gpio_num = -1; + mac_config.rx_task_prio = 1; + + return esp_eth_mac_new_w6100( &w6100_config, &mac_config ); +} + +//////////////////////////////////////// + +esp_eth_mac_t* w6100_begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPICLOCK_MHZ, + int SPIHOST) +{ + if (ESP_OK != gpio_install_isr_service(0)) + { + ESP_LOGE(TAG, "%s(%d): Error gpio_install_isr_service", __FUNCTION__, __LINE__); + + return NULL; + } + + /* w6100 ethernet driver is based on spi driver */ + spi_bus_config_t buscfg = + { + .miso_io_num = MISO_GPIO, + .mosi_io_num = MOSI_GPIO, + .sclk_io_num = SCLK_GPIO, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + }; + + if ( ESP_OK != spi_bus_initialize( SPIHOST, &buscfg, 1 )) + { + ESP_LOGE(TAG, "%s(%d): Error spi_bus_initialize", __FUNCTION__, __LINE__); + + return NULL; + } + + spi_device_interface_config_t devcfg = + { + .command_bits = 16, + .address_bits = 8, + .mode = 0, + .clock_speed_hz = SPICLOCK_MHZ * 1000 * 1000, + .spics_io_num = CS_GPIO, + .queue_size = 20, + .cs_ena_posttrans = w6100_cal_spi_cs_hold_time(SPICLOCK_MHZ), + }; + + spi_device_handle_t spi_handle = NULL; + + if (ESP_OK != spi_bus_add_device( SPIHOST, &devcfg, &spi_handle )) + { + ESP_LOGE(TAG, "%s(%d): Error spi_bus_add_device", __FUNCTION__, __LINE__); + + return NULL; + } + + return w6100_new_mac( &spi_handle, INT_GPIO ); +} + +//////////////////////////////////////// + diff --git a/src/w6100/esp_eth/esp_eth_w6100.h b/src/w6100/esp_eth/esp_eth_w6100.h new file mode 100644 index 0000000..40fa77f --- /dev/null +++ b/src/w6100/esp_eth/esp_eth_w6100.h @@ -0,0 +1,153 @@ +/**************************************************************************************************************************** + esp_eth_w6100.h + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W6100 v1.5.2 + *****************************************************************************************************************************/ + +// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifndef _ESP_ETH_W6100_H_ +#define _ESP_ETH_W6100_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////// + +#include "esp_eth_phy.h" +#include "esp_eth_mac.h" +#include "driver/spi_master.h" + +//////////////////////////////////////// + +#define CS_HOLD_TIME_MIN_NS 210 + +//////////////////////////////////////// + +/* + // From tools/sdk/esp32/include/esp_eth/include/esp_eth_mac.h + + typedef struct + { + void *spi_hdl; //!< Handle of SPI device driver + int int_gpio_num; //!< Interrupt GPIO number + } eth_w5500_config_t; + + + #define ETH_W6100_DEFAULT_CONFIG(spi_device) \ + { \ + .spi_hdl = spi_device, \ + .int_gpio_num = 4, \ + } + +*/ + +//////////////////////////////////////// + +// KH for W6100 +#define eth_w6100_config_t eth_w5500_config_t + +//////////////////////////////////////// + +/** + @brief Compute amount of SPI bit-cycles the CS should stay active after the transmission + to meet w6100 CS Hold Time specification. + + @param clock_speed_mhz SPI Clock frequency in MHz (valid range is <1, 20>) + @return uint8_t +*/ +static inline uint8_t w6100_cal_spi_cs_hold_time(int clock_speed_mhz) +{ + if (clock_speed_mhz <= 0 || clock_speed_mhz > 20) + { + return 0; + } + + int temp = clock_speed_mhz * CS_HOLD_TIME_MIN_NS; + uint8_t cs_posttrans = temp / 1000; + + if (temp % 1000) + { + cs_posttrans += 1; + } + + return cs_posttrans; +} + +//////////////////////////////////////// + +/** + @brief Create w6100 Ethernet MAC instance + + @param[in] w6100_config: w6100 specific configuration + @param[in] mac_config: Ethernet MAC configuration + + @return + - instance: create MAC instance successfully + - NULL: create MAC instance failed because some error occurred +*/ +esp_eth_mac_t *esp_eth_mac_new_w6100(const eth_w6100_config_t *w6100_config, + const eth_mac_config_t *mac_config); + +//////////////////////////////////////// + +/** + @brief Create a PHY instance of w6100 + + @param[in] config: configuration of PHY + + @return + - instance: create PHY instance successfully + - NULL: create PHY instance failed because some error occurred +*/ +esp_eth_phy_t *esp_eth_phy_new_w6100(const eth_phy_config_t *config); + +//////////////////////////////////////// + +// todo: the below functions should be accessed through ioctl in the future +/** + @brief Set w6100 Duplex mode. It sets Duplex mode first to the PHY and then + MAC is set based on what PHY indicates. + + @param phy w6100 PHY Handle + @param duplex Duplex mode + + @return esp_err_t + - ESP_OK when PHY registers were correctly written. +*/ +esp_err_t w6100_set_phy_duplex(esp_eth_phy_t *phy, eth_duplex_t duplex); + +//////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif // _ESP_ETH_W6100_H_ diff --git a/src/w6100/esp_eth/w6100.h b/src/w6100/esp_eth/w6100.h new file mode 100644 index 0000000..97382cd --- /dev/null +++ b/src/w6100/esp_eth/w6100.h @@ -0,0 +1,199 @@ +/**************************************************************************************************************************** + w6100.h + + For Ethernet shields using ESP32_W6100 (ESP32 + W6100) + + WebServer_ESP32_W6100 is a library for the ESP32 with Ethernet W6100 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W6100 + Licensed under GPLv3 license + + Version: 1.5.2 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.5.2 K Hoang 06/01/2022 Initial coding for ESP32_W6100 (ESP32 + W6100). Sync with WebServer_ESP32_W6100 v1.5.2 + *****************************************************************************************************************************/ + +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifndef _ESP32_ETH_W6100_H_ +#define _ESP32_ETH_W6100_H_ + +//////////////////////////////////////// + +#define W6100_ADDR_OFFSET (16) // Address length +#define W6100_BSB_OFFSET (3) // Block Select Bits offset +#define W6100_RWB_OFFSET (2) // Read Write Bits offset + +//////////////////////////////////////// + +#define W6100_BSB_COM_REG (0x00) // Common Register +#define W6100_BSB_SOCK_REG(s) ((s)*4+1) // Socket Register +#define W6100_BSB_SOCK_TX_BUF(s) ((s)*4+2) // Socket TX Buffer +#define W6100_BSB_SOCK_RX_BUF(s) ((s)*4+3) // Socket RX Buffer + +//////////////////////////////////////// + +#define W6100_ACCESS_MODE_READ (0) // Read Mode +#define W6100_ACCESS_MODE_WRITE (1) // Write Mode + +//////////////////////////////////////// + +#define W6100_SPI_OP_MODE_VDM (0x00) // Variable Data Length Mode (SPI frame is controlled by CS line) +#define W6100_SPI_OP_MODE_FDM_1 (0x01) // Fixed Data Length Mode, 1 Byte Length +#define W6100_SPI_OP_MODE_FDM_2 (0x02) // Fixed Data Length Mode, 2 Bytes Length +#define W6100_SPI_OP_MODE_FDM_4 (0x03) // Fixed Data Length Mode, 4 Bytes Length + +//////////////////////////////////////// + +#define W6100_MAKE_MAP(offset, bsb) ((offset) << W6100_ADDR_OFFSET | (bsb) << W6100_BSB_OFFSET) + +//////////////////////////////////////// + +#define W6100_REG_MR W6100_MAKE_MAP(0x4000, W6100_BSB_COM_REG) // Mode +#define W6100_REG_MAC W6100_MAKE_MAP(0x4120, W6100_BSB_COM_REG) // MAC Address + +#define W6100_REG_IR W6100_MAKE_MAP(0x2100, W6100_BSB_COM_REG) // Interrupt +#define W6100_REG_IMR W6100_MAKE_MAP(0x2104, W6100_BSB_COM_REG) // Interrupt Mask +#define W6100_REG_SIR W6100_MAKE_MAP(0x2101, W6100_BSB_COM_REG) // Socket Interrupt +#define W6100_REG_SIMR W6100_MAKE_MAP(0x2114, W6100_BSB_COM_REG) // Socket Interrupt Mask +#define W6100_REG_RTR W6100_MAKE_MAP(0x4200, W6100_BSB_COM_REG) // Retry Time + +#define W6100_REG_PHYCFGR W6100_MAKE_MAP(0x3000, W6100_BSB_COM_REG) // PHY Configuration +#define W6100_REG_VERSIONR W6100_MAKE_MAP(0x0000, W6100_BSB_COM_REG) // Chip version + +//////////////////////////////////////// + +#define W6100_REG_SOCK_MR(s) W6100_MAKE_MAP(0x0000, W6100_BSB_SOCK_REG(s)) // Socket Mode +#define W6100_REG_SOCK_CR(s) W6100_MAKE_MAP(0x0010, W6100_BSB_SOCK_REG(s)) // Socket Command +#define W6100_REG_SOCK_IR(s) W6100_MAKE_MAP(0x0020, W6100_BSB_SOCK_REG(s)) // Socket Interrupt +#define W6100_REG_SOCK_SR(s) W6100_MAKE_MAP(0x0030, W6100_BSB_SOCK_REG(s)) // Socket Status + +#define W6100_REG_SOCK_RXBUF_SIZE(s) W6100_MAKE_MAP(0x0220, W6100_BSB_SOCK_REG(s)) // Socket Receive Buffer Size +#define W6100_REG_SOCK_TXBUF_SIZE(s) W6100_MAKE_MAP(0x0200, W6100_BSB_SOCK_REG(s)) // Socket Transmit Buffer Size +#define W6100_REG_SOCK_TX_FSR(s) W6100_MAKE_MAP(0x0204, W6100_BSB_SOCK_REG(s)) // Socket TX Free Size + +#define W6100_REG_SOCK_TX_RD(s) W6100_MAKE_MAP(0x0208, W6100_BSB_SOCK_REG(s)) // Socket TX Read Pointer +#define W6100_REG_SOCK_TX_WR(s) W6100_MAKE_MAP(0x020C, W6100_BSB_SOCK_REG(s)) // Socket TX Write Pointer +#define W6100_REG_SOCK_RX_RSR(s) W6100_MAKE_MAP(0x0224, W6100_BSB_SOCK_REG(s)) // Socket RX Received Size +#define W6100_REG_SOCK_RX_RD(s) W6100_MAKE_MAP(0x0228, W6100_BSB_SOCK_REG(s)) // Socket RX Read Pointer +#define W6100_REG_SOCK_RX_WR(s) W6100_MAKE_MAP(0x022C, W6100_BSB_SOCK_REG(s)) // Socket RX Write Pointer + +#define W6100_REG_SOCK_IMR(s) W6100_MAKE_MAP(0x0024, W6100_BSB_SOCK_REG(s)) // Socket Interrupt Mask + +//////////////////////////////////////// + +#define W6100_MEM_SOCK_TX(s,addr) W6100_MAKE_MAP(addr, W6100_BSB_SOCK_TX_BUF(s)) // Socket TX buffer address +#define W6100_MEM_SOCK_RX(s,addr) W6100_MAKE_MAP(addr, W6100_BSB_SOCK_RX_BUF(s)) // Socket RX buffer address + +//////////////////////////////////////// + +#define W6100_MR_RST (1<<7) // Software reset +#define W6100_MR_PB (1<<4) // Ping block (block the response to a ping request) + +//////////////////////////////////////// + +#define W6100_SIMR_SOCK0 (1<<0) // Socket 0 interrupt + +//////////////////////////////////////// + +#define W6100_SMR_MAC_RAW (0x07) // MAC RAW mode +#define W6100_SMR_MAC_FILTER (1<<7) // MAC filter + +//////////////////////////////////////// + +#define W6100_SCR_OPEN (0x01) // Open command +#define W6100_SCR_CLOSE (0x10) // Close command +#define W6100_SCR_SEND (0x20) // Send command +#define W6100_SCR_RECV (0x40) // Recv command + +//////////////////////////////////////// + +#define W6100_SIR_RECV (1<<2) // Receive done +#define W6100_SIR_SEND (1<<4) // Send done + +//////////////////////////////////////// + +#define W6100_CHPLCKR_UNLOCK 0xCE +#define W6100_NETLCKR_UNLOCK 0x3A +#define W6100_PHYLCKR_UNLOCK 0x53 + +//////////////////////////////////////// + +#define SYCR0 0x2004 // System Config Register 0 +#define SYCR1 0x2005 // System Config Register 1 +#define SYSR_W6100 0x2000 // System Status Register + +#define CHPLCKR_W6100 0x41F4 // Chip Lock Register +#define NETLCKR_W6100 0x41F5 // Network Lock Register +#define PHYLCKR_W6100 0x41F6 // PHY Lock Register + +#define VERSIONR_W6100 0x0 // Chip Version Register [RO]=0x6100 +#define CVERSIONR_W6100 0x0002 // Chip Version Register [RO]=0x4661 + +////////////////////////////////////////////////// + +#define W6100_REG_SYCR0 W6100_MAKE_MAP(SYCR0, W6100_BSB_COM_REG) // System Command Register +#define W6100_REG_SYSR_W6100 W6100_MAKE_MAP(SYSR_W6100, W6100_BSB_COM_REG) // System Status Register + +#define W6100_REG_CHPLCKR_W6100 W6100_MAKE_MAP(CHPLCKR_W6100, W6100_BSB_COM_REG) // Chip Lock Register +#define W6100_REG_NETLCKR_W6100 W6100_MAKE_MAP(NETLCKR_W6100, W6100_BSB_COM_REG) // Network Lock Register +#define W6100_REG_PHYLCKR_W6100 W6100_MAKE_MAP(PHYLCKR_W6100, W6100_BSB_COM_REG) // PHY Lock Register + +// Chip Version Register +#define W6100_REG_VERSIONR_W6100 W6100_MAKE_MAP(VERSIONR_W6100, W6100_BSB_COM_REG) +#define W6100_REG_CVERSIONR_W6100 W6100_MAKE_MAP(CVERSIONR_W6100, W6100_BSB_COM_REG) + +//////////////////////////////////////// + +#define W6100_SYSR_CHPL_LOCK (1<<7) +#define W6100_SYSR_CHPL_ULOCK (0<<7) + +//////////////////////////////////////// + +#define W6100_UDP_HEADER_IPV (1<<7) +#define W6100_UDP_HEADER_IPV4 (0<<7) +#define W6100_UDP_HEADER_IPV6 (1<<7) +#define W6100_UDP_HEADER_ALL (1<<6) +#define W6100_UDP_HEADER_MUL (1<<5) +#define W6100_UDP_HEADER_GUA (0<<3) +#define W6100_UDP_HEADER_LLA (1<<3) + +//////////////////////////////////////// + +#define W6100_SLCR_NS (1<<2) +#define W6100_SLCR_RS (1<<1) +#define W6100_SLIR_TIOUT (1<<7) +#define W6100_ICMP6BLK_RA (1<<2) + +//////////////////////////////////////// + +#define W6100_SnESR_TCP4 (0<<2) +#define W6100_SnESR_TCP6 (1<<2) + +#define W6100_SnMR_TCP4 (1<<0) +#define W6100_SnMR_TCPD (13<<0) + +#define W6100_SnPSR_AUTO (0<<0) +#define W6100_SnPSR_LLA (2<<0) +#define W6100_SnPSR_GUA (3<<0) + +//////////////////////////////////////// + +#endif // _ESP32_ETH_W6100_H_ diff --git a/utils/astyle_library.conf b/utils/astyle_library.conf new file mode 100644 index 0000000..8a73bc2 --- /dev/null +++ b/utils/astyle_library.conf @@ -0,0 +1,70 @@ +# Code formatting rules for Arduino libraries, modified from for KH libraries: +# +# https://github.com/arduino/Arduino/blob/master/build/shared/examples_formatter.conf +# + +# astyle --style=allman -s2 -t2 -C -S -xW -Y -M120 -f -p -xg -H -xb -c --xC120 -xL *.h *.cpp *.ino + +--mode=c +--lineend=linux +--style=allman + +# -r or -R +#--recursive + +# -c => Converts tabs into spaces +convert-tabs + +# -s2 => 2 spaces indentation +--indent=spaces=2 + +# -t2 => tab =2 spaces +#--indent=tab=2 + +# -C +--indent-classes + +# -S +--indent-switches + +# -xW +--indent-preproc-block + +# -Y => indent classes, switches (and cases), comments starting at column 1 +--indent-col1-comments + +# -M120 => maximum of 120 spaces to indent a continuation line +--max-continuation-indent=120 + +# -xC120 => max‑code‑length will break a line if the code exceeds # characters +--max-code-length=120 + +# -f => +--break-blocks + +# -p => put a space around operators +--pad-oper + +# -xg => Insert space padding after commas +--pad-comma + +# -H => put a space after if/for/while +pad-header + +# -xb => Break one line headers (e.g. if/for/while) +--break-one-line-headers + +# -c => Converts tabs into spaces +#--convert-tabs + +# if you like one-liners, keep them +#keep-one-line-statements + +# -xV +--attach-closing-while + +#unpad-paren + +# -xp +remove-comment-prefix + diff --git a/utils/restyle.sh b/utils/restyle.sh new file mode 100644 index 0000000..bcd846f --- /dev/null +++ b/utils/restyle.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +for dir in . ; do + find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.ino" \) -exec astyle --suffix=none --options=./utils/astyle_library.conf \{\} \; +done +